diff --git a/src/wp-includes/class-wp-date-query.php b/src/wp-includes/class-wp-date-query.php index faa657dde5..00a7554dea 100644 --- a/src/wp-includes/class-wp-date-query.php +++ b/src/wp-includes/class-wp-date-query.php @@ -150,8 +150,8 @@ class WP_Date_Query { return; } - if ( isset( $date_query['relation'] ) && 'OR' === strtoupper( $date_query['relation'] ) ) { - $this->relation = 'OR'; + if ( isset( $date_query['relation'] ) ) { + $this->relation = $this->sanitize_relation( $date_query['relation'] ); } else { $this->relation = 'AND'; } @@ -220,6 +220,9 @@ class WP_Date_Query { $this->validate_date_values( $queries ); } + // Sanitize the relation parameter. + $queries['relation'] = $this->sanitize_relation( $queries['relation'] ); + foreach ( $queries as $key => $q ) { if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) { // This is a first-order query. Trust the values and sanitize when building SQL. @@ -1041,4 +1044,20 @@ class WP_Date_Query { return $wpdb->prepare( "DATE_FORMAT( $column, %s ) $compare %f", $format, $time ); } + + /** + * Sanitizes a 'relation' operator. + * + * @since 6.0.3 + * + * @param string $relation Raw relation key from the query argument. + * @return string Sanitized relation ('AND' or 'OR'). + */ + public function sanitize_relation( $relation ) { + if ( 'OR' === strtoupper( $relation ) ) { + return 'OR'; + } else { + return 'AND'; + } + } } diff --git a/tests/phpunit/tests/date/query.php b/tests/phpunit/tests/date/query.php index a432675f94..613d74957b 100644 --- a/tests/phpunit/tests/date/query.php +++ b/tests/phpunit/tests/date/query.php @@ -1145,4 +1145,142 @@ class Tests_Date_Query extends WP_UnitTestCase { // MySQL ignores the invalid clause. $this->assertSame( array( $p1, $p2 ), $q->posts ); } + + /** + * @covers WP_Date_Query::get_sql + */ + public function test_relation_in_query_and() { + $date_query = array( + 'relation' => 'AND', + array( + 'before' => array( + 'year' => 2021, + 'month' => 9, + 'day' => 20, + ), + 'after' => array( + 'year' => 2019, + 'month' => 2, + 'day' => 25, + ), + 'inclusive' => true, + ), + array( + 'before' => array( + 'year' => 2016, + 'month' => 9, + 'day' => 11, + ), + 'after' => array( + 'year' => 2014, + 'month' => 5, + 'day' => 12, + ), + 'inclusive' => false, + ), + ); + + $q = new WP_Date_Query( $date_query ); + + $sql = $q->get_sql(); + + $parts = mb_split( '\)\s+AND\s+\(', $sql ); + $this->assertIsArray( $parts, 'SQL query cannot be split into multiple parts using operator AND.' ); + $this->assertEquals( 2, count( $parts ), 'SQL query does not contain correct number of AND operators.' ); + + $this->assertStringNotContainsString( 'OR', $sql, 'SQL query contains conditions joined by operator OR.' ); + } + + /** + * @covers WP_Date_Query::get_sql + */ + public function test_relation_in_query_or() { + $date_query = array( + 'relation' => 'OR', + array( + 'before' => array( + 'year' => 2021, + 'month' => 9, + 'day' => 20, + ), + 'after' => array( + 'year' => 2019, + 'month' => 2, + 'day' => 25, + ), + 'inclusive' => true, + ), + array( + 'before' => array( + 'year' => 2016, + 'month' => 9, + 'day' => 11, + ), + 'after' => array( + 'year' => 2014, + 'month' => 5, + 'day' => 12, + ), + 'inclusive' => false, + ), + ); + + $q = new WP_Date_Query( $date_query ); + + $sql = $q->get_sql(); + + $this->assertStringContainsString( 'OR', $sql, 'SQL query does not contain conditions joined by operator OR.' ); + + $parts = mb_split( '\)\s+OR\s+\(', $sql ); + $this->assertIsArray( $parts, 'SQL query cannot be split into multiple parts using operator OR.' ); + $this->assertEquals( 2, count( $parts ), 'SQL query does not contain correct number of OR operators.' ); + + // Checking number of occurrences of AND while skipping the one at the beginning. + $this->assertSame( 2, substr_count( substr( $sql, 5 ), 'AND' ), 'SQL query does not contain expected number conditions joined by operator AND.' ); + } + + /** + * @covers WP_Date_Query::get_sql + */ + public function test_relation_in_query_unsupported() { + $date_query = array( + 'relation' => 'UNSUPPORTED', + array( + 'before' => array( + 'year' => 2021, + 'month' => 9, + 'day' => 20, + ), + 'after' => array( + 'year' => 2019, + 'month' => 2, + 'day' => 25, + ), + 'inclusive' => true, + ), + array( + 'before' => array( + 'year' => 2016, + 'month' => 9, + 'day' => 11, + ), + 'after' => array( + 'year' => 2014, + 'month' => 5, + 'day' => 12, + ), + 'inclusive' => false, + ), + ); + + $q = new WP_Date_Query( $date_query ); + + $sql = $q->get_sql(); + + $parts = mb_split( '\)\s+AND\s+\(', $sql ); + $this->assertIsArray( $parts, 'SQL query cannot be split into multiple parts using operator AND.' ); + $this->assertEquals( 2, count( $parts ), 'SQL query does not contain correct number of AND operators.' ); + + $this->assertStringNotContainsString( 'OR', $sql, 'SQL query contains conditions joined by operator OR.' ); + } } diff --git a/tests/phpunit/tests/term/taxQuery.php b/tests/phpunit/tests/term/taxQuery.php index e95027cb22..40f1d29612 100644 --- a/tests/phpunit/tests/term/taxQuery.php +++ b/tests/phpunit/tests/term/taxQuery.php @@ -335,6 +335,7 @@ class Tests_Term_Tax_Query extends WP_UnitTestCase { /** * @ticket 18105 + * @covers WP_Tax_Query::get_sql */ public function test_get_sql_relation_and_operator_in() { register_taxonomy( 'wptests_tax', 'post' ); @@ -381,11 +382,17 @@ class Tests_Term_Tax_Query extends WP_UnitTestCase { $this->assertSame( 3, substr_count( $sql['join'], 'JOIN' ) ); + // Checking number of occurrences of AND while skipping the one at the beginning. + $this->assertSame( 2, substr_count( substr( $sql['where'], 5 ), 'AND' ), 'SQL query does not contain expected number conditions joined by operator AND.' ); + + $this->assertStringNotContainsString( 'OR', $sql['where'], 'SQL query contains conditions joined by operator OR.' ); + _unregister_taxonomy( 'wptests_tax' ); } /** * @ticket 18105 + * @covers WP_Tax_Query::get_sql */ public function test_get_sql_nested_relation_or_operator_in() { register_taxonomy( 'wptests_tax', 'post' ); @@ -434,6 +441,8 @@ class Tests_Term_Tax_Query extends WP_UnitTestCase { $sql = $tq->get_sql( $wpdb->posts, 'ID' ); $this->assertSame( 2, substr_count( $sql['join'], 'JOIN' ) ); + $this->assertSame( 2, substr_count( $sql['where'], 'OR' ), 'SQL query does not contain expected number conditions joined by operator OR.' ); + $this->assertStringNotContainsString( 'AND', substr( $sql['where'], 5 ), 'SQL query contains conditions joined by operator AND.' ); _unregister_taxonomy( 'wptests_tax' ); } @@ -495,4 +504,60 @@ class Tests_Term_Tax_Query extends WP_UnitTestCase { _unregister_taxonomy( 'wptests_tax' ); } + + /** + * @ticket 18105 + * @covers WP_Tax_Query::get_sql + */ + public function test_get_sql_relation_unsupported() { + register_taxonomy( 'wptests_tax', 'post' ); + + $t1 = self::factory()->term->create( + array( + 'taxonomy' => 'wptests_tax', + ) + ); + $t2 = self::factory()->term->create( + array( + 'taxonomy' => 'wptests_tax', + ) + ); + $t3 = self::factory()->term->create( + array( + 'taxonomy' => 'wptests_tax', + ) + ); + + $tq = new WP_Tax_Query( + array( + 'relation' => 'UNSUPPORTED', + array( + 'taxonomy' => 'wptests_tax', + 'field' => 'term_id', + 'terms' => $t1, + ), + array( + 'taxonomy' => 'wptests_tax', + 'field' => 'term_id', + 'terms' => $t2, + ), + array( + 'taxonomy' => 'wptests_tax', + 'field' => 'term_id', + 'terms' => $t3, + ), + ) + ); + + global $wpdb; + $sql = $tq->get_sql( $wpdb->posts, 'ID' ); + + // Checking number of occurrences of AND while skipping the one at the beginning. + $this->assertSame( 2, substr_count( substr( $sql['where'], 5 ), 'AND' ), 'SQL query does not contain expected number conditions joined by operator AND.' ); + + $this->assertStringNotContainsString( 'UNSUPPORTED', $sql['where'], 'SQL query contains unsupported relation operator.' ); + $this->assertStringNotContainsString( 'OR', $sql['where'], 'SQL query contains conditions joined by operator OR.' ); + + _unregister_taxonomy( 'wptests_tax' ); + } }