stopwords = require( 'stopwords.php' ); //Register our search functionality add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ), 30 ); //Index posts on publish add_action( 'save_post', array( $this, 'publish_post' ),10 ,3 ); //Deindex posts on unpublish add_action( 'delete_post', array( $this, 'delete_post' )); add_action( 'transition_post_status', array( $this, 'maybe_delete_post' ) ,10 ,3 ); //Custom snippets add_filter( 'the_excerpt', array( $this, 'snippet' ),30 ); add_filter( 'the_content', array( $this, 'snippet' ),30 ); //Index some more posts $this->index_next(); } /** * Extracts a result snippet from a string of text * * @access public * @since 2.1.1 * * @param string $content The content to snippetify * * @return string the (maybe) highlighted snippet */ public function snippet( $content ) { //Check if it is the main search query if( !is_main_query() OR !in_the_loop() ){ return $content; }; //Make sure search term is set and custom snippets are allowed if( !als_get_option( 'als-snippet-enable' ) OR !isset( $_GET['s'] ) ){ return $content; }; global $post; $content = do_shortcode( $post->post_content ); $s = $_GET['s']; //Convert the search term to words and clean it $s_words = array_unique( $this->prepare($s) ); $s = $this->remove_modifiers($s); //Get a list of html tags to keep in the snippet $tags = explode(',', als_get_option( 'als-snippet-tags' )); $modified_tags = array(); for ( $i = 0; $i < count($tags); $i++ ) { //Converts them to something like array( 'a'=> array() ) $tags[$i] = trim($tags[$i]); $modified_tags[$tags[$i]] = array(); } //Finally, the content //Then strip html $content = wp_kses ( stripslashes ( $content ), $modified_tags ); //Calculate the length of the snippet $radius = absint ( als_get_option ( 'als-snippet-length' ) ) / 2; $s_len = strlen( $s ); $text_len = strlen( $content ); if( $radius < $s_len ) { $radius = $s_len; //Radius cant be less than the search term } //Then create the snippet. $excerpt = false; foreach ($s_words as $s_word) { $pos = strpos ( strtolower($content), strtolower ( $s_word ) ); //If current term missing from content move to next term if($pos === false) { //use === comparator coz the word might appear at the start hence giving a false positive continue; } $found = true; //Hopefully; the term should appear at the centre of the snippet $start = 0; if ($pos > $radius) { $start = $pos - $radius; } //If the word appears at the beginning... $overflow = 0; if ( $pos < $radius ) { $overflow = $radius - $pos; } $start = absint($start); $end = absint($start + strlen($s_word) + $overflow + ($radius*2)); //Set prefixes and suffixes $b = ''; $e = ''; if ($start != 0) $b = '...'; if ($end + 1 < $text_len) $e = ' ...'; $excerpt = $b . substr($content,$start , $end - $start) . $e; break; } //If no word matched the content; we create a standard excerpt if(!$excerpt){ $excerpt = substr($content, 0, $radius * 2) . '...'; } //If we not highlighting; return our excerpt if ( !als_get_option( 'als-snippet-highlight' ) ){ return $excerpt; } $color = als_get_option( 'als-snippet-color' ); $style = ''; if( $color ){ $style = "style=color:$color;"; } foreach($s_words as $s_word) { $excerpt=preg_replace("/($s_word)(?![^<]*>)/i", "\${1}", $excerpt); } return $excerpt; } /** * Gets a list of published posts * * @access public * @since 2.1.1 * * @param int $limit The maximum number of posts to fetch * @param int $offset The minimum id to begin from * * @return array an array of posts */ public function get_posts( $limit, $offset = 0 ) { $args = array( 'post_status' => array('publish'), 'posts_per_page' => $limit, 'offset' => $offset, 'ignore_sticky_posts' => true, 'order' => 'ASC', 'post_type'=> als_post_types(), 'orderby' => 'id', ); $posts = new WP_Query($args); return $posts->posts; } /** * Gets a list of published non-indexed posts * * @access public * @since 2.1.1 * * @param int $limit The maximum number of posts to fetch * @param int $offset The minimum id to begin from * * @return array an array of posts */ public function get_non_indexed_posts( $count ) { $count = intval ( $count ) + 1; $args = array( 'post_status' => array('publish'), 'posts_per_page' => $count, 'ignore_sticky_posts' => true, 'order' => 'ASC', 'orderby' => 'id', 'post_type'=> als_post_types(), 'post__not_in' => $this->indexed_ids(), ); //If we are also indexing attachments if( in_array('attachment', als_post_types()) ) { $args['post_status'] = array('publish', 'inherit'); } $posts = new WP_Query($args); return $posts->posts; } /** * Searches the index for a given query * * @access public * @since 2.1.1 * * @param string $query The query to search for * @return array an array of found post ids */ public function search ( $q ) { global $wpdb; $s = implode( ' ' ,$this->prepare ( $q )); //stems and rids stopwords $s1 = implode( ' ' ,$this->prepare ( $q, true )); //stems and rids stopwords except query modifiers //if the query was made up entirely of stopwords, we fallback to the inbuilt search engine, no need to waste resources if($s == '') { return array(); } //CLean the string $s = $wpdb->prepare('%s', $s); $s1 = $wpdb->prepare('%s', $s1); //Fetch content weight $title_weight = intval(als_get_option( 'als-title-weight' )); $content_weight = intval(als_get_option( 'als-content-weight' )); $excerpt_weight = intval(als_get_option( 'als-excerpt-weight' )); $url_weight = intval(als_get_option( 'als-url-weight' )); $comment_weight = intval(als_get_option( 'als-comment-weight' )); //Score field holds the relevancy score of a given post $score = '(0'; $fields_to_index = array_unique(als_get_option( 'fields-to-index' )); if ( is_array ( $fields_to_index ) ) { foreach($fields_to_index as $col) { if ( strval($col) != '0') {//Bug in redux?? $weight = intval(als_get_option( "als-$col-weight" )); $score .= " + (MATCH($col) AGAINST($s) * $weight)"; } } } //Should we favour popular posts? if ( als_get_option( 'als-favour-popular' ) ){ $score .= '+ (IF(comment_count = 0, 0, log(comment_count)+0.5))'; //Log 1=0, so we add 0.5 to atleast give more weight to posts containing 1 comment } //Should we favour new posts? if ( als_get_option( 'als-favour-new' ) ){ $score .= "+ (log(DATEDIFF(NOW(), date)+1))"; } //post types to search $post_types = als_post_types(); foreach($post_types as $type){ $type = strtolower($type); $weight = intval(als_get_option ( "als-{$type}-weight" )); $score .= "+((post_type IN('{$type}'))*{$weight})"; } $score .= ') as score'; $excluded_posts = als_get_option ( 'excluded-post-types' ); //And finally some restrictions $restrictions = " WHERE MATCH(content) AGAINST ($s1 IN BOOLEAN MODE)"; if (als_is_array( $excluded_posts )) { $excluded_posts = implode(',', $excluded_posts); $restrictions .= " AND id not in($excluded_posts)"; } $table = $wpdb->prefix . 'als_index'; $sql = "SELECT ID, $score FROM $table $restrictions ORDER BY score DESC LIMIT 100"; $results = $wpdb->get_col($sql); if (! is_array ( $results ) ) { return false; } $this->log_query($q, count($results)); return $results; } /** * Logs a given search query * * @access public * @since 2.1.1 * @global object $wpdb * @return void */ public function log_query ( $q, $count ) { global $wpdb; $searches_log_table = $wpdb->prefix . "als_log"; //Remove search modifiers then prepare for db insertion $original = $wpdb->prepare('%s', $q); $modified = $wpdb->prepare('%s', $this->remove_modifiers($q)); $indexed = $wpdb->prepare('%s', implode( ' ' ,$this->prepare ( $q ))); $exists = $wpdb->get_results ( "SELECT searches as searches FROM {$searches_log_table} WHERE LOWER(query)=LOWER({$original})" ); if(count($exists) < 1) { $sql = "INSERT IGNORE INTO $searches_log_table (query, modified, indexed, hits) VALUES ($original, $modified, $indexed, $count)"; return $wpdb->query($sql); } else { $searches = intval($exists[0]->searches) + 1; $sql = "UPDATE $searches_log_table SET searches=$searches, hits=$count WHERE LOWER(query)=LOWER($original)"; return $wpdb->query($sql); } } /** * Logs demo searches * * @access public * @since 2.2.2 * @global object $wpdb * @return void */ public function create_demo_queries () { global $wpdb; $searches_log_table = $wpdb->prefix . "als_log"; $keywords1 = explode (" ", "google facebook wordpress whatsapp gmail vanity envato twitter"); $keywords2 = explode (" ", "index delete deactivate edit hide hack duplicate remove"); $keywords3 = explode (" ", "account name user email password"); $searches = array(); foreach( $keywords2 as $first ){ foreach( $keywords1 as $second ) { foreach ( $keywords3 as $third ) { $count = mt_rand( 0, 40 ); $searched = mt_rand( 1, 100 ); $original = "$first $second $third"; $modified = $this->remove_modifiers( $original ); $indexed = implode( ' ' ,$this->prepare ( $original )); $value = $wpdb->prepare("(%s, %s,%s, %d, %d)", $original, $modified, $indexed, $count, $searched); $searches[] = $value; } } } $searches = implode(', ',$searches); $sql = "INSERT IGNORE INTO $searches_log_table (query, modified, indexed, hits, searches) VALUES $searches"; return $wpdb->query($sql); } /** * Reads the contents of the given pdf file * * @access public * @since 2.1.1 * @return string a string containing the pdfs content */ public function read_pdf ( $path ) { //TODO: } /** * Returs total number of indexed posts * * @access public * @since 2.1.1 * @return int id of the last indexed post */ public function total_indexed() { global $wpdb; $index_table = $wpdb->prefix . "als_index"; $sql = "SELECT COUNT(id) as total FROM $index_table"; return $wpdb->get_var($sql); } /** * Returns the id of the last indexed post * * @access public * @since 2.1.1 * @return int id of the last indexed post */ public function last_indexed() { //Since indexes are sequential; the last indexed post is also the post with the highest id global $wpdb; $index_table = $wpdb->prefix . "als_index"; $sql = "SELECT MAX(id) as last FROM $index_table"; return $wpdb->get_var($sql); } /** * Returns Total number of searches * * @access public * @since 2.1.1 * @return int numeber of searches */ public function total_searches ( $conditions ='1 = 1' ) { global $wpdb; $searches_log_table = $wpdb->prefix . "als_log"; $sql = "SELECT SUM(searches) as total FROM $searches_log_table WHERE {$conditions}"; return $wpdb->get_var($sql); } /** * Fetches previous searches * * @access public * @since 2.1.1 * @return array an array of previous searches */ public function previous_searches ( $conditions ='' ) { global $wpdb; $searches_log_table = $wpdb->prefix . "als_log"; $sql = "SELECT * FROM $searches_log_table {$conditions}"; return $wpdb->get_results($sql); } /** * Displays a table of previous searches * * @access public * @since 2.1.1 * @return void */ public function show_searches ( $conditions ='', $echo = true ) { ob_start(); $searches = $this->previous_searches( $conditions ); if( !is_array( $searches ) ) { return false; } ?>