Prenvents '##' to be escaped to '' in SQL querries (Closes #1156).
[platal.git] / classes / xdb.php
CommitLineData
0337d704 1<?php
2/***************************************************************************
2ab75571 3 * Copyright (C) 2003-2010 Polytechnique.org *
0337d704 4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 **************************************************************************/
21
08cce2ff 22class XDB
0337d704 23{
32d9ae72 24 private static $mysqli = null;
737f1378 25 private static $fatalErrors = true;
32d9ae72 26
821744e0 27 public static function connect()
32d9ae72 28 {
821744e0 29 global $globals;
e4f6c7d0 30 self::$mysqli = new mysqli($globals->dbhost, $globals->dbuser, $globals->dbpwd, $globals->dbdb);
81e9c63f 31 if ($globals->debug & DEBUG_BT) {
d3f26be9 32 $bt = new PlBacktrace('MySQL');
33 if (mysqli_connect_errno()) {
34 $bt->newEvent("MySQLI connection", 0, mysqli_connect_error());
35 return false;
36 }
32d9ae72 37 }
e4f6c7d0
FB
38 self::$mysqli->autocommit(true);
39 self::$mysqli->set_charset($globals->dbcharset);
32d9ae72 40 return true;
41 }
f1ca33de 42
737f1378
FB
43 public static function setNonFatalError()
44 {
45 self::$fatalErrors = false;
46 }
47
ed3f4d3e 48 public static function _prepare($args)
49 {
4d6eeacc 50 global $globals;
f62bd784 51 $query = array_map(Array('XDB', 'escape'), $args);
f3e3cab8 52 $query[0] = preg_replace('/#([a-z0-9]+)#/', $globals->dbprefix . '$1', $args[0]);
4d6eeacc
VZ
53 $query[0] = str_replace('%', '%%', $query[0]);
54 $query[0] = str_replace('{?}', '%s', $query[0]);
0337d704 55 return call_user_func_array('sprintf', $query);
56 }
13a25546 57
6995a9b9 58 public static function _reformatQuery($query)
7c571120 59 {
a4f89886 60 $query = preg_split("/\n\\s*/", trim($query));
7c571120 61 $length = 0;
d3c52d30 62 foreach ($query as $key=>$line) {
63 $local = -2;
a14159bf 64 if (preg_match('/^([A-Z]+(?:\s+(?:JOIN|BY|FROM|INTO))?)\s+(.*)/u', $line, $matches)
85d3b330 65 && $matches[1] != 'AND' && $matches[1] != 'OR')
66 {
d3c52d30 67 $local = strlen($matches[1]);
68 $line = $matches[1] . ' ' . $matches[2];
69 $length = max($length, $local);
7c571120 70 }
d3c52d30 71 $query[$key] = array($line, $local);
7c571120 72 }
73 $res = '';
d3c52d30 74 foreach ($query as $array) {
75 list($line, $local) = $array;
9630c649 76 $local = max(0, $length - $local);
7c571120 77 $res .= str_repeat(' ', $local) . $line . "\n";
78 $length += 2 * (substr_count($line, '(') - substr_count($line, ')'));
79 }
80 return $res;
81 }
82
ed3f4d3e 83 public static function _query($query)
84 {
f1ca33de 85 global $globals;
86
e4f6c7d0 87 if (!self::$mysqli && !self::connect()) {
12ccfec7 88 header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error');
084a60da
FB
89 Platal::page()->kill('Impossible de se connecter à la base de données.');
90 exit;
821744e0 91 }
92
81e9c63f 93 if ($globals->debug & DEBUG_BT) {
f1ca33de 94 $explain = array();
5fb22b39 95 if (strpos($query, 'FOUND_ROWS()') === false) {
e4f6c7d0 96 $res = self::$mysqli->query("EXPLAIN $query");
32d9ae72 97 if ($res) {
98 while ($row = $res->fetch_assoc()) {
5fb22b39 99 $explain[] = $row;
100 }
32d9ae72 101 $res->free();
5fb22b39 102 }
f1ca33de 103 }
d3f26be9 104 PlBacktrace::$bt['MySQL']->start(XDB::_reformatQuery($query));
f1ca33de 105 }
106
32d9ae72 107 $res = XDB::$mysqli->query($query);
0381e170 108
81e9c63f 109 if ($globals->debug & DEBUG_BT) {
e4f6c7d0
FB
110 PlBacktrace::$bt['MySQL']->stop(@$res->num_rows ? $res->num_rows : self::$mysqli->affected_rows,
111 self::$mysqli->error,
d3f26be9 112 $explain);
f1ca33de 113 }
084a60da
FB
114
115 if ($res === false) {
12ccfec7 116 header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error');
084a60da
FB
117 if (strpos($query, 'INSERT') === false && strpos($query, 'UPDATE') === false
118 && strpos($query, 'REPLACE') === false && strpos($query, 'DELETE') === false) {
7f10bc61 119 $text = 'Erreur lors de l\'interrogation de la base de données';
084a60da 120 } else {
7f10bc61 121 $text = 'Erreur lors de l\'écriture dans la base de données';
084a60da 122 }
1023ed57
FB
123 if (php_sapi_name() == 'cli') {
124 $text .= "\n" . XDB::_reformatQuery($query)
125 . "\n" . XDB::$mysqli->error;
126 } else if ($globals->debug) {
7f10bc61 127 $text .= '<pre>' . pl_entities(XDB::_reformatQuery($query)) . '</pre>';
f8eaef22
FB
128 } else {
129 $file = fopen($globals->spoolroot . '/spool/tmp/query_errors', 'a');
ded3ae94
SJ
130 fwrite($file, '<pre>' . date("Y-m-d G:i:s") . '</pre>'
131 . '<pre>' . pl_entities(XDB::_reformatQuery($query)) . '</pre>'
132 . '<pre>' . XDB::$mysqli->error . '</pre>'
133 . "--------------------------------------------------------------------------------\n");
f8eaef22 134 fclose($file);
7f10bc61 135 }
737f1378
FB
136 if (self::$fatalErrors) {
137 Platal::page()->kill($text);
138 exit;
139 } else {
140 throw new Exception($text . " :\n" . $query);
141 }
084a60da 142 }
f1ca33de 143 return $res;
144 }
145
e4f6c7d0
FB
146 private static function queryv($query)
147 {
148 return new XOrgDBResult(self::_prepare($query));
149 }
150
6995a9b9 151 public static function query()
0337d704 152 {
e4f6c7d0 153 return self::queryv(func_get_args());
0337d704 154 }
155
20973bf8
FB
156 public static function format()
157 {
e4f6c7d0 158 return self::_prepare(func_get_args());
20973bf8
FB
159 }
160
0ef5bd4b
FB
161 // Produce the SQL statement for setting/unsetting a flag
162 public static function changeFlag($fieldname, $flagname, $state)
163 {
164 if ($state) {
165 return XDB::format($fieldname . ' = CONCAT({?}, \',\', ' . $fieldname . ')', $flagname);
166 } else {
167 return XDB::format($fieldname . ' = REPLACE(' . $fieldname . ', {?}, \'\')', $flagname);
168 }
169 }
170
171 // Produce the SQL statement representing an array
172 public static function formatArray(array $array)
173 {
e677bc13 174 return self::escape($array);
0ef5bd4b
FB
175 }
176
adf947ff
RB
177 const WILDCARD_EXACT = 0x00;
178 const WILDCARD_PREFIX = 0x01;
179 const WILDCARD_SUFFIX = 0x02;
180 const WILDCARD_CONTAINS = 0x03; // WILDCARD_PREFIX | WILDCARD_SUFFIX
181
182 // Returns the SQL statement for a wildcard search.
183 public static function formatWildcards($mode, $text)
184 {
185 if ($mode == self::WILDCARD_EXACT) {
186 return XDB::format(' = {?}', $text);
187 } else {
188 $text = str_replace(array('%', '_'), array('\%', '\_'), $text);
189 if ($mode & self::WILDCARD_PREFIX) {
190 $text = $text . '%';
191 }
192 if ($mode & self::WILDCARD_SUFFIX) {
193 $text = '%' . $text;
194 }
195 return XDB::format(" LIKE {?}", $text);
196 }
197 }
198
47595f9a
RB
199 // Returns a FIELD(blah, 3, 1, 2) for use in an order with custom orders
200 public static function formatCustomOrder($field, $values)
201 {
29bd16df 202 return 'FIELD( ' . $field . ', ' . implode(', ', array_map(array('XDB', 'escape'), $values)) . ')';
47595f9a
RB
203 }
204
6995a9b9 205 public static function execute()
f1ca33de 206 {
fe556813
FB
207 global $globals;
208 $args = func_get_args();
209 if ($globals->mode != 'rw' && !strpos($args[0], 'logger')) {
210 return;
211 }
e4f6c7d0 212 return self::_query(XDB::_prepare($args));
0337d704 213 }
13a25546 214
6995a9b9 215 public static function iterator()
0337d704 216 {
e4f6c7d0 217 return new XOrgDBIterator(self::_prepare(func_get_args()));
0337d704 218 }
13a25546 219
6995a9b9 220 public static function iterRow()
0337d704 221 {
e4f6c7d0
FB
222 return new XOrgDBIterator(self::_prepare(func_get_args()), MYSQL_NUM);
223 }
224
225 private static function findQuery($params, $default = array())
226 {
227 for ($i = 0 ; $i < count($default) ; ++$i) {
228 $is_query = false;
229 foreach (array('insert', 'select', 'replace', 'delete', 'update') as $kwd) {
230 if (stripos($params[0], $kwd) !== false) {
231 $is_query = true;
232 break;
233 }
234 }
235 if ($is_query) {
236 break;
237 } else {
238 $default[$i] = array_shift($params);
239 }
240 }
241 return array($default, $params);
242 }
243
244 /** Fetch all rows returned by the given query.
245 * This functions can take 2 optional arguments (cf XOrgDBResult::fetchAllRow()).
246 * Optional arguments are given *before* the query.
247 */
248 public static function fetchAllRow()
249 {
250 list($args, $query) = self::findQuery(func_get_args(), array(false, false));
251 return self::queryv($query)->fetchAllRow($args[0], $args[1]);
252 }
253
254 /** Fetch all rows returned by the given query.
255 * This functions can take 2 optional arguments (cf XOrgDBResult::fetchAllAssoc()).
256 * Optional arguments are given *before* the query.
257 */
258 public static function fetchAllAssoc()
259 {
260 list($args, $query) = self::findQuery(func_get_args(), array(false, false));
261 return self::queryv($query)->fetchAllAssoc($args[0], $args[1]);
262 }
263
264 public static function fetchOneCell()
265 {
266 list($args, $query) = self::findQuery(func_get_args());
267 return self::queryv($query)->fetchOneCell();
268 }
269
270 public static function fetchOneRow()
271 {
272 list($args, $query) = self::findQuery(func_get_args());
273 return self::queryv($query)->fetchOneRow();
274 }
275
276 public static function fetchOneAssoc()
277 {
278 list($args, $query) = self::findQuery(func_get_args());
279 return self::queryv($query)->fetchOneAssoc();
280 }
281
282 /** Fetch a column from the result of the given query.
283 * This functions can take 1 optional arguments (cf XOrgDBResult::fetchColumn()).
284 * Optional arguments are given *before* the query.
285 */
286 public static function fetchColumn()
287 {
288 list($args, $query) = self::findQuery(func_get_args(), array(0));
289 return self::queryv($query)->fetchColumn();
0337d704 290 }
13a25546 291
6995a9b9 292 public static function insertId()
13a25546 293 {
e4f6c7d0 294 return self::$mysqli->insert_id;
13a25546 295 }
296
0380bf85 297 public static function errno()
298 {
e4f6c7d0 299 return self::$mysqli->errno;
0380bf85 300 }
301
302 public static function error()
834fd0f6 303 {
e4f6c7d0 304 return self::$mysqli->error;
0380bf85 305 }
306
307 public static function affectedRows()
308 {
e4f6c7d0 309 return self::$mysqli->affected_rows;
0380bf85 310 }
311
f62bd784 312 public static function escape($var)
0337d704 313 {
314 switch (gettype($var)) {
13a25546 315 case 'boolean':
316 return $var ? 1 : 0;
317
318 case 'integer':
319 case 'double':
320 case 'float':
321 return $var;
322
323 case 'string':
324 return "'".addslashes($var)."'";
325
326 case 'NULL':
327 return 'NULL';
328
329 case 'object':
113f6de8 330 if ($var instanceof PlFlagSet) {
04c1b2eb 331 return "'" . addslashes($var->flags()) . "'";
e677bc13
FB
332 } else {
333 return "'".addslashes(serialize($var))."'";
04c1b2eb 334 }
e677bc13 335
13a25546 336 case 'array':
e677bc13 337 return '(' . implode(', ', array_map(array('XDB', 'escape'), $var)) . ')';
13a25546 338
339 default:
340 die(var_export($var, true).' is not a valid for a database entry');
0337d704 341 }
342 }
0337d704 343}
344
0337d704 345class XOrgDBResult
346{
0337d704 347
32d9ae72 348 private $_res;
0337d704 349
0381e170 350 public function __construct($query)
0337d704 351 {
755abda6 352 $this->_res = XDB::_query($query);
0337d704 353 }
354
0381e170 355 public function free()
0337d704 356 {
0381e170 357 if ($this->_res) {
358 $this->_res->free();
359 }
0337d704 360 unset($this);
361 }
362
0381e170 363 protected function _fetchRow()
0337d704 364 {
0381e170 365 return $this->_res ? $this->_res->fetch_row() : null;
0337d704 366 }
367
0381e170 368 protected function _fetchAssoc()
0337d704 369 {
0381e170 370 return $this->_res ? $this->_res->fetch_assoc() : null;
0337d704 371 }
372
e4f6c7d0 373 public function fetchAllRow($id = false, $keep_array = false)
0337d704 374 {
375 $result = Array();
0381e170 376 if (!$this->_res) {
377 return $result;
378 }
e4f6c7d0
FB
379 while (($data = $this->_res->fetch_row())) {
380 if ($id !== false) {
381 $key = $data[$id];
382 unset($data[$id]);
383 if (!$keep_array && count($data) == 1) {
384 reset($data);
385 $result[$key] = current($data);
386 } else {
387 $result[$key] = $data;
388 }
389 } else {
390 $result[] = $data;
391 }
392 }
0337d704 393 $this->free();
394 return $result;
395 }
396
e4f6c7d0 397 public function fetchAllAssoc($id = false, $keep_array = false)
0337d704 398 {
399 $result = Array();
0381e170 400 if (!$this->_res) {
401 return $result;
402 }
e4f6c7d0
FB
403 while (($data = $this->_res->fetch_assoc())) {
404 if ($id !== false) {
405 $key = $data[$id];
406 unset($data[$id]);
407 if (!$keep_array && count($data) == 1) {
408 reset($data);
409 $result[$key] = current($data);
410 } else {
411 $result[$key] = $data;
412 }
413 } else {
414 $result[] = $data;
415 }
416 }
0337d704 417 $this->free();
418 return $result;
419 }
420
0381e170 421 public function fetchOneAssoc()
0337d704 422 {
423 $tmp = $this->_fetchAssoc();
424 $this->free();
425 return $tmp;
426 }
427
0381e170 428 public function fetchOneRow()
0337d704 429 {
430 $tmp = $this->_fetchRow();
431 $this->free();
432 return $tmp;
433 }
434
0381e170 435 public function fetchOneCell()
0337d704 436 {
437 $tmp = $this->_fetchRow();
438 $this->free();
439 return $tmp[0];
440 }
441
0381e170 442 public function fetchColumn($key = 0)
0337d704 443 {
444 $res = Array();
445 if (is_numeric($key)) {
446 while($tmp = $this->_fetchRow()) {
447 $res[] = $tmp[$key];
448 }
449 } else {
450 while($tmp = $this->_fetchAssoc()) {
451 $res[] = $tmp[$key];
452 }
453 }
454 $this->free();
455 return $res;
456 }
457
0381e170 458 public function fetchOneField()
0380bf85 459 {
0381e170 460 return $this->_res ? $this->_res->fetch_field() : null;
0380bf85 461 }
462
0381e170 463 public function fetchFields()
0380bf85 464 {
465 $res = array();
466 while ($res[] = $this->fetchOneField());
467 return $res;
468 }
469
0381e170 470 public function numRows()
0337d704 471 {
0381e170 472 return $this->_res ? $this->_res->num_rows : 0;
0337d704 473 }
0380bf85 474
0381e170 475 public function fieldCount()
0380bf85 476 {
0381e170 477 return $this->_res ? $this->_res->field_count : 0;
0380bf85 478 }
0337d704 479}
480
2b1ee50b 481require_once dirname(__FILE__) . '/pliterator.php';
482
0381e170 483class XOrgDBIterator extends XOrgDBResult implements PlIterator
0337d704 484{
85d3b330 485 private $_result;
486 private $_pos;
487 private $_total;
0380bf85 488 private $_fpos;
489 private $_fields;
85d3b330 490 private $_mode = MYSQL_ASSOC;
0337d704 491
0381e170 492 public function __construct($query, $mode = MYSQL_ASSOC)
0337d704 493 {
0381e170 494 parent::__construct($query);
0337d704 495 $this->_pos = 0;
0381e170 496 $this->_total = $this->numRows();
0380bf85 497 $this->_fpost = 0;
0381e170 498 $this->_fields = $this->fieldCount();
0337d704 499 $this->_mode = $mode;
500 }
501
0381e170 502 public function next()
0337d704 503 {
504 $this->_pos ++;
505 if ($this->_pos > $this->_total) {
0381e170 506 $this->free();
0337d704 507 unset($this);
508 return null;
509 }
0381e170 510 return $this->_mode != MYSQL_ASSOC ? $this->_fetchRow() : $this->_fetchAssoc();
0337d704 511 }
512
0381e170 513 public function first()
0337d704 514 {
515 return $this->_pos == 1;
516 }
517
0381e170 518 public function last()
0337d704 519 {
0380bf85 520 return $this->_pos == $this->_total;
0337d704 521 }
522
0381e170 523 public function total()
0337d704 524 {
525 return $this->_total;
526 }
0380bf85 527
0381e170 528 public function nextField()
0380bf85 529 {
530 $this->_fpos++;
531 if ($this->_fpos > $this->_fields) {
532 return null;
533 }
0381e170 534 return $this->fetchOneField();
0380bf85 535 }
536
0381e170 537 public function firstField()
0380bf85 538 {
539 return $this->_fpos == 1;
540 }
541
0381e170 542 public function lastField()
0380bf85 543 {
544 return $this->_fpos == $this->_fields;
545 }
546
0381e170 547 public function totalFields()
0380bf85 548 {
549 return $this->_fields;
550 }
0337d704 551}
552
a7de4ef7 553// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
0337d704 554?>