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