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