/modules/related-posts/jetpack-related-posts.php

  1. <?php 
  2. class Jetpack_RelatedPosts { 
  3. const VERSION = '20150408'; 
  4. const SHORTCODE = 'jetpack-related-posts'; 
  5.  
  6. /** 
  7. * Creates and returns a static instance of Jetpack_RelatedPosts. 
  8. * 
  9. * @return Jetpack_RelatedPosts 
  10. */ 
  11. public static function init() { 
  12. static $instance = NULL; 
  13.  
  14. if ( ! $instance ) { 
  15. if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init' ) ) { 
  16. $instance = WPCOM_RelatedPosts::init(); 
  17. } else { 
  18. $instance = new Jetpack_RelatedPosts( 
  19. get_current_blog_id(),  
  20. Jetpack_Options::get_option( 'id' ) 
  21. ); 
  22.  
  23. return $instance; 
  24.  
  25. /** 
  26. * Creates and returns a static instance of Jetpack_RelatedPosts_Raw. 
  27. * 
  28. * @return Jetpack_RelatedPosts 
  29. */ 
  30. public static function init_raw() { 
  31. static $instance = NULL; 
  32.  
  33. if ( ! $instance ) { 
  34. if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init_raw' ) ) { 
  35. $instance = WPCOM_RelatedPosts::init_raw(); 
  36. } else { 
  37. $instance = new Jetpack_RelatedPosts_Raw( 
  38. get_current_blog_id(),  
  39. Jetpack_Options::get_option( 'id' ) 
  40. ); 
  41.  
  42. return $instance; 
  43.  
  44. protected $_blog_id_local; 
  45. protected $_blog_id_wpcom; 
  46. protected $_options; 
  47. protected $_allow_feature_toggle; 
  48. protected $_blog_charset; 
  49. protected $_convert_charset; 
  50. protected $_previous_post_id; 
  51. protected $_found_shortcode = false; 
  52.  
  53. /** 
  54. * Constructor for Jetpack_RelatedPosts. 
  55. * 
  56. * @param int $blog_id_local 
  57. * @param int $blog_id_wpcom 
  58. * @uses get_option, add_action, apply_filters 
  59. * @return null 
  60. */ 
  61. public function __construct( $blog_id_local, $blog_id_wpcom ) { 
  62. $this->_blog_id_local = $blog_id_local; 
  63. $this->_blog_id_wpcom = $blog_id_wpcom; 
  64. $this->_blog_charset = get_option( 'blog_charset' ); 
  65. $this->_convert_charset = ( function_exists( 'iconv' ) && ! preg_match( '/^utf\-?8$/i', $this->_blog_charset ) ); 
  66.  
  67. add_action( 'admin_init', array( $this, 'action_admin_init' ) ); 
  68. add_action( 'wp', array( $this, 'action_frontend_init' ) ); 
  69.  
  70. /** 
  71. * ================= 
  72. * ACTIONS & FILTERS 
  73. * ================= 
  74. */ 
  75.  
  76. /** 
  77. * Add a checkbox field to Settings > Reading for enabling related posts. 
  78. * 
  79. * @action admin_init 
  80. * @uses add_settings_field, __, register_setting, add_action 
  81. * @return null 
  82. */ 
  83. public function action_admin_init() { 
  84.  
  85. // Add the setting field [jetpack_relatedposts] and place it in Settings > Reading 
  86. add_settings_field( 'jetpack_relatedposts', '<span id="jetpack_relatedposts">' . __( 'Related posts', 'jetpack' ) . '</span>', array( $this, 'print_setting_html' ), 'reading' ); 
  87. register_setting( 'reading', 'jetpack_relatedposts', array( $this, 'parse_options' ) ); 
  88. add_action('admin_head', array( $this, 'print_setting_head' ) ); 
  89.  
  90. if( 'options-reading.php' == $GLOBALS['pagenow'] ) { 
  91. // Enqueue style for live preview on the reading settings page 
  92. $this->_enqueue_assets( false, true ); 
  93.  
  94. /** 
  95. * Load related posts assets if it's a elegiable frontend page or execute search and return JSON if it's an endpoint request. 
  96. * 
  97. * @global $_GET 
  98. * @action wp 
  99. * @uses add_shortcode, get_the_ID 
  100. * @returns null 
  101. */ 
  102. public function action_frontend_init() { 
  103. // Add a shortcode handler that outputs nothing, this gets overridden later if we can display related content 
  104. add_shortcode( self::SHORTCODE, array( $this, 'get_target_html_unsupported' ) ); 
  105.  
  106. if ( ! $this->_enabled_for_request() ) 
  107. return; 
  108.  
  109. if ( isset( $_GET['relatedposts'] ) ) { 
  110. $excludes = array(); 
  111. if ( isset( $_GET['relatedposts_exclude'] ) ) { 
  112. $excludes = explode( ', ', $_GET['relatedposts_exclude'] ); 
  113.  
  114. $this->_action_frontend_init_ajax( $excludes ); 
  115. } else { 
  116. if ( isset( $_GET['relatedposts_hit'] ) ) { 
  117. $this->_log_click( $_GET['relatedposts_origin'], get_the_ID(), $_GET['relatedposts_position'] ); 
  118. $this->_previous_post_id = (int) $_GET['relatedposts_origin']; 
  119.  
  120. $this->_action_frontend_init_page(); 
  121.  
  122.  
  123. /** 
  124. * Adds a target to the post content to load related posts into if a shortcode for it did not already exist. 
  125. * 
  126. * @filter the_content 
  127. * @param string $content 
  128. * @returns string 
  129. */ 
  130. public function filter_add_target_to_dom( $content ) { 
  131. if ( !$this->_found_shortcode ) { 
  132. $content .= "\n" . $this->get_target_html(); 
  133.  
  134. return $content; 
  135.  
  136. /** 
  137. * Looks for our shortcode on the unfiltered content, this has to execute early. 
  138. * 
  139. * @filter the_content 
  140. * @param string $content 
  141. * @uses has_shortcode 
  142. * @returns string 
  143. */ 
  144. public function test_for_shortcode( $content ) { 
  145. $this->_found_shortcode = has_shortcode( $content, self::SHORTCODE ); 
  146.  
  147. return $content; 
  148.  
  149. /** 
  150. * Returns the HTML for the related posts section. 
  151. * 
  152. * @uses esc_html__, apply_filters 
  153. * @returns string 
  154. */ 
  155. public function get_target_html() { 
  156. $options = $this->get_options(); 
  157.  
  158. if ( $options['show_headline'] ) { 
  159. $headline = sprintf( 
  160. '<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',  
  161. esc_html__( 'Related', 'jetpack' ) 
  162. ); 
  163. } else { 
  164. $headline = ''; 
  165.  
  166. /** 
  167. * Filter the Related Posts headline. 
  168. * 
  169. * @module related-posts 
  170. * 
  171. * @since 3.0.0 
  172. * 
  173. * @param string $headline Related Posts heading. 
  174. */ 
  175. $headline = apply_filters( 'jetpack_relatedposts_filter_headline', $headline ); 
  176.  
  177. if ( $this->_previous_post_id ) { 
  178. $exclude = "data-exclude='{$this->_previous_post_id}'"; 
  179. } else { 
  180. $exclude = ""; 
  181.  
  182. return <<<EOT 
  183. <div id='jp-relatedposts' class='jp-relatedposts' $exclude> 
  184. $headline 
  185. </div> 
  186. EOT; 
  187.  
  188. /** 
  189. * Returns the HTML for the related posts section if it's running in the loop or other instances where we don't support related posts. 
  190. * 
  191. * @returns string 
  192. */ 
  193. public function get_target_html_unsupported() { 
  194. return "\n\n<!-- Jetpack Related Posts is not supported in this context. -->\n\n"; 
  195.  
  196. /** 
  197. * ======================== 
  198. * PUBLIC UTILITY FUNCTIONS 
  199. * ======================== 
  200. */ 
  201.  
  202. /** 
  203. * Gets options set for Jetpack_RelatedPosts and merge with defaults. 
  204. * 
  205. * @uses Jetpack_Options::get_option, apply_filters 
  206. * @return array 
  207. */ 
  208. public function get_options() { 
  209. if ( null === $this->_options ) { 
  210. $this->_options = Jetpack_Options::get_option( 'relatedposts' ); 
  211. if ( ! is_array( $this->_options ) ) 
  212. $this->_options = array(); 
  213. if ( ! isset( $this->_options['enabled'] ) ) 
  214. $this->_options['enabled'] = true; 
  215. if ( ! isset( $this->_options['show_headline'] ) ) 
  216. $this->_options['show_headline'] = true; 
  217. if ( ! isset( $this->_options['show_thumbnails'] ) ) 
  218. $this->_options['show_thumbnails'] = false; 
  219. if ( empty( $this->_options['size'] ) || (int)$this->_options['size'] < 1 ) 
  220. $this->_options['size'] = 3; 
  221.  
  222. /** 
  223. * Filter Related Posts basic options. 
  224. * 
  225. * @module related-posts 
  226. * 
  227. * @since 2.8.0 
  228. * 
  229. * @param array $this->_options Array of basic Related Posts options. 
  230. */ 
  231. $this->_options = apply_filters( 'jetpack_relatedposts_filter_options', $this->_options ); 
  232.  
  233. return $this->_options; 
  234.  
  235. /** 
  236. * Parses input and returnes normalized options array. 
  237. * 
  238. * @param array $input 
  239. * @uses self::get_options 
  240. * @return array 
  241. */ 
  242. public function parse_options( $input ) { 
  243. $current = $this->get_options(); 
  244.  
  245. if ( !is_array( $input ) ) 
  246. $input = array(); 
  247.  
  248. if ( isset( $input['enabled'] ) && '1' == $input['enabled'] ) { 
  249. $current['enabled'] = true; 
  250. $current['show_headline'] = ( isset( $input['show_headline'] ) && '1' == $input['show_headline'] ); 
  251. $current['show_thumbnails'] = ( isset( $input['show_thumbnails'] ) && '1' == $input['show_thumbnails'] ); 
  252. } else { 
  253. $current['enabled'] = false; 
  254.  
  255. if ( isset( $input['size'] ) && (int)$input['size'] > 0 ) 
  256. $current['size'] = (int)$input['size']; 
  257. else 
  258. $current['size'] = null; 
  259.  
  260. return $current; 
  261.  
  262. /** 
  263. * HTML for admin settings page. 
  264. * 
  265. * @uses self::get_options, checked, esc_html__ 
  266. * @returns null 
  267. */ 
  268. public function print_setting_html() { 
  269. $options = $this->get_options(); 
  270.  
  271. $ui_settings_template = <<<EOT 
  272. <ul id="settings-reading-relatedposts-customize"> 
  273. <li> 
  274. <label><input name="jetpack_relatedposts[show_headline]" type="checkbox" value="1" %s /> %s</label> 
  275. </li> 
  276. <li> 
  277. <label><input name="jetpack_relatedposts[show_thumbnails]" type="checkbox" value="1" %s /> %s</label> 
  278. </li> 
  279. </ul> 
  280. <div id='settings-reading-relatedposts-preview'> 
  281. %s 
  282. <div id="jp-relatedposts" class="jp-relatedposts"></div> 
  283. </div> 
  284. EOT; 
  285. $ui_settings = sprintf( 
  286. $ui_settings_template,  
  287. checked( $options['show_headline'], true, false ),  
  288. esc_html__( 'Show a "Related" header to more clearly separate the related section from posts', 'jetpack' ),  
  289. checked( $options['show_thumbnails'], true, false ),  
  290. esc_html__( 'Use a large and visually striking layout', 'jetpack' ),  
  291. esc_html__( 'Preview:', 'jetpack' ) 
  292. ); 
  293.  
  294. if ( !$this->_allow_feature_toggle() ) { 
  295. $template = <<<EOT 
  296. <input type="hidden" name="jetpack_relatedposts[enabled]" value="1" /> 
  297. %s 
  298. EOT; 
  299. printf( 
  300. $template,  
  301. $ui_settings 
  302. ); 
  303. } else { 
  304. $template = <<<EOT 
  305. <ul id="settings-reading-relatedposts"> 
  306. <li> 
  307. <label><input type="radio" name="jetpack_relatedposts[enabled]" value="0" class="tog" %s /> %s</label> 
  308. </li> 
  309. <li> 
  310. <label><input type="radio" name="jetpack_relatedposts[enabled]" value="1" class="tog" %s /> %s</label> 
  311. %s 
  312. </li> 
  313. </ul> 
  314. EOT; 
  315. printf( 
  316. $template,  
  317. checked( $options['enabled'], false, false ),  
  318. esc_html__( 'Hide related content after posts', 'jetpack' ),  
  319. checked( $options['enabled'], true, false ),  
  320. esc_html__( 'Show related content after posts', 'jetpack' ),  
  321. $ui_settings 
  322. ); 
  323.  
  324. /** 
  325. * Head JS/CSS for admin settings page. 
  326. * 
  327. * @uses esc_html__ 
  328. * @returns null 
  329. */ 
  330. public function print_setting_head() { 
  331.  
  332. // only dislay the Related Posts JavaScript on the Reading Settings Admin Page 
  333. $current_screen = get_current_screen(); 
  334. if( 'options-reading' != $current_screen->id ) 
  335. return; 
  336.  
  337. $related_headline = sprintf( 
  338. '<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',  
  339. esc_html__( 'Related', 'jetpack' ) 
  340. ); 
  341.  
  342. $href_params = 'class="jp-relatedposts-post-a" href="#jetpack_relatedposts" rel="nofollow" data-origin="0" data-position="0"'; 
  343. $related_with_images = <<<EOT 
  344. <div class="jp-relatedposts-items jp-relatedposts-items-visual"> 
  345. <div class="jp-relatedposts-post jp-relatedposts-post0 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image"> 
  346. <a $href_params> 
  347. <img class="jp-relatedposts-post-img" src="http://jetpackme.files.wordpress.com/2014/08/1-wpios-ipad-3-1-viewsite.png?w=350&h=200&crop=1" width="350" alt="Big iPhone/iPad Update Now Available" scale="0"> 
  348. </a> 
  349. <h4 class="jp-relatedposts-post-title"> 
  350. <a $href_params>Big iPhone/iPad Update Now Available</a> 
  351. </h4> 
  352. <p class="jp-relatedposts-post-excerpt">Big iPhone/iPad Update Now Available</p> 
  353. <p class="jp-relatedposts-post-context">In "Mobile"</p> 
  354. </div> 
  355. <div class="jp-relatedposts-post jp-relatedposts-post1 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image"> 
  356. <a $href_params> 
  357. <img class="jp-relatedposts-post-img" src="http://jetpackme.files.wordpress.com/2014/08/wordpress-com-news-wordpress-for-android-ui-update2.jpg?w=350&h=200&crop=1" width="350" alt="The WordPress for Android App Gets a Big Facelift" scale="0"> 
  358. </a> 
  359. <h4 class="jp-relatedposts-post-title"> 
  360. <a $href_params>The WordPress for Android App Gets a Big Facelift</a> 
  361. </h4> 
  362. <p class="jp-relatedposts-post-excerpt">The WordPress for Android App Gets a Big Facelift</p> 
  363. <p class="jp-relatedposts-post-context">In "Mobile"</p> 
  364. </div> 
  365. <div class="jp-relatedposts-post jp-relatedposts-post2 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image"> 
  366. <a $href_params> 
  367. <img class="jp-relatedposts-post-img" src="http://jetpackme.files.wordpress.com/2014/08/videopresswedding.jpg?w=350&h=200&crop=1" width="350" alt="Upgrade Focus: VideoPress For Weddings" scale="0"> 
  368. </a> 
  369. <h4 class="jp-relatedposts-post-title"> 
  370. <a $href_params>Upgrade Focus: VideoPress For Weddings</a> 
  371. </h4> 
  372. <p class="jp-relatedposts-post-excerpt">Upgrade Focus: VideoPress For Weddings</p> 
  373. <p class="jp-relatedposts-post-context">In "Upgrade"</p> 
  374. </div> 
  375. </div> 
  376. EOT; 
  377. $related_with_images = str_replace( "\n", '', $related_with_images ); 
  378. $related_without_images = <<<EOT 
  379. <div class="jp-relatedposts-items jp-relatedposts-items-minimal"> 
  380. <p class="jp-relatedposts-post jp-relatedposts-post0" data-post-id="0" data-post-format="image"> 
  381. <span class="jp-relatedposts-post-title"><a $href_params>Big iPhone/iPad Update Now Available</a></span> 
  382. <span class="jp-relatedposts-post-context">In "Mobile"</span> 
  383. </p> 
  384. <p class="jp-relatedposts-post jp-relatedposts-post1" data-post-id="0" data-post-format="image"> 
  385. <span class="jp-relatedposts-post-title"><a $href_params>The WordPress for Android App Gets a Big Facelift</a></span> 
  386. <span class="jp-relatedposts-post-context">In "Mobile"</span> 
  387. </p> 
  388. <p class="jp-relatedposts-post jp-relatedposts-post2" data-post-id="0" data-post-format="image"> 
  389. <span class="jp-relatedposts-post-title"><a $href_params>Upgrade Focus: VideoPress For Weddings</a></span> 
  390. <span class="jp-relatedposts-post-context">In "Upgrade"</span> 
  391. </p> 
  392. </div> 
  393. EOT; 
  394. $related_without_images = str_replace( "\n", '', $related_without_images ); 
  395.  
  396. if ( $this->_allow_feature_toggle() ) { 
  397. $extra_css = '#settings-reading-relatedposts-customize { padding-left:2em; margin-top:.5em; }'; 
  398. } else { 
  399. $extra_css = ''; 
  400.  
  401. echo <<<EOT 
  402. <style type="text/css"> 
  403. #settings-reading-relatedposts .disabled { opacity:.5; filter:Alpha(opacity=50); } 
  404. #settings-reading-relatedposts-preview .jp-relatedposts { background:#fff; padding:.5em; width:75%; } 
  405. $extra_css 
  406. </style> 
  407. <script type="text/javascript"> 
  408. jQuery( document ).ready( function($) { 
  409. var update_ui = function() { 
  410. var is_enabled = true; 
  411. if ( 'radio' == $( 'input[name="jetpack_relatedposts[enabled]"]' ).attr('type') ) { 
  412. if ( '0' == $( 'input[name="jetpack_relatedposts[enabled]"]:checked' ).val() ) { 
  413. is_enabled = false; 
  414. if ( is_enabled ) { 
  415. $( '#settings-reading-relatedposts-customize' ) 
  416. .removeClass( 'disabled' ) 
  417. .find( 'input' ) 
  418. .attr( 'disabled', false ); 
  419. $( '#settings-reading-relatedposts-preview' ) 
  420. .removeClass( 'disabled' ); 
  421. } else { 
  422. $( '#settings-reading-relatedposts-customize' ) 
  423. .addClass( 'disabled' ) 
  424. .find( 'input' ) 
  425. .attr( 'disabled', true ); 
  426. $( '#settings-reading-relatedposts-preview' ) 
  427. .addClass( 'disabled' ); 
  428. }; 
  429.  
  430. var update_preview = function() { 
  431. var html = ''; 
  432. if ( $( 'input[name="jetpack_relatedposts[show_headline]"]:checked' ).size() ) { 
  433. html += '$related_headline'; 
  434. if ( $( 'input[name="jetpack_relatedposts[show_thumbnails]"]:checked' ).size() ) { 
  435. html += '$related_with_images'; 
  436. } else { 
  437. html += '$related_without_images'; 
  438. $( '#settings-reading-relatedposts-preview .jp-relatedposts' ) 
  439. .html( html ) 
  440. .show(); 
  441. }; 
  442.  
  443. // Update on load 
  444. update_preview(); 
  445. update_ui(); 
  446.  
  447. // Update on change 
  448. $( '#settings-reading-relatedposts-customize input' ) 
  449. .change( update_preview ); 
  450. $( '#settings-reading-relatedposts' ) 
  451. .find( 'input.tog' ) 
  452. .change( update_ui ); 
  453. }); 
  454. </script> 
  455. EOT; 
  456.  
  457. /** 
  458. * Gets an array of related posts that match the given post_id. 
  459. * 
  460. * @param int $post_id 
  461. * @param array $args - params to use when building ElasticSearch filters to narrow down the search domain. 
  462. * @uses self::get_options, get_post_type, wp_parse_args, apply_filters 
  463. * @return array 
  464. */ 
  465. public function get_for_post_id( $post_id, array $args ) { 
  466. $options = $this->get_options(); 
  467.  
  468. if ( ! empty( $args['size'] ) ) 
  469. $options['size'] = $args['size']; 
  470.  
  471. if ( ! $options['enabled'] || 0 == (int)$post_id || empty( $options['size'] ) ) 
  472. return array(); 
  473.  
  474. $defaults = array( 
  475. 'size' => (int)$options['size'],  
  476. 'post_type' => get_post_type( $post_id ),  
  477. 'post_formats' => array(),  
  478. 'has_terms' => array(),  
  479. 'date_range' => array(),  
  480. 'exclude_post_ids' => array(),  
  481. ); 
  482. $args = wp_parse_args( $args, $defaults ); 
  483. /** 
  484. * Filter the arguments used to retrieve a list of Related Posts. 
  485. * 
  486. * @module related-posts 
  487. * 
  488. * @since 2.8.0 
  489. * 
  490. * @param array $args Array of options to retrieve Related Posts. 
  491. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  492. */ 
  493. $args = apply_filters( 'jetpack_relatedposts_filter_args', $args, $post_id ); 
  494.  
  495. $filters = $this->_get_es_filters_from_args( $post_id, $args ); 
  496. /** 
  497. * Filter ElasticSearch options used to calculate Related Posts. 
  498. * 
  499. * @module related-posts 
  500. * 
  501. * @since 2.8.0 
  502. * 
  503. * @param array $filters Array of ElasticSearch filters based on the post_id and args. 
  504. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  505. */ 
  506. $filters = apply_filters( 'jetpack_relatedposts_filter_filters', $filters, $post_id ); 
  507.  
  508. $results = $this->_get_related_posts( $post_id, $args['size'], $filters ); 
  509. /** 
  510. * Filter the array of related posts matched by ElasticSearch. 
  511. * 
  512. * @module related-posts 
  513. * 
  514. * @since 2.8.0 
  515. * 
  516. * @param array $results Array of related posts matched by ElasticSearch. 
  517. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  518. */ 
  519. return apply_filters( 'jetpack_relatedposts_returned_results', $results, $post_id ); 
  520.  
  521. /** 
  522. * ========================= 
  523. * PRIVATE UTILITY FUNCTIONS 
  524. * ========================= 
  525. */ 
  526.  
  527. /** 
  528. * Creates an array of ElasticSearch filters based on the post_id and args. 
  529. * 
  530. * @param int $post_id 
  531. * @param array $args 
  532. * @uses apply_filters, get_post_types, get_post_format_strings 
  533. * @return array 
  534. */ 
  535. protected function _get_es_filters_from_args( $post_id, array $args ) { 
  536. $filters = array(); 
  537.  
  538. /** 
  539. * Filter the terms used to search for Related Posts. 
  540. * 
  541. * @module related-posts 
  542. * 
  543. * @since 2.8.0 
  544. * 
  545. * @param array $args['has_terms'] Array of terms associated to the Related Posts. 
  546. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  547. */ 
  548. $args['has_terms'] = apply_filters( 'jetpack_relatedposts_filter_has_terms', $args['has_terms'], $post_id ); 
  549. if ( ! empty( $args['has_terms'] ) ) { 
  550. foreach( (array)$args['has_terms'] as $term ) { 
  551. if ( mb_strlen( $term->taxonomy ) ) { 
  552. switch ( $term->taxonomy ) { 
  553. case 'post_tag': 
  554. $tax_fld = 'tag.slug'; 
  555. break; 
  556. case 'category': 
  557. $tax_fld = 'category.slug'; 
  558. break; 
  559. default: 
  560. $tax_fld = 'taxonomy.' . $term->taxonomy . '.slug'; 
  561. break; 
  562. $filters[] = array( 'term' => array( $tax_fld => $term->slug ) ); 
  563.  
  564. /** 
  565. * Filter the Post Types where we search Related Posts. 
  566. * 
  567. * @module related-posts 
  568. * 
  569. * @since 2.8.0 
  570. * 
  571. * @param array $args['post_type'] Array of Post Types. 
  572. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  573. */ 
  574. $args['post_type'] = apply_filters( 'jetpack_relatedposts_filter_post_type', $args['post_type'], $post_id ); 
  575. $valid_post_types = get_post_types(); 
  576. if ( is_array( $args['post_type'] ) ) { 
  577. $sanitized_post_types = array(); 
  578. foreach ( $args['post_type'] as $pt ) { 
  579. if ( in_array( $pt, $valid_post_types ) ) 
  580. $sanitized_post_types[] = $pt; 
  581. if ( ! empty( $sanitized_post_types ) ) 
  582. $filters[] = array( 'terms' => array( 'post_type' => $sanitized_post_types ) ); 
  583. } else if ( in_array( $args['post_type'], $valid_post_types ) && 'all' != $args['post_type'] ) { 
  584. $filters[] = array( 'term' => array( 'post_type' => $args['post_type'] ) ); 
  585.  
  586. /** 
  587. * Filter the Post Formats where we search Related Posts. 
  588. * 
  589. * @module related-posts 
  590. * 
  591. * @since 3.3.0 
  592. * 
  593. * @param array $args['post_formats'] Array of Post Formats. 
  594. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  595. */ 
  596. $args['post_formats'] = apply_filters( 'jetpack_relatedposts_filter_post_formats', $args['post_formats'], $post_id ); 
  597. $valid_post_formats = get_post_format_strings(); 
  598. $sanitized_post_formats = array(); 
  599. foreach ( $args['post_formats'] as $pf ) { 
  600. if ( array_key_exists( $pf, $valid_post_formats ) ) { 
  601. $sanitized_post_formats[] = $pf; 
  602. if ( ! empty( $sanitized_post_formats ) ) { 
  603. $filters[] = array( 'terms' => array( 'post_format' => $sanitized_post_formats ) ); 
  604.  
  605. /** 
  606. * Filter the date range used to search Related Posts. 
  607. * 
  608. * @module related-posts 
  609. * 
  610. * @since 2.8.0 
  611. * 
  612. * @param array $args['date_range'] Array of a month interval where we search Related Posts. 
  613. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  614. */ 
  615. $args['date_range'] = apply_filters( 'jetpack_relatedposts_filter_date_range', $args['date_range'], $post_id ); 
  616. if ( is_array( $args['date_range'] ) && ! empty( $args['date_range'] ) ) { 
  617. $args['date_range'] = array_map( 'intval', $args['date_range'] ); 
  618. if ( !empty( $args['date_range']['from'] ) && !empty( $args['date_range']['to'] ) ) { 
  619. $filters[] = array( 
  620. 'range' => array( 
  621. 'date_gmt' => $this->_get_coalesced_range( $args['date_range'] ),  
  622. ); 
  623.  
  624. /** 
  625. * Filter the Post IDs excluded from appearing in Related Posts. 
  626. * 
  627. * @module related-posts 
  628. * 
  629. * @since 2.9.0 
  630. * 
  631. * @param array $args['exclude_post_ids'] Array of Post IDs. 
  632. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  633. */ 
  634. $args['exclude_post_ids'] = apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', $args['exclude_post_ids'], $post_id ); 
  635. if ( !empty( $args['exclude_post_ids'] ) && is_array( $args['exclude_post_ids'] ) ) { 
  636. foreach ( $args['exclude_post_ids'] as $exclude_post_id) { 
  637. $exclude_post_id = (int)$exclude_post_id; 
  638.  
  639. if ( $exclude_post_id > 0 ) 
  640. $filters[] = array( 'not' => array( 'term' => array( 'post_id' => $exclude_post_id ) ) ); 
  641.  
  642. return $filters; 
  643.  
  644. /** 
  645. * Takes a range and coalesces it into a month interval bracketed by a time as determined by the blog_id to enhance caching. 
  646. * 
  647. * @param array $date_range 
  648. * @return array 
  649. */ 
  650. protected function _get_coalesced_range( array $date_range ) { 
  651. $now = time(); 
  652. $coalesce_time = $this->_blog_id_wpcom % 86400; 
  653. $current_time = $now - strtotime( 'today', $now ); 
  654.  
  655. if ( $current_time < $coalesce_time && '01' == date( 'd', $now ) ) { 
  656. // Move back 1 period 
  657. return array( 
  658. 'from' => date( 'Y-m-01', strtotime( '-1 month', $date_range['from'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),  
  659. 'to' => date( 'Y-m-01', $date_range['to'] ) . ' ' . date( 'H:i:s', $coalesce_time ),  
  660. ); 
  661. } else { 
  662. // Use current period 
  663. return array( 
  664. 'from' => date( 'Y-m-01', $date_range['from'] ) . ' ' . date( 'H:i:s', $coalesce_time ),  
  665. 'to' => date( 'Y-m-01', strtotime( '+1 month', $date_range['to'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),  
  666. ); 
  667.  
  668. /** 
  669. * Generate and output ajax response for related posts API call. 
  670. * NOTE: Calls exit() to end all further processing after payload has been outputed. 
  671. * 
  672. * @param array $excludes array of post_ids to exclude 
  673. * @uses send_nosniff_header, self::get_for_post_id, get_the_ID 
  674. * @return null 
  675. */ 
  676. protected function _action_frontend_init_ajax( array $excludes ) { 
  677. define( 'DOING_AJAX', true ); 
  678.  
  679. header( 'Content-type: application/json; charset=utf-8' ); // JSON can only be UTF-8 
  680. send_nosniff_header(); 
  681.  
  682. $related_posts = $this->get_for_post_id( 
  683. get_the_ID(),  
  684. array( 
  685. 'exclude_post_ids' => $excludes,  
  686. ); 
  687.  
  688. $options = $this->get_options(); 
  689.  
  690. $response = array( 
  691. 'version' => self::VERSION,  
  692. 'show_thumbnails' => (bool) $options['show_thumbnails'],  
  693. 'items' => array(),  
  694. ); 
  695.  
  696. if ( count( $related_posts ) == $options['size'] ) 
  697. $response['items'] = $related_posts; 
  698.  
  699. echo json_encode( $response ); 
  700.  
  701. exit(); 
  702.  
  703. /** 
  704. * Returns a UTF-8 encoded array of post information for the given post_id 
  705. * 
  706. * @param int $post_id 
  707. * @param int $position 
  708. * @param int $origin The post id that this is related to 
  709. * @uses get_post, get_permalink, remove_query_arg, get_post_format, apply_filters 
  710. * @return array 
  711. */ 
  712. protected function _get_related_post_data_for_post( $post_id, $position, $origin ) { 
  713. $post = get_post( $post_id ); 
  714.  
  715. return array( 
  716. 'id' => $post->ID,  
  717. 'url' => get_permalink( $post->ID ),  
  718. 'url_meta' => array( 'origin' => $origin, 'position' => $position ),  
  719. 'title' => $this->_to_utf8( $this->_get_title( $post->post_title, $post->post_content ) ),  
  720. 'date' => get_the_date( '', $post->ID ),  
  721. 'format' => get_post_format( $post->ID ),  
  722. /** 
  723. * Filters the rel attribute for the Related Posts' links. 
  724. * 
  725. * @module related-posts 
  726. * 
  727. * @since 3.7.0 
  728. * 
  729. * @param string nofollow Link rel attribute for Related Posts' link. Default is nofollow. 
  730. * @param int $post->ID Post ID. 
  731. */ 
  732. 'rel' => apply_filters( 'jetpack_relatedposts_filter_post_link_rel', 'nofollow', $post->ID ),  
  733. 'excerpt' => html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $post->post_excerpt, $post->post_content ) ), ENT_QUOTES, 'UTF-8' ),  
  734. /** 
  735. * Filter the context displayed below each Related Post. 
  736. * 
  737. * @module related-posts 
  738. * 
  739. * @since 3.0.0 
  740. * 
  741. * @param string $this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ) Context displayed below each related post. 
  742. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  743. */ 
  744. 'context' => apply_filters( 
  745. 'jetpack_relatedposts_filter_post_context',  
  746. $this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ),  
  747. $post->ID 
  748. ),  
  749. 'img' => $this->_generate_related_post_image_params( $post->ID ),  
  750. /** 
  751. * Filter the post css classes added on HTML markup. 
  752. * 
  753. * @module related-posts 
  754. * 
  755. * @since 3.8.0 
  756. * 
  757. * @param array array() CSS classes added on post HTML markup. 
  758. * @param string $post_id Post ID. 
  759. */ 
  760. 'classes' => apply_filters( 
  761. 'jetpack_relatedposts_filter_post_css_classes',  
  762. array(),  
  763. $post->ID 
  764. ),  
  765. ); 
  766.  
  767. /** 
  768. * Returns either the title or a small excerpt to use as title for post. 
  769. * 
  770. * @param string $post_title 
  771. * @param string $post_content 
  772. * @uses strip_shortcodes, wp_trim_words, __ 
  773. * @return string 
  774. */ 
  775. protected function _get_title( $post_title, $post_content ) { 
  776. if ( ! empty( $post_title ) ) { 
  777. return wp_strip_all_tags( $post_title ); 
  778.  
  779. $post_title = wp_trim_words( wp_strip_all_tags( strip_shortcodes( $post_content ) ), 5, '*' ); 
  780. if ( ! empty( $post_title ) ) { 
  781. return $post_title; 
  782.  
  783. return __( 'Untitled Post', 'jetpack' ); 
  784.  
  785. /** 
  786. * Returns a plain text post excerpt for title attribute of links. 
  787. * 
  788. * @param string $post_excerpt 
  789. * @param string $post_content 
  790. * @uses strip_shortcodes, wp_strip_all_tags, wp_trim_words 
  791. * @return string 
  792. */ 
  793. protected function _get_excerpt( $post_excerpt, $post_content ) { 
  794. if ( empty( $post_excerpt ) ) 
  795. $excerpt = $post_content; 
  796. else 
  797. $excerpt = $post_excerpt; 
  798.  
  799. return wp_trim_words( wp_strip_all_tags( strip_shortcodes( $excerpt ) ), 50, '*' ); 
  800.  
  801. /** 
  802. * Generates the thumbnail image to be used for the post. Uses the 
  803. * image as returned by Jetpack_PostImages::get_image() 
  804. * 
  805. * @param int $post_id 
  806. * @uses self::get_options, apply_filters, Jetpack_PostImages::get_image, Jetpack_PostImages::fit_image_url 
  807. * @return string 
  808. */ 
  809. protected function _generate_related_post_image_params( $post_id ) { 
  810. $options = $this->get_options(); 
  811. $image_params = array( 
  812. 'src' => '',  
  813. 'width' => 0,  
  814. 'height' => 0,  
  815. ); 
  816.  
  817. if ( ! $options['show_thumbnails'] ) { 
  818. return $image_params; 
  819.  
  820. /** 
  821. * Filter the size of the Related Posts images. 
  822. * 
  823. * @module related-posts 
  824. * 
  825. * @since 2.8.0 
  826. * 
  827. * @param array array( 'width' => 350, 'height' => 200 ) Size of the images displayed below each Related Post. 
  828. */ 
  829. $thumbnail_size = apply_filters( 
  830. 'jetpack_relatedposts_filter_thumbnail_size',  
  831. array( 'width' => 350, 'height' => 200 ) 
  832. ); 
  833. if ( !is_array( $thumbnail_size ) ) { 
  834. $thumbnail_size = array( 
  835. 'width' => (int)$thumbnail_size,  
  836. 'height' => (int)$thumbnail_size 
  837. ); 
  838.  
  839. // Try to get post image 
  840. if ( class_exists( 'Jetpack_PostImages' ) ) { 
  841. $img_url = ''; 
  842. $post_image = Jetpack_PostImages::get_image( 
  843. $post_id,  
  844. $thumbnail_size 
  845. ); 
  846.  
  847. if ( is_array($post_image) ) { 
  848. $img_url = $post_image['src']; 
  849. } elseif ( class_exists( 'Jetpack_Media_Summary' ) ) { 
  850. $media = Jetpack_Media_Summary::get( $post_id ); 
  851.  
  852. if ( is_array($media) && !empty( $media['image'] ) ) { 
  853. $img_url = $media['image']; 
  854.  
  855. if ( !empty( $img_url ) ) { 
  856. $image_params['width'] = $thumbnail_size['width']; 
  857. $image_params['height'] = $thumbnail_size['height']; 
  858. $image_params['src'] = Jetpack_PostImages::fit_image_url( 
  859. $img_url,  
  860. $thumbnail_size['width'],  
  861. $thumbnail_size['height'] 
  862. ); 
  863.  
  864. return $image_params; 
  865.  
  866. /** 
  867. * Returns the string UTF-8 encoded 
  868. * 
  869. * @param string $text 
  870. * @return string 
  871. */ 
  872. protected function _to_utf8( $text ) { 
  873. if ( $this->_convert_charset ) { 
  874. return iconv( $this->_blog_charset, 'UTF-8', $text ); 
  875. } else { 
  876. return $text; 
  877.  
  878. /** 
  879. * ============================================= 
  880. * PROTECTED UTILITY FUNCTIONS EXTENDED BY WPCOM 
  881. * ============================================= 
  882. */ 
  883.  
  884. /** 
  885. * Workhorse method to return array of related posts matched by ElasticSearch. 
  886. * 
  887. * @param int $post_id 
  888. * @param int $size 
  889. * @param array $filters 
  890. * @uses wp_remote_post, is_wp_error, get_option, wp_remote_retrieve_body, get_post, add_query_arg, remove_query_arg, get_permalink, get_post_format, apply_filters 
  891. * @return array 
  892. */ 
  893. protected function _get_related_posts( $post_id, $size, array $filters ) { 
  894. $hits = $this->_filter_non_public_posts( 
  895. $this->_get_related_post_ids( 
  896. $post_id,  
  897. $size,  
  898. $filters 
  899. ); 
  900.  
  901. /** 
  902. * Filter the Related Posts matched by ElasticSearch. 
  903. * 
  904. * @module related-posts 
  905. * 
  906. * @since 2.9.0 
  907. * 
  908. * @param array $hits Array of Post IDs matched by ElasticSearch. 
  909. * @param string $post_id Post ID of the post for which we are retrieving Related Posts. 
  910. */ 
  911. $hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id ); 
  912.  
  913. $related_posts = array(); 
  914. foreach ( $hits as $i => $hit ) { 
  915. $related_posts[] = $this->_get_related_post_data_for_post( $hit['id'], $i, $post_id ); 
  916. return $related_posts; 
  917.  
  918. /** 
  919. * Get array of related posts matched by ElasticSearch. 
  920. * 
  921. * @param int $post_id 
  922. * @param int $size 
  923. * @param array $filters 
  924. * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body, get_post_meta, update_post_meta 
  925. * @return array 
  926. */ 
  927. protected function _get_related_post_ids( $post_id, $size, array $filters ) { 
  928. $now_ts = time(); 
  929. $cache_meta_key = '_jetpack_related_posts_cache'; 
  930.  
  931. $body = array( 
  932. 'size' => (int) $size,  
  933. ); 
  934.  
  935. if ( !empty( $filters ) ) 
  936. $body['filter'] = array( 'and' => $filters ); 
  937.  
  938. // Load all cached values 
  939. $cache = get_post_meta( $post_id, $cache_meta_key, true ); 
  940. if ( empty( $cache ) ) 
  941. $cache = array(); 
  942.  
  943. // Build cache key 
  944. $cache_key = md5( serialize( $body ) ); 
  945.  
  946. // Cache is valid! Return cached value. 
  947. if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) && $cache[ $cache_key ][ 'expires' ] > $now_ts ) { 
  948. return $cache[ $cache_key ][ 'payload' ]; 
  949.  
  950. $response = wp_remote_post( 
  951. "https://public-api.wordpress.com/rest/v1/sites/{$this->_blog_id_wpcom}/posts/$post_id/related/",  
  952. array( 
  953. 'timeout' => 10,  
  954. 'user-agent' => 'jetpack_related_posts',  
  955. 'sslverify' => true,  
  956. 'body' => $body,  
  957. ); 
  958.  
  959. // Oh no... return nothing don't cache errors. 
  960. if ( is_wp_error( $response ) ) { 
  961. if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) ) 
  962. return $cache[ $cache_key ][ 'payload' ]; // return stale 
  963. else 
  964. return array(); 
  965.  
  966. $results = json_decode( wp_remote_retrieve_body( $response ), true ); 
  967. $related_posts = array(); 
  968. if ( is_array( $results ) && !empty( $results['hits'] ) ) { 
  969. foreach( $results['hits'] as $hit ) { 
  970. $related_posts[] = array( 
  971. 'id' => $hit['fields']['post_id'],  
  972. ); 
  973.  
  974. // Copy all valid cache values 
  975. $new_cache = array(); 
  976. foreach ( $cache as $k => $v ) { 
  977. if ( is_array( $v ) && $v[ 'expires' ] > $now_ts ) { 
  978. $new_cache[ $k ] = $v; 
  979.  
  980. // Set new cache value if valid 
  981. if ( ! empty( $related_posts ) ) { 
  982. $new_cache[ $cache_key ] = array( 
  983. 'expires' => 12 * HOUR_IN_SECONDS + $now_ts,  
  984. 'payload' => $related_posts,  
  985. ); 
  986.  
  987. // Update cache 
  988. update_post_meta( $post_id, $cache_meta_key, $new_cache ); 
  989.  
  990. return $related_posts; 
  991.  
  992. /** 
  993. * Filter out any hits that are not public anymore. 
  994. * 
  995. * @param array $related_posts 
  996. * @uses get_post_stati, get_post_status 
  997. * @return array 
  998. */ 
  999. protected function _filter_non_public_posts( array $related_posts ) { 
  1000. $public_stati = get_post_stati( array( 'public' => true ) ); 
  1001.  
  1002. $filtered = array(); 
  1003. foreach ( $related_posts as $hit ) { 
  1004. if ( in_array( get_post_status( $hit['id'] ), $public_stati ) ) { 
  1005. $filtered[] = $hit; 
  1006. return $filtered; 
  1007.  
  1008. /** 
  1009. * Generates a context for the related content (second line in related post output). 
  1010. * Order of importance: 
  1011. * - First category (Not 'Uncategorized') 
  1012. * - First post tag 
  1013. * - Number of comments 
  1014. * 
  1015. * @param int $post_id 
  1016. * @uses get_the_category, get_the_terms, get_comments_number, number_format_i18n, __, _n 
  1017. * @return string 
  1018. */ 
  1019. protected function _generate_related_post_context( $post_id ) { 
  1020. $categories = get_the_category( $post_id ); 
  1021. if ( is_array( $categories ) ) { 
  1022. foreach ( $categories as $category ) { 
  1023. if ( 'uncategorized' != $category->slug && '' != trim( $category->name ) ) { 
  1024. $post_cat_context = sprintf( 
  1025. _x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),  
  1026. $category->name 
  1027. ); 
  1028. /** 
  1029. * Filter the "In Category" line displayed in the post context below each Related Post. 
  1030. * 
  1031. * @module related-posts 
  1032. * 
  1033. * @since 3.2.0 
  1034. * 
  1035. * @param string $post_cat_context "In Category" line displayed in the post context below each Related Post. 
  1036. * @param array $category Array containing information about the category. 
  1037. */ 
  1038. return apply_filters( 'jetpack_relatedposts_post_category_context', $post_cat_context, $category ); 
  1039.  
  1040. $tags = get_the_terms( $post_id, 'post_tag' ); 
  1041. if ( is_array( $tags ) ) { 
  1042. foreach ( $tags as $tag ) { 
  1043. if ( '' != trim( $tag->name ) ) { 
  1044. $post_tag_context = sprintf( 
  1045. _x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),  
  1046. $tag->name 
  1047. ); 
  1048. /** 
  1049. * Filter the "In Tag" line displayed in the post context below each Related Post. 
  1050. * 
  1051. * @module related-posts 
  1052. * 
  1053. * @since 3.2.0 
  1054. * 
  1055. * @param string $post_tag_context "In Tag" line displayed in the post context below each Related Post. 
  1056. * @param array $tag Array containing information about the tag. 
  1057. */ 
  1058. return apply_filters( 'jetpack_relatedposts_post_tag_context', $post_tag_context, $tag ); 
  1059.  
  1060. $comment_count = get_comments_number( $post_id ); 
  1061. if ( $comment_count > 0 ) { 
  1062. return sprintf( 
  1063. _n( 'With 1 comment', 'With %s comments', $comment_count, 'jetpack' ),  
  1064. number_format_i18n( $comment_count ) 
  1065. ); 
  1066.  
  1067. return __( 'Similar post', 'jetpack' ); 
  1068.  
  1069. /** 
  1070. * Logs clicks for clickthrough analysis and related result tuning. 
  1071. * 
  1072. * @return null 
  1073. */ 
  1074. protected function _log_click( $post_id, $to_post_id, $link_position ) { 
  1075.  
  1076.  
  1077. /** 
  1078. * Determines if the current post is able to use related posts. 
  1079. * 
  1080. * @uses self::get_options, is_admin, is_single, apply_filters 
  1081. * @return bool 
  1082. */ 
  1083. protected function _enabled_for_request() { 
  1084. // Default to enabled 
  1085. $enabled = true; 
  1086.  
  1087. // Must have feature enabled 
  1088. $options = $this->get_options(); 
  1089. if ( ! $options['enabled'] ) { 
  1090. $enabled = false; 
  1091.  
  1092. // Only run for frontend pages 
  1093. if ( is_admin() ) { 
  1094. $enabled = false; 
  1095.  
  1096. // Only run for standalone posts 
  1097. if ( ! is_single() ) { 
  1098. $enabled = false; 
  1099.  
  1100. /** 
  1101. * Filter the Enabled value to allow related posts to be shown on pages as well. 
  1102. * 
  1103. * @module related-posts 
  1104. * 
  1105. * @since 3.3.0 
  1106. * 
  1107. * @param bool $enabled Should Related Posts be enabled on the current page. 
  1108. */ 
  1109. return apply_filters( 'jetpack_relatedposts_filter_enabled_for_request', $enabled ); 
  1110.  
  1111. /** 
  1112. * Adds filters and enqueues assets. 
  1113. * 
  1114. * @uses self::_enqueue_assets, self::_setup_shortcode, add_filter 
  1115. * @return null 
  1116. */ 
  1117. protected function _action_frontend_init_page() { 
  1118. $this->_enqueue_assets( true, true ); 
  1119. $this->_setup_shortcode(); 
  1120.  
  1121. add_filter( 'the_content', array( $this, 'filter_add_target_to_dom' ), 40 ); 
  1122.  
  1123. /** 
  1124. * Enqueues assets needed to do async loading of related posts. 
  1125. * 
  1126. * @uses wp_enqueue_script, wp_enqueue_style, plugins_url 
  1127. * @return null 
  1128. */ 
  1129. protected function _enqueue_assets( $script, $style ) { 
  1130. if ( $script ) 
  1131. wp_enqueue_script( 'jetpack_related-posts', plugins_url( 'related-posts.js', __FILE__ ), array( 'jquery' ), self::VERSION ); 
  1132. if ( $style ) { 
  1133. if( is_rtl() ) { 
  1134. wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'rtl/related-posts-rtl.css', __FILE__ ), array(), self::VERSION ); 
  1135. } else { 
  1136. wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'related-posts.css', __FILE__ ), array(), self::VERSION ); 
  1137.  
  1138. /** 
  1139. * Sets up the shortcode processing. 
  1140. * 
  1141. * @uses add_filter, add_shortcode 
  1142. * @return null 
  1143. */ 
  1144. protected function _setup_shortcode() { 
  1145. add_filter( 'the_content', array( $this, 'test_for_shortcode' ), 0 ); 
  1146.  
  1147. add_shortcode( self::SHORTCODE, array( $this, 'get_target_html' ) ); 
  1148.  
  1149. protected function _allow_feature_toggle() { 
  1150. if ( null === $this->_allow_feature_toggle ) { 
  1151. /** 
  1152. * Filter the display of the Related Posts toggle in Settings > Reading. 
  1153. * 
  1154. * @module related-posts 
  1155. * 
  1156. * @since 2.8.0 
  1157. * 
  1158. * @param bool false Display a feature toggle. Default to false. 
  1159. */ 
  1160. $this->_allow_feature_toggle = apply_filters( 'jetpack_relatedposts_filter_allow_feature_toggle', false ); 
  1161. return $this->_allow_feature_toggle; 
  1162.  
  1163. class Jetpack_RelatedPosts_Raw extends Jetpack_RelatedPosts { 
  1164. protected $_query_name; 
  1165.  
  1166. /** 
  1167. * Allows callers of this class to tag each query with a unique name for tracking purposes. 
  1168. * 
  1169. * @param string $name 
  1170. * @return Jetpack_RelatedPosts_Raw 
  1171. */ 
  1172. public function set_query_name( $name ) { 
  1173. $this->_query_name = (string) $name; 
  1174. return $this; 
  1175.  
  1176. /** 
  1177. * The raw related posts class can be used by other plugins or themes 
  1178. * to get related content. This class wraps the existing RelatedPosts 
  1179. * logic thus we never want to add anything to the DOM or do anything 
  1180. * for event hooks. We will also not present any settings for this 
  1181. * class and keep it enabled as calls to this class is done 
  1182. * programmatically. 
  1183. */ 
  1184. public function action_admin_init() {} 
  1185. public function action_frontend_init() {} 
  1186. public function get_options() { 
  1187. return array( 
  1188. 'enabled' => true,  
  1189. ); 
  1190.  
  1191. /** 
  1192. * Workhorse method to return array of related posts ids matched by ElasticSearch. 
  1193. * 
  1194. * @param int $post_id 
  1195. * @param int $size 
  1196. * @param array $filters 
  1197. * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body 
  1198. * @return array 
  1199. */ 
  1200. protected function _get_related_posts( $post_id, $size, array $filters ) { 
  1201. $hits = $this->_filter_non_public_posts( 
  1202. $this->_get_related_post_ids( 
  1203. $post_id,  
  1204. $size,  
  1205. $filters 
  1206. ); 
  1207.  
  1208. /** This filter is already documented in modules/related-posts/related-posts.php */ 
  1209. $hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id ); 
  1210.  
  1211. return $hits; 
.