<?php
// class-query.php

class query extends database {

	public $connection;

	/**
	 * Flag to ensure we don't run into recursion problems when checking the collation.
	 *
	 * @since 4.2.0
	 * @see wpdb::check_safe_collation()
	 * @var bool
	 */
	private $checking_collation = false;

	public function __construct() {
		parent::__construct();
		// $database = new database;
		// $this->connection = $database->connection;
	} //__construct()

	public function query_to_array($query, $key = 'id') {
		$result = [];
		if(! $query)
			return false;
		while($rows = mysqli_fetch_assoc($query)) {
			if(!isset($rows[$key])) continue;
			$result[$rows[$key]] = $rows;
		}
		return $result;
	} //query_to_array()

	public function _query_to_array($query, $key = 'id') {
		$result = [];
		if(! $query)
			return false;
		while($rows = mysqli_fetch_assoc($query)) {
			$rows['meta'] = json_decode($rows['meta']);
			$result[$rows[$key]] = $rows;
		}
		return $result;
	} //query_to_array()

	// deprecating
	public function select($table,
				 $where = array(), 
				 $order_by = 0, 
				 $order = 'ASC', 
				 $limit = 0,
				 $compare = '=', 
				 $logic = array('AND'), 
				 $select = '*',
				 $group = false,
				 $offset = 0
				) {

		$sql = "SELECT ".$select;
		$sql .= " FROM ".$table;
		if(_count($where) && is_array($where)) :
			$sql .= " WHERE ";
			$i = 1;
			foreach ($where as $key => $value) {
				if(is_array($value)) {
					$sql .= "(";
					foreach ($value as $v) {
						$last = ($v == end($value)) ? true : false;
						if(!_is_numeric($v)) {
							$v = "'".$v."'";
						}
						if(is_array($compare)) {
							$compare_out = (isset($compare[$i - 1])) ? $compare[$i - 1] : '=';
						} else $compare_out = $compare;
						$sql .= $key." ".$compare_out." ".$v;
						$sql .= $last ? "" : " OR ";
					}
					$sql .= ")";
				} else {
					if(!_is_numeric($value)) {
						$value = "'".$value."'";
					}
					if(is_array($compare)) {
						$compare_out = (isset($compare[$i - 1])) ? $compare[$i - 1] : '=';
					} else $compare_out = $compare;
					$sql .= $key." ".$compare_out." ".$value;
					
				}
				if(_count($where) != $i){
					if(is_array($logic)) :
						if(isset($logic[$i - 1])) {
							$next_logic = $i - 1;
						} else $next_logic = 0;
						$sql .= ' '.$logic[$next_logic].' ';
					endif;
				}
				$i++;
			}
		endif;
		if($group) {
			$sql .= " GROUP BY ".$group;
		}
		if ($order_by) {
			$sql .= ($order == "RAND" || $order == "RAND()") ? " ORDER BY RAND()" :" ORDER BY `".$order_by."` ".$order;
		}
		if ($limit != -1 && $limit != 0) {
			$sql .= " LIMIT ".$limit;
		}
		if ($offset != -1 && $offset != 0) {
			$sql .= " OFFSET ".$offset;
		}
		return mysqli_query($this->connection, $sql);
	} //select()

	public function quick_select($table, $settings = array(), $format = OBJECT) {
		$default = [
			'where' => array(), 
			'order_by' => 0, 
			'order' => 'ASC', 
			'limit' => 0,
			'compare' => '=', 
			'logic' => array('AND'), 
			'select' => '*',
			'group' => false,
			'offset' => 0
		];
		$settings = array_merge($default, $settings);
		if(isset($settings['where']['user_fullname'])) {
			$fullname = $settings['where']['user_fullname'];
			unset($settings['where']['user_fullname']);
			if(trim($fullname) !== "") {
				$user = new user;
				$surname = $user->get_user_by_meta_data(['surname' => '%'.$fullname.'%'], "LIKE", false, 0, 0, 'user_id', 'ASC', "user_id");
				$other_name = $user->get_user_by_meta_data(['other_names' => '%'.$fullname.'%'], "LIKE", false, 0, 0, 'user_id', 'ASC', "user_id");
				if(!empty($surname) || !empty($other_name)) {
					$settings['where']['user_id'] = array_merge($surname, $other_name);
				} else {
					return $format == OBJECT_K || $format == OBJECT ? new object() : array();
				}
			}
		}

		$sql = "SELECT ".$settings['select'];
		$sql .= " FROM ".$table;
		if(_count($settings['where']) && is_array($settings['where'])) :
			$sql .= " WHERE ";
			$i = 1;
			foreach ($settings['where'] as $key => $value) {
				if(is_array($value)) {
					$sql .= "(";
					foreach ($value as $v) {
						$last = ($v == end($value)) ? true : false;
						if(!_is_numeric($v)) {
							$v = "'".$v."'";
						}
						if(is_array($settings['compare'])) {
							$compare_out = (isset($settings['compare'][$key])) ? $settings['compare'][$key] : '=';
						} else $compare_out = $settings['compare'];
						$sql .= $key." ".$compare_out." ".$v;
						$sql .= $last ? "" : " OR ";
					}
					$sql .= ")";
				} else {
					if(!_is_numeric($value)) {
						$value = "'".$value."'";
					}
					if(is_array($settings['compare'])) {
						$compare_out = (isset($settings['compare'][$key])) ? $settings['compare'][$key] : '=';
					} else $compare_out = $settings['compare'];
					$sql .= $key." ".$compare_out." ".$value;
					
				}
				if(_count($settings['where']) != $i){
					if(is_array($settings['logic'])) :
						if(isset($settings['logic'][$i - 1])) {
							$next_logic = $i - 1;
						} else $next_logic = 0;
						$sql .= ' '.$settings['logic'][$next_logic].' ';
					endif;
				}
				$i++;
			}
		endif;
		if($settings['group']) {
			$sql .= " GROUP BY ".$settings['group'];
		}
		if ($settings['order_by']) {
			$sql .= ($settings['order'] == "RAND" || $settings['order'] == "RAND()") ? " ORDER BY RAND()" :" ORDER BY `".$settings['order_by']."` ".$settings['order'];
		}
		if ($settings['limit'] != -1 && $settings['limit'] != 0) {
			$sql .= " LIMIT ".$settings['limit'];
		}
		if ($settings['offset'] != -1 && $settings['offset'] != 0) {
			$sql .= " OFFSET ".$settings['offset'];
		}

		return $this->get_results($sql, $format);
		
	} //quick_select()

	public function insert($table,
				$columns,
				$values,
				$multiple = false
				) {
		if(!is_array($columns) or !is_array($values)){
			return false;
		}
		if((_count($columns) != _count($values)) && !$multiple) {
			return false;
		}
		if(empty($columns) or empty($values)) {
			return false;
		}
		$values = sanitize($values, true, $this);
		$sql = "INSERT INTO ".$table." (";
		$i = 1;
		foreach ($columns as $column) {
			$sql .= ' '.$column;
			if($i < _count($columns)) {
				$sql .= ',';
			}
			$i++;
		}
		$sql .= " ) VALUES ";
		if($multiple) {
			$last = _count($values) - 1;
			foreach ($values as $key => $value) {
				if(!is_array($value)){
					return false;
				}
				if(_count($columns) != _count($value)) {
					return false;
				}
				$sql .= $this->create_value_string($value);
				if($key != $last) {
					$sql .= ", ";
				}
			}
		} else {
			$sql .= $this->create_value_string($values);
		}
		if (mysqli_query($this->connection, $sql)) {
			return mysqli_insert_id($this->connection);
		} else {
			echo mysqli_error($this->connection); 
			return false;
		}
	} //insert()

	private function create_value_string($values) {
		$str = "(";
		$i = 1;
		foreach ($values as $value) {
			if(!_is_numeric($value)) {
				$value = "'".$value."'";
			}
			$str .= ' '.$value;
			if($i < _count($values)) {
				$str .= ',';
			}
			$i++;
		}
		$str .= " )";
		return $str;
	}

	public function update( $table,
				$set,
				$where,
				$compare = "=",
				$logic = array("AND")
			) {
		if(!is_array($set) or !is_array($where)){
			return false;
		}
		if(empty($set) or empty($where)){
			return false;
		}
		$sql = "UPDATE ".$table." SET ";
		$i = 1;
		foreach ($set as $key => $value) {
			if(!_is_numeric($value)) {
				$value = "'".$value."'";
			}
			$sql .= $key ." = ". $value;
			if(_count($set) > $i) {
				$sql .= ", ";
			}
			$i++;
		}
		$sql .= " WHERE ";
		$i = 1;
		foreach ($where as $key => $value) {
			if(!_is_numeric($value)) {
				$value = "'".$value."'";
			}
			$sql .= $key." ".$compare." ".$value;
			if(_count($where) != $i){
				if(is_array($logic)) :
					if(isset($logic[$i - 1])) {
						$next_logic = $i - 1;
					} else $next_logic = 0;
					$sql .= ' '.$logic[$next_logic].' ';
				endif;
			}
			$i++;
		}
		try {
			$res = mysqli_query($this->connection, $sql);
		}
		catch(Exception $e) {

		}
		$error = mysqli_error($this->connection);
		$this->log_error($error, $sql);
		return $res;
	} //update()

	public function delete( $table,
					$where,
					$compare = "=",
					$logic = array('AND')
				) {
		$sql = "DELETE FROM ".$table. " WHERE ";
		$i = 1;
		foreach ($where as $key => $value) {
			if(!_is_numeric($value)) {
				$value = "'".$value."'";
			}
			$sql .= $key." ".$compare." ".$value;
			if(_count($where) != $i){
				if(is_array($logic)) :
					if(isset($logic[$i - 1])) {
						$next_logic = $i - 1;
					} else $next_logic = 0;
					$sql .= ' '.$logic[$next_logic].' ';
				endif;
			}
			$i++;
		}
		return $this->query($sql);
	} //delete()

	public function get_last_id_from_table($table, $to_array = false) {
		$sql = "SELECT max(id) FROM ".$table;
		$query = mysqli_query($this->connection, $sql);
		if($to_array) {
			if(!$query) {
				return 0;
			}
			$query = $this->query_to_array($query);
			if(_count($query)) {
				return array_shift($query)['max(id)'];
			} else return 1;
		}
		return $query;
	}

	/**
	 * Real escape, using mysqli_real_escape_string() or mysql_real_escape_string()
	 *
	 * @see mysqli_real_escape_string()
	 * @see mysql_real_escape_string()
	 * @since 2.8.0
	 *
	 * @param  string $string to escape
	 * @return string escaped
	 */
	function _real_escape( $string ) {
		if ( $this->connection ) {
			if ( $this->use_mysqli ) {
				$escaped = mysqli_real_escape_string( $this->connection, $string );
			} else {
				$escaped = mysql_real_escape_string( $string, $this->connection );
			}
		} else {
			$escaped = addslashes( $string );
		}

		// return $this->add_placeholder_escape( $escaped );
		return $escaped;
	}

	/**
	 * Adds a placeholder escape string, to escape anything that resembles a printf() placeholder.
	 *
	 * @since 4.8.3
	 *
	 * @param string $query The query to escape.
	 * @return string The query with the placeholder escape string inserted where necessary.
	 */
	public function add_placeholder_escape( $query ) {
		/*
		 * To prevent returning anything that even vaguely resembles a placeholder,
		 * we clobber every % we can find.
		 */
		return str_replace( '%', $this->placeholder_escape(), $query );
	}

	/**
	 * Removes the placeholder escape strings from a query.
	 *
	 * @since 4.8.3
	 *
	 * @param string $query The query from which the placeholder will be removed.
	 * @return string The query with the placeholder removed.
	 */
	public function remove_placeholder_escape( $query ) {
		return str_replace( $this->placeholder_escape(), '%', $query );
	}

	/**
	 * Generates and returns a placeholder escape string for use in queries returned by ::prepare().
	 *
	 * @since 4.8.3
	 *
	 * @return string String to escape placeholders.
	 */
	public function placeholder_escape() {
		static $placeholder;

		if ( ! $placeholder ) {
			// If ext/hash is not present, compat.php's hash_hmac() does not support sha256.
			$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
			// Old WP installs may not have AUTH_SALT defined.
			$salt = defined( 'SALT' ) && SALT ? SALT : (string) rand();

			$placeholder = '{' . hash_hmac( $algo, uniqid( $salt, true ), $salt ) . '}';
		}

		return $placeholder;
	}

	/**
	 * Perform a MySQL database query, using current database connection.
	 *
	 * More information can be found on the codex page.
	 *
	 * @since 0.71
	 *
	 * @param string $query Database query
	 * @return int|bool Boolean true for CREATE, ALTER, TRUNCATE and DROP queries. Number of rows
	 *                  affected/selected for all other queries. Boolean false on error.
	 */
	public function query( $query ) {
		if ( ! $this->ready ) {
			$this->check_current_query = true;
			return false;
		}

		/**
		 * Filters the database query.
		 *
		 * Some queries are made before the plugins have been loaded,
		 * and thus cannot be filtered with this method.
		 *
		 * @since 2.1.0
		 *
		 * @param string $query Database query.
		 */
		$query = apply_filters( 'query', $query );

		$this->flush();

		// Log how the function was called
		$this->func_call = "\$query->query(\"$query\")";

		// If we're writing to the database, make sure the query will write safely.
		if ( $this->check_current_query && ! $this->check_ascii( $query ) ) {
			$stripped_query = $this->strip_invalid_text_from_query( $query );
			// strip_invalid_text_from_query() can perform queries, so we need
			// to flush again, just to make sure everything is clear.
			$this->flush();
			if ( $stripped_query !== $query ) {
				$this->insert_id = 0;
				return false;
			}
		}

		$this->check_current_query = true;

		// Keep track of the last query for debug.
		$this->last_query = $query;

		$this->_do_query( $query );

		// MySQL server has gone away, try to reconnect.
		$mysql_errno = 0;
		if ( ! empty( $this->connection ) ) {
			if ( $this->use_mysqli ) {
				if ( $this->connection instanceof mysqli ) {
					$mysql_errno = mysqli_errno( $this->connection );
				} else {
					// $connection is defined, but isn't a real connection.
					// Something has gone horribly wrong, let's try a reconnect.
					$mysql_errno = 2006;
				}
			} else {
				if ( is_resource( $this->connection ) ) {
					$mysql_errno = mysql_errno( $this->connection );
				} else {
					$mysql_errno = 2006;
				}
			}
		}

		if ( empty( $this->connection ) || 2006 == $mysql_errno ) {
			if ( $this->check_connection() ) {
				$this->_do_query( $query );
			} else {
				$this->insert_id = 0;
				return false;
			}
		}

		// If there is an error then take note of it.
		if ( $this->use_mysqli ) {
			if ( $this->connection instanceof mysqli ) {
				$this->last_error = mysqli_error( $this->connection );
			} else {
				$this->last_error = __( 'Unable to retrieve the error message from MySQL' );
			}
		} else {
			if ( is_resource( $this->connection ) ) {
				$this->last_error = mysql_error( $this->connection );
			} else {
				$this->last_error = 'Unable to retrieve the error message from MySQL';
			}
		}

		if ( $this->last_error ) {
			// Clear insert_id on a subsequent failed insert.
			if ( $this->insert_id && preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
				$this->insert_id = 0;
			}
			return false;
		}

		if ( preg_match( '/^\s*(create|alter|truncate|drop)\s/i', $query ) ) {
			$return_val = $this->result;
		} elseif ( preg_match( '/^\s*(insert|delete|update|replace)\s/i', $query ) ) {
			if ( $this->use_mysqli ) {
				$this->rows_affected = mysqli_affected_rows( $this->connection );
			} else {
				$this->rows_affected = mysql_affected_rows( $this->connection );
			}
			// Take note of the insert_id
			if ( preg_match( '/^\s*(insert|replace)\s/i', $query ) ) {
				if ( $this->use_mysqli ) {
					$this->insert_id = mysqli_insert_id( $this->connection );
				} else {
					$this->insert_id = mysql_insert_id( $this->connection );
				}
			}
			// Return number of rows affected
			$return_val = $this->rows_affected;
		} else {
			$num_rows = 0;
			if ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
				while ( $row = mysqli_fetch_object( $this->result ) ) {
					$this->last_result[ $num_rows ] = $row;
					$num_rows++;
				}
			} elseif ( is_resource( $this->result ) ) {
				while ( $row = mysql_fetch_object( $this->result ) ) {
					$this->last_result[ $num_rows ] = $row;
					$num_rows++;
				}
			}

			// Log number of rows the query returned
			// and return number of rows selected
			$this->num_rows = $num_rows;
			$return_val     = $num_rows;
		}

		return $return_val;
	}

	/**
	 * Check if a string is ASCII.
	 *
	 * The negative regex is faster for non-ASCII strings, as it allows
	 * the search to finish as soon as it encounters a non-ASCII character.
	 *
	 * @since 4.2.0
	 *
	 * @param string $string String to check.
	 * @return bool True if ASCII, false if not.
	 */
	protected function check_ascii( $string ) {
		if ( function_exists( 'mb_check_encoding' ) ) {
			if ( mb_check_encoding( $string, 'ASCII' ) ) {
				return true;
			}
		} elseif ( ! preg_match( '/[^\x00-\x7F]/', $string ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Strips any invalid characters from the query.
	 *
	 * @since 4.2.0
	 *
	 * @param string $query Query to convert.
	 * @return string|WP_Error The converted query, or a WP_Error object if the conversion fails.
	 */
	protected function strip_invalid_text_from_query( $query ) {
		// We don't need to check the collation for queries that don't read data.
		$trimmed_query = ltrim( $query, "\r\n\t (" );
		if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $trimmed_query ) ) {
			return $query;
		}

		$table = $this->get_table_from_query( $query );
		if ( $table ) {
			$charset = $this->get_table_charset( $table );
			// if ( is_wp_error( $charset ) ) {
			// 	return $charset;
			// }

			// We can't reliably strip text from tables containing binary/blob columns
			if ( 'binary' === $charset ) {
				return $query;
			}
		} else {
			$charset = $this->charset;
		}

		$data = array(
			'value'   => $query,
			'charset' => $charset,
			'ascii'   => false,
			'length'  => false,
		);

		$data = $this->strip_invalid_text( array( $data ) );
		// if ( is_wp_error( $data ) ) {
		// 	return $data;
		// }

		return $data[0]['value'];
	}

	/**
	 * Find the first table name referenced in a query.
	 *
	 * @since 4.2.0
	 *
	 * @param string $query The query to search.
	 * @return string|false $table The table name found, or false if a table couldn't be found.
	 */
	protected function get_table_from_query( $query ) {
		// Remove characters that can legally trail the table name.
		$query = rtrim( $query, ';/-#' );

		// Allow (select...) union [...] style queries. Use the first query's table name.
		$query = ltrim( $query, "\r\n\t (" );

		// Strip everything between parentheses except nested selects.
		$query = preg_replace( '/\((?!\s*select)[^(]*?\)/is', '()', $query );

		// Quickly match most common queries.
		if ( preg_match(
			'/^\s*(?:'
				. 'SELECT.*?\s+FROM'
				. '|INSERT(?:\s+LOW_PRIORITY|\s+DELAYED|\s+HIGH_PRIORITY)?(?:\s+IGNORE)?(?:\s+INTO)?'
				. '|REPLACE(?:\s+LOW_PRIORITY|\s+DELAYED)?(?:\s+INTO)?'
				. '|UPDATE(?:\s+LOW_PRIORITY)?(?:\s+IGNORE)?'
				. '|DELETE(?:\s+LOW_PRIORITY|\s+QUICK|\s+IGNORE)*(?:.+?FROM)?'
			. ')\s+((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)/is',
			$query,
			$maybe
		) ) {
			return str_replace( '`', '', $maybe[1] );
		}

		// SHOW TABLE STATUS and SHOW TABLES WHERE Name = 'wp_posts'
		if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES).+WHERE\s+Name\s*=\s*("|\')((?:[0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)\\1/is', $query, $maybe ) ) {
			return $maybe[2];
		}

		// SHOW TABLE STATUS LIKE and SHOW TABLES LIKE 'wp\_123\_%'
		// This quoted LIKE operand seldom holds a full table name.
		// It is usually a pattern for matching a prefix so we just
		// strip the trailing % and unescape the _ to get 'wp_123_'
		// which drop-ins can use for routing these SQL statements.
		if ( preg_match( '/^\s*SHOW\s+(?:TABLE\s+STATUS|(?:FULL\s+)?TABLES)\s+(?:WHERE\s+Name\s+)?LIKE\s*("|\')((?:[\\\\0-9a-zA-Z$_.-]|[\xC2-\xDF][\x80-\xBF])+)%?\\1/is', $query, $maybe ) ) {
			return str_replace( '\\_', '_', $maybe[2] );
		}

		// Big pattern for the rest of the table-related queries.
		if ( preg_match(
			'/^\s*(?:'
				. '(?:EXPLAIN\s+(?:EXTENDED\s+)?)?SELECT.*?\s+FROM'
				. '|DESCRIBE|DESC|EXPLAIN|HANDLER'
				. '|(?:LOCK|UNLOCK)\s+TABLE(?:S)?'
				. '|(?:RENAME|OPTIMIZE|BACKUP|RESTORE|CHECK|CHECKSUM|ANALYZE|REPAIR).*\s+TABLE'
				. '|TRUNCATE(?:\s+TABLE)?'
				. '|CREATE(?:\s+TEMPORARY)?\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?'
				. '|ALTER(?:\s+IGNORE)?\s+TABLE'
				. '|DROP\s+TABLE(?:\s+IF\s+EXISTS)?'
				. '|CREATE(?:\s+\w+)?\s+INDEX.*\s+ON'
				. '|DROP\s+INDEX.*\s+ON'
				. '|LOAD\s+DATA.*INFILE.*INTO\s+TABLE'
				. '|(?:GRANT|REVOKE).*ON\s+TABLE'
				. '|SHOW\s+(?:.*FROM|.*TABLE)'
			. ')\s+\(*\s*((?:[0-9a-zA-Z$_.`-]|[\xC2-\xDF][\x80-\xBF])+)\s*\)*/is',
			$query,
			$maybe
		) ) {
			return str_replace( '`', '', $maybe[1] );
		}

		return false;
	}

	/**
	 * Strips any invalid characters based on value/charset pairs.
	 *
	 * @since 4.2.0
	 *
	 * @param array $data Array of value arrays. Each value array has the keys
	 *                    'value' and 'charset'. An optional 'ascii' key can be
	 *                    set to false to avoid redundant ASCII checks.
	 * @return array|WP_Error The $data parameter, with invalid characters removed from
	 *                        each value. This works as a passthrough: any additional keys
	 *                        such as 'field' are retained in each value array. If we cannot
	 *                        remove invalid characters, a WP_Error object is returned.
	 */
	protected function strip_invalid_text( $data ) {
		$db_check_string = false;

		foreach ( $data as &$value ) {
			$charset = $value['charset'];

			if ( is_array( $value['length'] ) ) {
				$length                  = $value['length']['length'];
				$truncate_by_byte_length = 'byte' === $value['length']['type'];
			} else {
				$length = false;
				// Since we have no length, we'll never truncate.
				// Initialize the variable to false. true would take us
				// through an unnecessary (for this case) codepath below.
				$truncate_by_byte_length = false;
			}

			// There's no charset to work with.
			if ( false === $charset ) {
				continue;
			}

			// Column isn't a string.
			if ( ! is_string( $value['value'] ) ) {
				continue;
			}

			$needs_validation = true;
			if (
				// latin1 can store any byte sequence
				'latin1' === $charset
			||
				// ASCII is always OK.
				( ! isset( $value['ascii'] ) && $this->check_ascii( $value['value'] ) )
			) {
				$truncate_by_byte_length = true;
				$needs_validation        = false;
			}

			if ( $truncate_by_byte_length ) {
				mbstring_binary_safe_encoding();
				if ( false !== $length && strlen( $value['value'] ) > $length ) {
					$value['value'] = substr( $value['value'], 0, $length );
				}
				reset_mbstring_encoding();

				if ( ! $needs_validation ) {
					continue;
				}
			}

			// utf8 can be handled by regex, which is a bunch faster than a DB lookup.
			if ( ( 'utf8' === $charset || 'utf8mb3' === $charset || 'utf8mb4' === $charset ) && function_exists( 'mb_strlen' ) ) {
				$regex = '/
					(
						(?: [\x00-\x7F]                  # single-byte sequences   0xxxxxxx
						|   [\xC2-\xDF][\x80-\xBF]       # double-byte sequences   110xxxxx 10xxxxxx
						|   \xE0[\xA0-\xBF][\x80-\xBF]   # triple-byte sequences   1110xxxx 10xxxxxx * 2
						|   [\xE1-\xEC][\x80-\xBF]{2}
						|   \xED[\x80-\x9F][\x80-\xBF]
						|   [\xEE-\xEF][\x80-\xBF]{2}';

				if ( 'utf8mb4' === $charset ) {
					$regex .= '
						|    \xF0[\x90-\xBF][\x80-\xBF]{2} # four-byte sequences   11110xxx 10xxxxxx * 3
						|    [\xF1-\xF3][\x80-\xBF]{3}
						|    \xF4[\x80-\x8F][\x80-\xBF]{2}
					';
				}

				$regex         .= '){1,40}                          # ...one or more times
					)
					| .                                  # anything else
					/x';
				$value['value'] = preg_replace( $regex, '$1', $value['value'] );

				if ( false !== $length && mb_strlen( $value['value'], 'UTF-8' ) > $length ) {
					$value['value'] = mb_substr( $value['value'], 0, $length, 'UTF-8' );
				}
				continue;
			}

			// We couldn't use any local conversions, send it to the DB.
			$value['db']     = true;
			$db_check_string = true;
		}
		unset( $value ); // Remove by reference.

		if ( $db_check_string ) {
			$queries = array();
			foreach ( $data as $col => $value ) {
				if ( ! empty( $value['db'] ) ) {
					// We're going to need to truncate by characters or bytes, depending on the length value we have.
					if ( isset( $value['length']['type'] ) && 'byte' === $value['length']['type'] ) {
						// Using binary causes LEFT() to truncate by bytes.
						$charset = 'binary';
					} else {
						$charset = $value['charset'];
					}

					if ( $this->charset ) {
						$connection_charset = $this->charset;
					} else {
						if ( $this->use_mysqli ) {
							$connection_charset = mysqli_character_set_name( $this->connection );
						} else {
							$connection_charset = mysql_client_encoding();
						}
					}

					if ( is_array( $value['length'] ) ) {
						$length          = sprintf( '%.0f', $value['length']['length'] );
						$queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), $length ) USING $connection_charset )", $value['value'] );
					} elseif ( 'binary' !== $charset ) {
						// If we don't have a length, there's no need to convert binary - it will always return the same result.
						$queries[ $col ] = $this->prepare( "CONVERT( CONVERT( %s USING $charset ) USING $connection_charset )", $value['value'] );
					}

					unset( $data[ $col ]['db'] );
				}
			}

			$sql = array();
			foreach ( $queries as $column => $query ) {
				if ( ! $query ) {
					continue;
				}

				$sql[] = $query . " AS x_$column";
			}

			$this->check_current_query = false;
			$row                       = $this->get_row( 'SELECT ' . implode( ', ', $sql ), ARRAY_A );
			if ( ! $row ) {
				// strip_invalid_text_failure
				return false;
			}

			foreach ( array_keys( $data ) as $column ) {
				if ( isset( $row[ "x_$column" ] ) ) {
					$data[ $column ]['value'] = $row[ "x_$column" ];
				}
			}
		}

		return $data;
	}

	/**
	 * Retrieves the character set for the given table.
	 *
	 * @since 4.2.0
	 *
	 * @param string $table Table name.
	 * @return string|WP_Error Table character set, WP_Error object if it couldn't be found.
	 */
	protected function get_table_charset( $table ) {
		$tablekey = strtolower( $table );

		/**
		 * Filters the table charset value before the DB is checked.
		 *
		 * Passing a non-null value to the filter will effectively short-circuit
		 * checking the DB for the charset, returning that value instead.
		 *
		 * @since 4.2.0
		 *
		 * @param string|null $charset The character set to use. Default null.
		 * @param string      $table   The name of the table being checked.
		 */
		$charset = apply_filters( 'pre_get_table_charset', null, $table );
		if ( null !== $charset ) {
			return $charset;
		}

		if ( isset( $this->table_charset[ $tablekey ] ) ) {
			return $this->table_charset[ $tablekey ];
		}

		$charsets = array();
		$columns  = array();

		$table_parts = explode( '.', $table );
		$table       = '`' . implode( '`.`', $table_parts ) . '`';
		$results     = $this->get_results( "SHOW FULL COLUMNS FROM $table" );
		if ( ! $results ) {
			// get_table_charset_failure
			return false;
		}

		foreach ( $results as $column ) {
			$columns[ strtolower( $column->Field ) ] = $column;
		}

		$this->col_meta[ $tablekey ] = $columns;

		foreach ( $columns as $column ) {
			if ( ! empty( $column->Collation ) ) {
				list( $charset ) = explode( '_', $column->Collation );

				// If the current connection can't support utf8mb4 characters, let's only send 3-byte utf8 characters.
				if ( 'utf8mb4' === $charset && ! $this->has_cap( 'utf8mb4' ) ) {
					$charset = 'utf8';
				}

				$charsets[ strtolower( $charset ) ] = true;
			}

			list( $type ) = explode( '(', $column->Type );

			// A binary/blob means the whole query gets treated like this.
			if ( in_array( strtoupper( $type ), array( 'BINARY', 'VARBINARY', 'TINYBLOB', 'MEDIUMBLOB', 'BLOB', 'LONGBLOB' ) ) ) {
				$this->table_charset[ $tablekey ] = 'binary';
				return 'binary';
			}
		}

		// utf8mb3 is an alias for utf8.
		if ( isset( $charsets['utf8mb3'] ) ) {
			$charsets['utf8'] = true;
			unset( $charsets['utf8mb3'] );
		}

		// Check if we have more than one charset in play.
		$count = _count( $charsets );
		if ( 1 === $count ) {
			$charset = key( $charsets );
		} elseif ( 0 === $count ) {
			// No charsets, assume this table can store whatever.
			$charset = false;
		} else {
			// More than one charset. Remove latin1 if present and recalculate.
			unset( $charsets['latin1'] );
			$count = _count( $charsets );
			if ( 1 === $count ) {
				// Only one charset (besides latin1).
				$charset = key( $charsets );
			} elseif ( 2 === $count && isset( $charsets['utf8'], $charsets['utf8mb4'] ) ) {
				// Two charsets, but they're utf8 and utf8mb4, use utf8.
				$charset = 'utf8';
			} else {
				// Two mixed character sets. ascii.
				$charset = 'ascii';
			}
		}

		$this->table_charset[ $tablekey ] = $charset;
		return $charset;
	}

	/**
	 * Determine if a database supports a particular feature.
	 *
	 * @since 2.7.0
	 * @since 4.1.0 Added support for the 'utf8mb4' feature.
	 * @since 4.6.0 Added support for the 'utf8mb4_520' feature.
	 *
	 * @see wpdb::db_version()
	 *
	 * @param string $db_cap The feature to check for. Accepts 'collation',
	 *                       'group_concat', 'subqueries', 'set_charset',
	 *                       'utf8mb4', or 'utf8mb4_520'.
	 * @return int|false Whether the database feature is supported, false otherwise.
	 */
	public function has_cap( $db_cap ) {
		$version = $this->db_version();

		switch ( strtolower( $db_cap ) ) {
			case 'collation':    // @since 2.5.0
			case 'group_concat': // @since 2.7.0
			case 'subqueries':   // @since 2.7.0
				return version_compare( $version, '4.1', '>=' );
			case 'set_charset':
				return version_compare( $version, '5.0.7', '>=' );
			case 'utf8mb4':      // @since 4.1.0
				if ( version_compare( $version, '5.5.3', '<' ) ) {
					return false;
				}
				if ( $this->use_mysqli ) {
					$client_version = mysqli_get_client_info();
				} else {
					$client_version = mysql_get_client_info();
				}

				/*
				 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
				 * mysqlnd has supported utf8mb4 since 5.0.9.
				 */
				if ( false !== strpos( $client_version, 'mysqlnd' ) ) {
					$client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version );
					return version_compare( $client_version, '5.0.9', '>=' );
				} else {
					return version_compare( $client_version, '5.5.3', '>=' );
				}
			case 'utf8mb4_520': // @since 4.6.0
				return version_compare( $version, '5.6', '>=' );
		}

		return false;
	}

	/**
	 * Retrieve an entire SQL result set from the database (i.e., many rows)
	 *
	 * Executes a SQL query and returns the entire SQL result.
	 *
	 * @since 0.71
	 *
	 * @param string $query  SQL query.
	 * @param string $output Optional. Any of ARRAY_A | ARRAY_N | OBJECT | OBJECT_K constants.
	 *                       With one of the first three, return an array of rows indexed from 0 by SQL result row number.
	 *                       Each row is an associative array (column => value, ...), a numerically indexed array (0 => value, ...), or an object. ( ->column = value ), respectively.
	 *                       With OBJECT_K, return an associative array of row objects keyed by the value of each row's first column's value.
	 *                       Duplicate keys are discarded.
	 * @return array|object|null Database query results
	 */
	public function get_results( $query = null, $output = OBJECT ) {
		$this->func_call = "\$query->get_results(\"$query\", $output)";

		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
			$this->check_current_query = false;
		}

		if ( $query ) {
			$this->query( $query );
		} else {
			return null;
		}

		$new_array = array();
		if ( $output == OBJECT ) {
			// Return an integer-keyed array of row objects
			return $this->last_result;
		} elseif ( $output == OBJECT_K ) {
			// Return an array of row objects with keys from column 1
			// (Duplicates are discarded)
			if ( $this->last_result ) {
				foreach ( $this->last_result as $row ) {
					$var_by_ref = get_object_vars( $row );
					$key        = array_shift( $var_by_ref );
					if ( ! isset( $new_array[ $key ] ) ) {
						$new_array[ $key ] = $row;
					}
				}
			}
			return $new_array;
		} elseif ( $output == ARRAY_A || $output == ARRAY_N ) {
			// Return an integer-keyed array of...
			if ( $this->last_result ) {
				foreach ( (array) $this->last_result as $row ) {
					if ( $output == ARRAY_N ) {
						// ...integer-keyed row arrays
						$new_array[] = array_values( get_object_vars( $row ) );
					} else {
						// ...column name-keyed row arrays
						$new_array[] = get_object_vars( $row );
					}
				}
			}
			return $new_array;
		} elseif ( strtoupper( $output ) === OBJECT ) {
			// Back compat for OBJECT being previously case insensitive.
			return $this->last_result;
		}
		return null;
	}


	/**
	 * Prepares a SQL query for safe execution. Uses sprintf()-like syntax.
	 *
	 * The following placeholders can be used in the query string:
	 *   %d (integer)
	 *   %f (float)
	 *   %s (string)
	 *
	 * All placeholders MUST be left unquoted in the query string. A corresponding argument
	 * MUST be passed for each placeholder.
	 *
	 * For compatibility with old behavior, numbered or formatted string placeholders (eg, %1$s, %5s)
	 * will not have quotes added by this function, so should be passed with appropriate quotes around
	 * them for your usage.
	 *
	 * Literal percentage signs (%) in the query string must be written as %%. Percentage wildcards (for example,
	 * to use in LIKE syntax) must be passed via a substitution argument containing the complete LIKE string, these
	 * cannot be inserted directly in the query string. Also see wpdb::esc_like().
	 *
	 * Arguments may be passed as individual arguments to the method, or as a single array containing
	 * all arguments. A combination of the two is not supported.
	 *
	 * Examples:
	 *     $wpdb->prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s", array( 'foo', 1337, '%bar' ) );
	 *     $wpdb->prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' );
	 *
	 * @link https://secure.php.net/sprintf Description of syntax.
	 * @since 2.3.0
	 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
	 *              by updating the function signature. The second parameter was changed
	 *              from `$args` to `...$args`.
	 *
	 * @param string      $query   Query statement with sprintf()-like placeholders
	 * @param array|mixed $args    The array of variables to substitute into the query's placeholders
	 *                             if being called with an array of arguments, or the first variable
	 *                             to substitute into the query's placeholders if being called with
	 *                             individual arguments.
	 * @param mixed       ...$args Further variables to substitute into the query's placeholders
	 *                             if being called with individual arguments.
	 * @return string|void Sanitized query string, if there is a query to prepare.
	 */
	public function prepare( $query, ...$args ) {
		if ( is_null( $query ) ) {
			return;
		}

		// This is not meant to be foolproof -- but it will catch obviously incorrect usage.
		if ( strpos( $query, '%' ) === false ) {
			var_dump('The query argument of prepare must have a placeholder.');
			return;
		}

		// If args were passed as an array (as in vsprintf), move them up.
		$passed_as_array = false;
		if ( is_array( $args[0] ) && _count( $args ) == 1 ) {
			$passed_as_array = true;
			$args            = $args[0];
		}

		foreach ( $args as $arg ) {
			if ( ! is_scalar( $arg ) && ! is_null( $arg ) ) {
				sprintf('Unsupported value type (%s).',
						gettype( $arg )
					);
			}
		}

		/*
		 * Specify the formatting allowed in a placeholder. The following are allowed:
		 *
		 * - Sign specifier. eg, $+d
		 * - Numbered placeholders. eg, %1$s
		 * - Padding specifier, including custom padding characters. eg, %05s, %'#5s
		 * - Alignment specifier. eg, %05-s
		 * - Precision specifier. eg, %.2f
		 */
		$allowed_format = '(?:[1-9][0-9]*[$])?[-+0-9]*(?: |0|\'.)?[-+0-9]*(?:\.[0-9]+)?';

		/*
		 * If a %s placeholder already has quotes around it, removing the existing quotes and re-inserting them
		 * ensures the quotes are consistent.
		 *
		 * For backward compatibility, this is only applied to %s, and not to placeholders like %1$s, which are frequently
		 * used in the middle of longer strings, or as table name placeholders.
		 */
		$query = str_replace( "'%s'", '%s', $query ); // Strip any existing single quotes.
		$query = str_replace( '"%s"', '%s', $query ); // Strip any existing double quotes.
		$query = preg_replace( '/(?<!%)%s/', "'%s'", $query ); // Quote the strings, avoiding escaped strings like %%s.

		$query = preg_replace( "/(?<!%)(%($allowed_format)?f)/", '%\\2F', $query ); // Force floats to be locale unaware.

		$query = preg_replace( "/%(?:%|$|(?!($allowed_format)?[sdF]))/", '%%\\1', $query ); // Escape any unescaped percents.

		// Count the number of valid placeholders in the query.
		$placeholders = preg_match_all( "/(^|[^%]|(%%)+)%($allowed_format)?[sdF]/", $query, $matches );

		if ( _count( $args ) !== $placeholders ) {
			if ( 1 === $placeholders && $passed_as_array ) {
				// If the passed query only expected one argument, but the wrong number of arguments were sent as an array, bail.
				var_dump('The query only expected one placeholder, but an array of multiple placeholders was sent.');

				return;
			} else {
				/*
				 * If we don't have the right number of placeholders, but they were passed as individual arguments,
				 * or we were expecting multiple arguments in an array, throw a warning.
				 */

				sprintf(
					/* translators: 1: Number of placeholders, 2: Number of arguments passed. */
					'The query does not contain the correct number of placeholders (%1$d) for the number of arguments passed (%2$d).',
					$placeholders,
					count( $args )
				);
			}
		}

		array_walk( $args, array( $this, 'escape_by_ref' ) );
		$query = vsprintf( $query, $args );

		return $this->add_placeholder_escape( $query );
	}

	/**
	 * Retrieve one row from the database.
	 *
	 * Executes a SQL query and returns the row from the SQL result.
	 *
	 * @since 0.71
	 *
	 * @param string|null $query  SQL query.
	 * @param string      $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
	 *                            an stdClass object, an associative array, or a numeric array, respectively. Default OBJECT.
	 * @param int         $y      Optional. Row to return. Indexed from 0.
	 * @return array|object|null|void Database query result in format specified by $output or null on failure
	 */
	public function get_row( $query = null, $output = OBJECT, $y = 0 ) {
		$this->func_call = "\$db->get_row(\"$query\",$output,$y)";

		if ( $this->check_current_query && $this->check_safe_collation( $query ) ) {
			$this->check_current_query = false;
		}

		if ( $query ) {
			$this->query( $query );
		} else {
			return null;
		}

		if ( ! isset( $this->last_result[ $y ] ) ) {
			return null;
		}

		if ( $output == OBJECT ) {
			return $this->last_result[ $y ] ? $this->last_result[ $y ] : null;
		} elseif ( $output == ARRAY_A ) {
			return $this->last_result[ $y ] ? get_object_vars( $this->last_result[ $y ] ) : null;
		} elseif ( $output == ARRAY_N ) {
			return $this->last_result[ $y ] ? array_values( get_object_vars( $this->last_result[ $y ] ) ) : null;
		} elseif ( strtoupper( $output ) === OBJECT ) {
			// Back compat for OBJECT being previously case insensitive.
			return $this->last_result[ $y ] ? $this->last_result[ $y ] : null;
		} else {
			$this->print_error( ' $db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N' );
		}
	}

	/**
	 * Check if the query is accessing a collation considered safe on the current version of MySQL.
	 *
	 * @since 4.2.0
	 *
	 * @param string $query The query to check.
	 * @return bool True if the collation is safe, false if it isn't.
	 */
	protected function check_safe_collation( $query ) {
		if ( $this->checking_collation ) {
			return true;
		}

		// We don't need to check the collation for queries that don't read data.
		$query = ltrim( $query, "\r\n\t (" );
		if ( preg_match( '/^(?:SHOW|DESCRIBE|DESC|EXPLAIN|CREATE)\s/i', $query ) ) {
			return true;
		}

		// All-ASCII queries don't need extra checking.
		if ( $this->check_ascii( $query ) ) {
			return true;
		}

		$table = $this->get_table_from_query( $query );
		if ( ! $table ) {
			return false;
		}

		$this->checking_collation = true;
		$collation                = $this->get_table_charset( $table );
		$this->checking_collation = false;

		// Tables with no collation, or latin1 only, don't need extra checking.
		if ( false === $collation || 'latin1' === $collation ) {
			return true;
		}

		$table = strtolower( $table );
		if ( empty( $this->col_meta[ $table ] ) ) {
			return false;
		}

		// If any of the columns don't have one of these collations, it needs more sanity checking.
		foreach ( $this->col_meta[ $table ] as $col ) {
			if ( empty( $col->Collation ) ) {
				continue;
			}

			if ( ! in_array( $col->Collation, array( 'utf8_general_ci', 'utf8_bin', 'utf8mb4_general_ci', 'utf8mb4_bin' ), true ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Kill cached query results.
	 *
	 * @since 0.71
	 */
	public function flush() {
		$this->last_result   = array();
		$this->col_info      = null;
		$this->last_query    = null;
		$this->rows_affected = 0;
		$this->num_rows      = 0;
		$this->last_error    = '';

		if ( $this->use_mysqli && $this->result instanceof mysqli_result ) {
			mysqli_free_result( $this->result );
			$this->result = null;

			// Sanity check before using the handle
			if ( empty( $this->connection ) || ! ( $this->connection instanceof mysqli ) ) {
				return;
			}

			// Clear out any results from a multi-query
			while ( mysqli_more_results( $this->connection ) ) {
				mysqli_next_result( $this->connection );
			}
		} elseif ( is_resource( $this->result ) ) {
			mysql_free_result( $this->result );
		}
	}

	/**
	 * Internal function to perform the mysql_query() call.
	 *
	 * @since 3.9.0
	 *
	 * @see wpdb::query()
	 *
	 * @param string $query The query to run.
	 */
	private function _do_query( $query ) {
		if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
			$this->timer_start();
		}

		try {
			if ( ! empty( $this->connection ) && $this->use_mysqli ) {

				$this->result = mysqli_query( $this->connection, $query );
			} elseif ( ! empty( $this->connection ) ) {

				$this->result = mysql_query( $query, $this->connection );
			}
		}
		catch(Exception $e) {
			// echo $e->getMessage();
		}
		
		$error = mysqli_error($this->connection);
		$this->log_error($error, $query);
		$this->num_queries++;

		if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
			$this->log_query(
				$query,
				$this->timer_stop(),
				false,
				$this->time_start,
				array()
			);
		}
	}

	/**
	 * Logs query data.
	 *
	 * @since 5.3.0
	 *
	 * @param string $query           The query's SQL.
	 * @param float  $query_time      Total time spent on the query, in seconds.
	 * @param string $query_callstack Comma separated list of the calling functions.
	 * @param float  $query_start     Unix timestamp of the time at the start of the query.
	 * @param array  $query_data      Custom query data.
	 * }
	 */
	public function log_query( $query, $query_time, $query_callstack, $query_start, $query_data ) {
		/**
		 * Filters the custom query data being logged.
		 *
		 * Caution should be used when modifying any of this data, it is recommended that any additional
		 * information you need to store about a query be added as a new associative entry to the fourth
		 * element $query_data.
		 *
		 * @since 5.3.0
		 *
		 * @param array  $query_data      Custom query data.
		 * @param string $query           The query's SQL.
		 * @param float  $query_time      Total time spent on the query, in seconds.
		 * @param string $query_callstack Comma separated list of the calling functions.
		 * @param float  $query_start     Unix timestamp of the time at the start of the query.
		 */
		// $query_data = apply_filters( 'log_query_custom_data', $query_data, $query, $query_time, $query_callstack, $query_start );

		$this->queries[] = array(
			$query,
			$query_time,
			$query_callstack,
			$query_start,
			$query_data,
		);
	}

	/**
	 * Starts the timer, for debugging purposes.
	 *
	 * @since 1.5.0
	 *
	 * @return true
	 */
	public function timer_start() {
		$this->time_start = microtime( true );
		return true;
	}

	/**
	 * Stops the debugging timer.
	 *
	 * @since 1.5.0
	 *
	 * @return float Total time spent on the query, in seconds
	 */
	public function timer_stop() {
		return ( microtime( true ) - $this->time_start );
	}

	/**
	 * Escapes content by reference for insertion into the database, for security
	 *
	 * @uses wpdb::_real_escape()
	 *
	 * @since 2.3.0
	 *
	 * @param string $string to escape
	 */
	public function escape_by_ref( &$string ) {
		if ( ! is_float( $string ) ) {
			$string = $this->_real_escape( $string );
		}
	}

	public function log_error($error = "", $sql = "") {
		if($error !== "") {
			$file = ABSPATH.'sql-errors.txt';
			if(!file_exists($file)) {
				file_put_contents($file, "SQL Error Log".PHP_EOL);
			}
			$error = date("d-m-Y h:i:sA")."\t".$error;
			if($sql !== "") {
				$error .= "\nSTATEMENT: \t\t\t\t\"".$sql."\"";
			}
			$error .= "\n";
			$fp = fopen($file, 'a');//opens file in append mode  
            fwrite($fp, $error.PHP_EOL); 
            fclose($fp);
		}
	}
}