Ticket #7394: 7394.8.diff
File 7394.8.diff, 12.7 KB (added by , 11 years ago) |
---|
-
src/wp-includes/deprecated.php
3317 3317 function _save_post_hook() {} 3318 3318 3319 3319 /** 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 */ 3326 function _search_terms_tidy( $t ) { 3327 _deprecated_function( __FUNCTION__, '3.5', '' ); 3328 return trim( $t, "\"'\n\r " ); 3329 } 3330 3331 /** 3320 3332 * Check if the installed version of GD supports particular image type 3321 3333 * 3322 3334 * @since 2.9.0 -
src/wp-includes/functions.php
3703 3703 } 3704 3704 3705 3705 /** 3706 * Used internally to tidy up the search terms.3707 *3708 * @access private3709 * @since 2.9.03710 *3711 * @param string $t3712 * @return string3713 */3714 function _search_terms_tidy($t) {3715 return trim($t, "\"'\n\r ");3716 }3717 3718 /**3719 3706 * Returns true. 3720 3707 * 3721 3708 * Useful for returning true to filters easily. -
src/wp-includes/query.php
1290 1290 var $thumbnails_cached = false; 1291 1291 1292 1292 /** 1293 * Cached list of search stopwords. 1294 * 1295 * @since 3.7.0 1296 * @var array 1297 */ 1298 private $stopwords; 1299 1300 /** 1293 1301 * Resets query flags to false. 1294 1302 * 1295 1303 * The query flags are what page info WordPress was able to figure out. … … 1895 1903 } 1896 1904 1897 1905 /** 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 $_words = 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,www', 1978 'Comma separated list of common words to exclude when searching (stopwords).' ) ); 1979 1980 foreach( $_words as $word ) { 1981 $word = trim( $word, "\r\n\t " ); 1982 1983 if ( ! $word ) 1984 continue; 1985 1986 $stopwords[] = $word; 1987 } 1988 1989 $this->stopwords = apply_filters( 'wp_search_stopwords', $stopwords ); 1990 } 1991 1992 foreach ( $terms as $term ) { 1993 // keep before/after spaces when term is for exact match 1994 if ( preg_match( '/^".+"$/', $term ) ) 1995 $term = trim( $term, "\"'" ); 1996 else 1997 $term = trim( $term, "\"' " ); 1998 1999 // \p{L} matches a single letter that is not a Chinese, Japanese, etc. char 2000 if ( ! $term || preg_match( '/^\p{L}$/u', $term ) ) 2001 continue; 2002 2003 if ( in_array( $strtolower_func( $term ), $this->stopwords, true ) ) 2004 continue; 2005 2006 $checked[] = $term; 2007 } 2008 2009 return $checked; 2010 } 2011 2012 /** 2013 * Generate SQL for the ORDER BY condition based on passed search terms. 2014 * 2015 * @global wpdb $wpdb 2016 * @param array $q 2017 * @return string 2018 */ 2019 function parse_search_order( &$q ) { 2020 global $wpdb; 2021 2022 $search_orderby = ''; 2023 // Allow plugins to override sorting on 'post_title' and/or 'post_content'. 2024 // Passing an empty array would disable sorting. 2025 $search_orderby_on = apply_filters( 'posts_search_orderby_on', array( 'post_title', 'post_content' ), $this ); 2026 2027 if ( ! empty( $search_orderby_on ) ) { 2028 if ( $q['num_all_terms'] > 1 ) { 2029 $num_terms = count( $q['search_orderby_title'] ); 2030 $search_orderby_s = esc_sql( like_escape( $q['s'] ) ); 2031 2032 if ( in_array( 'post_title', $search_orderby_on, true ) ) { 2033 $search_orderby = '(CASE '; 2034 // sentence match in 'post_title' 2035 $search_orderby .= "WHEN $wpdb->posts.post_title LIKE '%{$search_orderby_s}%' THEN 1 "; 2036 2037 // sanity limit, sort as sentence when more than 6 terms 2038 // (few searches are longer than 6 terms and most titles are not) 2039 if ( $num_terms < 7 ) { 2040 // all words in title 2041 $search_orderby .= 'WHEN ' . implode( ' AND ', $q['search_orderby_title'] ) . ' THEN 2 '; 2042 // any word in title, not needed when $num_terms == 1 2043 if ( $num_terms > 1 ) 2044 $search_orderby .= 'WHEN ' . implode( ' OR ', $q['search_orderby_title'] ) . ' THEN 3 '; 2045 } 2046 2047 // sentence match in 'post_content' 2048 if ( in_array( 'post_content', $search_orderby_on, true ) ) 2049 $search_orderby .= "WHEN $wpdb->posts.post_content LIKE '%{$search_orderby_s}%' THEN 4 "; 2050 2051 $search_orderby .= 'ELSE 5 END)'; 2052 } elseif ( in_array( 'post_content', $search_orderby_on, true ) ) { 2053 // Not sorting on 'post_title', order by sentence matches in 'post_content' 2054 $search_orderby = "$wpdb->posts.post_content LIKE '%{$search_orderby_s}%' DESC"; 2055 } 2056 } elseif ( in_array( 'post_title', $search_orderby_on, true ) ) { 2057 // single word or sentence search 2058 $search_orderby = reset( $q['search_orderby_title'] ) . ' DESC'; 2059 } 2060 } 2061 // Allow plugins to add/remove/modify the 'order by' for the search section of the database query 2062 return apply_filters( 'posts_search_orderby', $search_orderby, $this ); 2063 } 2064 2065 /** 1898 2066 * Sets the 404 property and saves whether query is feed. 1899 2067 * 1900 2068 * @since 2.0.0 … … 2236 2404 } 2237 2405 2238 2406 // 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 } 2407 // Sanity check: search string shouldn't be more than 1600 characters. 2408 // See ticket #21688 for more info. 2409 if ( ! empty( $q['s'] ) && strlen( $q['s'] ) < 1600 ) 2410 $search = $this->parse_search( $q, $search ); 2257 2411 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 query2266 $search = apply_filters_ref_array('posts_search', array( $search, &$this ) );2267 2268 2412 // Taxonomies 2269 2413 if ( !$this->is_singular ) { 2270 2414 $this->parse_tax_query( $q ); … … 2461 2605 $orderby .= " {$q['order']}"; 2462 2606 } 2463 2607 2608 // Order search results by relevance when another "orderby" is not specified in the query 2609 if ( ! empty( $q['search_orderby_title'] ) && empty( $q['orderby'] ) ) { 2610 $search_orderby = $this->parse_search_order( $q ); 2611 if ( $search_orderby ) 2612 $orderby = $orderby ? $search_orderby . ', ' . $orderby : $search_orderby; 2613 } 2614 2464 2615 if ( is_array( $post_type ) && count( $post_type ) > 1 ) { 2465 2616 $post_type_cap = 'multiple_post_type'; 2466 2617 } else { -
tests/phpunit/tests/query/search.php
1 <?php 2 /** 3 * 4 * @group query 5 * @group search 6 */ 7 class 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