Files
MYSOPHAL/inc/dbconnection.class.php
2025-08-07 13:15:31 +01:00

508 lines
14 KiB
PHP

<?php
/**
* ---------------------------------------------------------------------
* GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2015-2020 Teclib' and contributors.
*
* http://glpi-project.org
*
* based on GLPI - Gestionnaire Libre de Parc Informatique
* Copyright (C) 2003-2014 by the INDEPNET Development Team.
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* GLPI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* GLPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GLPI. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
/**
* Database class for Mysql
**/
class DBConnection extends CommonDBTM {
static protected $notable = true;
static function getTypeName($nb = 0) {
return _n('SQL replica', 'SQL replicas', $nb);
}
/**
* Create GLPI main configuration file
*
* @since 9.1
*
* @param $dbhost
* @param $user
* @param $password
* @param $DBname
*
* @return boolean
*
**/
static function createMainConfig($host, $user, $password, $DBname) {
$DB_str = "<?php\nclass DB extends DBmysql {\n" .
" public \$dbhost = '$host';\n" .
" public \$dbuser = '$user';\n" .
" public \$dbpassword = '". rawurlencode($password) . "';\n" .
" public \$dbdefault = '$DBname';\n" .
"}\n";
return Toolbox::writeConfig('config_db.php', $DB_str);
}
/**
* Create slave DB configuration file
*
* @param host the slave DB host(s)
* @param user the slave DB user
* @param password the slave DB password
* @param DBname the name of the slave DB
*
* @return boolean for success
**/
static function createSlaveConnectionFile($host, $user, $password, $DBname) {
$DB_str = "<?php \n class DBSlave extends DBmysql { \n public \$slave = true; \n public \$dbhost = ";
$host = trim($host);
if (strpos($host, ' ')) {
$hosts = explode(' ', $host);
$first = true;
foreach ($hosts as $host) {
if (!empty($host)) {
$DB_str .= ($first ? "array('" : ",'").$host."'";
$first = false;
}
}
if ($first) {
// no host configured
return false;
}
$DB_str .= ");\n";
} else {
$DB_str .= "'$host';\n";
}
$DB_str .= " public \$dbuser = '" . $user . "'; \n public \$dbpassword= '" .
rawurlencode($password) . "'; \n public \$dbdefault = '" . $DBname . "'; \n }\n";
return Toolbox::writeConfig('config_db_slave.php', $DB_str);
}
/**
* Indicates is the DB replicate is active or not
*
* @return true if active / false if not active
**/
static function isDBSlaveActive() {
return file_exists(GLPI_CONFIG_DIR . "/config_db_slave.php");
}
/**
* Read slave DB configuration file
*
* @param $choice integer, host number (default NULL)
*
* @return DBmysql object
**/
static function getDBSlaveConf($choice = null) {
if (self::isDBSlaveActive()) {
include_once (GLPI_CONFIG_DIR . "/config_db_slave.php");
return new DBSlave($choice);
}
}
/**
* Create a default slave DB configuration file
**/
static function createDBSlaveConfig() {
self::createSlaveConnectionFile("localhost", "glpi", "glpi", "glpi");
}
/**
* Save changes to the slave DB configuration file
*
* @param $host
* @param $user
* @param $password
* @param $DBname
**/
static function saveDBSlaveConf($host, $user, $password, $DBname) {
self::createSlaveConnectionFile($host, $user, $password, $DBname);
}
/**
* Delete slave DB configuration file
*/
static function deleteDBSlaveConfig() {
unlink(GLPI_CONFIG_DIR . "/config_db_slave.php");
}
/**
* Switch database connection to slave
**/
static function switchToSlave() {
global $DB;
if (self::isDBSlaveActive()) {
include_once (GLPI_CONFIG_DIR . "/config_db_slave.php");
$DB = new DBSlave();
return $DB->connected;
}
return false;
}
/**
* Switch database connection to master
**/
static function switchToMaster() {
global $DB;
$DB = new DB();
return $DB->connected;
}
/**
* Get Connection to slave, if exists,
* and if configured to be used for read only request
*
* @return DBmysql object
**/
static function getReadConnection() {
global $DB, $CFG_GLPI;
if ($CFG_GLPI['use_slave_for_search']
&& !$DB->isSlave()
&& self::isDBSlaveActive()) {
include_once (GLPI_CONFIG_DIR . "/config_db_slave.php");
$DBread = new DBSlave();
if ($DBread->connected) {
$sql = "SELECT MAX(`id`) AS maxid
FROM `glpi_logs`";
switch ($CFG_GLPI['use_slave_for_search']) {
case 3 : // If synced or read-only account
if (Session::isReadOnlyAccount()) {
return $DBread;
}
// nobreak;
case 1 : // If synced (all changes)
$slave = $DBread->request($sql)->next();
$master = $DB->request($sql)->next();
if (isset($slave['maxid']) && isset($master['maxid'])
&& ($slave['maxid'] == $master['maxid'])) {
// Latest Master change available on Slave
return $DBread;
}
break;
case 2 : // If synced (current user changes or profile in read only)
if (!isset($_SESSION['glpi_maxhistory'])) {
// No change yet
return $DBread;
}
$slave = $DBread->request($sql)->next();
if (isset($slave['maxid'])
&& ($slave['maxid'] >= $_SESSION['glpi_maxhistory'])) {
// Latest current user change avaiable on Slave
return $DBread;
}
break;
default: // Always
return $DBread;
}
}
}
return $DB;
}
/**
* Establish a connection to a mysql server (main or replicate)
*
* @param boolean $use_slave try to connect to slave server first not to main server
* @param boolean $required connection to the specified server is required
* (if connection failed, do not try to connect to the other server)
* @param boolean $display display error message (true by default)
*
* @return boolean True if successfull, false otherwise
**/
static function establishDBConnection($use_slave, $required, $display = true) {
global $DB;
$DB = null;
$res = false;
// First standard config : no use slave : try to connect to master
if (!$use_slave) {
$res = self::switchToMaster();
}
// If not already connected to master due to config or error
if (!$res) {
// No DB slave : first connection to master give error
if (!self::isDBSlaveActive()) {
// Slave wanted but not defined -> use master
// Ignore $required when no slave configured
if ($use_slave) {
$res = self::switchToMaster();
}
} else { // Slave DB configured
// Try to connect to slave if wanted
if ($use_slave) {
$res = self::switchToSlave();
}
// No connection to 'mandatory' server
if (!$res && !$required) {
//Try to establish the connection to the other mysql server
if ($use_slave) {
$res = self::switchToMaster();
} else {
$res = self::switchToSlave();
}
if ($res) {
$DB->first_connection = false;
}
}
}
}
// Display error if needed
if (!$res && $display) {
self::displayMySQLError();
}
return $res;
}
/**
* Get delay between slave and master
*
* @param $choice integer, host number (default NULL)
*
* @return integer
**/
static function getReplicateDelay($choice = null) {
include_once (GLPI_CONFIG_DIR . "/config_db_slave.php");
return (int) (self::getHistoryMaxDate(new DB())
- self::getHistoryMaxDate(new DBSlave($choice)));
}
/**
* Get history max date of a GLPI DB
*
* @param $DBconnection DB conneciton used
**/
static function getHistoryMaxDate($DBconnection) {
if ($DBconnection->connected) {
$result = $DBconnection->query("SELECT UNIX_TIMESTAMP(MAX(`date_mod`)) AS max_date
FROM `glpi_logs`");
if ($DBconnection->numrows($result) > 0) {
return $DBconnection->result($result, 0, "max_date");
}
}
return 0;
}
/**
* Display a common mysql connection error
**/
static function displayMySQLError() {
global $DB;
$error = $DB instanceof DBmysql ? $DB->error : 1;
switch ($error) {
case 2:
$en_msg = "Use of mysqlnd driver is required for exchanges with the MySQL server.";
$fr_msg = "L'utilisation du driver mysqlnd est requise pour les échanges avec le serveur MySQL.";
break;
case 1:
default:
$fr_msg = "Le serveur Mysql est inaccessible. Vérifiez votre configuration.";
$en_msg = "A link to the SQL server could not be established. Please check your configuration.";
break;
}
if (!isCommandLine()) {
Html::nullHeader("Mysql Error", '');
echo "<div class='center'><p class ='b'>$en_msg</p><p class='b'>$fr_msg</p></div>";
Html::nullFooter();
} else {
echo "$en_msg\n$fr_msg\n";
}
die(1);
}
/**
* @param $name
**/
static function cronInfo($name) {
return ['description' => __('Check the SQL replica'),
'parameter' => __('Max delay between master and slave (minutes)')];
}
/**
* Cron process to check DB replicate state
*
* @param CronTask $task to log and get param
*
* @return integer
**/
static function cronCheckDBreplicate(CronTask $task) {
global $DB;
//Lauch cron only is :
// 1 the master database is avalaible
// 2 the slave database is configurated
if (!$DB->isSlave() && self::isDBSlaveActive()) {
$DBslave = self::getDBSlaveConf();
if (is_array($DBslave->dbhost)) {
$hosts = $DBslave->dbhost;
} else {
$hosts = [$DBslave->dbhost];
}
foreach ($hosts as $num => $name) {
$diff = self::getReplicateDelay($num);
// Quite strange, but allow simple stat
$task->addVolume($diff);
if ($diff > 1000000000) { // very large means slave is disconnect
$task->log(sprintf(__s("SQL server: %s can't connect to the database"), $name));
} else {
//TRANS: %1$s is the server name, %2$s is the time
$task->log(sprintf(__('SQL server: %1$s, difference between master and slave: %2$s'),
$name, Html::timestampToString($diff, true)));
}
if ($diff > ($task->fields['param']*60)) {
//Raise event if replicate is not synchronized
$options = ['diff' => $diff,
'name' => $name,
'entities_id' => 0]; // entity to avoid warning in getReplyTo
NotificationEvent::raiseEvent('desynchronization', new self(), $options);
}
}
return 1;
}
return 0;
}
/**
* Display in HTML, delay between master and slave
* 1 line per slave is multiple
**/
static function showAllReplicateDelay() {
$DBslave = self::getDBSlaveConf();
if (is_array($DBslave->dbhost)) {
$hosts = $DBslave->dbhost;
} else {
$hosts = [$DBslave->dbhost];
}
foreach ($hosts as $num => $name) {
$diff = self::getReplicateDelay($num);
//TRANS: %s is namez of server Mysql
printf(__('%1$s: %2$s'), __('SQL server'), $name);
echo " - ";
if ($diff > 1000000000) {
echo __("can't connect to the database") . "<br>";
} else if ($diff) {
printf(__('%1$s: %2$s')."<br>", __('Difference between master and slave'),
Html::timestampToString($diff, 1));
} else {
printf(__('%1$s: %2$s')."<br>", __('Difference between master and slave'), __('None'));
}
}
}
/**
* @param $width
**/
function showSystemInformations($width) {
// No need to translate, this part always display in english (for copy/paste to forum)
echo "<tr class='tab_bg_2'><th>".self::getTypeName(Session::getPluralNumber())."</th></tr>";
echo "<tr class='tab_bg_1'><td><pre>\n&nbsp;\n";
if (self::isDBSlaveActive()) {
echo "Active\n";
self::showAllReplicateDelay();
} else {
echo "Not active\n";
}
echo "\n</pre></td></tr>";
}
/**
* Enable or disable db replication check cron task
*
* @param enable of disable cron task (true by default)
**/
static function changeCronTaskStatus($enable = true) {
$cron = new CronTask();
$cron->getFromDBbyName('DBConnection', 'CheckDBreplicate');
$input = [
'id' => $cron->fields['id'],
'state' => ($enable ? 1 : 0)
];
$cron->update($input);
}
}