site-accueil-insa/matomo/core/Tracker/Db/Mysqli.php

463 lines
13 KiB
PHP

<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*
*/
namespace Piwik\Tracker\Db;
use Exception;
use Piwik\Tracker\Db;
/**
* mysqli wrapper
*
*/
class Mysqli extends Db
{
protected $connection = null;
protected $host;
protected $port;
protected $socket;
protected $dbname;
protected $username;
protected $password;
protected $charset;
protected $activeTransaction = false;
protected $enable_ssl;
protected $ssl_key;
protected $ssl_cert;
protected $ssl_ca;
protected $ssl_ca_path;
protected $ssl_cipher;
protected $ssl_no_verify;
/**
* Builds the DB object
*
* @param array $dbInfo
* @param string $driverName
*/
public function __construct($dbInfo, $driverName = 'mysql')
{
if (isset($dbInfo['unix_socket']) && substr($dbInfo['unix_socket'], 0, 1) == '/') {
$this->host = null;
$this->port = null;
$this->socket = $dbInfo['unix_socket'];
} elseif (isset($dbInfo['port']) && substr($dbInfo['port'], 0, 1) == '/') {
$this->host = null;
$this->port = null;
$this->socket = $dbInfo['port'];
} else {
$this->host = $dbInfo['host'];
$this->port = (int)$dbInfo['port'];
$this->socket = null;
}
$this->dbname = $dbInfo['dbname'];
$this->username = $dbInfo['username'];
$this->password = $dbInfo['password'];
$this->charset = isset($dbInfo['charset']) ? $dbInfo['charset'] : null;
if(!empty($dbInfo['enable_ssl'])){
$this->enable_ssl = $dbInfo['enable_ssl'];
}
if(!empty($dbInfo['ssl_key'])){
$this->ssl_key = $dbInfo['ssl_key'];
}
if(!empty($dbInfo['ssl_cert'])){
$this->ssl_cert = $dbInfo['ssl_cert'];
}
if(!empty($dbInfo['ssl_ca'])){
$this->ssl_ca = $dbInfo['ssl_ca'];
}
if(!empty($dbInfo['ssl_ca_path'])){
$this->ssl_ca_path = $dbInfo['ssl_ca_path'];
}
if(!empty($dbInfo['ssl_cipher'])){
$this->ssl_cipher = $dbInfo['ssl_cipher'];
}
if(!empty($dbInfo['ssl_no_verify'])){
$this->ssl_no_verify = $dbInfo['ssl_no_verify'];
}
}
/**
* Destructor
*/
public function __destruct()
{
$this->connection = null;
}
/**
* Connects to the DB
*
* @throws Exception|DbException if there was an error connecting the DB
*/
public function connect()
{
if (self::$profiling) {
$timer = $this->initProfiler();
}
// The default error reporting of mysqli changed in PHP 8.1. To circumvent problems in our error handling we set
// the erroring reporting to the default that was used prior PHP 8.1
// See https://php.watch/versions/8.1/mysqli-error-mode for more details
mysqli_report(MYSQLI_REPORT_OFF);
$this->connection = mysqli_init();
if($this->enable_ssl){
mysqli_ssl_set($this->connection, $this->ssl_key, $this->ssl_cert, $this->ssl_ca, $this->ssl_ca_path, $this->ssl_cipher);
}
// Make sure MySQL returns all matched rows on update queries including
// rows that actually didn't have to be updated because the values didn't
// change. This matches common behaviour among other database systems.
// See #6296 why this is important in tracker
$flags = MYSQLI_CLIENT_FOUND_ROWS;
if ($this->enable_ssl){
$flags = $flags | MYSQLI_CLIENT_SSL;
}
if ($this->ssl_no_verify && defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT')){
$flags = $flags | MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
}
mysqli_real_connect($this->connection, $this->host, $this->username, $this->password, $this->dbname, $this->port, $this->socket, $flags);
if (!$this->connection || mysqli_connect_errno()) {
throw new DbException("Connect failed: " . mysqli_connect_error());
}
if ($this->charset && !mysqli_set_charset($this->connection, $this->charset)) {
throw new DbException("Set Charset failed: " . mysqli_error($this->connection));
}
$this->password = '';
if (self::$profiling && isset($timer)) {
$this->recordQueryProfile('connect', $timer);
}
}
/**
* Disconnects from the server
*/
public function disconnect()
{
mysqli_close($this->connection);
$this->connection = null;
}
/**
* @param \mysqli_stmt $stmt
* @param $fields
* @return array|bool|false
*/
private function fetchResult($stmt, $fields) {
$values = array_fill(0, count($fields), null);
$refs = array();
foreach ($values as $i => &$f) {
$refs[$i] = &$f;
}
call_user_func_array(array($stmt, 'bind_result'), $values);
$result = $stmt->fetch();
if ($result === null || $result === false) {
$stmt->reset();
return false;
}
$val = array();
foreach ($values as $key => $value) {
$val[] = $value;
}
$row = array_combine($fields, $values);
return $row;
}
/**
* Returns an array containing all the rows of a query result, using optional bound parameters.
*
* @see query()
*
* @param string $query Query
* @param array $parameters Parameters to bind
* @return array
* @throws Exception|DbException if an exception occurred
*/
public function fetchAll($query, $parameters = array())
{
try {
if (self::$profiling) {
$timer = $this->initProfiler();
}
list($stmt, $fields) = $this->executeQuery($query, $parameters);
$rows = array();
while ($row = $this->fetchResult($stmt, $fields)) {
$rows[] = $row;
}
$stmt->free_result();
$stmt->close();
if (self::$profiling && isset($timer)) {
$this->recordQueryProfile($query, $timer);
}
return $rows;
} catch (Exception $e) {
throw new DbException("Error query: " . $e->getMessage());
}
}
/**
* Returns the first row of a query result, using optional bound parameters.
*
* @see query()
*
* @param string $query Query
* @param array $parameters Parameters to bind
*
* @return array
*
* @throws DbException if an exception occurred
*/
public function fetch($query, $parameters = array())
{
try {
if (self::$profiling) {
$timer = $this->initProfiler();
}
list($stmt, $fields) = $this->executeQuery($query, $parameters);
$row = $this->fetchResult($stmt, $fields);
$stmt->free_result();
$stmt->close();
if (self::$profiling && isset($timer)) {
$this->recordQueryProfile($query, $timer);
}
if ($row === null) {
$row = false;
}
return $row;
} catch (Exception $e) {
throw new DbException("Error query: " . $e->getMessage());
}
}
/**
* Executes a query, using optional bound parameters.
*
* @param string $query Query
* @param array|string $parameters Parameters to bind array('idsite'=> 1)
*
* @return bool|resource false if failed
* @throws DbException if an exception occurred
*/
public function query($query, $parameters = array())
{
if (is_null($this->connection)) {
return false;
}
try {
if (self::$profiling) {
$timer = $this->initProfiler();
}
list($stmt, $fields) = $this->executeQuery($query, $parameters);
if (self::$profiling && isset($timer)) {
$this->recordQueryProfile($query, $timer);
}
return $stmt;
} catch (Exception $e) {
throw new DbException("Error query: " . $e->getMessage() . "
In query: $query
Parameters: " . var_export($parameters, true), $e->getCode());
}
}
/**
* Returns the last inserted ID in the DB
*
* @return int
*/
public function lastInsertId()
{
return mysqli_insert_id($this->connection);
}
private function executeQuery($sql, $bind) {
$stmt = mysqli_prepare($this->connection, $sql);
if (!$stmt) {
throw new DbException('preparing query failed: ' . mysqli_error($this->connection) . ' : ' . $sql);
}
if (!is_array($bind)) {
$bind = array($bind);
}
if (!empty($bind)) {
array_unshift($bind, str_repeat('s', count($bind)));
$refs = array();
foreach ($bind as $key => $value) {
$refs[$key] = &$bind[$key];
}
call_user_func_array(array($stmt, 'bind_param'), $refs);
}
$stmtResult = $stmt->execute();
if ($stmtResult === false) {
throw new DbException("Mysqli statement execute error : " . $stmt->error, $stmt->errno);
}
if (!empty($stmt->error)) {
throw new DbException('executeQuery() failed: ' . mysqli_error($this->connection) . ' : ' . $sql);
}
$metaResults = $stmt->result_metadata();
if ($stmt->errno) {
throw new DbException("Mysqli statement metadata error: " . $stmt->error, $stmt->errno);
}
$fields = array();
if ($metaResults) {
$fetchedFields = $metaResults->fetch_fields();
foreach ($fetchedFields as $fetchedField) {
$fields[] = $fetchedField->name;
}
$stmt->store_result();
}
return array($stmt, $fields);
}
/**
* Input is a prepared SQL statement and parameters
* Returns the SQL statement
*
* @param string $query
* @param array $parameters
* @return string
*/
private function prepare($query, $parameters)
{
if (!$parameters) {
$parameters = array();
} elseif (!is_array($parameters)) {
$parameters = array($parameters);
}
$this->paramNb = 0;
$this->params = & $parameters;
$query = preg_replace_callback('/\?/', array($this, 'replaceParam'), $query);
return $query;
}
public function replaceParam($match)
{
$param = & $this->params[$this->paramNb];
$this->paramNb++;
if ($param === null) {
return 'NULL';
} else {
return "'" . addslashes($param) . "'";
}
}
public function isErrNo($e, $errno)
{
return \Piwik\Db\Adapter\Mysqli::isMysqliErrorNumber($e, $this->connection, $errno);
}
/**
* Return number of affected rows in last query
*
* @param mixed $queryResult Result from query()
* @return int
*/
public function rowCount($queryResult)
{
if (!empty($queryResult) && is_object($queryResult) && $queryResult instanceof \mysqli_stmt) {
return $queryResult->affected_rows;
}
return mysqli_affected_rows($this->connection);
}
/**
* Start Transaction
* @return string TransactionID
*/
public function beginTransaction()
{
if (!$this->activeTransaction === false) {
return;
}
if ($this->connection->autocommit(false)) {
$this->activeTransaction = uniqid();
return $this->activeTransaction;
}
}
/**
* Commit Transaction
* @param $xid
* @throws DbException
* @internal param TransactionID $string from beginTransaction
*/
public function commit($xid)
{
if ($this->activeTransaction != $xid || $this->activeTransaction === false) {
return;
}
$this->activeTransaction = false;
if (!$this->connection->commit()) {
throw new DbException("Commit failed");
}
$this->connection->autocommit(true);
}
/**
* Rollback Transaction
* @param $xid
* @throws DbException
* @internal param TransactionID $string from beginTransaction
*/
public function rollBack($xid)
{
if ($this->activeTransaction != $xid || $this->activeTransaction === false) {
return;
}
$this->activeTransaction = false;
if (!$this->connection->rollback()) {
throw new DbException("Rollback failed");
}
$this->connection->autocommit(true);
}
}