Make WordPress Core

Ticket #7394: 7394.7.diff

File 7394.7.diff, 12.4 KB (added by wonderboymusic, 11 years ago)
  • tests/phpunit/tests/query/search.php

     
     1<?php
     2/**
     3 *
     4 * @group query
     5 * @group search
     6 */
     7class Tests_Search_Results extends WP_UnitTestCase {
     8        protected $q;
     9        protected $post_type;
     10
     11        function setUp() {
     12                parent::setUp();
     13
     14                $this->post_type = rand_str( 12 );
     15                register_post_type( $this->post_type );
     16
     17                $this->q = new WP_Query();
     18        }
     19
     20        function tearDown() {
     21                parent::tearDown();
     22
     23                _unregister_post_type( $this->post_type );
     24                unset( $this->q );
     25        }
     26
     27        function get_search_results( $terms ) {
     28                $args = http_build_query( array( 's' => $terms, 'post_type' => $this->post_type ) );
     29                return $this->q->query( $args );
     30        }
     31
     32        function test_search_order_title_relevance() {
     33                foreach ( range( 1, 7 ) as $i )
     34                        $this->factory->post->create( array( 'post_content' => $i . rand_str() . ' about', 'post_type' => $this->post_type ) );
     35                $post_id = $this->factory->post->create( array( 'post_title' => 'About', 'post_type' => $this->post_type ) );
     36
     37                $posts = $this->get_search_results( 'About' );
     38                $this->assertEquals( $post_id, reset( $posts )->ID );
     39        }
     40
     41        function test_search_order_content_relevance() {
     42                $post_id1 = $this->factory->post->create( array( 'post_title' => 'About', 'post_type' => $this->post_type ) );
     43                $post_id2 = $this->factory->post->create( array( 'post_content' => 'About', 'post_type' => $this->post_type ) );
     44
     45                $posts1 = $this->get_search_results( 'About' );
     46                $this->assertEquals( $post_id1, reset( $posts1 )->ID );
     47
     48                add_filter( 'posts_search_orderby_on', array( $this, 'filter_posts_search_orderby_on' ) );
     49
     50                $posts2 = $this->get_search_results( 'About' );
     51                $this->assertEquals( $post_id2, reset( $posts2 )->ID );
     52
     53                remove_filter( 'posts_search_orderby_on', array( $this, 'filter_posts_search_orderby_on' ) );
     54        }
     55
     56        function filter_posts_search_orderby_on() {
     57                return array( 'post_content' );
     58        }
     59
     60        function test_search_terms_query_var() {
     61                $terms = 'This is a search term';
     62                $query = new WP_Query( array( 's' => 'This is a search term' ) );
     63                $this->assertNotEquals( explode( ' ', $terms ), $query->get( 'search_terms' ) );
     64                $this->assertEquals( array( 'search', 'term' ), $query->get( 'search_terms' ) );
     65        }
     66
     67        function test_filter_stopwords() {
     68                $terms = 'This is a search term';
     69                add_filter( 'wp_search_stopwords', array( $this, 'filter_wp_search_stopwords' ) );
     70                $query = new WP_Query( array( 's' => $terms ) );
     71                remove_filter( 'wp_search_stopwords', array( $this, 'filter_wp_search_stopwords' ) );
     72
     73                $this->assertNotEquals( array( 'search', 'term' ), $query->get( 'search_terms' ) );
     74                $this->assertEquals( array( 'This', 'is', 'search', 'term' ), $query->get( 'search_terms' ) );
     75        }
     76
     77        function filter_wp_search_stopwords() {
     78                return array();
     79        }
     80}
     81 No newline at end of file
  • src/wp-includes/functions.php

     
    37033703}
    37043704
    37053705/**
    3706  * Used internally to tidy up the search terms.
    3707  *
    3708  * @access private
    3709  * @since 2.9.0
    3710  *
    3711  * @param string $t
    3712  * @return string
    3713  */
    3714 function _search_terms_tidy($t) {
    3715         return trim($t, "\"'\n\r ");
    3716 }
    3717 
    3718 /**
    37193706 * Returns true.
    37203707 *
    37213708 * Useful for returning true to filters easily.
  • src/wp-includes/query.php

     
    12901290         var $thumbnails_cached = false;
    12911291
    12921292        /**
     1293         * Cached list of search stopwords.
     1294         *
     1295         * @since 3.7.0
     1296         * @var array
     1297         */
     1298        private $stopwords;
     1299
     1300        /**
    12931301         * Resets query flags to false.
    12941302         *
    12951303         * The query flags are what page info WordPress was able to figure out.
     
    18951903        }
    18961904
    18971905        /**
     1906         * Generate SQL for the WHERE clause based on passed search terms.
     1907         *
     1908         * @since 3.7.0
     1909         *
     1910         * @global type $wpdb
     1911         * @param array $q
     1912         * @param string $search
     1913         */
     1914        function parse_search( &$q, $search ) {
     1915                global $wpdb;
     1916
     1917                // added slashes screw with quote grouping when done early, so done later
     1918                $q['s'] = stripslashes( $q['s'] );
     1919                if ( empty( $_GET['s'] ) && $this->is_main_query() )
     1920                        $q['s'] = urldecode( $q['s'] );
     1921                // there are no line breaks in <input /> fields
     1922                $q['s'] = str_replace( array( "\r", "\n" ), '', $q['s'] );
     1923                $q['num_all_terms'] = 1;
     1924                if ( ! empty( $q['sentence'] ) ) {
     1925                        $q['search_terms'] = array( $q['s'] );
     1926                } else {
     1927                        if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $q['s'], $matches ) ) {
     1928                                $q['num_all_terms'] = count( $matches[0] );
     1929                                $q['search_terms'] = $this->parse_search_terms( $matches[0] );
     1930                                // if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence
     1931                                if ( empty( $q['search_terms'] ) || count( $q['search_terms'] ) > 9 )
     1932                                        $q['search_terms'] = array( $q['s'] );
     1933                        } else {
     1934                                $q['search_terms'] = array( $q['s'] );
     1935                        }
     1936                }
     1937
     1938                $n = ! empty( $q['exact'] ) ? '' : '%';
     1939                $searchand = '';
     1940                $q['search_orderby_title'] = array();
     1941                foreach ( $q['search_terms'] as $term ) {
     1942                        $term = esc_sql( like_escape( $term ) );
     1943                        if ( $n )
     1944                                $q['search_orderby_title'][] = "$wpdb->posts.post_title LIKE '%$term%'";
     1945
     1946                        $search .= "{$searchand}(($wpdb->posts.post_title LIKE '{$n}{$term}{$n}') OR ($wpdb->posts.post_content LIKE '{$n}{$term}{$n}'))";
     1947                        $searchand = ' AND ';
     1948                }
     1949
     1950                if ( ! empty( $search ) ) {
     1951                        $search = " AND ({$search}) ";
     1952                        if ( ! is_user_logged_in() )
     1953                                $search .= " AND ($wpdb->posts.post_password = '') ";
     1954                }
     1955
     1956                // Allow plugins to contextually add/remove/modify the search section of the database query
     1957                return apply_filters( 'posts_search', $search, $this );
     1958        }
     1959
     1960        /**
     1961         * Check if the terms are suitable for searching.
     1962         *
     1963         * Includes array of stopwords (terms) that are excluded from the separate term matching when searching for posts.
     1964         * The list of English stopwords is the approximate search engines list. MySQL has a much longer default list of full-text stopwords.
     1965         *
     1966         * @since 3.7.0
     1967         *
     1968         * @param array Terms to check
     1969         * @return array
     1970         */
     1971        protected function parse_search_terms( $terms ) {
     1972                $strtolower_func = function_exists( 'mb_strtolower' ) ? 'mb_strtolower' : 'strtolower';
     1973                $checked = $stopwords = array();
     1974
     1975                if ( ! isset( $this->stopwords ) ) {
     1976                        // @todo translator comment
     1977                        $this->stopwords = explode( ',', _x( 'about,an,are,as,at,be,by,com,for,from,how,in,is,it,of,on,or,that,the,this,to,was,what,when,where,who,will,with,the,www',
     1978                                'Comma separated list of common words to exclude when searching (stopwords).' ) );
     1979
     1980                        $this->stopwords = array_filter( array_map( 'trim', $this->stopwords ) );
     1981                        $this->stopwords = apply_filters( 'wp_search_stopwords', $this->stopwords );
     1982                }
     1983
     1984                foreach ( $terms as $term ) {
     1985                        // keep before/after spaces when term is for exact match
     1986                        if ( preg_match( '/^".+"$/', $term ) )
     1987                                $term = trim( $term, "\"'" );
     1988                        else
     1989                                $term = trim( $term, "\"' " );
     1990
     1991                        // \p{L} matches a single letter that is not a Chinese, Japanese, etc. char
     1992                        if ( ! $term || preg_match( '/^\p{L}$/u', $term ) )
     1993                                continue;
     1994
     1995                        if ( in_array( $strtolower_func( $term ), $this->stopwords, true ) )
     1996                                continue;
     1997
     1998                        $checked[] = $term;
     1999                }
     2000
     2001                return $checked;
     2002        }
     2003
     2004        /**
     2005         * Generate SQL for the ORDER BY condition based on passed search terms.
     2006         *
     2007         * @global wpdb $wpdb
     2008         * @param array $q
     2009         * @return string
     2010         */
     2011        function parse_search_order( &$q ) {
     2012                global $wpdb;
     2013
     2014                $search_orderby = '';
     2015                // Allow plugins to override sorting on 'post_title' and/or 'post_content'.
     2016                // Passing an empty array would disable sorting.
     2017                $search_orderby_on = apply_filters( 'posts_search_orderby_on', array( 'post_title', 'post_content' ), $this );
     2018
     2019                if ( ! empty( $search_orderby_on ) ) {
     2020                        if ( $q['num_all_terms'] > 1 ) {
     2021                                $num_terms = count( $q['search_orderby_title'] );
     2022                                $search_orderby_s = esc_sql( like_escape( $q['s'] ) );
     2023
     2024                                if ( in_array( 'post_title', $search_orderby_on, true ) ) {
     2025                                        $search_orderby = '(CASE ';
     2026                                        // sentence match in 'post_title'
     2027                                        $search_orderby .= "WHEN $wpdb->posts.post_title LIKE '%{$search_orderby_s}%' THEN 1 ";
     2028
     2029                                        // sanity limit, sort as sentence when more than 6 terms
     2030                                        // (few searches are longer than 6 terms and most titles are not)
     2031                                        if ( $num_terms < 7 ) {
     2032                                                // all words in title
     2033                                                $search_orderby .= 'WHEN ' . implode( ' AND ', $q['search_orderby_title'] ) . ' THEN 2 ';
     2034                                                // any word in title, not needed when $num_terms == 1
     2035                                                if ( $num_terms > 1 )
     2036                                                        $search_orderby .= 'WHEN ' . implode( ' OR ', $q['search_orderby_title'] ) . ' THEN 3 ';
     2037                                        }
     2038
     2039                                        // sentence match in 'post_content'
     2040                                        if ( in_array( 'post_content', $search_orderby_on, true ) )
     2041                                                $search_orderby .= "WHEN $wpdb->posts.post_content LIKE '%{$search_orderby_s}%' THEN 4 ";
     2042
     2043                                        $search_orderby .= 'ELSE 5 END)';
     2044                                } elseif ( in_array( 'post_content', $search_orderby_on, true ) ) {
     2045                                        // Not sorting on 'post_title', order by sentence matches in 'post_content'
     2046                                        $search_orderby = "$wpdb->posts.post_content LIKE '%{$search_orderby_s}%' DESC";
     2047                                }
     2048                        } elseif ( in_array( 'post_title', $search_orderby_on, true ) ) {
     2049                                // single word or sentence search
     2050                                $search_orderby = reset( $q['search_orderby_title'] ) . ' DESC';
     2051                        }
     2052                }
     2053                // Allow plugins to add/remove/modify the 'order by' for the search section of the database query
     2054                return apply_filters( 'posts_search_orderby', $search_orderby, $this );
     2055        }
     2056
     2057        /**
    18982058         * Sets the 404 property and saves whether query is feed.
    18992059         *
    19002060         * @since 2.0.0
     
    22362396                }
    22372397
    22382398                // If a search pattern is specified, load the posts that match
    2239                 if ( !empty($q['s']) ) {
    2240                         // added slashes screw with quote grouping when done early, so done later
    2241                         $q['s'] = stripslashes($q['s']);
    2242                         if ( empty( $_GET['s'] ) && $this->is_main_query() )
    2243                                 $q['s'] = urldecode($q['s']);
    2244                         if ( !empty($q['sentence']) ) {
    2245                                 $q['search_terms'] = array($q['s']);
    2246                         } else {
    2247                                 preg_match_all('/".*?("|$)|((?<=[\r\n\t ",+])|^)[^\r\n\t ",+]+/', $q['s'], $matches);
    2248                                 $q['search_terms'] = array_map('_search_terms_tidy', $matches[0]);
    2249                         }
    2250                         $n = !empty($q['exact']) ? '' : '%';
    2251                         $searchand = '';
    2252                         foreach( (array) $q['search_terms'] as $term ) {
    2253                                 $term = esc_sql( like_escape( $term ) );
    2254                                 $search .= "{$searchand}(($wpdb->posts.post_title LIKE '{$n}{$term}{$n}') OR ($wpdb->posts.post_content LIKE '{$n}{$term}{$n}'))";
    2255                                 $searchand = ' AND ';
    2256                         }
     2399                // Sanity check: search string shouldn't be more than 1600 characters.
     2400                // See ticket #21688 for more info.
     2401                if ( ! empty( $q['s'] ) && strlen( $q['s'] ) < 1600 )
     2402                        $search = $this->parse_search( $q, $search );
    22572403
    2258                         if ( !empty($search) ) {
    2259                                 $search = " AND ({$search}) ";
    2260                                 if ( !is_user_logged_in() )
    2261                                         $search .= " AND ($wpdb->posts.post_password = '') ";
    2262                         }
    2263                 }
    2264 
    2265                 // Allow plugins to contextually add/remove/modify the search section of the database query
    2266                 $search = apply_filters_ref_array('posts_search', array( $search, &$this ) );
    2267 
    22682404                // Taxonomies
    22692405                if ( !$this->is_singular ) {
    22702406                        $this->parse_tax_query( $q );
     
    24612597                                $orderby .= " {$q['order']}";
    24622598                }
    24632599
     2600                // Order search results by relevance when another "orderby" is not specified in the query
     2601                if ( ! empty( $q['search_orderby_title'] ) && empty( $q['orderby'] ) ) {
     2602                        $search_orderby = $this->parse_search_order( $q );
     2603                        if ( $search_orderby )
     2604                                $orderby = $orderby ? $search_orderby . ', ' . $orderby : $search_orderby;
     2605                }
     2606
    24642607                if ( is_array( $post_type ) && count( $post_type ) > 1 ) {
    24652608                        $post_type_cap = 'multiple_post_type';
    24662609                } else {
  • src/wp-includes/deprecated.php

     
    33173317function _save_post_hook() {}
    33183318
    33193319/**
     3320 * Formerly used internally to tidy up the search terms.
     3321 *
     3322 * @access private
     3323 * @since 2.9.0
     3324 * @deprecated 3.7.0
     3325 */
     3326function _search_terms_tidy( $t ) {
     3327        _deprecated_function( __FUNCTION__, '3.5', '' );
     3328        return trim( $t, "\"'\n\r " );
     3329}
     3330
     3331/**
    33203332 * Check if the installed version of GD supports particular image type
    33213333 *
    33223334 * @since 2.9.0