Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1651 lines
42KB

  1. <?php
  2. namespace Friendica\Database;
  3. // Do not use Core\Config in this class at risk of infinite loop.
  4. // Please use App->getConfigVariable() instead.
  5. //use Friendica\Core\Config;
  6. use Friendica\Core\Logger;
  7. use Friendica\Core\System;
  8. use Friendica\Util\DateTimeFormat;
  9. use mysqli;
  10. use mysqli_result;
  11. use mysqli_stmt;
  12. use PDO;
  13. use PDOException;
  14. use PDOStatement;
  15. /**
  16. * @class MySQL database class
  17. *
  18. * This class is for the low level database stuff that does driver specific things.
  19. */
  20. class DBA
  21. {
  22. /**
  23. * Lowest possible date value
  24. */
  25. const NULL_DATE = '0001-01-01';
  26. /**
  27. * Lowest possible datetime value
  28. */
  29. const NULL_DATETIME = '0001-01-01 00:00:00';
  30. public static $connected = false;
  31. private static $server_info = '';
  32. private static $connection;
  33. private static $driver;
  34. private static $error = false;
  35. private static $errorno = 0;
  36. private static $affected_rows = 0;
  37. private static $in_transaction = false;
  38. private static $in_retrial = false;
  39. private static $relation = [];
  40. private static $db_serveraddr = '';
  41. private static $db_user = '';
  42. private static $db_pass = '';
  43. private static $db_name = '';
  44. private static $db_charset = '';
  45. public static function connect($serveraddr, $user, $pass, $db, $charset = null)
  46. {
  47. if (!is_null(self::$connection) && self::connected()) {
  48. return true;
  49. }
  50. // We are storing these values for being able to perform a reconnect
  51. self::$db_serveraddr = $serveraddr;
  52. self::$db_user = $user;
  53. self::$db_pass = $pass;
  54. self::$db_name = $db;
  55. self::$db_charset = $charset;
  56. $port = 0;
  57. $serveraddr = trim($serveraddr);
  58. $serverdata = explode(':', $serveraddr);
  59. $server = $serverdata[0];
  60. if (count($serverdata) > 1) {
  61. $port = trim($serverdata[1]);
  62. }
  63. $server = trim($server);
  64. $user = trim($user);
  65. $pass = trim($pass);
  66. $db = trim($db);
  67. $charset = trim($charset);
  68. if (!(strlen($server) && strlen($user))) {
  69. return false;
  70. }
  71. if (class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
  72. self::$driver = 'pdo';
  73. $connect = "mysql:host=".$server.";dbname=".$db;
  74. if ($port > 0) {
  75. $connect .= ";port=".$port;
  76. }
  77. if ($charset) {
  78. $connect .= ";charset=".$charset;
  79. }
  80. try {
  81. self::$connection = @new PDO($connect, $user, $pass);
  82. self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
  83. self::$connected = true;
  84. } catch (PDOException $e) {
  85. /// @TODO At least log exception, don't ignore it!
  86. }
  87. }
  88. if (!self::$connected && class_exists('\mysqli')) {
  89. self::$driver = 'mysqli';
  90. if ($port > 0) {
  91. self::$connection = @new mysqli($server, $user, $pass, $db, $port);
  92. } else {
  93. self::$connection = @new mysqli($server, $user, $pass, $db);
  94. }
  95. if (!mysqli_connect_errno()) {
  96. self::$connected = true;
  97. if ($charset) {
  98. self::$connection->set_charset($charset);
  99. }
  100. }
  101. }
  102. // No suitable SQL driver was found.
  103. if (!self::$connected) {
  104. self::$driver = null;
  105. self::$connection = null;
  106. }
  107. return self::$connected;
  108. }
  109. /**
  110. * Disconnects the current database connection
  111. */
  112. public static function disconnect()
  113. {
  114. if (is_null(self::$connection)) {
  115. return;
  116. }
  117. switch (self::$driver) {
  118. case 'pdo':
  119. self::$connection = null;
  120. break;
  121. case 'mysqli':
  122. self::$connection->close();
  123. self::$connection = null;
  124. break;
  125. }
  126. }
  127. /**
  128. * Perform a reconnect of an existing database connection
  129. */
  130. public static function reconnect() {
  131. self::disconnect();
  132. $ret = self::connect(self::$db_serveraddr, self::$db_user, self::$db_pass, self::$db_name, self::$db_charset);
  133. return $ret;
  134. }
  135. /**
  136. * Return the database object.
  137. * @return PDO|mysqli
  138. */
  139. public static function getConnection()
  140. {
  141. return self::$connection;
  142. }
  143. /**
  144. * @brief Returns the MySQL server version string
  145. *
  146. * This function discriminate between the deprecated mysql API and the current
  147. * object-oriented mysqli API. Example of returned string: 5.5.46-0+deb8u1
  148. *
  149. * @return string
  150. */
  151. public static function serverInfo() {
  152. if (self::$server_info == '') {
  153. switch (self::$driver) {
  154. case 'pdo':
  155. self::$server_info = self::$connection->getAttribute(PDO::ATTR_SERVER_VERSION);
  156. break;
  157. case 'mysqli':
  158. self::$server_info = self::$connection->server_info;
  159. break;
  160. }
  161. }
  162. return self::$server_info;
  163. }
  164. /**
  165. * @brief Returns the selected database name
  166. *
  167. * @return string
  168. */
  169. public static function databaseName() {
  170. $ret = self::p("SELECT DATABASE() AS `db`");
  171. $data = self::toArray($ret);
  172. return $data[0]['db'];
  173. }
  174. /**
  175. * @brief Analyze a database query and log this if some conditions are met.
  176. *
  177. * @param string $query The database query that will be analyzed
  178. */
  179. private static function logIndex($query) {
  180. $a = get_app();
  181. if (!$a->getConfigVariable('system', 'db_log_index')) {
  182. return;
  183. }
  184. // Don't explain an explain statement
  185. if (strtolower(substr($query, 0, 7)) == "explain") {
  186. return;
  187. }
  188. // Only do the explain on "select", "update" and "delete"
  189. if (!in_array(strtolower(substr($query, 0, 6)), ["select", "update", "delete"])) {
  190. return;
  191. }
  192. $r = self::p("EXPLAIN ".$query);
  193. if (!self::isResult($r)) {
  194. return;
  195. }
  196. $watchlist = explode(',', $a->getConfigVariable('system', 'db_log_index_watch'));
  197. $blacklist = explode(',', $a->getConfigVariable('system', 'db_log_index_blacklist'));
  198. while ($row = self::fetch($r)) {
  199. if ((intval($a->getConfigVariable('system', 'db_loglimit_index')) > 0)) {
  200. $log = (in_array($row['key'], $watchlist) &&
  201. ($row['rows'] >= intval($a->getConfigVariable('system', 'db_loglimit_index'))));
  202. } else {
  203. $log = false;
  204. }
  205. if ((intval($a->getConfigVariable('system', 'db_loglimit_index_high')) > 0) && ($row['rows'] >= intval($a->getConfigVariable('system', 'db_loglimit_index_high')))) {
  206. $log = true;
  207. }
  208. if (in_array($row['key'], $blacklist) || ($row['key'] == "")) {
  209. $log = false;
  210. }
  211. if ($log) {
  212. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  213. @file_put_contents($a->getConfigVariable('system', 'db_log_index'), DateTimeFormat::utcNow()."\t".
  214. $row['key']."\t".$row['rows']."\t".$row['Extra']."\t".
  215. basename($backtrace[1]["file"])."\t".
  216. $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
  217. substr($query, 0, 2000)."\n", FILE_APPEND);
  218. }
  219. }
  220. }
  221. public static function escape($str) {
  222. if (self::$connected) {
  223. switch (self::$driver) {
  224. case 'pdo':
  225. return substr(@self::$connection->quote($str, PDO::PARAM_STR), 1, -1);
  226. case 'mysqli':
  227. return @self::$connection->real_escape_string($str);
  228. }
  229. } else {
  230. return str_replace("'", "\\'", $str);
  231. }
  232. }
  233. public static function connected() {
  234. $connected = false;
  235. if (is_null(self::$connection)) {
  236. return false;
  237. }
  238. switch (self::$driver) {
  239. case 'pdo':
  240. $r = self::p("SELECT 1");
  241. if (self::isResult($r)) {
  242. $row = self::toArray($r);
  243. $connected = ($row[0]['1'] == '1');
  244. }
  245. break;
  246. case 'mysqli':
  247. $connected = self::$connection->ping();
  248. break;
  249. }
  250. return $connected;
  251. }
  252. /**
  253. * @brief Replaces ANY_VALUE() function by MIN() function,
  254. * if the database server does not support ANY_VALUE().
  255. *
  256. * Considerations for Standard SQL, or MySQL with ONLY_FULL_GROUP_BY (default since 5.7.5).
  257. * ANY_VALUE() is available from MySQL 5.7.5 https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html
  258. * A standard fall-back is to use MIN().
  259. *
  260. * @param string $sql An SQL string without the values
  261. * @return string The input SQL string modified if necessary.
  262. */
  263. public static function anyValueFallback($sql) {
  264. $server_info = self::serverInfo();
  265. if (version_compare($server_info, '5.7.5', '<') ||
  266. (stripos($server_info, 'MariaDB') !== false)) {
  267. $sql = str_ireplace('ANY_VALUE(', 'MIN(', $sql);
  268. }
  269. return $sql;
  270. }
  271. /**
  272. * @brief beautifies the query - useful for "SHOW PROCESSLIST"
  273. *
  274. * This is safe when we bind the parameters later.
  275. * The parameter values aren't part of the SQL.
  276. *
  277. * @param string $sql An SQL string without the values
  278. * @return string The input SQL string modified if necessary.
  279. */
  280. public static function cleanQuery($sql) {
  281. $search = ["\t", "\n", "\r", " "];
  282. $replace = [' ', ' ', ' ', ' '];
  283. do {
  284. $oldsql = $sql;
  285. $sql = str_replace($search, $replace, $sql);
  286. } while ($oldsql != $sql);
  287. return $sql;
  288. }
  289. /**
  290. * @brief Replaces the ? placeholders with the parameters in the $args array
  291. *
  292. * @param string $sql SQL query
  293. * @param array $args The parameters that are to replace the ? placeholders
  294. * @return string The replaced SQL query
  295. */
  296. private static function replaceParameters($sql, $args) {
  297. $offset = 0;
  298. foreach ($args AS $param => $value) {
  299. if (is_int($args[$param]) || is_float($args[$param])) {
  300. $replace = intval($args[$param]);
  301. } else {
  302. $replace = "'".self::escape($args[$param])."'";
  303. }
  304. $pos = strpos($sql, '?', $offset);
  305. if ($pos !== false) {
  306. $sql = substr_replace($sql, $replace, $pos, 1);
  307. }
  308. $offset = $pos + strlen($replace);
  309. }
  310. return $sql;
  311. }
  312. /**
  313. * @brief Convert parameter array to an universal form
  314. * @param array $args Parameter array
  315. * @return array universalized parameter array
  316. */
  317. private static function getParam($args) {
  318. unset($args[0]);
  319. // When the second function parameter is an array then use this as the parameter array
  320. if ((count($args) > 0) && (is_array($args[1]))) {
  321. return $args[1];
  322. } else {
  323. return $args;
  324. }
  325. }
  326. /**
  327. * @brief Executes a prepared statement that returns data
  328. * @usage Example: $r = p("SELECT * FROM `item` WHERE `guid` = ?", $guid);
  329. *
  330. * Please only use it with complicated queries.
  331. * For all regular queries please use DBA::select or DBA::exists
  332. *
  333. * @param string $sql SQL statement
  334. * @return bool|object statement object or result object
  335. */
  336. public static function p($sql) {
  337. $a = get_app();
  338. $stamp1 = microtime(true);
  339. $params = self::getParam(func_get_args());
  340. // Renumber the array keys to be sure that they fit
  341. $i = 0;
  342. $args = [];
  343. foreach ($params AS $param) {
  344. // Avoid problems with some MySQL servers and boolean values. See issue #3645
  345. if (is_bool($param)) {
  346. $param = (int)$param;
  347. }
  348. $args[++$i] = $param;
  349. }
  350. if (!self::$connected) {
  351. return false;
  352. }
  353. if ((substr_count($sql, '?') != count($args)) && (count($args) > 0)) {
  354. // Question: Should we continue or stop the query here?
  355. Logger::log('Parameter mismatch. Query "'.$sql.'" - Parameters '.print_r($args, true), Logger::DEBUG);
  356. }
  357. $sql = self::cleanQuery($sql);
  358. $sql = self::anyValueFallback($sql);
  359. $orig_sql = $sql;
  360. if ($a->getConfigValue('system', 'db_callstack')) {
  361. $sql = "/*".System::callstack()." */ ".$sql;
  362. }
  363. self::$error = '';
  364. self::$errorno = 0;
  365. self::$affected_rows = 0;
  366. // We have to make some things different if this function is called from "e"
  367. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
  368. if (isset($trace[1])) {
  369. $called_from = $trace[1];
  370. } else {
  371. // We use just something that is defined to avoid warnings
  372. $called_from = $trace[0];
  373. }
  374. // We are having an own error logging in the function "e"
  375. $called_from_e = ($called_from['function'] == 'e');
  376. switch (self::$driver) {
  377. case 'pdo':
  378. // If there are no arguments we use "query"
  379. if (count($args) == 0) {
  380. if (!$retval = self::$connection->query($sql)) {
  381. $errorInfo = self::$connection->errorInfo();
  382. self::$error = $errorInfo[2];
  383. self::$errorno = $errorInfo[1];
  384. $retval = false;
  385. break;
  386. }
  387. self::$affected_rows = $retval->rowCount();
  388. break;
  389. }
  390. if (!$stmt = self::$connection->prepare($sql)) {
  391. $errorInfo = self::$connection->errorInfo();
  392. self::$error = $errorInfo[2];
  393. self::$errorno = $errorInfo[1];
  394. $retval = false;
  395. break;
  396. }
  397. foreach ($args AS $param => $value) {
  398. if (is_int($args[$param])) {
  399. $data_type = PDO::PARAM_INT;
  400. } else {
  401. $data_type = PDO::PARAM_STR;
  402. }
  403. $stmt->bindParam($param, $args[$param], $data_type);
  404. }
  405. if (!$stmt->execute()) {
  406. $errorInfo = $stmt->errorInfo();
  407. self::$error = $errorInfo[2];
  408. self::$errorno = $errorInfo[1];
  409. $retval = false;
  410. } else {
  411. $retval = $stmt;
  412. self::$affected_rows = $retval->rowCount();
  413. }
  414. break;
  415. case 'mysqli':
  416. // There are SQL statements that cannot be executed with a prepared statement
  417. $parts = explode(' ', $orig_sql);
  418. $command = strtolower($parts[0]);
  419. $can_be_prepared = in_array($command, ['select', 'update', 'insert', 'delete']);
  420. // The fallback routine is called as well when there are no arguments
  421. if (!$can_be_prepared || (count($args) == 0)) {
  422. $retval = self::$connection->query(self::replaceParameters($sql, $args));
  423. if (self::$connection->errno) {
  424. self::$error = self::$connection->error;
  425. self::$errorno = self::$connection->errno;
  426. $retval = false;
  427. } else {
  428. if (isset($retval->num_rows)) {
  429. self::$affected_rows = $retval->num_rows;
  430. } else {
  431. self::$affected_rows = self::$connection->affected_rows;
  432. }
  433. }
  434. break;
  435. }
  436. $stmt = self::$connection->stmt_init();
  437. if (!$stmt->prepare($sql)) {
  438. self::$error = $stmt->error;
  439. self::$errorno = $stmt->errno;
  440. $retval = false;
  441. break;
  442. }
  443. $param_types = '';
  444. $values = [];
  445. foreach ($args AS $param => $value) {
  446. if (is_int($args[$param])) {
  447. $param_types .= 'i';
  448. } elseif (is_float($args[$param])) {
  449. $param_types .= 'd';
  450. } elseif (is_string($args[$param])) {
  451. $param_types .= 's';
  452. } else {
  453. $param_types .= 'b';
  454. }
  455. $values[] = &$args[$param];
  456. }
  457. if (count($values) > 0) {
  458. array_unshift($values, $param_types);
  459. call_user_func_array([$stmt, 'bind_param'], $values);
  460. }
  461. if (!$stmt->execute()) {
  462. self::$error = self::$connection->error;
  463. self::$errorno = self::$connection->errno;
  464. $retval = false;
  465. } else {
  466. $stmt->store_result();
  467. $retval = $stmt;
  468. self::$affected_rows = $retval->affected_rows;
  469. }
  470. break;
  471. }
  472. // We are having an own error logging in the function "e"
  473. if ((self::$errorno != 0) && !$called_from_e) {
  474. // We have to preserve the error code, somewhere in the logging it get lost
  475. $error = self::$error;
  476. $errorno = self::$errorno;
  477. Logger::log('DB Error '.self::$errorno.': '.self::$error."\n".
  478. System::callstack(8)."\n".self::replaceParameters($sql, $args));
  479. // On a lost connection we try to reconnect - but only once.
  480. if ($errorno == 2006) {
  481. if (self::$in_retrial || !self::reconnect()) {
  482. // It doesn't make sense to continue when the database connection was lost
  483. if (self::$in_retrial) {
  484. Logger::log('Giving up retrial because of database error '.$errorno.': '.$error);
  485. } else {
  486. Logger::log("Couldn't reconnect after database error ".$errorno.': '.$error);
  487. }
  488. exit(1);
  489. } else {
  490. // We try it again
  491. Logger::log('Reconnected after database error '.$errorno.': '.$error);
  492. self::$in_retrial = true;
  493. $ret = self::p($sql, $args);
  494. self::$in_retrial = false;
  495. return $ret;
  496. }
  497. }
  498. self::$error = $error;
  499. self::$errorno = $errorno;
  500. }
  501. $a->saveTimestamp($stamp1, 'database');
  502. if ($a->getConfigValue('system', 'db_log')) {
  503. $stamp2 = microtime(true);
  504. $duration = (float)($stamp2 - $stamp1);
  505. if (($duration > $a->getConfigValue('system', 'db_loglimit'))) {
  506. $duration = round($duration, 3);
  507. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  508. @file_put_contents($a->getConfigValue('system', 'db_log'), DateTimeFormat::utcNow()."\t".$duration."\t".
  509. basename($backtrace[1]["file"])."\t".
  510. $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
  511. substr(self::replaceParameters($sql, $args), 0, 2000)."\n", FILE_APPEND);
  512. }
  513. }
  514. return $retval;
  515. }
  516. /**
  517. * @brief Executes a prepared statement like UPDATE or INSERT that doesn't return data
  518. *
  519. * Please use DBA::delete, DBA::insert, DBA::update, ... instead
  520. *
  521. * @param string $sql SQL statement
  522. * @return boolean Was the query successfull? False is returned only if an error occurred
  523. */
  524. public static function e($sql) {
  525. $a = get_app();
  526. $stamp = microtime(true);
  527. $params = self::getParam(func_get_args());
  528. // In a case of a deadlock we are repeating the query 20 times
  529. $timeout = 20;
  530. do {
  531. $stmt = self::p($sql, $params);
  532. if (is_bool($stmt)) {
  533. $retval = $stmt;
  534. } elseif (is_object($stmt)) {
  535. $retval = true;
  536. } else {
  537. $retval = false;
  538. }
  539. self::close($stmt);
  540. } while ((self::$errorno == 1213) && (--$timeout > 0));
  541. if (self::$errorno != 0) {
  542. // We have to preserve the error code, somewhere in the logging it get lost
  543. $error = self::$error;
  544. $errorno = self::$errorno;
  545. Logger::log('DB Error '.self::$errorno.': '.self::$error."\n".
  546. System::callstack(8)."\n".self::replaceParameters($sql, $params));
  547. // On a lost connection we simply quit.
  548. // A reconnect like in self::p could be dangerous with modifications
  549. if ($errorno == 2006) {
  550. Logger::log('Giving up because of database error '.$errorno.': '.$error);
  551. exit(1);
  552. }
  553. self::$error = $error;
  554. self::$errorno = $errorno;
  555. }
  556. $a->saveTimestamp($stamp, "database_write");
  557. return $retval;
  558. }
  559. /**
  560. * @brief Check if data exists
  561. *
  562. * @param string $table Table name
  563. * @param array $condition array of fields for condition
  564. *
  565. * @return boolean Are there rows for that condition?
  566. */
  567. public static function exists($table, $condition) {
  568. if (empty($table)) {
  569. return false;
  570. }
  571. $fields = [];
  572. if (empty($condition)) {
  573. return DBStructure::existsTable($table);
  574. }
  575. reset($condition);
  576. $first_key = key($condition);
  577. if (!is_int($first_key)) {
  578. $fields = [$first_key];
  579. }
  580. $stmt = self::select($table, $fields, $condition, ['limit' => 1]);
  581. if (is_bool($stmt)) {
  582. $retval = $stmt;
  583. } else {
  584. $retval = (self::numRows($stmt) > 0);
  585. }
  586. self::close($stmt);
  587. return $retval;
  588. }
  589. /**
  590. * Fetches the first row
  591. *
  592. * Please use DBA::selectFirst or DBA::exists whenever this is possible.
  593. *
  594. * @brief Fetches the first row
  595. * @param string $sql SQL statement
  596. * @return array first row of query
  597. */
  598. public static function fetchFirst($sql) {
  599. $params = self::getParam(func_get_args());
  600. $stmt = self::p($sql, $params);
  601. if (is_bool($stmt)) {
  602. $retval = $stmt;
  603. } else {
  604. $retval = self::fetch($stmt);
  605. }
  606. self::close($stmt);
  607. return $retval;
  608. }
  609. /**
  610. * @brief Returns the number of affected rows of the last statement
  611. *
  612. * @return int Number of rows
  613. */
  614. public static function affectedRows() {
  615. return self::$affected_rows;
  616. }
  617. /**
  618. * @brief Returns the number of columns of a statement
  619. *
  620. * @param object Statement object
  621. * @return int Number of columns
  622. */
  623. public static function columnCount($stmt) {
  624. if (!is_object($stmt)) {
  625. return 0;
  626. }
  627. switch (self::$driver) {
  628. case 'pdo':
  629. return $stmt->columnCount();
  630. case 'mysqli':
  631. return $stmt->field_count;
  632. }
  633. return 0;
  634. }
  635. /**
  636. * @brief Returns the number of rows of a statement
  637. *
  638. * @param PDOStatement|mysqli_result|mysqli_stmt Statement object
  639. * @return int Number of rows
  640. */
  641. public static function numRows($stmt) {
  642. if (!is_object($stmt)) {
  643. return 0;
  644. }
  645. switch (self::$driver) {
  646. case 'pdo':
  647. return $stmt->rowCount();
  648. case 'mysqli':
  649. return $stmt->num_rows;
  650. }
  651. return 0;
  652. }
  653. /**
  654. * @brief Fetch a single row
  655. *
  656. * @param mixed $stmt statement object
  657. * @return array current row
  658. */
  659. public static function fetch($stmt) {
  660. $a = get_app();
  661. $stamp1 = microtime(true);
  662. $columns = [];
  663. if (!is_object($stmt)) {
  664. return false;
  665. }
  666. switch (self::$driver) {
  667. case 'pdo':
  668. $columns = $stmt->fetch(PDO::FETCH_ASSOC);
  669. break;
  670. case 'mysqli':
  671. if (get_class($stmt) == 'mysqli_result') {
  672. $columns = $stmt->fetch_assoc();
  673. break;
  674. }
  675. // This code works, but is slow
  676. // Bind the result to a result array
  677. $cols = [];
  678. $cols_num = [];
  679. for ($x = 0; $x < $stmt->field_count; $x++) {
  680. $cols[] = &$cols_num[$x];
  681. }
  682. call_user_func_array([$stmt, 'bind_result'], $cols);
  683. if (!$stmt->fetch()) {
  684. return false;
  685. }
  686. // The slow part:
  687. // We need to get the field names for the array keys
  688. // It seems that there is no better way to do this.
  689. $result = $stmt->result_metadata();
  690. $fields = $result->fetch_fields();
  691. foreach ($cols_num AS $param => $col) {
  692. $columns[$fields[$param]->name] = $col;
  693. }
  694. }
  695. $a->saveTimestamp($stamp1, 'database');
  696. return $columns;
  697. }
  698. /**
  699. * @brief Insert a row into a table
  700. *
  701. * @param string $table Table name
  702. * @param array $param parameter array
  703. * @param bool $on_duplicate_update Do an update on a duplicate entry
  704. *
  705. * @return boolean was the insert successful?
  706. */
  707. public static function insert($table, $param, $on_duplicate_update = false) {
  708. if (empty($table) || empty($param)) {
  709. Logger::log('Table and fields have to be set');
  710. return false;
  711. }
  712. $sql = "INSERT INTO `".self::escape($table)."` (`".implode("`, `", array_keys($param))."`) VALUES (".
  713. substr(str_repeat("?, ", count($param)), 0, -2).")";
  714. if ($on_duplicate_update) {
  715. $sql .= " ON DUPLICATE KEY UPDATE `".implode("` = ?, `", array_keys($param))."` = ?";
  716. $values = array_values($param);
  717. $param = array_merge_recursive($values, $values);
  718. }
  719. return self::e($sql, $param);
  720. }
  721. /**
  722. * @brief Fetch the id of the last insert command
  723. *
  724. * @return integer Last inserted id
  725. */
  726. public static function lastInsertId() {
  727. switch (self::$driver) {
  728. case 'pdo':
  729. $id = self::$connection->lastInsertId();
  730. break;
  731. case 'mysqli':
  732. $id = self::$connection->insert_id;
  733. break;
  734. }
  735. return $id;
  736. }
  737. /**
  738. * @brief Locks a table for exclusive write access
  739. *
  740. * This function can be extended in the future to accept a table array as well.
  741. *
  742. * @param string $table Table name
  743. *
  744. * @return boolean was the lock successful?
  745. */
  746. public static function lock($table) {
  747. // See here: https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html
  748. if (self::$driver == 'pdo') {
  749. self::e("SET autocommit=0");
  750. self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
  751. } else {
  752. self::$connection->autocommit(false);
  753. }
  754. $success = self::e("LOCK TABLES `".self::escape($table)."` WRITE");
  755. if (self::$driver == 'pdo') {
  756. self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
  757. }
  758. if (!$success) {
  759. if (self::$driver == 'pdo') {
  760. self::e("SET autocommit=1");
  761. } else {
  762. self::$connection->autocommit(true);
  763. }
  764. } else {
  765. self::$in_transaction = true;
  766. }
  767. return $success;
  768. }
  769. /**
  770. * @brief Unlocks all locked tables
  771. *
  772. * @return boolean was the unlock successful?
  773. */
  774. public static function unlock() {
  775. // See here: https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html
  776. self::performCommit();
  777. if (self::$driver == 'pdo') {
  778. self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
  779. }
  780. $success = self::e("UNLOCK TABLES");
  781. if (self::$driver == 'pdo') {
  782. self::$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
  783. self::e("SET autocommit=1");
  784. } else {
  785. self::$connection->autocommit(true);
  786. }
  787. self::$in_transaction = false;
  788. return $success;
  789. }
  790. /**
  791. * @brief Starts a transaction
  792. *
  793. * @return boolean Was the command executed successfully?
  794. */
  795. public static function transaction() {
  796. if (!self::performCommit()) {
  797. return false;
  798. }
  799. switch (self::$driver) {
  800. case 'pdo':
  801. if (!self::$connection->inTransaction() && !self::$connection->beginTransaction()) {
  802. return false;
  803. }
  804. break;
  805. case 'mysqli':
  806. if (!self::$connection->begin_transaction()) {
  807. return false;
  808. }
  809. break;
  810. }
  811. self::$in_transaction = true;
  812. return true;
  813. }
  814. private static function performCommit()
  815. {
  816. switch (self::$driver) {
  817. case 'pdo':
  818. if (!self::$connection->inTransaction()) {
  819. return true;
  820. }
  821. return self::$connection->commit();
  822. case 'mysqli':
  823. return self::$connection->commit();
  824. }
  825. return true;
  826. }
  827. /**
  828. * @brief Does a commit
  829. *
  830. * @return boolean Was the command executed successfully?
  831. */
  832. public static function commit() {
  833. if (!self::performCommit()) {
  834. return false;
  835. }
  836. self::$in_transaction = false;
  837. return true;
  838. }
  839. /**
  840. * @brief Does a rollback
  841. *
  842. * @return boolean Was the command executed successfully?
  843. */
  844. public static function rollback() {
  845. $ret = false;
  846. switch (self::$driver) {
  847. case 'pdo':
  848. if (!self::$connection->inTransaction()) {
  849. $ret = true;
  850. break;
  851. }
  852. $ret = self::$connection->rollBack();
  853. break;
  854. case 'mysqli':
  855. $ret = self::$connection->rollback();
  856. break;
  857. }
  858. self::$in_transaction = false;
  859. return $ret;
  860. }
  861. /**
  862. * @brief Build the array with the table relations
  863. *
  864. * The array is build from the database definitions in DBStructure.php
  865. *
  866. * This process must only be started once, since the value is cached.
  867. */
  868. private static function buildRelationData() {
  869. $definition = DBStructure::definition();
  870. foreach ($definition AS $table => $structure) {
  871. foreach ($structure['fields'] AS $field => $field_struct) {
  872. if (isset($field_struct['relation'])) {
  873. foreach ($field_struct['relation'] AS $rel_table => $rel_field) {
  874. self::$relation[$rel_table][$rel_field][$table][] = $field;
  875. }
  876. }
  877. }
  878. }
  879. }
  880. /**
  881. * @brief Delete a row from a table
  882. *
  883. * @param string $table Table name
  884. * @param array $conditions Field condition(s)
  885. * @param array $options
  886. * - cascade: If true we delete records in other tables that depend on the one we're deleting through
  887. * relations (default: true)
  888. * @param array $callstack Internal use: prevent endless loops
  889. *
  890. * @return boolean was the delete successful?
  891. */
  892. public static function delete($table, array $conditions, array $options = [], array &$callstack = [])
  893. {
  894. if (empty($table) || empty($conditions)) {
  895. Logger::log('Table and conditions have to be set');
  896. return false;
  897. }
  898. $commands = [];
  899. // Create a key for the loop prevention
  900. $key = $table . ':' . json_encode($conditions);
  901. // We quit when this key already exists in the callstack.
  902. if (isset($callstack[$key])) {
  903. return $commands;
  904. }
  905. $callstack[$key] = true;
  906. $table = self::escape($table);
  907. $commands[$key] = ['table' => $table, 'conditions' => $conditions];
  908. // Don't use "defaults" here, since it would set "false" to "true"
  909. if (isset($options['cascade'])) {
  910. $cascade = $options['cascade'];
  911. } else {
  912. $cascade = true;
  913. }
  914. // To speed up the whole process we cache the table relations
  915. if ($cascade && count(self::$relation) == 0) {
  916. self::buildRelationData();
  917. }
  918. // Is there a relation entry for the table?
  919. if ($cascade && isset(self::$relation[$table])) {
  920. // We only allow a simple "one field" relation.
  921. $field = array_keys(self::$relation[$table])[0];
  922. $rel_def = array_values(self::$relation[$table])[0];
  923. // Create a key for preventing double queries
  924. $qkey = $field . '-' . $table . ':' . json_encode($conditions);
  925. // When the search field is the relation field, we don't need to fetch the rows
  926. // This is useful when the leading record is already deleted in the frontend but the rest is done in the backend
  927. if ((count($conditions) == 1) && ($field == array_keys($conditions)[0])) {
  928. foreach ($rel_def AS $rel_table => $rel_fields) {
  929. foreach ($rel_fields AS $rel_field) {
  930. $retval = self::delete($rel_table, [$rel_field => array_values($conditions)[0]], $options, $callstack);
  931. }
  932. }
  933. // We quit when this key already exists in the callstack.
  934. } elseif (!isset($callstack[$qkey])) {
  935. $callstack[$qkey] = true;
  936. // Fetch all rows that are to be deleted
  937. $data = self::select($table, [$field], $conditions);
  938. while ($row = self::fetch($data)) {
  939. self::delete($table, [$field => $row[$field]], $options, $callstack);
  940. }
  941. self::close($data);
  942. // Since we had split the delete command we don't need the original command anymore
  943. unset($commands[$key]);
  944. }
  945. }
  946. // Now we finalize the process
  947. $do_transaction = !self::$in_transaction;
  948. if ($do_transaction) {
  949. self::transaction();
  950. }
  951. $compacted = [];
  952. $counter = [];
  953. foreach ($commands AS $command) {
  954. $conditions = $command['conditions'];
  955. reset($conditions);
  956. $first_key = key($conditions);
  957. $condition_string = self::buildCondition($conditions);
  958. if ((count($command['conditions']) > 1) || is_int($first_key)) {
  959. $sql = "DELETE FROM `" . $command['table'] . "`" . $condition_string;
  960. Logger::log(self::replaceParameters($sql, $conditions), Logger::DATA);
  961. if (!self::e($sql, $conditions)) {
  962. if ($do_transaction) {
  963. self::rollback();
  964. }
  965. return false;
  966. }
  967. } else {
  968. $key_table = $command['table'];
  969. $key_condition = array_keys($command['conditions'])[0];
  970. $value = array_values($command['conditions'])[0];
  971. // Split the SQL queries in chunks of 100 values
  972. // We do the $i stuff here to make the code better readable
  973. $i = isset($counter[$key_table][$key_condition]) ? $counter[$key_table][$key_condition] : 0;
  974. if (isset($compacted[$key_table][$key_condition][$i]) && count($compacted[$key_table][$key_condition][$i]) > 100) {
  975. ++$i;
  976. }
  977. $compacted[$key_table][$key_condition][$i][$value] = $value;
  978. $counter[$key_table][$key_condition] = $i;
  979. }
  980. }
  981. foreach ($compacted AS $table => $values) {
  982. foreach ($values AS $field => $field_value_list) {
  983. foreach ($field_value_list AS $field_values) {
  984. $sql = "DELETE FROM `" . $table . "` WHERE `" . $field . "` IN (" .
  985. substr(str_repeat("?, ", count($field_values)), 0, -2) . ");";
  986. Logger::log(self::replaceParameters($sql, $field_values), Logger::DATA);
  987. if (!self::e($sql, $field_values)) {
  988. if ($do_transaction) {
  989. self::rollback();
  990. }
  991. return false;
  992. }
  993. }
  994. }
  995. }
  996. if ($do_transaction) {
  997. self::commit();
  998. }
  999. return true;
  1000. }
  1001. /**
  1002. * @brief Updates rows
  1003. *
  1004. * Updates rows in the database. When $old_fields is set to an array,
  1005. * the system will only do an update if the fields in that array changed.
  1006. *
  1007. * Attention:
  1008. * Only the values in $old_fields are compared.
  1009. * This is an intentional behaviour.
  1010. *
  1011. * Example:
  1012. * We include the timestamp field in $fields but not in $old_fields.
  1013. * Then the row will only get the new timestamp when the other fields had changed.
  1014. *
  1015. * When $old_fields is set to a boolean value the system will do this compare itself.
  1016. * When $old_fields is set to "true" the system will do an insert if the row doesn't exists.
  1017. *
  1018. * Attention:
  1019. * Only set $old_fields to a boolean value when you are sure that you will update a single row.
  1020. * When you set $old_fields to "true" then $fields must contain all relevant fields!
  1021. *
  1022. * @param string $table Table name
  1023. * @param array $fields contains the fields that are updated
  1024. * @param array $condition condition array with the key values
  1025. * @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate)
  1026. *
  1027. * @return boolean was the update successfull?
  1028. */
  1029. public static function update($table, $fields, $condition, $old_fields = []) {
  1030. if (empty($table) || empty($fields) || empty($condition)) {
  1031. Logger::log('Table, fields and condition have to be set');
  1032. return false;
  1033. }
  1034. $table = self::escape($table);
  1035. $condition_string = self::buildCondition($condition);
  1036. if (is_bool($old_fields)) {
  1037. $do_insert = $old_fields;
  1038. $old_fields = self::selectFirst($table, [], $condition);
  1039. if (is_bool($old_fields)) {
  1040. if ($do_insert) {
  1041. $values = array_merge($condition, $fields);
  1042. return self::insert($table, $values, $do_insert);
  1043. }
  1044. $old_fields = [];
  1045. }
  1046. }
  1047. $do_update = (count($old_fields) == 0);
  1048. foreach ($old_fields AS $fieldname => $content) {
  1049. if (isset($fields[$fieldname])) {
  1050. if ($fields[$fieldname] == $content) {
  1051. unset($fields[$fieldname]);
  1052. } else {
  1053. $do_update = true;
  1054. }
  1055. }
  1056. }
  1057. if (!$do_update || (count($fields) == 0)) {
  1058. return true;
  1059. }
  1060. $sql = "UPDATE `".$table."` SET `".
  1061. implode("` = ?, `", array_keys($fields))."` = ?".$condition_string;
  1062. $params1 = array_values($fields);
  1063. $params2 = array_values($condition);
  1064. $params = array_merge_recursive($params1, $params2);
  1065. return self::e($sql, $params);
  1066. }
  1067. /**
  1068. * Retrieve a single record from a table and returns it in an associative array
  1069. *
  1070. * @brief Retrieve a single record from a table
  1071. * @param string $table
  1072. * @param array $fields
  1073. * @param array $condition
  1074. * @param array $params
  1075. * @return bool|array
  1076. * @see self::select
  1077. */
  1078. public static function selectFirst($table, array $fields = [], array $condition = [], $params = [])
  1079. {
  1080. $params['limit'] = 1;
  1081. $result = self::select($table, $fields, $condition, $params);
  1082. if (is_bool($result)) {
  1083. return $result;
  1084. } else {
  1085. $row = self::fetch($result);
  1086. self::close($result);
  1087. return $row;
  1088. }
  1089. }
  1090. /**
  1091. * @brief Select rows from a table
  1092. *
  1093. * @param string $table Table name
  1094. * @param array $fields Array of selected fields, empty for all
  1095. * @param array $condition Array of fields for condition
  1096. * @param array $params Array of several parameters
  1097. *
  1098. * @return boolean|object
  1099. *
  1100. * Example:
  1101. * $table = "item";
  1102. * $fields = array("id", "uri", "uid", "network");
  1103. *
  1104. * $condition = array("uid" => 1, "network" => 'dspr');
  1105. * or:
  1106. * $condition = array("`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr');
  1107. *
  1108. * $params = array("order" => array("id", "received" => true), "limit" => 10);
  1109. *
  1110. * $data = DBA::select($table, $fields, $condition, $params);
  1111. */
  1112. public static function select($table, array $fields = [], array $condition = [], array $params = [])
  1113. {
  1114. if ($table == '') {
  1115. return false;
  1116. }
  1117. $table = self::escape($table);
  1118. if (count($fields) > 0) {
  1119. $select_fields = "`" . implode("`, `", array_values($fields)) . "`";
  1120. } else {
  1121. $select_fields = "*";
  1122. }
  1123. $condition_string = self::buildCondition($condition);
  1124. $param_string = self::buildParameter($params);
  1125. $sql = "SELECT " . $select_fields . " FROM `" . $table . "`" . $condition_string . $param_string;
  1126. $result = self::p($sql, $condition);
  1127. return $result;
  1128. }
  1129. /**
  1130. * @brief Counts the rows from a table satisfying the provided condition
  1131. *
  1132. * @param string $table Table name
  1133. * @param array $condition array of fields for condition
  1134. *
  1135. * @return int
  1136. *
  1137. * Example:
  1138. * $table = "item";
  1139. *
  1140. * $condition = ["uid" => 1, "network" => 'dspr'];
  1141. * or:
  1142. * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
  1143. *
  1144. * $count = DBA::count($table, $condition);
  1145. */
  1146. public static function count($table, array $condition = [])
  1147. {
  1148. if ($table == '') {
  1149. return false;
  1150. }
  1151. $condition_string = self::buildCondition($condition);
  1152. $sql = "SELECT COUNT(*) AS `count` FROM `".$table."`".$condition_string;
  1153. $row = self::fetchFirst($sql, $condition);
  1154. return $row['count'];
  1155. }
  1156. /**
  1157. * @brief Returns the SQL condition string built from the provided condition array
  1158. *
  1159. * This function operates with two modes.
  1160. * - Supplied with a filed/value associative array, it builds simple strict
  1161. * equality conditions linked by AND.
  1162. * - Supplied with a flat list, the first element is the condition string and
  1163. * the following arguments are the values to be interpolated
  1164. *
  1165. * $condition = ["uid" => 1, "network" => 'dspr'];
  1166. * or:
  1167. * $condition = ["`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr'];
  1168. *
  1169. * In either case, the provided array is left with the parameters only
  1170. *
  1171. * @param array $condition
  1172. * @return string
  1173. */
  1174. public static function buildCondition(array &$condition = [])
  1175. {
  1176. $condition_string = '';
  1177. if (count($condition) > 0) {
  1178. reset($condition);
  1179. $first_key = key($condition);
  1180. if (is_int($first_key)) {
  1181. $condition_string = " WHERE (" . array_shift($condition) . ")";
  1182. } else {
  1183. $new_values = [];
  1184. $condition_string = "";
  1185. foreach ($condition as $field => $value) {
  1186. if ($condition_string != "") {
  1187. $condition_string .= " AND ";
  1188. }
  1189. if (is_array($value)) {
  1190. /* Workaround for MySQL Bug #64791.
  1191. * Never mix data types inside any IN() condition.
  1192. * In case of mixed types, cast all as string.
  1193. * Logic needs to be consistent with DBA::p() data types.
  1194. */
  1195. $is_int = false;
  1196. $is_alpha = false;
  1197. foreach ($value as $single_value) {
  1198. if (is_int($single_value)) {
  1199. $is_int = true;
  1200. } else {
  1201. $is_alpha = true;
  1202. }
  1203. }
  1204. if ($is_int && $is_alpha) {
  1205. foreach ($value as &$ref) {
  1206. if (is_int($ref)) {
  1207. $ref = (string)$ref;
  1208. }
  1209. }
  1210. unset($ref); //Prevent accidental re-use.
  1211. }
  1212. $new_values = array_merge($new_values, array_values($value));
  1213. $placeholders = substr(str_repeat("?, ", count($value)), 0, -2);
  1214. $condition_string .= "`" . $field . "` IN (" . $placeholders . ")";
  1215. } else {
  1216. $new_values[$field] = $value;
  1217. $condition_string .= "`" . $field . "` = ?";
  1218. }
  1219. }
  1220. $condition_string = " WHERE (" . $condition_string . ")";
  1221. $condition = $new_values;
  1222. }
  1223. }
  1224. return $condition_string;
  1225. }
  1226. /**
  1227. * @brief Returns the SQL parameter string built from the provided parameter array
  1228. *
  1229. * @param array $params
  1230. * @return string
  1231. */
  1232. public static function buildParameter(array $params = [])
  1233. {
  1234. $order_string = '';
  1235. if (isset($params['order'])) {
  1236. $order_string = " ORDER BY ";
  1237. foreach ($params['order'] AS $fields => $order) {
  1238. if (!is_int($fields)) {
  1239. $order_string .= "`" . $fields . "` " . ($order ? "DESC" : "ASC") . ", ";
  1240. } else {
  1241. $order_string .= "`" . $order . "`, ";
  1242. }
  1243. }
  1244. $order_string = substr($order_string, 0, -2);
  1245. }
  1246. $limit_string = '';
  1247. if (isset($params['limit']) && is_int($params['limit'])) {
  1248. $limit_string = " LIMIT " . intval($params['limit']);
  1249. }
  1250. if (isset($params['limit']) && is_array($params['limit'])) {
  1251. $limit_string = " LIMIT " . intval($params['limit'][0]) . ", " . intval($params['limit'][1]);
  1252. }
  1253. return $order_string.$limit_string;
  1254. }
  1255. /**
  1256. * @brief Fills an array with data from a query
  1257. *
  1258. * @param object $stmt statement object
  1259. * @return array Data array
  1260. */
  1261. public static function toArray($stmt, $do_close = true) {
  1262. if (is_bool($stmt)) {
  1263. return $stmt;
  1264. }
  1265. $data = [];
  1266. while ($row = self::fetch($stmt)) {
  1267. $data[] = $row;
  1268. }
  1269. if ($do_close) {
  1270. self::close($stmt);
  1271. }
  1272. return $data;
  1273. }
  1274. /**
  1275. * @brief Returns the error number of the last query
  1276. *
  1277. * @return string Error number (0 if no error)
  1278. */
  1279. public static function errorNo() {
  1280. return self::$errorno;
  1281. }
  1282. /**
  1283. * @brief Returns the error message of the last query
  1284. *
  1285. * @return string Error message ('' if no error)
  1286. */
  1287. public static function errorMessage() {
  1288. return self::$error;
  1289. }
  1290. /**
  1291. * @brief Closes the current statement
  1292. *
  1293. * @param object $stmt statement object
  1294. * @return boolean was the close successful?
  1295. */
  1296. public static function close($stmt) {
  1297. $a = get_app();
  1298. $stamp1 = microtime(true);
  1299. if (!is_object($stmt)) {
  1300. return false;
  1301. }
  1302. switch (self::$driver) {
  1303. case 'pdo':
  1304. $ret = $stmt->closeCursor();
  1305. break;
  1306. case 'mysqli':
  1307. // MySQLi offers both a mysqli_stmt and a mysqli_result class.
  1308. // We should be careful not to assume the object type of $stmt
  1309. // because DBA::p() has been able to return both types.
  1310. if ($stmt instanceof mysqli_stmt) {
  1311. $stmt->free_result();
  1312. $ret = $stmt->close();
  1313. } elseif ($stmt instanceof mysqli_result) {
  1314. $stmt->free();
  1315. $ret = true;
  1316. } else {
  1317. $ret = false;
  1318. }
  1319. break;
  1320. }
  1321. $a->saveTimestamp($stamp1, 'database');
  1322. return $ret;
  1323. }
  1324. /**
  1325. * @brief Return a list of database processes
  1326. *
  1327. * @return array
  1328. * 'list' => List of processes, separated in their different states
  1329. * 'amount' => Number of concurrent database processes
  1330. */
  1331. public static function processlist()
  1332. {
  1333. $ret = self::p("SHOW PROCESSLIST");
  1334. $data = self::toArray($ret);
  1335. $s = [];
  1336. $processes = 0;
  1337. $states = [];
  1338. foreach ($data as $process) {
  1339. $state = trim($process["State"]);
  1340. // Filter out all non blocking processes
  1341. if (!in_array($state, ["", "init", "statistics", "updating"])) {
  1342. ++$states[$state];
  1343. ++$processes;
  1344. }
  1345. }
  1346. $statelist = "";
  1347. foreach ($states as $state => $usage) {
  1348. if ($statelist != "") {
  1349. $statelist .= ", ";
  1350. }
  1351. $statelist .= $state.": ".$usage;
  1352. }
  1353. return(["list" => $statelist, "amount" => $processes]);
  1354. }
  1355. /**
  1356. * Checks if $array is a filled array with at least one entry.
  1357. *
  1358. * @param mixed $array A filled array with at least one entry
  1359. *
  1360. * @return boolean Whether $array is a filled array or an object with rows
  1361. */
  1362. public static function isResult($array)
  1363. {
  1364. // It could be a return value from an update statement
  1365. if (is_bool($array)) {
  1366. return $array;
  1367. }
  1368. if (is_object($array)) {
  1369. return self::numRows($array) > 0;
  1370. }
  1371. return (is_array($array) && (count($array) > 0));
  1372. }
  1373. /**
  1374. * @brief Callback function for "esc_array"
  1375. *
  1376. * @param mixed $value Array value
  1377. * @param string $key Array key
  1378. * @param boolean $add_quotation add quotation marks for string values
  1379. * @return void
  1380. */
  1381. private static function escapeArrayCallback(&$value, $key, $add_quotation)
  1382. {
  1383. if (!$add_quotation) {
  1384. if (is_bool($value)) {
  1385. $value = ($value ? '1' : '0');
  1386. } else {
  1387. $value = self::escape($value);
  1388. }
  1389. return;
  1390. }
  1391. if (is_bool($value)) {
  1392. $value = ($value ? 'true' : 'false');
  1393. } elseif (is_float($value) || is_integer($value)) {
  1394. $value = (string) $value;
  1395. } else {
  1396. $value = "'" . self::escape($value) . "'";
  1397. }
  1398. }
  1399. /**
  1400. * @brief Escapes a whole array
  1401. *
  1402. * @param mixed $arr Array with values to be escaped
  1403. * @param boolean $add_quotation add quotation marks for string values
  1404. * @return void
  1405. */
  1406. public static function escapeArray(&$arr, $add_quotation = false)
  1407. {
  1408. array_walk($arr, 'self::escapeArrayCallback', $add_quotation);
  1409. }
  1410. }