PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.2.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.2.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Tracker / Db / Mysqli.php
matomo / app / core / Tracker / Db Last commit date
Pdo 1 year ago DbException.php 2 years ago Mysqli.php 1 year ago
Mysqli.php
407 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\Tracker\Db;
10
11 use Exception;
12 use Piwik\Tracker\Db;
13 /**
14 * mysqli wrapper
15 *
16 */
17 class Mysqli extends Db
18 {
19 protected $connection = null;
20 protected $host;
21 protected $port;
22 protected $socket;
23 protected $dbname;
24 protected $username;
25 protected $password;
26 protected $charset;
27 protected $collation;
28 protected $activeTransaction = \false;
29 protected $enable_ssl;
30 protected $ssl_key;
31 protected $ssl_cert;
32 protected $ssl_ca;
33 protected $ssl_ca_path;
34 protected $ssl_cipher;
35 protected $ssl_no_verify;
36 /**
37 * Builds the DB object
38 *
39 * @param array $dbInfo
40 * @param string $driverName
41 */
42 public function __construct($dbInfo, $driverName = 'mysql')
43 {
44 if (isset($dbInfo['unix_socket']) && substr($dbInfo['unix_socket'], 0, 1) == '/') {
45 $this->host = null;
46 $this->port = null;
47 $this->socket = $dbInfo['unix_socket'];
48 } elseif (isset($dbInfo['port']) && substr($dbInfo['port'], 0, 1) == '/') {
49 $this->host = null;
50 $this->port = null;
51 $this->socket = $dbInfo['port'];
52 } else {
53 $this->host = $dbInfo['host'];
54 $this->port = (int) $dbInfo['port'];
55 $this->socket = null;
56 }
57 $this->dbname = $dbInfo['dbname'];
58 $this->username = $dbInfo['username'];
59 $this->password = $dbInfo['password'];
60 $this->charset = $dbInfo['charset'] ?? null;
61 $this->collation = $dbInfo['collation'] ?? null;
62 if (!empty($dbInfo['enable_ssl'])) {
63 $this->enable_ssl = $dbInfo['enable_ssl'];
64 }
65 if (!empty($dbInfo['ssl_key'])) {
66 $this->ssl_key = $dbInfo['ssl_key'];
67 }
68 if (!empty($dbInfo['ssl_cert'])) {
69 $this->ssl_cert = $dbInfo['ssl_cert'];
70 }
71 if (!empty($dbInfo['ssl_ca'])) {
72 $this->ssl_ca = $dbInfo['ssl_ca'];
73 }
74 if (!empty($dbInfo['ssl_ca_path'])) {
75 $this->ssl_ca_path = $dbInfo['ssl_ca_path'];
76 }
77 if (!empty($dbInfo['ssl_cipher'])) {
78 $this->ssl_cipher = $dbInfo['ssl_cipher'];
79 }
80 if (!empty($dbInfo['ssl_no_verify'])) {
81 $this->ssl_no_verify = $dbInfo['ssl_no_verify'];
82 }
83 }
84 /**
85 * Destructor
86 */
87 public function __destruct()
88 {
89 $this->connection = null;
90 }
91 /**
92 * Connects to the DB
93 *
94 * @throws Exception|DbException if there was an error connecting the DB
95 */
96 public function connect()
97 {
98 if (self::$profiling) {
99 $timer = $this->initProfiler();
100 }
101 // The default error reporting of mysqli changed in PHP 8.1. To circumvent problems in our error handling we set
102 // the erroring reporting to the default that was used prior PHP 8.1
103 // See https://php.watch/versions/8.1/mysqli-error-mode for more details
104 mysqli_report(\MYSQLI_REPORT_OFF);
105 $this->connection = mysqli_init();
106 if ($this->enable_ssl) {
107 mysqli_ssl_set($this->connection, $this->ssl_key, $this->ssl_cert, $this->ssl_ca, $this->ssl_ca_path, $this->ssl_cipher);
108 }
109 // Make sure MySQL returns all matched rows on update queries including
110 // rows that actually didn't have to be updated because the values didn't
111 // change. This matches common behaviour among other database systems.
112 // See #6296 why this is important in tracker
113 $flags = \MYSQLI_CLIENT_FOUND_ROWS;
114 if ($this->enable_ssl) {
115 $flags = $flags | \MYSQLI_CLIENT_SSL;
116 }
117 if ($this->ssl_no_verify && defined('MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT')) {
118 $flags = $flags | \MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
119 }
120 mysqli_real_connect($this->connection, $this->host, $this->username, $this->password, $this->dbname, $this->port, $this->socket, $flags);
121 if (!$this->connection || mysqli_connect_errno()) {
122 throw new \Piwik\Tracker\Db\DbException("Connect failed: " . mysqli_connect_error());
123 }
124 if ($this->charset && $this->collation) {
125 // mysqli_set_charset does not support setting a collation
126 $query = "SET NAMES '" . $this->charset . "' COLLATE '" . $this->collation . "'";
127 if (!mysqli_query($this->connection, $query)) {
128 throw new \Piwik\Tracker\Db\DbException("Set charset/connection collation failed: " . mysqli_error($this->connection));
129 }
130 } elseif ($this->charset) {
131 if (!mysqli_set_charset($this->connection, $this->charset)) {
132 throw new \Piwik\Tracker\Db\DbException("Set Charset failed: " . mysqli_error($this->connection));
133 }
134 }
135 $this->password = '';
136 if (self::$profiling && isset($timer)) {
137 $this->recordQueryProfile('connect', $timer);
138 }
139 }
140 /**
141 * Disconnects from the server
142 */
143 public function disconnect()
144 {
145 mysqli_close($this->connection);
146 $this->connection = null;
147 }
148 /**
149 * @param \mysqli_stmt $stmt
150 * @param $fields
151 * @return array|bool|false
152 */
153 private function fetchResult($stmt, $fields)
154 {
155 $values = array_fill(0, count($fields), null);
156 $refs = array();
157 foreach ($values as $i => &$f) {
158 $refs[$i] =& $f;
159 }
160 call_user_func_array(array($stmt, 'bind_result'), $values);
161 $result = $stmt->fetch();
162 if ($result === null || $result === \false) {
163 $stmt->reset();
164 return \false;
165 }
166 $val = array();
167 foreach ($values as $key => $value) {
168 $val[] = $value;
169 }
170 $row = array_combine($fields, $values);
171 return $row;
172 }
173 /**
174 * Returns an array containing all the rows of a query result, using optional bound parameters.
175 *
176 * @see query()
177 *
178 * @param string $query Query
179 * @param array $parameters Parameters to bind
180 * @return array
181 * @throws Exception|DbException if an exception occurred
182 */
183 public function fetchAll($query, $parameters = array())
184 {
185 try {
186 if (self::$profiling) {
187 $timer = $this->initProfiler();
188 }
189 list($stmt, $fields) = $this->executeQuery($query, $parameters);
190 $rows = array();
191 while ($row = $this->fetchResult($stmt, $fields)) {
192 $rows[] = $row;
193 }
194 $stmt->free_result();
195 $stmt->close();
196 if (self::$profiling && isset($timer)) {
197 $this->recordQueryProfile($query, $timer);
198 }
199 return $rows;
200 } catch (Exception $e) {
201 throw new \Piwik\Tracker\Db\DbException("Error query: " . $e->getMessage());
202 }
203 }
204 /**
205 * Returns the first row of a query result, using optional bound parameters.
206 *
207 * @see query()
208 *
209 * @param string $query Query
210 * @param array $parameters Parameters to bind
211 *
212 * @return array
213 *
214 * @throws DbException if an exception occurred
215 */
216 public function fetch($query, $parameters = array())
217 {
218 try {
219 if (self::$profiling) {
220 $timer = $this->initProfiler();
221 }
222 list($stmt, $fields) = $this->executeQuery($query, $parameters);
223 $row = $this->fetchResult($stmt, $fields);
224 $stmt->free_result();
225 $stmt->close();
226 if (self::$profiling && isset($timer)) {
227 $this->recordQueryProfile($query, $timer);
228 }
229 if ($row === null) {
230 $row = \false;
231 }
232 return $row;
233 } catch (Exception $e) {
234 throw new \Piwik\Tracker\Db\DbException("Error query: " . $e->getMessage());
235 }
236 }
237 /**
238 * Executes a query, using optional bound parameters.
239 *
240 * @param string $query Query
241 * @param array|string $parameters Parameters to bind array('idsite'=> 1)
242 *
243 * @return bool|resource false if failed
244 * @throws DbException if an exception occurred
245 */
246 public function query($query, $parameters = array())
247 {
248 if (is_null($this->connection)) {
249 return \false;
250 }
251 try {
252 if (self::$profiling) {
253 $timer = $this->initProfiler();
254 }
255 list($stmt, $fields) = $this->executeQuery($query, $parameters);
256 if (self::$profiling && isset($timer)) {
257 $this->recordQueryProfile($query, $timer);
258 }
259 return $stmt;
260 } catch (Exception $e) {
261 throw new \Piwik\Tracker\Db\DbException("Error query: " . $e->getMessage() . "\n In query: {$query}\n Parameters: " . var_export($parameters, \true), $e->getCode());
262 }
263 }
264 /**
265 * Returns the last inserted ID in the DB
266 *
267 * @return int
268 */
269 public function lastInsertId()
270 {
271 return mysqli_insert_id($this->connection);
272 }
273 private function executeQuery($sql, $bind)
274 {
275 $stmt = mysqli_prepare($this->connection, $sql);
276 if (!$stmt) {
277 throw new \Piwik\Tracker\Db\DbException('preparing query failed: ' . mysqli_error($this->connection) . ' : ' . $sql);
278 }
279 if (!is_array($bind)) {
280 $bind = array($bind);
281 }
282 if (!empty($bind)) {
283 array_unshift($bind, str_repeat('s', count($bind)));
284 $refs = array();
285 foreach ($bind as $key => $value) {
286 $refs[$key] =& $bind[$key];
287 }
288 call_user_func_array(array($stmt, 'bind_param'), $refs);
289 }
290 $stmtResult = $stmt->execute();
291 if ($stmtResult === \false) {
292 throw new \Piwik\Tracker\Db\DbException("Mysqli statement execute error : " . $stmt->error, $stmt->errno);
293 }
294 if (!empty($stmt->error)) {
295 throw new \Piwik\Tracker\Db\DbException('executeQuery() failed: ' . mysqli_error($this->connection) . ' : ' . $sql);
296 }
297 $metaResults = $stmt->result_metadata();
298 if ($stmt->errno) {
299 throw new \Piwik\Tracker\Db\DbException("Mysqli statement metadata error: " . $stmt->error, $stmt->errno);
300 }
301 $fields = array();
302 if ($metaResults) {
303 $fetchedFields = $metaResults->fetch_fields();
304 foreach ($fetchedFields as $fetchedField) {
305 $fields[] = $fetchedField->name;
306 }
307 $stmt->store_result();
308 }
309 return array($stmt, $fields);
310 }
311 /**
312 * Input is a prepared SQL statement and parameters
313 * Returns the SQL statement
314 *
315 * @param string $query
316 * @param array $parameters
317 * @return string
318 */
319 private function prepare($query, $parameters)
320 {
321 if (!$parameters) {
322 $parameters = array();
323 } elseif (!is_array($parameters)) {
324 $parameters = array($parameters);
325 }
326 $this->paramNb = 0;
327 $this->params =& $parameters;
328 $query = preg_replace_callback('/\\?/', array($this, 'replaceParam'), $query);
329 return $query;
330 }
331 public function replaceParam($match)
332 {
333 $param =& $this->params[$this->paramNb];
334 $this->paramNb++;
335 if ($param === null) {
336 return 'NULL';
337 } else {
338 return "'" . addslashes($param) . "'";
339 }
340 }
341 public function isErrNo($e, $errno)
342 {
343 return \Piwik\Db\Adapter\Mysqli::isMysqliErrorNumber($e, $this->connection, $errno);
344 }
345 /**
346 * Return number of affected rows in last query
347 *
348 * @param mixed $queryResult Result from query()
349 * @return int
350 */
351 public function rowCount($queryResult)
352 {
353 if (!empty($queryResult) && is_object($queryResult) && $queryResult instanceof \mysqli_stmt) {
354 return $queryResult->affected_rows;
355 }
356 return mysqli_affected_rows($this->connection);
357 }
358 /**
359 * Start Transaction
360 * @return string TransactionID
361 */
362 public function beginTransaction()
363 {
364 if (!$this->activeTransaction === \false) {
365 return;
366 }
367 if ($this->connection->autocommit(\false)) {
368 $this->activeTransaction = uniqid();
369 return $this->activeTransaction;
370 }
371 }
372 /**
373 * Commit Transaction
374 * @param $xid
375 * @throws DbException
376 * @internal param TransactionID $string from beginTransaction
377 */
378 public function commit($xid)
379 {
380 if ($this->activeTransaction != $xid || $this->activeTransaction === \false) {
381 return;
382 }
383 $this->activeTransaction = \false;
384 if (!$this->connection->commit()) {
385 throw new \Piwik\Tracker\Db\DbException("Commit failed");
386 }
387 $this->connection->autocommit(\true);
388 }
389 /**
390 * Rollback Transaction
391 * @param $xid
392 * @throws DbException
393 * @internal param TransactionID $string from beginTransaction
394 */
395 public function rollBack($xid)
396 {
397 if ($this->activeTransaction != $xid || $this->activeTransaction === \false) {
398 return;
399 }
400 $this->activeTransaction = \false;
401 if (!$this->connection->rollback()) {
402 throw new \Piwik\Tracker\Db\DbException("Rollback failed");
403 }
404 $this->connection->autocommit(\true);
405 }
406 }
407