+ /**
+ * @brief Analyze a database query and log this if some conditions are met.
+ *
+ * @param string $query The database query that will be analyzed
+ */
+ private static function log_index($query) {
+ $a = get_app();
+
+ if (empty($a->config["system"]["db_log_index"])) {
+ return;
+ }
+
+ // Don't explain an explain statement
+ if (strtolower(substr($query, 0, 7)) == "explain") {
+ return;
+ }
+
+ // Only do the explain on "select", "update" and "delete"
+ if (!in_array(strtolower(substr($query, 0, 6)), array("select", "update", "delete"))) {
+ return;
+ }
+
+ $r = self::p("EXPLAIN ".$query);
+ if (!DBM::is_result($r)) {
+ return;
+ }
+
+ $watchlist = explode(',', $a->config["system"]["db_log_index_watch"]);
+ $blacklist = explode(',', $a->config["system"]["db_log_index_blacklist"]);
+
+ while ($row = dba::fetch($r)) {
+ if ((intval($a->config["system"]["db_loglimit_index"]) > 0)) {
+ $log = (in_array($row['key'], $watchlist) &&
+ ($row['rows'] >= intval($a->config["system"]["db_loglimit_index"])));
+ } else {
+ $log = false;
+ }
+
+ if ((intval($a->config["system"]["db_loglimit_index_high"]) > 0) && ($row['rows'] >= intval($a->config["system"]["db_loglimit_index_high"]))) {
+ $log = true;
+ }
+
+ if (in_array($row['key'], $blacklist) || ($row['key'] == "")) {
+ $log = false;
+ }
+
+ if ($log) {
+ $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
+ @file_put_contents($a->config["system"]["db_log_index"], datetime_convert()."\t".
+ $row['key']."\t".$row['rows']."\t".$row['Extra']."\t".
+ basename($backtrace[1]["file"])."\t".
+ $backtrace[1]["line"]."\t".$backtrace[2]["function"]."\t".
+ substr($query, 0, 2000)."\n", FILE_APPEND);
+ }
+ }
+ }
+
+ public static function escape($str) {
+ switch (self::$driver) {
+ case 'pdo':
+ return substr(@self::$db->quote($str, PDO::PARAM_STR), 1, -1);
+ case 'mysqli':
+ return @self::$db->real_escape_string($str);
+ }
+ }
+
+ public static function connected() {
+ $connected = false;
+
+ switch (self::$driver) {
+ case 'pdo':
+ $r = dba::p("SELECT 1");
+ if (DBM::is_result($r)) {
+ $row = dba::inArray($r);
+ $connected = ($row[0]['1'] == '1');
+ }
+ break;
+ case 'mysqli':
+ $connected = self::$db->ping();
+ break;
+ }
+ return $connected;
+ }
+
+ /**
+ * @brief Replaces ANY_VALUE() function by MIN() function,
+ * if the database server does not support ANY_VALUE().
+ *
+ * Considerations for Standard SQL, or MySQL with ONLY_FULL_GROUP_BY (default since 5.7.5).
+ * ANY_VALUE() is available from MySQL 5.7.5 https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html
+ * A standard fall-back is to use MIN().
+ *
+ * @param string $sql An SQL string without the values
+ * @return string The input SQL string modified if necessary.
+ */
+ public static function any_value_fallback($sql) {
+ $server_info = self::server_info();
+ if (version_compare($server_info, '5.7.5', '<') ||
+ (stripos($server_info, 'MariaDB') !== false)) {
+ $sql = str_ireplace('ANY_VALUE(', 'MIN(', $sql);
+ }
+ return $sql;
+ }
+
+ /**
+ * @brief beautifies the query - useful for "SHOW PROCESSLIST"
+ *
+ * This is safe when we bind the parameters later.
+ * The parameter values aren't part of the SQL.
+ *
+ * @param string $sql An SQL string without the values
+ * @return string The input SQL string modified if necessary.
+ */
+ public static function clean_query($sql) {
+ $search = array("\t", "\n", "\r", " ");
+ $replace = array(' ', ' ', ' ', ' ');
+ do {
+ $oldsql = $sql;
+ $sql = str_replace($search, $replace, $sql);
+ } while ($oldsql != $sql);
+
+ return $sql;
+ }
+
+
+ /**
+ * @brief Replaces the ? placeholders with the parameters in the $args array
+ *
+ * @param string $sql SQL query
+ * @param array $args The parameters that are to replace the ? placeholders
+ * @return string The replaced SQL query
+ */
+ private static function replace_parameters($sql, $args) {
+ $offset = 0;
+ foreach ($args AS $param => $value) {
+ if (is_int($args[$param]) || is_float($args[$param])) {
+ $replace = intval($args[$param]);
+ } else {
+ $replace = "'".self::escape($args[$param])."'";
+ }
+
+ $pos = strpos($sql, '?', $offset);
+ if ($pos !== false) {
+ $sql = substr_replace($sql, $replace, $pos, 1);
+ }
+ $offset = $pos + strlen($replace);
+ }
+ return $sql;
+ }
+
+ /**
+ * @brief Convert parameter array to an universal form
+ * @param array $args Parameter array
+ * @return array universalized parameter array
+ */
+ private static function getParam($args) {
+ unset($args[0]);
+
+ // When the second function parameter is an array then use this as the parameter array
+ if ((count($args) > 0) && (is_array($args[1]))) {
+ return $args[1];