1081 lines
35 KiB
PHP
1081 lines
35 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Validation object
|
|
*
|
|
* Standard: PSR-2
|
|
*
|
|
* @link http://www.php-fig.org/psr/psr-2 Full Documentation
|
|
*
|
|
* @package SC\DUPX\U
|
|
*/
|
|
|
|
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
|
|
|
|
use Duplicator\Installer\Core\InstState;
|
|
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
|
|
use Duplicator\Installer\Utils\Log\Log;
|
|
use Duplicator\Installer\Core\Params\PrmMng;
|
|
use Duplicator\Libs\Snap\SnapServer;
|
|
|
|
require_once(DUPX_INIT . '/api/class.cpnl.ctrl.php');
|
|
|
|
class DUPX_Validation_database_service
|
|
{
|
|
/** @var ?self */
|
|
private static $instance;
|
|
/** @var null|false|mysqli */
|
|
private $dbh;
|
|
/** @var bool */
|
|
private $skipOtherTests = false;
|
|
private \DUPX_cPanel_Controller $cpnlAPI;
|
|
/** @var null|false|string */
|
|
private $cpnlToken;
|
|
/** @var null|false|DUPX_cPanelHost */
|
|
private $cpnlConnection;
|
|
/** @var bool */
|
|
private $userCreated = false;
|
|
/** @var bool */
|
|
private $dbCreated = false;
|
|
|
|
/**
|
|
*
|
|
* @return self
|
|
*/
|
|
public static function getInstance()
|
|
{
|
|
if (is_null(self::$instance)) {
|
|
self::$instance = new self();
|
|
}
|
|
|
|
return self::$instance;
|
|
}
|
|
|
|
private function __construct()
|
|
{
|
|
$this->cpnlAPI = new DUPX_cPanel_Controller();
|
|
}
|
|
|
|
/**
|
|
* Get db connetction
|
|
*
|
|
* @return false|mysqli Returns an object which represents the connection to a MySQL Server.
|
|
*/
|
|
public function getDbConnection()
|
|
{
|
|
if (is_null($this->dbh)) {
|
|
$paramsManager = PrmMng::getInstance();
|
|
|
|
$dbName = $paramsManager->getValue(PrmMng::PARAM_DB_NAME);
|
|
if (empty($dbName)) {
|
|
$dbName = null;
|
|
}
|
|
|
|
$this->dbh = DUPX_DB_Functions::getInstance()->dbConnection([
|
|
'dbhost' => $paramsManager->getValue(PrmMng::PARAM_DB_HOST),
|
|
'dbuser' => $paramsManager->getValue(PrmMng::PARAM_DB_USER),
|
|
'dbpass' => $paramsManager->getValue(PrmMng::PARAM_DB_PASS),
|
|
'dbname' => $dbName,
|
|
]);
|
|
|
|
if (empty($this->dbh)) {
|
|
DUPX_DB_Functions::getInstance()->closeDbConnection();
|
|
$this->dbh = false;
|
|
}
|
|
}
|
|
|
|
return $this->dbh;
|
|
}
|
|
|
|
/**
|
|
* Close db connection if is open
|
|
*
|
|
* @return void
|
|
*/
|
|
public function closeDbConnection(): void
|
|
{
|
|
if (!is_null($this->dbh)) {
|
|
mysqli_close($this->dbh);
|
|
$this->dbh = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set skip other tests
|
|
*
|
|
* @param bool $skip if skip other tests
|
|
*
|
|
* @return void
|
|
*/
|
|
public function setSkipOtherTests($skip = true): void
|
|
{
|
|
$this->skipOtherTests = (bool) $skip;
|
|
}
|
|
|
|
/**
|
|
* Get skip other tests
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function skipDatabaseTests()
|
|
{
|
|
return $this->skipOtherTests;
|
|
}
|
|
|
|
/**
|
|
* Get cpanel token
|
|
*
|
|
* @return false|string
|
|
*/
|
|
public function getCpnlToken()
|
|
{
|
|
if (is_null($this->cpnlToken)) {
|
|
try {
|
|
$paramsManager = PrmMng::getInstance();
|
|
$this->cpnlToken = $this->cpnlAPI->create_token(
|
|
$paramsManager->getValue(PrmMng::PARAM_CPNL_HOST),
|
|
$paramsManager->getValue(PrmMng::PARAM_CPNL_USER),
|
|
$paramsManager->getValue(PrmMng::PARAM_CPNL_PASS)
|
|
);
|
|
} catch (Exception | Error $e) {
|
|
Log::logException($e, Log::LV_DEFAULT, 'CPANEL CREATE TOKEN EXCEPTION: ');
|
|
$this->cpnlToken = false;
|
|
}
|
|
}
|
|
|
|
return $this->cpnlToken;
|
|
}
|
|
|
|
/**
|
|
* Get cpanel connection
|
|
*
|
|
* @return false|DUPX_cPanelHost
|
|
*/
|
|
public function getCpnlConnection()
|
|
{
|
|
if (is_null($this->cpnlConnection)) {
|
|
if ($this->getCpnlToken() === false) {
|
|
$this->cpnlConnection = false;
|
|
} else {
|
|
try {
|
|
$this->cpnlConnection = $this->cpnlAPI->connect($this->cpnlToken);
|
|
} catch (Exception | Error $e) {
|
|
Log::logException($e, Log::LV_DEFAULT, 'CPANEL CONNECTION EXCEPTION: ');
|
|
$this->cpnlConnection = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $this->cpnlConnection;
|
|
}
|
|
|
|
/**
|
|
* Cpanel create database user
|
|
*
|
|
* @param ?array<string,mixed> $userResult user result
|
|
*
|
|
* @param-out array<string,mixed> $userResult user result
|
|
*
|
|
* @return bool true if success or false if error
|
|
*/
|
|
public function cpnlCreateDbUser(&$userResult = null): bool
|
|
{
|
|
if ($this->userCreated) {
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
if (!$this->getCpnlConnection()) {
|
|
throw new Exception('Cpanel not connected');
|
|
}
|
|
|
|
$paramsManager = PrmMng::getInstance();
|
|
$userResult = $this->cpnlAPI->create_db_user(
|
|
$this->cpnlToken,
|
|
$paramsManager->getValue(PrmMng::PARAM_DB_USER),
|
|
$paramsManager->getValue(PrmMng::PARAM_DB_PASS)
|
|
);
|
|
} catch (Exception | Error $e) {
|
|
$userResult['status'] = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'CPANEL CREATE DB USER EXCEPTION: ');
|
|
return false;
|
|
}
|
|
|
|
if ($userResult['status'] === true) {
|
|
$this->userCreated = true;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Database exists
|
|
*
|
|
* @param ?string $errorMessage error message
|
|
*
|
|
* @return bool true if exists or false if not exists
|
|
*/
|
|
public function databaseExists(&$errorMessage = null)
|
|
{
|
|
try {
|
|
$result = true;
|
|
|
|
if (!$this->getDbConnection()) {
|
|
throw new Exception('Database not connected');
|
|
}
|
|
|
|
$paramsManager = PrmMng::getInstance();
|
|
if (mysqli_select_db($this->dbh, $paramsManager->getValue(PrmMng::PARAM_DB_NAME)) !== true) {
|
|
$errorMessage = mysqli_error($this->dbh);
|
|
$result = false;
|
|
}
|
|
} catch (Exception | Error $e) {
|
|
$errorMessage = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE SELECT EXCEPTION: ');
|
|
$result = false;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Create database
|
|
*
|
|
* @param ?string $errorMessage error message
|
|
*
|
|
* @return bool true if success or false if error
|
|
*/
|
|
public function createDatabase(&$errorMessage = null): bool
|
|
{
|
|
if ($this->dbCreated) {
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
$result = true;
|
|
|
|
if (!$this->getDbConnection()) {
|
|
throw new Exception('Database not connected');
|
|
}
|
|
|
|
$dbName = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME);
|
|
|
|
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_VIEW_MODE)) {
|
|
case 'basic':
|
|
$query = 'CREATE DATABASE `' . mysqli_real_escape_string($this->dbh, $dbName) . '`';
|
|
if (DUPX_DB::mysqli_query($this->dbh, $query) === false) {
|
|
$errorMessage = mysqli_error($this->dbh);
|
|
$result = false;
|
|
}
|
|
|
|
if ($result && $this->databaseExists() === false) {
|
|
$errorMessage = 'Can\'t select database after creation';
|
|
$result = false;
|
|
}
|
|
break;
|
|
case 'cpnl':
|
|
$result = $this->cpnlAPI->create_db($this->cpnlToken, $dbName);
|
|
if ($result['status'] !== true) {
|
|
$errorMessage = $result['status'];
|
|
$result = false;
|
|
}
|
|
break;
|
|
default:
|
|
$result = false;
|
|
$errorMessage = 'Invalid db view mode';
|
|
break;
|
|
}
|
|
} catch (Exception | Error $e) {
|
|
$errorMessage = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE CREATE EXCEPTION: ');
|
|
$result = false;
|
|
}
|
|
|
|
if ($result) {
|
|
$this->dbCreated = true;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is database created
|
|
*
|
|
* @return bool true if created or false if not created
|
|
*/
|
|
public function isDatabaseCreated()
|
|
{
|
|
return $this->dbCreated;
|
|
}
|
|
|
|
/**
|
|
* Clean up database
|
|
*
|
|
* @param ?string $errorMessage error message
|
|
*
|
|
* @return bool true if success or false if error
|
|
*/
|
|
public function cleanUpDatabase(&$errorMessage = null)
|
|
{
|
|
if (!$this->dbCreated) {
|
|
return true;
|
|
}
|
|
|
|
$result = true;
|
|
|
|
try {
|
|
$dbName = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME);
|
|
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_VIEW_MODE)) {
|
|
case 'basic':
|
|
//DELETE DB
|
|
if (DUPX_DB::mysqli_query($this->dbh, "DROP DATABASE IF EXISTS `" . mysqli_real_escape_string($this->dbh, $dbName) . "`") === false) {
|
|
$errorMessage = mysqli_error($this->dbh);
|
|
$result = false;
|
|
}
|
|
break;
|
|
case 'cpnl':
|
|
//DELETE DB
|
|
$result = $this->cpnlAPI->delete_db($this->cpnlToken, $dbName);
|
|
if ($result['status'] !== true) {
|
|
$errorMessage = $result['status'];
|
|
$result = false;
|
|
}
|
|
break;
|
|
default:
|
|
$errorMessage = 'Invalid db view mode';
|
|
$result = false;
|
|
break;
|
|
}
|
|
} catch (Exception | Error $e) {
|
|
$errorMessage = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE CLEANUP EXCEPTION: ');
|
|
$result = false;
|
|
}
|
|
|
|
if ($result) {
|
|
$this->dbCreated = false;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Is user created
|
|
*
|
|
* @return bool true if created or false if not created
|
|
*/
|
|
public function isUserCreated()
|
|
{
|
|
return $this->userCreated;
|
|
}
|
|
|
|
/**
|
|
* Clean up user
|
|
*
|
|
* @param ?string $errorMessage error message
|
|
*
|
|
* @return bool true if success or false if error
|
|
*/
|
|
public function cleanUpUser(&$errorMessage = null)
|
|
{
|
|
if (!$this->userCreated) {
|
|
return true;
|
|
}
|
|
|
|
$result = true;
|
|
|
|
try {
|
|
$dbUser = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_USER);
|
|
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_VIEW_MODE)) {
|
|
case 'cpnl':
|
|
//DELETE DB USER
|
|
$result = $this->cpnlAPI->delete_db_user($this->cpnlToken, $dbUser);
|
|
if ($result['status'] !== true) {
|
|
$errorMessage = $result['status'];
|
|
$result = false;
|
|
}
|
|
break;
|
|
case 'basic':
|
|
default:
|
|
$result = false;
|
|
$errorMessage = 'Invalid db view mode';
|
|
break;
|
|
}
|
|
} catch (Exception | Error $e) {
|
|
$errorMessage = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE USER CLEANUP EXCEPTION: ');
|
|
$result = false;
|
|
}
|
|
|
|
if ($result) {
|
|
$this->userCreated = false;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get list of databases
|
|
*
|
|
* @return string[] list of databases
|
|
*/
|
|
public function getDatabases(): array
|
|
{
|
|
if (!$this->getDbConnection()) {
|
|
return [];
|
|
}
|
|
|
|
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_VIEW_MODE)) {
|
|
case 'basic':
|
|
$dbUser = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_USER);
|
|
$host_user = substr_replace($dbUser, '', strpos($dbUser, '_'));
|
|
break;
|
|
case 'cpnl':
|
|
$host_user = PrmMng::getInstance()->getValue(PrmMng::PARAM_CPNL_USER);
|
|
break;
|
|
default:
|
|
return [];
|
|
}
|
|
return DUPX_DB::getDatabases($this->dbh, $host_user);
|
|
}
|
|
|
|
/**
|
|
* Get list of tables that are affect by the DB action
|
|
*
|
|
* @param ?string $dbAction Adb action, if null get param db action
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public function getDBActionAffectedTables($dbAction = null): array
|
|
{
|
|
if ($dbAction === null) {
|
|
$dbAction = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_ACTION);
|
|
}
|
|
|
|
$affectedTables = [];
|
|
$excludeTables = DUPX_DB_Functions::getExcludedTables();
|
|
$escapedDbName = mysqli_real_escape_string($this->dbh, PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME));
|
|
$allTables = DUPX_DB::queryColumnToArray($this->dbh, 'SHOW TABLES FROM `' . $escapedDbName . '`');
|
|
|
|
switch ($dbAction) {
|
|
case DUPX_DBInstall::DBACTION_EMPTY:
|
|
case DUPX_DBInstall::DBACTION_RENAME:
|
|
$affectedTables = array_diff($allTables, $excludeTables);
|
|
break;
|
|
case DUPX_DBInstall::DBACTION_REMOVE_ONLY_TABLES:
|
|
$affectedTables = array_intersect(
|
|
DUPX_DB_Tables::getInstance()->getNewTablesNames(),
|
|
array_diff($allTables, $excludeTables)
|
|
);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return $affectedTables;
|
|
}
|
|
|
|
/**
|
|
* Get number of tables that are affect by the DB action
|
|
*
|
|
* @param ?string $dbAction Adb action, if null get param db action
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getDBActionAffectedTablesCount($dbAction = null): int
|
|
{
|
|
$isCreateNewDatabase = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_ACTION) == DUPX_DBInstall::DBACTION_CREATE;
|
|
return ($isCreateNewDatabase) ? 0 : count($this->getDBActionAffectedTables($dbAction));
|
|
}
|
|
|
|
|
|
/**
|
|
* Check database visibility
|
|
*
|
|
* @param ?string $errorMessage error message
|
|
*
|
|
* @return bool true if success or false if error
|
|
*/
|
|
public function checkDbVisibility(&$errorMessage = null)
|
|
{
|
|
$result = true;
|
|
|
|
try {
|
|
if (!$this->getDbConnection()) {
|
|
throw new Exception('Database not connected');
|
|
}
|
|
|
|
$dbName = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME);
|
|
$dbUser = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_USER);
|
|
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_VIEW_MODE)) {
|
|
case 'basic':
|
|
$result = $this->databaseExists($errorMessage);
|
|
break;
|
|
case 'cpnl':
|
|
$result = $this->cpnlAPI->is_user_in_db($this->cpnlToken, $dbName, $dbUser);
|
|
if ($result['status'] !== true) {
|
|
$result = $this->cpnlAPI->assign_db_user($this->cpnlToken, $dbName, $dbUser);
|
|
if ($result['status'] !== true) {
|
|
$errorMessage = $result['status'];
|
|
$result = false;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
$errorMessage = 'Invalid db view mode';
|
|
$result = false;
|
|
break;
|
|
}
|
|
} catch (Exception | Error $e) {
|
|
$errorMessage = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE CHECK VISIBILITY EXCEPTION: ');
|
|
$result = false;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* This is validation test for "Prefix too long". Checks if there are
|
|
* any new table names longer than 64 characters.
|
|
*
|
|
* @param string $errorMessage Will be filled with error message in case when validation test fails
|
|
*
|
|
* @return bool Returns true if validation test passes, false otherwise
|
|
*/
|
|
public function checkDbPrefixTooLong(&$errorMessage = null)
|
|
{
|
|
$result = true;
|
|
$numOfTooLongNewTableNames = count($this->getTooLongNewTableNames());
|
|
if ($numOfTooLongNewTableNames > 0) {
|
|
$errorMessage = "Length of $numOfTooLongNewTableNames table names exceeds limit of 64 after adding prefix.";
|
|
$result = false;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Returns list of new table names for case when importing subsite(s) into a multisite
|
|
*
|
|
* @return string[]
|
|
*/
|
|
protected function getNewTableNamesSiteToMultisite(): array
|
|
{
|
|
if (!InstState::isAddSiteOnMultisite()) {
|
|
return [];
|
|
}
|
|
|
|
$paramsManager = PrmMng::getInstance();
|
|
$origPrefix = DUPX_ArchiveConfig::getInstance()->wp_tableprefix;
|
|
$destPrefix = $paramsManager->getValue(PrmMng::PARAM_DB_TABLE_PREFIX);
|
|
|
|
$tables = \DUPX_DB_Tables::getInstance()->getTables();
|
|
$owrMaps = $paramsManager->getValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING);
|
|
|
|
if (count($owrMaps) == 0) {
|
|
// This should never happen, but if it does just return empty array
|
|
return [];
|
|
}
|
|
|
|
// $overwriteData["nextSubsiteIdAI"] is Auto_increment value of wp_blogs table in multisite.
|
|
// That is id of the first next subsite that will be added/created.
|
|
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
|
|
$targetIdAI = $overwriteData["nextSubsiteIdAI"];
|
|
if ($targetIdAI < 1) {
|
|
// This should never happen, but if it does let's do something
|
|
// that won't cause problems for the user.
|
|
return [];
|
|
}
|
|
$targetIdAI--; // Prepare it so that we can use ++ on it in the next loop
|
|
|
|
$sourceIds = [];
|
|
$targetIds = [];
|
|
foreach ($owrMaps as $owrMap) {
|
|
$sourceIds[] = $owrMap->getSourceId();
|
|
$targetId = $owrMap->getTargetId();
|
|
switch ($targetId) {
|
|
case SiteOwrMap::NEW_SUBSITE_WITH_SLUG:
|
|
case SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN:
|
|
$targetId = ++$targetIdAI;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
$targetIds[] = $targetId;
|
|
}
|
|
|
|
$newTableNames = [];
|
|
$skipTables = DUPX_DB_Functions::getDuplicatorTablesNames($origPrefix);
|
|
|
|
foreach ($tables as $tableOjb) {
|
|
$newName = $tableOjb->getNewName();
|
|
if (strlen($newName) > 0) {
|
|
// In validation phase getNewName will return non-empty string
|
|
// only for some tables with primary prefix that will be imported,
|
|
// for example wp_usermeta and wp_users
|
|
$newTableNames[] = $newName;
|
|
continue;
|
|
}
|
|
|
|
if (in_array($tableOjb->getOriginalName(), $skipTables)) {
|
|
// These tables will be skipped when importing subsites into multisite
|
|
continue;
|
|
}
|
|
|
|
$indexId = array_search($tableOjb->getSubsisteId(), $sourceIds);
|
|
if ($indexId !== false) {
|
|
// This table belongs to one of chosen subsites that we want to import
|
|
$targetId = $targetIds[$indexId];
|
|
$nameWithoutPrefix = $tableOjb->getNameWithoutPrefix();
|
|
if (!$tableOjb->havePrefix()) {
|
|
// Tables that did not have prefix, won't have prefix when they are imported
|
|
$newName = $nameWithoutPrefix;
|
|
} elseif ($targetId == 1) {
|
|
// Tables whose target is subsite 1 don't have suffix
|
|
// appended to prefix in destination database
|
|
$newName = $destPrefix . $nameWithoutPrefix;
|
|
} else {
|
|
$newName = $destPrefix . $targetId . "_" . $nameWithoutPrefix;
|
|
}
|
|
$newTableNames[] = $newName;
|
|
}
|
|
}
|
|
return $newTableNames;
|
|
}
|
|
|
|
/**
|
|
* Returns list of new table names whose length is bigger than 64 limit
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public function getTooLongNewTableNames(): array
|
|
{
|
|
$tooLongNewTableNames = [];
|
|
$newTableNames = [];
|
|
if (InstState::isAddSiteOnMultisite()) {
|
|
$newTableNames = $this->getNewTableNamesSiteToMultisite();
|
|
} else {
|
|
$newTableNames = DUPX_DB_Tables::getInstance()->getNewTablesNames();
|
|
}
|
|
for ($i = 0; $i < count($newTableNames); $i++) {
|
|
if (strlen($newTableNames[$i]) > 64) {
|
|
$tooLongNewTableNames[] = $newTableNames[$i];
|
|
}
|
|
}
|
|
return $tooLongNewTableNames;
|
|
}
|
|
|
|
/**
|
|
* Get tables count
|
|
*
|
|
* @param string|null $errorMessage error message
|
|
*
|
|
* @return int|false
|
|
*/
|
|
public function dbTablesCount(&$errorMessage = null)
|
|
{
|
|
$result = true;
|
|
|
|
try {
|
|
if (!$this->getDbConnection()) {
|
|
throw new Exception('Database not connected');
|
|
}
|
|
|
|
$dbName = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME);
|
|
$result = DUPX_DB::countTables($this->dbh, $dbName);
|
|
} catch (Exception | Error $e) {
|
|
$errorMessage = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE TABLES COUNT EXCEPTION: ');
|
|
$result = false;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Database check user permissions
|
|
*
|
|
* @param array<string, int> $perms array of permissions
|
|
* @param string[] $errorMessages array of error messages
|
|
*
|
|
* @return int test result level
|
|
*/
|
|
public function dbCheckUserPerms(&$perms = [], &$errorMessages = []): int
|
|
{
|
|
|
|
$perms = [
|
|
'create' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
'insert' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
'select' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
'update' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
'delete' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
'drop' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
'view' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
'proc' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
'func' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
'trigger' => DUPX_Validation_abstract_item::LV_SKIP,
|
|
];
|
|
|
|
$errorMessages = [];
|
|
try {
|
|
if (!$this->getDbConnection()) {
|
|
throw new Exception('Database not connected');
|
|
}
|
|
|
|
$dbName = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME);
|
|
|
|
if (mysqli_select_db($this->dbh, $dbName) === false) {
|
|
throw new Exception('Can\'t select database ' . $dbName);
|
|
}
|
|
|
|
$tmpTable = '__dupli_temp_' . random_int(1000, 9999) . '_' . date("ymdHis");
|
|
$tmpTableEscaped = '`' . mysqli_real_escape_string($this->dbh, $tmpTable) . '`';
|
|
|
|
if (
|
|
$this->isQueryWorking("CREATE TABLE " . $tmpTableEscaped . " ("
|
|
. "`id` int(11) NOT NULL AUTO_INCREMENT, "
|
|
. "`text` text NOT NULL, "
|
|
. "PRIMARY KEY (`id`))", $errorMessages)
|
|
) {
|
|
$perms['create'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['create'] = DUPX_Validation_abstract_item::LV_FAIL;
|
|
}
|
|
|
|
if ($perms['create']) {
|
|
if ($this->isQueryWorking("INSERT INTO " . $tmpTableEscaped . " (`text`) VALUES ('TEXT-1')", $errorMessages)) {
|
|
$perms['insert'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['insert'] = DUPX_Validation_abstract_item::LV_FAIL;
|
|
}
|
|
|
|
if ($this->isQueryWorking("SELECT COUNT(*) FROM " . $tmpTableEscaped, $errorMessages)) {
|
|
$perms['select'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['select'] = DUPX_Validation_abstract_item::LV_FAIL;
|
|
}
|
|
|
|
if ($this->isQueryWorking("UPDATE " . $tmpTableEscaped . " SET text = 'TEXT-2' WHERE text = 'TEXT-1'", $errorMessages)) {
|
|
$perms['update'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['update'] = DUPX_Validation_abstract_item::LV_FAIL;
|
|
}
|
|
|
|
if ($this->isQueryWorking("DELETE FROM " . $tmpTableEscaped . " WHERE text = 'TEXT-2'", $errorMessages)) {
|
|
$perms['delete'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['delete'] = DUPX_Validation_abstract_item::LV_FAIL;
|
|
}
|
|
|
|
if ($this->isQueryWorking("DROP TABLE IF EXISTS " . $tmpTableEscaped . ";", $errorMessages)) {
|
|
$perms['drop'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['drop'] = DUPX_Validation_abstract_item::LV_FAIL;
|
|
}
|
|
}
|
|
|
|
if ($this->dbHasViews()) {
|
|
if ($this->dbCheckGrants(["CREATE VIEW"], $errorMessages)) {
|
|
$perms['view'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['view'] = DUPX_Validation_abstract_item::LV_HARD_WARNING;
|
|
}
|
|
}
|
|
|
|
if ($this->dbHasProcedures()) {
|
|
if ($this->dbCheckGrants(["CREATE ROUTINE", "ALTER ROUTINE"], $errorMessages)) {
|
|
$perms['proc'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['proc'] = DUPX_Validation_abstract_item::LV_HARD_WARNING;
|
|
}
|
|
}
|
|
|
|
if ($this->dbHasFunctions()) {
|
|
if ($this->dbCheckGrants(["CREATE ROUTINE", "ALTER ROUTINE"], $errorMessages)) {
|
|
$perms['func'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['func'] = DUPX_Validation_abstract_item::LV_HARD_WARNING;
|
|
}
|
|
}
|
|
|
|
if ($this->dbHasTriggers()) {
|
|
if ($this->dbCheckGrants(["TRIGGER"], $errorMessages)) {
|
|
$perms['trigger'] = DUPX_Validation_abstract_item::LV_PASS;
|
|
} else {
|
|
$perms['trigger'] = DUPX_Validation_abstract_item::LV_SOFT_WARNING;
|
|
}
|
|
}
|
|
} catch (Exception | Error $e) {
|
|
$errorMessages[] = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE CHECK USER PERMS EXCEPTION: ');
|
|
}
|
|
|
|
return min($perms);
|
|
}
|
|
|
|
/**
|
|
* Is the query working?
|
|
*
|
|
* @param string $query The SQL query
|
|
* @param string[] $errorMessages Optionally you can capture the errors in this array
|
|
*
|
|
* @return boolean returns true if running the query did not fail
|
|
*/
|
|
public function isQueryWorking($query, &$errorMessages = [])
|
|
{
|
|
$result = true;
|
|
$currentError = '';
|
|
|
|
try {
|
|
if (DUPX_DB::mysqli_query($this->dbh, $query) === false) {
|
|
$currentError = mysqli_error($this->dbh);
|
|
$result = false;
|
|
}
|
|
} catch (Exception | Error $e) {
|
|
$currentError = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'TESTING QUERY: ');
|
|
$result = false;
|
|
}
|
|
|
|
if ($result === false) {
|
|
$errorMessages[] = $currentError;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Database check grants
|
|
*
|
|
* @param string[] $grants ist of grants to check
|
|
* @param string[] $errorMessages Optionally you can capture the errors in this array
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function dbCheckGrants($grants, &$errorMessages = []): bool
|
|
{
|
|
try {
|
|
if (($queryResult = DUPX_DB::mysqli_query($this->dbh, "SHOW GRANTS")) === false) {
|
|
$errorMessages[] = mysqli_error($this->dbh);
|
|
return false;
|
|
}
|
|
|
|
$dbName = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME);
|
|
$regex = '/^GRANT\s+(?!USAGE)(.+)\s+ON\s+(\*|`.*?`)\..*$/';
|
|
$matches = null;
|
|
$matchFound = false;
|
|
|
|
while ($row = mysqli_fetch_array($queryResult)) {
|
|
if (!preg_match($regex, $row[0], $matches)) {
|
|
continue;
|
|
}
|
|
|
|
if (
|
|
$matches['2'] === '*' ||
|
|
$matches['2'] === $dbName ||
|
|
$matches['2'] === addcslashes($dbName, '_%')
|
|
) {
|
|
Log::info('SHOW GRANTS CURRENT DB: ' . $row[0], Log::LV_DEBUG);
|
|
$matchFound = true;
|
|
break;
|
|
}
|
|
|
|
//The GRANT queries can have wildcarsds in them which we have to take into account.
|
|
//Turn wildcards into regex expressions and try matching the expression against the DB name.
|
|
$dbNameRegex = preg_replace('/(?<!\\\\)%/', '.*', $matches['2']); // unescaped % becomes .*
|
|
$dbNameRegex = preg_replace('/(?<!\\\\)_/', '.', $dbNameRegex); // unescaped _ becomes .
|
|
if (preg_match($dbNameRegex, $dbName) === 1) {
|
|
Log::info('Grant matched via Wildcard: ' . $dbNameRegex, Log::LV_DEBUG);
|
|
Log::info('SHOW GRANTS CURRENT DB: ' . $row[0], Log::LV_DEBUG);
|
|
$matchFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!$matchFound) {
|
|
Log::info('GRANTS LINE OF CURRENT DB NOT FOUND');
|
|
return false;
|
|
}
|
|
|
|
if ($matches['1'] === 'ALL PRIVILEGES') {
|
|
return true;
|
|
}
|
|
|
|
$userPrivileges = preg_split('/\s*,\s*/', $matches['1']);
|
|
if (($notGrants = array_diff($grants, $userPrivileges))) {
|
|
$message = "The mysql user does not have the '" . implode(', ', $notGrants) . "' permission.";
|
|
Log::info('NO GRANTS: ' . $message);
|
|
$errorMessages[] = $message;
|
|
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
} catch (Exception $e) {
|
|
$errorMessages[] = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE CHECK PERM EXCEPTION: ');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if the database has procedures
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function dbHasProcedures(): bool
|
|
{
|
|
if (DUPX_ArchiveConfig::getInstance()->dbInfo->procCount > 0) {
|
|
Log::info("SOURCE SITE DB HAD PROCEDURES", Log::LV_DEBUG);
|
|
return true;
|
|
}
|
|
|
|
if (($result = DUPX_DB::mysqli_query($this->dbh, "SHOW PROCEDURE STATUS"))) {
|
|
if (mysqli_num_rows($result) > 0) {
|
|
Log::info("INSTALL SITE HAS PROCEDURES", Log::LV_DEBUG);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if the database has functions
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function dbHasFunctions(): bool
|
|
{
|
|
if (DUPX_ArchiveConfig::getInstance()->dbInfo->funcCount > 0) {
|
|
Log::info("SOURCE SITE DB HAD FUNCTIONS", Log::LV_DEBUG);
|
|
return true;
|
|
}
|
|
|
|
if (($result = DUPX_DB::mysqli_query($this->dbh, "SHOW FUNCTION STATUS"))) {
|
|
if (mysqli_num_rows($result) > 0) {
|
|
Log::info("INSTALL SITE HAS FUNCTIONS", Log::LV_DEBUG);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if the database has triggers
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function dbHasTriggers(): bool
|
|
{
|
|
if (($result = DUPX_DB::mysqli_query($this->dbh, "SHOW TRIGGERS"))) {
|
|
if (mysqli_num_rows($result) > 0) {
|
|
Log::info("INSTALL SITE HAS TRIGGERS", Log::LV_DEBUG);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if the database has views
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function dbHasViews(): bool
|
|
{
|
|
if (DUPX_ArchiveConfig::getInstance()->dbInfo->viewCount > 0) {
|
|
Log::info("SOURCE SITE DB HAD VIEWS", Log::LV_DEBUG);
|
|
return true;
|
|
}
|
|
|
|
if (($result = DUPX_DB::mysqli_query($this->dbh, "SHOW FULL TABLES WHERE Table_Type = 'VIEW'"))) {
|
|
if (mysqli_num_rows($result) > 0) {
|
|
Log::info("INSTALL SITE HAS VIEWS", Log::LV_DEBUG);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if database gtid mode is enabled
|
|
*
|
|
* @param string[] $errorMessages Optionally you can capture the errors in this array
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function dbGtidModeEnabled(&$errorMessages = []): bool
|
|
{
|
|
if (($result = DUPX_DB::mysqli_query($this->dbh, "SHOW GLOBAL VARIABLES LIKE 'gtid\_mode'")) === false) {
|
|
return false;
|
|
}
|
|
|
|
if ($result->num_rows > 0 && ($row = mysqli_fetch_array($result, MYSQLI_NUM)) !== false && strtolower($row[1]) === 'on') {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check case sensitive tables value
|
|
*
|
|
* @param string $errorMessage error message
|
|
*
|
|
* @return int<-1, max>
|
|
*/
|
|
public function caseSensitiveTablesValue($errorMessage = ''): int
|
|
{
|
|
try {
|
|
if (!$this->getDbConnection()) {
|
|
throw new Exception('Database not connected');
|
|
}
|
|
|
|
if (($lowerCaseTableNames = DUPX_DB::getVariable($this->dbh, 'lower_case_table_names')) === null) {
|
|
if (SnapServer::isWindows()) {
|
|
$lowerCaseTableNames = 1;
|
|
} elseif (SnapServer::isOSX()) {
|
|
$lowerCaseTableNames = 2;
|
|
} else {
|
|
$lowerCaseTableNames = 0;
|
|
}
|
|
}
|
|
|
|
$result = $lowerCaseTableNames;
|
|
} catch (Exception | Error $e) {
|
|
$errorMessage = $e->getMessage();
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE CHECK CHARSET EXCEPTION: ');
|
|
$result = -1;
|
|
}
|
|
|
|
return (int) $result;
|
|
}
|
|
|
|
/**
|
|
* Get user resources
|
|
*
|
|
* @return false|mixed[]
|
|
*/
|
|
public function getUserResources()
|
|
{
|
|
try {
|
|
$host = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_HOST);
|
|
$user = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_USER);
|
|
$query = "SELECT max_questions, max_updates, max_connections FROM mysql.user WHERE user = '{$user}' AND host = '{$host}'";
|
|
|
|
if (($result = DUPX_DB::mysqli_query($this->dbh, $query, Log::LV_DEFAULT)) != false && $result->num_rows > 0) {
|
|
return $result->fetch_assoc();
|
|
}
|
|
} catch (Exception $e) {
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE CHECK USER RESOURCE EXCEPTION: ');
|
|
} catch (Error $e) {
|
|
Log::logException($e, Log::LV_DEFAULT, 'DATABASE CHECK USER RESOURCE ERROR: ');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|