Нема описа

jetpack-related-posts.php 57KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846
  1. <?php
  2. use Automattic\Jetpack\Assets;
  3. use Automattic\Jetpack\Blocks;
  4. use Automattic\Jetpack\Sync\Settings;
  5. class Jetpack_RelatedPosts {
  6. const VERSION = '20210930';
  7. const SHORTCODE = 'jetpack-related-posts';
  8. private static $instance = null;
  9. private static $instance_raw = null;
  10. /**
  11. * Creates and returns a static instance of Jetpack_RelatedPosts.
  12. *
  13. * @return Jetpack_RelatedPosts
  14. */
  15. public static function init() {
  16. if ( ! self::$instance ) {
  17. if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init' ) ) {
  18. self::$instance = WPCOM_RelatedPosts::init();
  19. } else {
  20. self::$instance = new Jetpack_RelatedPosts();
  21. }
  22. }
  23. return self::$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. if ( ! self::$instance_raw ) {
  32. if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init_raw' ) ) {
  33. self::$instance_raw = WPCOM_RelatedPosts::init_raw();
  34. } else {
  35. self::$instance_raw = new Jetpack_RelatedPosts_Raw();
  36. }
  37. }
  38. return self::$instance_raw;
  39. }
  40. protected $_options;
  41. protected $_allow_feature_toggle;
  42. protected $_blog_charset;
  43. protected $_convert_charset;
  44. protected $_previous_post_id;
  45. protected $_found_shortcode = false;
  46. /**
  47. * Constructor for Jetpack_RelatedPosts.
  48. *
  49. * @uses get_option, add_action, apply_filters
  50. *
  51. * @return null
  52. */
  53. public function __construct() {
  54. $this->_blog_charset = get_option( 'blog_charset' );
  55. $this->_convert_charset = ( function_exists( 'iconv' ) && ! preg_match( '/^utf\-?8$/i', $this->_blog_charset ) );
  56. add_action( 'admin_init', array( $this, 'action_admin_init' ) );
  57. add_action( 'wp', array( $this, 'action_frontend_init' ) );
  58. if ( ! class_exists( 'Jetpack_Media_Summary' ) ) {
  59. jetpack_require_lib( 'class.media-summary' );
  60. }
  61. // Add Related Posts to the REST API Post response.
  62. add_action( 'rest_api_init', array( $this, 'rest_register_related_posts' ) );
  63. Blocks::jetpack_register_block(
  64. 'jetpack/related-posts',
  65. array(
  66. 'render_callback' => array( $this, 'render_block' ),
  67. )
  68. );
  69. }
  70. protected function get_blog_id() {
  71. return Jetpack_Options::get_option( 'id' );
  72. }
  73. /**
  74. * =================
  75. * ACTIONS & FILTERS
  76. * =================
  77. */
  78. /**
  79. * Add a checkbox field to Settings > Reading for enabling related posts.
  80. *
  81. * @action admin_init
  82. * @uses add_settings_field, __, register_setting, add_action
  83. * @return null
  84. */
  85. public function action_admin_init() {
  86. // Add the setting field [jetpack_relatedposts] and place it in Settings > Reading
  87. add_settings_field( 'jetpack_relatedposts', '<span id="jetpack_relatedposts">' . __( 'Related posts', 'jetpack' ) . '</span>', array( $this, 'print_setting_html' ), 'reading' );
  88. register_setting( 'reading', 'jetpack_relatedposts', array( $this, 'parse_options' ) );
  89. add_action('admin_head', array( $this, 'print_setting_head' ) );
  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. /**
  96. * Load related posts assets if it's an eligible front end page or execute search and return JSON if it's an endpoint request.
  97. *
  98. * @global $_GET
  99. * @action wp
  100. * @uses add_shortcode, get_the_ID
  101. * @returns null
  102. */
  103. public function action_frontend_init() {
  104. // Add a shortcode handler that outputs nothing, this gets overridden later if we can display related content
  105. add_shortcode( self::SHORTCODE, array( $this, 'get_client_rendered_html_unsupported' ) );
  106. if ( ! $this->_enabled_for_request() )
  107. return;
  108. if ( isset( $_GET['relatedposts'] ) ) {
  109. $excludes = $this->parse_numeric_get_arg( 'relatedposts_exclude' );
  110. $this->_action_frontend_init_ajax( $excludes );
  111. } else {
  112. if ( isset( $_GET['relatedposts_hit'], $_GET['relatedposts_origin'], $_GET['relatedposts_position'] ) ) {
  113. $this->_log_click( $_GET['relatedposts_origin'], get_the_ID(), $_GET['relatedposts_position'] );
  114. $this->_previous_post_id = (int) $_GET['relatedposts_origin'];
  115. }
  116. $this->_action_frontend_init_page();
  117. }
  118. }
  119. /**
  120. * Render insertion point.
  121. *
  122. * @since 4.2.0
  123. *
  124. * @return string
  125. */
  126. public function get_headline() {
  127. $options = $this->get_options();
  128. if ( $options['show_headline'] ) {
  129. $headline = sprintf(
  130. /** This filter is already documented in modules/sharedaddy/sharing-service.php */
  131. apply_filters( 'jetpack_sharing_headline_html', '<h3 class="jp-relatedposts-headline"><em>%s</em></h3>', esc_html( $options['headline'] ), 'related-posts' ),
  132. esc_html( $options['headline'] )
  133. );
  134. } else {
  135. $headline = '';
  136. }
  137. return $headline;
  138. }
  139. /**
  140. * Adds a target to the post content to load related posts into if a shortcode for it did not already exist.
  141. * Will skip adding the target if the post content contains a Related Posts block, if the 'get_the_excerpt'
  142. * hook is in the current filter list, or if the site is running an FSE/Site Editor theme.
  143. *
  144. * @filter the_content
  145. *
  146. * @param string $content Post content.
  147. *
  148. * @returns string
  149. */
  150. public function filter_add_target_to_dom( $content ) {
  151. if ( has_block( 'jetpack/related-posts' ) || Blocks::is_fse_theme() ) {
  152. return $content;
  153. }
  154. if ( ! $this->_found_shortcode && ! doing_filter( 'get_the_excerpt' ) ) {
  155. if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
  156. $content .= "\n" . $this->get_server_rendered_html();
  157. } else {
  158. $content .= "\n" . $this->get_client_rendered_html();
  159. }
  160. }
  161. return $content;
  162. }
  163. /**
  164. * Render static markup based on the Gutenberg block code
  165. *
  166. * @return string Rendered related posts HTML.
  167. */
  168. public function get_server_rendered_html() {
  169. $rp_settings = $this->get_options();
  170. $block_rp_settings = array(
  171. 'displayThumbnails' => $rp_settings['show_thumbnails'],
  172. 'showHeadline' => $rp_settings['show_headline'],
  173. 'displayDate' => isset( $rp_settings['show_date'] ) ? (bool) $rp_settings['show_date'] : true,
  174. 'displayContext' => isset( $rp_settings['show_context'] ) && $rp_settings['show_context'],
  175. 'postLayout' => isset( $rp_settings['layout'] ) ? $rp_settings['layout'] : 'grid',
  176. 'postsToShow' => isset( $rp_settings['size'] ) ? $rp_settings['size'] : 3,
  177. /** This filter is already documented in modules/related-posts/jetpack-related-posts.php */
  178. 'headline' => apply_filters( 'jetpack_relatedposts_filter_headline', $this->get_headline() ),
  179. );
  180. return $this->render_block( $block_rp_settings );
  181. }
  182. /**
  183. * Looks for our shortcode on the unfiltered content, this has to execute early.
  184. *
  185. * @filter the_content
  186. * @param string $content
  187. * @uses has_shortcode
  188. * @returns string
  189. */
  190. public function test_for_shortcode( $content ) {
  191. $this->_found_shortcode = has_shortcode( $content, self::SHORTCODE );
  192. return $content;
  193. }
  194. /**
  195. * Returns the HTML for the related posts section.
  196. *
  197. * @uses esc_html__, apply_filters
  198. * @returns string
  199. */
  200. public function get_client_rendered_html() {
  201. if ( Settings::is_syncing() ) {
  202. return '';
  203. }
  204. /**
  205. * Filter the Related Posts headline.
  206. *
  207. * @module related-posts
  208. *
  209. * @since 3.0.0
  210. *
  211. * @param string $headline Related Posts heading.
  212. */
  213. $headline = apply_filters( 'jetpack_relatedposts_filter_headline', $this->get_headline() );
  214. if ( $this->_previous_post_id ) {
  215. $exclude = "data-exclude='{$this->_previous_post_id}'";
  216. } else {
  217. $exclude = "";
  218. }
  219. return <<<EOT
  220. <div id='jp-relatedposts' class='jp-relatedposts' $exclude>
  221. $headline
  222. </div>
  223. EOT;
  224. }
  225. /**
  226. * 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.
  227. *
  228. * @returns string
  229. */
  230. public function get_client_rendered_html_unsupported() {
  231. if ( Settings::is_syncing() ) {
  232. return '';
  233. }
  234. return "\n\n<!-- Jetpack Related Posts is not supported in this context. -->\n\n";
  235. }
  236. /**
  237. * ===============
  238. * GUTENBERG BLOCK
  239. * ===============
  240. */
  241. /**
  242. * Echoes out items for the Gutenberg block
  243. *
  244. * @param array $related_post The post oject.
  245. * @param array $block_attributes The block attributes.
  246. */
  247. public function render_block_item( $related_post, $block_attributes ) {
  248. $instance_id = 'related-posts-item-' . uniqid();
  249. $label_id = $instance_id . '-label';
  250. $item_markup = sprintf(
  251. '<ul id="%1$s" aria-labelledby="%2$s" class="jp-related-posts-i2__post" role="menuitem">',
  252. esc_attr( $instance_id ),
  253. esc_attr( $label_id )
  254. );
  255. $item_markup .= sprintf(
  256. '<li class="jp-related-posts-i2__post-link"><a id="%1$s" href="%2$s" %4$s>%3$s</a></li>',
  257. esc_attr( $label_id ),
  258. esc_url( $related_post['url'] ),
  259. esc_attr( $related_post['title'] ),
  260. ( ! empty( $related_post['rel'] ) ? 'rel="' . esc_attr( $related_post['rel'] ) . '"' : '' )
  261. );
  262. if ( ! empty( $block_attributes['show_thumbnails'] ) && ! empty( $related_post['img']['src'] ) ) {
  263. $img_link = sprintf(
  264. '<li class="jp-related-posts-i2__post-img-link"><a href="%1$s" %2$s><img src="%3$s" width="%4$s" height="%5$s" alt="%6$s" loading="lazy" /></a></li>',
  265. esc_url( $related_post['url'] ),
  266. ( ! empty( $related_post['rel'] ) ? 'rel="' . esc_attr( $related_post['rel'] ) . '"' : '' ),
  267. esc_url( $related_post['img']['src'] ),
  268. esc_attr( $related_post['img']['width'] ),
  269. esc_attr( $related_post['img']['height'] ),
  270. esc_attr( $related_post['img']['alt_text'] )
  271. );
  272. $item_markup .= $img_link;
  273. }
  274. if ( $block_attributes['show_date'] ) {
  275. $date_tag = sprintf(
  276. '<li class="jp-related-posts-i2__post-date">%1$s</li>',
  277. esc_html( $related_post['date'] )
  278. );
  279. $item_markup .= $date_tag;
  280. }
  281. if ( ( $block_attributes['show_context'] ) && ! empty( $related_post['context'] ) ) {
  282. $context_tag = sprintf(
  283. '<li class="jp-related-posts-i2__post-context">%1$s</li>',
  284. esc_html( $related_post['context'] )
  285. );
  286. $item_markup .= $context_tag;
  287. }
  288. $item_markup .= '</ul>';
  289. return $item_markup;
  290. }
  291. /**
  292. * Render a related posts row.
  293. *
  294. * @param array $posts The posts to render into the row.
  295. * @param array $block_attributes Block attributes.
  296. */
  297. public function render_block_row( $posts, $block_attributes ) {
  298. $rows_markup = '';
  299. foreach ( $posts as $post ) {
  300. $rows_markup .= $this->render_block_item( $post, $block_attributes );
  301. }
  302. return sprintf(
  303. '<div class="jp-related-posts-i2__row" data-post-count="%1$s">%2$s</div>',
  304. count( $posts ),
  305. $rows_markup
  306. );
  307. }
  308. /**
  309. * Render the related posts markup.
  310. *
  311. * @param array $attributes Block attributes.
  312. * @return string
  313. */
  314. public function render_block( $attributes ) {
  315. $block_attributes = array(
  316. 'headline' => isset( $attributes['headline'] ) ? $attributes['headline'] : null,
  317. 'show_thumbnails' => isset( $attributes['displayThumbnails'] ) && $attributes['displayThumbnails'],
  318. 'show_date' => isset( $attributes['displayDate'] ) ? (bool) $attributes['displayDate'] : true,
  319. 'show_context' => isset( $attributes['displayContext'] ) && $attributes['displayContext'],
  320. 'layout' => isset( $attributes['postLayout'] ) && 'list' === $attributes['postLayout'] ? $attributes['postLayout'] : 'grid',
  321. 'size' => ! empty( $attributes['postsToShow'] ) ? absint( $attributes['postsToShow'] ) : 3,
  322. );
  323. $excludes = $this->parse_numeric_get_arg( 'relatedposts_origin' );
  324. $related_posts = $this->get_for_post_id(
  325. get_the_ID(),
  326. array(
  327. 'size' => $block_attributes['size'],
  328. 'exclude_post_ids' => $excludes,
  329. )
  330. );
  331. $display_lower_row = $block_attributes['size'] > 3;
  332. if ( empty( $related_posts ) ) {
  333. return '';
  334. }
  335. switch ( count( $related_posts ) ) {
  336. case 2:
  337. case 4:
  338. case 5:
  339. $top_row_end = 2;
  340. break;
  341. default:
  342. $top_row_end = 3;
  343. break;
  344. }
  345. $upper_row_posts = array_slice( $related_posts, 0, $top_row_end );
  346. $lower_row_posts = array_slice( $related_posts, $top_row_end );
  347. $rows_markup = $this->render_block_row( $upper_row_posts, $block_attributes );
  348. if ( $display_lower_row ) {
  349. $rows_markup .= $this->render_block_row( $lower_row_posts, $block_attributes );
  350. }
  351. return sprintf(
  352. '<nav class="jp-relatedposts-i2" data-layout="%1$s">%2$s%3$s</nav>',
  353. esc_attr( $block_attributes['layout'] ),
  354. $block_attributes['headline'],
  355. $rows_markup
  356. );
  357. }
  358. /**
  359. * ========================
  360. * PUBLIC UTILITY FUNCTIONS
  361. * ========================
  362. */
  363. /**
  364. * Parse a numeric GET variable to an array of values.
  365. *
  366. * @since 6.9.0
  367. *
  368. * @uses absint
  369. *
  370. * @param string $arg Name of the GET variable.
  371. * @return array $result Parsed value(s)
  372. */
  373. public function parse_numeric_get_arg( $arg ) {
  374. $result = array();
  375. if ( isset( $_GET[ $arg ] ) ) {
  376. if ( is_string( $_GET[ $arg ] ) ) {
  377. $result = explode( ',', $_GET[ $arg ] );
  378. } elseif ( is_array( $_GET[ $arg ] ) ) {
  379. $result = array_values( $_GET[ $arg ] );
  380. }
  381. $result = array_unique( array_filter( array_map( 'absint', $result ) ) );
  382. }
  383. return $result;
  384. }
  385. /**
  386. * Gets options set for Jetpack_RelatedPosts and merge with defaults.
  387. *
  388. * @uses Jetpack_Options::get_option, apply_filters
  389. * @return array
  390. */
  391. public function get_options() {
  392. if ( null === $this->_options ) {
  393. $this->_options = Jetpack_Options::get_option( 'relatedposts', array() );
  394. if ( ! is_array( $this->_options ) )
  395. $this->_options = array();
  396. if ( ! isset( $this->_options['enabled'] ) )
  397. $this->_options['enabled'] = true;
  398. if ( ! isset( $this->_options['show_headline'] ) )
  399. $this->_options['show_headline'] = true;
  400. if ( ! isset( $this->_options['show_thumbnails'] ) )
  401. $this->_options['show_thumbnails'] = false;
  402. if ( ! isset( $this->_options['show_date'] ) ) {
  403. $this->_options['show_date'] = true;
  404. }
  405. if ( ! isset( $this->_options['show_context'] ) ) {
  406. $this->_options['show_context'] = true;
  407. }
  408. if ( ! isset( $this->_options['layout'] ) ) {
  409. $this->_options['layout'] = 'grid';
  410. }
  411. if ( ! isset( $this->_options['headline'] ) ) {
  412. $this->_options['headline'] = esc_html__( 'Related', 'jetpack' );
  413. }
  414. if ( empty( $this->_options['size'] ) || (int)$this->_options['size'] < 1 )
  415. $this->_options['size'] = 3;
  416. /**
  417. * Filter Related Posts basic options.
  418. *
  419. * @module related-posts
  420. *
  421. * @since 2.8.0
  422. *
  423. * @param array $this->_options Array of basic Related Posts options.
  424. */
  425. $this->_options = apply_filters( 'jetpack_relatedposts_filter_options', $this->_options );
  426. }
  427. return $this->_options;
  428. }
  429. public function get_option( $option_name ) {
  430. $options = $this->get_options();
  431. if ( isset( $options[ $option_name ] ) ) {
  432. return $options[ $option_name ];
  433. }
  434. return false;
  435. }
  436. /**
  437. * Parses input and returns normalized options array.
  438. *
  439. * @param array $input
  440. * @uses self::get_options
  441. * @return array
  442. */
  443. public function parse_options( $input ) {
  444. $current = $this->get_options();
  445. if ( !is_array( $input ) )
  446. $input = array();
  447. if (
  448. ! isset( $input['enabled'] )
  449. || isset( $input['show_date'] )
  450. || isset( $input['show_context'] )
  451. || isset( $input['layout'] )
  452. || isset( $input['headline'] )
  453. ) {
  454. $input['enabled'] = '1';
  455. }
  456. if ( '1' == $input['enabled'] ) {
  457. $current['enabled'] = true;
  458. $current['show_headline'] = ( isset( $input['show_headline'] ) && '1' == $input['show_headline'] );
  459. $current['show_thumbnails'] = ( isset( $input['show_thumbnails'] ) && '1' == $input['show_thumbnails'] );
  460. $current['show_date'] = ( isset( $input['show_date'] ) && '1' == $input['show_date'] );
  461. $current['show_context'] = ( isset( $input['show_context'] ) && '1' == $input['show_context'] );
  462. $current['layout'] = isset( $input['layout'] ) && in_array( $input['layout'], array( 'grid', 'list' ), true ) ? $input['layout'] : 'grid';
  463. $current['headline'] = isset( $input['headline'] ) ? $input['headline'] : esc_html__( 'Related', 'jetpack' );
  464. } else {
  465. $current['enabled'] = false;
  466. }
  467. if ( isset( $input['size'] ) && (int)$input['size'] > 0 )
  468. $current['size'] = (int)$input['size'];
  469. else
  470. $current['size'] = null;
  471. return $current;
  472. }
  473. /**
  474. * HTML for admin settings page.
  475. *
  476. * @uses self::get_options, checked, esc_html__
  477. * @returns null
  478. */
  479. public function print_setting_html() {
  480. $options = $this->get_options();
  481. $ui_settings_template = <<<EOT
  482. <p class="description">%s</p>
  483. <ul id="settings-reading-relatedposts-customize">
  484. <li>
  485. <label><input name="jetpack_relatedposts[show_headline]" type="checkbox" value="1" %s /> %s</label>
  486. </li>
  487. <li>
  488. <label><input name="jetpack_relatedposts[show_thumbnails]" type="checkbox" value="1" %s /> %s</label>
  489. </li>
  490. <li>
  491. <label><input name="jetpack_relatedposts[show_date]" type="checkbox" value="1" %s /> %s</label>
  492. </li>
  493. <li>
  494. <label><input name="jetpack_relatedposts[show_context]" type="checkbox" value="1" %s /> %s</label>
  495. </li>
  496. </ul>
  497. <div id='settings-reading-relatedposts-preview'>
  498. %s
  499. <div id="jp-relatedposts" class="jp-relatedposts"></div>
  500. </div>
  501. EOT;
  502. $ui_settings = sprintf(
  503. $ui_settings_template,
  504. esc_html__( 'The following settings will impact all related posts on your site, except for those you created via the block editor:', 'jetpack' ),
  505. checked( $options['show_headline'], true, false ),
  506. esc_html__( 'Highlight related content with a heading', 'jetpack' ),
  507. checked( $options['show_thumbnails'], true, false ),
  508. esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
  509. checked( $options['show_date'], true, false ),
  510. esc_html__( 'Show entry date', 'jetpack' ),
  511. checked( $options['show_context'], true, false ),
  512. esc_html__( 'Show context (category or tag)', 'jetpack' ),
  513. esc_html__( 'Preview:', 'jetpack' )
  514. );
  515. if ( !$this->_allow_feature_toggle() ) {
  516. $template = <<<EOT
  517. <input type="hidden" name="jetpack_relatedposts[enabled]" value="1" />
  518. %s
  519. EOT;
  520. printf(
  521. $template,
  522. $ui_settings
  523. );
  524. } else {
  525. $template = <<<EOT
  526. <ul id="settings-reading-relatedposts">
  527. <li>
  528. <label><input type="radio" name="jetpack_relatedposts[enabled]" value="0" class="tog" %s /> %s</label>
  529. </li>
  530. <li>
  531. <label><input type="radio" name="jetpack_relatedposts[enabled]" value="1" class="tog" %s /> %s</label>
  532. %s
  533. </li>
  534. </ul>
  535. EOT;
  536. printf(
  537. $template,
  538. checked( $options['enabled'], false, false ),
  539. esc_html__( 'Hide related content after posts', 'jetpack' ),
  540. checked( $options['enabled'], true, false ),
  541. esc_html__( 'Show related content after posts', 'jetpack' ),
  542. $ui_settings
  543. );
  544. }
  545. }
  546. /**
  547. * Head JS/CSS for admin settings page.
  548. *
  549. * @uses esc_html__
  550. * @returns null
  551. */
  552. public function print_setting_head() {
  553. // only dislay the Related Posts JavaScript on the Reading Settings Admin Page
  554. $current_screen = get_current_screen();
  555. if ( is_null( $current_screen ) ) {
  556. return;
  557. }
  558. if( 'options-reading' != $current_screen->id )
  559. return;
  560. $related_headline = sprintf(
  561. '<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',
  562. esc_html__( 'Related', 'jetpack' )
  563. );
  564. $href_params = 'class="jp-relatedposts-post-a" href="#jetpack_relatedposts" rel="nofollow" data-origin="0" data-position="0"';
  565. $related_with_images = <<<EOT
  566. <div class="jp-relatedposts-items jp-relatedposts-items-visual">
  567. <div class="jp-relatedposts-post jp-relatedposts-post0 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
  568. <a $href_params>
  569. <img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2019/03/cat-blog.png" width="350" alt="Big iPhone/iPad Update Now Available" scale="0">
  570. </a>
  571. <h4 class="jp-relatedposts-post-title">
  572. <a $href_params>Big iPhone/iPad Update Now Available</a>
  573. </h4>
  574. <p class="jp-relatedposts-post-excerpt">Big iPhone/iPad Update Now Available</p>
  575. <p class="jp-relatedposts-post-context">In "Mobile"</p>
  576. </div>
  577. <div class="jp-relatedposts-post jp-relatedposts-post1 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
  578. <a $href_params>
  579. <img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2019/03/devices.jpg" width="350" alt="The WordPress for Android App Gets a Big Facelift" scale="0">
  580. </a>
  581. <h4 class="jp-relatedposts-post-title">
  582. <a $href_params>The WordPress for Android App Gets a Big Facelift</a>
  583. </h4>
  584. <p class="jp-relatedposts-post-excerpt">The WordPress for Android App Gets a Big Facelift</p>
  585. <p class="jp-relatedposts-post-context">In "Mobile"</p>
  586. </div>
  587. <div class="jp-relatedposts-post jp-relatedposts-post2 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
  588. <a $href_params>
  589. <img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg" width="350" alt="Upgrade Focus: VideoPress For Weddings" scale="0">
  590. </a>
  591. <h4 class="jp-relatedposts-post-title">
  592. <a $href_params>Upgrade Focus: VideoPress For Weddings</a>
  593. </h4>
  594. <p class="jp-relatedposts-post-excerpt">Upgrade Focus: VideoPress For Weddings</p>
  595. <p class="jp-relatedposts-post-context">In "Upgrade"</p>
  596. </div>
  597. </div>
  598. EOT;
  599. $related_with_images = str_replace( "\n", '', $related_with_images );
  600. $related_without_images = <<<EOT
  601. <div class="jp-relatedposts-items jp-relatedposts-items-minimal">
  602. <p class="jp-relatedposts-post jp-relatedposts-post0" data-post-id="0" data-post-format="image">
  603. <span class="jp-relatedposts-post-title"><a $href_params>Big iPhone/iPad Update Now Available</a></span>
  604. <span class="jp-relatedposts-post-context">In "Mobile"</span>
  605. </p>
  606. <p class="jp-relatedposts-post jp-relatedposts-post1" data-post-id="0" data-post-format="image">
  607. <span class="jp-relatedposts-post-title"><a $href_params>The WordPress for Android App Gets a Big Facelift</a></span>
  608. <span class="jp-relatedposts-post-context">In "Mobile"</span>
  609. </p>
  610. <p class="jp-relatedposts-post jp-relatedposts-post2" data-post-id="0" data-post-format="image">
  611. <span class="jp-relatedposts-post-title"><a $href_params>Upgrade Focus: VideoPress For Weddings</a></span>
  612. <span class="jp-relatedposts-post-context">In "Upgrade"</span>
  613. </p>
  614. </div>
  615. EOT;
  616. $related_without_images = str_replace( "\n", '', $related_without_images );
  617. if ( $this->_allow_feature_toggle() ) {
  618. $extra_css = '#settings-reading-relatedposts-customize { padding-left:2em; margin-top:.5em; }';
  619. } else {
  620. $extra_css = '';
  621. }
  622. echo <<<EOT
  623. <style type="text/css">
  624. #settings-reading-relatedposts .disabled { opacity:.5; filter:Alpha(opacity=50); }
  625. #settings-reading-relatedposts-preview .jp-relatedposts { background:#fff; padding:.5em; width:75%; }
  626. $extra_css
  627. </style>
  628. <script type="text/javascript">
  629. jQuery( document ).ready( function($) {
  630. var update_ui = function() {
  631. var is_enabled = true;
  632. if ( 'radio' == $( 'input[name="jetpack_relatedposts[enabled]"]' ).attr('type') ) {
  633. if ( '0' == $( 'input[name="jetpack_relatedposts[enabled]"]:checked' ).val() ) {
  634. is_enabled = false;
  635. }
  636. }
  637. if ( is_enabled ) {
  638. $( '#settings-reading-relatedposts-customize' )
  639. .removeClass( 'disabled' )
  640. .find( 'input' )
  641. .attr( 'disabled', false );
  642. $( '#settings-reading-relatedposts-preview' )
  643. .removeClass( 'disabled' );
  644. } else {
  645. $( '#settings-reading-relatedposts-customize' )
  646. .addClass( 'disabled' )
  647. .find( 'input' )
  648. .attr( 'disabled', true );
  649. $( '#settings-reading-relatedposts-preview' )
  650. .addClass( 'disabled' );
  651. }
  652. };
  653. var update_preview = function() {
  654. var html = '';
  655. if ( $( 'input[name="jetpack_relatedposts[show_headline]"]:checked' ).length ) {
  656. html += '$related_headline';
  657. }
  658. if ( $( 'input[name="jetpack_relatedposts[show_thumbnails]"]:checked' ).length ) {
  659. html += '$related_with_images';
  660. } else {
  661. html += '$related_without_images';
  662. }
  663. $( '#settings-reading-relatedposts-preview .jp-relatedposts' ).html( html );
  664. if ( $( 'input[name="jetpack_relatedposts[show_date]"]:checked' ).length ) {
  665. $( '.jp-relatedposts-post-title' ).each( function() {
  666. $( this ).after( $( '<span>August 8, 2005</span>' ) );
  667. } );
  668. }
  669. if ( $( 'input[name="jetpack_relatedposts[show_context]"]:checked' ).length ) {
  670. $( '.jp-relatedposts-post-context' ).show();
  671. } else {
  672. $( '.jp-relatedposts-post-context' ).hide();
  673. }
  674. $( '#settings-reading-relatedposts-preview .jp-relatedposts' ).show();
  675. };
  676. // Update on load
  677. update_preview();
  678. update_ui();
  679. // Update on change
  680. $( '#settings-reading-relatedposts-customize input' )
  681. .change( update_preview );
  682. $( '#settings-reading-relatedposts' )
  683. .find( 'input.tog' )
  684. .change( update_ui );
  685. });
  686. </script>
  687. EOT;
  688. }
  689. /**
  690. * Gets an array of related posts that match the given post_id.
  691. *
  692. * @param int $post_id Post which we want to find related posts for.
  693. * @param array $args - params to use when building Elasticsearch filters to narrow down the search domain.
  694. * @uses self::get_options, get_post_type, wp_parse_args, apply_filters
  695. * @return array
  696. */
  697. public function get_for_post_id( $post_id, array $args ) {
  698. $options = $this->get_options();
  699. if ( ! empty( $args['size'] ) ) {
  700. $options['size'] = $args['size'];
  701. }
  702. if (
  703. ! $options['enabled']
  704. || 0 === (int) $post_id
  705. || empty( $options['size'] )
  706. ) {
  707. return array();
  708. }
  709. $defaults = array(
  710. 'size' => (int) $options['size'],
  711. 'post_type' => get_post_type( $post_id ),
  712. 'post_formats' => array(),
  713. 'has_terms' => array(),
  714. 'date_range' => array(),
  715. 'exclude_post_ids' => array(),
  716. );
  717. $args = wp_parse_args( $args, $defaults );
  718. /**
  719. * Filter the arguments used to retrieve a list of Related Posts.
  720. *
  721. * @module related-posts
  722. *
  723. * @since 2.8.0
  724. *
  725. * @param array $args Array of options to retrieve Related Posts.
  726. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  727. */
  728. $args = apply_filters( 'jetpack_relatedposts_filter_args', $args, $post_id );
  729. $filters = $this->_get_es_filters_from_args( $post_id, $args );
  730. /**
  731. * Filter Elasticsearch options used to calculate Related Posts.
  732. *
  733. * @module related-posts
  734. *
  735. * @since 2.8.0
  736. *
  737. * @param array $filters Array of Elasticsearch filters based on the post_id and args.
  738. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  739. */
  740. $filters = apply_filters( 'jetpack_relatedposts_filter_filters', $filters, $post_id );
  741. $results = $this->_get_related_posts( $post_id, $args['size'], $filters );
  742. /**
  743. * Filter the array of related posts matched by Elasticsearch.
  744. *
  745. * @module related-posts
  746. *
  747. * @since 2.8.0
  748. *
  749. * @param array $results Array of related posts matched by Elasticsearch.
  750. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  751. */
  752. return apply_filters( 'jetpack_relatedposts_returned_results', $results, $post_id );
  753. }
  754. /**
  755. * =========================
  756. * PRIVATE UTILITY FUNCTIONS
  757. * =========================
  758. */
  759. /**
  760. * Creates an array of Elasticsearch filters based on the post_id and args.
  761. *
  762. * @param int $post_id
  763. * @param array $args
  764. * @uses apply_filters, get_post_types, get_post_format_strings
  765. * @return array
  766. */
  767. protected function _get_es_filters_from_args( $post_id, array $args ) {
  768. $filters = array();
  769. /**
  770. * Filter the terms used to search for Related Posts.
  771. *
  772. * @module related-posts
  773. *
  774. * @since 2.8.0
  775. *
  776. * @param array $args['has_terms'] Array of terms associated to the Related Posts.
  777. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  778. */
  779. $args['has_terms'] = apply_filters( 'jetpack_relatedposts_filter_has_terms', $args['has_terms'], $post_id );
  780. if ( ! empty( $args['has_terms'] ) ) {
  781. foreach( (array)$args['has_terms'] as $term ) {
  782. if ( mb_strlen( $term->taxonomy ) ) {
  783. switch ( $term->taxonomy ) {
  784. case 'post_tag':
  785. $tax_fld = 'tag.slug';
  786. break;
  787. case 'category':
  788. $tax_fld = 'category.slug';
  789. break;
  790. default:
  791. $tax_fld = 'taxonomy.' . $term->taxonomy . '.slug';
  792. break;
  793. }
  794. $filters[] = array( 'term' => array( $tax_fld => $term->slug ) );
  795. }
  796. }
  797. }
  798. /**
  799. * Filter the Post Types where we search Related Posts.
  800. *
  801. * @module related-posts
  802. *
  803. * @since 2.8.0
  804. *
  805. * @param array $args['post_type'] Array of Post Types.
  806. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  807. */
  808. $args['post_type'] = apply_filters( 'jetpack_relatedposts_filter_post_type', $args['post_type'], $post_id );
  809. $valid_post_types = get_post_types();
  810. if ( is_array( $args['post_type'] ) ) {
  811. $sanitized_post_types = array();
  812. foreach ( $args['post_type'] as $pt ) {
  813. if ( in_array( $pt, $valid_post_types ) )
  814. $sanitized_post_types[] = $pt;
  815. }
  816. if ( ! empty( $sanitized_post_types ) )
  817. $filters[] = array( 'terms' => array( 'post_type' => $sanitized_post_types ) );
  818. } else if ( in_array( $args['post_type'], $valid_post_types ) && 'all' != $args['post_type'] ) {
  819. $filters[] = array( 'term' => array( 'post_type' => $args['post_type'] ) );
  820. }
  821. /**
  822. * Filter the Post Formats where we search Related Posts.
  823. *
  824. * @module related-posts
  825. *
  826. * @since 3.3.0
  827. *
  828. * @param array $args['post_formats'] Array of Post Formats.
  829. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  830. */
  831. $args['post_formats'] = apply_filters( 'jetpack_relatedposts_filter_post_formats', $args['post_formats'], $post_id );
  832. $valid_post_formats = get_post_format_strings();
  833. $sanitized_post_formats = array();
  834. foreach ( $args['post_formats'] as $pf ) {
  835. if ( array_key_exists( $pf, $valid_post_formats ) ) {
  836. $sanitized_post_formats[] = $pf;
  837. }
  838. }
  839. if ( ! empty( $sanitized_post_formats ) ) {
  840. $filters[] = array( 'terms' => array( 'post_format' => $sanitized_post_formats ) );
  841. }
  842. /**
  843. * Filter the date range used to search Related Posts.
  844. *
  845. * @module related-posts
  846. *
  847. * @since 2.8.0
  848. *
  849. * @param array $args['date_range'] Array of a month interval where we search Related Posts.
  850. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  851. */
  852. $args['date_range'] = apply_filters( 'jetpack_relatedposts_filter_date_range', $args['date_range'], $post_id );
  853. if ( is_array( $args['date_range'] ) && ! empty( $args['date_range'] ) ) {
  854. $args['date_range'] = array_map( 'intval', $args['date_range'] );
  855. if ( !empty( $args['date_range']['from'] ) && !empty( $args['date_range']['to'] ) ) {
  856. $filters[] = array(
  857. 'range' => array(
  858. 'date_gmt' => $this->_get_coalesced_range( $args['date_range'] ),
  859. )
  860. );
  861. }
  862. }
  863. /**
  864. * Filter the Post IDs excluded from appearing in Related Posts.
  865. *
  866. * @module related-posts
  867. *
  868. * @since 2.9.0
  869. *
  870. * @param array $args['exclude_post_ids'] Array of Post IDs.
  871. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  872. */
  873. $args['exclude_post_ids'] = apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', $args['exclude_post_ids'], $post_id );
  874. if ( !empty( $args['exclude_post_ids'] ) && is_array( $args['exclude_post_ids'] ) ) {
  875. $excluded_post_ids = array();
  876. foreach ( $args['exclude_post_ids'] as $exclude_post_id) {
  877. $exclude_post_id = (int)$exclude_post_id;
  878. if ( $exclude_post_id > 0 )
  879. $excluded_post_ids[] = $exclude_post_id;
  880. }
  881. $filters[] = array( 'not' => array( 'terms' => array( 'post_id' => $excluded_post_ids ) ) );
  882. }
  883. return $filters;
  884. }
  885. /**
  886. * Takes a range and coalesces it into a month interval bracketed by a time as determined by the blog_id to enhance caching.
  887. *
  888. * @param array $date_range
  889. * @return array
  890. */
  891. protected function _get_coalesced_range( array $date_range ) {
  892. $now = time();
  893. $coalesce_time = $this->get_blog_id() % 86400;
  894. $current_time = $now - strtotime( 'today', $now );
  895. if ( $current_time < $coalesce_time && '01' == date( 'd', $now ) ) {
  896. // Move back 1 period
  897. return array(
  898. 'from' => date( 'Y-m-01', strtotime( '-1 month', $date_range['from'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
  899. 'to' => date( 'Y-m-01', $date_range['to'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
  900. );
  901. } else {
  902. // Use current period
  903. return array(
  904. 'from' => date( 'Y-m-01', $date_range['from'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
  905. 'to' => date( 'Y-m-01', strtotime( '+1 month', $date_range['to'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
  906. );
  907. }
  908. }
  909. /**
  910. * Generate and output ajax response for related posts API call.
  911. * NOTE: Calls exit() to end all further processing after payload has been outputed.
  912. *
  913. * @param array $excludes array of post_ids to exclude
  914. * @uses send_nosniff_header, self::get_for_post_id, get_the_ID
  915. * @return null
  916. */
  917. protected function _action_frontend_init_ajax( array $excludes ) {
  918. define( 'DOING_AJAX', true );
  919. header( 'Content-type: application/json; charset=utf-8' ); // JSON can only be UTF-8
  920. send_nosniff_header();
  921. $options = $this->get_options();
  922. if ( isset( $_GET['jetpackrpcustomize'] ) ) {
  923. // If we're in the customizer, add dummy content.
  924. $date_now = current_time( get_option( 'date_format' ) );
  925. $related_posts = array(
  926. array(
  927. 'id' => - 1,
  928. 'url' => 'https://jetpackme.files.wordpress.com/2019/03/cat-blog.png',
  929. 'url_meta' => array(
  930. 'origin' => 0,
  931. 'position' => 0
  932. ),
  933. 'title' => esc_html__( 'Big iPhone/iPad Update Now Available', 'jetpack' ),
  934. 'date' => $date_now,
  935. 'format' => false,
  936. 'excerpt' => esc_html__( 'It is that time of the year when devices are shiny again.', 'jetpack' ),
  937. 'rel' => 'nofollow',
  938. 'context' => esc_html__( 'In "Mobile"', 'jetpack' ),
  939. 'img' => array(
  940. 'src' => 'https://jetpackme.files.wordpress.com/2019/03/cat-blog.png',
  941. 'width' => 350,
  942. 'height' => 200
  943. ),
  944. 'classes' => array()
  945. ),
  946. array(
  947. 'id' => - 1,
  948. 'url' => 'https://jetpackme.files.wordpress.com/2019/03/devices.jpg',
  949. 'url_meta' => array(
  950. 'origin' => 0,
  951. 'position' => 0
  952. ),
  953. 'title' => esc_html__( 'The WordPress for Android App Gets a Big Facelift', 'jetpack' ),
  954. 'date' => $date_now,
  955. 'format' => false,
  956. 'excerpt' => esc_html__( 'Writing is new again in Android with the new WordPress app.', 'jetpack' ),
  957. 'rel' => 'nofollow',
  958. 'context' => esc_html__( 'In "Mobile"', 'jetpack' ),
  959. 'img' => array(
  960. 'src' => 'https://jetpackme.files.wordpress.com/2019/03/devices.jpg',
  961. 'width' => 350,
  962. 'height' => 200
  963. ),
  964. 'classes' => array()
  965. ),
  966. array(
  967. 'id' => - 1,
  968. 'url' => 'https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg',
  969. 'url_meta' => array(
  970. 'origin' => 0,
  971. 'position' => 0
  972. ),
  973. 'title' => esc_html__( 'Upgrade Focus, VideoPress for weddings', 'jetpack' ),
  974. 'date' => $date_now,
  975. 'format' => false,
  976. 'excerpt' => esc_html__( 'Weddings are in the spotlight now with VideoPress for weddings.', 'jetpack' ),
  977. 'rel' => 'nofollow',
  978. 'context' => esc_html__( 'In "Mobile"', 'jetpack' ),
  979. 'img' => array(
  980. 'src' => 'https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg',
  981. 'width' => 350,
  982. 'height' => 200
  983. ),
  984. 'classes' => array()
  985. ),
  986. );
  987. for ( $total = 0; $total < $options['size'] - 3; $total++ ) {
  988. $related_posts[] = $related_posts[ $total ];
  989. }
  990. $current_post = get_post();
  991. // Exclude current post after filtering to make sure it's excluded and not lost during filtering.
  992. $excluded_posts = array_merge(
  993. /** This filter is already documented in modules/related-posts/jetpack-related-posts.php */
  994. apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', array() ),
  995. array( $current_post->ID )
  996. );
  997. // Fetch posts with featured image.
  998. $with_post_thumbnails = get_posts( array(
  999. 'posts_per_page' => $options['size'],
  1000. 'post__not_in' => $excluded_posts,
  1001. 'post_type' => $current_post->post_type,
  1002. 'meta_key' => '_thumbnail_id',
  1003. 'suppress_filters' => false,
  1004. ) );
  1005. // If we don't have enough, fetch posts without featured image.
  1006. if ( 0 < ( $more = $options['size'] - count( $with_post_thumbnails ) ) ) {
  1007. $no_post_thumbnails = get_posts( array(
  1008. 'posts_per_page' => $more,
  1009. 'post__not_in' => $excluded_posts,
  1010. 'post_type' => $current_post->post_type,
  1011. 'meta_query' => array(
  1012. array(
  1013. 'key' => '_thumbnail_id',
  1014. 'compare' => 'NOT EXISTS',
  1015. ),
  1016. ),
  1017. 'suppress_filters' => false,
  1018. ) );
  1019. } else {
  1020. $no_post_thumbnails = array();
  1021. }
  1022. foreach ( array_merge( $with_post_thumbnails, $no_post_thumbnails ) as $index => $real_post ) {
  1023. $related_posts[ $index ]['id'] = $real_post->ID;
  1024. $related_posts[ $index ]['url'] = esc_url( get_permalink( $real_post ) );
  1025. $related_posts[ $index ]['title'] = $this->_to_utf8( $this->get_title( $real_post->post_title, $real_post->post_content, $real_post->ID ) );
  1026. $related_posts[ $index ]['date'] = get_the_date( '', $real_post );
  1027. $related_posts[ $index ]['excerpt'] = html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $real_post->post_excerpt, $real_post->post_content ) ), ENT_QUOTES, 'UTF-8' );
  1028. $related_posts[ $index ]['img'] = $this->_generate_related_post_image_params( $real_post->ID );
  1029. $related_posts[ $index ]['context'] = $this->_generate_related_post_context( $real_post->ID );
  1030. }
  1031. } else {
  1032. $related_posts = $this->get_for_post_id(
  1033. get_the_ID(),
  1034. array(
  1035. 'exclude_post_ids' => $excludes,
  1036. )
  1037. );
  1038. }
  1039. $response = array(
  1040. 'version' => self::VERSION,
  1041. 'show_thumbnails' => (bool) $options['show_thumbnails'],
  1042. 'show_date' => (bool) $options['show_date'],
  1043. 'show_context' => (bool) $options['show_context'],
  1044. 'layout' => (string) $options['layout'],
  1045. 'headline' => (string) $options['headline'],
  1046. 'items' => array(),
  1047. );
  1048. if ( count( $related_posts ) == $options['size'] )
  1049. $response['items'] = $related_posts;
  1050. echo json_encode( $response );
  1051. exit();
  1052. }
  1053. /**
  1054. * Returns a UTF-8 encoded array of post information for the given post_id
  1055. *
  1056. * @param int $post_id
  1057. * @param int $position
  1058. * @param int $origin The post id that this is related to
  1059. * @uses get_post, get_permalink, remove_query_arg, get_post_format, apply_filters
  1060. * @return array
  1061. */
  1062. public function get_related_post_data_for_post( $post_id, $position, $origin ) {
  1063. $post = get_post( $post_id );
  1064. return array(
  1065. 'id' => $post->ID,
  1066. 'url' => get_permalink( $post->ID ),
  1067. 'url_meta' => array(
  1068. 'origin' => $origin,
  1069. 'position' => $position,
  1070. ),
  1071. 'title' => $this->_to_utf8( $this->get_title( $post->post_title, $post->post_content, $post->ID ) ),
  1072. 'date' => get_the_date( '', $post->ID ),
  1073. 'format' => get_post_format( $post->ID ),
  1074. 'excerpt' => html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $post->post_excerpt, $post->post_content ) ), ENT_QUOTES, 'UTF-8' ),
  1075. /**
  1076. * Filters the rel attribute for the Related Posts' links.
  1077. *
  1078. * @module related-posts
  1079. *
  1080. * @since 3.7.0
  1081. * @since 7.9.0 - Change Default value to empty.
  1082. *
  1083. * @param string $link_rel Link rel attribute for Related Posts' link. Default is empty.
  1084. * @param int $post->ID Post ID.
  1085. */
  1086. 'rel' => apply_filters( 'jetpack_relatedposts_filter_post_link_rel', '', $post->ID ),
  1087. /**
  1088. * Filter the context displayed below each Related Post.
  1089. *
  1090. * @module related-posts
  1091. *
  1092. * @since 3.0.0
  1093. *
  1094. * @param string $this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ) Context displayed below each related post.
  1095. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  1096. */
  1097. 'context' => apply_filters(
  1098. 'jetpack_relatedposts_filter_post_context',
  1099. $this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ),
  1100. $post->ID
  1101. ),
  1102. 'img' => $this->_generate_related_post_image_params( $post->ID ),
  1103. /**
  1104. * Filter the post css classes added on HTML markup.
  1105. *
  1106. * @module related-posts
  1107. *
  1108. * @since 3.8.0
  1109. *
  1110. * @param array array() CSS classes added on post HTML markup.
  1111. * @param string $post_id Post ID.
  1112. */
  1113. 'classes' => apply_filters(
  1114. 'jetpack_relatedposts_filter_post_css_classes',
  1115. array(),
  1116. $post->ID
  1117. ),
  1118. );
  1119. }
  1120. /**
  1121. * Returns either the title or a small excerpt to use as title for post.
  1122. *
  1123. * @uses strip_shortcodes, wp_trim_words, __, apply_filters
  1124. *
  1125. * @param string $post_title Post title.
  1126. * @param string $post_content Post content.
  1127. * @param int $post_id Post ID.
  1128. *
  1129. * @return string
  1130. */
  1131. protected function get_title( $post_title, $post_content, $post_id ) {
  1132. if ( ! empty( $post_title ) ) {
  1133. return wp_strip_all_tags(
  1134. /** This filter is documented in core/src/wp-includes/post-template.php */
  1135. apply_filters( 'the_title', $post_title, $post_id )
  1136. );
  1137. }
  1138. $post_title = wp_trim_words( wp_strip_all_tags( strip_shortcodes( $post_content ) ), 5, '…' );
  1139. if ( ! empty( $post_title ) ) {
  1140. return $post_title;
  1141. }
  1142. return __( 'Untitled Post', 'jetpack' );
  1143. }
  1144. /**
  1145. * Returns a plain text post excerpt for title attribute of links.
  1146. *
  1147. * @param string $post_excerpt
  1148. * @param string $post_content
  1149. * @uses strip_shortcodes, wp_strip_all_tags, wp_trim_words
  1150. * @return string
  1151. */
  1152. protected function _get_excerpt( $post_excerpt, $post_content ) {
  1153. if ( empty( $post_excerpt ) )
  1154. $excerpt = $post_content;
  1155. else
  1156. $excerpt = $post_excerpt;
  1157. return wp_trim_words( wp_strip_all_tags( strip_shortcodes( $excerpt ) ), 50, '…' );
  1158. }
  1159. /**
  1160. * Generates the thumbnail image to be used for the post. Uses the
  1161. * image as returned by Jetpack_PostImages::get_image()
  1162. *
  1163. * @param int $post_id
  1164. * @uses self::get_options, apply_filters, Jetpack_PostImages::get_image, Jetpack_PostImages::fit_image_url
  1165. * @return string
  1166. */
  1167. protected function _generate_related_post_image_params( $post_id ) {
  1168. $options = $this->get_options();
  1169. $image_params = array(
  1170. 'alt_text' => '',
  1171. 'src' => '',
  1172. 'width' => 0,
  1173. 'height' => 0,
  1174. );
  1175. /**
  1176. * Filter the size of the Related Posts images.
  1177. *
  1178. * @module related-posts
  1179. *
  1180. * @since 2.8.0
  1181. *
  1182. * @param array array( 'width' => 350, 'height' => 200 ) Size of the images displayed below each Related Post.
  1183. */
  1184. $thumbnail_size = apply_filters(
  1185. 'jetpack_relatedposts_filter_thumbnail_size',
  1186. array( 'width' => 350, 'height' => 200 )
  1187. );
  1188. if ( !is_array( $thumbnail_size ) ) {
  1189. $thumbnail_size = array(
  1190. 'width' => (int)$thumbnail_size,
  1191. 'height' => (int)$thumbnail_size
  1192. );
  1193. }
  1194. // Try to get post image
  1195. if ( class_exists( 'Jetpack_PostImages' ) ) {
  1196. $img_url = '';
  1197. $post_image = Jetpack_PostImages::get_image(
  1198. $post_id,
  1199. $thumbnail_size
  1200. );
  1201. if ( is_array($post_image) ) {
  1202. $img_url = $post_image['src'];
  1203. } elseif ( class_exists( 'Jetpack_Media_Summary' ) ) {
  1204. $media = Jetpack_Media_Summary::get( $post_id );
  1205. if ( is_array($media) && !empty( $media['image'] ) ) {
  1206. $img_url = $media['image'];
  1207. }
  1208. }
  1209. if ( ! empty( $img_url ) ) {
  1210. if ( ! empty( $post_image['alt_text'] ) ) {
  1211. $image_params['alt_text'] = $post_image['alt_text'];
  1212. } else {
  1213. $image_params['alt_text'] = '';
  1214. }
  1215. $image_params['width'] = $thumbnail_size['width'];
  1216. $image_params['height'] = $thumbnail_size['height'];
  1217. $image_params['src'] = Jetpack_PostImages::fit_image_url(
  1218. $img_url,
  1219. $thumbnail_size['width'],
  1220. $thumbnail_size['height']
  1221. );
  1222. }
  1223. }
  1224. return $image_params;
  1225. }
  1226. /**
  1227. * Returns the string UTF-8 encoded
  1228. *
  1229. * @param string $text
  1230. * @return string
  1231. */
  1232. protected function _to_utf8( $text ) {
  1233. if ( $this->_convert_charset ) {
  1234. return iconv( $this->_blog_charset, 'UTF-8', $text );
  1235. } else {
  1236. return $text;
  1237. }
  1238. }
  1239. /**
  1240. * =============================================
  1241. * PROTECTED UTILITY FUNCTIONS EXTENDED BY WPCOM
  1242. * =============================================
  1243. */
  1244. /**
  1245. * Workhorse method to return array of related posts matched by Elasticsearch.
  1246. *
  1247. * @param int $post_id
  1248. * @param int $size
  1249. * @param array $filters
  1250. * @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
  1251. * @return array
  1252. */
  1253. protected function _get_related_posts( $post_id, $size, array $filters ) {
  1254. $hits = $this->_filter_non_public_posts(
  1255. $this->_get_related_post_ids(
  1256. $post_id,
  1257. $size,
  1258. $filters
  1259. )
  1260. );
  1261. /**
  1262. * Filter the Related Posts matched by Elasticsearch.
  1263. *
  1264. * @module related-posts
  1265. *
  1266. * @since 2.9.0
  1267. *
  1268. * @param array $hits Array of Post IDs matched by Elasticsearch.
  1269. * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
  1270. */
  1271. $hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
  1272. $related_posts = array();
  1273. foreach ( $hits as $i => $hit ) {
  1274. $related_posts[] = $this->get_related_post_data_for_post( $hit['id'], $i, $post_id );
  1275. }
  1276. return $related_posts;
  1277. }
  1278. /**
  1279. * Get array of related posts matched by Elasticsearch.
  1280. *
  1281. * @param int $post_id
  1282. * @param int $size
  1283. * @param array $filters
  1284. * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body, get_post_meta, update_post_meta
  1285. * @return array
  1286. */
  1287. protected function _get_related_post_ids( $post_id, $size, array $filters ) {
  1288. $now_ts = time();
  1289. $cache_meta_key = '_jetpack_related_posts_cache';
  1290. $body = array(
  1291. 'size' => (int) $size,
  1292. );
  1293. if ( !empty( $filters ) )
  1294. $body['filter'] = array( 'and' => $filters );
  1295. // Build cache key
  1296. $cache_key = md5( serialize( $body ) );
  1297. // Load all cached values
  1298. if ( wp_using_ext_object_cache() ) {
  1299. $transient_name = "{$cache_meta_key}_{$cache_key}_{$post_id}";
  1300. $cache = get_transient( $transient_name );
  1301. if ( false !== $cache ) {
  1302. return $cache;
  1303. }
  1304. } else {
  1305. $cache = get_post_meta( $post_id, $cache_meta_key, true );
  1306. if ( empty( $cache ) )
  1307. $cache = array();
  1308. // Cache is valid! Return cached value.
  1309. if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) && $cache[ $cache_key ][ 'expires' ] > $now_ts ) {
  1310. return $cache[ $cache_key ][ 'payload' ];
  1311. }
  1312. }
  1313. $response = wp_remote_post(
  1314. "https://public-api.wordpress.com/rest/v1/sites/{$this->get_blog_id()}/posts/$post_id/related/",
  1315. array(
  1316. 'timeout' => 10,
  1317. 'user-agent' => 'jetpack_related_posts',
  1318. 'sslverify' => true,
  1319. 'body' => $body,
  1320. )
  1321. );
  1322. // Oh no... return nothing don't cache errors.
  1323. if ( is_wp_error( $response ) ) {
  1324. if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) )
  1325. return $cache[ $cache_key ][ 'payload' ]; // return stale
  1326. else
  1327. return array();
  1328. }
  1329. $results = json_decode( wp_remote_retrieve_body( $response ), true );
  1330. $related_posts = array();
  1331. if ( is_array( $results ) && !empty( $results['hits'] ) ) {
  1332. foreach( $results['hits'] as $hit ) {
  1333. $related_posts[] = array(
  1334. 'id' => $hit['fields']['post_id'],
  1335. );
  1336. }
  1337. }
  1338. // An empty array might indicate no related posts or that posts
  1339. // are not yet synced to WordPress.com, so we cache for only 1
  1340. // minute in this case
  1341. if ( empty( $related_posts ) ) {
  1342. $cache_ttl = 60;
  1343. } else {
  1344. $cache_ttl = 12 * HOUR_IN_SECONDS;
  1345. }
  1346. // Update cache
  1347. if ( wp_using_ext_object_cache() ) {
  1348. set_transient( $transient_name, $related_posts, $cache_ttl );
  1349. } else {
  1350. // Copy all valid cache values
  1351. $new_cache = array();
  1352. foreach ( $cache as $k => $v ) {
  1353. if ( is_array( $v ) && $v[ 'expires' ] > $now_ts ) {
  1354. $new_cache[ $k ] = $v;
  1355. }
  1356. }
  1357. // Set new cache value
  1358. $cache_expires = $cache_ttl + $now_ts;
  1359. $new_cache[ $cache_key ] = array(
  1360. 'expires' => $cache_expires,
  1361. 'payload' => $related_posts,
  1362. );
  1363. update_post_meta( $post_id, $cache_meta_key, $new_cache );
  1364. }
  1365. return $related_posts;
  1366. }
  1367. /**
  1368. * Filter out any hits that are not public anymore.
  1369. *
  1370. * @param array $related_posts
  1371. * @uses get_post_stati, get_post_status
  1372. * @return array
  1373. */
  1374. protected function _filter_non_public_posts( array $related_posts ) {
  1375. $public_stati = get_post_stati( array( 'public' => true ) );
  1376. $filtered = array();
  1377. foreach ( $related_posts as $hit ) {
  1378. if ( in_array( get_post_status( $hit['id'] ), $public_stati ) ) {
  1379. $filtered[] = $hit;
  1380. }
  1381. }
  1382. return $filtered;
  1383. }
  1384. /**
  1385. * Generates a context for the related content (second line in related post output).
  1386. * Order of importance:
  1387. * - First category (Not 'Uncategorized')
  1388. * - First post tag
  1389. * - Number of comments
  1390. *
  1391. * @param int $post_id
  1392. * @uses get_the_category, get_the_terms, get_comments_number, number_format_i18n, __, _n
  1393. * @return string
  1394. */
  1395. protected function _generate_related_post_context( $post_id ) {
  1396. $categories = get_the_category( $post_id );
  1397. if ( is_array( $categories ) ) {
  1398. foreach ( $categories as $category ) {
  1399. if ( 'uncategorized' != $category->slug && '' != trim( $category->name ) ) {
  1400. $post_cat_context = sprintf(
  1401. esc_html_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
  1402. $category->name
  1403. );
  1404. /**
  1405. * Filter the "In Category" line displayed in the post context below each Related Post.
  1406. *
  1407. * @module related-posts
  1408. *
  1409. * @since 3.2.0
  1410. *
  1411. * @param string $post_cat_context "In Category" line displayed in the post context below each Related Post.
  1412. * @param array $category Array containing information about the category.
  1413. */
  1414. return apply_filters( 'jetpack_relatedposts_post_category_context', $post_cat_context, $category );
  1415. }
  1416. }
  1417. }
  1418. $tags = get_the_terms( $post_id, 'post_tag' );
  1419. if ( is_array( $tags ) ) {
  1420. foreach ( $tags as $tag ) {
  1421. if ( '' != trim( $tag->name ) ) {
  1422. $post_tag_context = sprintf(
  1423. _x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
  1424. $tag->name
  1425. );
  1426. /**
  1427. * Filter the "In Tag" line displayed in the post context below each Related Post.
  1428. *
  1429. * @module related-posts
  1430. *
  1431. * @since 3.2.0
  1432. *
  1433. * @param string $post_tag_context "In Tag" line displayed in the post context below each Related Post.
  1434. * @param array $tag Array containing information about the tag.
  1435. */
  1436. return apply_filters( 'jetpack_relatedposts_post_tag_context', $post_tag_context, $tag );
  1437. }
  1438. }
  1439. }
  1440. $comment_count = get_comments_number( $post_id );
  1441. if ( $comment_count > 0 ) {
  1442. return sprintf(
  1443. _n( 'With 1 comment', 'With %s comments', $comment_count, 'jetpack' ),
  1444. number_format_i18n( $comment_count )
  1445. );
  1446. }
  1447. return __( 'Similar post', 'jetpack' );
  1448. }
  1449. /**
  1450. * Logs clicks for clickthrough analysis and related result tuning.
  1451. *
  1452. * @return null
  1453. */
  1454. protected function _log_click( $post_id, $to_post_id, $link_position ) {
  1455. }
  1456. /**
  1457. * Determines if the current post is able to use related posts.
  1458. *
  1459. * @uses self::get_options, is_admin, is_single, apply_filters
  1460. * @return bool
  1461. */
  1462. protected function _enabled_for_request() {
  1463. $enabled = is_single()
  1464. && ! is_attachment()
  1465. && ! is_admin()
  1466. && ! is_embed()
  1467. && ( ! $this->_allow_feature_toggle() || $this->get_option( 'enabled' ) );
  1468. /**
  1469. * Filter the Enabled value to allow related posts to be shown on pages as well.
  1470. *
  1471. * @module related-posts
  1472. *
  1473. * @since 3.3.0
  1474. *
  1475. * @param bool $enabled Should Related Posts be enabled on the current page.
  1476. */
  1477. return apply_filters( 'jetpack_relatedposts_filter_enabled_for_request', $enabled );
  1478. }
  1479. /**
  1480. * Adds filters.
  1481. *
  1482. * @uses self::_enqueue_assets, self::_setup_shortcode, add_filter
  1483. * @return null
  1484. */
  1485. protected function _action_frontend_init_page() {
  1486. $this->_enqueue_assets( true, true );
  1487. $this->_setup_shortcode();
  1488. add_filter( 'the_content', array( $this, 'filter_add_target_to_dom' ), 40 );
  1489. }
  1490. /**
  1491. * Determines if the scripts need be enqueued.
  1492. *
  1493. * @return bool
  1494. */
  1495. protected function requires_scripts() {
  1496. return (
  1497. ! ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) &&
  1498. ! has_block( 'jetpack/related-posts' ) &&
  1499. ! Blocks::is_fse_theme()
  1500. );
  1501. }
  1502. /**
  1503. * Enqueues assets needed to do async loading of related posts.
  1504. *
  1505. * @uses wp_enqueue_script, wp_enqueue_style, plugins_url
  1506. * @return null
  1507. */
  1508. protected function _enqueue_assets( $script, $style ) {
  1509. $dependencies = is_customize_preview() ? array( 'customize-base' ) : array();
  1510. // Do not enqueue scripts unless they are required.
  1511. if ( $script && $this->requires_scripts() ) {
  1512. wp_enqueue_script(
  1513. 'jetpack_related-posts',
  1514. Assets::get_file_url_for_environment(
  1515. '_inc/build/related-posts/related-posts.min.js',
  1516. 'modules/related-posts/related-posts.js'
  1517. ),
  1518. $dependencies,
  1519. self::VERSION
  1520. );
  1521. $related_posts_js_options = array(
  1522. /**
  1523. * Filter each Related Post Heading structure.
  1524. *
  1525. * @since 4.0.0
  1526. *
  1527. * @param string $str Related Post Heading structure. Default to h4.
  1528. */
  1529. 'post_heading' => apply_filters( 'jetpack_relatedposts_filter_post_heading', esc_attr( 'h4' ) ),
  1530. );
  1531. wp_localize_script( 'jetpack_related-posts', 'related_posts_js_options', $related_posts_js_options );
  1532. }
  1533. if ( $style ){
  1534. wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'related-posts.css', __FILE__ ), array(), self::VERSION );
  1535. wp_style_add_data( 'jetpack_related-posts', 'rtl', 'replace' );
  1536. add_action( 'amp_post_template_css', array( $this, 'render_amp_reader_mode_css' ) );
  1537. }
  1538. }
  1539. public function render_amp_reader_mode_css() {
  1540. echo file_get_contents( plugin_dir_path( __FILE__ ) . 'related-posts.css' );
  1541. }
  1542. /**
  1543. * Sets up the shortcode processing.
  1544. *
  1545. * @uses add_filter, add_shortcode
  1546. * @return null
  1547. */
  1548. protected function _setup_shortcode() {
  1549. add_filter( 'the_content', array( $this, 'test_for_shortcode' ), 0 );
  1550. add_shortcode( self::SHORTCODE, array( $this, 'get_client_rendered_html' ) );
  1551. }
  1552. protected function _allow_feature_toggle() {
  1553. if ( null === $this->_allow_feature_toggle ) {
  1554. /**
  1555. * Filter the display of the Related Posts toggle in Settings > Reading.
  1556. *
  1557. * @module related-posts
  1558. *
  1559. * @since 2.8.0
  1560. *
  1561. * @param bool false Display a feature toggle. Default to false.
  1562. */
  1563. $this->_allow_feature_toggle = apply_filters( 'jetpack_relatedposts_filter_allow_feature_toggle', false );
  1564. }
  1565. return $this->_allow_feature_toggle;
  1566. }
  1567. /**
  1568. * ===================================================
  1569. * FUNCTIONS EXPOSING RELATED POSTS IN THE WP REST API
  1570. * ===================================================
  1571. */
  1572. /**
  1573. * Add Related Posts to the REST API Post response.
  1574. *
  1575. * @since 4.4.0
  1576. *
  1577. * @action rest_api_init
  1578. * @uses register_rest_field, self::rest_get_related_posts
  1579. * @return null
  1580. */
  1581. public function rest_register_related_posts() {
  1582. /** This filter is already documented in class.json-api-endpoints.php */
  1583. $post_types = apply_filters( 'rest_api_allowed_post_types', array( 'post', 'page', 'revision' ) );
  1584. foreach ( $post_types as $post_type ) {
  1585. register_rest_field(
  1586. $post_type,
  1587. 'jetpack-related-posts',
  1588. array(
  1589. 'get_callback' => array( $this, 'rest_get_related_posts' ),
  1590. 'update_callback' => null,
  1591. 'schema' => null,
  1592. )
  1593. );
  1594. }
  1595. }
  1596. /**
  1597. * Build an array of Related Posts.
  1598. * By default returns cached results that are stored for up to 12 hours.
  1599. *
  1600. * @since 4.4.0
  1601. *
  1602. * @param array $object Details of current post.
  1603. * @param string $field_name Name of field.
  1604. * @param WP_REST_Request $request Current request
  1605. *
  1606. * @uses self::get_for_post_id
  1607. *
  1608. * @return array
  1609. */
  1610. public function rest_get_related_posts( $object, $field_name, $request ) {
  1611. return $this->get_for_post_id( $object['id'], array( 'size' => 6 ) );
  1612. }
  1613. }
  1614. class Jetpack_RelatedPosts_Raw extends Jetpack_RelatedPosts {
  1615. protected $_query_name;
  1616. /**
  1617. * Allows callers of this class to tag each query with a unique name for tracking purposes.
  1618. *
  1619. * @param string $name
  1620. * @return Jetpack_RelatedPosts_Raw
  1621. */
  1622. public function set_query_name( $name ) {
  1623. $this->_query_name = (string) $name;
  1624. return $this;
  1625. }
  1626. /**
  1627. * The raw related posts class can be used by other plugins or themes
  1628. * to get related content. This class wraps the existing RelatedPosts
  1629. * logic thus we never want to add anything to the DOM or do anything
  1630. * for event hooks. We will also not present any settings for this
  1631. * class and keep it enabled as calls to this class is done
  1632. * programmatically.
  1633. */
  1634. public function action_admin_init() {}
  1635. public function action_frontend_init() {}
  1636. public function get_options() {
  1637. return array(
  1638. 'enabled' => true,
  1639. );
  1640. }
  1641. /**
  1642. * Workhorse method to return array of related posts ids matched by Elasticsearch.
  1643. *
  1644. * @param int $post_id
  1645. * @param int $size
  1646. * @param array $filters
  1647. * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body
  1648. * @return array
  1649. */
  1650. protected function _get_related_posts( $post_id, $size, array $filters ) {
  1651. $hits = $this->_filter_non_public_posts(
  1652. $this->_get_related_post_ids(
  1653. $post_id,
  1654. $size,
  1655. $filters
  1656. )
  1657. );
  1658. /** This filter is already documented in modules/related-posts/related-posts.php */
  1659. $hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
  1660. return $hits;
  1661. }
  1662. }