3 * Functions for the replication GUI
6 declare(strict_types
=1);
10 use PhpMyAdmin\Query\Utilities
;
12 use function htmlspecialchars
;
13 use function in_array
;
14 use function is_array
;
15 use function mb_strrpos
;
16 use function mb_strtolower
;
17 use function mb_substr
;
19 use function str_replace
;
25 * Functions for the replication GUI
29 /** @var Replication */
36 * @param Replication $replication Replication instance
37 * @param Template $template Template instance
39 public function __construct(Replication
$replication, Template
$template)
41 $this->replication
= $replication;
42 $this->template
= $template;
46 * returns HTML for error message
48 * @return string HTML code
50 public function getHtmlForErrorMessage(): string
53 if (isset($_SESSION['replication']['sr_action_status'], $_SESSION['replication']['sr_action_info'])) {
54 if ($_SESSION['replication']['sr_action_status'] === 'error') {
55 $errorMessage = $_SESSION['replication']['sr_action_info'];
56 $html .= Message
::error($errorMessage)->getDisplay();
57 $_SESSION['replication']['sr_action_status'] = 'unknown';
58 } elseif ($_SESSION['replication']['sr_action_status'] === 'success') {
59 $successMessage = $_SESSION['replication']['sr_action_info'];
60 $html .= Message
::success($successMessage)->getDisplay();
61 $_SESSION['replication']['sr_action_status'] = 'unknown';
69 * returns HTML for master replication
71 * @return string HTML code
73 public function getHtmlForMasterReplication(): string
77 if (! isset($_POST['repl_clear_scr'])) {
78 $masterStatusTable = $this->getHtmlForReplicationStatusTable('master', true, false);
79 $slaves = $dbi->fetchResult('SHOW SLAVE HOSTS', null, null);
81 $urlParams = $GLOBALS['urlParams'];
82 $urlParams['mr_adduser'] = true;
83 $urlParams['repl_clear_scr'] = true;
86 if (isset($_POST['mr_adduser'])) {
87 $masterAddSlaveUser = $this->getHtmlForReplicationMasterAddSlaveUser();
90 return $this->template
->render('server/replication/master_replication', [
91 'clear_screen' => isset($_POST['repl_clear_scr']),
92 'master_status_table' => $masterStatusTable ??
'',
93 'slaves' => $slaves ??
[],
94 'url_params' => $urlParams ??
[],
95 'master_add_user' => isset($_POST['mr_adduser']),
96 'master_add_slave_user' => $masterAddSlaveUser ??
'',
101 * returns HTML for master replication configuration
103 * @return string HTML code
105 public function getHtmlForMasterConfiguration(): string
107 $databaseMultibox = $this->getHtmlForReplicationDbMultibox();
109 return $this->template
->render(
110 'server/replication/master_configuration',
111 ['database_multibox' => $databaseMultibox]
116 * returns HTML for slave replication configuration
118 * @param bool $serverSlaveStatus Whether it is Master or Slave
119 * @param array $serverSlaveReplication Slave replication
121 * @return string HTML code
123 public function getHtmlForSlaveConfiguration(
125 array $serverSlaveReplication
129 $serverSlaveMultiReplication = $dbi->fetchResult(
130 'SHOW ALL SLAVES STATUS'
132 if ($serverSlaveStatus) {
133 $urlParams = $GLOBALS['urlParams'];
134 $urlParams['sr_take_action'] = true;
135 $urlParams['sr_slave_server_control'] = true;
137 if ($serverSlaveReplication[0]['Slave_IO_Running'] === 'No') {
138 $urlParams['sr_slave_action'] = 'start';
140 $urlParams['sr_slave_action'] = 'stop';
143 $urlParams['sr_slave_control_parm'] = 'IO_THREAD';
144 $slaveControlIoLink = Url
::getCommon($urlParams, '');
146 if ($serverSlaveReplication[0]['Slave_SQL_Running'] === 'No') {
147 $urlParams['sr_slave_action'] = 'start';
149 $urlParams['sr_slave_action'] = 'stop';
152 $urlParams['sr_slave_control_parm'] = 'SQL_THREAD';
153 $slaveControlSqlLink = Url
::getCommon($urlParams, '');
156 $serverSlaveReplication[0]['Slave_IO_Running'] === 'No'
157 ||
$serverSlaveReplication[0]['Slave_SQL_Running'] === 'No'
159 $urlParams['sr_slave_action'] = 'start';
161 $urlParams['sr_slave_action'] = 'stop';
164 $urlParams['sr_slave_control_parm'] = null;
165 $slaveControlFullLink = Url
::getCommon($urlParams, '');
167 $urlParams['sr_slave_action'] = 'reset';
168 $slaveControlResetLink = Url
::getCommon($urlParams, '');
170 $urlParams = $GLOBALS['urlParams'];
171 $urlParams['sr_take_action'] = true;
172 $urlParams['sr_slave_skip_error'] = true;
173 $slaveSkipErrorLink = Url
::getCommon($urlParams, '');
175 $urlParams = $GLOBALS['urlParams'];
176 $urlParams['sl_configure'] = true;
177 $urlParams['repl_clear_scr'] = true;
179 $reconfigureMasterLink = Url
::getCommon($urlParams, '');
181 $slaveStatusTable = $this->getHtmlForReplicationStatusTable('slave', true, false);
183 $slaveIoRunning = $serverSlaveReplication[0]['Slave_IO_Running'] !== 'No';
184 $slaveSqlRunning = $serverSlaveReplication[0]['Slave_SQL_Running'] !== 'No';
187 return $this->template
->render('server/replication/slave_configuration', [
188 'server_slave_multi_replication' => $serverSlaveMultiReplication,
189 'url_params' => $GLOBALS['urlParams'],
190 'master_connection' => $_POST['master_connection'] ??
'',
191 'server_slave_status' => $serverSlaveStatus,
192 'slave_status_table' => $slaveStatusTable ??
'',
193 'slave_sql_running' => $slaveSqlRunning ??
false,
194 'slave_io_running' => $slaveIoRunning ??
false,
195 'slave_control_full_link' => $slaveControlFullLink ??
'',
196 'slave_control_reset_link' => $slaveControlResetLink ??
'',
197 'slave_control_sql_link' => $slaveControlSqlLink ??
'',
198 'slave_control_io_link' => $slaveControlIoLink ??
'',
199 'slave_skip_error_link' => $slaveSkipErrorLink ??
'',
200 'reconfigure_master_link' => $reconfigureMasterLink ??
'',
201 'has_slave_configure' => isset($_POST['sl_configure']),
206 * returns HTML code for selecting databases
208 * @return string HTML code
210 public function getHtmlForReplicationDbMultibox(): string
213 foreach ($GLOBALS['dblist']->databases
as $database) {
214 if (Utilities
::isSystemSchema($database)) {
218 $databases[] = $database;
221 return $this->template
->render('server/replication/database_multibox', ['databases' => $databases]);
225 * returns HTML for changing master
227 * @param string $submitName submit button name
229 * @return string HTML code
231 public function getHtmlForReplicationChangeMaster($submitName): string
236 ] = $this->getUsernameHostnameLength();
238 return $this->template
->render('server/replication/change_master', [
239 'server_id' => time(),
240 'username_length' => $usernameLength,
241 'hostname_length' => $hostnameLength,
242 'submit_name' => $submitName,
247 * This function returns html code for table with replication status.
249 * @param string $type either master or slave
250 * @param bool $isHidden if true, then default style is set to hidden,
251 * default value false
252 * @param bool $hasTitle if true, then title is displayed, default true
254 * @return string HTML code
256 public function getHtmlForReplicationStatusTable(
263 $replicationInfo = new ReplicationInfo($dbi);
264 $replicationInfo->load($_POST['master_connection'] ??
null);
266 $replicationVariables = $replicationInfo->primaryVariables
;
267 $variablesAlerts = null;
268 $variablesOks = null;
269 $serverReplication = $replicationInfo->getPrimaryStatus();
270 if ($type === 'slave') {
271 $replicationVariables = $replicationInfo->replicaVariables
;
273 'Slave_IO_Running' => 'No',
274 'Slave_SQL_Running' => 'No',
277 'Slave_IO_Running' => 'Yes',
278 'Slave_SQL_Running' => 'Yes',
280 $serverReplication = $replicationInfo->getReplicaStatus();
284 foreach ($replicationVariables as $variable) {
285 $serverReplicationVariable = is_array($serverReplication) && isset($serverReplication[0])
286 ?
$serverReplication[0][$variable]
289 $variables[$variable] = [
292 'value' => $serverReplicationVariable,
296 isset($variablesAlerts[$variable])
297 && $variablesAlerts[$variable] === $serverReplicationVariable
299 $variables[$variable]['status'] = 'text-danger';
301 isset($variablesOks[$variable])
302 && $variablesOks[$variable] === $serverReplicationVariable
304 $variables[$variable]['status'] = 'text-success';
309 'Replicate_Ignore_DB',
310 'Replicate_Do_Table',
311 'Replicate_Ignore_Table',
312 'Replicate_Wild_Do_Table',
313 'Replicate_Wild_Ignore_Table',
315 if (! in_array($variable, $variablesWrap)) {
319 $variables[$variable]['value'] = str_replace(
322 $serverReplicationVariable
326 return $this->template
->render('server/replication/status_table', [
328 'is_hidden' => $isHidden,
329 'has_title' => $hasTitle,
330 'variables' => $variables,
335 * get the correct username and hostname lengths for this MySQL server
337 * @return array<int,int> username length, hostname length
339 public function getUsernameHostnameLength(): array
343 $fieldsInfo = $dbi->getColumns('mysql', 'user');
344 $usernameLength = 16;
345 $hostnameLength = 41;
346 foreach ($fieldsInfo as $val) {
347 if ($val['Field'] === 'User') {
348 strtok($val['Type'], '()');
350 if (Util
::isInteger($v)) {
351 $usernameLength = (int) $v;
353 } elseif ($val['Field'] === 'Host') {
354 strtok($val['Type'], '()');
356 if (Util
::isInteger($v)) {
357 $hostnameLength = (int) $v;
369 * returns html code to add a replication slave user to the master
371 * @return string HTML code
373 public function getHtmlForReplicationMasterAddSlaveUser(): string
380 ] = $this->getUsernameHostnameLength();
382 if (isset($_POST['username']) && strlen($_POST['username']) === 0) {
383 $GLOBALS['pred_username'] = 'any';
387 if (! empty($_POST['username'])) {
388 $username = $GLOBALS['new_username'] ??
$_POST['username'];
391 $currentUser = $dbi->fetchValue('SELECT USER();');
392 if (! empty($currentUser)) {
393 $userHost = str_replace(
398 mb_strrpos($currentUser, '@') +
1
401 if ($userHost !== 'localhost' && $userHost !== '127.0.0.1') {
402 $thisHost = $userHost;
406 // when we start editing a user, $GLOBALS['pred_hostname'] is not defined
407 if (! isset($GLOBALS['pred_hostname']) && isset($_POST['hostname'])) {
408 switch (mb_strtolower($_POST['hostname'])) {
411 $GLOBALS['pred_hostname'] = 'localhost';
414 $GLOBALS['pred_hostname'] = 'any';
417 $GLOBALS['pred_hostname'] = 'userdefined';
422 return $this->template
->render('server/replication/master_add_slave_user', [
423 'username_length' => $usernameLength,
424 'hostname_length' => $hostnameLength,
425 'has_username' => isset($_POST['username']),
426 'username' => $username,
427 'hostname' => $_POST['hostname'] ??
'',
428 'predefined_username' => $GLOBALS['pred_username'] ??
'',
429 'predefined_hostname' => $GLOBALS['pred_hostname'] ??
'',
430 'this_host' => $thisHost ??
null,
435 * handle control requests
437 public function handleControlRequest(): void
439 if (! isset($_POST['sr_take_action'])) {
445 $messageSuccess = '';
448 if (isset($_POST['slave_changemaster']) && ! $GLOBALS['cfg']['AllowArbitraryServer']) {
449 $_SESSION['replication']['sr_action_status'] = 'error';
450 $_SESSION['replication']['sr_action_info'] = __(
451 'Connection to server is disabled, please enable'
452 . ' $cfg[\'AllowArbitraryServer\'] in phpMyAdmin configuration.'
454 } elseif (isset($_POST['slave_changemaster'])) {
455 $result = $this->handleRequestForSlaveChangeMaster();
456 } elseif (isset($_POST['sr_slave_server_control'])) {
457 $result = $this->handleRequestForSlaveServerControl();
460 switch ($_POST['sr_slave_action']) {
462 $messageSuccess = __('Replication started successfully.');
463 $messageError = __('Error starting replication.');
466 $messageSuccess = __('Replication stopped successfully.');
467 $messageError = __('Error stopping replication.');
470 $messageSuccess = __('Replication resetting successfully.');
471 $messageError = __('Error resetting replication.');
474 $messageSuccess = __('Success.');
475 $messageError = __('Error.');
478 } elseif (isset($_POST['sr_slave_skip_error'])) {
479 $result = $this->handleRequestForSlaveSkipError();
483 $response = Response
::getInstance();
484 if ($response->isAjax()) {
485 $response->setRequestStatus($result);
489 ? Message
::success($messageSuccess)
490 : Message
::error($messageError)
493 Core
::sendHeaderLocation(
494 './index.php?route=/server/replication'
495 . Url
::getCommonRaw($GLOBALS['urlParams'], '&')
504 * handle control requests for Slave Change Master
506 public function handleRequestForSlaveChangeMaster(): bool
511 'username' => $dbi->escapeString($_POST['username']),
512 'pma_pw' => $dbi->escapeString($_POST['pma_pw']),
513 'hostname' => $dbi->escapeString($_POST['hostname']),
514 'port' => (int) $dbi->escapeString($_POST['text_port']),
517 $_SESSION['replication']['m_username'] = $sr['username'];
518 $_SESSION['replication']['m_password'] = $sr['pma_pw'];
519 $_SESSION['replication']['m_hostname'] = $sr['hostname'];
520 $_SESSION['replication']['m_port'] = $sr['port'];
521 $_SESSION['replication']['m_correct'] = '';
522 $_SESSION['replication']['sr_action_status'] = 'error';
523 $_SESSION['replication']['sr_action_info'] = __('Unknown error');
525 // Attempt to connect to the new master server
526 $linkToMaster = $this->replication
->connectToMaster(
533 if (! $linkToMaster) {
534 $_SESSION['replication']['sr_action_status'] = 'error';
535 $_SESSION['replication']['sr_action_info'] = sprintf(
536 __('Unable to connect to master %s.'),
537 htmlspecialchars($sr['hostname'])
540 // Read the current master position
541 $position = $this->replication
->slaveBinLogMaster(DatabaseInterface
::CONNECT_AUXILIARY
);
543 if (empty($position)) {
544 $_SESSION['replication']['sr_action_status'] = 'error';
545 $_SESSION['replication']['sr_action_info'] = __(
546 'Unable to read master log position. '
547 . 'Possible privilege problem on master.'
550 $_SESSION['replication']['m_correct'] = true;
553 ! $this->replication
->slaveChangeMaster(
561 DatabaseInterface
::CONNECT_USER
564 $_SESSION['replication']['sr_action_status'] = 'error';
565 $_SESSION['replication']['sr_action_info'] = __('Unable to change master!');
567 $_SESSION['replication']['sr_action_status'] = 'success';
568 $_SESSION['replication']['sr_action_info'] = sprintf(
569 __('Master server changed successfully to %s.'),
570 htmlspecialchars($sr['hostname'])
576 return $_SESSION['replication']['sr_action_status'] === 'success';
580 * handle control requests for Slave Server Control
582 public function handleRequestForSlaveServerControl(): bool
586 if (empty($_POST['sr_slave_control_parm'])) {
587 $_POST['sr_slave_control_parm'] = null;
590 if ($_POST['sr_slave_action'] === 'reset') {
591 $qStop = $this->replication
->slaveControl('STOP', null, DatabaseInterface
::CONNECT_USER
);
592 $qReset = $dbi->tryQuery('RESET SLAVE;');
593 $qStart = $this->replication
->slaveControl('START', null, DatabaseInterface
::CONNECT_USER
);
595 $result = $qStop !== false && $qStop !== -1 &&
596 $qReset !== false && $qReset !== -1 &&
597 $qStart !== false && $qStart !== -1;
599 $qControl = $this->replication
->slaveControl(
600 $_POST['sr_slave_action'],
601 $_POST['sr_slave_control_parm'],
602 DatabaseInterface
::CONNECT_USER
605 $result = $qControl !== false && $qControl !== -1;
612 * handle control requests for Slave Skip Error
614 public function handleRequestForSlaveSkipError(): bool
619 if (isset($_POST['sr_skip_errors_count'])) {
620 $count = $_POST['sr_skip_errors_count'] * 1;
623 $qStop = $this->replication
->slaveControl('STOP', null, DatabaseInterface
::CONNECT_USER
);
624 $qSkip = $dbi->tryQuery(
625 'SET GLOBAL SQL_SLAVE_SKIP_COUNTER = ' . $count . ';'
627 $qStart = $this->replication
->slaveControl('START', null, DatabaseInterface
::CONNECT_USER
);
629 return $qStop !== false && $qStop !== -1 &&
630 $qSkip !== false && $qSkip !== -1 &&
631 $qStart !== false && $qStart !== -1;