diff --git a/ChangeLog b/ChangeLog index 53e2f747b2..c46eae09b6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -120,6 +120,33 @@ phpMyAdmin - ChangeLog - issue #18210 Fixed add replica replication user on MariaDB doesn't work (SQL syntax) - issue #19041 Fix footer.twig gets printed to Binary File Download - issue #19091 Fix to stop processing queries on error +- issue #18241 Fix copy SQL query button on error messages +- issue #17190 Fix an error with SELECT ... FOR UPDATE queries +- issue #19145 Remove duplicate server and lang parameters from links +- issue #19158 Fix an issue with backticks on the query generator +- issue #19174 Fix an issue with column alias with asterisk on the query generator +- issue #19146 Fix column sorting with limit subquery +- issue #19152 Fix the number of lines being ignored in GIS visualization after a search +- issue #19189 Fix issue with column sorting when using 'group by' +- issue #19188 Fix issue with simulated queries reporting syntax errors +- issue #19141 Add cookie prefix '-__Secure-' to cookies to help prevent cookie smuggling +- issue #19218 Fix textarea horizontal resizing with Bootstap theme +- issue #19199 Add support for fractional seconds to current_timestamp() +- issue #19221 Fix query statistics for queries with count(*) +- issue #19203 Fix single quotes and backslashes for the query generator +- issue #19163 Fix queries with IS NULL or IS NOT NULL for the query generator +- issue #19181 Fix query generator support for IN() and NOT IN() +- issue #19167 Fix criteria on column '*' for the query generator +- issue #19213 Fix possible issue when exporting a large data set +- issue #19217 Fix issue when editing a cell of a JSON column +- issue #19244 Add yarn 1.22 to the package.json's packageManager field +- issue #19185 Fix visual issue when a row has only empty cells +- issue #19257 Fix issue when adding an index with an invalid name +- issue #19276 Fix compatibility with Twig 3.12 +- issue #19283 Fix issue when the server starts with skip-innodb option +- issue #19299 Fix charset in procedure's parameter type +- issue #19316 Fix input size for hexadecimal values +- issue #19321 Suppress deprecation message of E_STRICT constant 5.2.1 (2023-02-07) - issue #17522 Fix case where the routes cache file is invalid diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 9072a49d65..515d396c2e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -11919,7 +11919,7 @@ parameters: - message: "#^Parameter \\#1 \\$str of method PhpMyAdmin\\\\DatabaseInterface\\:\\:quoteString\\(\\) expects string, mixed given\\.$#" - count: 1 + count: 2 path: src/Replication/Replication.php - @@ -11938,6 +11938,16 @@ parameters: count: 8 path: src/Replication/ReplicationGui.php + - + message: "#^Cannot access offset 'Replica_IO_Running' on mixed\\.$#" + count: 1 + path: src/Replication/ReplicationGui.php + + - + message: "#^Cannot access offset 'Replica_SQL_Running' on mixed\\.$#" + count: 2 + path: src/Replication/ReplicationGui.php + - message: "#^Cannot access offset 'Slave_IO_Running' on mixed\\.$#" count: 1 diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 64d35f1d4f..f595daed20 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -9581,6 +9581,7 @@ + @@ -9592,6 +9593,7 @@ + @@ -9630,8 +9632,6 @@ - - @@ -13098,6 +13098,7 @@ + @@ -13123,6 +13124,8 @@ + + @@ -14066,6 +14069,7 @@ + diff --git a/resources/js/src/sql.ts b/resources/js/src/sql.ts index 6296e46a48..eda96b949b 100644 --- a/resources/js/src/sql.ts +++ b/resources/js/src/sql.ts @@ -422,7 +422,7 @@ AJAX.registerTeardown('sql.js', function () { $(document).off('click', 'a.delete_row.ajax'); $(document).off('submit', '.bookmarkQueryForm'); $('input#bkm_label').off('input'); - $(document).off('makegrid', '.sqlqueryresults'); + $(document).off('makeGrid', '.sqlqueryresults'); $('#togglequerybox').off('click'); $(document).off('click', '#button_submit_query'); $(document).off('change', '#id_bookmark'); @@ -649,11 +649,11 @@ AJAX.registerOnload('sql.js', function () { }); // end of Copy to Clipboard action /** - * Attach the {@link makegrid} function to a custom event, which will be + * Attach the {@link makeGrid} function to a custom event, which will be * triggered manually everytime the table of results is reloaded * @memberOf jQuery */ - $(document).on('makegrid', '.sqlqueryresults', function () { + $(document).on('makeGrid', '.sqlqueryresults', function () { $('.table_results').each(function () { window.makeGrid(this); }); @@ -887,7 +887,7 @@ AJAX.registerOnload('sql.js', function () { }; } - $('.sqlqueryresults').trigger('makegrid'); + $('.sqlqueryresults').trigger('makeGrid'); $('#togglequerybox').show(); if (typeof data.action_bookmark === 'undefined') { @@ -927,7 +927,7 @@ AJAX.registerOnload('sql.js', function () { var $sqlqueryresults = $form.parents('.sqlqueryresults'); $sqlqueryresults .html(data.message) - .trigger('makegrid'); + .trigger('makeGrid'); highlightSql($sqlqueryresults); }); // end $.post() @@ -1307,7 +1307,7 @@ AJAX.registerOnload('sql.js', function () { /** * create resizable table */ - $('.sqlqueryresults').trigger('makegrid'); + $('.sqlqueryresults').trigger('makeGrid'); /** * Check if there is any saved query diff --git a/resources/js/src/table/select.ts b/resources/js/src/table/select.ts index d42b4bd6d6..d691e147e0 100644 --- a/resources/js/src/table/select.ts +++ b/resources/js/src/table/select.ts @@ -166,7 +166,7 @@ AJAX.registerOnload('table/select.js', function () { $('#sqlqueryresultsouter').html(data.sql_query); } else { // results found $('#sqlqueryresultsouter').html(data.message); - $('.sqlqueryresults').trigger('makegrid'); + $('.sqlqueryresults').trigger('makeGrid'); } $('#tbl_search_form') diff --git a/src/Controllers/Server/BinlogController.php b/src/Controllers/Server/BinlogController.php index e0fccd6d86..ccb315796d 100644 --- a/src/Controllers/Server/BinlogController.php +++ b/src/Controllers/Server/BinlogController.php @@ -31,7 +31,7 @@ final class BinlogController implements InvocableController public function __construct(private readonly ResponseRenderer $response, private readonly DatabaseInterface $dbi) { - $this->binaryLogs = $this->dbi->fetchResult('SHOW MASTER LOGS', 'Log_name'); + $this->binaryLogs = $this->dbi->fetchResult('SHOW BINARY LOGS', 'Log_name'); } public function __invoke(ServerRequest $request): Response diff --git a/src/DatabaseInterface.php b/src/DatabaseInterface.php index 9ddeaede15..661d4bbda4 100644 --- a/src/DatabaseInterface.php +++ b/src/DatabaseInterface.php @@ -1990,6 +1990,12 @@ class DatabaseInterface implements DbalInterface return $this->versionComment; } + /** Whether connection is MySQL */ + public function isMySql(): bool + { + return ! $this->isMariaDb; + } + /** * Whether connection is MariaDB */ diff --git a/src/Dbal/DbalInterface.php b/src/Dbal/DbalInterface.php index 374dbb26b7..b4f0c2fcfe 100644 --- a/src/Dbal/DbalInterface.php +++ b/src/Dbal/DbalInterface.php @@ -578,6 +578,9 @@ interface DbalInterface */ public function getVersionComment(): string; + /** Whether connection is MySQL */ + public function isMySql(): bool; + /** * Whether connection is MariaDB */ diff --git a/src/Error/Error.php b/src/Error/Error.php index 6cee9bb01c..a750701ff9 100644 --- a/src/Error/Error.php +++ b/src/Error/Error.php @@ -40,7 +40,6 @@ use const E_ERROR; use const E_NOTICE; use const E_PARSE; use const E_RECOVERABLE_ERROR; -use const E_STRICT; use const E_USER_DEPRECATED; use const E_USER_ERROR; use const E_USER_NOTICE; @@ -271,7 +270,7 @@ class Error extends Message E_USER_ERROR => 'User Error', E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice', - E_STRICT => 'Runtime Notice', + 2048 => 'Runtime Notice', // E_STRICT E_DEPRECATED => 'Deprecation Notice', E_USER_DEPRECATED => 'Deprecation Notice', E_RECOVERABLE_ERROR => 'Catchable Fatal Error', @@ -284,7 +283,7 @@ class Error extends Message default => MessageType::Error, E_NOTICE, E_USER_NOTICE, - E_STRICT, + 2048, // E_STRICT E_DEPRECATED, E_USER_DEPRECATED => MessageType::Notice, }; diff --git a/src/Error/ErrorHandler.php b/src/Error/ErrorHandler.php index dc452b52d6..aafa4f3097 100644 --- a/src/Error/ErrorHandler.php +++ b/src/Error/ErrorHandler.php @@ -33,7 +33,6 @@ use const E_ERROR; use const E_NOTICE; use const E_PARSE; use const E_RECOVERABLE_ERROR; -use const E_STRICT; use const E_USER_DEPRECATED; use const E_USER_ERROR; use const E_USER_NOTICE; @@ -283,7 +282,7 @@ class ErrorHandler } switch ($error->getErrorNumber()) { - case E_STRICT: + case 2048: // E_STRICT case E_DEPRECATED: case E_NOTICE: case E_WARNING: diff --git a/src/Menu.php b/src/Menu.php index 70c191ae93..ee76af24f3 100644 --- a/src/Menu.php +++ b/src/Menu.php @@ -424,7 +424,7 @@ class Menu if (SessionCache::has('binary_logs')) { $binaryLogs = SessionCache::get('binary_logs'); } else { - $binaryLogs = $this->dbi->fetchResult('SHOW MASTER LOGS', 'Log_name'); + $binaryLogs = $this->dbi->fetchResult('SHOW BINARY LOGS', 'Log_name'); SessionCache::set('binary_logs', $binaryLogs); } diff --git a/src/Query/Compatibility.php b/src/Query/Compatibility.php index f89addaddf..fd8d714ee2 100644 --- a/src/Query/Compatibility.php +++ b/src/Query/Compatibility.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Query; use PhpMyAdmin\DatabaseInterface; +use PhpMyAdmin\Dbal\DbalInterface; use PhpMyAdmin\Util; use function array_keys; @@ -260,4 +261,18 @@ class Compatibility { return $isMariaDb && $version >= 100402 || ! $isMariaDb && $version >= 50706; } + + /** @return non-empty-string */ + public static function getShowBinLogStatusStmt(DbalInterface $dbal): string + { + if ($dbal->isMySql() && $dbal->getVersion() >= 80200) { + return 'SHOW BINARY LOG STATUS'; + } + + if ($dbal->isMariaDB() && $dbal->getVersion() >= 100502) { + return 'SHOW BINLOG STATUS'; + } + + return 'SHOW MASTER STATUS'; + } } diff --git a/src/Replication/Replication.php b/src/Replication/Replication.php index 2a1866f394..d1b5fb3bb3 100644 --- a/src/Replication/Replication.php +++ b/src/Replication/Replication.php @@ -10,6 +10,7 @@ use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Dbal\Connection; use PhpMyAdmin\Dbal\ConnectionType; use PhpMyAdmin\Dbal\ResultInterface; +use PhpMyAdmin\Query\Compatibility; use function explode; use function mb_strtoupper; @@ -68,6 +69,13 @@ class Replication return -1; } + if ( + $this->dbi->isMySql() && $this->dbi->getVersion() >= 80022 + || $this->dbi->isMariaDB() && $this->dbi->getVersion() >= 100501 + ) { + return $this->dbi->tryQuery($action . ' REPLICA ' . $control . ';', $connectionType); + } + return $this->dbi->tryQuery($action . ' SLAVE ' . $control . ';', $connectionType); } @@ -98,16 +106,29 @@ class Replication $this->replicaControl('STOP', null, $connectionType); } - $out = $this->dbi->tryQuery( - 'CHANGE MASTER TO ' . - 'MASTER_HOST=' . $this->dbi->quoteString($host) . ',' . - 'MASTER_PORT=' . $port . ',' . - 'MASTER_USER=' . $this->dbi->quoteString($user) . ',' . - 'MASTER_PASSWORD=' . $this->dbi->quoteString($password) . ',' . - 'MASTER_LOG_FILE=' . $this->dbi->quoteString($pos['File']) . ',' . - 'MASTER_LOG_POS=' . $pos['Position'] . ';', - $connectionType, - ); + if ($this->dbi->isMySql() && $this->dbi->getVersion() >= 80023) { + $out = $this->dbi->tryQuery( + 'CHANGE REPLICATION SOURCE TO ' . + 'SOURCE_HOST=' . $this->dbi->quoteString($host) . ',' . + 'SOURCE_PORT=' . $port . ',' . + 'SOURCE_USER=' . $this->dbi->quoteString($user) . ',' . + 'SOURCE_PASSWORD=' . $this->dbi->quoteString($password) . ',' . + 'SOURCE_LOG_FILE=' . $this->dbi->quoteString($pos['File']) . ',' . + 'SOURCE_LOG_POS=' . $pos['Position'] . ';', + $connectionType, + ); + } else { + $out = $this->dbi->tryQuery( + 'CHANGE MASTER TO ' . + 'MASTER_HOST=' . $this->dbi->quoteString($host) . ',' . + 'MASTER_PORT=' . $port . ',' . + 'MASTER_USER=' . $this->dbi->quoteString($user) . ',' . + 'MASTER_PASSWORD=' . $this->dbi->quoteString($password) . ',' . + 'MASTER_LOG_FILE=' . $this->dbi->quoteString($pos['File']) . ',' . + 'MASTER_LOG_POS=' . $pos['Position'] . ';', + $connectionType, + ); + } if ($start) { $this->replicaControl('START', null, $connectionType); @@ -154,7 +175,12 @@ class Replication */ public function replicaBinLogPrimary(ConnectionType $connectionType): array { - $data = $this->dbi->fetchResult('SHOW MASTER STATUS', null, null, $connectionType); + $data = $this->dbi->fetchResult( + Compatibility::getShowBinLogStatusStmt($this->dbi), + null, + null, + $connectionType, + ); $output = []; if ($data !== []) { diff --git a/src/Replication/ReplicationGui.php b/src/Replication/ReplicationGui.php index b6cc69df56..7081da8d13 100644 --- a/src/Replication/ReplicationGui.php +++ b/src/Replication/ReplicationGui.php @@ -74,7 +74,14 @@ class ReplicationGui ): string { if (! $hasReplicaClearScreen) { $primaryStatusTable = $this->getHtmlForReplicationStatusTable($connection, 'primary', true, false); - $replicas = DatabaseInterface::getInstance()->fetchResult('SHOW SLAVE HOSTS'); + $dbi = DatabaseInterface::getInstance(); + if ($dbi->isMySql() && $dbi->getVersion() >= 80022) { + $replicas = $dbi->fetchResult('SHOW REPLICAS', null, null); + } elseif ($dbi->isMariaDB() && $dbi->getVersion() >= 100501) { + $replicas = $dbi->fetchResult('SHOW REPLICA HOSTS', null, null); + } else { + $replicas = $dbi->fetchResult('SHOW SLAVE HOSTS'); + } $urlParams = $GLOBALS['urlParams']; $urlParams['primary_add_user'] = true; @@ -126,13 +133,34 @@ class ReplicationGui array $serverReplicaReplication, bool $replicaConfigure, ): string { - $serverReplicaMultiReplication = DatabaseInterface::getInstance()->fetchResult('SHOW ALL SLAVES STATUS'); + $dbi = DatabaseInterface::getInstance(); + + $serverReplicaMultiReplication = []; + if ($dbi->isMariaDB() && $dbi->getVersion() >= 100501) { + $serverReplicaMultiReplication = $dbi->fetchResult('SHOW ALL REPLICAS STATUS'); + } elseif ($dbi->isMariaDB()) { + $serverReplicaMultiReplication = $dbi->fetchResult('SHOW ALL SLAVES STATUS'); + } + + $isReplicaIoRunning = false; + $isReplicaSqlRunning = false; + if ($serverReplicaStatus) { $urlParams = $GLOBALS['urlParams']; $urlParams['sr_take_action'] = true; $urlParams['sr_replica_server_control'] = true; - if ($serverReplicaReplication[0]['Slave_IO_Running'] === 'No') { + $isReplicaIoRunning = isset($serverReplicaReplication[0]['Slave_IO_Running']) + && $serverReplicaReplication[0]['Slave_IO_Running'] !== 'No' + || isset($serverReplicaReplication[0]['Replica_IO_Running']) + && $serverReplicaReplication[0]['Replica_SQL_Running'] !== 'No'; + + $isReplicaSqlRunning = isset($serverReplicaReplication[0]['Slave_SQL_Running']) + && $serverReplicaReplication[0]['Slave_SQL_Running'] !== 'No' + || isset($serverReplicaReplication[0]['Replica_SQL_Running']) + && $serverReplicaReplication[0]['Replica_SQL_Running'] !== 'No'; + + if (! $isReplicaIoRunning) { $urlParams['sr_replica_action'] = 'start'; } else { $urlParams['sr_replica_action'] = 'stop'; @@ -141,7 +169,7 @@ class ReplicationGui $urlParams['sr_replica_control_param'] = 'IO_THREAD'; $replicaControlIoLink = Url::getCommon($urlParams, '', false); - if ($serverReplicaReplication[0]['Slave_SQL_Running'] === 'No') { + if (! $isReplicaSqlRunning) { $urlParams['sr_replica_action'] = 'start'; } else { $urlParams['sr_replica_action'] = 'stop'; @@ -150,10 +178,7 @@ class ReplicationGui $urlParams['sr_replica_control_param'] = 'SQL_THREAD'; $replicaControlSqlLink = Url::getCommon($urlParams, '', false); - if ( - $serverReplicaReplication[0]['Slave_IO_Running'] === 'No' - || $serverReplicaReplication[0]['Slave_SQL_Running'] === 'No' - ) { + if (! $isReplicaIoRunning || ! $isReplicaSqlRunning) { $urlParams['sr_replica_action'] = 'start'; } else { $urlParams['sr_replica_action'] = 'stop'; @@ -177,9 +202,6 @@ class ReplicationGui $reconfigurePrimaryLink = Url::getCommon($urlParams, '', false); $replicaStatusTable = $this->getHtmlForReplicationStatusTable($connection, 'replica', true, false); - - $replicaIoRunning = $serverReplicaReplication[0]['Slave_IO_Running'] !== 'No'; - $replicaSqlRunning = $serverReplicaReplication[0]['Slave_SQL_Running'] !== 'No'; } return $this->template->render('server/replication/replica_configuration', [ @@ -188,8 +210,8 @@ class ReplicationGui 'primary_connection' => $connection ?? '', 'server_replica_status' => $serverReplicaStatus, 'replica_status_table' => $replicaStatusTable ?? '', - 'replica_sql_running' => $replicaSqlRunning ?? false, - 'replica_io_running' => $replicaIoRunning ?? false, + 'replica_sql_running' => $isReplicaIoRunning, + 'replica_io_running' => $isReplicaSqlRunning, 'replica_control_full_link' => $replicaControlFullLink ?? '', 'replica_control_reset_link' => $replicaControlResetLink ?? '', 'replica_control_sql_link' => $replicaControlSqlLink ?? '', @@ -258,21 +280,33 @@ class ReplicationGui $replicationInfo->load($connection); $replicationVariables = $replicationInfo->primaryVariables; - $variablesAlerts = null; - $variablesOks = null; + $variablesAlerts = []; + $variablesOks = []; $serverReplication = $replicationInfo->getPrimaryStatus(); if ($type === 'replica') { $replicationVariables = $replicationInfo->replicaVariables; - $variablesAlerts = ['Slave_IO_Running' => 'No', 'Slave_SQL_Running' => 'No']; - $variablesOks = ['Slave_IO_Running' => 'Yes', 'Slave_SQL_Running' => 'Yes']; + $variablesAlerts = [ + 'Slave_IO_Running' => 'No', + 'Slave_SQL_Running' => 'No', + 'Replica_IO_Running' => 'No', + 'Replica_SQL_Running' => 'No', + ]; + $variablesOks = [ + 'Slave_IO_Running' => 'Yes', + 'Slave_SQL_Running' => 'Yes', + 'Replica_IO_Running' => 'Yes', + 'Replica_SQL_Running' => 'Yes', + ]; $serverReplication = $replicationInfo->getReplicaStatus(); } $variables = []; foreach ($replicationVariables as $variable) { - $serverReplicationVariable = isset($serverReplication[0]) - ? $serverReplication[0][$variable] - : ''; + if (! isset($serverReplication[0], $serverReplication[0][$variable])) { + continue; + } + + $serverReplicationVariable = $serverReplication[0][$variable]; $variables[$variable] = ['name' => $variable, 'status' => '', 'value' => $serverReplicationVariable]; @@ -529,7 +563,13 @@ class ReplicationGui { if ($srReplicaAction === 'reset') { $qStop = $this->replication->replicaControl('STOP', null, ConnectionType::User); - $qReset = DatabaseInterface::getInstance()->tryQuery('RESET SLAVE;'); + $dbi = DatabaseInterface::getInstance(); + if ($dbi->isMySql() && $dbi->getVersion() >= 80022 || $dbi->isMariaDB() && $dbi->getVersion() >= 100501) { + $qReset = $dbi->tryQuery('RESET REPLICA;'); + } else { + $qReset = $dbi->tryQuery('RESET SLAVE;'); + } + $qStart = $this->replication->replicaControl('START', null, ConnectionType::User); return $qStop !== false && $qStop !== -1 && $qReset !== false && $qStart !== false && $qStart !== -1; @@ -544,7 +584,12 @@ class ReplicationGui { $dbi = DatabaseInterface::getInstance(); $qStop = $this->replication->replicaControl('STOP', null, ConnectionType::User); - $qSkip = $dbi->tryQuery('SET GLOBAL SQL_SLAVE_SKIP_COUNTER = ' . $srSkipErrorsCount . ';'); + if ($dbi->isMySql() && $dbi->getVersion() >= 80400) { + $qSkip = $dbi->tryQuery('SET GLOBAL SQL_REPLICA_SKIP_COUNTER = ' . $srSkipErrorsCount . ';'); + } else { + $qSkip = $dbi->tryQuery('SET GLOBAL SQL_SLAVE_SKIP_COUNTER = ' . $srSkipErrorsCount . ';'); + } + $qStart = $this->replication->replicaControl('START', null, ConnectionType::User); return $qStop !== false && $qStop !== -1 && $qSkip !== false && $qStart !== false && $qStart !== -1; diff --git a/src/Replication/ReplicationInfo.php b/src/Replication/ReplicationInfo.php index ab4a2f7d83..73c0d10a21 100644 --- a/src/Replication/ReplicationInfo.php +++ b/src/Replication/ReplicationInfo.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Replication; use PhpMyAdmin\DatabaseInterface; +use PhpMyAdmin\Query\Compatibility; use function explode; use function sprintf; @@ -17,17 +18,26 @@ final class ReplicationInfo /** @var string[] */ public array $replicaVariables = [ 'Slave_IO_State', + 'Replica_IO_State', 'Master_Host', + 'Source_Host', 'Master_User', + 'Source_User', 'Master_Port', + 'Source_Port', 'Connect_Retry', 'Master_Log_File', + 'Source_Log_File', 'Read_Master_Log_Pos', + 'Read_Source_Log_Pos', 'Relay_Log_File', 'Relay_Log_Pos', 'Relay_Master_Log_File', + 'Relay_Source_Log_File', 'Slave_IO_Running', + 'Replica_IO_Running', 'Slave_SQL_Running', + 'Replica_SQL_Running', 'Replicate_Do_DB', 'Replicate_Ignore_DB', 'Replicate_Do_Table', @@ -38,17 +48,25 @@ final class ReplicationInfo 'Last_Error', 'Skip_Counter', 'Exec_Master_Log_Pos', + 'Exec_Source_Log_Pos', 'Relay_Log_Space', 'Until_Condition', 'Until_Log_File', 'Until_Log_Pos', 'Master_SSL_Allowed', + 'Source_SSL_Allowed', 'Master_SSL_CA_File', + 'Source_SSL_CA_File', 'Master_SSL_CA_Path', + 'Source_SSL_CA_Path', 'Master_SSL_Cert', + 'Source_SSL_Cert', 'Master_SSL_Cipher', + 'Source_SSL_Cipher', 'Master_SSL_Key', + 'Source_SSL_Key', 'Seconds_Behind_Master', + 'Seconds_Behind_Source', ]; /** @var mixed[] */ @@ -92,7 +110,7 @@ final class ReplicationInfo private function setPrimaryStatus(): void { - $this->primaryStatus = $this->dbi->fetchResult('SHOW MASTER STATUS'); + $this->primaryStatus = $this->dbi->fetchResult(Compatibility::getShowBinLogStatusStmt($this->dbi)); } /** @return mixed[] */ @@ -103,7 +121,14 @@ final class ReplicationInfo private function setReplicaStatus(): void { - $this->replicaStatus = $this->dbi->fetchResult('SHOW SLAVE STATUS'); + if ( + $this->dbi->isMySql() && $this->dbi->getVersion() >= 80022 + || $this->dbi->isMariaDB() && $this->dbi->getVersion() >= 100501 + ) { + $this->replicaStatus = $this->dbi->fetchResult('SHOW REPLICA STATUS'); + } else { + $this->replicaStatus = $this->dbi->fetchResult('SHOW SLAVE STATUS'); + } } /** @return mixed[] */ @@ -114,7 +139,12 @@ final class ReplicationInfo private function setMultiPrimaryStatus(): void { - $this->multiPrimaryStatus = $this->dbi->fetchResult('SHOW ALL SLAVES STATUS'); + $this->multiPrimaryStatus = []; + if ($this->dbi->isMariaDB() && $this->dbi->getVersion() >= 100501) { + $this->multiPrimaryStatus = $this->dbi->fetchResult('SHOW ALL REPLICAS STATUS'); + } elseif ($this->dbi->isMariaDB()) { + $this->multiPrimaryStatus = $this->dbi->fetchResult('SHOW ALL SLAVES STATUS'); + } } private function setDefaultPrimaryConnection(string $connection): void diff --git a/src/Table/Search.php b/src/Table/Search.php index 2b306fdf83..17e226e2fd 100644 --- a/src/Table/Search.php +++ b/src/Table/Search.php @@ -44,7 +44,7 @@ final class Search // (more efficient and this helps prevent a problem in IE // if one of the rows is edited and we come back to the Select results) if (isset($_POST['zoom_submit']) || ! empty($_POST['displayAllColumns'])) { - $sqlQuery .= '* '; + $sqlQuery .= '*'; } else { $columnsToDisplay = $_POST['columnsToDisplay']; $quotedColumns = []; diff --git a/tests/unit/Error/ErrorHandlerTest.php b/tests/unit/Error/ErrorHandlerTest.php index ece16f497a..87871d426a 100644 --- a/tests/unit/Error/ErrorHandlerTest.php +++ b/tests/unit/Error/ErrorHandlerTest.php @@ -19,8 +19,14 @@ use Throwable; use function array_keys; use function array_pop; +use const E_COMPILE_WARNING; +use const E_CORE_WARNING; use const E_ERROR; +use const E_NOTICE; use const E_RECOVERABLE_ERROR; +use const E_STRICT; +use const E_USER_DEPRECATED; +use const E_USER_ERROR; use const E_USER_NOTICE; use const E_USER_WARNING; use const E_WARNING; @@ -131,6 +137,33 @@ class ErrorHandlerTest extends AbstractTestCase ); } + #[DataProvider('addErrorProvider')] + public function testAddError(int $errorNumber, string $expected): void + { + $errorHandler = new ErrorHandler(); + $errorHandler->addError('[em]Error[/em]', $errorNumber, 'error.txt', 15); + $errors = $errorHandler->getCurrentErrors(); + self::assertCount(1, $errors); + $error = array_pop($errors); + self::assertSame($errorNumber, $error->getErrorNumber()); + self::assertSame($expected, $error->getMessage()); + } + + /** @return iterable */ + public static function addErrorProvider(): iterable + { + yield 'E_STRICT' => [@E_STRICT, '[em]Error[/em]']; + yield 'E_NOTICE' => [E_NOTICE, '[em]Error[/em]']; + yield 'E_WARNING' => [E_WARNING, '[em]Error[/em]']; + yield 'E_CORE_WARNING' => [E_CORE_WARNING, '[em]Error[/em]']; + yield 'E_COMPILE_WARNING' => [E_COMPILE_WARNING, '[em]Error[/em]']; + yield 'E_RECOVERABLE_ERROR' => [E_RECOVERABLE_ERROR, '[em]Error[/em]']; + yield 'E_USER_NOTICE' => [E_USER_NOTICE, 'Error']; + yield 'E_USER_WARNING' => [E_USER_WARNING, 'Error']; + yield 'E_USER_ERROR' => [E_USER_ERROR, 'Error']; + yield 'E_USER_DEPRECATED' => [E_USER_DEPRECATED, 'Error']; + } + /** * Test for sliceErrors */ diff --git a/tests/unit/Error/ErrorTest.php b/tests/unit/Error/ErrorTest.php index de50fd5c01..67195e2c38 100644 --- a/tests/unit/Error/ErrorTest.php +++ b/tests/unit/Error/ErrorTest.php @@ -13,6 +13,21 @@ use function preg_match; use function str_replace; use const DIRECTORY_SEPARATOR; +use const E_COMPILE_ERROR; +use const E_COMPILE_WARNING; +use const E_CORE_ERROR; +use const E_CORE_WARNING; +use const E_DEPRECATED; +use const E_ERROR; +use const E_NOTICE; +use const E_PARSE; +use const E_RECOVERABLE_ERROR; +use const E_STRICT; +use const E_USER_DEPRECATED; +use const E_USER_ERROR; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use const E_WARNING; #[CoversClass(Error::class)] class ErrorTest extends AbstractTestCase @@ -146,6 +161,60 @@ class ErrorTest extends AbstractTestCase self::assertStringEndsWith('' . "\n", $actual); } + #[DataProvider('errorLevelProvider')] + public function testGetLevel(int $errorNumber, string $expected): void + { + self::assertSame($expected, (new Error($errorNumber, 'Error', 'error.txt', 15))->getContext()); + } + + /** @return iterable */ + public static function errorLevelProvider(): iterable + { + yield 'internal error' => [0, 'danger']; + yield 'E_ERROR error' => [E_ERROR, 'danger']; + yield 'E_WARNING error' => [E_WARNING, 'danger']; + yield 'E_PARSE error' => [E_PARSE, 'danger']; + yield 'E_NOTICE notice' => [E_NOTICE, 'primary']; + yield 'E_CORE_ERROR error' => [E_CORE_ERROR, 'danger']; + yield 'E_CORE_WARNING error' => [E_CORE_WARNING, 'danger']; + yield 'E_COMPILE_ERROR error' => [E_COMPILE_ERROR, 'danger']; + yield 'E_COMPILE_WARNING error' => [E_COMPILE_WARNING, 'danger']; + yield 'E_USER_ERROR error' => [E_USER_ERROR, 'danger']; + yield 'E_USER_WARNING error' => [E_USER_WARNING, 'danger']; + yield 'E_USER_NOTICE notice' => [E_USER_NOTICE, 'primary']; + yield 'E_STRICT notice' => [@E_STRICT, 'primary']; + yield 'E_DEPRECATED notice' => [E_DEPRECATED, 'primary']; + yield 'E_USER_DEPRECATED notice' => [E_USER_DEPRECATED, 'primary']; + yield 'E_RECOVERABLE_ERROR error' => [E_RECOVERABLE_ERROR, 'danger']; + } + + #[DataProvider('errorTypeProvider')] + public function testGetType(int $errorNumber, string $expected): void + { + self::assertSame($expected, (new Error($errorNumber, 'Error', 'error.txt', 15))->getType()); + } + + /** @return iterable */ + public static function errorTypeProvider(): iterable + { + yield 'internal error' => [0, 'Internal error']; + yield 'E_ERROR error' => [E_ERROR, 'Error']; + yield 'E_WARNING warning' => [E_WARNING, 'Warning']; + yield 'E_PARSE error' => [E_PARSE, 'Parsing Error']; + yield 'E_NOTICE notice' => [E_NOTICE, 'Notice']; + yield 'E_CORE_ERROR error' => [E_CORE_ERROR, 'Core Error']; + yield 'E_CORE_WARNING warning' => [E_CORE_WARNING, 'Core Warning']; + yield 'E_COMPILE_ERROR error' => [E_COMPILE_ERROR, 'Compile Error']; + yield 'E_COMPILE_WARNING warning' => [E_COMPILE_WARNING, 'Compile Warning']; + yield 'E_USER_ERROR error' => [E_USER_ERROR, 'User Error']; + yield 'E_USER_WARNING warning' => [E_USER_WARNING, 'User Warning']; + yield 'E_USER_NOTICE notice' => [E_USER_NOTICE, 'User Notice']; + yield 'E_STRICT notice' => [@E_STRICT, 'Runtime Notice']; + yield 'E_DEPRECATED notice' => [E_DEPRECATED, 'Deprecation Notice']; + yield 'E_USER_DEPRECATED notice' => [E_USER_DEPRECATED, 'Deprecation Notice']; + yield 'E_RECOVERABLE_ERROR error' => [E_RECOVERABLE_ERROR, 'Catchable Fatal Error']; + } + /** * Test for getHtmlTitle */ diff --git a/tests/unit/Query/CompatibilityTest.php b/tests/unit/Query/CompatibilityTest.php index 4398a10ff1..976f7ad4c2 100644 --- a/tests/unit/Query/CompatibilityTest.php +++ b/tests/unit/Query/CompatibilityTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Tests\Query; use PhpMyAdmin\DatabaseInterface; +use PhpMyAdmin\Dbal\DbalInterface; use PhpMyAdmin\Query\Compatibility; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; @@ -59,4 +60,24 @@ class CompatibilityTest extends TestCase 'MariaDB 10.7.0' => [true, true, 100700], ]; } + + #[DataProvider('showBinLogStatusProvider')] + public function testGetShowBinLogStatusStmt(string $serverName, int $version, string $expected): void + { + $dbal = self::createStub(DbalInterface::class); + $dbal->method('isMySql')->willReturn($serverName === 'MySQL'); + $dbal->method('isMariaDB')->willReturn($serverName === 'MariaDB'); + $dbal->method('getVersion')->willReturn($version); + self::assertSame($expected, Compatibility::getShowBinLogStatusStmt($dbal)); + } + + /** @return iterable */ + public static function showBinLogStatusProvider(): iterable + { + yield ['MySQL', 80200, 'SHOW BINARY LOG STATUS']; + yield ['MariaDB', 100502, 'SHOW BINLOG STATUS']; + yield ['MySQL', 80199, 'SHOW MASTER STATUS']; + yield ['MariaDB', 100501, 'SHOW MASTER STATUS']; + yield ['MySQL', 100502, 'SHOW BINARY LOG STATUS']; + } } diff --git a/tests/unit/Stubs/DbiDummy.php b/tests/unit/Stubs/DbiDummy.php index 4822c95a7f..f989504add 100644 --- a/tests/unit/Stubs/DbiDummy.php +++ b/tests/unit/Stubs/DbiDummy.php @@ -384,7 +384,7 @@ class DbiDummy implements DbiExtension 'result' => [['1']], ], [ - 'query' => 'SHOW MASTER LOGS', + 'query' => 'SHOW BINARY LOGS', 'columns' => ['Log_name', 'File_size'], 'result' => [['index1', 100], ['index2', 200]], ], diff --git a/tests/unit/Table/SearchTest.php b/tests/unit/Table/SearchTest.php index a07bbfe74a..7d102646c6 100644 --- a/tests/unit/Table/SearchTest.php +++ b/tests/unit/Table/SearchTest.php @@ -33,14 +33,14 @@ class SearchTest extends AbstractTestCase $_POST['customWhereClause'] = "name='pma'"; self::assertSame( - 'SELECT DISTINCT * FROM `PMA` WHERE name=\'pma\' ORDER BY `name` asc', + 'SELECT DISTINCT * FROM `PMA` WHERE name=\'pma\' ORDER BY `name` asc', $this->search->buildSqlQuery(), ); unset($_POST['customWhereClause']); self::assertSame( - 'SELECT DISTINCT * FROM `PMA` ORDER BY `name` asc', + 'SELECT DISTINCT * FROM `PMA` ORDER BY `name` asc', $this->search->buildSqlQuery(), ); @@ -58,7 +58,7 @@ class SearchTest extends AbstractTestCase 'BETWEEN', ]; - $expected = 'SELECT DISTINCT * FROM `PMA` WHERE `name` != \'value1\'' + $expected = 'SELECT DISTINCT * FROM `PMA` WHERE `name` != \'value1\'' . ' AND `id` > value2 AND `index` IS NULL AND `index2` LIKE \'%value4%\'' . ' AND `index3` REGEXP ^value5$ AND `index4` IN (value6) AND `index5`' . ' BETWEEN value7 AND value8 ORDER BY `name` asc'; @@ -74,14 +74,14 @@ class SearchTest extends AbstractTestCase $_POST['table'] = 'PMA'; self::assertSame( - 'SELECT * FROM `PMA`', + 'SELECT * FROM `PMA`', $this->search->buildSqlQuery(), ); $_POST['customWhereClause'] = '`table` = \'WhereClause\''; self::assertSame( - 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'', + 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'', $this->search->buildSqlQuery(), ); @@ -92,7 +92,7 @@ class SearchTest extends AbstractTestCase $_POST['criteriaColumnTypes'] = ['int(11)', 'int(11)', 'int(11)', 'int(11)']; self::assertSame( - 'SELECT * FROM `PMA` WHERE `b` <= 10 AND `a` = 2 AND `c` IS NULL AND `d` IS NOT NULL', + 'SELECT * FROM `PMA` WHERE `b` <= 10 AND `a` = 2 AND `c` IS NULL AND `d` IS NOT NULL', $this->search->buildSqlQuery(), ); } @@ -103,14 +103,14 @@ class SearchTest extends AbstractTestCase $_POST['table'] = 'PMA'; self::assertSame( - 'SELECT * FROM `PMA`', + 'SELECT * FROM `PMA`', $this->search->buildSqlQuery(), ); $_POST['customWhereClause'] = '`table` = \'WhereClause\''; self::assertSame( - 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'', + 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'', $this->search->buildSqlQuery(), ); @@ -122,7 +122,7 @@ class SearchTest extends AbstractTestCase $_POST['criteriaColumnTypes'] = ['geometry']; self::assertSame( - 'SELECT * FROM `PMA` WHERE Dimension(`b`) = \'1\'', + 'SELECT * FROM `PMA` WHERE Dimension(`b`) = \'1\'', $this->search->buildSqlQuery(), ); } @@ -133,14 +133,14 @@ class SearchTest extends AbstractTestCase $_POST['table'] = 'PMA'; self::assertSame( - 'SELECT * FROM `PMA`', + 'SELECT * FROM `PMA`', $this->search->buildSqlQuery(), ); $_POST['customWhereClause'] = '`table` = \'WhereClause\''; self::assertSame( - 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'', + 'SELECT * FROM `PMA` WHERE `table` = \'WhereClause\'', $this->search->buildSqlQuery(), ); @@ -152,7 +152,7 @@ class SearchTest extends AbstractTestCase $_POST['criteriaColumnTypes'] = ['enum(\'G\', \'PG\', \'PG-13\', \'R\', \'NC-17\')']; self::assertSame( - 'SELECT * FROM `PMA` WHERE `rating` = \'PG-13\'', + 'SELECT * FROM `PMA` WHERE `rating` = \'PG-13\'', $this->search->buildSqlQuery(), ); } @@ -163,14 +163,14 @@ class SearchTest extends AbstractTestCase $_POST['table'] = 'PMA'; self::assertSame( - 'SELECT * FROM `PMA`', + 'SELECT * FROM `PMA`', $this->search->buildSqlQuery(), ); $_POST['customWhereClause'] = ''; self::assertSame( - 'SELECT * FROM `PMA`', + 'SELECT * FROM `PMA`', $this->search->buildSqlQuery(), ); @@ -182,7 +182,7 @@ class SearchTest extends AbstractTestCase $_POST['criteriaColumnTypes'] = ['uuid']; self::assertSame( - "SELECT * FROM `PMA` WHERE `id` = '07ca1fdd-4805-11ed-a4dc-0242ac110002'", + "SELECT * FROM `PMA` WHERE `id` = '07ca1fdd-4805-11ed-a4dc-0242ac110002'", $this->search->buildSqlQuery(), ); } @@ -201,6 +201,6 @@ class SearchTest extends AbstractTestCase $_POST['ajax_request'] = 'true'; $_POST['displayAllColumns'] = 'true'; - self::assertSame('SELECT * FROM `world_cities`', $this->search->buildSqlQuery()); + self::assertSame('SELECT * FROM `world_cities`', $this->search->buildSqlQuery()); } }