Translated using Weblate (Portuguese)
[phpmyadmin.git] / src / Replication / ReplicationGui.php
blobb6cc69df56211dd9dfebfda039f97b4d5369938c
1 <?php
2 /**
3 * Functions for the replication GUI
4 */
6 declare(strict_types=1);
8 namespace PhpMyAdmin\Replication;
10 use PhpMyAdmin\Config;
11 use PhpMyAdmin\DatabaseInterface;
12 use PhpMyAdmin\Dbal\ConnectionType;
13 use PhpMyAdmin\Message;
14 use PhpMyAdmin\Query\Utilities;
15 use PhpMyAdmin\ResponseRenderer;
16 use PhpMyAdmin\Template;
17 use PhpMyAdmin\Url;
18 use PhpMyAdmin\Util;
20 use function __;
21 use function htmlspecialchars;
22 use function in_array;
23 use function mb_strrpos;
24 use function mb_strtolower;
25 use function mb_substr;
26 use function sprintf;
27 use function str_replace;
28 use function strtok;
29 use function time;
31 /**
32 * Functions for the replication GUI
34 class ReplicationGui
36 public function __construct(private Replication $replication, private Template $template)
40 /**
41 * returns HTML for error message
43 * @return string HTML code
45 public function getHtmlForErrorMessage(): string
47 $html = '';
48 if (isset($_SESSION['replication']['sr_action_status'], $_SESSION['replication']['sr_action_info'])) {
49 if ($_SESSION['replication']['sr_action_status'] === 'error') {
50 $errorMessage = $_SESSION['replication']['sr_action_info'];
51 $html .= Message::error($errorMessage)->getDisplay();
52 $_SESSION['replication']['sr_action_status'] = 'unknown';
53 } elseif ($_SESSION['replication']['sr_action_status'] === 'success') {
54 $successMessage = $_SESSION['replication']['sr_action_info'];
55 $html .= Message::success($successMessage)->getDisplay();
56 $_SESSION['replication']['sr_action_status'] = 'unknown';
60 return $html;
63 /**
64 * returns HTML for primary replication
66 * @return string HTML code
68 public function getHtmlForPrimaryReplication(
69 string|null $connection,
70 bool $hasReplicaClearScreen,
71 string|null $primaryAddUser,
72 string|null $username,
73 string|null $hostname,
74 ): string {
75 if (! $hasReplicaClearScreen) {
76 $primaryStatusTable = $this->getHtmlForReplicationStatusTable($connection, 'primary', true, false);
77 $replicas = DatabaseInterface::getInstance()->fetchResult('SHOW SLAVE HOSTS');
79 $urlParams = $GLOBALS['urlParams'];
80 $urlParams['primary_add_user'] = true;
81 $urlParams['replica_clear_screen'] = true;
84 if ($primaryAddUser !== null) {
85 $primaryAddReplicaUser = $this->getHtmlForReplicationPrimaryAddReplicaUser($username, $hostname);
88 return $this->template->render('server/replication/primary_replication', [
89 'clear_screen' => $hasReplicaClearScreen,
90 'primary_status_table' => $primaryStatusTable ?? '',
91 'replicas' => $replicas ?? [],
92 'url_params' => $urlParams ?? [],
93 'primary_add_user' => $primaryAddUser !== null,
94 'primary_add_replica_user' => $primaryAddReplicaUser ?? '',
95 ]);
98 /**
99 * returns HTML for primary replication configuration
101 * @return string HTML code
103 public function getHtmlForPrimaryConfiguration(): string
105 $databaseMultibox = $this->getHtmlForReplicationDbMultibox();
107 return $this->template->render(
108 'server/replication/primary_configuration',
109 ['database_multibox' => $databaseMultibox],
114 * returns HTML for replica replication configuration
116 * @param string|null $connection Primary connection
117 * @param bool $serverReplicaStatus Whether it is Primary or Replica
118 * @param mixed[] $serverReplicaReplication Replica replication
119 * @param bool $replicaConfigure Replica configure
121 * @return string HTML code
123 public function getHtmlForReplicaConfiguration(
124 string|null $connection,
125 bool $serverReplicaStatus,
126 array $serverReplicaReplication,
127 bool $replicaConfigure,
128 ): string {
129 $serverReplicaMultiReplication = DatabaseInterface::getInstance()->fetchResult('SHOW ALL SLAVES STATUS');
130 if ($serverReplicaStatus) {
131 $urlParams = $GLOBALS['urlParams'];
132 $urlParams['sr_take_action'] = true;
133 $urlParams['sr_replica_server_control'] = true;
135 if ($serverReplicaReplication[0]['Slave_IO_Running'] === 'No') {
136 $urlParams['sr_replica_action'] = 'start';
137 } else {
138 $urlParams['sr_replica_action'] = 'stop';
141 $urlParams['sr_replica_control_param'] = 'IO_THREAD';
142 $replicaControlIoLink = Url::getCommon($urlParams, '', false);
144 if ($serverReplicaReplication[0]['Slave_SQL_Running'] === 'No') {
145 $urlParams['sr_replica_action'] = 'start';
146 } else {
147 $urlParams['sr_replica_action'] = 'stop';
150 $urlParams['sr_replica_control_param'] = 'SQL_THREAD';
151 $replicaControlSqlLink = Url::getCommon($urlParams, '', false);
153 if (
154 $serverReplicaReplication[0]['Slave_IO_Running'] === 'No'
155 || $serverReplicaReplication[0]['Slave_SQL_Running'] === 'No'
157 $urlParams['sr_replica_action'] = 'start';
158 } else {
159 $urlParams['sr_replica_action'] = 'stop';
162 $urlParams['sr_replica_control_param'] = null;
163 $replicaControlFullLink = Url::getCommon($urlParams, '', false);
165 $urlParams['sr_replica_action'] = 'reset';
166 $replicaControlResetLink = Url::getCommon($urlParams, '', false);
168 $urlParams = $GLOBALS['urlParams'];
169 $urlParams['sr_take_action'] = true;
170 $urlParams['sr_replica_skip_error'] = true;
171 $replicaSkipErrorLink = Url::getCommon($urlParams, '', false);
173 $urlParams = $GLOBALS['urlParams'];
174 $urlParams['replica_configure'] = true;
175 $urlParams['replica_clear_screen'] = true;
177 $reconfigurePrimaryLink = Url::getCommon($urlParams, '', false);
179 $replicaStatusTable = $this->getHtmlForReplicationStatusTable($connection, 'replica', true, false);
181 $replicaIoRunning = $serverReplicaReplication[0]['Slave_IO_Running'] !== 'No';
182 $replicaSqlRunning = $serverReplicaReplication[0]['Slave_SQL_Running'] !== 'No';
185 return $this->template->render('server/replication/replica_configuration', [
186 'server_replica_multi_replication' => $serverReplicaMultiReplication,
187 'url_params' => $GLOBALS['urlParams'],
188 'primary_connection' => $connection ?? '',
189 'server_replica_status' => $serverReplicaStatus,
190 'replica_status_table' => $replicaStatusTable ?? '',
191 'replica_sql_running' => $replicaSqlRunning ?? false,
192 'replica_io_running' => $replicaIoRunning ?? false,
193 'replica_control_full_link' => $replicaControlFullLink ?? '',
194 'replica_control_reset_link' => $replicaControlResetLink ?? '',
195 'replica_control_sql_link' => $replicaControlSqlLink ?? '',
196 'replica_control_io_link' => $replicaControlIoLink ?? '',
197 'replica_skip_error_link' => $replicaSkipErrorLink ?? '',
198 'reconfigure_primary_link' => $reconfigurePrimaryLink ?? '',
199 'has_replica_configure' => $replicaConfigure,
204 * returns HTML code for selecting databases
206 * @return string HTML code
208 public function getHtmlForReplicationDbMultibox(): string
210 $databases = [];
211 foreach (DatabaseInterface::getInstance()->getDatabaseList() as $database) {
212 if (Utilities::isSystemSchema($database)) {
213 continue;
216 $databases[] = $database;
219 return $this->template->render('server/replication/database_multibox', ['databases' => $databases]);
223 * returns HTML for changing primary
225 * @param string $submitName submit button name
227 * @return string HTML code
229 public function getHtmlForReplicationChangePrimary(string $submitName): string
231 [$usernameLength, $hostnameLength] = $this->getUsernameHostnameLength();
233 return $this->template->render('server/replication/change_primary', [
234 'server_id' => time(),
235 'username_length' => $usernameLength,
236 'hostname_length' => $hostnameLength,
237 'submit_name' => $submitName,
242 * This function returns html code for table with replication status.
244 * @param string|null $connection primary connection
245 * @param string $type either primary or replica
246 * @param bool $isHidden if true, then default style is set to hidden, default value false
247 * @param bool $hasTitle if true, then title is displayed, default true
249 * @return string HTML code
251 public function getHtmlForReplicationStatusTable(
252 string|null $connection,
253 string $type,
254 bool $isHidden = false,
255 bool $hasTitle = true,
256 ): string {
257 $replicationInfo = new ReplicationInfo(DatabaseInterface::getInstance());
258 $replicationInfo->load($connection);
260 $replicationVariables = $replicationInfo->primaryVariables;
261 $variablesAlerts = null;
262 $variablesOks = null;
263 $serverReplication = $replicationInfo->getPrimaryStatus();
264 if ($type === 'replica') {
265 $replicationVariables = $replicationInfo->replicaVariables;
266 $variablesAlerts = ['Slave_IO_Running' => 'No', 'Slave_SQL_Running' => 'No'];
267 $variablesOks = ['Slave_IO_Running' => 'Yes', 'Slave_SQL_Running' => 'Yes'];
268 $serverReplication = $replicationInfo->getReplicaStatus();
271 $variables = [];
272 foreach ($replicationVariables as $variable) {
273 $serverReplicationVariable = isset($serverReplication[0])
274 ? $serverReplication[0][$variable]
275 : '';
277 $variables[$variable] = ['name' => $variable, 'status' => '', 'value' => $serverReplicationVariable];
279 if (isset($variablesAlerts[$variable]) && $variablesAlerts[$variable] === $serverReplicationVariable) {
280 $variables[$variable]['status'] = 'text-danger';
281 } elseif (isset($variablesOks[$variable]) && $variablesOks[$variable] === $serverReplicationVariable) {
282 $variables[$variable]['status'] = 'text-success';
285 $variablesWrap = [
286 'Replicate_Do_DB',
287 'Replicate_Ignore_DB',
288 'Replicate_Do_Table',
289 'Replicate_Ignore_Table',
290 'Replicate_Wild_Do_Table',
291 'Replicate_Wild_Ignore_Table',
293 if (! in_array($variable, $variablesWrap, true)) {
294 continue;
297 $variables[$variable]['value'] = str_replace(',', ', ', $serverReplicationVariable);
300 return $this->template->render('server/replication/status_table', [
301 'type' => $type,
302 'is_hidden' => $isHidden,
303 'has_title' => $hasTitle,
304 'variables' => $variables,
309 * get the correct username and hostname lengths for this MySQL server
311 * @return array<int,int> username length, hostname length
313 public function getUsernameHostnameLength(): array
315 $fieldsInfo = DatabaseInterface::getInstance()->getColumns('mysql', 'user');
316 $usernameLength = 16;
317 $hostnameLength = 41;
318 foreach ($fieldsInfo as $val) {
319 if ($val->field === 'User') {
320 strtok($val->type, '()');
321 $v = strtok('()');
322 if (Util::isInteger($v)) {
323 $usernameLength = (int) $v;
325 } elseif ($val->field === 'Host') {
326 strtok($val->type, '()');
327 $v = strtok('()');
328 if (Util::isInteger($v)) {
329 $hostnameLength = (int) $v;
334 return [$usernameLength, $hostnameLength];
338 * returns html code to add a replication replica user to the primary
340 * @return string HTML code
342 public function getHtmlForReplicationPrimaryAddReplicaUser(string|null $postUsername, string|null $hostname): string
344 [$usernameLength, $hostnameLength] = $this->getUsernameHostnameLength();
346 $username = '';
347 if ($postUsername === '') {
348 $GLOBALS['pred_username'] = 'any';
349 } elseif ($postUsername !== null && $postUsername !== '0') {
350 $username = $GLOBALS['new_username'] ?? $postUsername;
353 $currentUser = DatabaseInterface::getInstance()->fetchValue('SELECT USER();');
354 if ($currentUser !== '' && $currentUser !== false && $currentUser !== null) {
355 $userHost = str_replace(
356 "'",
358 mb_substr(
359 $currentUser,
360 mb_strrpos($currentUser, '@') + 1,
363 if ($userHost !== 'localhost' && $userHost !== '127.0.0.1') {
364 $thisHost = $userHost;
368 // when we start editing a user, $GLOBALS['pred_hostname'] is not defined
369 if (! isset($GLOBALS['pred_hostname']) && $hostname !== null) {
370 $GLOBALS['pred_hostname'] = match (mb_strtolower($hostname)) {
371 'localhost', '127.0.0.1' => 'localhost',
372 '%' => 'any',
373 default => 'userdefined',
377 return $this->template->render('server/replication/primary_add_replica_user', [
378 'username_length' => $usernameLength,
379 'hostname_length' => $hostnameLength,
380 'has_username' => $postUsername !== null,
381 'username' => $username,
382 'hostname' => $hostname ?? '',
383 'predefined_username' => $GLOBALS['pred_username'] ?? '',
384 'predefined_hostname' => $GLOBALS['pred_hostname'] ?? '',
385 'this_host' => $thisHost ?? null,
390 * handle control requests
392 public function handleControlRequest(
393 bool $srTakeAction,
394 bool $replicaChangePrimary,
395 bool $srReplicaServerControl,
396 string|null $srReplicaAction,
397 bool $srReplicaSkipError,
398 int $srSkipErrorsCount,
399 string|null $srReplicaControlParam,
400 string $username,
401 string $pmaPassword,
402 string $hostname,
403 int $port,
404 ): void {
405 if (! $srTakeAction) {
406 return;
409 $refresh = false;
410 $result = false;
411 $messageSuccess = '';
412 $messageError = '';
414 if ($replicaChangePrimary && ! Config::getInstance()->settings['AllowArbitraryServer']) {
415 $_SESSION['replication']['sr_action_status'] = 'error';
416 $_SESSION['replication']['sr_action_info'] = __(
417 'Connection to server is disabled, please enable'
418 . ' $cfg[\'AllowArbitraryServer\'] in phpMyAdmin configuration.',
420 } elseif ($replicaChangePrimary) {
421 $result = $this->handleRequestForReplicaChangePrimary($username, $pmaPassword, $hostname, $port);
422 } elseif ($srReplicaServerControl) {
423 $result = $this->handleRequestForReplicaServerControl($srReplicaAction, $srReplicaControlParam);
424 $refresh = true;
426 switch ($srReplicaAction) {
427 case 'start':
428 $messageSuccess = __('Replication started successfully.');
429 $messageError = __('Error starting replication.');
430 break;
431 case 'stop':
432 $messageSuccess = __('Replication stopped successfully.');
433 $messageError = __('Error stopping replication.');
434 break;
435 case 'reset':
436 $messageSuccess = __('Replication resetting successfully.');
437 $messageError = __('Error resetting replication.');
438 break;
439 default:
440 $messageSuccess = __('Success.');
441 $messageError = __('Error.');
442 break;
444 } elseif ($srReplicaSkipError) {
445 $result = $this->handleRequestForReplicaSkipError($srSkipErrorsCount);
448 if ($refresh) {
449 $response = ResponseRenderer::getInstance();
450 if ($response->isAjax()) {
451 $response->setRequestStatus($result);
452 $response->addJSON(
453 'message',
454 $result
455 ? Message::success($messageSuccess)
456 : Message::error($messageError),
458 } else {
459 $response->redirect(
460 './index.php?route=/server/replication' . Url::getCommonRaw($GLOBALS['urlParams'], '&'),
465 unset($refresh);
468 public function handleRequestForReplicaChangePrimary(
469 string $username,
470 string $pmaPassword,
471 string $hostname,
472 int $port,
473 ): bool {
474 $_SESSION['replication']['m_username'] = $username;
475 $_SESSION['replication']['m_password'] = $pmaPassword;
476 $_SESSION['replication']['m_hostname'] = $hostname;
477 $_SESSION['replication']['m_port'] = $port;
478 $_SESSION['replication']['m_correct'] = '';
480 // Attempt to connect to the new primary server
481 $connectionToPrimary = $this->replication->connectToPrimary($username, $pmaPassword, $hostname, $port);
483 if ($connectionToPrimary === null) {
484 $_SESSION['replication']['sr_action_status'] = 'error';
485 $_SESSION['replication']['sr_action_info'] = sprintf(
486 __('Unable to connect to primary %s.'),
487 htmlspecialchars($hostname),
489 } else {
490 // Read the current primary position
491 $position = $this->replication->replicaBinLogPrimary(ConnectionType::Auxiliary);
493 if ($position === []) {
494 $_SESSION['replication']['sr_action_status'] = 'error';
495 $_SESSION['replication']['sr_action_info'] = __(
496 'Unable to read primary log position. Possible privilege problem on primary.',
498 } else {
499 $_SESSION['replication']['m_correct'] = true;
501 if (
502 ! $this->replication->replicaChangePrimary(
503 $username,
504 $pmaPassword,
505 $hostname,
506 $port,
507 $position,
508 true,
509 false,
510 ConnectionType::User,
513 $_SESSION['replication']['sr_action_status'] = 'error';
514 $_SESSION['replication']['sr_action_info'] = __('Unable to change primary!');
515 } else {
516 $_SESSION['replication']['sr_action_status'] = 'success';
517 $_SESSION['replication']['sr_action_info'] = sprintf(
518 __('Primary server changed successfully to %s.'),
519 htmlspecialchars($hostname),
525 return $_SESSION['replication']['sr_action_status'] === 'success';
528 public function handleRequestForReplicaServerControl(string|null $srReplicaAction, string|null $control): bool
530 if ($srReplicaAction === 'reset') {
531 $qStop = $this->replication->replicaControl('STOP', null, ConnectionType::User);
532 $qReset = DatabaseInterface::getInstance()->tryQuery('RESET SLAVE;');
533 $qStart = $this->replication->replicaControl('START', null, ConnectionType::User);
535 return $qStop !== false && $qStop !== -1 && $qReset !== false && $qStart !== false && $qStart !== -1;
538 $qControl = $this->replication->replicaControl($srReplicaAction, $control, ConnectionType::User);
540 return $qControl !== false && $qControl !== -1;
543 public function handleRequestForReplicaSkipError(int $srSkipErrorsCount): bool
545 $dbi = DatabaseInterface::getInstance();
546 $qStop = $this->replication->replicaControl('STOP', null, ConnectionType::User);
547 $qSkip = $dbi->tryQuery('SET GLOBAL SQL_SLAVE_SKIP_COUNTER = ' . $srSkipErrorsCount . ';');
548 $qStart = $this->replication->replicaControl('START', null, ConnectionType::User);
550 return $qStop !== false && $qStop !== -1 && $qSkip !== false && $qStart !== false && $qStart !== -1;