]> git.mxchange.org Git - friendica.git/blob - include/dba.php
Fixed settings for test mysql database and updated documentation
[friendica.git] / include / dba.php
1 <?php
2 use \Friendica\Core\System;
3
4 require_once("dbm.php");
5 require_once('include/datetime.php');
6
7 /**
8  * @class MySQL database class
9  *
10  * For debugging, insert 'dbg(1);' anywhere in the program flow.
11  * dbg(0); will turn it off. Logging is performed at LOGGER_DATA level.
12  * When logging, all binary info is converted to text and html entities are escaped so that
13  * the debugging stream is safe to view within both terminals and web pages.
14  *
15  * This class is for the low level database stuff that does driver specific things.
16  */
17
18 class dba {
19
20         private $debug = 0;
21         private $db;
22         private $result;
23         private $driver;
24         public  $connected = false;
25         public  $error = false;
26         public  $errorno = 0;
27         public  $affected_rows = 0;
28         private $_server_info = '';
29         private static $in_transaction = false;
30         private static $dbo;
31         private static $relation = array();
32
33         function __construct($serveraddr, $user, $pass, $db, $install = false) {
34                 $a = get_app();
35
36                 $stamp1 = microtime(true);
37
38                 $serveraddr = trim($serveraddr);
39
40                 $serverdata = explode(':', $serveraddr);
41                 $server = $serverdata[0];
42
43                 if (count($serverdata) > 1) {
44                         $port = trim($serverdata[1]);
45                 }
46
47                 $server = trim($server);
48                 $user = trim($user);
49                 $pass = trim($pass);
50                 $db = trim($db);
51
52                 if (!(strlen($server) && strlen($user))) {
53                         $this->connected = false;
54                         $this->db = null;
55                         return;
56                 }
57
58                 if ($install) {
59                         if (strlen($server) && ($server !== 'localhost') && ($server !== '127.0.0.1')) {
60                                 if (! dns_get_record($server, DNS_A + DNS_CNAME + DNS_PTR)) {
61                                         $this->error = sprintf(t('Cannot locate DNS info for database server \'%s\''), $server);
62                                         $this->connected = false;
63                                         $this->db = null;
64                                         return;
65                                 }
66                         }
67                 }
68
69                 if (class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
70                         $this->driver = 'pdo';
71                         $connect = "mysql:host=".$server.";dbname=".$db;
72
73                         if (isset($port)) {
74                                 $connect .= ";port=".$port;
75                         }
76
77                         if (isset($a->config["system"]["db_charset"])) {
78                                 $connect .= ";charset=".$a->config["system"]["db_charset"];
79                         }
80                         try {
81                                 $this->db = @new PDO($connect, $user, $pass);
82                                 $this->connected = true;
83                         } catch (PDOException $e) {
84                                 $this->connected = false;
85                         }
86                 }
87
88                 if (!$this->connected && class_exists('mysqli')) {
89                         $this->driver = 'mysqli';
90                         $this->db = @new mysqli($server, $user, $pass, $db, $port);
91                         if (!mysqli_connect_errno()) {
92                                 $this->connected = true;
93
94                                 if (isset($a->config["system"]["db_charset"])) {
95                                         $this->db->set_charset($a->config["system"]["db_charset"]);
96                                 }
97                         }
98                 }
99
100                 if (!$this->connected && function_exists('mysql_connect')) {
101                         $this->driver = 'mysql';
102                         $this->db = mysql_connect($serveraddr, $user, $pass);
103                         if ($this->db && mysql_select_db($db, $this->db)) {
104                                 $this->connected = true;
105
106                                 if (isset($a->config["system"]["db_charset"])) {
107                                         mysql_set_charset($a->config["system"]["db_charset"], $this->db);
108                                 }
109                         }
110                 }
111
112                 // No suitable SQL driver was found.
113                 if (!$this->connected) {
114                         $this->db = null;
115                         if (!$install) {
116                                 system_unavailable();
117                         }
118                 }
119                 $a->save_timestamp($stamp1, "network");
120
121                 self::$dbo = $this;
122         }
123
124         /**
125          * @brief Returns the MySQL server version string
126          *
127          * This function discriminate between the deprecated mysql API and the current
128          * object-oriented mysqli API. Example of returned string: 5.5.46-0+deb8u1
129          *
130          * @return string
131          */
132         public function server_info() {
133                 if ($this->_server_info == '') {
134                         switch ($this->driver) {
135                                 case 'pdo':
136                                         $this->_server_info = $this->db->getAttribute(PDO::ATTR_SERVER_VERSION);
137                                         break;
138                                 case 'mysqli':
139                                         $this->_server_info = $this->db->server_info;
140                                         break;
141                                 case 'mysql':
142                                         $this->_server_info = mysql_get_server_info($this->db);
143                                         break;
144                         }
145                 }
146                 return $this->_server_info;
147         }
148
149         /**
150          * @brief Returns the selected database name
151          *
152          * @return string
153          */
154         public function database_name() {
155                 $r = $this->q("SELECT DATABASE() AS `db`");
156
157                 return $r[0]['db'];
158         }
159
160         /**
161          * @brief Analyze a database query and log this if some conditions are met.
162          *
163          * @param string $query The database query that will be analyzed
164          */
165         public function log_index($query) {
166                 $a = get_app();
167
168                 if (empty($a->config["system"]["db_log_index"])) {
169                         return;
170                 }
171
172                 // Don't explain an explain statement
173                 if (strtolower(substr($query, 0, 7)) == "explain") {
174                         return;
175                 }
176
177                 // Only do the explain on "select", "update" and "delete"
178                 if (!in_array(strtolower(substr($query, 0, 6)), array("select", "update", "delete"))) {
179                         return;
180                 }
181
182                 $r = $this->q("EXPLAIN ".$query);
183                 if (!dbm::is_result($r)) {
184                         return;
185                 }
186
187                 $watchlist = explode(',', $a->config["system"]["db_log_index_watch"]);
188                 $blacklist = explode(',', $a->config["system"]["db_log_index_blacklist"]);
189
190                 foreach ($r AS $row) {
191                         if ((intval($a->config["system"]["db_loglimit_index"]) > 0)) {
192                                 $log = (in_array($row['key'], $watchlist) &&
193                                         ($row['rows'] >= intval($a->config["system"]["db_loglimit_index"])));
194                         } else {
195                                 $log = false;
196                         }
197
198                         if ((intval($a->config["system"]["db_loglimit_index_high"]) > 0) && ($row['rows'] >= intval($a->config["system"]["db_loglimit_index_high"]))) {
199                                 $log = true;
200                         }
201
202                         if (in_array($row['key'], $blacklist) || ($row['key'] == "")) {
203                                 $log = false;
204                         }
205
206                         if ($log) {
207                                 $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
208                                 @file_put_contents($a->config["system"]["db_log_index"], datetime_convert()."\t".
209                                                 $row['key']."\t".$row['rows']."\t".$row['Extra']."\t".
210                                                 basename($backtrace[1]["file"])."\t".
211                                                 $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
212                                                 substr($query, 0, 2000)."\n", FILE_APPEND);
213                         }
214                 }
215         }
216
217         public function q($sql, $onlyquery = false) {
218                 $a = get_app();
219
220                 if (!$this->db || !$this->connected) {
221                         return false;
222                 }
223
224                 $this->error = '';
225
226                 $connstr = ($this->connected() ? "Connected" : "Disonnected");
227
228                 $stamp1 = microtime(true);
229
230                 $orig_sql = $sql;
231
232                 if (x($a->config,'system') && x($a->config['system'], 'db_callstack')) {
233                         $sql = "/*".System::callstack()." */ ".$sql;
234                 }
235
236                 $columns = 0;
237
238                 switch ($this->driver) {
239                         case 'pdo':
240                                 $result = @$this->db->query($sql);
241                                 // Is used to separate between queries that returning data - or not
242                                 if (!is_bool($result)) {
243                                         $columns = $result->columnCount();
244                                 }
245                                 break;
246                         case 'mysqli':
247                                 $result = @$this->db->query($sql);
248                                 break;
249                         case 'mysql':
250                                 $result = @mysql_query($sql,$this->db);
251                                 break;
252                 }
253                 $stamp2 = microtime(true);
254                 $duration = (float)($stamp2 - $stamp1);
255
256                 $a->save_timestamp($stamp1, "database");
257
258                 if (strtolower(substr($orig_sql, 0, 6)) != "select") {
259                         $a->save_timestamp($stamp1, "database_write");
260                 }
261                 if (x($a->config,'system') && x($a->config['system'],'db_log')) {
262                         if (($duration > $a->config["system"]["db_loglimit"])) {
263                                 $duration = round($duration, 3);
264                                 $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
265                                 @file_put_contents($a->config["system"]["db_log"], datetime_convert()."\t".$duration."\t".
266                                                 basename($backtrace[1]["file"])."\t".
267                                                 $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
268                                                 substr($sql, 0, 2000)."\n", FILE_APPEND);
269                         }
270                 }
271
272                 switch ($this->driver) {
273                         case 'pdo':
274                                 $errorInfo = $this->db->errorInfo();
275                                 if ($errorInfo) {
276                                         $this->error = $errorInfo[2];
277                                         $this->errorno = $errorInfo[1];
278                                 }
279                                 break;
280                         case 'mysqli':
281                                 if ($this->db->errno) {
282                                         $this->error = $this->db->error;
283                                         $this->errorno = $this->db->errno;
284                                 }
285                                 break;
286                         case 'mysql':
287                                 if (mysql_errno($this->db)) {
288                                         $this->error = mysql_error($this->db);
289                                         $this->errorno = mysql_errno($this->db);
290                                 }
291                                 break;
292                 }
293                 if (strlen($this->error)) {
294                         logger('DB Error ('.$connstr.') '.$this->errorno.': '.$this->error);
295                 }
296
297                 if ($this->debug) {
298
299                         $mesg = '';
300
301                         if ($result === false) {
302                                 $mesg = 'false';
303                         } elseif ($result === true) {
304                                 $mesg = 'true';
305                         } else {
306                                 switch ($this->driver) {
307                                         case 'pdo':
308                                                 $mesg = $result->rowCount().' results'.EOL;
309                                                 break;
310                                         case 'mysqli':
311                                                 $mesg = $result->num_rows.' results'.EOL;
312                                                 break;
313                                         case 'mysql':
314                                                 $mesg = mysql_num_rows($result).' results'.EOL;
315                                                 break;
316                                 }
317                         }
318
319                         $str =  'SQL = ' . printable($sql) . EOL . 'SQL returned ' . $mesg
320                                 . (($this->error) ? ' error: ' . $this->error : '')
321                                 . EOL;
322
323                         logger('dba: ' . $str );
324                 }
325
326                 /**
327                  * If dbfail.out exists, we will write any failed calls directly to it,
328                  * regardless of any logging that may or may nor be in effect.
329                  * These usually indicate SQL syntax errors that need to be resolved.
330                  */
331
332                 if ($result === false) {
333                         logger('dba: ' . printable($sql) . ' returned false.' . "\n" . $this->error);
334                         if (file_exists('dbfail.out')) {
335                                 file_put_contents('dbfail.out', datetime_convert() . "\n" . printable($sql) . ' returned false' . "\n" . $this->error . "\n", FILE_APPEND);
336                         }
337                 }
338
339                 if (is_bool($result)) {
340                         return $result;
341                 }
342                 if ($onlyquery) {
343                         $this->result = $result;
344                         return true;
345                 }
346
347                 $r = array();
348                 switch ($this->driver) {
349                         case 'pdo':
350                                 while ($x = $result->fetch(PDO::FETCH_ASSOC)) {
351                                         $r[] = $x;
352                                 }
353                                 $result->closeCursor();
354                                 break;
355                         case 'mysqli':
356                                 while ($x = $result->fetch_array(MYSQLI_ASSOC)) {
357                                         $r[] = $x;
358                                 }
359                                 $result->free_result();
360                                 break;
361                         case 'mysql':
362                                 while ($x = mysql_fetch_array($result, MYSQL_ASSOC)) {
363                                         $r[] = $x;
364                                 }
365                                 mysql_free_result($result);
366                                 break;
367                 }
368
369                 // PDO doesn't return "true" on successful operations - like mysqli does
370                 // Emulate this behaviour by checking if the query returned data and had columns
371                 // This should be reliable enough
372                 if (($this->driver == 'pdo') && (count($r) == 0) && ($columns == 0)) {
373                         return true;
374                 }
375
376                 //$a->save_timestamp($stamp1, "database");
377
378                 if ($this->debug) {
379                         logger('dba: ' . printable(print_r($r, true)));
380                 }
381                 return($r);
382         }
383
384         public function dbg($dbg) {
385                 $this->debug = $dbg;
386         }
387
388         public function escape($str) {
389                 if ($this->db && $this->connected) {
390                         switch ($this->driver) {
391                                 case 'pdo':
392                                         return substr(@$this->db->quote($str, PDO::PARAM_STR), 1, -1);
393                                 case 'mysqli':
394                                         return @$this->db->real_escape_string($str);
395                                 case 'mysql':
396                                         return @mysql_real_escape_string($str,$this->db);
397                         }
398                 }
399         }
400
401         function connected() {
402                 switch ($this->driver) {
403                         case 'pdo':
404                                 // Not sure if this really is working like expected
405                                 $connected = ($this->db->getAttribute(PDO::ATTR_CONNECTION_STATUS) != "");
406                                 break;
407                         case 'mysqli':
408                                 $connected = $this->db->ping();
409                                 break;
410                         case 'mysql':
411                                 $connected = mysql_ping($this->db);
412                                 break;
413                 }
414                 return $connected;
415         }
416
417         function __destruct() {
418                 if ($this->db) {
419                         switch ($this->driver) {
420                                 case 'pdo':
421                                         $this->db = null;
422                                         break;
423                                 case 'mysqli':
424                                         $this->db->close();
425                                         break;
426                                 case 'mysql':
427                                         mysql_close($this->db);
428                                         break;
429                         }
430                 }
431         }
432
433         /**
434          * @brief Replaces ANY_VALUE() function by MIN() function,
435          *  if the database server does not support ANY_VALUE().
436          *
437          * Considerations for Standard SQL, or MySQL with ONLY_FULL_GROUP_BY (default since 5.7.5).
438          * ANY_VALUE() is available from MySQL 5.7.5 https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html
439          * A standard fall-back is to use MIN().
440          *
441          * @param string $sql An SQL string without the values
442          * @return string The input SQL string modified if necessary.
443          */
444         public function any_value_fallback($sql) {
445                 $server_info = $this->server_info();
446                 if (version_compare($server_info, '5.7.5', '<') ||
447                         (stripos($server_info, 'MariaDB') !== false)) {
448                         $sql = str_ireplace('ANY_VALUE(', 'MIN(', $sql);
449                 }
450                 return $sql;
451         }
452
453         /**
454          * @brief beautifies the query - useful for "SHOW PROCESSLIST"
455          *
456          * This is safe when we bind the parameters later.
457          * The parameter values aren't part of the SQL.
458          *
459          * @param string $sql An SQL string without the values
460          * @return string The input SQL string modified if necessary.
461          */
462         public function clean_query($sql) {
463                 $search = array("\t", "\n", "\r", "  ");
464                 $replace = array(' ', ' ', ' ', ' ');
465                 do {
466                         $oldsql = $sql;
467                         $sql = str_replace($search, $replace, $sql);
468                 } while ($oldsql != $sql);
469
470                 return $sql;
471         }
472
473
474         /**
475          * @brief Replaces the ? placeholders with the parameters in the $args array
476          *
477          * @param string $sql SQL query
478          * @param array $args The parameters that are to replace the ? placeholders
479          * @return string The replaced SQL query
480          */
481         private static function replace_parameters($sql, $args) {
482                 $offset = 0;
483                 foreach ($args AS $param => $value) {
484                         if (is_int($args[$param]) || is_float($args[$param])) {
485                                 $replace = intval($args[$param]);
486                         } else {
487                                 $replace = "'".self::$dbo->escape($args[$param])."'";
488                         }
489
490                         $pos = strpos($sql, '?', $offset);
491                         if ($pos !== false) {
492                                 $sql = substr_replace($sql, $replace, $pos, 1);
493                         }
494                         $offset = $pos + strlen($replace);
495                 }
496                 return $sql;
497         }
498
499         /**
500          * @brief Convert parameter array to an universal form
501          * @param array $args Parameter array
502          * @return array universalized parameter array
503          */
504         private static function getParam($args) {
505                 unset($args[0]);
506
507                 // When the second function parameter is an array then use this as the parameter array
508                 if ((count($args) > 0) && (is_array($args[1]))) {
509                         return $args[1];
510                 } else {
511                         return $args;
512                 }
513         }
514
515         /**
516          * @brief Executes a prepared statement that returns data
517          * @usage Example: $r = p("SELECT * FROM `item` WHERE `guid` = ?", $guid);
518          * @param string $sql SQL statement
519          * @return object statement object
520          */
521         public static function p($sql) {
522                 $a = get_app();
523
524                 $stamp1 = microtime(true);
525
526                 $params = self::getParam(func_get_args());
527
528                 // Renumber the array keys to be sure that they fit
529                 $i = 0;
530                 $args = array();
531                 foreach ($params AS $param) {
532                         // Avoid problems with some MySQL servers and boolean values. See issue #3645
533                         if (is_bool($param)) {
534                                 $param = (int)$param;
535                         }
536                         $args[++$i] = $param;
537                 }
538
539                 if (!self::$dbo || !self::$dbo->connected) {
540                         return false;
541                 }
542
543                 if (substr_count($sql, '?') != count($args)) {
544                         // Question: Should we continue or stop the query here?
545                         logger('Parameter mismatch. Query "'.$sql.'" - Parameters '.print_r($args, true), LOGGER_DEBUG);
546                 }
547
548                 $sql = self::$dbo->clean_query($sql);
549                 $sql = self::$dbo->any_value_fallback($sql);
550
551                 $orig_sql = $sql;
552
553                 if (x($a->config,'system') && x($a->config['system'], 'db_callstack')) {
554                         $sql = "/*".System::callstack()." */ ".$sql;
555                 }
556
557                 self::$dbo->error = '';
558                 self::$dbo->errorno = 0;
559                 self::$dbo->affected_rows = 0;
560
561                 // We have to make some things different if this function is called from "e"
562                 $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
563
564                 if (isset($trace[1])) {
565                         $called_from = $trace[1];
566                 } else {
567                         // We use just something that is defined to avoid warnings
568                         $called_from = $trace[0];
569                 }
570                 // We are having an own error logging in the function "e"
571                 $called_from_e = ($called_from['function'] == 'e');
572
573                 switch (self::$dbo->driver) {
574                         case 'pdo':
575                                 if (!$stmt = self::$dbo->db->prepare($sql)) {
576                                         $errorInfo = self::$dbo->db->errorInfo();
577                                         self::$dbo->error = $errorInfo[2];
578                                         self::$dbo->errorno = $errorInfo[1];
579                                         $retval = false;
580                                         break;
581                                 }
582
583                                 foreach ($args AS $param => $value) {
584                                         $stmt->bindParam($param, $args[$param]);
585                                 }
586
587                                 if (!$stmt->execute()) {
588                                         $errorInfo = $stmt->errorInfo();
589                                         self::$dbo->error = $errorInfo[2];
590                                         self::$dbo->errorno = $errorInfo[1];
591                                         $retval = false;
592                                 } else {
593                                         $retval = $stmt;
594                                         self::$dbo->affected_rows = $retval->rowCount();
595                                 }
596                                 break;
597                         case 'mysqli':
598                                 // There are SQL statements that cannot be executed with a prepared statement
599                                 $parts = explode(' ', $orig_sql);
600                                 $command = strtolower($parts[0]);
601                                 $can_be_prepared = in_array($command, array('select', 'update', 'insert', 'delete'));
602
603                                 // The fallback routine currently only works with statements that doesn't return values
604                                 if (!$can_be_prepared && $called_from_e) {
605                                         $retval = self::$dbo->db->query(self::replace_parameters($sql, $args));
606                                         if (self::$dbo->db->errno) {
607                                                 self::$dbo->error = self::$dbo->db->error;
608                                                 self::$dbo->errorno = self::$dbo->db->errno;
609                                                 $retval = false;
610                                         } else {
611                                                 if (isset($retval->num_rows)) {
612                                                         self::$dbo->affected_rows = $retval->num_rows;
613                                                 } else {
614                                                         self::$dbo->affected_rows = self::$dbo->db->affected_rows;
615                                                 }
616                                         }
617                                         break;
618                                 }
619
620                                 $stmt = self::$dbo->db->stmt_init();
621
622                                 if (!$stmt->prepare($sql)) {
623                                         self::$dbo->error = $stmt->error;
624                                         self::$dbo->errorno = $stmt->errno;
625                                         $retval = false;
626                                         break;
627                                 }
628
629                                 $params = '';
630                                 $values = array();
631                                 foreach ($args AS $param => $value) {
632                                         if (is_int($args[$param])) {
633                                                 $params .= 'i';
634                                         } elseif (is_float($args[$param])) {
635                                                 $params .= 'd';
636                                         } elseif (is_string($args[$param])) {
637                                                 $params .= 's';
638                                         } else {
639                                                 $params .= 'b';
640                                         }
641                                         $values[] = &$args[$param];
642                                 }
643
644                                 if (count($values) > 0) {
645                                         array_unshift($values, $params);
646                                         call_user_func_array(array($stmt, 'bind_param'), $values);
647                                 }
648
649                                 if (!$stmt->execute()) {
650                                         self::$dbo->error = self::$dbo->db->error;
651                                         self::$dbo->errorno = self::$dbo->db->errno;
652                                         $retval = false;
653                                 } else {
654                                         $stmt->store_result();
655                                         $retval = $stmt;
656                                         self::$dbo->affected_rows = $retval->affected_rows;
657                                 }
658                                 break;
659                         case 'mysql':
660                                 // For the old "mysql" functions we cannot use prepared statements
661                                 $retval = mysql_query(self::replace_parameters($sql, $args), self::$dbo->db);
662                                 if (mysql_errno(self::$dbo->db)) {
663                                         self::$dbo->error = mysql_error(self::$dbo->db);
664                                         self::$dbo->errorno = mysql_errno(self::$dbo->db);
665                                 } else {
666                                         self::$dbo->affected_rows = mysql_affected_rows($retval);
667
668                                         // Due to missing mysql_* support this here wasn't tested at all
669                                         // See here: http://php.net/manual/en/function.mysql-num-rows.php
670                                         if (self::$dbo->affected_rows <= 0) {
671                                                 self::$dbo->affected_rows = mysql_num_rows($retval);
672                                         }
673                                 }
674                                 break;
675                 }
676
677                 // We are having an own error logging in the function "e"
678                 if ((self::$dbo->errorno != 0) && !$called_from_e) {
679                         // We have to preserve the error code, somewhere in the logging it get lost
680                         $error = self::$dbo->error;
681                         $errorno = self::$dbo->errorno;
682
683                         logger('DB Error '.self::$dbo->errorno.': '.self::$dbo->error."\n".
684                                 System::callstack(8)."\n".self::replace_parameters($sql, $params));
685
686                         self::$dbo->error = $error;
687                         self::$dbo->errorno = $errorno;
688                 }
689
690                 $a->save_timestamp($stamp1, 'database');
691
692                 if (x($a->config,'system') && x($a->config['system'], 'db_log')) {
693
694                         $stamp2 = microtime(true);
695                         $duration = (float)($stamp2 - $stamp1);
696
697                         if (($duration > $a->config["system"]["db_loglimit"])) {
698                                 $duration = round($duration, 3);
699                                 $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
700
701                                 @file_put_contents($a->config["system"]["db_log"], datetime_convert()."\t".$duration."\t".
702                                                 basename($backtrace[1]["file"])."\t".
703                                                 $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
704                                                 substr(self::replace_parameters($sql, $args), 0, 2000)."\n", FILE_APPEND);
705                         }
706                 }
707                 return $retval;
708         }
709
710         /**
711          * @brief Executes a prepared statement like UPDATE or INSERT that doesn't return data
712          *
713          * @param string $sql SQL statement
714          * @return boolean Was the query successfull? False is returned only if an error occurred
715          */
716         public static function e($sql) {
717                 $a = get_app();
718
719                 $stamp = microtime(true);
720
721                 $params = self::getParam(func_get_args());
722
723                 // In a case of a deadlock we are repeating the query 20 times
724                 $timeout = 20;
725
726                 do {
727                         $stmt = self::p($sql, $params);
728
729                         if (is_bool($stmt)) {
730                                 $retval = $stmt;
731                         } elseif (is_object($stmt)) {
732                                 $retval = true;
733                         } else {
734                                 $retval = false;
735                         }
736
737                         self::close($stmt);
738
739                 } while ((self::$dbo->errorno == 1213) && (--$timeout > 0));
740
741                 if (self::$dbo->errorno != 0) {
742                         // We have to preserve the error code, somewhere in the logging it get lost
743                         $error = self::$dbo->error;
744                         $errorno = self::$dbo->errorno;
745
746                         logger('DB Error '.self::$dbo->errorno.': '.self::$dbo->error."\n".
747                                 System::callstack(8)."\n".self::replace_parameters($sql, $params));
748
749                         self::$dbo->error = $error;
750                         self::$dbo->errorno = $errorno;
751                 }
752
753                 $a->save_timestamp($stamp, "database_write");
754
755                 return $retval;
756         }
757
758         /**
759          * @brief Check if data exists
760          *
761          * @param string $table Table name
762          * @param array $condition array of fields for condition
763          *
764          * @return boolean Are there rows for that condition?
765          */
766         public static function exists($table, $condition) {
767                 if (empty($table)) {
768                         return false;
769                 }
770
771                 $fields = array();
772
773                 $array_element = each($condition);
774                 $array_key = $array_element['key'];
775                 if (!is_int($array_key)) {
776                         $fields = array($array_key);
777                 }
778
779                 $stmt = self::select($table, $fields, $condition, array('limit' => 1, 'only_query' => true));
780
781                 if (is_bool($stmt)) {
782                         $retval = $stmt;
783                 } else {
784                         $retval = (self::num_rows($stmt) > 0);
785                 }
786
787                 self::close($stmt);
788
789                 return $retval;
790         }
791
792         /**
793          * @brief Fetches the first row
794          *
795          * @param string $sql SQL statement
796          * @return array first row of query
797          */
798         public static function fetch_first($sql) {
799                 $params = self::getParam(func_get_args());
800
801                 $stmt = self::p($sql, $params);
802
803                 if (is_bool($stmt)) {
804                         $retval = $stmt;
805                 } else {
806                         $retval = self::fetch($stmt);
807                 }
808
809                 self::close($stmt);
810
811                 return $retval;
812         }
813
814         /**
815          * @brief Returns the number of affected rows of the last statement
816          *
817          * @return int Number of rows
818          */
819         public static function affected_rows() {
820                 return self::$dbo->affected_rows;
821         }
822
823         /**
824          * @brief Returns the number of rows of a statement
825          *
826          * @param object Statement object
827          * @return int Number of rows
828          */
829         public static function num_rows($stmt) {
830                 if (!is_object($stmt)) {
831                         return 0;
832                 }
833                 switch (self::$dbo->driver) {
834                         case 'pdo':
835                                 return $stmt->rowCount();
836                         case 'mysqli':
837                                 return $stmt->num_rows;
838                         case 'mysql':
839                                 return mysql_num_rows($stmt);
840                 }
841                 return 0;
842         }
843
844         /**
845          * @brief Fetch a single row
846          *
847          * @param object $stmt statement object
848          * @return array current row
849          */
850         public static function fetch($stmt) {
851                 if (!is_object($stmt)) {
852                         return false;
853                 }
854
855                 switch (self::$dbo->driver) {
856                         case 'pdo':
857                                 return $stmt->fetch(PDO::FETCH_ASSOC);
858                         case 'mysqli':
859                                 // This code works, but is slow
860
861                                 // Bind the result to a result array
862                                 $cols = array();
863
864                                 $cols_num = array();
865                                 for ($x = 0; $x < $stmt->field_count; $x++) {
866                                         $cols[] = &$cols_num[$x];
867                                 }
868
869                                 call_user_func_array(array($stmt, 'bind_result'), $cols);
870
871                                 if (!$stmt->fetch()) {
872                                         return false;
873                                 }
874
875                                 // The slow part:
876                                 // We need to get the field names for the array keys
877                                 // It seems that there is no better way to do this.
878                                 $result = $stmt->result_metadata();
879                                 $fields = $result->fetch_fields();
880
881                                 $columns = array();
882                                 foreach ($cols_num AS $param => $col) {
883                                         $columns[$fields[$param]->name] = $col;
884                                 }
885                                 return $columns;
886                         case 'mysql':
887                                 return mysql_fetch_array(self::$dbo->result, MYSQL_ASSOC);
888                 }
889         }
890
891         /**
892          * @brief Insert a row into a table
893          *
894          * @param string $table Table name
895          * @param array $param parameter array
896          * @param bool $on_duplicate_update Do an update on a duplicate entry
897          *
898          * @return boolean was the insert successfull?
899          */
900         public static function insert($table, $param, $on_duplicate_update = false) {
901                 $sql = "INSERT INTO `".self::$dbo->escape($table)."` (`".implode("`, `", array_keys($param))."`) VALUES (".
902                         substr(str_repeat("?, ", count($param)), 0, -2).")";
903
904                 if ($on_duplicate_update) {
905                         $sql .= " ON DUPLICATE KEY UPDATE `".implode("` = ?, `", array_keys($param))."` = ?";
906
907                         $values = array_values($param);
908                         $param = array_merge_recursive($values, $values);
909                 }
910
911                 return self::e($sql, $param);
912         }
913
914         /**
915          * @brief Fetch the id of the last insert command
916          *
917          * @return integer Last inserted id
918          */
919         public static function lastInsertId() {
920                 switch (self::$dbo->driver) {
921                         case 'pdo':
922                                 $id = self::$dbo->db->lastInsertId();
923                                 break;
924                         case 'mysqli':
925                                 $id = self::$dbo->db->insert_id;
926                                 break;
927                         case 'mysql':
928                                 $id = mysql_insert_id(self::$dbo);
929                                 break;
930                 }
931                 return $id;
932         }
933
934         /**
935          * @brief Locks a table for exclusive write access
936          *
937          * This function can be extended in the future to accept a table array as well.
938          *
939          * @param string $table Table name
940          *
941          * @return boolean was the lock successful?
942          */
943         public static function lock($table) {
944                 // See here: https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html
945                 self::e("SET autocommit=0");
946                 $success = self::e("LOCK TABLES `".self::$dbo->escape($table)."` WRITE");
947                 if (!$success) {
948                         self::e("SET autocommit=1");
949                 } else {
950                         self::$in_transaction = true;
951                 }
952                 return $success;
953         }
954
955         /**
956          * @brief Unlocks all locked tables
957          *
958          * @return boolean was the unlock successful?
959          */
960         public static function unlock() {
961                 // See here: https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html
962                 self::e("COMMIT");
963                 $success = self::e("UNLOCK TABLES");
964                 self::e("SET autocommit=1");
965                 self::$in_transaction = false;
966                 return $success;
967         }
968
969         /**
970          * @brief Starts a transaction
971          *
972          * @return boolean Was the command executed successfully?
973          */
974         public static function transaction() {
975                 if (!self::e('COMMIT')) {
976                         return false;
977                 }
978                 if (!self::e('START TRANSACTION')) {
979                         return false;
980                 }
981                 self::$in_transaction = true;
982                 return true;
983         }
984
985         /**
986          * @brief Does a commit
987          *
988          * @return boolean Was the command executed successfully?
989          */
990         public static function commit() {
991                 if (!self::e('COMMIT')) {
992                         return false;
993                 }
994                 self::$in_transaction = false;
995                 return true;
996         }
997
998         /**
999          * @brief Does a rollback
1000          *
1001          * @return boolean Was the command executed successfully?
1002          */
1003         public static function rollback() {
1004                 if (!self::e('ROLLBACK')) {
1005                         return false;
1006                 }
1007                 self::$in_transaction = false;
1008                 return true;
1009         }
1010
1011         /**
1012          * @brief Build the array with the table relations
1013          *
1014          * The array is build from the database definitions in dbstructure.php
1015          *
1016          * This process must only be started once, since the value is cached.
1017          */
1018         private static function build_relation_data() {
1019                 $definition = db_definition();
1020
1021                 foreach ($definition AS $table => $structure) {
1022                         foreach ($structure['fields'] AS $field => $field_struct) {
1023                                 if (isset($field_struct['relation'])) {
1024                                         foreach ($field_struct['relation'] AS $rel_table => $rel_field) {
1025                                                 self::$relation[$rel_table][$rel_field][$table][] = $field;
1026                                         }
1027                                 }
1028                         }
1029                 }
1030         }
1031
1032         /**
1033          * @brief Delete a row from a table
1034          *
1035          * @param string $table Table name
1036          * @param array $param parameter array
1037          * @param boolean $in_process Internal use: Only do a commit after the last delete
1038          * @param array $callstack Internal use: prevent endless loops
1039          *
1040          * @return boolean|array was the delete successfull? When $in_process is set: deletion data
1041          */
1042         public static function delete($table, $param, $in_process = false, &$callstack = array()) {
1043
1044                 $commands = array();
1045
1046                 // Create a key for the loop prevention
1047                 $key = $table.':'.implode(':', array_keys($param)).':'.implode(':', $param);
1048
1049                 // We quit when this key already exists in the callstack.
1050                 if (isset($callstack[$key])) {
1051                         return $commands;
1052                 }
1053
1054                 $callstack[$key] = true;
1055
1056                 $table = self::$dbo->escape($table);
1057
1058                 $commands[$key] = array('table' => $table, 'param' => $param);
1059
1060                 // To speed up the whole process we cache the table relations
1061                 if (count(self::$relation) == 0) {
1062                         self::build_relation_data();
1063                 }
1064
1065                 // Is there a relation entry for the table?
1066                 if (isset(self::$relation[$table])) {
1067                         // We only allow a simple "one field" relation.
1068                         $field = array_keys(self::$relation[$table])[0];
1069                         $rel_def = array_values(self::$relation[$table])[0];
1070
1071                         // Create a key for preventing double queries
1072                         $qkey = $field.'-'.$table.':'.implode(':', array_keys($param)).':'.implode(':', $param);
1073
1074                         // When the search field is the relation field, we don't need to fetch the rows
1075                         // This is useful when the leading record is already deleted in the frontend but the rest is done in the backend
1076                         if ((count($param) == 1) && ($field == array_keys($param)[0])) {
1077                                 foreach ($rel_def AS $rel_table => $rel_fields) {
1078                                         foreach ($rel_fields AS $rel_field) {
1079                                                 $retval = self::delete($rel_table, array($rel_field => array_values($param)[0]), true, $callstack);
1080                                                 $commands = array_merge($commands, $retval);
1081                                         }
1082                                 }
1083                         // We quit when this key already exists in the callstack.
1084                         } elseif (!isset($callstack[$qkey])) {
1085
1086                                 $callstack[$qkey] = true;
1087
1088                                 // Fetch all rows that are to be deleted
1089                                 $data = self::select($table, array($field), $param);
1090
1091                                 while ($row = self::fetch($data)) {
1092                                         // Now we accumulate the delete commands
1093                                         $retval = self::delete($table, array($field => $row[$field]), true, $callstack);
1094                                         $commands = array_merge($commands, $retval);
1095                                 }
1096
1097                                 self::close($data);
1098
1099                                 // Since we had split the delete command we don't need the original command anymore
1100                                 unset($commands[$key]);
1101                         }
1102                 }
1103
1104                 if (!$in_process) {
1105                         // Now we finalize the process
1106                         $do_transaction = !self::$in_transaction;
1107
1108                         if ($do_transaction) {
1109                                 self::transaction();
1110                         }
1111
1112                         $compacted = array();
1113                         $counter = array();
1114
1115                         foreach ($commands AS $command) {
1116                                 $condition = $command['param'];
1117                                 $array_element = each($condition);
1118                                 $array_key = $array_element['key'];
1119                                 if (is_int($array_key)) {
1120                                         $condition_string = " WHERE ".array_shift($condition);
1121                                 } else {
1122                                         $condition_string = " WHERE `".implode("` = ? AND `", array_keys($condition))."` = ?";
1123                                 }
1124
1125                                 if ((count($command['param']) > 1) || is_int($array_key)) {
1126                                         $sql = "DELETE FROM `".$command['table']."`".$condition_string;
1127                                         logger(self::replace_parameters($sql, $condition), LOGGER_DATA);
1128
1129                                         if (!self::e($sql, $condition)) {
1130                                                 if ($do_transaction) {
1131                                                         self::rollback();
1132                                                 }
1133                                                 return false;
1134                                         }
1135                                 } else {
1136                                         $key_table = $command['table'];
1137                                         $key_param = array_keys($command['param'])[0];
1138                                         $value = array_values($command['param'])[0];
1139
1140                                         // Split the SQL queries in chunks of 100 values
1141                                         // We do the $i stuff here to make the code better readable
1142                                         $i = $counter[$key_table][$key_param];
1143                                         if (count($compacted[$key_table][$key_param][$i]) > 100) {
1144                                                 ++$i;
1145                                         }
1146
1147                                         $compacted[$key_table][$key_param][$i][$value] = $value;
1148                                         $counter[$key_table][$key_param] = $i;
1149                                 }
1150                         }
1151                         foreach ($compacted AS $table => $values) {
1152                                 foreach ($values AS $field => $field_value_list) {
1153                                         foreach ($field_value_list AS $field_values) {
1154                                                 $sql = "DELETE FROM `".$table."` WHERE `".$field."` IN (".
1155                                                         substr(str_repeat("?, ", count($field_values)), 0, -2).");";
1156
1157                                                 logger(self::replace_parameters($sql, $field_values), LOGGER_DATA);
1158
1159                                                 if (!self::e($sql, $field_values)) {
1160                                                         if ($do_transaction) {
1161                                                                 self::rollback();
1162                                                         }
1163                                                         return false;
1164                                                 }
1165                                         }
1166                                 }
1167                         }
1168                         if ($do_transaction) {
1169                                 self::commit();
1170                         }
1171                         return true;
1172                 }
1173
1174                 return $commands;
1175         }
1176
1177         /**
1178          * @brief Updates rows
1179          *
1180          * Updates rows in the database. When $old_fields is set to an array,
1181          * the system will only do an update if the fields in that array changed.
1182          *
1183          * Attention:
1184          * Only the values in $old_fields are compared.
1185          * This is an intentional behaviour.
1186          *
1187          * Example:
1188          * We include the timestamp field in $fields but not in $old_fields.
1189          * Then the row will only get the new timestamp when the other fields had changed.
1190          *
1191          * When $old_fields is set to a boolean value the system will do this compare itself.
1192          * When $old_fields is set to "true" the system will do an insert if the row doesn't exists.
1193          *
1194          * Attention:
1195          * Only set $old_fields to a boolean value when you are sure that you will update a single row.
1196          * When you set $old_fields to "true" then $fields must contain all relevant fields!
1197          *
1198          * @param string $table Table name
1199          * @param array $fields contains the fields that are updated
1200          * @param array $condition condition array with the key values
1201          * @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate)
1202          *
1203          * @return boolean was the update successfull?
1204          */
1205         public static function update($table, $fields, $condition, $old_fields = array()) {
1206
1207                 $table = self::$dbo->escape($table);
1208
1209                 if (count($condition) > 0) {
1210                         $array_element = each($condition);
1211                         $array_key = $array_element['key'];
1212                         if (is_int($array_key)) {
1213                                 $condition_string = " WHERE ".array_shift($condition);
1214                         } else {
1215                                 $condition_string = " WHERE `".implode("` = ? AND `", array_keys($condition))."` = ?";
1216                         }
1217                 } else {
1218                         $condition_string = "";
1219                 }
1220
1221                 if (is_bool($old_fields)) {
1222                         $do_insert = $old_fields;
1223
1224                         $old_fields = self::select($table, array(), $condition, array('limit' => 1));
1225
1226                         if (is_bool($old_fields)) {
1227                                 if ($do_insert) {
1228                                         $values = array_merge($condition, $fields);
1229                                         return self::insert($table, $values, $do_insert);
1230                                 }
1231                                 $old_fields = array();
1232                         }
1233                 }
1234
1235                 $do_update = (count($old_fields) == 0);
1236
1237                 foreach ($old_fields AS $fieldname => $content) {
1238                         if (isset($fields[$fieldname])) {
1239                                 if ($fields[$fieldname] == $content) {
1240                                         unset($fields[$fieldname]);
1241                                 } else {
1242                                         $do_update = true;
1243                                 }
1244                         }
1245                 }
1246
1247                 if (!$do_update || (count($fields) == 0)) {
1248                         return true;
1249                 }
1250
1251                 $sql = "UPDATE `".$table."` SET `".
1252                         implode("` = ?, `", array_keys($fields))."` = ?".$condition_string;
1253
1254                 $params1 = array_values($fields);
1255                 $params2 = array_values($condition);
1256                 $params = array_merge_recursive($params1, $params2);
1257
1258                 return self::e($sql, $params);
1259         }
1260
1261         /**
1262          * @brief Select rows from a table
1263          *
1264          * @param string $table Table name
1265          * @param array $fields array of selected fields
1266          * @param array $condition array of fields for condition
1267          * @param array $params array of several parameters
1268          *
1269          * @return boolean|object If "limit" is equal "1" only a single row is returned, else a query object is returned
1270          *
1271          * Example:
1272          * $table = "item";
1273          * $fields = array("id", "uri", "uid", "network");
1274          * $condition = array("uid" => 1, "network" => 'dspr');
1275          * $params = array("order" => array("id", "received" => true), "limit" => 1);
1276          *
1277          * $data = dba::select($table, $fields, $condition, $params);
1278          */
1279         public static function select($table, $fields = array(), $condition = array(), $params = array()) {
1280                 if ($table == '') {
1281                         return false;
1282                 }
1283
1284                 if (count($fields) > 0) {
1285                         $select_fields = "`".implode("`, `", array_values($fields))."`";
1286                 } else {
1287                         $select_fields = "*";
1288                 }
1289
1290                 if (count($condition) > 0) {
1291                         $array_element = each($condition);
1292                         $array_key = $array_element['key'];
1293                         if (is_int($array_key)) {
1294                                 $condition_string = " WHERE ".array_shift($condition);
1295                         } else {
1296                                 $condition_string = " WHERE `".implode("` = ? AND `", array_keys($condition))."` = ?";
1297                         }
1298                 } else {
1299                         $condition_string = "";
1300                 }
1301
1302                 $param_string = '';
1303                 $single_row = false;
1304
1305                 if (isset($params['order'])) {
1306                         $param_string .= " ORDER BY ";
1307                         foreach ($params['order'] AS $fields => $order) {
1308                                 if (!is_int($fields)) {
1309                                         $param_string .= "`".$fields."` ".($order ? "DESC" : "ASC").", ";
1310                                 } else {
1311                                         $param_string .= "`".$order."`, ";
1312                                 }
1313                         }
1314                         $param_string = substr($param_string, 0, -2);
1315                 }
1316
1317                 if (isset($params['limit']) && is_int($params['limit'])) {
1318                         $param_string .= " LIMIT ".$params['limit'];
1319                         $single_row = ($params['limit'] == 1);
1320                 }
1321
1322                 if (isset($params['only_query']) && $params['only_query']) {
1323                         $single_row = !$params['only_query'];
1324                 }
1325
1326                 $sql = "SELECT ".$select_fields." FROM `".$table."`".$condition_string.$param_string;
1327
1328                 $result = self::p($sql, $condition);
1329
1330                 if (is_bool($result) || !$single_row) {
1331                         return $result;
1332                 } else {
1333                         $row = self::fetch($result);
1334                         self::close($result);
1335                         return $row;
1336                 }
1337         }
1338
1339
1340         /**
1341          * @brief Fills an array with data from a query
1342          *
1343          * @param object $stmt statement object
1344          * @return array Data array
1345          */
1346         public static function inArray($stmt, $do_close = true) {
1347                 if (is_bool($stmt)) {
1348                         return $stmt;
1349                 }
1350
1351                 $data = array();
1352                 while ($row = self::fetch($stmt)) {
1353                         $data[] = $row;
1354                 }
1355                 if ($do_close) {
1356                         self::close($stmt);
1357                 }
1358                 return $data;
1359         }
1360
1361         /**
1362          * @brief Closes the current statement
1363          *
1364          * @param object $stmt statement object
1365          * @return boolean was the close successfull?
1366          */
1367         public static function close($stmt) {
1368                 if (!is_object($stmt)) {
1369                         return false;
1370                 }
1371
1372                 switch (self::$dbo->driver) {
1373                         case 'pdo':
1374                                 return $stmt->closeCursor();
1375                         case 'mysqli':
1376                                 return $stmt->free_result();
1377                                 return $stmt->close();
1378                         case 'mysql':
1379                                 return mysql_free_result($stmt);
1380                 }
1381         }
1382 }
1383
1384 function printable($s) {
1385         $s = preg_replace("~([\x01-\x08\x0E-\x0F\x10-\x1F\x7F-\xFF])~",".", $s);
1386         $s = str_replace("\x00",'.',$s);
1387         if (x($_SERVER,'SERVER_NAME')) {
1388                 $s = escape_tags($s);
1389         }
1390         return $s;
1391 }
1392
1393 // Procedural functions
1394 function dbg($state) {
1395         global $db;
1396
1397         if ($db) {
1398                 $db->dbg($state);
1399         }
1400 }
1401
1402 function dbesc($str) {
1403         global $db;
1404
1405         if ($db && $db->connected) {
1406                 return($db->escape($str));
1407         } else {
1408                 return(str_replace("'","\\'",$str));
1409         }
1410 }
1411
1412 // Function: q($sql,$args);
1413 // Description: execute SQL query with printf style args.
1414 // Example: $r = q("SELECT * FROM `%s` WHERE `uid` = %d",
1415 //                   'user', 1);
1416 function q($sql) {
1417         global $db;
1418         $args = func_get_args();
1419         unset($args[0]);
1420
1421         if ($db && $db->connected) {
1422                 $sql = $db->clean_query($sql);
1423                 $sql = $db->any_value_fallback($sql);
1424                 $stmt = @vsprintf($sql,$args); // Disabled warnings
1425                 //logger("dba: q: $stmt", LOGGER_ALL);
1426                 if ($stmt === false)
1427                         logger('dba: vsprintf error: ' . print_r(debug_backtrace(),true), LOGGER_DEBUG);
1428
1429                 $db->log_index($stmt);
1430
1431                 return $db->q($stmt);
1432         }
1433
1434         /**
1435          *
1436          * This will happen occasionally trying to store the
1437          * session data after abnormal program termination
1438          *
1439          */
1440         logger('dba: no database: ' . print_r($args,true));
1441         return false;
1442 }
1443
1444 /**
1445  * @brief Performs a query with "dirty reads"
1446  *
1447  * By doing dirty reads (reading uncommitted data) no locks are performed
1448  * This function can be used to fetch data that doesn't need to be reliable.
1449  *
1450  * @param $args Query parameters (1 to N parameters of different types)
1451  * @return array Query array
1452  */
1453 function qu($sql) {
1454         global $db;
1455
1456         $args = func_get_args();
1457         unset($args[0]);
1458
1459         if ($db && $db->connected) {
1460                 $sql = $db->clean_query($sql);
1461                 $sql = $db->any_value_fallback($sql);
1462                 $stmt = @vsprintf($sql,$args); // Disabled warnings
1463                 if ($stmt === false)
1464                         logger('dba: vsprintf error: ' . print_r(debug_backtrace(),true), LOGGER_DEBUG);
1465
1466                 $db->log_index($stmt);
1467
1468                 $db->q("SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");
1469                 $retval = $db->q($stmt);
1470                 $db->q("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;");
1471                 return $retval;
1472         }
1473
1474         /**
1475          *
1476          * This will happen occasionally trying to store the
1477          * session data after abnormal program termination
1478          *
1479          */
1480         logger('dba: no database: ' . print_r($args,true));
1481         return false;
1482 }
1483
1484 /**
1485  *
1486  * Raw db query, no arguments
1487  *
1488  */
1489 function dbq($sql) {
1490         global $db;
1491
1492         if ($db && $db->connected) {
1493                 $ret = $db->q($sql);
1494         } else {
1495                 $ret = false;
1496         }
1497         return $ret;
1498 }
1499
1500 // Caller is responsible for ensuring that any integer arguments to
1501 // dbesc_array are actually integers and not malformed strings containing
1502 // SQL injection vectors. All integer array elements should be specifically
1503 // cast to int to avoid trouble.
1504 function dbesc_array_cb(&$item, $key) {
1505         if (is_string($item))
1506                 $item = dbesc($item);
1507 }
1508
1509 function dbesc_array(&$arr) {
1510         if (is_array($arr) && count($arr)) {
1511                 array_walk($arr,'dbesc_array_cb');
1512         }
1513 }
1514
1515 function dba_timer() {
1516         return microtime(true);
1517 }