Make WordPress Core

Ticket #10667: search.php

File search.php, 34.3 KB (added by jshreve, 15 years ago)

SearchAPI Core file. (to be placed in wp-includes/)

Line 
1<?php
2 /**
3 * WordPress API for searching.
4 *
5 * @package WordPress
6 * @subpackage Search
7 */
8 
9 /**
10 * Search API
11 * An API that is loaded for search related actions
12 * The class contains various functions for outputing to WordPress and doing the necessary legwork for common search functions
13 * (pagination, trimming content for display, options and filtering, etc)
14 */
15class WP_Search
16{
17
18        var $plugin = NULL; //Reference to the active search plugin
19        var $flags = array(); // Array of search parameters
20        var $custom_options = array(); // Options array to edit in the admin control panel
21       
22        /**
23        * Constructor
24        * This function creates a new filter 'search_load' so that search plugins can call it and load their class object into "$this->plugin".
25        */
26        function WP_Search() { 
27                // Check if the plugin is loaded before we try to load this class to it
28                if( $this->plugin == NULL ) {
29                        $this->plugin = apply_filters( 'search_load', array() );
30                        $this->plugin->flags =& $this->create_flags();
31                        $this->plugin->parent =& $this;
32                }
33               
34                if( $this->plugin->options['advanced'] == 1 && $_GET['advancedsearch'] == "1" )
35                        add_filter( 'template_redirect', array( &$this, 'advanced_search_wrapper' ) );
36                if( $this->plugin->options['advanced'] == 1 )
37                        add_filter( 'get_search_form', array( &$this, 'advanced_link' ) );
38                               
39                $this->custom_options = unserialize( get_option( "searchapi_custom_options" ) );
40               
41                /**
42                * Config Warning
43                * Shows a warning if you need to configure additonal settings
44                */
45                function config_warning() {
46                        echo "<div id='message' class='updated fade'><p><strong>" . __('The search system is almost ready.' ) . "</strong> ".sprintf( __ ( '<a href="%1$s">You must fill out a few additional configuration fields before the search engine can be used.</a>'), "options-general.php?page=search/search.php" ) . "</p></div>";
47                }
48               
49                // Should we show the above notice?
50                if( is_array( $this->custom_options ) && ( count ( $this->custom_options ) > 0 ) ) {
51                        foreach( $this->custom_options as $setting ) {
52                                if( $setting['required'] == 1 ) {
53                                        if( ! trim( get_option( $setting['id'] ) ) ) {
54                                                add_action('admin_notices', 'config_warning');
55                                                break;
56                                        }
57                                }
58                        }
59                }
60        }
61       
62       
63        /**
64        * Initialize Search
65        * This function overloads the template outputer so that we can replace the content with our search results. We do this so the legacy search template or older search stuff
66        * doesn't load instead. This function calls execute_search which does the bulk of the work for this class.
67        * @see search_api::execute_search()
68        */
69        function init_search() {
70                add_filter( 'template_redirect', array( &$this, 'execute_search' ) );
71        }
72       
73        /**
74        * Build Options
75        * This function takes a list of custom options a plugin might have (a search api key, a charset string, etc) so that you can edit them from the control panel
76        * @param array $options An array of options stored in arrays containing ids, values, titles and descriptions
77        */
78        function build_options( $options, $help = '' ) {
79                global $wpdb;
80               
81                foreach( $options as $option ) {
82                        add_option( esc_attr__ ( $option['id'] ) , esc_attr__ ( $option['value'] ) );
83                }
84               
85                update_option( "searchapi_custom_options", serialize( $options ) );
86                update_option( "searchapi_help", $wpdb->escape( $help ) );
87        }
88       
89        /**
90        * Options Admin Menu
91        * This function creates a menu under "settings" for search settings if the search plugin has any
92        */
93        function options_admin_menu() {
94                if( is_array ( $this->custom_options ) ) {
95                        add_options_page (
96                                __("Search Settings"),
97                                __("Search Settings"),
98                                'manage_options',
99                                __FILE__,
100                                array( &$this, "options_admin_page" )
101                        );
102                }
103        }
104       
105        /**
106        * Options Admin Page
107        * This function takes creates a page for editing search settings
108        */
109        function options_admin_page() {
110                global $wpdb;
111                echo "<div class='wrap'><h2>" . __( "Search Settings " ) . "</h2>";
112               
113                if( get_option( 'searchapi_help' ) )
114                        echo "<div id='message' class='update fade'>\n" . stripslashes( get_option( 'searchapi_help' ) ) . "</div>\n";
115               
116                // Update settings
117                if( $_REQUEST['submit'] ) {
118                        $ok = false;
119                       
120                        foreach( $this->custom_options as $option ) {
121                                if( !empty( $_REQUEST[$option['id']] ) ) {
122                                        update_option( $wpdb->escape( $option['id'] ), $wpdb->escape ( $_REQUEST[$option['id']] ) );
123                                        $ok = true;
124                                }
125                        }
126                       
127                        if( $ok == true )
128                                echo "<div id='message' class='update fade'><p>" . __("Options Saved") . "</p></div>";
129                        else
130                                echo "<div id='message' class='error fade'><p>" . __("Failed to save options.") . "</p></div>";
131                }
132               
133                // Display the form
134                echo "<form method='post'>\n";
135               
136                        foreach( $this->custom_options as $option ) {
137                                echo "<p><label for='{$option['id']}'> " . esc_html__( $option['title'] ) . ": \n";
138                                if( !empty( $option['desc'] ) )
139                                        echo "<br /> " . esc_html__( $option['desc'] ) . "<br />";
140                                echo "<input type='text' name='{$option['id']}' value='" . esc_attr__( stripslashes( get_option( $option['id'] ) ) ) ."' /></label></p>";
141                        }
142                       
143                        echo "<input type='submit' name='submit' class='button-primary' value='Save Settings' /></form>";
144               
145                echo "</div>";
146        }
147       
148        /**
149        * Pagination
150        * This function takes the total number of results from a set and calculates how many pages is needed to display X (WordPress's posts per page option) results per page.
151        * @param int $total The total number of results from a query
152        * @return string HTML output containing the pagination links
153        */
154        function pagination( $total ) {
155                // Only go through the pagination code if the search plugin calls for it       
156                if( $this->plugin->options['pagination'] == 1 ) {
157                       
158                        // Load some required variables such as the total results, the total needed pages and variable place holders.
159                        if( $total > 0 )
160                                $pages = ceil( $total / get_option( 'posts_per_page' ) );
161
162                        if( empty ( $_GET['pg'] ) )
163                                $_GET['pg'] = 1;
164                                                       
165                        $pages = $pages ? $pages : 1;
166                        $links = "";
167                       
168                        // Grab the current URL (minus the query string) since search pages can have many query strings
169                        $current = "http" . ( empty( $_SERVER["HTTPS"] ) ? "": ( $_SERVER["HTTPS"]=='on' ) ? "s": "" )."://" . esc_attr__( $_SERVER["HTTP_HOST"] ) . esc_attr__( $_SERVER["REQUEST_URI"] );
170                        $current = preg_replace( "/&amp;pg=([0-9]+)/i", "", $current );
171                       
172                        // Create the previous, next and number links
173                        if( $_GET['pg'] > 1 )
174                                $previous_link = "<span class=\"searchpglink\"><a href=\"".$current."&amp;pg=".($_GET['pg'] - 1)."\">&lt;</a></span>&nbsp;";
175                       
176                        if( $_GET['pg'] < $pages )
177                                $next_link = "&nbsp;<span class=\"searchpglink\"><a href=\"".$current."&amp;pg=".($_GET['pg'] + 1)."\">&gt;</a></span>";
178                       
179                        if( $pages > 1 ) {
180                                for( $i = 0, $j = $pages - 1; $i <= $j; ++$i ) {
181                                        $pagenum = $i+1;
182                                        $page = ceil( $pagenum );
183                                       
184                                        if ( $pagenum < ( $_GET['pg'] - 4 ) ) {
185                                                $i = $_GET['pg'] - 6;
186                                                continue;
187                                        }
188                               
189                                        if ( $page == $_GET['pg'] )
190                                                $links .= "&nbsp;<span class=\"searchpgcurrent\">{$page}</span>"; // this is the current page, no need for a link
191                                        else {
192                                                $links .= "&nbsp;<span class=\"searchpglink\"><a href=\"".$current."&amp;pg=".$page."\" title=\"$page\">$page</a></span>";
193                                                if ( $pagenum > ( $_GET['pg'] + 4 ) )
194                                                        break;
195                                        }
196                                }
197                        }               
198                }
199               
200                // FILTER: search_pagination Allows you to edit the output of the pagination links
201                return apply_filters('search_pagination', $previous_link . $links . $next_link);
202        }
203       
204        /**
205        * Advanced Search Wrapper
206        * This function creates a "virtual" page which is used to display all the advanced search form HTML. The short code [advsearch] is replaced with output from
207        * the advanced_search function here.
208        * @see search_api::advanced_search()
209        * @global class WordPress DB Abstraction Layer
210        */
211        function advanced_search_wrapper() {
212                global $wp_query;
213               
214                // set "the loop" to nothing
215                $wp_query->posts = NULL;
216               
217                // Create a blank "result set" for the wordpress query object
218                $object = new stdClass();
219                $object->post_title = __( "Advanced Search" );
220                $object->post_content = "[advsearch]";
221                       
222                $wp_query->posts[0] = $object; 
223               
224                // replace the advsearch shortcode with the form, and then return the page template
225                add_shortcode( 'advsearch', array( &$this, 'advanced_search' ) );
226               
227                // Return the template
228                $template = get_page_template();
229               
230                if ( $template )
231                        include $template;
232                else
233                        include(TEMPLATEPATH . "/index.php");
234                       
235                exit;
236        }
237       
238        /**
239        * Advanced Link
240        * This function returns a link to the advanced search form. If additonal output is passed to it then that is also returned.
241        * @param string $output Output to be returned, usually passed from get_search_form or another WordPress search
242        * @return string HTML output containing an advanced search link
243        */
244        function advanced_link( $output = '' ) {       
245                // output usually comes from get_search-form
246                if( !empty( $output ) )
247                        $html = $output;
248                       
249                // return a link to the advanced search form
250                $html .= "<small><a href='index.php?advancedsearch=1'>". __( "Advanced Search" ) . "</a></small>";
251
252                // FILTER: search_advanced_link Allows you to edit the output of the advanced link and the search in the sidebar (using the filter get_search_template)
253                return apply_filters('search_advanced_link', $html);
254        }
255
256        /**
257        * Advanced Search
258        * This function generates HTML for an avanced search form (author, categories, etc). This function is outputed using a WordPress short code.
259        * @return string HTML output for an advanced search form
260        */
261        function advanced_search() {
262       
263                // Content type picker
264                $option_output = "<br /> " . __( "Search:" ) ." <input type='checkbox' name='types[]' value='posts' /> " . __( "Posts" ) ."  <input type='checkbox' name='types[]' value='pages' /> " . __( "Pages" ) ." <input type='checkbox' name='types[]' value='comments' /> " . __( "Comments " ) ."<br />";
265               
266                // author text box
267                $author_output .= "<br />" . __( "Author:" ) ." <input type='text' value='' name=\"author\" />";
268               
269                // category and tag/taxonomy selector (multiple choice box)
270                $tax_output = "";
271               
272                foreach( get_object_taxonomies('post') as $taxonomy ) {
273                        $data = get_taxonomy( $taxonomy );
274                       
275                        if( $data->name == "category" )
276                                $tax_output = "<br /> <br /> " . __( "Categories: " ) ."<br /><br />" . str_replace( "name='cat'", "name='cats[]' multiple='multiple' size='10'", wp_dropdown_categories( 'hierarchical=1&echo=0' ) );
277                               
278                        else {
279                                $terms = get_terms( $data->name );
280                               
281                                if( count ( $terms ) != 0 ) {
282                                        $tax_output .= "<br /> <br /> " . $data->label . "<br /><br /><select name='tags[]' multiple='multiple' size='10'>";
283                                        foreach( $terms as $term ) {
284                                                $tax_output .= "<option value='".$term->term_id."'>" . $term->name . '</option>';
285                                        }
286                                        $tax_output .= "</select>";
287                                }
288                        }
289                }
290               
291                // Month Picker, created once and used for start and end dates
292                $month_picker = "<option value=''>" . __( 'Select Month' ) . "</option>
293                                        <option value='1'>" . __( 'January' ) . "</option>
294                                        <option value='2'>" . __( 'February' ) . "</option>
295                                        <option value='3'>" . __( 'March' ) . "</option>
296                                        <option value='4'>" . __( 'April' ) . "</option>
297                                        <option value='5'>" . __( 'May' ) . "</option>
298                                        <option value='6'>" . __( 'June' ) . "</option>
299                                        <option value='7'>" . __( 'July' ) . "</option>
300                                        <option value='8'>" . __( 'August' ) . "</option>
301                                        <option value='9'>" . __( 'September' ) . "</option>
302                                        <option value='10'>" . __( 'October' ) . "</option>
303                                        <option value='11'>" . __( 'November' ) . "</option>
304                                        <option value='12'>" . __( 'December' ) . "</option>";
305               
306                // Start date dropdowns
307                $date_output = "<br /><br />" . __( 'From' ) . "<br /> <select name='startMonth'>{$month_picker}</select>
308                                                <select name='startYear'> <option value=\"\">" . __( 'Select Year' ) . "</option>
309                                            " . str_replace( get_option( "siteurl" ) . "/?m=", "", wp_get_archives( 'type=yearly&format=option&show_post_count=0&echo=0') ) . "</select>
310                                                <select name='startDay'> <option value=\"\">" . __( 'Select Day' ) . "</option>";                                               
311
312                for ( $i = 1; $i <= 31; $i++ ) {
313                        $date_output .= "<option value='{$i}'>{$i}</option>";
314                }
315
316                $date_output .= "</select>";
317               
318                // End date dropdowns
319                $date_output .= "<br />" . __( 'to' ) . "<br /> <select name='endMonth'>{$month_picker}</select>
320                                                <select name='endYear'> <option value=\"\">" . __( 'Select Year' ) . "</option>
321                                            " . str_replace( get_option( "siteurl" ) . "/?m=", "", wp_get_archives( 'type=yearly&format=option&show_post_count=0&echo=0' ) ) . "</select>
322                                                <select name='endDay'> <option value=\"\">" . __( 'Select Day' ) . "</option>";                                         
323
324                for ( $i = 1; $i <= 31; $i++ ) {
325                        $date_output .= "<option value='{$i}'>{$i}</option>";
326                }
327
328                $date_output .= "</select>";
329               
330                // Return the final output
331               
332                // FILTER: search_advanced Allows you to edit the output of the advanced search page
333                return apply_filters( 'search_advanced', "<br /><form method='get' id='result_search_form' action='' style='text-align: left;'>
334                <input type='text' value='{$this->flags['string']}' name='s' id='results' size='50' />
335                {$option_output}
336                {$author_output}
337                {$tax_output}
338                {$date_output}
339                <p><input type='submit' id='searchsubmit' value='" . __( 'Search' ) . "' /></p>
340                </form>" );
341        }
342
343        /**
344        * Create Flags
345        * Cleans up input from the query string and sticks it in an easier to digest format for search plugins
346        * @return array An array of search parameters
347        */
348        function create_flags() {
349                global $wpdb;
350                // Clean up and separate the terms
351                $this->flags['string'] = $wpdb->escape( $_GET['s'] );
352                preg_match_all( '/".*?("|$)|((?<=[\\s",+])|^)[^\\s",+]+/', $this->flags['string'], $matches );
353                $this->flags['terms'] = array_map( create_function( '$a', 'return trim($a, "\\"\'\\n\\r ");' ), $matches[0] );
354               
355                // Clean up the start and end dates
356               
357                $this->flags['startYear'] = intval( $_GET['startYear'] );
358                $this->flags['startMonth'] = intval( $_GET['startMonth'] );
359                $this->flags['startDay'] = intval( $_GET['startDay'] );
360               
361                $this->flags['endYear'] = intval( $_GET['endYear'] );
362                $this->flags['endMonth'] = intval( $_GET['endMonth'] );
363                $this->flags['endDay'] = intval( $_GET['endDay'] );
364               
365                // Clean up the author flag
366                $this->flags['author'] = $wpdb->escape( $_GET['authors'] );
367               
368                // Clean up categories
369                if( is_array( $_GET['cats'] ) ) {
370                        foreach( $_GET['cats'] as $i )
371                                $this->flags['categories'][] = intval( $i );
372                }
373               
374                // Clean up tags
375                if( is_array( $_GET['tags'] ) ) {
376                        foreach( $_GET['tags'] as $i )
377                                $this->flags['tags'][] = intval( $i );
378                }
379               
380                // Clean up the content types (all, posts, pages, comments)
381                if( is_array( $_GET['types'] ) ) {
382                        foreach( $_GET['types'] as $i ) {
383                                $this->flags['types'][] = $wpdb->escape( $i );
384                        }
385                }
386               
387                // Build our sort by/order by flag
388                $this->flags['sort'] = "relevance";
389                if( $_GET['sort'] == "date" )
390                        $this->flags['sort'] = "date";
391                elseif( $_GET['sort'] == "alpha" )
392                        $this->flags['sort'] = "alpha";
393
394                       
395                // Decide ascending or decesnding
396                $this->flags['sorttype'] = "DESC";
397                if( $_GET['sorttype'] == "ASC" )
398                        $this->flags['sorttype'] = "ASC";
399
400                // decie which page we are on
401                $this->flags['page'] = 1;                       
402                if( !empty( $_GET['pg'] ) )
403                        $this->flags['page'] = intval( $_GET['pg'] );
404
405                // return a mass array of all the flags/query strings           
406                // FILTER: search_flags Allows you to make any changes to the search flags array
407                return apply_filters( 'search_flags', $this->flags );
408        }
409       
410       
411        /**
412        * Result Search Box
413        * This function generates HTML for options to be displayed on a search result page. This includes options for filtering by type, reordering results and an
414        * advanced search link
415        * @return string HTML output for the various form controls
416        */
417        function result_search_box( ) {
418                $output = "";
419
420                // Return filters if the feature is enabled. flters are for filtering by content type (posts, pages, comments)
421                if( $this->plugin->options['filters'] == 1 ) {
422                        $option_output = "<br />";
423                       
424                        if( is_array( $this->flags['types'] ) ) {
425                                if( in_array( 'posts', $this->flags['types'] ) )
426                                        $checked['post'] = ' checked="checked"';
427                                       
428                                if( in_array( 'pages', $this->flags['types'] ) )
429                                        $checked['page'] = ' checked="checked"';
430                                       
431                                if( in_array( 'comments', $this->flags['types'] ) )
432                                        $checked['comment'] = ' checked="checked"';
433                        }
434                       
435                        // all three types are searched at once
436                        else {
437                                $checked['post'] = ' checked="checked"';
438                                $checked['page'] = ' checked="checked"';
439                                $checked['comment'] = ' checked="checked"';
440                        }
441                       
442                        // Find the current url for use within javascript (the switch sort and switch sort type dropdowns)     
443                        $current_url = "http" . ( empty( $_SERVER["HTTPS"] ) ? "": ( $_SERVER["HTTPS"]=='on' ) ? "s": "" )."://" . ( $_SERVER["HTTP_HOST"] ) . ( $_SERVER["REQUEST_URI"] );
444               
445               
446                        // Start building the options output
447                        $option_output .= __( 'Search Only:' ) . "<input type='checkbox' name='types[]' value='posts'{$checked['post']} /> " . __( 'Posts' ) . "
448                                                                 <input type='checkbox' name='types[]' value='pages'{$checked['page']}/> " . __( 'Pages ' ) . "
449                                                                 <input type='checkbox' name='types[]' value='comments'{$checked['comment']} /> " . __( 'Comments ') . "<br />";       
450                }
451               
452                if( $this->plugin->options['sort'] == 1 ) {
453                       
454                        // Decide which sort flag is chosen
455                        if( $this->flags['sort'] == "date" )
456                                $checked['date'] = ' selected="selected"';
457                        elseif( $this->flags['sort'] == "alpha" )
458                                $checked['alpha'] = ' selected="selected"';
459                        elseif( $this->flags['sort'] == "relevance" )
460                                $checked['relevance'] = ' selected="selected"';
461                               
462                        // Decide which sort type flag is chosen
463                        if( $this->flags['sorttype'] == "ASC" )
464                                $checked['ASC'] = ' selected="selected"';
465                        elseif( $this->flags['sorttype'] == "DESC" )
466                                $checked['DESC'] = ' selected="selected"';
467                               
468                        // Start building the options output
469                        $option_output .= "<script type='text/javascript'>                       
470                                                                        function switchSort(value) {
471                                                                                document.location.href = '" . preg_replace( "/&sort=([A-Za-z]+)/i", "", $current_url ) . "'+'&sort='+value;
472                                                                        }
473                                                                       
474                                                                        function switchSortType(value) {
475                                                                                document.location.href = '" . preg_replace( "/&sorttype=([A-Za-z]+)/i", "", $current_url ) . "'+'&sorttype='+value;
476                                                                        }
477                                                                </script>
478                                                               
479                                                                <br /><div style='text-align: right;'>" . __( 'Order results by:' ) . "
480                                                               
481                                                                <select name='sort' onChange='switchSort(this.options[this.selectedIndex].value);'>\n
482                                                                        <option value='relevance'{$checked['relevance']}>" . __( 'Relevance' ) . "</option>
483                                                                        <option value='date'{$checked['date']}>" . __( 'Date' ) . "</option>
484                                                                        <option value='alpha'{$checked['alpha']}>" . __( 'Alphabetical' ) . "</option>
485                                                                </select>
486                                                               
487                                                                <select name='sorttype' onChange='switchSortType(this.options[this.selectedIndex].value);'>\n
488                                                                <option value='ASC'{$checked['ASC']}>" . __( 'Ascending' ) . "</option>
489                                                                <option value='DESC'{$checked['DESC']}>" . __( 'Descending' ) . "</option>
490                                                                </select></div>";
491                                                               
492                }
493               
494                if( $this->plugin->options['advanced'] == 1 )
495                        $advanced = $this->advanced_link();
496               
497                // Result Box Output
498                $output = "<br /><form method='get' id='resultsearchform' action='' style='text-align: left;'>
499                <input type='text' value='" . esc_attr( apply_filters( 'the_search_query', get_search_query() ) ) . "' name='s' id='results' />
500                <input type='submit' id='searchsubmit' value='" . __( 'Search' ) . "' />
501                {$advanced}
502                {$option_output}
503                </form>";
504                               
505                // FILTER: search_result_box Allows you to make changes to the filter/order box above the search results
506                return apply_filters( 'search_result_box', $output );
507        }
508       
509        /**
510        * Trim Excerpt
511        * If a line of text is longer then $chars characters this functon shortens a line of text and returns it with $end at the end. If a line of text is shorter the whole
512        * thing is returned.
513        * @param string $content The text to shorten
514        * @param int $chars The amount of characters to start shortening content at
515        * @param string $end The characters to append to the end of the shortend text
516        * @return string The shortend text with appended characters OR full already short text
517        */
518        function trim_excerpt( $content, $chars = 150, $end = "..." ) { 
519                $content = substr( strip_tags( trim( $content ) ), 0, $chars );
520                $content = substr( $content, 0, strrpos( $content, ' ' ) ) . $end;
521                apply_filters( 'the_excerpt', $content );
522                return $content;
523        }
524       
525        /**
526        * Execute Search
527        * This function creates a virtual page and then replaces a shortcode [search] with output from another search plugin (the plugin's search() function). This function also
528        * outputs all final HTML to the browser for search results.
529        * @global class WordPress's DB Abstraction Class
530        * @global class WordPress query object (from wp-includes/query.php). used for overloading "the loop" to display in a page.
531        */
532        function execute_search() {
533                global $wpdb, $wp_query;
534               
535                // Default the loop to nothing since we are trying to overload the page template
536                $wp_query->posts = NULL;
537               
538                // Try to load the search page which SHOULD contain the short code [search]
539                $wp_query->posts = $wpdb->get_results( "SELECT * from {$wpdb->prefix}posts WHERE post_type = 'page' and post_title = 'search'" );
540               
541                // Create a virtual page if the search page does not exist
542                if( empty( $wp_query->posts[0] ) ) {
543                        $object = new stdClass();
544                        $object->post_title = __( "Search" );
545                        $object->post_content = "[search]";
546                        $wp_query->posts[0] = $object;
547                }
548               
549                // Parse the shortodes for the loop     
550                add_shortcode( 'search', array( $this->plugin, 'search' ) );
551
552                // Return the template
553                // Return the template
554                $template = get_page_template();
555               
556                if ( $template )
557                        include $template;
558                else
559                        include(TEMPLATEPATH . "/index.php");
560               
561                exit;
562        }
563       
564}
565
566// END SEARCH API
567
568// BEGIN SEARCH INDEX CLASS
569
570/**
571* Search Index
572* This class contains a set of fucntions for "refreshing" (updating) the search index when a piece of content is changed in WordPress.
573* The purpose is to only need to search one table instead of 3+ for different pieces of content, have a standard set of data and make it easier for
574* search libraries like Sphinx to read.
575* @verson 1.0.0
576*/
577class search_index {
578
579        function search_index() {
580                add_filter( 'delete_post', array( &$this, 'delete_post' ) );
581                add_filter( 'delete_comment', array( &$this, 'delete_comment' ) );
582                add_filter( 'save_post', array( &$this, 'save_post' ) );
583                add_filter( 'comment_post', array( &$this, 'comment_post' ) );
584                add_filter( 'edit_comment', array( &$this, 'edit_comment' ) );
585                add_filter( 'wp_set_comment_status', array( &$this, 'edit_comment' ) );
586        }
587        /**
588        * Delete Post
589        * This function is ran when a post or a page is deleted from WordPress. The post or page is then also removed from being searched.
590        * This function can be hooked into with the hook 'refreshed_search_index'
591        * @param int $id The id of the post or page being deleted
592        * @global object WordPress Database Abstraction Layer
593        */
594        function delete_post( $id ) {
595                global $wpdb;
596                $wpdb->query( "DELETE FROM {$wpdb->prefix}search_index WHERE type = 'post' OR type = 'page' AND object = '" . $wpdb->escape( $id ) . "'" );
597                do_action( 'refreshed_search_index' );
598        }
599       
600        /**
601        * Delete Comment
602        * This function is ran when a comment is deleted from WordPress. The comment is then also removed from being searched.
603        * This function can be hooked into with the hook 'refreshed_search_index'
604        * @param int $id The id of the comment being deleted
605        * @global object WordPress Database Abstraction Layer
606        */
607        function delete_comment( $id ) {
608                global $wpdb;
609                $wpdb->query( "DELETE FROM {$wpdb->prefix}search_index WHERE type = 'comment' AND object = '" . intval( $id ) . "'" );
610                do_action( 'refreshed_search_index' );
611        }
612       
613        /**
614        * Save Post
615        * This function is ran when a page or post  is saved or created in WordPress. The data or changed data is then made avaiable for searching.
616        * This function can be hooked into with the hook 'refreshed_search_index'
617        * @param int $id The id of the page or post
618        * @global object WordPress Database Abstraction Layer
619        */
620        function save_post( $id ) {
621                global $wpdb;
622       
623                $post = $wpdb->get_results ( "SELECT * from {$wpdb->prefix}posts WHERE ID = '" . intval( $id ) . "'" );
624               
625//              print_r($post);
626                if( $post[0]->post_type != "revision" )
627                {                       
628                        $type = $post[0]->post_type;
629               
630                        // Do we have an index for this (are we updating?) or are we creating
631                        $idx = $wpdb->get_results( "SELECT * from {$wpdb->prefix}search_index WHERE object = '" . intval( $post[0]->ID ) . "' AND ( type = 'post' OR type='page' ) " );
632
633                        // Build a string of categories and tags that this object belongs to
634                       
635                        $cats = ",";
636                        $tags = ",";
637                       
638                        $terms = $wpdb->get_results( "SELECT r.term_taxonomy_id,t.taxonomy,r.object_id from $wpdb->term_relationships r LEFT JOIN $wpdb->term_taxonomy t ON(t.term_taxonomy_id=r.term_taxonomy_id) WHERE r.object_id = '" . intval( $post[0]->ID ) . "'" );
639               
640                        foreach( $terms as $term ) {
641                                if( $term->taxonomy == "category" )
642                                        $cats .= intval( $term->term_taxonomy_id ) . ",";
643                                else
644                                        $tags .= intval( $term->term_taxonomy_id ) . ",";
645                        }
646               
647                        // Find which author created this object
648                        $authors = $wpdb->get_results( "SELECT display_name FROM $wpdb->users WHERE ID = '" . intval( $post[0]->post_author ) ."'" );
649                        $author = $authors[0]->display_name;
650                       
651                        // Can this item be searched yet?       
652                        if( $post[0]->post_status == "publish" && empty( $post[0]->post_password ) )
653                                $protected = "0";
654                        else
655                                $protected = "1";
656       
657                        // update
658                        if( !empty( $idx[0]->id ) ) {
659                                $wpdb->query( "UPDATE {$wpdb->prefix}search_index SET title = '" . $wpdb->escape( $post[0]->post_title ) . "', content = '" . $wpdb->escape( $post[0]->post_content ) . "', post_date = '" . $wpdb->escape( $post[0]->post_date ) . "', parent = '', categories = '" . $wpdb->escape ( $cats ) . "', tags = '" . $wpdb->escape( $tags ) . "', author = '" . $wpdb->escape ( $author ) . "', type = '" . $wpdb->escape( $type ) . "', protected = '" . $wpdb->escape ( $protected ) . "' WHERE object = '" . intval( $post[0]->ID ) . "'" );
660                        }
661                       
662                        // create new
663                        else {
664                                $wpdb->query( "INSERT INTO {$wpdb->prefix}search_index (object, title, content, post_date, parent, categories, tags, author, type, protected)
665                                VALUES (
666                                                          '" . intval( $id ) . "', '" . $wpdb->escape( $post[0]->post_title ) . "', '" . $wpdb->escape( $post[0]->post_content ) . "', '" . $wpdb->escape( $post[0]->post_date ) . "', '0', '" . $wpdb->escape( $cat ) . "', '" . $wpdb->escape( $tags ) . "', '" . $wpdb->escape( $author ) . "', '" . $wpdb->escape( $type ) . "', '" . $wpdb->escape( $protected ) . "'
667                                );" );
668               
669                        }       
670                        do_action( 'refreshed_search_index' );
671                }
672        }
673
674        /**
675        * Comment Post
676        * This function is ran when a comment is made on a post in WordPress. The comment is made avaiable for searching.
677        * This function can be hooked into with the hook 'refreshed_search_index'
678        * @param int $id The id of the of the comment
679        * @global object WordPress Database Abstraction Layer
680        */
681        function comment_post( $id ) {
682                global $wpdb;
683               
684                // Load the comment
685                $comment = $wpdb->get_results( "SELECT * from {$wpdb->prefix}comments WHERE comment_ID = '" . intval( $id ) . "'" );
686
687                // Load the straight post data
688                $post = $wpdb->get_results( "SELECT * from {$wpdb->prefix}posts WHERE ID = '" . intval( $comment[0]->comment_post_ID ) . "'" );
689               
690                // Load a string of the cats the comment is in
691                $cat = ",";
692                $cats = $wpdb->get_results( "SELECT term_taxonomy_id from $wpdb->term_relationships WHERE object_id = '" . intval( $comment[0]->comment_post_ID ) . "'" );
693       
694                foreach( $cats[0] as $catz ) {
695                        $cat .= $catz.","; 
696                }
697               
698                // Can we search this comment?
699                if( $comment[0]->comment_approved == 1 )
700                        $protected = 0;
701                else
702                        $protected = 1;
703                       
704                // create the index entry
705                $wpdb->query( "INSERT INTO {$wpdb->prefix}search_index (object, title, content, post_date, parent, categories, author, type, protected)
706                                VALUES (
707                                                          '" . intval( $comment[0]->comment_ID ) . "', '', '" . $wpdb->escape( $comment[0]->comment_content ) . "', '" . $wpdb->escape( $comment[0]->comment_date ) . "', '". intval( $post[0]->ID ) ."', '" . $wpdb->escape( $cat ) . "', '" . $wpdb->escape( $comment[0]->comment_author ) . "', 'comment', '" . $wpdb->escape( $protected ) . "'
708                                );" ); 
709                               
710                do_action( 'refreshed_search_index' );
711        }
712
713
714        /**
715        * Edit Comment
716        * This function is ran when a comment is changed. The updated comment is made avaiable for searching.
717        * This function can be hooked into with the hook 'refreshed_search_index'
718        * @param int $id The id of the of the comment
719        * @param string $status The status of the comment (closed, etc)
720        * @global object WordPress Database Abstraction Layer
721        */     
722        function edit_comment( $id, $status = '' ) {
723                global $wpdb;
724               
725                // Load the comment data
726                $comment = $wpdb->get_results( "SELECT * from {$wpdb->prefix}comments WHERE comment_ID = '" . intval( $id ) . "'" );
727               
728                // Load the post data for this comment
729                $post = $wpdb->get_results(  "SELECT * from {$wpdb->prefix}posts WHERE ID = '" . intval( $comment[0]->comment_post_ID ) . "'" );
730       
731                // Load a string of the categories the comment is in
732                $cat = ",";
733                $cats = $wpdb->get_results( "SELECT term_taxonomy_id from $wpdb->term_relationships WHERE object_id = '". intval( $post[0]->ID ) . "'" );
734
735                if( is_array( $cats[0] ) )
736                {
737                        foreach( $cats[0] as $catz ) {
738                                $cat .= $catz->term_taxonomy_id.","; 
739                        }
740                }
741                       
742                // see if we can search this comment
743                if( $comment[0]->comment_approved )
744                        $protected = 0;
745                else
746                        $protected = 1;
747               
748                // update the entry
749                $wpdb->query( "UPDATE {$wpdb->prefix}search_index SET title = '', content = '" . $wpdb->escape( $comment[0]->comment_content ) . "', post_date = '" . $wpdb->escape( $comment[0]->comment_date ) . "', parent = '" . intval( $post[0]->ID ) . "', categories = '" . $wpdb->escape( $cat ) . "', author = '" . $wpdb->escape( $comment[0]->comment_author ) . "', protected = '" . $wpdb->escape( $protected ) . "' WHERE object = '" . intval( $id ) . "' AND type = 'comment'" );
750               
751                do_action( 'refreshed_search_index' );         
752        }
753
754        /**
755        * Refresh All
756        * This function is database resource intensive. It clears out the entire search index and rebuilds it.
757        * This function can be hooked into with the hook 'refreshed_search_index'
758        * @global object WordPress Database Abstraction Layer
759        */
760        function all() {
761                global $wpdb;
762               
763                // delete the current index     
764                $wpdb->query( "DELETE FROM {$wpdb->prefix}search_index" );
765       
766                // Load all the posts and pages stored within WordPress
767                $posts = $wpdb->get_results( "SELECT * from {$wpdb->prefix}posts" );
768                       
769                foreach( $posts as $post ) {
770                        // don't search revisions
771                        if( $post->post_type == "page" || $post->post_type == "post" ) {
772
773                                // Build a string of categories and tags that this object belongs to
774                                $cats = ",";
775                                $tags = ",";
776       
777                                $terms = $wpdb->get_results( "SELECT r.term_taxonomy_id,t.taxonomy,r.object_id from $wpdb->term_relationships r LEFT JOIN $wpdb->term_taxonomy t ON(t.term_taxonomy_id=r.term_taxonomy_id) WHERE r.object_id = '" . intval( $post->ID ) . "'" );
778                       
779                                foreach( $terms as $term ) {
780                                        if( $term->taxonomy == "category" )
781                                                $cats .= intval( $term->term_taxonomy_id ) . ",";
782                                        else
783                                                $tags .= intval( $term->term_taxonomy_id ) . ",";
784                                }
785                               
786                                // find the author
787                                $authors = $wpdb->get_results( "SELECT display_name FROM $wpdb->users WHERE ID = '" . intval( $post->post_author ) . "'" );
788                                $author = $authors[0]->display_name;                   
789                               
790                                // Can we search this item?
791                                if( $post->post_status == "publish" && empty( $post->post_password ) )
792                                        $protected = "0";
793                                else
794                                        $protected = "1";
795               
796                                // Create the entry in the search index
797                                $wpdb->query( "INSERT INTO {$wpdb->prefix}search_index (object, title, content, post_date, parent, categories, tags, author, type, protected)
798                                VALUES (
799                                                          '". intval( $post->ID ) . "', '" . $wpdb->escape( $post->post_title ) . "', '" . $wpdb->escape( $post->post_content ) . "', '" . $wpdb->escape( $post->post_date ) . "', '0', '" . $wpdb->escape( $cats ) . "', '" . $wpdb->escape( $tags ) . "', '" . $wpdb->escape( $author ) . "', '" . $wpdb->escape( $post->post_type ) . "', '" . $wpdb->escape( $protected ) . "'
800                                );" );
801                               
802                                do_action( 'refreshed_search_index' );
803                        }       
804                }
805                       
806                // Load up the comments for insertion now
807                $comments = $wpdb->get_results( "SELECT * from {$wpdb->prefix}comments" );
808               
809                foreach($comments as $comment) {
810                        // load the post data
811                        $post = $wpdb->get_results( "SELECT * from {$wpdb->prefix}posts WHERE ID = '" . intval( $comment->comment_post_ID ) . "'" );
812                       
813                        // load a string of categories that the comment is in
814                        $cat = ",";
815                        $cats = $wpdb->get_results( "SELECT term_taxonomy_id from $wpdb->term_relationships WHERE object_id = '" . intval( $comment->comment_post_ID ) . "'" );
816               
817                        foreach( $cats as $catz ) {
818                                $cat .= intval( $catz->term_taxonomy_id ) . ","; 
819                        }
820                               
821                        // is the comment searchable
822                        if( $comment->comment_approved )
823                                $protected = 0;
824                        else
825                                $protected = 1;
826                       
827                        // create the search index row
828                        $wpdb->query( "INSERT INTO {$wpdb->prefix}search_index (object, title, content, post_date, parent, categories, author, type, protected)
829                                        VALUES (
830                                                                  '" . intval( $comment->comment_ID ) . "', '', '" . $wpdb->escape( $comment->comment_content ) . "', '" . $wpdb->escape( $comment->comment_date ) . "', '" . intval( $post[0]->ID ) . "', '" . $wpdb->escape( $cat ) . "', '" . $wpdb->escape( $comment->comment_author ) . "', 'comment', '" . $wpdb->escape( $protected ) . "'
831                                        );" ); 
832                }
833               
834        }
835}
836// END SEARCH INDEX
837
838function load_search_api() {
839        global $wpsearch;
840        $wpsearch = new WP_Search();
841        add_action( 'admin_menu', array( &$wpsearch, 'options_admin_menu' ) );
842}
843
844if ( ! isset($wpsearch) ) {
845        add_action( 'plugins_loaded', 'load_search_api' );
846}
847
848
849// Load the search index and have WordPress call the functions if we have indexing enabled
850if( $wpsearch->plugin->options['index'] == 1 )
851        $index = new search_index();
852
853?>