<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[2934] sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory: Hack in an incomplete search implementation using Greg's Jetpack_Search class.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/2934">2934</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/2934","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>tellyworth</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2016-04-12 01:41:59 +0000 (Tue, 12 Apr 2016)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Hack in an incomplete search implementation using Greg's Jetpack_Search class.

There's still more to be done on both sides of the API, but this at least proves the mechanism works.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryclassplugindirectoryphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/</li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectorylibssitesearchclassjetpacksearchresultpostsiteratorphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/class.jetpack-searchresult-posts-iterator.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectorylibssitesearchjetpacksearchphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/jetpack-search.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryclassplugindirectoryphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php      2016-04-11 20:26:10 UTC (rev 2933)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-plugin-directory.php        2016-04-12 01:41:59 UTC (rev 2934)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -169,6 +169,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        add_filter( 'option_home',    array( $this, 'rosetta_network_localize_url' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                        add_filter( 'option_siteurl', array( $this, 'rosetta_network_localize_url' ) );
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+               // Instantiate our copy of the Jetpack_Search class if 
+               if ( !class_exists( 'Jetpack_Search' ) ) {
+                       require_once( __DIR__ . '/libs/site-search/jetpack-search.php' );
+                       \Jetpack_Search::instance();
+               }
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectorylibssitesearchclassjetpacksearchresultpostsiteratorphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/class.jetpack-searchresult-posts-iterator.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/class.jetpack-searchresult-posts-iterator.php                          (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/class.jetpack-searchresult-posts-iterator.php    2016-04-12 01:41:59 UTC (rev 2934)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,135 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+/*
+ * WARNING: This file is distributed verbatim in Jetpack.
+ * There should be nothing WordPress.com specific in this file.
+ *
+ * @hide-in-jetpack
+ */
+
+/**
+ * Provide a custom Iterator for seamlessly switching to the appropriate blog and inflating posts for 
+ * search results that may or many not live on other sites.
+ *
+ * Provides lazy loading of posts (by id) and transparent blog context switching for iterating an ES result set
+ *
+ * Forked from WP.com VIP plugin.
+ */
+
+class Jetpack_SearchResult_Posts_Iterator implements SeekableIterator, Countable, ArrayAccess {
+       /**
+        * The ES search result to retrieve posts for
+        * 
+        * @var array
+        */
+       protected $search_result;
+
+       /**
+        * An array of inflated posts represented in the $search_result
+        * 
+        * @var array
+        */
+       protected $posts;
+
+       /**
+        * The current offset
+        * 
+        * @var int
+        */
+       protected $pointer = 0;
+
+       /**
+        * Retrieve the ES search result
+        * 
+        * @return array The ES search result
+        */
+       public function get_search_result() {
+               return $this->search_result;
+       }
+
+       /**
+        * Set the ES search result
+        * 
+        * @param array $search_result The ES search result to associate with this iterator
+        */
+       public function set_search_result( array $search_result ) {
+               $this->search_result = $search_result;
+
+               return $this;
+       }
+
+       /**
+        * Retrieve a post from the database by id, and conditionally switch to the appropriate blog, if needed
+        * 
+        * @param  array        $es_result The individual hit in an ES search result to inflate a post for
+        * @return WP_Post      The inflated WP_Post object, or null if not found
+        */
+       protected function inflate_post( $es_result ) {
+
+               #$post = get_blog_post( $es_result['fields']['blog_id'], $es_result['fields']['post_id'] );
+
+               //TODO: is this a good thing to kill?
+               //if ( isset( $es_result['fields']['blog_id'] ) && get_current_blog_id() !== $es_result['fields']['blog_id'] )
+               //      switch_to_blog( $es_result['fields']['blog_id'] );
+
+               $post = get_post( $es_result['fields']['post_id'] );
+
+               return $post;
+       }
+
+       // Implement SeekableIterator
+       
+       public function seek( $position ) {
+               $this->pointer = $position;
+       }
+
+       public function current () {
+               return $this->pointer;
+       }
+
+       public function key () {
+               return $this->pointer;
+       }
+
+       public function next() {
+               $this->pointer++;
+       }
+
+       public function rewind() {
+               $this->pointer = 0;
+       }
+
+       public function valid() {
+               return $this->offsetExists( $this->pointer );
+       }
+
+       // Implement Countable
+       
+       public function count() {
+               return count( $this->search_result['results']['hits'] );
+       }
+
+       // Implement ArrayAccess
+       
+       public function offsetExists( $index ) {
+               return isset( $this->search_result['results']['hits'][ $index ] );
+       }
+
+       public function offsetGet( $index ) {
+               if ( ! $this->offsetExists( $index ) )
+                       return null;
+
+               // Lazy load the post
+               if ( ! isset( $this->posts[ $index ] ) )
+                       $this->posts[ $index ] = $this->inflate_post( $this->search_result['results']['hits'][ $index ] );
+
+               return $this->posts[ $index ];
+       }
+
+       public function offsetSet( $index, $value ) {
+               $this->posts[ $index ] = $value;
+       }
+
+       public function offsetUnset( $index ) {
+               unset( $this->posts[ $index ] );
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/class.jetpack-searchresult-posts-iterator.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectorylibssitesearchjetpacksearchphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/jetpack-search.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/jetpack-search.php                             (rev 0)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/jetpack-search.php       2016-04-12 01:41:59 UTC (rev 2934)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,667 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php 
+/*
+ * WARNING: This file is distributed verbatim in Jetpack.
+ * There should be nothing WordPress.com specific in this file.
+ * Extend WPCOM_Search instead.
+ *
+ * @hide-in-jetpack
+ */
+
+/*
+ * This is a preliminary version of Jetpack Search.
+ * It is highly likely that 95% of the time the search will not be using the loop.
+ * 
+ */
+
+/*
+ * Known Bugs:
+ *  - no real Jetpack support yet: missing public API
+ *  - lots of TODO
+ *  - phrase searching, and other searches that WP already supports
+ *  - paging (especially with max number of results)
+ *  - reduce number of queries for getting posts? use an IN query when possible?
+ *  - check that post is displayable
+ *  - infinite scroll?
+ *  - tags/cats/author filtering from query args not working
+ *  - multi-lingual field searches
+ *  - multi-lingual needs more testing
+ *  - Jetpack Query Parser currently depends on wp.com data to do the parsing. hmmm...
+ *  - username parsing from query args is wp.com specific
+ *
+ *
+ * O2 Known Bugs:
+ *  - sticky posts in search results?
+ *  - should we override the results display so that highlighted results are shown?
+ *
+ * Missing Features:
+ *  - Filtering of what gets searched (hooks for dates, authors, tags, etc)
+ *  - other search syntx that WP already supports
+ *  - sorting by date or whatever
+ *  - tracking clicks
+ *  - highlighting using the ES highlighter? Offload from user's server?
+ *    - can we make this pretty? use the excerpts to correct the original content?
+ *  - show images in results
+ *
+ * Untested:
+ *  - test different taxonomy filters
+ *  - author filtering
+ *  - faceting
+ *
+ */
+
+require_once( __DIR__ . '/class.jetpack-searchresult-posts-iterator.php' );
+
+class Jetpack_Search {
+
+       protected $do_found_posts;
+       protected $found_posts = 0;
+
+       protected $search_result;
+
+       protected $original_blog_id;
+       protected $jetpack_blog_id;
+
+       protected $blog_lang;
+
+       protected static $instance;
+
+       //Languages with custom analyzers, other languages are supported,
+       // but are analyzed with the default analyzer.
+       public static $analyzed_langs = array( 'ar', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', 'eu', 'fa', 'fi', 'fr', 'he', 'hi', 'hu', 'hy', 'id', 'it', 'ja', 'ko', 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' );
+
+       protected function __construct() {
+               /* Don't do anything, needs to be initialized via instance() method */
+       }
+
+       public function __clone() { wp_die( "Please don't __clone WPCOM_elasticsearch" ); }
+
+       public function __wakeup() { wp_die( "Please don't __wakeup WPCOM_elasticsearch" ); }
+
+       public static function instance() {
+               if ( ! isset( self::$instance ) ) {
+                       self::$instance = new Jetpack_Search;
+                       self::$instance->setup();
+               }
+               return self::$instance;
+       }
+
+       public function setup() {
+               //TODO: only enable if this site is public (otherwise we don't have content)
+               //TODO: check that the module is activated
+
+               $this->jetpack_blog_id = Jetpack::get_option( 'id' );
+
+               if ( ! is_admin() ) {
+                       $this->init_hooks();
+               }
+       }
+
+       public function set_lang( $lang = false ) {
+               if ( ! $lang ) {
+                       //TODO: don't think this works for Jetpack
+                       $blog = get_blog_details( $blog_id );
+                       $lang = get_lang_code_by_id( $blog->lang_id );
+               }
+               $this->blog_lang = $lang;
+       }
+
+       /////////////////////////////////////////////////////
+       // Lots of hooks
+
+       public function init_hooks() {
+               // Checks to see if we need to worry about found_posts
+               add_filter( 'post_limits_request', array( $this, 'filter__post_limits_request' ), 999, 2 );
+
+               # Note: Advanced Post Cache hooks in at 10 so it's important to hook in before that
+
+               // Force $q['cache_results'] = false; this prevents the un-inflated WP_Post objects from being stored in cache
+               add_action( 'pre_get_posts', array( $this, 'action__pre_get_posts' ), 5 );
+
+               // Run the ES query and kill the standard search query - allow the 'the_posts' filter to handle inflation
+               add_filter( 'posts_request', array( $this, 'filter__posts_request' ), 5, 2 );
+
+               // Nukes the FOUND_ROWS() database query
+               add_filter( 'found_posts_query', array( $this, 'filter__found_posts_query' ), 5, 2 );
+
+               // Since the FOUND_ROWS() query was nuked, we need to supply the total number of found posts
+               add_filter( 'found_posts', array( $this, 'filter__found_posts' ), 5, 2 );
+
+               // Hook into the_posts to return posts from the ES results
+               add_filter( 'the_posts', array( $this, 'filter__the_posts' ), 5, 2 );
+
+               add_filter( 'jetpack_search_es_wp_query_args', array( $this, 'filter__add_date_filter_to_query' ), 10, 2 );
+       }
+
+       /**
+        * Register the hooks needed to transparently handle posts in The Loop
+        *
+        * Handles inflating the post, switching to the appropriate blog context, and setting up post data
+        */
+       public function register_loop_hooks() {
+               add_action( 'loop_start',       array( $this, 'action__loop_start' ) );
+               add_action( 'loop_end',         array( $this, 'action__loop_end' ) );
+       }
+
+       /**
+        * Unregister the hooks for The Loop
+        *
+        * Needs to be called when the search Loop is complete, so later queries are not affected
+        */
+       public function unregister_loop_hooks() {
+               remove_action( 'the_post',              array( $this, 'action__the_post' ) );
+               remove_action( 'loop_end',              array( $this, 'action__loop_end' ) );
+       }
+
+
+       /////////////////////////////////////////////////////////
+       // Raw Search Query
+
+       /*
+        * Run a search on the WP.com public API.
+        *
+        * @param object $es_args : Args conforming to the WP.com /sites/<blog_id>/search endpoint
+        * @return object : the response from the public api (could be a WP_Error)
+        */
+       public function search( $es_args ) {
+               $service_url = 'http://public-api.wordpress.com/rest/v1/sites/' . $this->jetpack_blog_id . '/search';
+
+               $request = wp_remote_post( $service_url, array(
+                       'headers' => array(
+                               'Content-Type' => 'application/json',
+                       ),
+                       'timeout' => 10,
+                       'user-agent' => 'jetpack_search',
+                       'body' => json_encode( $es_args ),
+               ) );
+
+               if ( is_wp_error( $request ) )
+                       return $request;
+
+               return json_decode( wp_remote_retrieve_body( $request ), true );
+       }
+
+       //TODO: add secured search for posts/comments
+
+       /////////////////////////////////////////////////////////
+       // Insert the ES results into the Loop when searching
+       //
+
+       public function filter__post_limits_request( $limits, $query ) {
+               if ( ! $query->is_search() )
+                       return $limits;
+
+               if ( empty( $limits ) || $query->get( 'no_found_rows' ) ) {
+                       $this->do_found_posts = false;
+               } else {
+                       $this->do_found_posts = true;
+               }
+
+               return $limits;
+       }
+
+       public function filter__the_posts( $posts, $query ) {
+               if ( ! $query->is_main_query() || ! $query->is_search() )
+                       return $posts;
+
+               if ( ! is_array( $this->search_result ) )
+                       return $posts;
+
+               // This class handles the heavy lifting of transparently switching blogs and inflating posts
+               $this->posts_iterator = new Jetpack_SearchResult_Posts_Iterator();
+               $this->posts_iterator->set_search_result( $this->search_result );
+
+               $posts = array();
+
+               // We have to return something in $posts for regular search templates to work, so build up an array
+               // of simple, un-inflated WP_Post objects that will be inflated by Jetpack_SearchResult_Posts_Iterator in The Loop
+               foreach ( $this->search_result['results']['hits'] as $result ) {
+                       // Create an empty WP_Post object that will be inflated later
+                       $post = new stdClass();
+
+                       $post->ID            = $result['fields']['post_id'];
+                       $post->blog_id       = $result['fields']['blog_id'];
+
+                       // Run through get_post() to add all expected properties (even if they're empty)
+                       $post = get_post( $post );
+
+                       if ( $post )
+                               $posts[] = $post;
+               }
+
+               // Listen for the start/end of The Loop, to add some action handlers for transparently loading the post
+               $this->register_loop_hooks();
+
+               return $posts;
+       }
+
+       public function filter__posts_request( $sql, $query ) {
+               global $wpdb;
+
+               if ( ! $query->is_main_query() || ! $query->is_search() )
+                       return $sql;
+
+               $page = ( $query->get( 'paged' ) ) ? absint( $query->get( 'paged' ) ) : 1;
+               $posts_per_page = $query->get( 'posts_per_page' );
+
+               // ES API does not allow more than 15 results at a time
+               if ( $posts_per_page > 15 )
+                       $posts_per_page = 15;
+
+               // Start building the WP-style search query args
+               // They'll be translated to ES format args later
+               $es_wp_query_args = array(
+                       'query'          => $query->get( 's' ),
+                       'posts_per_page' => $posts_per_page,
+                       'paged'          => $page,
+                       'orderby'        => $query->get( 'orderby' ),
+                       'order'          => $query->get( 'order' ),
+               );
+
+               // You can use this filter to modify the search query parameters, such as controlling the post_type.
+               // These arguments are in the format for convert_wp_es_to_es_args(), i.e. WP-style.
+               $es_wp_query_args = apply_filters( 'jetpack_search_es_wp_query_args', $es_wp_query_args, $query );
+
+               // Convert the WP-style args into ES args
+               $es_query_args = $this->convert_wp_es_to_es_args( $es_wp_query_args );
+
+               //Only trust ES to give us IDs, not the content since it is a mirror
+               $es_query_args['fields'] = array( 
+                       'post_id',
+                       'blog_id'
+               );
+
+               // This filter is harder to use if you're unfamiliar with ES but it allows complete control over the query
+               $es_query_args = apply_filters( 'jetpack_search_es_query_args', $es_query_args, $query );
+
+               // Do the actual search query!
+               $this->search_result = $this->search( $es_query_args );
+
+               if ( is_wp_error( $this->search_result ) || ! is_array( $this->search_result ) || empty( $this->search_result['results'] ) || empty( $this->search_result['results']['hits'] ) ) {
+                       $this->found_posts = 0;
+                       return '';
+               }
+
+               // Total number of results for paging purposes
+               $this->found_posts = $this->search_result['results']['total'];
+
+               // Don't select anything, posts are inflated by Jetpack_SearchResult_Posts_Iterator 
+               // in The Loop, to allow for multi site search
+               return '';
+       }
+
+
+       public function filter__found_posts_query( $sql, $query ) {
+               if ( ! $query->is_main_query() || ! $query->is_search() )
+                       return $sql;
+
+               return '';
+       }
+
+       public function filter__found_posts( $found_posts, $query ) {
+               if ( ! $query->is_main_query() || ! $query->is_search() )
+                       return $found_posts;
+
+               return $this->found_posts;
+       }
+
+       public function action__pre_get_posts( $query ) {
+               if ( ! $query->is_main_query() || ! $query->is_search() )
+                       return;
+
+               $query->set( 'cache_results', false );
+       }
+
+       public function action__loop_start( $query ) {
+               if ( ! $query->is_main_query() || ! $query->is_search() ) {
+                       return;
+               }
+
+               add_action( 'the_post', array( $this, 'action__the_post' ) );
+
+               $this->original_blog_id = get_current_blog_id();
+       }
+
+       public function action__loop_end( $query ) {
+               // Once The Loop is finished, remove any hooks so future queries are unaffected by our shenanigans
+               $this->unregister_loop_hooks();
+
+               if ( ! $query->is_main_query() || ! $query->is_search() ) {
+                       return;
+               }
+
+               // Restore the original blog, if we're not on it
+               if ( get_current_blog_id() !== $this->original_blog_id )
+                       switch_to_blog( $this->original_blog_id );
+       }
+
+       public function action__the_post( &$post ) {
+               global $id, $post, $wp_query, $authordata, $currentday, $currentmonth, $page, $pages, $multipage, $more, $numpages;
+
+               $post = $this->get_post_by_index( $wp_query->current_post );
+
+               if ( ! $post )
+                       return;
+
+               // Do some additional setup that normally happens in setup_postdata(), but gets skipped
+               // in this plugin because the posts hadn't yet been inflated.
+               $authordata     = get_userdata( $post->post_author );
+
+               $currentday     = mysql2date('d.m.y', $post->post_date, false);
+               $currentmonth   = mysql2date('m', $post->post_date, false);
+
+               $numpages = 1;
+               $multipage = 0;
+               $page = get_query_var('page');
+               if ( ! $page )
+                       $page = 1;
+               if ( is_single() || is_page() || is_feed() )
+                       $more = 1;
+               $content = $post->post_content;
+               if ( false !== strpos( $content, '<!--nextpage-->' ) ) {
+                       if ( $page > 1 )
+                               $more = 1;
+                       $content = str_replace( "\n<!--nextpage-->\n", '<!--nextpage-->', $content );
+                       $content = str_replace( "\n<!--nextpage-->", '<!--nextpage-->', $content );
+                       $content = str_replace( "<!--nextpage-->\n", '<!--nextpage-->', $content );
+                       // Ignore nextpage at the beginning of the content.
+                       if ( 0 === strpos( $content, '<!--nextpage-->' ) )
+                               $content = substr( $content, 15 );
+                       $pages = explode('<!--nextpage-->', $content);
+                       $numpages = count($pages);
+                       if ( $numpages > 1 )
+                               $multipage = 1;
+               } else {
+                       $pages = array( $post->post_content );
+               }
+       }
+
+       /**
+        * Retrieve a full post by it's index in search results
+        */
+       public function get_post_by_index( $index ) {
+               return $this->posts_iterator[ $index ];
+       }
+
+       public function get_search_result( $raw = false ) {
+               if ( $raw )
+                       return $this->search_result;
+
+               return ( ! empty( $this->search_result ) && ! is_wp_error( $this->search_result ) && is_array( $this->search_result ) && ! empty( $this->search_result['results'] ) ) ? $this->search_result['results'] : false;
+       }
+
+
+       /////////////////////////////////////////////////
+       // Standard Filters Applied to the search query
+       //
+
+       public function filter__add_date_filter_to_query( $es_wp_query_args, $query ) {
+               if ( $query->get( 'year' ) ) {
+                       if ( $query->get( 'monthnum' ) ) {
+                               // Padding
+                               $date_monthnum = sprintf( '%02d', $query->get( 'monthnum' ) );
+
+                               if ( $query->get( 'day' ) ) {
+                                       // Padding
+                                       $date_day = sprintf( '%02d', $query->get( 'day' ) );
+
+                                       $date_start = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $date_day . ' 00:00:00';
+                                       $date_end   = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $date_day . ' 23:59:59';
+                               } else {
+                                       $days_in_month = date( 't', mktime( 0, 0, 0, $query->get( 'monthnum' ), 14, $query->get( 'year' ) ) ); // 14 = middle of the month so no chance of DST issues
+
+                                       $date_start = $query->get( 'year' ) . '-' . $date_monthnum . '-01 00:00:00';
+                                       $date_end   = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $days_in_month . ' 23:59:59';
+                               }
+                       } else {
+                               $date_start = $query->get( 'year' ) . '-01-01 00:00:00';
+                               $date_end   = $query->get( 'year' ) . '-12-31 23:59:59';
+                       }
+
+                       $es_wp_query_args['date_range'] = array( 'field' => 'date', 'gte' => $date_start, 'lte' => $date_end );
+               }
+
+               return $es_wp_query_args;
+       }
+
+       /////////////////////////////////////////////////
+       // Helpers for manipulating queries
+       //
+
+       // Someday: Should we just use ES_WP_Query???
+
+       // Converts WP-style args to ES args
+       function convert_wp_es_to_es_args( $args ) {
+               $defaults = array(
+                       'blog_id'        => get_current_blog_id(),
+       
+                       'query'          => null,    // Search phrase
+                       'query_fields'   => array( 'title', 'content', 'author', 'tag', 'category' ),
+       
+                       'post_type'      => null,  // string or an array
+                       'terms'          => array(), // ex: array( 'taxonomy-1' => array( 'slug' ), 'taxonomy-2' => array( 'slug-a', 'slug-b' ) )
+       
+                       'author'         => null,    // id or an array of ids
+                       'author_name'    => array(), // string or an array
+       
+                       'date_range'     => null,    // array( 'field' => 'date', 'gt' => 'YYYY-MM-dd', 'lte' => 'YYYY-MM-dd' ); date formats: 'YYYY-MM-dd' or 'YYYY-MM-dd HH:MM:SS'
+       
+                       'orderby'        => null,    // Defaults to 'relevance' if query is set, otherwise 'date'. Pass an array for multiple orders.
+                       'order'          => 'DESC',
+       
+                       'posts_per_page' => 10,
+                       'offset'         => null,
+                       'paged'          => null,
+       
+                       /**
+                        * Facets. Examples:
+                        * array(
+                        *     'Tag'       => array( 'type' => 'taxonomy', 'taxonomy' => 'post_tag', 'count' => 10 ) ),
+                        *     'Post Type' => array( 'type' => 'post_type', 'count' => 10 ) ),
+                        * );
+                        */
+                       'facets'         => null,
+               );
+       
+               $raw_args = $args; // Keep a copy
+       
+               $args = wp_parse_args( $args, $defaults );
+       
+               $es_query_args = array(
+                       'blog_id' => absint( $args['blog_id'] ),
+                       'size'    => absint( $args['posts_per_page'] ),
+               );
+
+               //TODO: limit size to 15
+       
+               // ES "from" arg (offset)
+               if ( $args['offset'] ) {
+                       $es_query_args['from'] = absint( $args['offset'] );
+               } elseif ( $args['paged'] ) {
+                       $es_query_args['from'] = max( 0, ( absint( $args['paged'] ) - 1 ) * $es_query_args['size'] );
+               }
+       
+               if ( !is_array( $args['author_name'] ) ) {
+                       $args['author_name'] = array( $args['author_name'] );
+               }
+       
+               // ES stores usernames, not IDs, so transform
+               if ( ! empty( $args['author'] ) ) {
+                       if ( !is_array( $args['author'] ) )
+                               $args['author'] = array( $args['author'] );
+                       foreach ( $args['author'] as $author ) {
+                               $user = get_user_by( 'id', $author );
+       
+                               if ( $user && ! empty( $user->user_login ) ) {
+                                       $args['author_name'][] = $user->user_login;
+                               }
+                       }
+               }
+
+               //////////////////////////////////////////////////
+               // Build the filters from the query elements.
+               // Filters rock because they are cached from one query to the next
+               // but they are cached as individual filters, rather than all combined together.
+               // May get performance boost by also caching the top level boolean filter too.
+               $filters = array();
+       
+               if ( $args['post_type'] ) {
+                       if ( !is_array( $args['post_type'] ) )
+                               $args['post_type'] = array( $args['post_type'] );
+                       $filters[] = array( 'terms' => array( 'post_type' => $args['post_type'] ) );
+               }
+       
+               if ( $args['author_name'] ) {
+                       $filters[] = array( 'terms' => array( 'author_login' => $args['author_name'] ) );
+               }
+       
+               if ( !empty( $args['date_range'] ) && isset( $args['date_range']['field'] ) ) {
+                       $field = $args['date_range']['field'];
+                       unset( $args['date_range']['field'] );
+                       $filters[] = array( 'range' => array( $field => $args['date_range'] ) );
+               }
+       
+               if ( is_array( $args['terms'] ) ) {
+                       foreach ( $args['terms'] as $tax => $terms ) {
+                               $terms = (array) $terms;
+                               if ( count( $terms ) && mb_strlen( $tax ) ) {
+                                       switch ( $tax ) {
+                                               case 'post_tag':
+                                                       $tax_fld = 'tag.slug';
+                                                       break;
+                                               case 'category':
+                                                       $tax_fld = 'category.slug';
+                                                       break;
+                                               default:
+                                                       $tax_fld = 'taxonomy.' . $tax . '.slug';
+                                                       break;
+                                       }
+                                       foreach ( $terms as $term ) {
+                                               $filters[] = array( 'term' => array( $tax_fld => $term ) );
+                                       }
+                               }
+                       }
+               }
+
+               ///////////////////////////////////////////////////////////
+               // Build the query - potentially extracting more filters
+               //  TODO: add auto phrase searching
+               //  TODO: add fuzzy searching to correct for spelling mistakes
+               //  TODO: boost title, tag, and category matches
+               if ( $args['query'] ) {
+                       $analyzer = Jetpack_Search::get_analyzer_name( $this->blog_lang );
+                       $query = array( 'multi_match' => array(
+                               'query'  => $args['query'],
+                               'fields' => $args['query_fields'],
+                               'operator'  => 'and',
+                               'type'  => 'cross_fields',
+                               'analyzer' => $analyzer
+                       ) );
+                       $es_query_args['query'] = Jetpack_Search::score_query_by_recency( $query );
+
+                       if ( ! $args['orderby'] ) {
+                               $args['orderby'] = array( 'relevance' );
+                       }
+               } else {
+                       if ( ! $args['orderby'] ) {
+                               $args['orderby'] = array( 'date' );
+                       }
+               }
+       
+               // Validate the "order" field
+               switch ( strtolower( $args['order'] ) ) {
+                       case 'asc':
+                               $args['order'] = 'asc';
+                               break;
+                       case 'desc':
+                       default:
+                               $args['order'] = 'desc';
+                               break;
+               }
+       
+               $es_query_args['sort'] = array();
+               foreach ( (array) $args['orderby'] as $orderby ) {
+                       // Translate orderby from WP field to ES field
+                       // todo: add support for sorting by title, num likes, num comments, num views, etc
+                       switch ( $orderby ) {
+                               case 'relevance' :
+                                       //never order by score ascending
+                                       $es_query_args['sort'][] = array( '_score' => array( 'order' => 'desc' ) );
+                                       break;
+                               case 'date' :
+                                       $es_query_args['sort'][] = array( 'date' => array( 'order' => $args['order'] ) );
+                                       break;
+                               case 'ID' :
+                                       $es_query_args['sort'][] = array( 'id' => array( 'order' => $args['order'] ) );
+                                       break;
+                               case 'author' :
+                                       $es_query_args['sort'][] = array( 'author.raw' => array( 'order' => $args['order'] ) );
+                                       break;
+                       }
+               }
+               if ( empty( $es_query_args['sort'] ) )
+                       unset( $es_query_args['sort'] );
+
+       
+               if ( ! empty( $filters ) ) {
+                       $es_query_args['filter'] = array( 'and' => $filters );
+               } else {
+                       $es_query_args['filter'] = array( 'match_all' => new stdClass() );
+               }
+       
+       
+               return $es_query_args;
+       }
+
+       public static function get_analyzer_name( $lang_code ) {
+               $analyzer = 'default';
+               if ( in_array( $lang_code, Jetpack_Search::$analyzed_langs ) ) {
+                       $analyzer = $lang_code . '_analyzer';
+               } else {
+                       $split_lang = explode( '-', $lang_code );
+                       if ( in_array( $split_lang[0], Jetpack_Search::$analyzed_langs ) )
+                               $analyzer = $split_lang[0] . '_analyzer';
+               }
+               return $analyzer;
+       }
+
+       ////////////////////////////////////////////
+       // ES Filter Manipulation
+
+       /*
+        * And an existing filter object with a list of additional filters.
+        *   Attempts to optimize the filters somewhat.
+        */
+       public static function and_es_filters( $curr_filter, $filters ) {
+               if ( !is_array( $curr_filter ) || isset( $curr_filter['match_all'] ) ) {
+                       if ( 1 == count( $filters ) )
+                               return $filters[0];
+
+                       return array( 'and' => $filters );
+               }
+
+               return array( 'and' => array_merge( array( $curr_filter ), $filters ) );
+       }
+
+       ////////////////////////////////////////////
+       // ES Query Manipulation
+
+       public static function score_query_by_recency( $query ) {
+               //Newer content gets weighted slightly higher
+               $date_scale = '360d';
+               $date_decay = 0.9;
+               $date_origin = date( 'Y-m-d' );
+
+               return array( 
+                        'function_score' => array(
+                                'query' => $query,
+                                'gauss'=> array(
+                                        'date_gmt' => array(
+                                                'origin' => $date_origin,
+                                                'scale' => $date_scale,
+                                                'decay' => $date_decay
+                                ) ),
+                                'boost_mode' => 'multiply'
+               ) );
+       }
+
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/libs/site-search/jetpack-search.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span></div>

</body>
</html>