term_id; $kids = get_term_children( $t_id, $row['taxonomy'] ); foreach ( $kids as $kid ) { $kid_term_tax_id = relevanssi_get_term_tax_id( $kid, $row['taxonomy'] ); if ( $kid_term_tax_id ) { // In some weird cases, this may be null. See: https://wordpress.org/support/topic/childrens-of-chosen-product_cat-not-showing-up/. $term_tax_id[] = $kid_term_tax_id; } } } } $term_tax_id = array_unique( $term_tax_id ); if ( ! empty( $term_tax_id ) ) { $n = count( $term_tax_id ); $term_tax_id = implode( ',', $term_tax_id ); $tq_operator = 'IN'; // Assuming the default operator "IN", unless something else is provided. if ( isset( $row['operator'] ) ) { $tq_operator = strtoupper( $row['operator'] ); } if ( ! in_array( $tq_operator, array( 'IN', 'NOT IN', 'AND' ), true ) ) { $tq_operator = 'IN'; } if ( 'and' === $tax_query_relation ) { if ( 'AND' === $tq_operator ) { $query_restrictions .= " AND relevanssi.doc IN ( SELECT ID FROM $wpdb->posts WHERE 1=1 AND ( SELECT COUNT(1) FROM $wpdb->term_relationships AS tr WHERE tr.term_taxonomy_id IN ($term_tax_id) AND tr.object_id = $wpdb->posts.ID ) = $n )"; // Clean: $term_tax_id and $n are Relevanssi-generated. } else { $query_restrictions .= " AND relevanssi.doc $tq_operator ( SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr WHERE tr.term_taxonomy_id IN ($term_tax_id))"; // Clean: all variables are Relevanssi-generated. } } else { if ( 'IN' === $tq_operator ) { $local_term_tax_ids[] = $term_tax_id; } if ( 'NOT IN' === $tq_operator ) { $local_not_term_tax_ids[] = $term_tax_id; } if ( 'AND' === $tq_operator ) { $local_and_term_tax_ids[] = $term_tax_id; } } } else { global $wp_query; $wp_query->is_category = false; } $copy_term_tax_ids = false; if ( ! $is_sub_row ) { $copy_term_tax_ids = true; } if ( $is_sub_row && ( 'or' === $global_relation || 'or' === $tax_query_relation ) ) { $copy_term_tax_ids = true; } if ( $copy_term_tax_ids ) { $term_tax_ids = array_merge( $term_tax_ids, $local_term_tax_ids ); $not_term_tax_ids = array_merge( $not_term_tax_ids, $local_not_term_tax_ids ); $and_term_tax_ids = array_merge( $and_term_tax_ids, $local_and_term_tax_ids ); } if ( $exists_query ) { $taxonomy = $row['taxonomy']; $operator = 'IN'; if ( 'not exists' === strtolower( $row['operator'] ) ) { $operator = 'NOT IN'; } $exist_query_sql = " relevanssi.doc $operator ( SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr, $wpdb->term_taxonomy AS tt WHERE tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = '$taxonomy' )"; $exist_queries[] = $exist_query_sql; if ( 'and' === $tax_query_relation ) { $query_restrictions .= ' AND ' . $exist_query_sql; } } return array( $query_restrictions, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids, $exist_queries ); } /** * Generates query restrictions from the term taxonomy ids. * * Combines different term tax ID arrays into a set of query restrictions that can be * used in an OR query. * * @global object $wpdb The WP database interface. * * @param array $term_tax_ids The regular terms. * @param array $not_term_tax_ids The NOT terms. * @param array $and_term_tax_ids The AND terms. * @param array $exist_queries The EXIST queries. * * @return string The MySQL query restrictions. */ function relevanssi_process_term_tax_ids( $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids, $exist_queries ) { global $wpdb; $query_restriction_parts = array(); $query_restrictions = ''; $term_tax_ids = array_unique( $term_tax_ids ); if ( count( $term_tax_ids ) > 0 ) { $term_tax_ids = implode( ',', $term_tax_ids ); $query_restriction_parts[] = " relevanssi.doc IN ( SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr WHERE tr.term_taxonomy_id IN ($term_tax_ids) )"; // Clean: all variables are Relevanssi-generated. } if ( count( $not_term_tax_ids ) > 0 ) { $not_term_tax_ids = implode( ',', $not_term_tax_ids ); $query_restriction_parts[] .= " relevanssi.doc NOT IN ( SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr WHERE tr.term_taxonomy_id IN ($not_term_tax_ids) )"; // Clean: all variables are Relevanssi-generated. } if ( count( $and_term_tax_ids ) > 0 ) { $and_term_tax_ids = implode( ',', $and_term_tax_ids ); $n = count( explode( ',', $and_term_tax_ids ) ); $query_restriction_parts[] .= " relevanssi.doc IN ( SELECT ID FROM $wpdb->posts WHERE 1=1 AND ( SELECT COUNT(1) FROM $wpdb->term_relationships AS tr WHERE tr.term_taxonomy_id IN ($and_term_tax_ids) AND tr.object_id = $wpdb->posts.ID ) = $n )"; // Clean: all variables are Relevanssi-generated. } if ( $exist_queries ) { $query_restriction_parts = array_merge( $query_restriction_parts, $exist_queries ); } if ( count( $query_restriction_parts ) > 1 ) { $query_restrictions .= '('; } $query_restrictions .= implode( ' OR', $query_restriction_parts ); if ( count( $query_restriction_parts ) > 1 ) { $query_restrictions .= ')'; } if ( $query_restrictions ) { $query_restrictions = ' AND ' . $query_restrictions; } return $query_restrictions; } /** * Gets and sanitizes the taxonomy name and slug parameters. * * Checks parameters: if they're numeric, pass them for term_id filtering, otherwise * sanitize and create a comma-separated list. * * @param string $terms_parameter The 'terms' field from the tax_query row. * @param string $taxonomy The taxonomy name. * @param string $field_name The field name ('slug', 'name'). * * @return array An array containing numeric terms and the list of sanitized term * names. */ function relevanssi_get_term_in( $terms_parameter, $taxonomy, $field_name ) { $numeric_terms = array(); $names = array(); if ( ! is_array( $terms_parameter ) ) { $terms_parameter = array( $terms_parameter ); } foreach ( $terms_parameter as $name ) { $term = get_term_by( $field_name, $name, $taxonomy ); if ( ! $term ) { if ( ctype_digit( strval( $name ) ) ) { $numeric_terms[] = $name; } } else { if ( isset( $term->term_id ) && in_array( $field_name, array( 'slug', 'name' ), true ) ) { $names[] = "'" . esc_sql( $name ) . "'"; } else { $numeric_terms[] = $name; } } } return array( 'numeric_terms' => implode( ',', $numeric_terms ), 'term_in' => implode( ',', $names ), ); } /** * Gets the term_tax_id from a row with 'field' set to 'slug' or 'name'. * * If the slugs or names are all numeric values, will switch the 'field' parameter * to 'term_id'. * * @param array $row The taxonomy query row. * * @return array An array of term taxonomy IDs. */ function relevanssi_term_tax_id_from_row( $row ) { global $wpdb; $type = $row['field']; $term_in_results = relevanssi_get_term_in( $row['terms'], $row['taxonomy'], $type ); $numeric_terms = $term_in_results['numeric_terms']; $term_in = $term_in_results['term_in']; $term_tax_id = array(); if ( ! empty( $numeric_terms ) ) { $type = 'term_id'; $term_in = $numeric_terms; } if ( ! empty( $term_in ) ) { $row_taxonomy = sanitize_text_field( $row['taxonomy'] ); $tt_q = "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt LEFT JOIN $wpdb->terms AS t ON (tt.term_id=t.term_id) WHERE tt.taxonomy = '$row_taxonomy' AND t.$type IN ($term_in)"; // Clean: $row_taxonomy is sanitized, each term in $term_in is sanitized. $term_tax_id = $wpdb->get_col( $tt_q ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } return $term_tax_id; }