- $this->clean_query( $clause );
-
- if ( is_wp_error( $clause ) ) {
- return self::$no_results;
- }
-
- $terms = $clause['terms'];
- $operator = strtoupper( $clause['operator'] );
-
- if ( 'IN' == $operator ) {
-
- if ( empty( $terms ) ) {
- return self::$no_results;
- }
-
- $terms = implode( ',', $terms );
-
- /*
- * Before creating another table join, see if this clause has a
- * sibling with an existing join that can be shared.
- */
- $alias = $this->find_compatible_table_alias( $clause, $parent_query );
- if ( false === $alias ) {
- $i = count( $this->table_aliases );
- $alias = $i ? 'tt' . $i : $wpdb->term_relationships;
-
- // Store the alias as part of a flat array to build future iterators.
- $this->table_aliases[] = $alias;
-
- // Store the alias with this clause, so later siblings can use it.
- $clause['alias'] = $alias;
-
- $join .= " INNER JOIN $wpdb->term_relationships";
- $join .= $i ? " AS $alias" : '';
- $join .= " ON ($this->primary_table.$this->primary_id_column = $alias.object_id)";
- }
-
-
- $where = "$alias.term_taxonomy_id $operator ($terms)";
-
- } elseif ( 'NOT IN' == $operator ) {
-
- if ( empty( $terms ) ) {
- return $sql;
- }
-
- $terms = implode( ',', $terms );
-
- $where = "$this->primary_table.$this->primary_id_column NOT IN (
- SELECT object_id
- FROM $wpdb->term_relationships
- WHERE term_taxonomy_id IN ($terms)
- )";
-
- } elseif ( 'AND' == $operator ) {
-
- if ( empty( $terms ) ) {
- return $sql;
- }
-
- $num_terms = count( $terms );
-
- $terms = implode( ',', $terms );
-
- $where = "(
- SELECT COUNT(1)
- FROM $wpdb->term_relationships
- WHERE term_taxonomy_id IN ($terms)
- AND object_id = $this->primary_table.$this->primary_id_column
- ) = $num_terms";
-
- } elseif ( 'NOT EXISTS' === $operator || 'EXISTS' === $operator ) {
-
- $where = $wpdb->prepare( "$operator (
- SELECT 1
- FROM $wpdb->term_relationships
- INNER JOIN $wpdb->term_taxonomy
- ON $wpdb->term_taxonomy.term_taxonomy_id = $wpdb->term_relationships.term_taxonomy_id
- WHERE $wpdb->term_taxonomy.taxonomy = %s
- AND $wpdb->term_relationships.object_id = $this->primary_table.$this->primary_id_column
- )", $clause['taxonomy'] );
-
- }
-
- $sql['join'][] = $join;
- $sql['where'][] = $where;
- return $sql;
- }
-
- /**
- * Identify an existing table alias that is compatible with the current query clause.
- *
- * We avoid unnecessary table joins by allowing each clause to look for
- * an existing table alias that is compatible with the query that it
- * needs to perform.
- *
- * An existing alias is compatible if (a) it is a sibling of `$clause`
- * (ie, it's under the scope of the same relation), and (b) the combination
- * of operator and relation between the clauses allows for a shared table
- * join. In the case of {@see WP_Tax_Query}, this only applies to 'IN'
- * clauses that are connected by the relation 'OR'.
- *
- * @since 4.1.0
- * @access protected
- *
- * @param array $clause Query clause.
- * @param array $parent_query Parent query of $clause.
- * @return string|bool Table alias if found, otherwise false.
- */
- protected function find_compatible_table_alias( $clause, $parent_query ) {
- $alias = false;
-
- // Sanity check. Only IN queries use the JOIN syntax .
- if ( ! isset( $clause['operator'] ) || 'IN' !== $clause['operator'] ) {
- return $alias;
- }
-
- // Since we're only checking IN queries, we're only concerned with OR relations.
- if ( ! isset( $parent_query['relation'] ) || 'OR' !== $parent_query['relation'] ) {
- return $alias;
- }
-
- $compatible_operators = array( 'IN' );
-
- foreach ( $parent_query as $sibling ) {
- if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) {
- continue;
- }
-
- if ( empty( $sibling['alias'] ) || empty( $sibling['operator'] ) ) {
- continue;
- }
-
- // The sibling must both have compatible operator to share its alias.
- if ( in_array( strtoupper( $sibling['operator'] ), $compatible_operators ) ) {
- $alias = $sibling['alias'];
- break;
- }
- }
-
- return $alias;
- }
-
- /**
- * Validates a single query.
- *
- * @since 3.2.0
- * @access private
- *
- * @param array &$query The single query.
- */
- private function clean_query( &$query ) {
- if ( empty( $query['taxonomy'] ) ) {
- if ( 'term_taxonomy_id' !== $query['field'] ) {
- $query = new WP_Error( 'Invalid taxonomy' );
- return;
- }
-
- // so long as there are shared terms, include_children requires that a taxonomy is set
- $query['include_children'] = false;
- } elseif ( ! taxonomy_exists( $query['taxonomy'] ) ) {
- $query = new WP_Error( 'Invalid taxonomy' );
- return;
- }
-
- $query['terms'] = array_unique( (array) $query['terms'] );
-
- if ( is_taxonomy_hierarchical( $query['taxonomy'] ) && $query['include_children'] ) {
- $this->transform_query( $query, 'term_id' );
-
- if ( is_wp_error( $query ) )
- return;
-
- $children = array();
- foreach ( $query['terms'] as $term ) {
- $children = array_merge( $children, get_term_children( $term, $query['taxonomy'] ) );
- $children[] = $term;
- }
- $query['terms'] = $children;
- }
-
- $this->transform_query( $query, 'term_taxonomy_id' );
- }
-
- /**
- * Transforms a single query, from one field to another.
- *
- * @since 3.2.0
- *
- * @param array &$query The single query.
- * @param string $resulting_field The resulting field. Accepts 'slug', 'name', 'term_taxonomy_id',
- * or 'term_id'. Default: 'term_id'.
- */
- public function transform_query( &$query, $resulting_field ) {
- global $wpdb;
-
- if ( empty( $query['terms'] ) )
- return;
-
- if ( $query['field'] == $resulting_field )
- return;
-
- $resulting_field = sanitize_key( $resulting_field );
-
- switch ( $query['field'] ) {
- case 'slug':
- case 'name':
- foreach ( $query['terms'] as &$term ) {
- /*
- * 0 is the $term_id parameter. We don't have a term ID yet, but it doesn't
- * matter because `sanitize_term_field()` ignores the $term_id param when the
- * context is 'db'.
- */
- $term = "'" . esc_sql( sanitize_term_field( $query['field'], $term, 0, $query['taxonomy'], 'db' ) ) . "'";
- }
-
- $terms = implode( ",", $query['terms'] );
-
- $terms = $wpdb->get_col( "
- SELECT $wpdb->term_taxonomy.$resulting_field
- FROM $wpdb->term_taxonomy
- INNER JOIN $wpdb->terms USING (term_id)
- WHERE taxonomy = '{$query['taxonomy']}'
- AND $wpdb->terms.{$query['field']} IN ($terms)
- " );
- break;
- case 'term_taxonomy_id':
- $terms = implode( ',', array_map( 'intval', $query['terms'] ) );
- $terms = $wpdb->get_col( "
- SELECT $resulting_field
- FROM $wpdb->term_taxonomy
- WHERE term_taxonomy_id IN ($terms)
- " );
- break;
- default:
- $terms = implode( ',', array_map( 'intval', $query['terms'] ) );
- $terms = $wpdb->get_col( "
- SELECT $resulting_field
- FROM $wpdb->term_taxonomy
- WHERE taxonomy = '{$query['taxonomy']}'
- AND term_id IN ($terms)
- " );
- }
-
- if ( 'AND' == $query['operator'] && count( $terms ) < count( $query['terms'] ) ) {
- $query = new WP_Error( 'Inexistent terms' );
- return;
- }
-
- $query['terms'] = $terms;
- $query['field'] = $resulting_field;
- }
-}
-
-/**
- * Get all Term data from database by Term ID.
- *
- * The usage of the get_term function is to apply filters to a term object. It
- * is possible to get a term object from the database before applying the
- * filters.
- *
- * $term ID must be part of $taxonomy, to get from the database. Failure, might
- * be able to be captured by the hooks. Failure would be the same value as $wpdb
- * returns for the get_row method.
- *
- * There are two hooks, one is specifically for each term, named 'get_term', and
- * the second is for the taxonomy name, 'term_$taxonomy'. Both hooks gets the
- * term object, and the taxonomy name as parameters. Both hooks are expected to
- * return a Term object.
- *
- * 'get_term' hook - Takes two parameters the term Object and the taxonomy name.
- * Must return term object. Used in get_term() as a catch-all filter for every
- * $term.
- *
- * 'get_$taxonomy' hook - Takes two parameters the term Object and the taxonomy
- * name. Must return term object. $taxonomy will be the taxonomy name, so for
- * example, if 'category', it would be 'get_category' as the filter name. Useful
- * for custom taxonomies or plugging into default taxonomies.
- *
- * @since 2.3.0
- *
- * @global wpdb $wpdb WordPress database abstraction object.
- * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
- *
- * @param int|object $term If integer, will get from database. If object will apply filters and return $term.
- * @param string $taxonomy Taxonomy name that $term is part of.
- * @param string $output Constant OBJECT, ARRAY_A, or ARRAY_N
- * @param string $filter Optional, default is raw or no WordPress defined filter will applied.
- * @return mixed|null|WP_Error Term Row from database. Will return null if $term is empty. If taxonomy does not
- * exist then WP_Error will be returned.
- */
-function get_term($term, $taxonomy, $output = OBJECT, $filter = 'raw') {
- global $wpdb;
-
- if ( empty($term) ) {
- $error = new WP_Error('invalid_term', __('Empty Term'));
- return $error;
- }
-
- if ( ! taxonomy_exists($taxonomy) ) {
- $error = new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
- return $error;
- }
-
- if ( is_object($term) && empty($term->filter) ) {
- wp_cache_add( $term->term_id, $term, $taxonomy );
- $_term = $term;
- } else {
- if ( is_object($term) )
- $term = $term->term_id;
- if ( !$term = (int) $term )
- return null;
- if ( ! $_term = wp_cache_get( $term, $taxonomy ) ) {
- $_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND t.term_id = %d LIMIT 1", $taxonomy, $term) );
- if ( ! $_term )
- return null;
- wp_cache_add( $term, $_term, $taxonomy );
- }
- }
-
- /**
- * Filter a term.
- *
- * @since 2.3.0
- *
- * @param int|object $_term Term object or ID.
- * @param string $taxonomy The taxonomy slug.
- */
- $_term = apply_filters( 'get_term', $_term, $taxonomy );
-
- /**
- * Filter a taxonomy.
- *
- * The dynamic portion of the filter name, `$taxonomy`, refers
- * to the taxonomy slug.
- *
- * @since 2.3.0
- *
- * @param int|object $_term Term object or ID.
- * @param string $taxonomy The taxonomy slug.
- */
- $_term = apply_filters( "get_$taxonomy", $_term, $taxonomy );
- $_term = sanitize_term($_term, $taxonomy, $filter);
-
- if ( $output == OBJECT ) {
- return $_term;
- } elseif ( $output == ARRAY_A ) {
- $__term = get_object_vars($_term);
- return $__term;
- } elseif ( $output == ARRAY_N ) {
- $__term = array_values(get_object_vars($_term));
- return $__term;
- } else {
- return $_term;
- }
-}
-
-/**
- * Get all Term data from database by Term field and data.
- *
- * Warning: $value is not escaped for 'name' $field. You must do it yourself, if
- * required.
- *
- * The default $field is 'id', therefore it is possible to also use null for
- * field, but not recommended that you do so.
- *
- * If $value does not exist, the return value will be false. If $taxonomy exists
- * and $field and $value combinations exist, the Term will be returned.
- *
- * @since 2.3.0
- *
- * @global wpdb $wpdb WordPress database abstraction object.
- * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
- *
- * @param string $field Either 'slug', 'name', 'id' (term_id), or 'term_taxonomy_id'
- * @param string|int $value Search for this term value
- * @param string $taxonomy Taxonomy Name
- * @param string $output Constant OBJECT, ARRAY_A, or ARRAY_N
- * @param string $filter Optional, default is raw or no WordPress defined filter will applied.
- * @return mixed Term Row from database. Will return false if $taxonomy does not exist or $term was not found.
- */
-function get_term_by($field, $value, $taxonomy, $output = OBJECT, $filter = 'raw') {
- global $wpdb;
-
- if ( ! taxonomy_exists($taxonomy) )
- return false;
-
- if ( 'slug' == $field ) {
- $field = 't.slug';
- $value = sanitize_title($value);
- if ( empty($value) )
- return false;
- } elseif ( 'name' == $field ) {
- // Assume already escaped
- $value = wp_unslash($value);
- $field = 't.name';
- } elseif ( 'term_taxonomy_id' == $field ) {
- $value = (int) $value;
- $field = 'tt.term_taxonomy_id';
- } else {
- $term = get_term( (int) $value, $taxonomy, $output, $filter );
- if ( is_wp_error( $term ) )
- $term = false;
- return $term;
- }
-
- $term = $wpdb->get_row( $wpdb->prepare( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND $field = %s LIMIT 1", $taxonomy, $value ) );
- if ( ! $term )
- return false;
-
- wp_cache_add( $term->term_id, $term, $taxonomy );
-
- /** This filter is documented in wp-includes/taxonomy.php */
- $term = apply_filters( 'get_term', $term, $taxonomy );
-
- /** This filter is documented in wp-includes/taxonomy.php */
- $term = apply_filters( "get_$taxonomy", $term, $taxonomy );
-
- $term = sanitize_term($term, $taxonomy, $filter);
-
- if ( $output == OBJECT ) {
- return $term;
- } elseif ( $output == ARRAY_A ) {
- return get_object_vars($term);
- } elseif ( $output == ARRAY_N ) {
- return array_values(get_object_vars($term));
- } else {
- return $term;
- }