You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

646 lines
16 KiB

<?php
/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program 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.
**
** This program 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 this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**/
require_once __DIR__.'/../../../include/gettextwrapper.inc.php';
require_once __DIR__.'/../../../include/defines.inc.php';
require_once __DIR__.'/../../../conf/zabbix.conf.php';
require_once __DIR__.'/../../../include/func.inc.php';
require_once __DIR__.'/../../../include/classes/api/CApiService.php';
require_once __DIR__.'/../../../include/db.inc.php';
require_once __DIR__.'/../../../include/classes/db/DB.php';
require_once __DIR__.'/../../../include/classes/db/DBException.php';
require_once __DIR__.'/../../../include/classes/user/CWebUser.php';
require_once __DIR__.'/../../../include/classes/debug/CProfiler.php';
require_once __DIR__.'/../../../include/classes/db/DbBackend.php';
require_once __DIR__.'/../../../include/classes/db/MysqlDbBackend.php';
require_once __DIR__.'/../../../include/classes/db/PostgresqlDbBackend.php';
require_once __DIR__.'/CTestArrayHelper.php';
/**
* Database helper.
*/
class CDBHelper {
/**
* Backup stack.
*
* @var array
*/
static $backups = [];
static $db_extension;
/**
* Perform select query and check the result.
*
* @param string $sql query to be executed
* @param integer $limit data limit
* @param integer $offset data offset
*
* @return mixed
*
* @throws Exception
*/
protected static function select($sql, $limit = null, $offset = 0) {
if (($result = DBselect($sql, $limit, $offset)) === false) {
throw new Exception('Failed to execute query: "'.$sql.'".');
}
return $result;
}
/**
* Get database data suitable for PHPUnit data provider functions.
*
* @param string $sql query to be executed
*
* @return array
*/
public static function getDataProvider($sql) {
DBconnect($error);
$data = [];
$result = static::select($sql);
while ($row = DBfetch($result)) {
$data[] = [$row];
}
DBclose();
return $data;
}
/**
* Get database data.
*
* @param string $sql query to be executed
* @param integer $limit data limit
* @param integer $offset data offset
*
* @return array
*
* @throws Exception
*/
public static function getAll($sql, $limit = null, $offset = 0) {
return DBfetchArray(static::select($sql, $limit, $offset));
}
/**
* Get random database data set (limited set of random records).
*
* @param string $sql query to be executed
* @param integer $count data set size (or null for all data set)
*
* @return array
*
* @throws Exception
*/
public static function getRandom($sql, $count = null) {
$data = self::getAll($sql);
shuffle($data);
if ($count !== null) {
$data = array_slice($data, 0, $count);
}
return $data;
}
/**
* Get random database data suitable for PHPUnit data provider functions (limited set of random records).
*
* @param string $sql query to be executed
* @param integer $count data set size (or null for all data set)
*
* @return array
*
* @throws Exception
*/
public static function getRandomizedDataProvider($sql, $count = null) {
DBconnect($error);
$data = [];
foreach (CDBHelper::getRandom($sql, $count) as $row) {
$data[] = [$row];
}
DBclose();
return $data;
}
/**
* Get database data row.
*
* @param string $sql query to be executed
*
* @return mixed
*
* @throws Exception
*/
public static function getRow($sql) {
return DBfetch(static::select($sql, 1));
}
/**
* Get single value from database.
*
* @param string $sql query to be executed
*
* @return mixed
*
* @throws Exception
*/
public static function getValue($sql) {
$row = static::getRow($sql);
if ($row === false) {
throw new Exception('Failed to retrieve data row from query: "'.$sql.'".');
}
return reset($row);
}
/**
* Get all values of database column.
*
* @param type $sql query to be executed
* @param type $column column name
*
* @return array
*/
public static function getColumn($sql, $column) {
$data = [];
foreach (CDBHelper::getAll($sql) as $row) {
$data[] = $row[$column];
}
return $data;
}
/**
* Get list of all referenced tables sorted by dependency level.
*
* For example: getTables($tables, 'users')
* Result: [users,alerts,acknowledges,auditlog,auditlog_details,opmessage_usr,media,profiles,sessions,...]
*/
public static function getTables(&$tables, $top_table) {
if (is_array($top_table)) {
foreach ($top_table as $table) {
self::getTables($tables, $table);
}
return;
}
if (in_array($top_table, $tables)) {
return;
}
if (substr($top_table, 0, 1) === '!') {
$tables[] = substr($top_table, 1);
return;
}
$schema = DB::getSchema();
foreach ($schema[$top_table]['fields'] as $field => $field_data) {
if (!array_key_exists('ref_table', $field_data)) {
continue;
}
$ref_table = $field_data['ref_table'];
if ($ref_table != $top_table) {
static::getTables($tables, $ref_table);
}
}
if (!in_array($top_table, $tables)) {
$tables[] = $top_table;
}
foreach (array_keys($schema) as $table) {
foreach ($schema[$table]['fields'] as $field => $field_data) {
if (!array_key_exists('ref_table', $field_data)) {
continue;
}
$ref_table = $field_data['ref_table'];
if ($ref_table == $top_table && $top_table !== $table) {
static::getTables($tables, $table);
}
}
}
}
/*
* Saves data of the specified table and all dependent tables in temporary storage.
* For example: backupTables(['users'])
*/
public static function backupTables(array $top_tables) {
global $DB;
$tables = [];
static::getTables($tables, $top_tables);
self::$backups[] = $tables;
$suffix = '_tmp'.count(self::$backups);
if ($DB['TYPE'] === ZBX_DB_POSTGRESQL) {
if (self::$db_extension == null) {
$res = DBfetch(DBselect('SELECT db_extension FROM config'));
if ($res) {
self::$db_extension = $res['db_extension'];
}
}
if ($DB['PASSWORD'] !== '') {
putenv('PGPASSWORD='.$DB['PASSWORD']);
}
$cmd = 'pg_dump';
if ($DB['SERVER'] !== 'v') {
$cmd .= ' --host='.$DB['SERVER'];
}
if ($DB['PORT'] !== '' && $DB['PORT'] != 0) {
$cmd .= ' --port='.$DB['PORT'];
}
$file = PHPUNIT_COMPONENT_DIR.$DB['DATABASE'].$suffix.'.dump';
$cmd .= ' --username='.$DB['USER'].' --format=d --jobs=5 --dbname='.$DB['DATABASE'];
$cmd .= ' --table='.implode(' --table=', $tables).' --file='.$file;
if (self::$db_extension == ZBX_DB_EXTENSION_TIMESCALEDB) {
$cmd .= ' 2>/dev/null';
}
exec($cmd, $output, $result_code);
if ($result_code != 0) {
throw new Exception('Failed to backup "'.implode('", "', $top_tables).'".');
}
}
else {
if ($DB['PASSWORD'] !== '') {
putenv('MYSQL_PWD='.$DB['PASSWORD']);
}
$cmd = 'mysqldump';
if ($DB['SERVER'] !== 'v') {
$cmd .= ' --host='.$DB['SERVER'];
}
if ($DB['PORT'] !== '' && $DB['PORT'] != 0) {
$cmd .= ' --port='.$DB['PORT'];
}
$file = PHPUNIT_COMPONENT_DIR.$DB['DATABASE'].$suffix.'.dump.gz';
$cmd .= ' --user='.$DB['USER'].' --add-drop-table '.$DB['DATABASE'];
$cmd .= ' '.implode(' ', $tables).' | gzip -c > '.$file;
exec($cmd, $output, $result_code);
if ($result_code != 0) {
throw new Exception('Failed to backup "'.implode('", "', $top_tables).'".');
}
}
}
/**
* Restores data from temporary storage. backupTables() must be called first.
* For example: restoreTables()
*/
public static function restoreTables() {
global $DB;
if (!self::$backups) {
return;
}
$suffix = '_tmp'.count(self::$backups);
$tables = array_pop(self::$backups);
if ($DB['TYPE'] === ZBX_DB_POSTGRESQL) {
if ($DB['PASSWORD'] !== '') {
putenv('PGPASSWORD='.$DB['PASSWORD']);
}
$cmd = 'pg_restore';
if ($DB['SERVER'] !== 'v') {
$server = ' --host='.$DB['SERVER'];
}
$cmd .= $server;
$port = '';
if ($DB['PORT'] !== '' && $DB['PORT'] != 0) {
$port .= ' --port='.$DB['PORT'];
}
$cmd .= $port;
$file = PHPUNIT_COMPONENT_DIR.$DB['DATABASE'].$suffix.'.dump';
$cmd .= ' --username='.$DB['USER'].' --format=d --jobs=5 --clean --dbname='.$DB['DATABASE'];
$cmd .= ' '.$file;
if (self::$db_extension == ZBX_DB_EXTENSION_TIMESCALEDB) {
$cmd_tdb = 'psql --username='.$DB['USER'].$server.$port.' --dbname='.$DB['DATABASE'].' --command="SELECT timescaledb_pre_restore();"; ';
$cmd_tdb .= $cmd .' 2>/dev/null; ';
$cmd_tdb .= 'psql --username='.$DB['USER'].$server.$port.' --dbname='.$DB['DATABASE'].' --command="SELECT timescaledb_post_restore();" ';
exec($cmd_tdb, $output, $result_code);
}
else {
exec($cmd, $output, $result_code);
}
if ($result_code != 0) {
throw new Exception('Failed to restore "'.$file.'".');
}
if (strstr(strtolower(PHP_OS), 'win') !== false) {
$file = str_replace('/', '\\', $file);
exec('rd '.$file.' /q /s');
}
else {
exec('rm -rf '.$file, $output, $result_code);
}
if ($result_code != 0) {
throw new Exception('Failed to remove "'.$file.'".');
}
}
else {
if ($DB['PASSWORD'] !== '') {
putenv('MYSQL_PWD='.$DB['PASSWORD']);
}
$cmd = 'mysql';
if ($DB['SERVER'] !== 'v') {
$cmd .= ' --host='.$DB['SERVER'];
}
if ($DB['PORT'] !== '' && $DB['PORT'] != 0) {
$cmd .= ' --port='.$DB['PORT'];
}
$file = PHPUNIT_COMPONENT_DIR.$DB['DATABASE'].$suffix.'.dump.gz';
$cmd .= ' --user='.$DB['USER'].' '.$DB['DATABASE'];
$cmd = 'gzip -cd '.$file.' | '.$cmd;
exec($cmd, $output, $result_code);
if ($result_code != 0) {
throw new Exception('Failed to restore "'.$file.'".');
}
if (strstr(strtolower(PHP_OS), 'win') !== false) {
$file = str_replace('/', '\\', $file);
exec('del '.$file);
}
else {
exec('rm -rf '.$file, $output, $result_code);
}
if ($result_code != 0) {
throw new Exception('Failed to remove "'.$file.'".');
}
}
}
/**
* Get md5 hash sum of database result.
*
* @param string $sql query to be executed
*
* @return string
*
* @throws Exception
*/
public static function getHash($sql) {
$hash = '<empty hash>';
$result = static::select($sql);
while ($row = DBfetch($result)) {
$hash = md5($hash.json_encode($row));
}
return $hash;
}
/**
* Get number of records in database result.
*
* @param string $sql query to be executed
* @param integer $limit data limit
* @param integer $offset data offset
*
* @return integer
*
* @throws Exception
*/
public static function getCount($sql, $limit = null, $offset = 0) {
$result = static::select($sql, $limit, $offset);
$count = 0;
while (DBfetch($result)) {
$count++;
}
return $count;
}
/**
* Returns comma-delimited list of the fields.
*
* @param string $table_name
* @param array $exclude_fields
*/
public static function getTableFields($table_name, array $exclude_fields = []) {
$field_names = [];
foreach (DB::getSchema($table_name)['fields'] as $field_name => $field) {
if (!in_array($field_name, $exclude_fields, true)) {
$field_names[] = $field_name;
}
}
return implode(', ', $field_names);
}
/**
* Escapes value to be used in SQL query.
*
* @param mixed $value value to be escaped
*
* @return string
*/
public static function escape($value) {
if (!is_array($value)) {
return zbx_dbstr($value);
}
$result = [];
foreach ($value as $part) {
$result[] = zbx_dbstr($part);
}
return implode(',', $result);
}
/**
* Add host groups to user group with these rights.
*
* @param string $usergroup_name
* @param string $hostgroup_name
* @param int $permission
* @param bool $subgroups
*/
public static function setHostGroupPermissions($usergroup_name, $hostgroup_name, $permission, $subgroups = false) {
$usergroup = DB::find('usrgrp', ['name' => $usergroup_name]);
$hostgroups = DB::find('hstgrp', ['name' => $hostgroup_name]);
if ($usergroup && $hostgroups) {
$usergroup = $usergroup[0];
if ($subgroups) {
$hostgroups = array_merge($hostgroups, DBfetchArray(DBselect(
'SELECT * FROM hstgrp WHERE name LIKE '.zbx_dbstr($hostgroups[0]['name'].'/%')
)));
}
$rights_old = DB::find('rights', [
'groupid' => $usergroup['usrgrpid'],
'id' => array_column($hostgroups, 'groupid')
]);
$rights_new = [];
foreach ($hostgroups as $hostgroup) {
$rights_new[] = [
'groupid' => $usergroup['usrgrpid'],
'permission' => $permission,
'id' => $hostgroup['groupid']
];
}
DB::replace('rights', $rights_old, $rights_new);
}
}
/**
* Create problem or resolved events of trigger.
*
* @param string $trigger_name
* @param int $value TRIGGER_VALUE_FALSE
* @param array $event_fields
*/
public static function setTriggerProblem($trigger_name, $value = TRIGGER_VALUE_TRUE, $event_fields = []) {
$trigger = DB::find('triggers', ['description' => $trigger_name]);
if ($trigger) {
$trigger = $trigger[0];
$tags = DB::select('trigger_tag', [
'output' => ['tag', 'value'],
'filter' => ['triggerid' => $trigger['triggerid']],
'preservekeys' => true
]);
$fields = [
'source' => EVENT_SOURCE_TRIGGERS,
'object' => EVENT_OBJECT_TRIGGER,
'objectid' => $trigger['triggerid'],
'value' => $value,
'name' => $trigger['description'],
'severity' => $trigger['priority'],
'clock' => CTestArrayHelper::get($event_fields, 'clock', time()),
'ns' => CTestArrayHelper::get($event_fields, 'ns', 0),
'acknowledged' => CTestArrayHelper::get($event_fields, 'acknowledged', EVENT_NOT_ACKNOWLEDGED)
];
$eventid = DB::insert('events', [$fields]);
if ($eventid) {
$fields['eventid'] = $eventid[0];
if ($value == TRIGGER_VALUE_TRUE) {
DB::insert('problem', [$fields], false);
DB::update('triggers', [
'values' => [
'value' => TRIGGER_VALUE_TRUE,
'lastchange' => CTestArrayHelper::get($event_fields, 'clock', time())
],
'where' => ['triggerid' => $trigger['triggerid']]
]);
}
else {
$problems = DBfetchArray(DBselect(
'SELECT *'.
' FROM problem'.
' WHERE objectid = '.$trigger['triggerid'].
' AND r_eventid IS NULL'
));
if ($problems) {
DB::update('triggers', [
'values' => [
'value' => TRIGGER_VALUE_FALSE,
'lastchange' => CTestArrayHelper::get($event_fields, 'clock', time())
],
'where' => ['triggerid' => $trigger['triggerid']]
]);
DB::update('problem', [
'values' => [
'r_eventid' => $fields['eventid'],
'r_clock' => $fields['clock'],
'r_ns' => $fields['ns']
],
'where' => ['eventid' => array_column($problems, 'eventid')]
]);
$recovery = [];
foreach ($problems as $problem) {
$recovery[] = [
'eventid' => $problem['eventid'],
'r_eventid' => $fields['eventid']
];
}
DB::insert('event_recovery', $recovery, false);
}
}
if ($tags) {
foreach ($tags as &$tag) {
$tag['eventid'] = $fields['eventid'];
}
unset($tag);
DB::insertBatch('event_tag', $tags);
if ($value == TRIGGER_VALUE_TRUE) {
DB::insertBatch('problem_tag', $tags);
}
}
}
}
}
}