Ticket #7394: 7394.5.diff
File 7394.5.diff, 12.2 KB (added by , 11 years ago) |
---|
-
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 -
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. … … 4042 4029 } 4043 4030 4044 4031 /** 4032 * Check if the terms are suitable for searching. 4033 * 4034 * Includes array of stopwords (terms) that are excluded from the separate term matching when searching for posts. 4035 * The list of English stopwords is the approximate search engines list. MySQL has a much longer default list of full-text stopwords. 4036 * 4037 * @since 3.7.0 4038 * @access private 4039 * 4040 * @param array Terms to check 4041 * @return array 4042 */ 4043 function _check_search_terms( $terms ) { 4044 $strtolower_func = function_exists( 'mb_strtolower' ) ? 'mb_strtolower' : 'strtolower'; 4045 $checked = $stopwords = array(); 4046 4047 $_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,the,www', 'Comma separated list of common words to exclude when searching (stopwords).' ) ); 4048 4049 foreach( $_words as $word ) { 4050 $word = trim( $word, "\r\n\t " ); 4051 if ( ! $word ) 4052 continue; 4053 $stopwords[] = $word; 4054 } 4055 4056 $stopwords = apply_filters( 'wp_search_stopwords', $stopwords ); 4057 4058 foreach ( $terms as $term ) { 4059 // keep before/after spaces when term is for exact match 4060 if ( preg_match( '/^".+"$/', $term ) ) 4061 $term = trim( $term, "\"'" ); 4062 else 4063 $term = trim( $term, "\"' " ); 4064 4065 // \p{L} matches a single letter that is not a Chinese, Japanese, etc. char 4066 if ( ! $term || preg_match( '/^\p{L}$/u', $term ) ) 4067 continue; 4068 4069 if ( in_array( $strtolower_func( $term ), $stopwords, true ) ) 4070 continue; 4071 4072 $checked[] = $term; 4073 } 4074 4075 return $checked; 4076 } 4077 4078 /** 4045 4079 * Load the auth check for monitoring whether the user is still logged in. 4046 4080 * 4047 4081 * Can be disabled with remove_action( 'admin_enqueue_scripts', 'wp_auth_check_load' ); -
src/wp-includes/query.php
1889 1889 } 1890 1890 1891 1891 /** 1892 * Generate SQL for the WHERE clause based on passed search terms. 1893 * 1894 * @since 3.7.0 1895 * 1896 * @global type $wpdb 1897 * @param array $q 1898 * @param string $search 1899 */ 1900 function parse_search( &$q, $search ) { 1901 global $wpdb; 1902 1903 // added slashes screw with quote grouping when done early, so done later 1904 $q['s'] = stripslashes( $q['s'] ); 1905 if ( empty( $_GET['s'] ) && $this->is_main_query() ) 1906 $q['s'] = urldecode( $q['s'] ); 1907 // there are no line breaks in <input /> fields 1908 $q['s'] = str_replace( array( "\r", "\n" ), '', $q['s'] ); 1909 $q['num_all_terms'] = 1; 1910 if ( ! empty( $q['sentence'] ) ) { 1911 $q['search_terms'] = array( $q['s'] ); 1912 } else { 1913 if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $q['s'], $matches ) ) { 1914 $q['num_all_terms'] = count( $matches[0] ); 1915 $q['search_terms'] = _check_search_terms( $matches[0] ); 1916 // if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence 1917 if ( empty( $q['search_terms'] ) || count( $q['search_terms'] ) > 9 ) 1918 $q['search_terms'] = array( $q['s'] ); 1919 } else { 1920 $q['search_terms'] = array( $q['s'] ); 1921 } 1922 } 1923 1924 $n = ! empty( $q['exact'] ) ? '' : '%'; 1925 $searchand = ''; 1926 $q['search_orderby_title'] = array(); 1927 foreach ( $q['search_terms'] as $term ) { 1928 $term = esc_sql( like_escape( $term ) ); 1929 if ( $n ) 1930 $q['search_orderby_title'][] = "$wpdb->posts.post_title LIKE '%$term%'"; 1931 1932 $search .= "{$searchand}(($wpdb->posts.post_title LIKE '{$n}{$term}{$n}') OR ($wpdb->posts.post_content LIKE '{$n}{$term}{$n}'))"; 1933 $searchand = ' AND '; 1934 } 1935 1936 if ( ! empty( $search ) ) { 1937 $search = " AND ({$search}) "; 1938 if ( ! is_user_logged_in() ) 1939 $search .= " AND ($wpdb->posts.post_password = '') "; 1940 } 1941 1942 // Allow plugins to contextually add/remove/modify the search section of the database query 1943 return apply_filters( 'posts_search', $search, $this ); 1944 } 1945 1946 /** 1947 * Generate SQL for the ORDER BY condition based on passed search terms. 1948 * 1949 * @global wpdb $wpdb 1950 * @param array $q 1951 * @return string 1952 */ 1953 function parse_search_order( &$q ) { 1954 global $wpdb; 1955 1956 $search_orderby = ''; 1957 // Allow plugins to override sorting on 'post_title' and/or 'post_content'. 1958 // Passing an empty array would disable sorting. 1959 $search_orderby_on = apply_filters( 'posts_search_orderby_on', array( 'post_title', 'post_content' ), $this ); 1960 1961 if ( ! empty( $search_orderby_on ) ) { 1962 if ( $q['num_all_terms'] > 1 ) { 1963 $num_terms = count( $q['search_orderby_title'] ); 1964 $search_orderby_s = esc_sql( like_escape( $q['s'] ) ); 1965 1966 if ( in_array( 'post_title', $search_orderby_on, true ) ) { 1967 $search_orderby = '(CASE '; 1968 // sentence match in 'post_title' 1969 $search_orderby .= "WHEN $wpdb->posts.post_title LIKE '%{$search_orderby_s}%' THEN 1 "; 1970 1971 // sanity limit, sort as sentence when more than 6 terms 1972 // (few searches are longer than 6 terms and most titles are not) 1973 if ( $num_terms < 7 ) { 1974 // all words in title 1975 $search_orderby .= 'WHEN ' . implode( ' AND ', $q['search_orderby_title'] ) . ' THEN 2 '; 1976 // any word in title, not needed when $num_terms == 1 1977 if ( $num_terms > 1 ) 1978 $search_orderby .= 'WHEN ' . implode( ' OR ', $q['search_orderby_title'] ) . ' THEN 3 '; 1979 } 1980 1981 // sentence match in 'post_content' 1982 if ( in_array( 'post_content', $search_orderby_on, true ) ) 1983 $search_orderby .= "WHEN $wpdb->posts.post_content LIKE '%{$search_orderby_s}%' THEN 4 "; 1984 1985 $search_orderby .= 'ELSE 5 END)'; 1986 } elseif ( in_array( 'post_content', $search_orderby_on, true ) ) { 1987 // Not sorting on 'post_title', order by sentence matches in 'post_content' 1988 $search_orderby = "$wpdb->posts.post_content LIKE '%{$search_orderby_s}%' DESC"; 1989 } 1990 } elseif ( in_array( 'post_title', $search_orderby_on, true ) ) { 1991 // single word or sentence search 1992 $search_orderby = reset( $q['search_orderby_title'] ) . ' DESC'; 1993 } 1994 } 1995 // Allow plugins to add/remove/modify the 'order by' for the search section of the database query 1996 return apply_filters( 'posts_search_orderby', $search_orderby, $this ); 1997 } 1998 1999 /** 1892 2000 * Sets the 404 property and saves whether query is feed. 1893 2001 * 1894 2002 * @since 2.0.0 … … 2230 2338 } 2231 2339 2232 2340 // If a search pattern is specified, load the posts that match 2233 if ( !empty($q['s']) ) { 2234 // added slashes screw with quote grouping when done early, so done later 2235 $q['s'] = stripslashes($q['s']); 2236 if ( empty( $_GET['s'] ) && $this->is_main_query() ) 2237 $q['s'] = urldecode($q['s']); 2238 if ( !empty($q['sentence']) ) { 2239 $q['search_terms'] = array($q['s']); 2240 } else { 2241 preg_match_all('/".*?("|$)|((?<=[\r\n\t ",+])|^)[^\r\n\t ",+]+/', $q['s'], $matches); 2242 $q['search_terms'] = array_map('_search_terms_tidy', $matches[0]); 2243 } 2244 $n = !empty($q['exact']) ? '' : '%'; 2245 $searchand = ''; 2246 foreach( (array) $q['search_terms'] as $term ) { 2247 $term = esc_sql( like_escape( $term ) ); 2248 $search .= "{$searchand}(($wpdb->posts.post_title LIKE '{$n}{$term}{$n}') OR ($wpdb->posts.post_content LIKE '{$n}{$term}{$n}'))"; 2249 $searchand = ' AND '; 2250 } 2341 // Sanity check: search string shouldn't be more than 1600 characters. 2342 // See ticket #21688 for more info. 2343 if ( ! empty( $q['s'] ) && strlen( $q['s'] ) < 1600 ) 2344 $search = $this->parse_search( $q, $search ); 2251 2345 2252 if ( !empty($search) ) {2253 $search = " AND ({$search}) ";2254 if ( !is_user_logged_in() )2255 $search .= " AND ($wpdb->posts.post_password = '') ";2256 }2257 }2258 2259 // Allow plugins to contextually add/remove/modify the search section of the database query2260 $search = apply_filters_ref_array('posts_search', array( $search, &$this ) );2261 2262 2346 // Taxonomies 2263 2347 if ( !$this->is_singular ) { 2264 2348 $this->parse_tax_query( $q ); … … 2455 2539 $orderby .= " {$q['order']}"; 2456 2540 } 2457 2541 2542 // Order search results by relevance when another "orderby" is not specified in the query 2543 if ( ! empty( $q['search_orderby_title'] ) && empty( $q['orderby'] ) ) { 2544 $search_orderby = $this->parse_search_order( $q ); 2545 if ( $search_orderby ) 2546 $orderby = $orderby ? $search_orderby . ', ' . $orderby : $search_orderby; 2547 } 2548 2458 2549 if ( is_array( $post_type ) && count( $post_type ) > 1 ) { 2459 2550 $post_type_cap = 'multiple_post_type'; 2460 2551 } else { -
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