?
Current File : /home/c/i/d/cideo/www/wp-includesVIp/js/crop/images/query.tar
class-gf-query-literal.php000066600000002226151263676600011573 0ustar00<?php

/**
 * The Gravity Forms Query Literal class.
 */
class GF_Query_Literal {
	/**
	 * @var int|string|float The value.
	 */
	private $_value;

	/**
	 * A literal value.
	 *
	 * @param int|string|float $value
	 */
	public function __construct( $value ) {
		if ( is_int( $value ) || is_string( $value ) || is_float( $value ) ) {
			$this->_value = $value;
		}
	}

	/**
	 * Get SQL for this.
	 *
	 * @param GF_Query $query The query.
	 * @param string $delimiter The delimiter for arrays.
	 *
	 * @return string The SQL.
	 */
	public function sql( $query, $delimiter = '' ) {
		global $wpdb;

		if ( is_int( $this->value ) ) {
			return $wpdb->prepare( '%d', $this->value );
		} elseif ( is_double( $this->value ) ) {
			return $this->value;
		} elseif ( is_string( $this->value ) || is_float( $this->value ) ) {
			return $wpdb->prepare( '%s', $this->value );
		}

		/**
		 * @todo Add support for Column, Call
		 */

		return '';
	}

	/**
	 * Proxy read-only values.
	 */
	public function __get( $key ) {
		switch ( $key ) :
			case 'value':
				return $this->_value;
		endswitch;
	}

	public function __isset( $key ) {
		return in_array( $key, array( 'value' ) );
	}
}
class-gf-query-column.php000066600000005761151263676600011443 0ustar00<?php

/**
 * The Gravity Forms Query Column class.
 */
class GF_Query_Column {

	/**
	 * @const Identifier for searching across any meta field.
	 */
	const META = '{{ANY META FIELD}}';

	/**
	 * @var string The field ID.
	 */
	private $_field_id;

	/**
	 * @var int|array The source.
	 */
	private $_source;

	/**
	 * @var string|false The alias override (for meta rows)
	 */
	private $_alias = false;

	/**
	 * Represents a column.
	 *
	 * @param string $field_id The field ID (meta key or column name).
	 * @param int|array $source The source this field is referencing.
	 * @param string|bool $alias An alias override. Default: false.
	 */
	public function __construct( $field_id, $source = 0, $alias = false ) {
		if ( ! ( is_int( $source ) || is_array( $source ) ) ) {
			return;
		}

		$this->_field_id = $field_id;
		$this->_source = $source;
		$this->_alias = $alias;
	}

	/**
	 * Get some SQL for this column.
	 *
	 * @param $query GF_Query The query.
	 *
	 * @return string The SQL.
	 */
	public function sql( $query ) {
		if ( ! $query instanceof GF_Query ) {
			return '';
		}

		if ( ! $this->field_id ) {
			return '';
		}

		if ( $this->is_entry_column() ) {
			return sprintf( '`%s`.`%s`', $this->alias ? $this->alias : $query->_alias( null, $this->source ), $this->field_id );
		} else if ( $this->is_meta_column() ) {
			return sprintf( '`%s`.`%s`', $this->alias ? $this->alias : $query->_alias( $this->field_id, $this->source, 'm' ), $this->field_id );
		}

		return '';
	}

	/**
	 * Whether this field is an entry column.
	 *
	 * @return boolean An entry column or not.
	 */
	public function is_entry_column() {
		if ( ! $this->field_id ) {
			return false;
		}

		static $entry_columns = array();
		if ( empty( $entry_columns ) ) {
			global $wpdb;
			$entry_columns = wp_list_pluck( $wpdb->get_results( 'SHOW COLUMNS FROM ' . GFFormsModel::get_entry_table_name(), ARRAY_A ), 'Field' );
		}
		return in_array( $this->field_id, $entry_columns );
	}

	/**
	 * Whether this field is a nullable entry column.
	 *
	 * @since 2.3.1.10
	 *
	 * @return boolean An entry column or not.
	 */
	public function is_nullable_entry_column() {
		if ( ! $this->field_id ) {
			return false;
		}

		static $nullable_entry_columns = array();
		if ( empty( $nullable_entry_columns ) ) {
			global $wpdb;
			$nullable_entry_columns = wp_list_pluck( $wpdb->get_results( 'SHOW COLUMNS FROM ' . GFFormsModel::get_entry_table_name() . " WHERE `Null` = 'YES'", ARRAY_A ), 'Field' );
		}
		return in_array( $this->field_id, $nullable_entry_columns );
	}



	/**
	 * Whether this field is a meta column.
	 *
	 * @return boolean A meta column or not.
	 */
	public function is_meta_column() {
		if ( ! $this->field_id ) {
			return false;
		}

		return in_array( $this->field_id, array( 'meta_key', 'meta_value' ) );
	}

	/**
	 * Proxy read-only values.
	 */
	public function __get( $key ) {
		switch ( $key ):
			case 'field_id':
				return $this->_field_id;
			case 'source':
				return $this->_source;
			case 'alias':
				return $this->_alias;
		endswitch;
	}
}
class-gf-query-series.php000066600000002216151263676600011430 0ustar00<?php

/**
 * The Gravity Forms Query Series class.
 *
 * A list of arguments. Would have named "List" but it's a reserved keyword.
 */
class GF_Query_Series {
	/**
	 * @var array A series of values.
	 */
	private $_values = array();

	/**
	 * A series of expressions.
	 *
	 * @param mixed[] $values With a valid expression type (GF_Query_Literal, GF_Query_Column, GF_Query_Call)
	 */
	public function __construct( $values ) {
		if ( is_array( $values ) ) {
			$this->_values = array_filter( $values, array( 'GF_Query_Condition', 'is_valid_expression_type' ) );
		}
	}

	/**
	 * Get SQL for this.
	 *
	 * @param GF_Query $query     The query.
	 * @param string           $delimiter The delimiter to stick the series values with.
	 *
	 * @return string The SQL.
	 */
	public function sql( $query, $delimiter = '' ) {
		$values = array();

		foreach( $this->_values as $value ) {
			$values[] = $value->sql( $query );
		}

		$chunks = array_filter( $values, 'strlen' );

		return implode( $delimiter, $chunks);
	}

	/**
	 * Proxy read-only values.
	 */
	public function __get( $key ) {
		switch ( $key ):
			case 'values':
				return $this->_values;
		endswitch;
	}
}
class-gf-query-condition.php000066600000036357151263676600012141 0ustar00<?php

/**
 * The Gravity Forms Query Condition class.
 */
class GF_Query_Condition {
	/**
	 * @var string The chosen operator.
	 */
	private $_operator = null;

	/**
	 * @var array Boolean combined expressions.
	 */
	private $_expressions = array();

	/**
	 * @var mixed The left-hand expression.
	 */
	private $_left = null;

	/**
	 * @var mixed The right-hand expression.
	 */
	private $_right = null;

	/**
	 * @const string The AND operator.
	 */
	const _AND = 'AND';

	/**
	 * @const string The AND operator.
	 */
	const _OR = 'OR';

	/**
	 * @const string The less than operator.
	 */
	const LT = '<';

	/**
	 * @const string The less than or equal to operator.
	 */
	const LTE = '<=';

	/**
	 * @const string The greater than operator.
	 */
	const GT = '>';

	/**
	 * @const string The greater than or equal to operator.
	 */
	const GTE = '>=';

	/**
	 * @const string The equal to operator.
	 */
	const EQ = '=';

	/**
	 * @const string The not equal to operator.
	 */
	const NEQ = '!=';

	/**
	 * @const string The IN operator.
	 */
	const IN = 'IN';

	/**
	 * @const string The NOT IN operator.
	 */
	const NIN = 'NOT IN';

	/**
	 * @const string The LIKE operator.
	 */
	const LIKE = 'LIKE';

	/**
	 * @const string The NOT LIKE operator.
	 */
	const NLIKE = 'NOT LIKE';

	/**
	 * @const string The BETWEEN operator.
	 */
	const BETWEEN = 'BETWEEN';

	/**
	 * @const string The NOT BETWEEN operator.
	 */
	const NBETWEEN = 'NOT BETWEEN';

	/**
	 * @const string The inverse IN operator.
	 */
	const CONTAINS = 'CONTAINS';

	/**
	 * @const string The inverse NIN operator.
	 */
	const NCONTAINS = 'NCONTAINS';

	/**
	 * @const string The IS operator.
	 */
	const IS = 'IS';

	/**
	 * @const string The IS NOT operator.
	 */
	const ISNOT = 'IS NOT';

	/**
	 * @const string NULL.
	 */
	const NULL = 'NULL';

	/**
	 * A condition.
	 *
	 * @param GF_Query_Column|GF_Query_Call|GF_Query_Literal|null $left The left-hand expression.
	 * @param string|null $operator The operator.
	 * @param GF_Query_Column|GF_Query_Call|GF_Query_Literal|GF_Query_Series|null $right The right-hand expression.
	 *
	 * @return GF_Query_Condition $this This condition.
	 */
	public function __construct( $left = null, $operator = null, $right = null ) {

		$allowed_ops = array( self::LT, self::LTE, self::GT, self::GTE, self::EQ, self::NEQ, self::IN, self::NIN, self::LIKE, self::NLIKE, self::BETWEEN, self::NBETWEEN, self::CONTAINS, self::NCONTAINS, self::IS, self::ISNOT );

		/**
		 * Left-hand expression, non Series.
		 */
		if ( self::is_valid_expression_type( $left ) && ! $left instanceof GF_Query_Series ) {
			$this->_left = $left;
		}

		/**
		 * The operator.
		 */
		if ( in_array( $operator, $allowed_ops ) ) {
			$this->_operator = $operator;
		}

		/**
		 * Right-hand expression, non Series.
		 */
		if ( self::is_valid_expression_type( $right ) ) {
			$this->_right = $right;
		}
	}

	/**
	 * Tie several conditions together with an AND relationship.
	 *
	 * Accepts any number of GF_Query_Condition objects.
	 *
	 * @return GF_Query_Condition The condition.
	 */
	public static function _and() {
		$conditions = array();

		foreach ( func_get_args() as $arg ) {
			if ( $arg instanceof GF_Query_Condition ) {
				$conditions[] = $arg;
			}
		}

		$_this = new self();
		$_this->_operator = self::_AND;
		$_this->_expressions = $conditions;
		return $_this;
	}

	/**
	 * Tie several conditions together with an OR relationship.
	 *
	 * Accepts any number of GF_Query_Condition objects.
	 *
	 * @return GF_Query_Condition The condition.
	 */
	public static function _or() {

		$conditions = array();

		foreach ( func_get_args() as $arg ) {
			if ( $arg instanceof GF_Query_Condition ) {
				$conditions[] = $arg;
			}
		}

		$_this = new self();
		$_this->_operator = self::_OR;
		$_this->_expressions = $conditions;
		return $_this;
	}

	/**
	 * Compile the expressions into a SQL string.
	 *
	 * @param GF_Query $query The query.
	 *
	 * @return string The SQL string.
	 */
	public function sql( $query ) {
		global $wpdb;

		/**
		 * Both expressions are given.
		 */
		if ( $this->operator && $this->left && $this->right ) {

			/**
			 * Meta field.
			 */
			if ( $this->left instanceof GF_Query_Column && ( ! $this->left->is_entry_column() && ! $this->left->is_meta_column() )  && $this->left->field_id ) {
				/**
				 * A meta field needs some extra conditions: "meta_key", "meta_value", and sometimes EXISTS.
				 */
				$alias = $query->_alias( $this->left->field_id, $this->left->source, 'm' );

				if ( $this->left->field_id == GF_Query_Column::META ) {

					// Global meta search doesn't require a meta_key clause.
					$compare_condition = new self(
						new GF_Query_Column( 'meta_value', $this->left->source, $alias ),
						$this->operator,
						$this->right
					);

					return $query->_where_unwrap( $compare_condition );
				}

				/**
				 * Multi-input fields are processed in a more complex way.
				 *
				 * If a non-specific input is requested (radio, checkboxes, usually)
				 *  we have to include all the input ids in the query.
				 */
				if ( is_numeric( $this->left->field_id ) && intval( $this->left->field_id ) == $this->left->field_id ) {
					if ( ( $field = GFFormsModel::get_field( GFAPI::get_form( $this->left->source ), $this->left->field_id ) ) && $field->get_entry_inputs() ) {

						/**
						 * EQ and NEQ require an unordered comparison of all the values for the entry.
						 */
						if ( in_array( $this->operator, array( self::EQ, self::NEQ ) ) && $this->right instanceof GF_Query_Series ) {
							$compare_conditions = array();
							foreach ( $this->right->values as $literal ) {
								$subquery = $wpdb->prepare( sprintf( "SELECT 1 FROM `%s` WHERE `meta_key` LIKE %%s AND `meta_value` = %s AND `entry_id` = `%s`.`id`",
									GFFormsModel::get_entry_meta_table_name(), $literal->sql( $query ), $query->_alias( null, $this->left->source ) ),
									sprintf( '%d.%%', $this->left->field_id ) );
								$compare_condition = new self( new GF_Query_Call( sprintf( '%sEXISTS', $this->operator == self::NEQ ? 'NOT ' : '' ), array( $subquery ) ) );
								$compare_conditions []= $compare_condition->sql( $query );
							}

							$subquery = $wpdb->prepare( sprintf( "SELECT COUNT(1) FROM `%s` WHERE `meta_key` LIKE %%s AND `entry_id` = `%s`.`id`",
								GFFormsModel::get_entry_meta_table_name(), $query->_alias( null, $this->left->source ) ),
								sprintf( '%d.%%', $this->left->field_id ) );

							/**
							 * Add length comparison to make sure all the needed values are found
							 *  and no extra ones exist.
							 */
							$compare_conditions []= sprintf( "(%s) %s %d", $subquery, $this->operator == self::NEQ ? '!=' : '=', count( $compare_conditions ) );
							return sprintf( "(%s)", implode( $this->operator == self::NEQ ? ' OR ' : ' AND ', $compare_conditions ) );

							/**
							 * Inverse contains.
							 */
						} elseif ( in_array( $this->operator, array( self::CONTAINS, self::NCONTAINS ) ) ) {
							$subquery = $wpdb->prepare( sprintf( "SELECT 1 FROM `%s` WHERE `meta_key` LIKE %%s AND `meta_value` = %s AND `entry_id` = `%s`.`id`",
								GFFormsModel::get_entry_meta_table_name(), $this->right->sql( $query ), $query->_alias( null, $this->left->source ) ),
								sprintf( '%d.%%', $this->left->field_id ) );
							$compare_condition = new self( new GF_Query_Call( sprintf( '%sEXISTS', $this->operator == self::NCONTAINS ? 'NOT ' : '' ), array( $subquery ) ) );
							return $compare_condition->sql( $query );

							/**
							 * One of.
							 */
						} elseif ( in_array( $this->operator, array( self::IN, self::NIN ) ) && $this->right instanceof GF_Query_Series ) {
							$subquery = $wpdb->prepare( sprintf( "SELECT 1 FROM `%s` WHERE `meta_key` LIKE %%s AND `meta_value` IN (%s) AND `entry_id` = `%s`.`id`",
								GFFormsModel::get_entry_meta_table_name(), str_replace( '%', '%%', $this->right->sql( $query, ', ' ) ), $query->_alias( null, $this->left->source ) ),
								sprintf( '%d.%%', $this->left->field_id ) );
							$compare_condition = new self( new GF_Query_Call( sprintf( '%sEXISTS', $this->operator == self::NIN ? 'NOT ' : '' ), array( $subquery ) ) );
							return $compare_condition->sql( $query );

							/**
							 * Everything else.
							 */
						} else {
							$operator = $this->operator;
							$is_negative = in_array( $operator, array( self::NLIKE, self::NBETWEEN, self::NEQ ) );
							if ( $is_negative ) {
								/**
								 * Convert operator to positive, since we're doing it the NOT EXISTS way.
								 */
								switch ( $operator ) {
									case self::NLIKE:
										$operator = self::LIKE;
										break;
									case self::NBETWEEN:
										$operator = self::BETWEEN;
										break;
									case self::NEQ:
										$operator = self::EQ;
										break;
								}
							}

							$subquery = $wpdb->prepare( sprintf( "SELECT 1 FROM `%s` WHERE `meta_key` LIKE %%s AND `meta_value` %s %s AND `entry_id` = `%s`.`id`",
								GFFormsModel::get_entry_meta_table_name(), $operator, str_replace( '%', '%%', $this->right->sql( $query ) ), $query->_alias( null, $this->left->source ) ),
								sprintf( '%d.%%', $this->left->field_id ) );
							$compare_condition = new self( new GF_Query_Call( sprintf( '%sEXISTS', $is_negative ? 'NOT ' : '' ), array( $subquery ) ) );
							return $compare_condition->sql( $query );
						}
					}
				}

				$compare_condition = self::_and(
					new self(
						new GF_Query_Column( 'meta_key', $this->left->source, $alias ),
						self::EQ,
						new GF_Query_Literal( $this->left->field_id )
					),
					new self(
						new GF_Query_Column( 'meta_value', $this->left->source, $alias ),
						$this->operator,
						$this->right
					)
				);

				if ( ( in_array( $this->operator, array( self::NIN, self::NBETWEEN ) ) && ! in_array( new GF_Query_Literal(''), $this->right->values ) )
				     || ( $this->operator == self::NEQ && ! $this->right->value == '')
				     || ( $this->operator == self::EQ && $this->right->value == '' )
				) {
					/**
					 * Empty string comparisons and negative comparisons need a NOT EXISTS clause to grab entries that
					 *  don't have the value set in the first place.
					 */
					$subquery = $wpdb->prepare( sprintf( "SELECT 1 FROM `%s` WHERE `meta_key` = %%s AND `entry_id` = `%s`.`id`",
						GFFormsModel::get_entry_meta_table_name(), $query->_alias( null, $this->left->source ) ), $this->left->field_id );
					$not_exists = new self( new GF_Query_Call( 'NOT EXISTS', array( $subquery ) ) );
					return $query->_where_unwrap( self::_or( $not_exists, $compare_condition ) );
				}

				return $query->_where_unwrap( $compare_condition );
			}

			if ( ( $left = $this->left_sql( $query ) ) && ( $right = $this->right_sql( $query ) ) ) {
				if ( in_array( $this->operator, array( self::NBETWEEN, self::BETWEEN ) ) ) {
					return "($left {$this->operator} $right)";
				}

				if ( $this->left instanceof GF_Query_Column && $this->left->is_nullable_entry_column() ) {
					if ( ( $this->operator == self::EQ && empty ( $this->right->value ) ) || ( $this->operator == self::NEQ && ! empty ( $this->right->value ) ) ) {
						$right .= ' OR ' . $left . ' IS NULL)';
						$left = "($left";
					}
				}

				if ( $this->left instanceof GF_Query_Column && $this->left->is_entry_column() && $this->left->source ) {
					if ( $query->is_multisource() && $this->left->field_id != 'form_id' ) {
						$alias = $query->_alias( null, $this->left->source );
						$left = "(`$alias`.`form_id` = {$this->left->source} AND $left";
						$right .= ')';
					}
				}

				return "$left {$this->operator} $right";
			}
		}

		if ( $this->left && ( $this->left instanceof GF_Query_Call || $this->left instanceof GF_Query_Column ) ) {
			return $this->left_sql( $query );
		}

		return '';
	}

	/**
	 * Checks whether the expression is of a valid type.
	 *
	 * @param mixed $expression The expression to check.
	 *
	 * @return boolean Valid or not.
	 */
	public static function is_valid_expression_type( $expression ) {
		return (
			( $expression instanceof GF_Query_Literal ) ||
			( $expression instanceof GF_Query_Column ) ||
			( $expression instanceof GF_Query_Series ) ||
			( $expression instanceof GF_Query_Call ) ||
			( $expression === self::NULL )
		);
	}

	/**
	 * The right expression.
	 *
	 * @return string The SQL string or null for the right expression.
	 */
	private function right_sql( $query ) {
		/**
		 * (NOT) IN
		 *
		 * Only works with literal arrays, which can be made
		 * up of a Literal, Column or Call.
		 */
		if ( in_array( $this->operator, array( self::IN, self::NIN ) ) ) {
			if ( ! $this->right instanceof GF_Query_Series ) {
				return '';
			}

			return sprintf( '(%s)', $this->right->sql( $query, ', ' ) );

			/**
			 * BETWEEN
			 */
		} elseif ( in_array( $this->operator, array( self::BETWEEN, self::NBETWEEN ) ) ) {
			if ( ! $this->right instanceof GF_Query_Series ) {
				return '';
			}

			return $this->right->sql( $query, ' AND ' );
		} elseif ( in_array( $this->operator, array( self::IS, self::ISNOT ) ) ) {
			if ( $this->right !== self::NULL ) {
				return '';
			}

			return self::NULL;
		}

		return $this->right->sql( $query );
	}

	/**
	 * The left expression.
	 *
	 * @return string The SQL string or null for the left expression.
	 */
	private function left_sql( $query ) {
		if ( $this->left instanceof GF_Query_Call ) {

			$columns = array();

			foreach ( $this->left->parameters as $c ) {
				if ( $c instanceof GF_Query_Column ) {
					$columns[] = $c;
				}
			}

			/**
			 * Add a meta_key condition to a calls.
			 */
			if ( $columns ) {
				$meta_key_conditions = array();
				foreach ( $columns as $column ) {
					$alias = $column->alias ? $column->alias : $query->_alias( $column->field_id, $column->source, 'm' );
					$condition = new GF_Query_Condition(
						new GF_Query_Column( 'meta_key', $column->source, $alias ),
						GF_Query_Condition::EQ,
						new GF_Query_Literal( $column->field_id )
					);

					$meta_key_conditions []= $condition->sql( $query );
				}

				return implode( ' AND ',
					array_merge(
						$meta_key_conditions,
						array( $this->left->sql( $query ) )
					)
				);
			}
		}
		return $this->left->sql( $query );
	}

	/**
	 * Retrieve all the columns present in the left, right clauses.
	 *
	 * @return array The columns.
	 */
	public function get_columns() {
		$columns = array();

		if ( $this->left instanceof GF_Query_Column ) {
			$columns[] = $this->left;
		}

		if ( $this->right instanceof GF_Query_Column ) {
			$columns[] = $this->right;
		}

		/**
		 * Support Calls
		 */
		if ( $this->left instanceof GF_Query_Call) {

			$left_columns = array();

			foreach ( $this->left->parameters as $c ) {
				if ( $c instanceof GF_Query_Column ) {
					$left_columns[] = $c;
				}
			}

			$columns = array_merge( $columns, $left_columns );
		}

		/**
		 * Support series of columns.
		 */
		if ( $this->right instanceof GF_Query_Series ) {

			$right_columns = array();

			foreach ( $this->right->values as $c ) {
				if ( $c instanceof GF_Query_Column ) {
					$right_columns[] = $c;
				}
			}

			$columns = array_merge( $columns, $right_columns );
		}

		return $columns;
	}

	/**
	 * Proxy read-only values.
	 */
	public function __get( $key ) {
		switch ( $key ) :
			case 'operator':
				return $this->_operator;
			case 'expressions':
				return $this->_expressions;
			case 'left':
				return $this->_left;
			case 'right':
				return $this->_right;
			case 'columns':
				return $this->get_columns();
		endswitch;
	}
}
class-gf-query-call.php000066600000006113151263676600011051 0ustar00<?php

/**
 * The Gravity Forms Query Call class.
 */
class GF_Query_Call {
	/**
	 * @var string The function name.
	 */
	private $_function_name = null;

	/**
	 * @var array The parameters.
	 */
	private $_parameters = array();

	/**
	 * A function call.
	 *
	 * @param string $function_name The function to call.
	 * @param array $parameters The function parameters. Default: []
	 */
	public function __construct( $function_name, $parameters = array() ) {
		$this->_function_name = $function_name;
		$this->_parameters = $parameters;
	}

	/**
	 * Generate the SQL.
	 *
	 * The default behavior is to just plop function_name( implode( ', ', $parameters ) ).
	 * For other cases, like CAST, check the derived classes.
	 *
	 * @param GF_Query $query The query.
	 *
	 * @return string The generated SQL.
	 */
	public function sql( $query ) {
		if ( method_exists( $this, strtolower( $this->function_name ) . '_sql' ) ) {
			return call_user_func( array( $this, strtolower( $this->function_name ) . '_sql' ), $query );
		}
		return sprintf( "{$this->function_name}(%s)", implode( ', ', $this->parameters ) );
	}

	/**
	 * A cast call.
	 *
	 * @param GF_Query_Column $column The column to cast.
	 * @param string $type The type to cast to.
	 *
	 * @return GF_Query_Call|null instance or null.
	 */
	public static function CAST( $column, $type ) {
		if ( ! $column instanceof GF_Query_Column ) {
			return null;
		}

		return new self( 'CAST', array( $column, $type ) );
	}

	/**
	 * A RAND call.
	 *
	 * @return GF_Query_Call|null instance or null.
	 */
	public static function RAND( ) {
		return new self( 'RAND' );
	}

	/**
	 * Generate the RAND call SQL.
	 *
	 * @return string The generated SQL.
	 */
	private function rand_sql() {
		return 'RAND()';
	}

	/**
	 * Generate the CAST call SQL.
	 *
	 * @param GF_Query $query The query.
	 *
	 * @return string The generated SQL.
	 */
	private function cast_sql( $query ) {
		if ( ! $this->parameters || count( $this->parameters ) != 2 ) {
			return '';
		}

		list( $column, $type ) = $this->parameters;

		if ( ! in_array( $type, array( GF_Query::TYPE_SIGNED, GF_Query::TYPE_UNSIGNED, GF_Query::TYPE_DECIMAL ) ) ) {
			return '';
		}

		if ( ! $column->field_id || ! $column->source ) {
			return '';
		}

		if ( GF_Query::TYPE_DECIMAL === $type ) {
			$type = 'DECIMAL(65, 6)'; // @todo make the decimal point configurable one day
		}

		$id = $column->is_entry_column() ? $column->field_id : 'meta_value';
		return sprintf( 'CAST(`%s`.`%s` AS %s)', $query->_alias( $column->field_id, $column->source, 'c' ), $id, $type );
	}

	/**
	 * Retrieve all the columns from the parameters of this call.
	 *
	 * @return array The columns.
	 */
	private function get_columns() {
		$columns = array();
		foreach ( $this->parameters as $p ) {
			if ( $p instanceof GF_Query_Column ) {
				$columns[] = $p;
			}
		}
		return $columns;
	}

	/**
	 * Proxy read-only values.
	 */
	public function __get( $key ) {
		switch ( $key ):
			case 'parameters':
				return $this->_parameters;
			case 'function_name':
				return $this->_function_name;
			case 'columns':
				return $this->get_columns();
		endswitch;
	}
}
class-gf-query.php000066600000116215151263676600010145 0ustar00<?php

if ( ! class_exists( 'GF_Query_Call' ) ) {
	require_once( 'class-gf-query-call.php' );
}

if ( ! class_exists( 'GF_Query_Column' ) ) {
	require_once( 'class-gf-query-column.php' );
}

if ( ! class_exists( 'GF_Query_Condition' ) ) {
	require_once( 'class-gf-query-condition.php' );
}

if ( ! class_exists( 'GF_Query_Literal' ) ) {
	require_once( 'class-gf-query-literal.php' );
}

if ( ! class_exists( 'GF_Query_JSON_Literal' ) ) {
	require_once( 'class-gf-query-json-literal.php' );
}

if ( ! class_exists( 'GF_Query_Series' ) ) {
	require_once( 'class-gf-query-series.php' );
}

/**
 * The Gravity Forms Query Builder class.
 *
 * @internal
 *
 * @since 2.3
 */
class GF_Query {


	/**
	 * @var null|int Holds the total number of entries found after calling get()
	 */
	public $total_found = null;

	/**
	 * @var array An array of Form IDs
	 */
	private $from;

	/**
	 * @var array Join clauses.
	 */
	private $joins = array();

	/**
	 * @var array All the where clauses.
	 */
	private $where = array();

	/**
	 * @var array All the order clauses.
	 */
	private $order = array();

	/**
	 * @var int The limit of entries returned.
	 */
	private $limit = 0;

	/**
	 * @var int The offset of entries returned.
	 */
	private $offset = 0;

	/**
	 * @var array The table aliases.
	 */
	private $aliases = array();

	/**
	 * @var string The queries executed against the database and their times.
	 */
	private $queries = array();

	/**
	 * @var float An internal timer. Used for instrumentation purposes.
	 */
	private $timer = 0.0;

	/**
	 * @var array An array of inferred joins used by _join_infer().
	 */
	private $_inferred_joins = array();

	/**
	 * @const null Nothing.
	 */
	const NOOP = null;

	/**
	 * @const string (ORDER BY) ASC operator.
	 */
	const ASC = 'ASC';

	/**
	 * @const string (ORDER BY) DESC operator.
	 */
	const DESC = 'DESC';

	/**
	 * @const string SIGNED type.
	 */
	const TYPE_SIGNED = 'SIGNED';

	/**
	 * @const string UNSIGNED type.
	 */
	const TYPE_UNSIGNED = 'UNSIGNED';

	/**
	 * @const string DECIMAL type.
	 */
	const TYPE_DECIMAL = 'DECIMAL';

	/**
	 * GF_Query constructor.
	 *
	 * @param null|int|array $form_ids
	 * @param null|array     $search_criteria
	 * @param null|array     $sorting
	 * @param null|array     $paging
	 */
	public function __construct( $form_ids = null, $search_criteria = null, $sorting = null, $paging = null ) {
		if ( ! is_null( $search_criteria ) || ! is_null( $form_ids ) || ! empty( $sorting ) || ! empty( $paging ) ) {
			$this->parse( $form_ids, $search_criteria, $sorting, $paging );
		}
	}

	/**
	 * Parses the Search Criteria args.
	 *
	 * @param int|array $form_id
	 * @param array     $search_criteria
	 * @param null      $sorting
	 * @param null      $paging
	 */
	public function parse( $form_id, $search_criteria = array(), $sorting = null, $paging = null ) {

		$page_size = isset( $paging['page_size'] ) ? $paging['page_size'] : 20;
		$offset = isset( $paging['offset'] ) ? $paging['offset'] : 0;

		$sort_field = ! empty( $sorting['key'] ) ? $sorting['key'] : 'id';
		$sort_dir = isset( $sorting['direction'] ) ? strtoupper( $sorting['direction'] ) : 'DESC';

		switch ( $sort_dir ) {
			case 'DESC':
				$sort_dir_query = self::DESC;
				break;
			case 'RAND':
				$sort_dir_query = 'RAND';
				break;
			case 'ASC':
			default:
				$sort_dir_query = self::ASC;
				break;
		}

		$from = ! is_array( $form_id ) ? array( $form_id ) : $form_id;

		$from = array_map( 'absint', $from );

		$form_id = is_array( $form_id ) ? reset( $form_id ) : $form_id;

		$form_id = absint( $form_id );

		$order = new GF_Query_Column( $sort_field, $form_id );

		$force_order_numeric = false;
		if ( ! $order->is_entry_column() ) {
			if ( isset( $sorting['is_numeric'] ) ) {
				$force_order_numeric = $sorting['is_numeric'];
			} else {
				$field = GFAPI::get_field( $form_id, $sort_field );

				if ( $field instanceof GF_Field ) {
					$force_order_numeric = $field->get_input_type() == 'number';
				} else {
					$entry_meta          = GFFormsModel::get_entry_meta( $form_id );
					$force_order_numeric = rgars( $entry_meta, $sort_field . '/is_numeric' );
				}
			}
		}

		if ( $force_order_numeric ) {
			$order = GF_Query_Call::CAST( $order, GF_Query::TYPE_DECIMAL );
		}

		$this->from( $from );

		if ( $sort_dir_query == 'RAND' ) {
			$this->order( GF_Query_Call::RAND() );
		} else {
			$this->order( $order, $sort_dir_query );
		}

		$this->limit( $page_size )
		     ->offset( $offset );

		$properties_condition = null;
		$filters_condition = null;
		$filters = array();

		if ( isset( $search_criteria['status'] ) ) {
			$property_conditions[] = new GF_Query_Condition(
				new GF_Query_Column( 'status' ),
				GF_Query_Condition::EQ,
				new GF_Query_Literal( $search_criteria['status'] )
			);
		}

		$start_date = rgar( $search_criteria, 'start_date' );
		$end_date = rgar( $search_criteria, 'end_date' );

		$column = count( $from ) > 1 ? new GF_Query_Column( 'date_created' ) : new GF_Query_Column( 'date_created', $form_id );

		if ( ! empty( $start_date ) ) {

			try {
				$start_date         = new DateTime( $search_criteria['start_date'] );
				$start_datetime_str = $start_date->format( 'Y-m-d H:i:s' );
				$start_date_str     = $start_date->format( 'Y-m-d' );
				if ( $start_datetime_str == $start_date_str . ' 00:00:00' ) {
					$start_date_str = $start_date_str . ' 00:00:00';
				} else {
					$start_date_str = $start_date->format( 'Y-m-d H:i:s' );
				}

				$start_date_str_utc = get_gmt_from_date( $start_date_str );

				$property_conditions[] = new GF_Query_Condition(
					$column,
					GF_Query_Condition::GTE,
					new GF_Query_Literal( $start_date_str_utc )
				);
			} catch ( Exception $e ) {
				GFAPI::log_error( __METHOD__ . '(): Invalid start_date; ' . $e->getMessage() );
			}

		}

		if ( ! empty( $end_date ) ) {

			try {
				$end_date         = new DateTime( $search_criteria['end_date'] );
				$end_datetime_str = $end_date->format( 'Y-m-d H:i:s' );
				$end_date_str     = $end_date->format( 'Y-m-d' );

				// extend end date till the end of the day unless a time was specified. 00:00:00 is ignored.
				if ( $end_datetime_str == $end_date_str . ' 00:00:00' ) {
					$end_date_str = $end_date->format( 'Y-m-d' ) . ' 23:59:59';
				} else {
					$end_date_str = $end_date->format( 'Y-m-d H:i:s' );
				}

				$end_date_str_utc = get_gmt_from_date( $end_date_str );

				if ( ! empty( $end_date ) ) {
					$property_conditions[] = new GF_Query_Condition(
						$column,
						GF_Query_Condition::LTE,
						new GF_Query_Literal( $end_date_str_utc )
					);
				}
			} catch ( Exception $e ) {
				GFAPI::log_error( __METHOD__ . '(): Invalid end_date; ' . $e->getMessage() );
			}

		}

		if ( ! empty( $property_conditions ) ) {
			if ( count( $property_conditions ) > 1 ) {
				$properties_condition = call_user_func_array( array( 'GF_Query_Condition', '_and' ), $property_conditions );
			} else {
				$properties_condition = $property_conditions[0];
			}
		}

		if ( ! empty( $search_criteria['field_filters'] ) ) {
			$field_filters = $search_criteria['field_filters'];
			$search_mode = isset( $field_filters['mode'] ) ? strtolower( $field_filters['mode'] ) : 'all';
			unset( $field_filters['mode'] );

			foreach ( $field_filters as $filter ) {
				$key = rgar( $filter, 'key' );

				if ( empty( $key ) ) {
					$global_condition = $this->get_global_condition( $filter, $form_id );
					$filters[] = $global_condition;
					continue;
				}

				$value = rgar( $filter, 'value' );

				$operator = isset( $filter['operator'] ) ? $filter['operator'] : GF_Query_Condition::EQ;
				$operator = strtoupper( $operator );

				switch ( $operator ) {
					case 'CONTAINS':
						$operator = GF_Query_Condition::LIKE;
						$value    = '%' . $value . '%';
						break;
					case 'IS NOT':
					case 'ISNOT':
					case '<>':
						$operator = GF_Query_Condition::NEQ;
						break;
					case 'IS':
					case '=':
						$operator = GF_Query_Condition::EQ;
						break;
					case 'LIKE':
						$operator = GF_Query_Condition::LIKE;
						break;
					case 'NOT IN':
					case 'NOTIN':
						$operator = GF_Query_Condition::NIN;
						break;
					case 'IN':
						$operator = GF_Query_Condition::IN;
				}

				$form = GFFormsModel::get_form_meta( $form_id );
				$field = GFFormsModel::get_field( $form, $key );
				if ( $field && $operator != GF_Query_Condition::LIKE && ( $field->get_input_type() == 'number' || rgar( $filter, 'is_numeric' ) ) ) {
					if ( ! is_numeric( $value ) ) {
						$value = floatval( $value );
					}
					$filters[] = new GF_Query_Condition(
						GF_Query_Call::CAST( new GF_Query_Column( $key, $form_id ), self::TYPE_DECIMAL ),
						$operator,
						new GF_Query_Literal( $value )
					);
					continue;
				}

				if ( is_array( $value ) ) {
					foreach ( $value as &$v ) {
						$v = $field && $field->storageType == 'json' ? new GF_Query_JSON_Literal( (string) $v ) : new GF_Query_Literal( $v );
					}
					$value = new GF_Query_Series( $value );

					$filters[] = new GF_Query_Condition(
						new GF_Query_Column( $key, $form_id ),
						$operator,
						$value
					);

					continue;
				}

				if ( $key == 'date_created' && $operator == GF_Query_Condition::EQ ) {
					if ( ! empty( $value ) ) {
						try {
							$search_date           = new DateTime( $value );
							$search_date_str       = $search_date->format( 'Y-m-d' );
							$date_created_start    = $search_date_str . ' 00:00:00';
							$date_create_start_utc = get_gmt_from_date( $date_created_start );
							$date_created_end      = $search_date_str . ' 23:59:59';
							$date_created_end_utc  = get_gmt_from_date( $date_created_end );

							$column = count( $from ) > 1 ? new GF_Query_Column( $key ) : new GF_Query_Column( $key, $form_id );

							$filters[] = new GF_Query_Condition(
								$column,
								GF_Query_Condition::BETWEEN,
								new GF_Query_Series( array(
									new GF_Query_Literal( $date_create_start_utc ),
									new GF_Query_Literal( $date_created_end_utc ),
								) )
							);
						} catch ( Exception $e ) {
							GFAPI::log_error( __METHOD__ . '(): Invalid date_created; ' . $e->getMessage() );
						}
					}

					continue;
				}

				$literal = $field && $field->storageType == 'json' ? new GF_Query_JSON_Literal( (string) $value ) : new GF_Query_Literal( (string) $value );

				$column = count( $from ) > 1 ? new GF_Query_Column( $key ) : new GF_Query_Column( $key, $form_id );
				$filters[] = new GF_Query_Condition(
					$column,
					$operator,
					$literal
				);

			}

			$condition_mode = strtolower( $search_mode ) == 'any' ? '_or' : '_and';
			if ( count( $filters ) > 1 ) {
				$filters_condition = call_user_func_array( array( 'GF_Query_Condition', $condition_mode ), $filters );
			} elseif ( $filters ) {
				$filters_condition = $filters[0];
			}
		}

		if ( ! empty( $properties_condition ) && ! empty( $filters_condition ) ) {
			$where = call_user_func_array( array( 'GF_Query_Condition', '_and' ), array( $properties_condition, $filters_condition ) );
		} else {
			$where = ! empty( $properties_condition ) ? $properties_condition : $filters_condition;
		}

		if ( ! empty( $where ) ) {
			$this->where( $where );
		}

	}

	/**
	 * @param array     $filter
	 * @param int|array $form_ids
	 *
	 * @return GF_Query_Condition|mixed
	 */
	private function get_global_condition( $filter, $form_ids ) {

		// include choice text
		$forms = array();
		if ( $form_ids == 0 ) {
			$forms = GFAPI::get_forms();
		} elseif ( is_array( $form_ids ) ) {
			foreach ( $form_ids as $id ) {
				$forms[] = GFAPI::get_form( $id );
			}
		} else {
			$forms[] = GFAPI::get_form( $form_ids );
		}

		$original_operator = strtoupper( rgar( $filter, 'operator' ) );

		switch ( $original_operator ) {
			case 'CONTAINS':
				$operator = GF_Query_Condition::LIKE;
				break;
			case 'IS NOT':
			case 'ISNOT':
			case '<>':
				$operator = GF_Query_Condition::NEQ;
				break;
			case 'IS':
			case '=':
				$operator = GF_Query_Condition::EQ;
				break;
			case 'LIKE':
				$operator = GF_Query_Condition::LIKE;
				break;
			case 'NOT IN':
			case 'NOTIN':
				$operator = GF_Query_Condition::NIN;
				break;
			case 'IN':
				$operator = GF_Query_Condition::IN;
				break;
			default:
				$operator = empty( $original_operator ) ? GF_Query_Condition::EQ : $original_operator;
		}

		$val = $filter['value'];

		$choice_filters = array();

		foreach ( $forms as $form ) {
			if ( isset( $form['fields'] ) ) {
				foreach ( $form['fields'] as $field ) {
					/* @var GF_Field $field */
					if ( is_array( $field->choices ) ) {
						foreach ( $field->choices as $choice ) {
							if ( ( $operator == '=' && strtolower( $choice['text'] ) == strtolower( $val ) ) || ( $operator == 'LIKE' && ! empty( $val ) && strpos( strtolower( $choice['text'] ), strtolower( $val ) ) !== false ) ) {
								if ( $field->gsurveyLikertEnableMultipleRows ) {
									$choice_value           = $choice['value'];
									$choice_search_operator = 'like';
									$choice_value = '%' . $choice_value;
								} else {
									$choice_value           = $choice['value'];
									$choice_search_operator = '=';
								}
								$choice_filters[] = new GF_Query_Condition(
									new GF_Query_Column( $field->id, $form['id'] ),
									$choice_search_operator,
									new GF_Query_Literal( $choice_value )
								);
							}
						}
					}
				}
			}
		}

		if ( is_array( $val ) ) {
			foreach ( $val as &$v ) {
				$v = new GF_Query_Literal( $v );
			}
			$val = new GF_Query_Series( $val );

			$choice_filters[] = new GF_Query_Condition(
				new GF_Query_Column( GF_Query_Column::META, 0 ),
				$operator,
				$val
			);
		} else {
			if ( $original_operator == 'CONTAINS' ) {
				$val = '%' . $val . '%';
			}
			$choice_filters[] = new GF_Query_Condition(
				new GF_Query_Column( GF_Query_Column::META, $form_ids ),
				$operator,
				new GF_Query_Literal( $val )
			);
		}

		if ( count( $choice_filters ) > 1 ) {

			$combine_operator = in_array( $operator, array( GF_Query_Condition::NEQ, GF_Query_Condition::NIN ) ) ? '_and' : '_or';

			$condition = call_user_func_array( array( 'GF_Query_Condition', $combine_operator ), $choice_filters );
		} else {
			$condition = $choice_filters[0];
		}

		return $condition;
	}

	/**
	 * Query a source.
	 *
	 * Sets the FROM clause.
	 *
	 * @param int|array The Form ID or array of IDs.
	 *
	 * @return GF_Query Chainable $this.
	 */
	public function from( $source ) {

		if ( ! is_array( $source ) ) {
			$source = array( $source );
		}

		$this->from = $source;

		/**
		 * Reserve the alias.
		 */
		$this->_alias( null, reset( $source ) );

		return $this;
	}

	/**
	 * Join on column tables. The serious stuff.
	 *
	 * LEFT JOIN $on_column.table alias ON alias.$on_column = $to_column
	 *
	 * @param GF_Query_Column $on_column The column to join on.
	 * @param GF_Query_Column $to_column The column to join the above to.
	 *
	 * @return GF_Query Chainable $this.
	 */
	public function join( $on_column, $to_column) {
		if ( $on_column instanceof GF_Query_Column && $to_column instanceof GF_Query_Column ) {
			$this->joins[] = array( $on_column, $to_column );
		}
		return $this;
	}

	/**
	 * Where something is something :)
	 *
	 * @param GF_Query_Condition $condition A condition.
	 *
	 * @return GF_Query Chainable $this.
	 */
	public function where( $condition ) {
		if ( $condition instanceof GF_Query_Condition ) {
			$this->where = $condition;
		}
		return $this;
	}

	/**
	 * Sets the order.
	 *
	 * @param GF_Query_Column|GF_Query_Call $column The field, function to order by.
	 * @param string $order The order (one of self::ASC, self::DESC or empty). Default for GF_Query_Column: self::ASC Default for GF_Query_Call: empty
	 *
	 * @return GF_Query Chainable $this.
	 */
	public function order( $column, $order = null ) {

		/**
		 * This is a function.
		 */
		if ( $column instanceof GF_Query_Call ) {

			if ( ! in_array( $order, array( self::ASC, self::DESC ) ) ) {
				$order = '';
			}

			$this->order[ spl_object_hash( $column ) . '()' ] = array( $column, $order );
		} elseif ( $column instanceof GF_Query_Column ) {

			if ( ! in_array( $order, array( self::ASC, self::DESC ) ) ) {
				$order = self::ASC;
			}

			$source_id = $column->source;
			$field_id = $column->field_id ? $column->field_id : '-';
			$this->order[ "{$source_id}_{$field_id}" ] = array( $column, $order );
		}

		return $this;
	}

	/**
	 * Sets the limit.
	 *
	 * @param int $limit The limit to set.
	 *
	 * @return GF_Query Chainable $this.
	 */
	public function limit( $limit ) {
		if ( is_numeric( $limit ) ) {
			$this->limit = $limit;
		}
		return $this;
	}

	/**
	 * Sets the offset.
	 *
	 * Use self::page() as a more convenient wrapper.
	 *
	 * @return GF_Query Chainable $this.
	 */
	public function offset( $offset ) {
		if ( is_numeric( $offset ) ) {
			$this->offset = $offset;
		}
		return $this;
	}

	/**
	 * Sets the offset from page.
	 *
	 * Calculated from self::$limit. If not set, offset will be 0.
	 *
	 * @return GF_Query Chainable $this.
	 */
	public function page( $page ) {
		if ( is_numeric( $page ) ) {
			$this->offset = max( 0, $page - 1 ) * $this->limit;
		}
		return $this;
	}

	/**
	 * Retrieve the results.
	 *
	 * @return array The resulting entries.
	 */
	public function get() {
		global $wpdb;

		$entries = array();

		$results = $this->query();

		$this->total_found = (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' );

		return $this->get_entries( $results );
	}

	/**
	 * Retrieve the IDs for the results.
	 *
	 * @return array The resulting entry IDs.
	 */
	public function get_ids() {
		global $wpdb;

		$results = $this->query();

		$this->total_found = (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' );

		$ids = array();

		foreach( $results as $result ) {
			$ids[] = $result[0];
		}

		return $ids;
	}

	/**
	 * Build the query and return the raw rows.
	 *
	 * @return array The rows.
	 */
	private function query() {
		if ( count( $this->queries ) ) {
			GFCommon::log_debug( 'Reusing GF_Query is undefined behavior. Create a new instance instead.' );
		}

		if ( ! is_array( $this->from ) || ! count( $this->from ) ) {
			return array();
		}

		global $wpdb;

		/**
		 * FROM.
		 *
		 * We always select from the entry table first.
		 *  It is our main source.
		 */
		$from = sprintf( 'FROM `%s` AS `%s`', GFFormsModel::get_entry_table_name(), $this->_alias( null, reset( $this->from ) ) );

		/**
		 * ORDER.
		 */
		$order = '';
		if ( ! empty( $this->order ) && $orders = $this->_order_generate( $this->order ) ) {
			$order = 'ORDER BY ' . implode( ', ', $orders );
		}

		/**
		 * JOIN.
		 */
		$joins = array_merge( $this->_join_infer( $this->where ), $this->_join_infer_orders( $this->where, $this->order ), $this->_join_generate( $this->joins ) );
		$join = count( $joins ) ? 'LEFT JOIN ' . implode( ' LEFT JOIN ', $this->_prime_joins( $joins ) ) : '';

		/**
		 * SELECT.
		 */
		$select = sprintf( 'SELECT SQL_CALC_FOUND_ROWS DISTINCT %s', implode( ', ', $this->_select_infer( $this->joins ) ) );

		$form_ids = array();
		foreach ( $this->from as $f ) {
			$f = new GF_Query_Literal( $f );
			if ( $f->value ) {
				$form_ids[] = $f;
			}
		}

		if ( $form_ids ) {
			$this->where( GF_Query_Condition::_and(
			/**
			 * Prepend the selected form IDs.
			 */
				new GF_Query_Condition(
					new GF_Query_Column( 'form_id' ),
					GF_Query_Condition::IN,
					new GF_Query_Series( $form_ids )
				),

				$this->where
			) );
		}

		/**
		 * WHERE.
		 */
		if ( $where = $this->_where_unwrap( $this->where ) ) {
			$where = "WHERE $where";
		}

		/**
		 * LIMIT and OFFSET.
		 */
		$limit = $offset = '';
		if ( $this->limit ) {
			$limit = sprintf( 'LIMIT %d', $this->limit );
			if ( $this->offset ) {
				$offset = sprintf( 'OFFSET %d', $this->offset );
			}
		}

		$paginate = implode( ' ', array_filter( array( $limit, $offset ), 'strlen' ) );

		/**
		 * Filter the SQL query fragments to allow low-level advanced analysis and modification before the query is run.
		 *
		 * @since 2.4.3
		 *
		 * @param array $sql An array with all the SQL fragments: select, from, join, where, order, paginate.
		 */
		$sql = apply_filters( 'gform_gf_query_sql', compact( 'select', 'from', 'join', 'where', 'order', 'paginate' ) );
		$sql = implode( ' ', array_filter( $sql, 'strlen' ) );

		GFCommon::log_debug( __METHOD__ . '(): sql => ' . $sql );

		$this->timer_start();
		$results = $wpdb->get_results( $sql, ARRAY_N );
		$this->queries []= array( $this->timer_stop(), $sql );

		if ( is_null( $results ) ) {
			return array();
		}

		return $results;
	}

	/**
	 * Generate the ORDER BY clause fragments.
	 *
	 * @param array $orders Usually self::$order, the orders.
	 *
	 * @internal
	 *
	 * @return array The ORDER BY clause fragments.
	 */
	public function _order_generate( $orders ) {
		$order_clauses = array();

		foreach ( $orders as $o ) {
			list( $column, $order ) = $o;

			if ( ! in_array( $order, array( self::ASC, self::DESC, '' ) ) ) {
				continue;
			}

			if ( $column instanceof GF_Query_Call ) {
				$order_clauses []= sprintf( '%s %s', $column->sql( $this ), $order );

			} else if ( $column instanceof GF_Query_Column ) {
				if ( $column->is_entry_column() ) {
					$order_clauses []= sprintf( '`%s`.`%s` %s', $this->_alias( null, $column->source ), $column->field_id, $order );

					/**
					 * An entry meta field.
					 */
				} else {
					$alias = $this->_alias( $column->field_id, $column->source, 'o' );
					$order_clauses []= sprintf( '`%s`.`meta_value` %s', $alias, $order );
				}
			}
		}

		$order_clauses = array_map( 'trim', $order_clauses );

		return $order_clauses;
	}

	/**
	 * Recursive function to find joins.
	 *
	 * @param $condition
	 * @param $already_joined
	 */
	private function find_joins( $condition, &$already_joined ) {
		if ( ! $condition instanceof GF_Query_Condition ) {
			return;
		}

		/**
		 * This is an OR or AND clause. Needs unwrapping.
		 */
		if ( in_array( $condition->operator, array( GF_Query_Condition::_AND, GF_Query_Condition::_OR ) ) ) {
			foreach ( $condition->expressions as $expression ) {
				$this->find_joins( $expression, $already_joined );
			}
			return;
		}

		foreach ( $condition->columns as $column ) {
			/** @var GF_Query_Column $column */
			if ( $column->is_entry_column() || ! $column->field_id || ! $column->source )
				continue;

			$source = $column->source;

			$source_id = is_array( $source ) ? reset( $source ) : $source;
			$field_id = $column->field_id;

			/** Reserve the alias. */
			$this->_alias( $column->field_id, $column->source, 'm' );

			$already_joined []= "{$source_id}_{$field_id}";
		}
	}

	/**
	 * Generate the needed SELECT columns from join arrays.
	 *
	 * @param array $joins The joins (from self::$joins)
	 *
	 * @internal
	 *
	 * @return string[] The individual SELECT columns.
	 */
	public function _select_infer( $joins ) {

		$select[] = sprintf( '`%s`.`id`', $this->_alias( null, reset( $this->from ) ) );

		foreach ( $joins as $join ) {
			list( $on, $column ) = $join;
			if ( ! $on instanceof GF_Query_Column || ! $column instanceof GF_Query_Column ) {
				continue;
			}

			if ( ! $on->field_id || ! $on->source || ! $column->field_id || ! $column->source ) {
				continue;
			}

			$alias = $this->_alias( $on->is_entry_column() ? null : $on->field_id, $on->source );
			$select[] = sprintf( '`%s`.`%s` AS `%s_id`', $alias, $on->is_entry_column() ? 'id' : 'entry_id', $alias );
		}

		return $select;
	}

	/**
	 * Generate the needed JOIN statements from join arrays.
	 *
	 * @param array $joins The joins (from self::$joins)
	 *
	 * @internal
	 *
	 * @return string[] The individual JOIN statements.
	 */
	public function _join_generate( $joins ) {
		$_joins = array();

		foreach ( $joins as $join ) {
			list( $on, $column ) = $join;
			if ( ! $on instanceof GF_Query_Column || ! $column instanceof GF_Query_Column ) {
				continue;
			}

			if ( ! $on->field_id || ! $on->source || ! $column->field_id || ! $column->source ) {
				continue;
			}

			/**
			 * Join on the entry table when dealing with an entry column.
			 */
			if ( $on->is_entry_column() ) {
				$table_on = GFFormsModel::get_entry_table_name();
				$alias_on = $this->_alias( null, $on->source, 't' );
				$column_on = $on->field_id;

				/**
				 * Join on the meta table when dealing with a meta field.
				 */
			} else {
				$table_on = GFFormsModel::get_entry_meta_table_name();
				$alias_on = $this->_alias( $on->field_id, $on->source, 'm' );
				$column_on = 'meta_value';

				/**
				 * Make sure a WHERE clause exists on meta fields.
				 */
				$meta_condition = new GF_Query_Condition(
					new GF_Query_Column( 'meta_key', $on->source, $alias_on ),
					GF_Query_Condition::EQ,
					new GF_Query_Literal( $on->field_id )
				);

				$this->where(
					GF_Query_Condition::_and( $this->where, $meta_condition )
				);
			}

			$this->where( GF_Query_Condition::_and(
				$this->where,
				new GF_Query_Condition(
					new GF_Query_Column( 'form_id', $on->source, $alias_on ),
					GF_Query_Condition::EQ,
					new GF_Query_Literal( $on->source )
				)
			) );

			/**
			 * Join to on an entry table.
			 */
			if ( $column->is_entry_column() ) {
				$equals_table = $this->_alias( null, $column->source );
				$equals_column = $column->field_id;


				/**
				 * Join to a meta table.
				 */
			} else {
				$equals_table = $this->_alias( $column->field_id, $column->source, 'm' );
				$equals_column = 'meta_value';

				/**
				 * Make sure a WHERE clause exists on meta fields.
				 */
				$meta_condition = new GF_Query_Condition(
					new GF_Query_Column( 'meta_key', $column->source, $equals_table ),
					GF_Query_Condition::EQ,
					new GF_Query_Literal( $column->field_id )
				);

				$this->where(
					GF_Query_Condition::_and( $this->where, $meta_condition )
				);

				/**
				 * Make sure the initial join exists.
				 */
				$_joins = array_merge( $this->_join_infer( $meta_condition ), $_joins );
			}

			$_joins[] = sprintf( '`%s` AS `%s` ON `%s`.`%s` = `%s`.`%s`',
				$table_on, $alias_on, $alias_on, $column_on, $equals_table, $equals_column );
		}

		return $_joins;
	}

	/**
	 * Generate the needed JOIN statements for a condition.
	 *
	 * @param array $condition A condition (from self::$where)
	 *
	 * @internal
	 *
	 * @return string[] The individual JOIN statements.
	 */
	public function _join_infer( $condition ) {
		$this->_inferred_joins = array();

		if ( ! $condition instanceof GF_Query_Condition ) {
			return $this->_inferred_joins;
		}

		/**
		 * This is an OR or AND clause. Needs unwrapping.
		 */
		if ( in_array( $condition->operator, array( GF_Query_Condition::_AND, GF_Query_Condition::_OR ) ) ) {

			/** Recurse and unwrap. */
			$_joins = array_map( array( $this, __FUNCTION__ ), $condition->expressions );
			$this->_inferred_joins = array();
			array_walk_recursive( $_joins, array( $this, 'merge_joins' ) );
			return array_unique( $this->_inferred_joins );
		}

		/**
		 * Regular WHERE clause JOIN inference.
		 */
		foreach ( $condition->columns as $column ) {
			if ( $column->is_entry_column() || ! $column->field_id )
				continue;

			$alias = $column->alias ? $column->alias : $this->_alias( $column->field_id, $column->source, 'm' );

			if ( ! $column->is_meta_column() && $column->field_id != GF_Query_Column::META ) {
				if ( $column->field_id == intval( $column->field_id ) && ( $field = GFFormsModel::get_field( GFAPI::get_form( $column->source ? $column->source : reset( $this->from ) ), $column->field_id ) ) && $field->get_entry_inputs() ) {
					/**
					 * Multi-input across all inputs.
					 */
					$literal = new GF_Query_Literal( sprintf( '%d.%%', $column->field_id ) );
					$this->_inferred_joins []= sprintf( '`%s` AS `%s` ON (`%s`.`entry_id` = `%s`.`id` AND `%s`.`meta_key` LIKE %s)',
						GFFormsModel::get_entry_meta_table_name(), $alias, $alias, $this->_alias( null, $column->source ), $alias, $literal->sql( $this )
					);
				} else {
					$literal = new GF_Query_Literal( $column->field_id );
					$this->_inferred_joins []= sprintf( '`%s` AS `%s` ON (`%s`.`entry_id` = `%s`.`id` AND `%s`.`meta_key` = %s)',
						GFFormsModel::get_entry_meta_table_name(), $alias, $alias, $this->_alias( null, $column->source ), $alias, $literal->sql( $this )
					);
				}
			} else {
				$this->_inferred_joins []= sprintf( '`%s` AS `%s` ON `%s`.`entry_id` = `%s`.`id`',
					GFFormsModel::get_entry_meta_table_name(), $alias, $alias, $this->_alias( null, $column->source )
				);
			}
		}

		return array_unique( $this->_inferred_joins );
	}

	/**
	 * Generate the needed JOIN statements for stanalone ORDER BY clauses.
	 *
	 * @param array $condition A condition (from self::$where)
	 * @param array $order The orders (from self::$order )
	 *
	 * @internal
	 *
	 * @return string[] The individual JOIN statements.
	 */
	public function _join_infer_orders( $condition, $order ) {
		$already_joined = array();
		$this->find_joins( $condition, $already_joined );

		$joins = array();

		foreach ( $order as $o ) {
			list( $column, $_ ) = $o;

			if ( $column instanceof GF_Query_Call ) {
				$columns = $column->columns;
			} else if ( $column instanceof GF_Query_Column ) {
				$columns = array( $column );
			} else {
				continue;
			}

			foreach ( $columns as $column ) {
				if ( $column->is_entry_column() || $column->is_meta_column() ) {
					continue;
				}
				
				$source_id = $column->source;
				$field_id = $column->field_id ? $column->field_id : '-';
				$alias = $this->_alias( $column->field_id, $column->source, 'm' );

				if ( ! in_array( "{$source_id}_{$field_id}", $already_joined ) ) {
					$already_joined []= "{$source_id}_{$field_id}";

					$literal = new GF_Query_Literal( $field_id );
					$joins []= sprintf( '`%s` AS `%s` ON (`%s`.`entry_id` = `%s`.`id` AND `%s`.`meta_key` = %s)',
						GFFormsModel::get_entry_meta_table_name(), $alias, $alias, $this->_alias( null, $column->source ),
						$alias, $literal->sql( $this ) );
				}
			}
		}

		return $joins;
	}

	/**
	 * @param $join
	 */
	private function merge_joins( $join ) {
		$this->_inferred_joins = array_merge( $this->_inferred_joins, array( $join ) );
	}

	/**
	 * Remove simplified join statements on the same column.
	 *
	 * Used to avoid duplicate/non-unique aliases in joins. We always
	 *  select the more specific join clause.
	 *
	 * @param array $joins
	 *
	 * @return array
	 */
	public function _prime_joins( $joins ) {
		$joins = array_unique( $joins );

		$primed_joins = array();
		foreach ( $joins as $join ) {
			if ( preg_match( '#` AS `([motc]\d+)` ON #', $join, $matches ) ) {
				$alias = $matches[1];
				if ( ! empty( $primed_joins[ $alias ] ) ) {
					if ( strlen( $primed_joins[ $alias ] ) > strlen( $join ) ) {
						continue;
					}
				}
				$primed_joins[ $alias ] = $join;
			}
		}

		return array_values( $primed_joins );
	}

	/**
	 * Return a nice table alias for this source/field.
	 *
	 * Every source is marked as unique by ID. Different instances
	 *  of the same source will have the same alias.
	 *
	 * @internal
	 *
	 * @param string $field_id The field.
	 * @param int|array $source The source. Default: self::$from
	 * @param string $prefix The table prefix. Default: "t"
	 *
	 * @return string|null The alias.
	 */
	public function _alias( $field_id, $source = null, $prefix = 't' ) {
		if ( 't' == $prefix && ( ! $source || empty ( $this->from ) || in_array( $source, $this->from ) ) ) {
			if ( ! isset( $this->aliases['-'] ) ) {
				$this->aliases['-'] = true;
			}
			return 't1';
		}

		if ( $source && is_array( $source ) ) {
			$source = reset( $source );
		}
		$source_id = $source ? $source : ( ( ! empty( $this->from[0] ) ) ? $this->from[0] : '-' );
		$field_id = $field_id ? $field_id : '-';

		$source_id = "{$source_id}_{$field_id}";

		if ( ! isset( $this->aliases[ $source_id ] ) ) {
			$this->aliases[ $source_id ] = sprintf( '%s%d', $prefix, count( $this->aliases ) + 1 );
		};

		return $this->aliases[ $source_id ];
	}

	/**
	 * Unwraps a nested array of conditionals into one long string.
	 *
	 * @param GF_Query_Condition $condition Either a condition or an array of conditions. Usually from self::$where.
	 *
	 * @internal
	 *
	 * @return string.
	 */
	public function _where_unwrap( $condition ) {
		if ( ! $condition instanceof GF_Query_Condition ) {
			return '';
		}

		/**
		 * This is an OR or AND clause. Needs unwrapping.
		 */
		if ( in_array( $condition->operator, array( GF_Query_Condition::_AND, GF_Query_Condition::_OR ) ) ) {

			/** Recurse. */
			$conditions = array_filter( array_map( array( $this, __FUNCTION__ ), $condition->expressions ), 'strlen' );

			if ( count( $conditions ) ) {
				return count( $conditions ) == 1 ? current( $conditions ) : '(' . implode( " {$condition->operator} ", $conditions ) . ')';
			}

			return '';
		}

		return $condition->sql( $this );
	}

	/**
	 * The CAST function call.
	 *
	 * @param string $field_id The field to cast.
	 * @param string $type The type, one of self::TYPE_*
	 *
	 * @unused This seems to be a development artifact. Candidate for removal.
	 *
	 * @return array|$field The function name, args in order. Or the field if error.
	 */
	public function cast( $field_id, $type ) {
		if ( ! in_array( $type, array( self::TYPE_SIGNED, self::TYPE_UNSIGNED, self::TYPE_DECIMAL ) ) ) {
			return $field_id;
		}

		if ( self::TYPE_DECIMAL === $type ) {
			$type = 'DECIMAL(65, 6)';
		}

		return array( 'CAST', $field_id, 'AS', $type );
	}

	/**
	 * Whether there are several forms that are being selected from or not.
	 *
	 * Does not return true for joins and unions.
	 *
	 * @return bool
	 */
	public function is_multisource() {
		return is_array( $this->from ) && count( $this->from ) > 1;
	}

	/**
	 * Shows the private internal state of this query.
	 *
	 * For debugging and testing purposes only.
	 *
	 * @internal
	 *
	 * @return array Containing introspection data.
	 */
	public function _introspect() {
		return array(
			'from' => $this->from,
			'joins' => $this->joins,
			'where' => $this->where,
			'offset' => $this->offset,
			'limit' => $this->limit,
			'order' => $this->order,

			'aliases' => $this->aliases,
			'queries' => $this->queries,
		);
	}

	/**
	 * Start the stopwatch.
	 *
	 * @return void
	 */
	private function timer_start() {
		$this->timer = microtime( true );
	}

	/**
	 * Stop the stopwatch.
	 *
	 * @return float The time it took in seconds.
	 */
	private function timer_stop() {
		return microtime( true ) - $this->timer;
	}

	/**
	 * Returns an array with the field values for a given entry ID.
	 *
	 * @param $entry_id
	 *
	 * @return array|bool
	 */
	public function get_entry( $entry_id ) {

		if ( empty( $entry_id ) ) {
			return false;
		}

		$entries = $this->get_entries( array( $entry_id ) );
		if ( empty( $entries ) ) {
			return false;
		}
		return array_pop( $entries );
	}

	/**
	 * Returns an array or array entries with the field values for the given entry IDs.
	 *
	 * @param int[]|int[][] $entry_ids A (nested) array of entry IDs to fetch. Invalid IDs are discarded.
	 *
	 * @return array[] An array of entry objects.
	 */
	public function get_entries( $entry_ids ) {
		global $wpdb;

		foreach ( $entry_ids as $i => $id ) {
			if ( ! is_array( $id ) ) {
				$entry_ids[ $i ] = array( $id );
			}
		}

		$ids = array();
		foreach ( $entry_ids as $entry_id ) {
			$ids = array_merge( $ids, $entry_id );
		}
		$ids = array_unique( $ids );

		if ( empty( $ids ) ) {
			return array();
		}

		$entry_table = GFFormsModel::get_entry_table_name();
		$sql = sprintf( "SELECT * from $entry_table WHERE id IN(%s)", $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ) );
		$entryset = $wpdb->get_results( $wpdb->prepare( $sql, $ids ), ARRAY_A );

		$entry_meta_table = GFFormsModel::get_entry_meta_table_name();

		$entries = array();

		foreach ( $entryset as $entry ) {
			$entries[ $entry['id'] ] = $entry;
		}

		$cache = array(
			'form_meta' => array(),
			'form_entry_meta' => array(),
		);

		$meta_clauses = array();

		foreach ( $entryset as $entry ) {
			$form_id = absint( $entry['form_id'] );
			if ( isset( $cache['form_entry_meta'][ $form_id ] ) ) {
				$entry_meta = $cache['form_entry_meta'][ $form_id ];
			} else {
				$entry_meta = $cache['form_entry_meta'][ $form_id ] = RGFormsModel::get_entry_meta( $form_id );
			}
			if ( ! empty( $entry_meta ) ) {
				$entry_meta_placeholders = implode( ',', array_fill( 0, count( $entry_meta ), '%s' ) );
				$sql = sprintf( '( form_id = %d AND meta_key IN (%s) )', $form_id, $entry_meta_placeholders );
				if ( ! isset( $meta_clauses[ $form_id ] ) ) {
					$meta_clauses[ $form_id ] = $wpdb->prepare( $sql, array_keys( $entry_meta ) );
				}
			}
		}

		$meta_clauses_str =  empty ( $meta_clauses ) ?  '' : sprintf( 'OR (%s)', join( ' OR ', $meta_clauses ) );

		$sql = sprintf( "
SELECT entry_id, meta_key, meta_value, item_index 
FROM $entry_meta_table 
WHERE entry_id IN(%s) 
AND ( meta_key REGEXP '^[0-9|.]+$'
%s )
", $placeholders, $meta_clauses_str );
		$metaset = $wpdb->get_results( $wpdb->prepare( $sql, $ids ), ARRAY_A );


		foreach ( $metaset as $meta ) {
			$entries[ $meta['entry_id'] ][ $meta['meta_key'] . $meta['item_index'] ] = $meta['meta_value'];
		}

		foreach ( $entryset as $entry ) {
			if ( isset( $cache['form_meta'][ $entry['form_id'] ] ) ) {
				$form = $cache['form_meta'][ $entry['form_id'] ];
			} else {
				$form = $cache['form_meta'][ $entry['form_id'] ] = RGFormsModel::get_form_meta( $entry['form_id'] );
			}

			$openssl_encrypted_fields = GFFormsModel::get_openssl_encrypted_fields( $entry['id'] );

			foreach ( $form['fields'] as $field ) {
				/* @var GF_Field $field */

				$inputs = $field->get_entry_inputs();
				if ( is_array( $inputs ) ) {
					foreach ( $inputs as $input ) {
						$entries[ $entry['id'] ][ (string) $input['id'] ] = gf_apply_filters( array(
							'gform_get_input_value',
							$form['id'],
							$field->id,
							$input['id']
						), rgar( $entries[ $entry['id'] ], (string) $input['id'] ), $entry, $field, $input['id'] );
					}
				} else {
					$value = rgar( $entries[ $entry['id'] ], (string) $field->id );
					if ( in_array( (string) $field->id, $openssl_encrypted_fields ) ) {
						$value = GFCommon::openssl_decrypt( $value );
					}
					$entries[ $entry['id'] ][ $field->id ] = gf_apply_filters( array(
						'gform_get_input_value',
						$form['id'],
						$field->id
					), $value, $entry, $field, '' );
				}
			}

			if ( isset( $cache['form_entry_meta'][ $entry['form_id'] ] ) ) {
				$entry_meta = $cache['form_entry_meta'][ $entry['form_id'] ];
			}

			foreach ( array_keys( $entry_meta ) as $meta_key ) {
				if ( isset( $entries[ $entry['id'] ][ $meta_key ] ) ) {
					$entries[ $entry['id'] ][ $meta_key ] = maybe_unserialize( $entries[ $entry['id'] ][ $meta_key ] );
				} else {
					$entries[ $entry['id'] ][ $meta_key ] = false;
				}
			}

			GFFormsModel::hydrate_repeaters( $entries[ $entry['id'] ], $form, true );
		}

		$results = array();

		foreach ( $entry_ids as $entry_id ) {
			if ( count( $entry_id ) > 1 ) {
				$joined_entries = array();
				foreach ( $entry_id as $id ) {
					if ( ! isset( $entries[ $id ] ) ) {
						continue;
					}
					$joined_entries[ $entries[ $id ][ 'form_id' ] ] = &$entries[ $id ];
				}
				$results[] = $joined_entries;
			} elseif ( count( $entry_id ) == 1 ) {
				if ( ! isset( $entries[ $entry_id[0] ] ) ) {
					continue;
				}
				$results[] = &$entries[ $entry_id[0] ];
			}
		}

		return $results;
	}

	private function set_sub_field_values( $field, $db_values, $sub_field_values, $form, &$entry ) {
		if ( isset( $field->fields ) && is_array( $field->fields ) ) {
			foreach ( $field->fields as $sub_field ) {
				$this->set_sub_field_values( $sub_field, $db_values, $sub_field_values, $form, $entry );
			}
			return;
		}
		foreach( $db_values as $key => $db_value ) {
			if ( $key == $field->id || preg_match( "/$field->id(\.|_)/", $key ) ) {
				$entry[ $key ] = $db_value;
			}
		}
		return;
	}
}