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