<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[55248] trunk: Query: Add a `search_columns` argument to control which fields are searched in a search query.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/55248">55248</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/55248","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>audrasjb</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-02-07 08:53:01 +0000 (Tue, 07 Feb 2023)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Query: Add a `search_columns` argument to control which fields are searched in a search query.
Previously, the `s` argument of the `WP_Query::parse_query()` method searched the `post_title`, `post_excerpt`, and `post_content` fields, with no way of controlling this apart from using the `posts_search` filter and adjusting the SQL manually. This changeset adds the ability to specify which fields are searched when performing a query, using the `search_columns` argument.
Props johnbillion, birgire, petitphp, audrasjb, costdev, mukesh27.
Fixes <a href="https://core.trac.wordpress.org/ticket/43867">#43867</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesclasswpqueryphp">trunk/src/wp-includes/class-wp-query.php</a></li>
<li><a href="#trunksrcwpincludesrestapiendpointsclasswprestpostscontrollerphp">trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestattachmentscontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-attachments-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestpagescontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-pages-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestpostscontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-posts-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapiwpRestMenuItemsControllerphp">trunk/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php</a></li>
<li><a href="#trunktestsqunitfixtureswpapigeneratedjs">trunk/tests/qunit/fixtures/wp-api-generated.js</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestsquerysearchColumnsphp">trunk/tests/phpunit/tests/query/searchColumns.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesclasswpqueryphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/class-wp-query.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/class-wp-query.php 2023-02-07 07:14:11 UTC (rev 55247)
+++ trunk/src/wp-includes/class-wp-query.php 2023-02-07 08:53:01 UTC (rev 55248)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -610,6 +610,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'post_parent__not_in',
</span><span class="cx" style="display: block; padding: 0 10px"> 'author__in',
</span><span class="cx" style="display: block; padding: 0 10px"> 'author__not_in',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'search_columns',
</ins><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> foreach ( $array_keys as $key ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -637,6 +638,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 5.1.0 Introduced the `$meta_compare_key` parameter.
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 5.3.0 Introduced the `$meta_type_key` parameter.
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 6.1.0 Introduced the `$update_menu_item_cache` parameter.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.2.0 Introduced the `$search_columns` parameter.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param string|array $query {
</span><span class="cx" style="display: block; padding: 0 10px"> * Optional. Array or string of Query parameters.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -750,6 +752,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * return posts containing 'pillow' but not 'sofa'. The
</span><span class="cx" style="display: block; padding: 0 10px"> * character used for exclusion can be modified using the
</span><span class="cx" style="display: block; padding: 0 10px"> * the 'wp_query_search_exclusion_prefix' filter.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @type array $search_columns Array of column names to be searched. Accepts 'post_title',
+ * 'post_excerpt' and 'post_content'. Default empty array.
</ins><span class="cx" style="display: block; padding: 0 10px"> * @type int $second Second of the minute. Default empty. Accepts numbers 0-59.
</span><span class="cx" style="display: block; padding: 0 10px"> * @type bool $sentence Whether to search by phrase. Default false.
</span><span class="cx" style="display: block; padding: 0 10px"> * @type bool $suppress_filters Whether to suppress filters. Default false.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1410,7 +1414,33 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $searchand = '';
</span><span class="cx" style="display: block; padding: 0 10px"> $q['search_orderby_title'] = array();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $default_search_columns = array( 'post_title', 'post_excerpt', 'post_content' );
+ $search_columns = ! empty( $q['search_columns'] ) ? $q['search_columns'] : $default_search_columns;
+ if ( ! is_array( $search_columns ) ) {
+ $search_columns = array( $search_columns );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Filters the columns to search in a WP_Query search.
+ *
+ * The supported columns are `post_title`, `post_excerpt` and `post_content`.
+ * They are all included by default.
+ *
+ * @since 6.2.0
+ *
+ * @param string[] $search_columns Array of column names to be searched.
+ * @param string $search Text being searched.
+ * @param WP_Query $query The current WP_Query instance.
+ */
+ $search_columns = (array) apply_filters( 'post_search_columns', $search_columns, $q['s'], $this );
+
+ // Use only supported search columns.
+ $search_columns = array_intersect( $search_columns, $default_search_columns );
+ if ( empty( $search_columns ) ) {
+ $search_columns = $default_search_columns;
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Filters the prefix that indicates that a search term should be excluded from results.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 4.7.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1439,11 +1469,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $like = $n . $wpdb->esc_like( $term ) . $n;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $search_columns_parts = array();
+ foreach ( $search_columns as $search_column ) {
+ $search_columns_parts[ $search_column ] = $wpdb->prepare( "({$wpdb->posts}.$search_column $like_op %s)", $like );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> if ( ! empty( $this->allow_query_attachment_by_filename ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $search .= $wpdb->prepare( "{$searchand}(({$wpdb->posts}.post_title $like_op %s) $andor_op ({$wpdb->posts}.post_excerpt $like_op %s) $andor_op ({$wpdb->posts}.post_content $like_op %s) $andor_op (sq1.meta_value $like_op %s))", $like, $like, $like, $like );
- } else {
- $search .= $wpdb->prepare( "{$searchand}(({$wpdb->posts}.post_title $like_op %s) $andor_op ({$wpdb->posts}.post_excerpt $like_op %s) $andor_op ({$wpdb->posts}.post_content $like_op %s))", $like, $like, $like );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $search_columns_parts['attachment'] = $wpdb->prepare( "(sq1.meta_value $like_op %s)", $like );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ $search .= "$searchand(" . implode( " $andor_op ", $search_columns_parts ) . ')';
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $searchand = ' AND ';
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span></span></pre></div>
<a id="trunksrcwpincludesrestapiendpointsclasswprestpostscontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php 2023-02-07 07:14:11 UTC (rev 55247)
+++ trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php 2023-02-07 08:53:01 UTC (rev 55248)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -249,6 +249,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'parent' => 'post_parent__in',
</span><span class="cx" style="display: block; padding: 0 10px"> 'parent_exclude' => 'post_parent__not_in',
</span><span class="cx" style="display: block; padding: 0 10px"> 'search' => 's',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'search_columns' => 'search_columns',
</ins><span class="cx" style="display: block; padding: 0 10px"> 'slug' => 'post_name__in',
</span><span class="cx" style="display: block; padding: 0 10px"> 'status' => 'post_status',
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2891,6 +2892,16 @@
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $query_params['search_columns'] = array(
+ 'default' => array(),
+ 'description' => __( 'Array of column names to be searched.' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'enum' => array( 'post_title', 'post_content', 'post_excerpt' ),
+ 'type' => 'string',
+ ),
+ );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $query_params['slug'] = array(
</span><span class="cx" style="display: block; padding: 0 10px"> 'description' => __( 'Limit result set to posts with one or more specific slugs.' ),
</span><span class="cx" style="display: block; padding: 0 10px"> 'type' => 'array',
</span></span></pre></div>
<a id="trunktestsphpunittestsquerysearchColumnsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/query/searchColumns.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/query/searchColumns.php (rev 0)
+++ trunk/tests/phpunit/tests/query/searchColumns.php 2023-02-07 08:53:01 UTC (rev 55248)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,414 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/**
+ * Testing the search columns support in `WP_Query`.
+ *
+ * @package WordPress\UnitTests
+ * @since 6.2.0
+ */
+
+/**
+ * Test cases for the search columns feature.
+ *
+ * @group query
+ * @group search
+ *
+ * @covers WP_Query::parse_search
+ *
+ * @since 6.2.0
+ */
+class Tests_Query_SearchColumns extends WP_UnitTestCase {
+ /**
+ * The post ID of the first fixture post.
+ *
+ * @since 6.2.0
+ * @var int $pid1
+ */
+ protected static $pid1;
+
+ /**
+ * The post ID of the second fixture post.
+ *
+ * @since 6.2.0
+ * @var int $pid2
+ */
+ protected static $pid2;
+
+ /**
+ * The post ID of the third fixture post.
+ *
+ * @since 6.2.0
+ * @var int $pid3
+ */
+ protected static $pid3;
+
+ /**
+ * Create posts fixtures.
+ *
+ * @param WP_UnitTest_Factory $factory The factory instance.
+ */
+ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
+ self::$pid1 = $factory->post->create(
+ array(
+ 'post_status' => 'publish',
+ 'post_title' => 'foo title',
+ 'post_excerpt' => 'foo excerpt',
+ 'post_content' => 'foo content',
+ )
+ );
+ self::$pid2 = $factory->post->create(
+ array(
+ 'post_status' => 'publish',
+ 'post_title' => 'bar title',
+ 'post_excerpt' => 'foo bar excerpt',
+ 'post_content' => 'foo bar content',
+ )
+ );
+
+ self::$pid3 = $factory->post->create(
+ array(
+ 'post_status' => 'publish',
+ 'post_title' => 'baz title',
+ 'post_excerpt' => 'baz bar excerpt',
+ 'post_content' => 'baz bar foo content',
+ )
+ );
+ }
+
+ /**
+ * Tests that search uses default search columns when search columns are empty.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_use_default_search_columns_when_empty_search_columns() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array(),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertStringContainsString( 'post_title', $q->request, 'SQL request should contain post_title string.' );
+ $this->assertStringContainsString( 'post_excerpt', $q->request, 'SQL request should contain post_excerpt string.' );
+ $this->assertStringContainsString( 'post_content', $q->request, 'SQL request should contain post_content string.' );
+ $this->assertSame( array( self::$pid1, self::$pid2, self::$pid3 ), $q->posts, 'Query results should be equal to the set.' );
+ }
+
+ /**
+ * Tests that search supports the `post_title` search column.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_post_title_search_column() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array( 'post_title' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid1 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports the `post_excerpt` search column.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_post_excerpt_search_column() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array( 'post_excerpt' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid1, self::$pid2 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports the `post_content` search column.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_post_content_search_column() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array( 'post_content' ),
+ 'fields' => 'ids',
+ )
+ );
+ $this->assertSame( array( self::$pid1, self::$pid2, self::$pid3 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports the `post_title` and `post_excerpt` search columns together.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_post_title_and_post_excerpt_search_columns() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array( 'post_title', 'post_excerpt' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid1, self::$pid2 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports the `post_title` and `post_content` search columns together.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_post_title_and_post_content_search_columns() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array( 'post_title', 'post_content' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid1, self::$pid2, self::$pid3 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports the `post_excerpt` and `post_content` search columns together.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_post_excerpt_and_post_content_search_columns() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array( 'post_excerpt', 'post_content' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid1, self::$pid2, self::$pid3 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports the `post_title`, `post_excerpt` and `post_content` search columns together.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_post_title_and_post_excerpt_and_post_content_search_columns() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array( 'post_title', 'post_excerpt', 'post_content' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid1, self::$pid2, self::$pid3 ), $q->posts );
+ }
+
+ /**
+ * Tests that search uses default search columns when using a non-existing search column.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_use_default_search_columns_when_using_non_existing_search_column() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array( 'post_non_existing_column' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertStringContainsString( 'post_title', $q->request, 'SQL request should contain post_title string.' );
+ $this->assertStringContainsString( 'post_excerpt', $q->request, 'SQL request should contain post_excerpt string.' );
+ $this->assertStringContainsString( 'post_content', $q->request, 'SQL request should contain post_content string.' );
+ $this->assertSame( array( self::$pid1, self::$pid2, self::$pid3 ), $q->posts, 'Query results should be equal to the set.' );
+ }
+
+ /**
+ * Tests that search ignores a non-existing search column when used together with a supported one.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_ignore_non_existing_search_column_when_used_with_supported_one() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'search_columns' => array( 'post_title', 'post_non_existing_column' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid1 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports search columns when searching multiple terms.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_search_columns_when_searching_multiple_terms() {
+ $q = new WP_Query(
+ array(
+ 's' => 'foo bar',
+ 'search_columns' => array( 'post_content' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid2, self::$pid3 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports search columns when searching for a sentence.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_search_columns_when_sentence_true() {
+ $q = new WP_Query(
+ array(
+ 's' => 'bar foo',
+ 'search_columns' => array( 'post_content' ),
+ 'sentence' => true,
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid3 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports search columns when searching for a sentence.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_search_columns_when_sentence_false() {
+ $q = new WP_Query(
+ array(
+ 's' => 'bar foo',
+ 'search_columns' => array( 'post_content' ),
+ 'sentence' => false,
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid2, self::$pid3 ), $q->posts );
+ }
+
+ /**
+ * Tests that search supports search columns when using term exclusion.
+ *
+ * @ticket 43867
+ */
+ public function test_s_should_support_search_columns_when_searching_with_term_exclusion() {
+ $q = new WP_Query(
+ array(
+ 's' => 'bar -baz',
+ 'search_columns' => array( 'post_excerpt', 'post_content' ),
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid2 ), $q->posts );
+ }
+
+ /**
+ * Tests that search columns is filterable with the `post_search_columns` filter.
+ *
+ * @ticket 43867
+ */
+ public function test_search_columns_should_be_filterable() {
+ add_filter( 'post_search_columns', array( $this, 'post_supported_search_column' ), 10, 3 );
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSame( array( self::$pid1 ), $q->posts );
+ }
+
+ /**
+ * Filter callback that sets a supported search column.
+ *
+ * @param string[] $search_columns Array of column names to be searched.
+ * @param string $search Text being searched.
+ * @param WP_Query $wp_query The current WP_Query instance.
+ * @return string[] $search_columns Array of column names to be searched.
+ */
+ public function post_supported_search_column( $search_columns, $search, $wp_query ) {
+ $search_columns = array( 'post_title' );
+ return $search_columns;
+ }
+
+ /**
+ * Tests that search columns ignores non-supported search columns from the `post_search_columns` filter.
+ *
+ * @ticket 43867
+ */
+ public function test_search_columns_should_not_be_filterable_with_non_supported_search_columns() {
+ add_filter( 'post_search_columns', array( $this, 'post_non_supported_search_column' ), 10, 3 );
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertStringNotContainsString( 'post_name', $q->request, "SQL request shouldn't contain post_name string." );
+ $this->assertSame( array( self::$pid1, self::$pid2, self::$pid3 ), $q->posts, 'Query results should be equal to the set.' );
+ }
+
+ /**
+ * Filter callback that sets an existing but non-supported search column.
+ *
+ * @param string[] $search_columns Array of column names to be searched.
+ * @param string $search Text being searched.
+ * @param WP_Query $wp_query The current WP_Query instance.
+ * @return string[] $search_columns Array of column names to be searched.
+ */
+ public function post_non_supported_search_column( $search_columns, $search, $wp_query ) {
+ $search_columns = array( 'post_name' );
+ return $search_columns;
+ }
+
+ /**
+ * Tests that search columns ignores non-existing search columns from the `post_search_columns` filter.
+ *
+ * @ticket 43867
+ */
+ public function test_search_columns_should_not_be_filterable_with_non_existing_search_column() {
+ add_filter( 'post_search_columns', array( $this, 'post_non_existing_search_column' ), 10, 3 );
+ $q = new WP_Query(
+ array(
+ 's' => 'foo',
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertStringNotContainsString( 'post_non_existing_column', $q->request, "SQL request shouldn't contain post_non_existing_column string." );
+ $this->assertSame( array( self::$pid1, self::$pid2, self::$pid3 ), $q->posts, 'Query results should be equal to the set.' );
+ }
+
+ /**
+ * Filter callback that sets a non-existing search column.
+ *
+ * @param string[] $search_columns Array of column names to be searched.
+ * @param string $search Text being searched.
+ * @param WP_Query $wp_query The current WP_Query instance.
+ * @return string[] $search_columns Array of column names to be searched.
+ */
+ public function post_non_existing_search_column( $search_columns, $search, $wp_query ) {
+ $search_columns = array( 'post_non_existing_column' );
+ return $search_columns;
+ }
+
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/query/searchColumns.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsrestapirestattachmentscontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-attachments-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-attachments-controller.php 2023-02-07 07:14:11 UTC (rev 55247)
+++ trunk/tests/phpunit/tests/rest-api/rest-attachments-controller.php 2023-02-07 08:53:01 UTC (rev 55248)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -230,6 +230,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'parent_exclude',
</span><span class="cx" style="display: block; padding: 0 10px"> 'per_page',
</span><span class="cx" style="display: block; padding: 0 10px"> 'search',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'search_columns',
</ins><span class="cx" style="display: block; padding: 0 10px"> 'slug',
</span><span class="cx" style="display: block; padding: 0 10px"> 'status',
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestpagescontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-pages-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-pages-controller.php 2023-02-07 07:14:11 UTC (rev 55247)
+++ trunk/tests/phpunit/tests/rest-api/rest-pages-controller.php 2023-02-07 08:53:01 UTC (rev 55248)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -86,6 +86,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'parent_exclude',
</span><span class="cx" style="display: block; padding: 0 10px"> 'per_page',
</span><span class="cx" style="display: block; padding: 0 10px"> 'search',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'search_columns',
</ins><span class="cx" style="display: block; padding: 0 10px"> 'slug',
</span><span class="cx" style="display: block; padding: 0 10px"> 'status',
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestpostscontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-posts-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-posts-controller.php 2023-02-07 07:14:11 UTC (rev 55247)
+++ trunk/tests/phpunit/tests/rest-api/rest-posts-controller.php 2023-02-07 08:53:01 UTC (rev 55248)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -204,6 +204,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'page',
</span><span class="cx" style="display: block; padding: 0 10px"> 'per_page',
</span><span class="cx" style="display: block; padding: 0 10px"> 'search',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'search_columns',
</ins><span class="cx" style="display: block; padding: 0 10px"> 'slug',
</span><span class="cx" style="display: block; padding: 0 10px"> 'status',
</span><span class="cx" style="display: block; padding: 0 10px"> 'sticky',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1528,6 +1529,38 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Tests that Rest Post controller supports search columns.
+ *
+ * @ticket 43867
+ * @covers WP_REST_Posts_Controller::get_items
+ */
+ public function test_get_items_with_custom_search_columns() {
+ $id1 = self::factory()->post->create(
+ array(
+ 'post_title' => 'Title contain foo and bar',
+ 'post_content' => 'Content contain bar',
+ 'post_excerpt' => 'Excerpt contain baz',
+ )
+ );
+ $id2 = self::factory()->post->create(
+ array(
+ 'post_title' => 'Title contain baz',
+ 'post_content' => 'Content contain foo and bar',
+ 'post_excerpt' => 'Excerpt contain foo, bar and baz',
+ )
+ );
+
+ $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
+ $request->set_param( 'search', 'foo bar' );
+ $request->set_param( 'search_columns', array( 'post_title' ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status(), 'Response should have a status code 200.' );
+ $data = $response->get_data();
+ $this->assertCount( 1, $data, 'Response should contain one result.' );
+ $this->assertSame( $id1, $data[0]['id'], 'Result should match expected value.' );
+ }
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * @ticket 55592
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @covers WP_REST_Posts_Controller::get_items
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapiwpRestMenuItemsControllerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php 2023-02-07 07:14:11 UTC (rev 55247)
+++ trunk/tests/phpunit/tests/rest-api/wpRestMenuItemsController.php 2023-02-07 08:53:01 UTC (rev 55248)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -147,6 +147,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'page', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'per_page', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'search', $properties );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->assertArrayHasKey( 'search_columns', $properties );
</ins><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'slug', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'status', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsqunitfixtureswpapigeneratedjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/qunit/fixtures/wp-api-generated.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/qunit/fixtures/wp-api-generated.js 2023-02-07 07:14:11 UTC (rev 55247)
+++ trunk/tests/qunit/fixtures/wp-api-generated.js 2023-02-07 08:53:01 UTC (rev 55248)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -395,6 +395,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> ],
</span><span class="cx" style="display: block; padding: 0 10px"> "required": false
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "search_columns": {
+ "default": [],
+ "description": "Array of column names to be searched.",
+ "type": "array",
+ "items": {
+ "enum": [
+ "post_title",
+ "post_content",
+ "post_excerpt"
+ ],
+ "type": "string"
+ },
+ "required": false
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "slug": {
</span><span class="cx" style="display: block; padding: 0 10px"> "description": "Limit result set to posts with one or more specific slugs.",
</span><span class="cx" style="display: block; padding: 0 10px"> "type": "array",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1752,6 +1766,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> "default": [],
</span><span class="cx" style="display: block; padding: 0 10px"> "required": false
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "search_columns": {
+ "default": [],
+ "description": "Array of column names to be searched.",
+ "type": "array",
+ "items": {
+ "enum": [
+ "post_title",
+ "post_content",
+ "post_excerpt"
+ ],
+ "type": "string"
+ },
+ "required": false
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "slug": {
</span><span class="cx" style="display: block; padding: 0 10px"> "description": "Limit result set to posts with one or more specific slugs.",
</span><span class="cx" style="display: block; padding: 0 10px"> "type": "array",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2833,6 +2861,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> "default": [],
</span><span class="cx" style="display: block; padding: 0 10px"> "required": false
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "search_columns": {
+ "default": [],
+ "description": "Array of column names to be searched.",
+ "type": "array",
+ "items": {
+ "enum": [
+ "post_title",
+ "post_content",
+ "post_excerpt"
+ ],
+ "type": "string"
+ },
+ "required": false
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "slug": {
</span><span class="cx" style="display: block; padding: 0 10px"> "description": "Limit result set to posts with one or more specific slugs.",
</span><span class="cx" style="display: block; padding: 0 10px"> "type": "array",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3543,6 +3585,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> ],
</span><span class="cx" style="display: block; padding: 0 10px"> "required": false
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "search_columns": {
+ "default": [],
+ "description": "Array of column names to be searched.",
+ "type": "array",
+ "items": {
+ "enum": [
+ "post_title",
+ "post_content",
+ "post_excerpt"
+ ],
+ "type": "string"
+ },
+ "required": false
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "slug": {
</span><span class="cx" style="display: block; padding: 0 10px"> "description": "Limit result set to posts with one or more specific slugs.",
</span><span class="cx" style="display: block; padding: 0 10px"> "type": "array",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4339,6 +4395,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> ],
</span><span class="cx" style="display: block; padding: 0 10px"> "required": false
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "search_columns": {
+ "default": [],
+ "description": "Array of column names to be searched.",
+ "type": "array",
+ "items": {
+ "enum": [
+ "post_title",
+ "post_content",
+ "post_excerpt"
+ ],
+ "type": "string"
+ },
+ "required": false
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "slug": {
</span><span class="cx" style="display: block; padding: 0 10px"> "description": "Limit result set to posts with one or more specific slugs.",
</span><span class="cx" style="display: block; padding: 0 10px"> "type": "array",
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6490,6 +6560,20 @@
</span><span class="cx" style="display: block; padding: 0 10px"> ],
</span><span class="cx" style="display: block; padding: 0 10px"> "required": false
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "search_columns": {
+ "default": [],
+ "description": "Array of column names to be searched.",
+ "type": "array",
+ "items": {
+ "enum": [
+ "post_title",
+ "post_content",
+ "post_excerpt"
+ ],
+ "type": "string"
+ },
+ "required": false
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "slug": {
</span><span class="cx" style="display: block; padding: 0 10px"> "description": "Limit result set to posts with one or more specific slugs.",
</span><span class="cx" style="display: block; padding: 0 10px"> "type": "array",
</span></span></pre>
</div>
</div>
</body>
</html>