<!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>