Ticket #7394: 7394.9.diff
File 7394.9.diff, 12.3 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. … … 1470 1478 if ( '' !== $qv['second'] ) $qv['second'] = absint($qv['second']); 1471 1479 if ( '' !== $qv['menu_order'] ) $qv['menu_order'] = absint($qv['menu_order']); 1472 1480 1481 // Fairly insane upper bound for search string lengths. 1482 if ( ! empty( $qv['s'] ) && strlen( $qv['s'] ) > 1600 ) 1483 $qv['s'] = ''; 1484 1473 1485 // Compat. Map subpost to attachment. 1474 1486 if ( '' != $qv['subpost'] ) 1475 1487 $qv['attachment'] = $qv['subpost']; … … 1895 1907 } 1896 1908 1897 1909 /** 1910 * Generate SQL for the WHERE clause based on passed search terms. 1911 * 1912 * @since 3.7.0 1913 * 1914 * @global type $wpdb 1915 * @param array $q Query variables. 1916 */ 1917 protected function parse_search( &$q ) { 1918 global $wpdb; 1919 1920 $search = ''; 1921 1922 // added slashes screw with quote grouping when done early, so done later 1923 $q['s'] = stripslashes( $q['s'] ); 1924 if ( empty( $_GET['s'] ) && $this->is_main_query() ) 1925 $q['s'] = urldecode( $q['s'] ); 1926 // there are no line breaks in <input /> fields 1927 $q['s'] = str_replace( array( "\r", "\n" ), '', $q['s'] ); 1928 $q['search_terms_count'] = 1; 1929 if ( ! empty( $q['sentence'] ) ) { 1930 $q['search_terms'] = array( $q['s'] ); 1931 } else { 1932 if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $q['s'], $matches ) ) { 1933 $q['search_terms_count'] = count( $matches[0] ); 1934 $q['search_terms'] = $this->parse_search_terms( $matches[0] ); 1935 // if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence 1936 if ( empty( $q['search_terms'] ) || count( $q['search_terms'] ) > 9 ) 1937 $q['search_terms'] = array( $q['s'] ); 1938 } else { 1939 $q['search_terms'] = array( $q['s'] ); 1940 } 1941 } 1942 1943 $n = ! empty( $q['exact'] ) ? '' : '%'; 1944 $searchand = ''; 1945 $q['search_orderby_title'] = array(); 1946 foreach ( $q['search_terms'] as $term ) { 1947 $term = like_escape( esc_sql( $term ) ); 1948 if ( $n ) 1949 $q['search_orderby_title'][] = "$wpdb->posts.post_title LIKE '%$term%'"; 1950 1951 $search .= "{$searchand}(($wpdb->posts.post_title LIKE '{$n}{$term}{$n}') OR ($wpdb->posts.post_content LIKE '{$n}{$term}{$n}'))"; 1952 $searchand = ' AND '; 1953 } 1954 1955 if ( ! empty( $search ) ) { 1956 $search = " AND ({$search}) "; 1957 if ( ! is_user_logged_in() ) 1958 $search .= " AND ($wpdb->posts.post_password = '') "; 1959 } 1960 1961 /** 1962 * Filter the search SQL that is used in the WHERE clause of WP_Query. 1963 * 1964 * @since 3.0.0 1965 * 1966 * @param string $search Search SQL for WHERE clause. 1967 * @param WP_Query $this The current WP_Query object. 1968 */ 1969 return apply_filters( 'posts_search', $search, $this ); 1970 } 1971 1972 /** 1973 * Check if the terms are suitable for searching. 1974 * 1975 * Uses an array of stopwords (terms) that are excluded from the separate 1976 * term matching when searching for posts. The list of English stopwords is 1977 * the approximate search engines list, and is translatable. 1978 * 1979 * @since 3.7.0 1980 * 1981 * @param array Terms to check. 1982 * @return array Terms that are not stopwords. 1983 */ 1984 protected function parse_search_terms( $terms ) { 1985 $strtolower = function_exists( 'mb_strtolower' ) ? 'mb_strtolower' : 'strtolower'; 1986 $checked = array(); 1987 1988 $stopwords = $this->get_search_stopwords(); 1989 1990 foreach ( $terms as $term ) { 1991 // keep before/after spaces when term is for exact match 1992 if ( preg_match( '/^".+"$/', $term ) ) 1993 $term = trim( $term, "\"'" ); 1994 else 1995 $term = trim( $term, "\"' " ); 1996 1997 // \p{L} matches a single letter that is not a Chinese, Japanese, etc. char 1998 if ( ! $term || preg_match( '/^\p{L}$/u', $term ) ) 1999 continue; 2000 2001 if ( in_array( call_user_func( $strtolower, $term ), $stopwords, true ) ) 2002 continue; 2003 2004 $checked[] = $term; 2005 } 2006 2007 return $checked; 2008 } 2009 2010 /** 2011 * Retrieve stopwords used when parsing search terms. 2012 * 2013 * @since 3.7.0 2014 * 2015 * @return array Stopwords. 2016 */ 2017 protected function get_search_stopwords() { 2018 if ( isset( $this->stopwords ) ) 2019 return $this->stopwords; 2020 2021 /* translators: This is a comma-separated list of very common words that should be excluded from a search, 2022 * like a, an, and the. These are usually called "stopwords". You should not simply translate these individual 2023 * words into your language. Instead, look for and provide commonly accepted stopwords in your language. 2024 */ 2025 $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', 2026 'Comma-separated list of search stopwords in your language' ) ); 2027 2028 foreach( $words as $word ) { 2029 $word = trim( $word, "\r\n\t " ); 2030 if ( $word ) 2031 $stopwords[] = $word; 2032 } 2033 2034 /** 2035 * Filter stopwords used when parsing search terms. 2036 * 2037 * @since 3.7.0 2038 * 2039 * @param array $stopwords Stopwords. 2040 */ 2041 $this->stopwords = apply_filters( 'wp_search_stopwords', $stopwords ); 2042 return $this->stopwords; 2043 } 2044 2045 /** 2046 * Generate SQL for the ORDER BY condition based on passed search terms. 2047 * 2048 * @global wpdb $wpdb 2049 * @param array $q Query variables. 2050 * @return string ORDER BY clause. 2051 */ 2052 protected function parse_search_order( &$q ) { 2053 global $wpdb; 2054 2055 $search_orderby = ''; 2056 2057 if ( $q['search_terms_count'] > 1 ) { 2058 $num_terms = count( $q['search_orderby_title'] ); 2059 $search_orderby_s = like_escape( esc_sql( $q['s'] ) ); 2060 2061 $search_orderby = '(CASE '; 2062 // sentence match in 'post_title' 2063 $search_orderby .= "WHEN $wpdb->posts.post_title LIKE '%{$search_orderby_s}%' THEN 1 "; 2064 2065 // sanity limit, sort as sentence when more than 6 terms 2066 // (few searches are longer than 6 terms and most titles are not) 2067 if ( $num_terms < 7 ) { 2068 // all words in title 2069 $search_orderby .= 'WHEN ' . implode( ' AND ', $q['search_orderby_title'] ) . ' THEN 2 '; 2070 // any word in title, not needed when $num_terms == 1 2071 if ( $num_terms > 1 ) 2072 $search_orderby .= 'WHEN ' . implode( ' OR ', $q['search_orderby_title'] ) . ' THEN 3 '; 2073 } 2074 2075 // sentence match in 'post_content' 2076 $search_orderby .= "WHEN $wpdb->posts.post_content LIKE '%{$search_orderby_s}%' THEN 4 "; 2077 $search_orderby .= 'ELSE 5 END)'; 2078 } else { 2079 // single word or sentence search 2080 $search_orderby = reset( $q['search_orderby_title'] ) . ' DESC'; 2081 } 2082 2083 return $search_orderby; 2084 } 2085 2086 /** 1898 2087 * Sets the 404 property and saves whether query is feed. 1899 2088 * 1900 2089 * @since 2.0.0 … … 2235 2424 } 2236 2425 } 2237 2426 2238 // 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 } 2427 // If a search pattern is specified, load the posts that match. 2428 if ( ! empty( $q['s'] ) ) 2429 $search = $this->parse_search( $q ); 2257 2430 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 2431 // Taxonomies 2269 2432 if ( !$this->is_singular ) { 2270 2433 $this->parse_tax_query( $q ); … … 2461 2624 $orderby .= " {$q['order']}"; 2462 2625 } 2463 2626 2627 // Order search results by relevance only when another "orderby" is not specified in the query. 2628 if ( ! empty( $q['s'] ) ) { 2629 $search_orderby = ''; 2630 if ( ! empty( $q['search_orderby_title'] ) && empty( $q['orderby'] ) ) 2631 $search_orderby = $this->parse_search_order( $q ); 2632 2633 /** 2634 * Filter the ORDER BY used when ordering results by relevance in a search. 2635 * 2636 * @since 3.7.0 2637 * 2638 * @param string $search_orderby The ORDER BY clause. 2639 * @param WP_Query $this The current WP_Query instance. 2640 */ 2641 $search_orderby = apply_filters( 'posts_search_orderby', $search_orderby, $this ); 2642 if ( $search_orderby ) 2643 $orderby = $orderby ? $search_orderby . ', ' . $orderby : $search_orderby; 2644 } 2645 2464 2646 if ( is_array( $post_type ) && count( $post_type ) > 1 ) { 2465 2647 $post_type_cap = 'multiple_post_type'; 2466 2648 } 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_terms_query_var() { 42 $terms = 'This is a search term'; 43 $query = new WP_Query( array( 's' => 'This is a search term' ) ); 44 $this->assertNotEquals( explode( ' ', $terms ), $query->get( 'search_terms' ) ); 45 $this->assertEquals( array( 'search', 'term' ), $query->get( 'search_terms' ) ); 46 } 47 48 function test_filter_stopwords() { 49 $terms = 'This is a search term'; 50 add_filter( 'wp_search_stopwords', array( $this, 'filter_wp_search_stopwords' ) ); 51 $query = new WP_Query( array( 's' => $terms ) ); 52 remove_filter( 'wp_search_stopwords', array( $this, 'filter_wp_search_stopwords' ) ); 53 54 $this->assertNotEquals( array( 'search', 'term' ), $query->get( 'search_terms' ) ); 55 $this->assertEquals( array( 'This', 'is', 'search', 'term' ), $query->get( 'search_terms' ) ); 56 } 57 58 function filter_wp_search_stopwords() { 59 return array(); 60 } 61 }