2022-06-16 14:01:47 +02:00
< ? php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* The Jetpack_RelatedPosts class .
*
* @ package automattic / jetpack
*/
2019-11-15 23:26:29 +01:00
use Automattic\Jetpack\Assets ;
2020-11-18 09:10:44 +01:00
use Automattic\Jetpack\Blocks ;
2019-11-15 23:26:29 +01:00
use Automattic\Jetpack\Sync\Settings ;
2022-06-16 14:01:47 +02:00
/**
* The Jetpack_RelatedPosts class .
*/
2019-11-15 23:26:29 +01:00
class Jetpack_RelatedPosts {
2024-02-16 11:03:01 +01:00
const VERSION = '20240116' ;
2019-11-15 23:26:29 +01:00
const SHORTCODE = 'jetpack-related-posts' ;
2022-06-16 14:01:47 +02:00
/**
* Instance of the class .
*
* @ var Jetpack_RelatedPosts
*/
private static $instance = null ;
/**
* Instance of the raw class ( ? ) .
*
* @ var Jetpack_RelatedPosts
*/
2019-11-15 23:26:29 +01:00
private static $instance_raw = null ;
/**
* Creates and returns a static instance of Jetpack_RelatedPosts .
*
* @ return Jetpack_RelatedPosts
*/
public static function init () {
if ( ! self :: $instance ) {
2022-06-16 14:01:47 +02:00
if ( class_exists ( 'WPCOM_RelatedPosts' ) && method_exists ( 'WPCOM_RelatedPosts' , 'init' ) ) {
2019-11-15 23:26:29 +01:00
self :: $instance = WPCOM_RelatedPosts :: init ();
} else {
self :: $instance = new Jetpack_RelatedPosts ();
}
}
return self :: $instance ;
}
/**
* Creates and returns a static instance of Jetpack_RelatedPosts_Raw .
*
* @ return Jetpack_RelatedPosts
*/
public static function init_raw () {
if ( ! self :: $instance_raw ) {
2022-06-16 14:01:47 +02:00
if ( class_exists ( 'WPCOM_RelatedPosts' ) && method_exists ( 'WPCOM_RelatedPosts' , 'init_raw' ) ) {
2019-11-15 23:26:29 +01:00
self :: $instance_raw = WPCOM_RelatedPosts :: init_raw ();
} else {
self :: $instance_raw = new Jetpack_RelatedPosts_Raw ();
}
}
return self :: $instance_raw ;
}
2022-06-16 14:01:47 +02:00
/**
* Options .
*
* @ var array $options
*/
protected $options ;
/**
* Allow feature toggle variable .
*
* @ var bool
*/
protected $allow_feature_toggle ;
/**
* Blog character set .
*
* @ var mixed
*/
protected $blog_charset ;
/**
* Convert character set .
*
* @ var bool
*/
protected $convert_charset ;
/**
* Previous Post ID
*
* @ var int
*/
protected $previous_post_id ;
/**
* Shortcode usage .
*
* @ var bool
*/
protected $found_shortcode = false ;
2019-11-15 23:26:29 +01:00
/**
* Constructor for Jetpack_RelatedPosts .
*
* @ uses get_option , add_action , apply_filters
*/
public function __construct () {
2022-06-16 14:01:47 +02:00
$this -> blog_charset = get_option ( 'blog_charset' );
$this -> convert_charset = ( function_exists ( 'iconv' ) && ! preg_match ( '/^utf\-?8$/i' , $this -> blog_charset ) );
2019-11-15 23:26:29 +01:00
add_action ( 'admin_init' , array ( $this , 'action_admin_init' ) );
add_action ( 'wp' , array ( $this , 'action_frontend_init' ) );
if ( ! class_exists ( 'Jetpack_Media_Summary' ) ) {
2022-12-15 17:41:53 +01:00
require_once JETPACK__PLUGIN_DIR . '_inc/lib/class.media-summary.php' ;
2019-11-15 23:26:29 +01:00
}
// Add Related Posts to the REST API Post response.
add_action ( 'rest_api_init' , array ( $this , 'rest_register_related_posts' ) );
}
2022-06-16 14:01:47 +02:00
/**
* Get the blog ID .
*
* @ return Object current blog id .
*/
2019-11-15 23:26:29 +01:00
protected function get_blog_id () {
return Jetpack_Options :: get_option ( 'id' );
}
/**
* =================
* ACTIONS & FILTERS
* =================
*/
/**
* Add a checkbox field to Settings > Reading for enabling related posts .
*
* @ action admin_init
* @ uses add_settings_field , __ , register_setting , add_action
*/
public function action_admin_init () {
2022-06-16 14:01:47 +02:00
// Add the setting field [jetpack_relatedposts] and place it in Settings > Reading.
2019-11-15 23:26:29 +01:00
add_settings_field ( 'jetpack_relatedposts' , '<span id="jetpack_relatedposts">' . __ ( 'Related posts' , 'jetpack' ) . '</span>' , array ( $this , 'print_setting_html' ), 'reading' );
register_setting ( 'reading' , 'jetpack_relatedposts' , array ( $this , 'parse_options' ) );
2022-06-16 14:01:47 +02:00
add_action ( 'admin_head' , array ( $this , 'print_setting_head' ) );
2019-11-15 23:26:29 +01:00
2022-06-16 14:01:47 +02:00
if ( 'options-reading.php' === $GLOBALS [ 'pagenow' ] ) {
// Enqueue style for live preview on the reading settings page.
$this -> enqueue_assets ( false , true );
2019-11-15 23:26:29 +01:00
}
}
/**
2021-07-23 11:58:50 +02:00
* Load related posts assets if it 's an eligible front end page or execute search and return JSON if it' s an endpoint request .
2019-11-15 23:26:29 +01:00
*
* @ global $_GET
* @ action wp
* @ uses add_shortcode , get_the_ID
*/
public function action_frontend_init () {
2022-06-16 14:01:47 +02:00
// Add a shortcode handler that outputs nothing, this gets overridden later if we can display related content.
2019-11-15 23:26:29 +01:00
add_shortcode ( self :: SHORTCODE , array ( $this , 'get_client_rendered_html_unsupported' ) );
2022-06-16 14:01:47 +02:00
if ( ! $this -> enabled_for_request () ) {
2019-11-15 23:26:29 +01:00
return ;
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
2022-06-16 14:01:47 +02:00
if ( isset ( $_GET [ 'relatedposts' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading and checking if we need to generate a list of excuded posts, does not update anything on the site.
2019-11-15 23:26:29 +01:00
$excludes = $this -> parse_numeric_get_arg ( 'relatedposts_exclude' );
2022-06-16 14:01:47 +02:00
$this -> action_frontend_init_ajax ( $excludes );
2019-11-15 23:26:29 +01:00
} else {
2023-09-26 10:24:36 +02:00
if ( isset ( $_GET [ 'relatedposts_hit' ] ) && isset ( $_GET [ 'relatedposts_origin' ] ) && isset ( $_GET [ 'relatedposts_position' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- checking if fields are set to setup tracking, nothing is changing on the site.
2022-12-15 17:41:53 +01:00
$this -> previous_post_id = ( int ) $_GET [ 'relatedposts_origin' ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- fetching a previous post ID for tracking, nothing is changing on the site.
2022-06-16 14:01:47 +02:00
$this -> log_click ( $this -> previous_post_id , get_the_ID (), sanitize_text_field ( wp_unslash ( $_GET [ 'relatedposts_position' ] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- logging the click for tracking, nothing is changing on the site.
2019-11-15 23:26:29 +01:00
}
2022-06-16 14:01:47 +02:00
$this -> action_frontend_init_page ();
2019-11-15 23:26:29 +01:00
}
}
/**
* Render insertion point .
*
* @ since 4.2 . 0
*
* @ return string
*/
public function get_headline () {
$options = $this -> get_options ();
if ( $options [ 'show_headline' ] ) {
$headline = sprintf (
/** This filter is already documented in modules/sharedaddy/sharing-service.php */
apply_filters ( 'jetpack_sharing_headline_html' , '<h3 class="jp-relatedposts-headline"><em>%s</em></h3>' , esc_html ( $options [ 'headline' ] ), 'related-posts' ),
esc_html ( $options [ 'headline' ] )
);
} else {
$headline = '' ;
}
return $headline ;
}
/**
* Adds a target to the post content to load related posts into if a shortcode for it did not already exist .
2021-07-23 11:58:50 +02:00
* Will skip adding the target if the post content contains a Related Posts block , if the 'get_the_excerpt'
* hook is in the current filter list , or if the site is running an FSE / Site Editor theme .
2019-11-15 23:26:29 +01:00
*
* @ filter the_content
*
* @ param string $content Post content .
*
* @ returns string
*/
public function filter_add_target_to_dom ( $content ) {
2023-09-26 10:24:36 +02:00
// Do not output related posts for ActivityPub requests.
if (
function_exists ( '\Activitypub\is_activitypub_request' )
&& \Activitypub\is_activitypub_request ()
) {
return $content ;
}
2021-07-23 11:58:50 +02:00
if ( has_block ( 'jetpack/related-posts' ) || Blocks :: is_fse_theme () ) {
2019-11-15 23:26:29 +01:00
return $content ;
}
2022-06-16 14:01:47 +02:00
if ( ! $this -> found_shortcode && ! doing_filter ( 'get_the_excerpt' ) ) {
2019-11-15 23:26:29 +01:00
if ( class_exists ( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support :: is_amp_request () ) {
$content .= " \n " . $this -> get_server_rendered_html ();
} else {
$content .= " \n " . $this -> get_client_rendered_html ();
}
}
return $content ;
}
/**
* Render static markup based on the Gutenberg block code
*
* @ return string Rendered related posts HTML .
*/
public function get_server_rendered_html () {
2020-03-03 18:49:45 +01:00
$rp_settings = $this -> get_options ();
2019-11-15 23:26:29 +01:00
$block_rp_settings = array (
'displayThumbnails' => $rp_settings [ 'show_thumbnails' ],
'showHeadline' => $rp_settings [ 'show_headline' ],
'displayDate' => isset ( $rp_settings [ 'show_date' ] ) ? ( bool ) $rp_settings [ 'show_date' ] : true ,
'displayContext' => isset ( $rp_settings [ 'show_context' ] ) && $rp_settings [ 'show_context' ],
'postLayout' => isset ( $rp_settings [ 'layout' ] ) ? $rp_settings [ 'layout' ] : 'grid' ,
'postsToShow' => isset ( $rp_settings [ 'size' ] ) ? $rp_settings [ 'size' ] : 3 ,
/** This filter is already documented in modules/related-posts/jetpack-related-posts.php */
'headline' => apply_filters ( 'jetpack_relatedposts_filter_headline' , $this -> get_headline () ),
2022-12-15 17:41:53 +01:00
'isServerRendered' => true ,
2019-11-15 23:26:29 +01:00
);
2023-09-26 10:24:36 +02:00
return $this -> render_block ( $block_rp_settings , '' );
2019-11-15 23:26:29 +01:00
}
/**
* Looks for our shortcode on the unfiltered content , this has to execute early .
*
* @ filter the_content
2022-06-16 14:01:47 +02:00
* @ param string $content - content of the post .
2019-11-15 23:26:29 +01:00
* @ uses has_shortcode
2022-06-16 14:01:47 +02:00
* @ return string $content
2019-11-15 23:26:29 +01:00
*/
public function test_for_shortcode ( $content ) {
2022-06-16 14:01:47 +02:00
$this -> found_shortcode = has_shortcode ( $content , self :: SHORTCODE );
2019-11-15 23:26:29 +01:00
return $content ;
}
/**
* Returns the HTML for the related posts section .
*
* @ uses esc_html__ , apply_filters
2022-06-16 14:01:47 +02:00
* @ return string
2019-11-15 23:26:29 +01:00
*/
public function get_client_rendered_html () {
if ( Settings :: is_syncing () ) {
return '' ;
}
/**
* Filter the Related Posts headline .
*
* @ module related - posts
*
* @ since 3.0 . 0
*
* @ param string $headline Related Posts heading .
*/
$headline = apply_filters ( 'jetpack_relatedposts_filter_headline' , $this -> get_headline () );
2022-06-16 14:01:47 +02:00
if ( $this -> previous_post_id ) {
$exclude = " data-exclude=' { $this -> previous_post_id } ' " ;
2019-11-15 23:26:29 +01:00
} else {
2022-06-16 14:01:47 +02:00
$exclude = '' ;
2019-11-15 23:26:29 +01:00
}
return <<< EOT
< div id = 'jp-relatedposts' class = 'jp-relatedposts' $exclude >
$headline
</ div >
EOT ;
}
/**
* 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 .
*
* @ returns string
*/
public function get_client_rendered_html_unsupported () {
if ( Settings :: is_syncing () ) {
return '' ;
}
return " \n \n <!-- Jetpack Related Posts is not supported in this context. --> \n \n " ;
}
/**
* ===============
* GUTENBERG BLOCK
* ===============
*/
/**
* Echoes out items for the Gutenberg block
*
2023-09-26 10:24:36 +02:00
* @ param array $related_post The post object .
2019-11-15 23:26:29 +01:00
* @ param array $block_attributes The block attributes .
*/
public function render_block_item ( $related_post , $block_attributes ) {
$instance_id = 'related-posts-item-' . uniqid ();
$label_id = $instance_id . '-label' ;
2024-02-16 11:03:01 +01:00
$title = $related_post [ 'title' ];
$url = $related_post [ 'url' ];
$rel = $related_post [ 'rel' ];
$img = '' ;
$list = '' ;
2019-11-15 23:26:29 +01:00
$item_markup = sprintf (
2024-02-16 11:03:01 +01:00
'<li id="%1$s" class="jp-related-posts-i2__post">' ,
esc_attr ( $instance_id )
2019-11-15 23:26:29 +01:00
);
2024-02-16 11:03:01 +01:00
// Thumbnail
2019-11-15 23:26:29 +01:00
if ( ! empty ( $block_attributes [ 'show_thumbnails' ] ) && ! empty ( $related_post [ 'img' ][ 'src' ] ) ) {
2024-02-16 11:03:01 +01:00
$img = sprintf (
'<img loading="lazy" class="jp-related-posts-i2__post-img" src="%1$s" alt="%2$s" %3$s/>' ,
2019-11-15 23:26:29 +01:00
esc_url ( $related_post [ 'img' ][ 'src' ] ),
2023-09-26 10:24:36 +02:00
esc_attr ( $related_post [ 'img' ][ 'alt_text' ] ),
( ! empty ( $related_post [ 'img' ][ 'srcset' ] ) ? 'srcset="' . esc_attr ( $related_post [ 'img' ][ 'srcset' ] ) . '"' : '' )
2019-11-15 23:26:29 +01:00
);
}
2024-02-16 11:03:01 +01:00
// Link
$item_markup .= sprintf (
'<a id="%1$s" href="%2$s" class="jp-related-posts-i2__post-link" %3$s>%4$s%5$s</a>' ,
esc_attr ( $label_id ),
esc_url ( $url ),
( ! empty ( $rel ) ? 'rel="' . esc_attr ( $rel ) . '"' : '' ),
esc_html ( $title ),
$img
);
2019-11-15 23:26:29 +01:00
2024-02-16 11:03:01 +01:00
// Date
if ( $block_attributes [ 'show_date' ] ) {
$list .= '<dt>' . __ ( 'Date' , 'jetpack' ) . '</dt>' ;
$list .= '<dd class="jp-related-posts-i2__post-date">' ;
$list .= esc_html ( $related_post [ 'date' ] );
$list .= '</dd>' ;
2019-11-15 23:26:29 +01:00
}
2024-02-16 11:03:01 +01:00
// Author
2023-09-26 10:24:36 +02:00
if ( $block_attributes [ 'show_author' ] ) {
2024-02-16 11:03:01 +01:00
$list .= '<dt>' . __ ( 'Author' , 'jetpack' ) . '</dt>' ;
$list .= '<dd class="jp-related-posts-i2__post-author">' ;
$list .= esc_html ( $related_post [ 'author' ] );
$list .= '</dd>' ;
2023-09-26 10:24:36 +02:00
}
2024-02-16 11:03:01 +01:00
// Context
2023-09-26 10:24:36 +02:00
if ( ( $block_attributes [ 'show_context' ] ) && ! empty ( $related_post [ 'block_context' ] ) ) {
2024-02-16 11:03:01 +01:00
// translators: this is followed by the reason why the item is related to the current post
$list .= '<dt>' . __ ( 'In relation to' , 'jetpack' ) . '</dt>' ;
$list .= '<dd class="jp-related-posts-i2__post-context">' ;
2023-09-26 10:24:36 +02:00
// Note: The original 'context' value is not used when rendering the block.
// It is still generated and available for the legacy rendering code path though.
// See './related-posts.js' for that usage.
$block_context = $related_post [ 'block_context' ];
2024-02-16 11:03:01 +01:00
2023-09-26 10:24:36 +02:00
if ( ! empty ( $block_context [ 'link' ] ) ) {
2024-02-16 11:03:01 +01:00
$list .= sprintf (
'<a href="%1$s">%2$s</a>' ,
2023-09-26 10:24:36 +02:00
esc_url ( $block_context [ 'link' ] ),
esc_html ( $block_context [ 'text' ] )
);
} else {
2024-02-16 11:03:01 +01:00
$list .= esc_html ( $block_context [ 'text' ] );
2023-09-26 10:24:36 +02:00
}
2024-02-16 11:03:01 +01:00
$list .= '</dd>' ;
2019-11-15 23:26:29 +01:00
}
2024-02-16 11:03:01 +01:00
// Metadata
if ( ! empty ( $list ) ) {
$item_markup .= '<dl class="jp-related-posts-i2__post-defs">' . $list . '</dl>' ;
}
$item_markup .= '</li>' ;
2019-11-15 23:26:29 +01:00
return $item_markup ;
}
/**
2024-02-16 11:03:01 +01:00
* Render the list of related posts .
2019-11-15 23:26:29 +01:00
*
2024-02-16 11:03:01 +01:00
* @ param array $posts The posts to render into the list .
2019-11-15 23:26:29 +01:00
* @ param array $block_attributes Block attributes .
2024-02-16 11:03:01 +01:00
* @ return string
2019-11-15 23:26:29 +01:00
*/
2024-02-16 11:03:01 +01:00
public function render_post_list ( $posts , $block_attributes ) {
$markup = '' ;
2019-11-15 23:26:29 +01:00
foreach ( $posts as $post ) {
2024-02-16 11:03:01 +01:00
$markup .= $this -> render_block_item ( $post , $block_attributes );
2019-11-15 23:26:29 +01:00
}
2024-02-16 11:03:01 +01:00
2019-11-15 23:26:29 +01:00
return sprintf (
2024-02-16 11:03:01 +01:00
// role="list" is required for accessibility as VoiceOver ignores unstyled lists.
'<ul class="jp-related-posts-i2__list" role="list" data-post-count="%1$s">%2$s</ul>' ,
2019-11-15 23:26:29 +01:00
count ( $posts ),
2024-02-16 11:03:01 +01:00
$markup
2019-11-15 23:26:29 +01:00
);
}
/**
* Render the related posts markup .
*
2024-04-17 11:32:24 +02:00
* @ param array $attributes Block attributes .
* @ param string $content String containing the related Posts block content .
* @ param WP_Block $block The block object .
2019-11-15 23:26:29 +01:00
* @ return string
*/
2024-04-17 11:32:24 +02:00
public function render_block ( $attributes , $content , $block = null ) {
2023-09-26 10:24:36 +02:00
if ( ! jetpack_is_frontend () ) {
return $content ;
}
$wrapper_attributes = array ();
$post_id = get_the_ID ();
$block_attributes = array (
2019-11-15 23:26:29 +01:00
'headline' => isset ( $attributes [ 'headline' ] ) ? $attributes [ 'headline' ] : null ,
'show_thumbnails' => isset ( $attributes [ 'displayThumbnails' ] ) && $attributes [ 'displayThumbnails' ],
2023-09-26 10:24:36 +02:00
'show_author' => isset ( $attributes [ 'displayAuthor' ] ) ? ( bool ) $attributes [ 'displayAuthor' ] : false ,
'show_headline' => isset ( $attributes [ 'displayHeadline' ] ) ? ( bool ) $attributes [ 'displayHeadline' ] : false ,
2019-11-15 23:26:29 +01:00
'show_date' => isset ( $attributes [ 'displayDate' ] ) ? ( bool ) $attributes [ 'displayDate' ] : true ,
'show_context' => isset ( $attributes [ 'displayContext' ] ) && $attributes [ 'displayContext' ],
'layout' => isset ( $attributes [ 'postLayout' ] ) && 'list' === $attributes [ 'postLayout' ] ? $attributes [ 'postLayout' ] : 'grid' ,
'size' => ! empty ( $attributes [ 'postsToShow' ] ) ? absint ( $attributes [ 'postsToShow' ] ) : 3 ,
);
$excludes = $this -> parse_numeric_get_arg ( 'relatedposts_origin' );
$related_posts = $this -> get_for_post_id (
2022-04-02 10:26:41 +02:00
$post_id ,
2019-11-15 23:26:29 +01:00
array (
'size' => $block_attributes [ 'size' ],
'exclude_post_ids' => $excludes ,
)
);
if ( empty ( $related_posts ) ) {
return '' ;
}
2024-02-16 11:03:01 +01:00
$list_markup = $this -> render_post_list ( $related_posts , $block_attributes );
2019-11-15 23:26:29 +01:00
2022-12-15 17:41:53 +01:00
if ( empty ( $attributes [ 'isServerRendered' ] ) ) {
// The get_server_rendered_html() path won't register a block,
// so only apply block supports when not server rendered.
$wrapper_attributes = \WP_Block_Supports :: get_instance () -> apply_block_supports ();
}
2022-04-02 10:26:41 +02:00
2023-09-26 10:24:36 +02:00
$headline_markup = '' ;
2024-02-16 11:03:01 +01:00
2024-04-17 11:32:24 +02:00
if ( isset ( $block ) ) {
foreach ( $block -> inner_blocks as $inner_block ) {
if ( 'core/heading' === $inner_block -> name && ! empty ( wp_strip_all_tags ( $inner_block -> inner_html ) ) ) {
$headline_markup = trim ( $inner_block -> inner_html );
break ;
}
}
}
if ( empty ( $headline_markup ) && $block_attributes [ 'show_headline' ] === true ) {
2023-09-26 10:24:36 +02:00
$headline = $block_attributes [ 'headline' ];
if ( strlen ( trim ( $headline ) ) !== 0 ) {
$headline_markup = sprintf (
2024-04-17 11:32:24 +02:00
'<h3 class="jp-relatedposts-headline">%1$s</h3>' ,
esc_html ( $headline )
2023-09-26 10:24:36 +02:00
);
}
}
2024-04-17 11:32:24 +02:00
2022-04-02 10:26:41 +02:00
$display_markup = sprintf (
2024-04-17 11:32:24 +02:00
'<nav class="jp-relatedposts-i2%1$s"%2$s data-layout="%3$s" aria-label="%6$s">%4$s%5$s</nav>' ,
2022-04-02 10:26:41 +02:00
! empty ( $wrapper_attributes [ 'class' ] ) ? ' ' . esc_attr ( $wrapper_attributes [ 'class' ] ) : '' ,
! empty ( $wrapper_attributes [ 'style' ] ) ? ' style="' . esc_attr ( $wrapper_attributes [ 'style' ] ) . '"' : '' ,
2019-11-15 23:26:29 +01:00
esc_attr ( $block_attributes [ 'layout' ] ),
2023-09-26 10:24:36 +02:00
$headline_markup ,
2024-02-16 11:03:01 +01:00
$list_markup ,
2024-04-17 11:32:24 +02:00
empty ( $headline_markup ) ? esc_attr__ ( 'Related Posts' , 'jetpack' ) : esc_attr ( wp_strip_all_tags ( $headline_markup ) )
2019-11-15 23:26:29 +01:00
);
2022-04-02 10:26:41 +02:00
/**
* Filter the output HTML of Related Posts .
*
* @ module related - posts
*
* @ since 10.7
*
* @ param string $display_markup HTML output of Related Posts .
* @ param int | false get_the_ID () Post ID of the post for which we are retrieving Related Posts .
* @ param array $related_posts Array of related posts .
* @ param array $block_attributes Array of Block attributes .
*/
2023-04-26 17:39:43 +02:00
return ( string ) apply_filters ( 'jetpack_related_posts_display_markup' , $display_markup , $post_id , $related_posts , $block_attributes );
2019-11-15 23:26:29 +01:00
}
/**
* ========================
* PUBLIC UTILITY FUNCTIONS
* ========================
*/
/**
* Parse a numeric GET variable to an array of values .
*
* @ since 6.9 . 0
*
* @ uses absint
*
* @ param string $arg Name of the GET variable .
* @ return array $result Parsed value ( s )
*/
public function parse_numeric_get_arg ( $arg ) {
$result = array ();
2022-06-16 14:01:47 +02:00
if ( isset ( $_GET [ $arg ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- requests are used to generate a list of related posts we want to exclude.
if ( is_string ( $_GET [ $arg ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$result = explode ( ',' , sanitize_text_field ( wp_unslash ( $_GET [ $arg ] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
} elseif ( is_array ( $_GET [ $arg ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$args = array_map ( 'sanitize_text_field' , wp_unslash ( $_GET [ $arg ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$result = array_values ( $args );
2019-11-15 23:26:29 +01:00
}
$result = array_unique ( array_filter ( array_map ( 'absint' , $result ) ) );
}
return $result ;
}
/**
* Gets options set for Jetpack_RelatedPosts and merge with defaults .
*
* @ uses Jetpack_Options :: get_option , apply_filters
* @ return array
*/
public function get_options () {
2022-06-16 14:01:47 +02:00
if ( null === $this -> options ) {
$this -> options = Jetpack_Options :: get_option ( 'relatedposts' , array () );
if ( ! is_array ( $this -> options ) ) {
$this -> options = array ();
}
if ( ! isset ( $this -> options [ 'enabled' ] ) ) {
$this -> options [ 'enabled' ] = true ;
}
if ( ! isset ( $this -> options [ 'show_headline' ] ) ) {
$this -> options [ 'show_headline' ] = true ;
2019-11-15 23:26:29 +01:00
}
2022-06-16 14:01:47 +02:00
if ( ! isset ( $this -> options [ 'show_thumbnails' ] ) ) {
$this -> options [ 'show_thumbnails' ] = false ;
2019-11-15 23:26:29 +01:00
}
2022-06-16 14:01:47 +02:00
if ( ! isset ( $this -> options [ 'show_date' ] ) ) {
$this -> options [ 'show_date' ] = true ;
2019-11-15 23:26:29 +01:00
}
2022-06-16 14:01:47 +02:00
if ( ! isset ( $this -> options [ 'show_context' ] ) ) {
$this -> options [ 'show_context' ] = true ;
}
if ( ! isset ( $this -> options [ 'layout' ] ) ) {
$this -> options [ 'layout' ] = 'grid' ;
}
if ( ! isset ( $this -> options [ 'headline' ] ) ) {
$this -> options [ 'headline' ] = esc_html__ ( 'Related' , 'jetpack' );
}
if ( empty ( $this -> options [ 'size' ] ) || ( int ) $this -> options [ 'size' ] < 1 ) {
$this -> options [ 'size' ] = 3 ;
2019-11-15 23:26:29 +01:00
}
/**
* Filter Related Posts basic options .
*
* @ module related - posts
*
* @ since 2.8 . 0
*
* @ param array $this -> _options Array of basic Related Posts options .
*/
2022-06-16 14:01:47 +02:00
$this -> options = apply_filters ( 'jetpack_relatedposts_filter_options' , $this -> options );
2019-11-15 23:26:29 +01:00
}
2022-06-16 14:01:47 +02:00
return $this -> options ;
2019-11-15 23:26:29 +01:00
}
2022-06-16 14:01:47 +02:00
/**
* Gets options .
*
* @ param string $option_name - option we want to get .
*/
2019-11-15 23:26:29 +01:00
public function get_option ( $option_name ) {
$options = $this -> get_options ();
if ( isset ( $options [ $option_name ] ) ) {
return $options [ $option_name ];
}
return false ;
}
/**
* Parses input and returns normalized options array .
*
2022-06-16 14:01:47 +02:00
* @ param array $input - input we ' re parsing .
2019-11-15 23:26:29 +01:00
* @ uses self :: get_options
* @ return array
*/
public function parse_options ( $input ) {
$current = $this -> get_options ();
2022-06-16 14:01:47 +02:00
if ( ! is_array ( $input ) ) {
2019-11-15 23:26:29 +01:00
$input = array ();
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
if (
! isset ( $input [ 'enabled' ] )
|| isset ( $input [ 'show_date' ] )
|| isset ( $input [ 'show_context' ] )
|| isset ( $input [ 'layout' ] )
|| isset ( $input [ 'headline' ] )
) {
$input [ 'enabled' ] = '1' ;
}
2022-06-16 14:01:47 +02:00
if ( '1' == $input [ 'enabled' ] ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- expecting string, but may return bools.
$current [ 'enabled' ] = true ;
$current [ 'show_headline' ] = ( isset ( $input [ 'show_headline' ] ) && '1' == $input [ 'show_headline' ] ); // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
$current [ 'show_thumbnails' ] = ( isset ( $input [ 'show_thumbnails' ] ) && '1' == $input [ 'show_thumbnails' ] ); // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
$current [ 'show_date' ] = ( isset ( $input [ 'show_date' ] ) && '1' == $input [ 'show_date' ] ); // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
$current [ 'show_context' ] = ( isset ( $input [ 'show_context' ] ) && '1' == $input [ 'show_context' ] ); // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
$current [ 'layout' ] = isset ( $input [ 'layout' ] ) && in_array ( $input [ 'layout' ], array ( 'grid' , 'list' ), true ) ? $input [ 'layout' ] : 'grid' ;
$current [ 'headline' ] = isset ( $input [ 'headline' ] ) ? $input [ 'headline' ] : esc_html__ ( 'Related' , 'jetpack' );
2019-11-15 23:26:29 +01:00
} else {
$current [ 'enabled' ] = false ;
}
2022-06-16 14:01:47 +02:00
if ( isset ( $input [ 'size' ] ) && ( int ) $input [ 'size' ] > 0 ) {
$current [ 'size' ] = ( int ) $input [ 'size' ];
} else {
2019-11-15 23:26:29 +01:00
$current [ 'size' ] = null ;
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
return $current ;
}
/**
* HTML for admin settings page .
*
* @ uses self :: get_options , checked , esc_html__
* @ returns null
*/
public function print_setting_html () {
$options = $this -> get_options ();
$ui_settings_template = <<< EOT
< p class = " description " >% s </ p >
< ul id = " settings-reading-relatedposts-customize " >
< li >
< label >< input name = " jetpack_relatedposts[show_headline] " type = " checkbox " value = " 1 " % s /> % s </ label >
</ li >
< li >
< label >< input name = " jetpack_relatedposts[show_thumbnails] " type = " checkbox " value = " 1 " % s /> % s </ label >
</ li >
< li >
< label >< input name = " jetpack_relatedposts[show_date] " type = " checkbox " value = " 1 " % s /> % s </ label >
</ li >
< li >
< label >< input name = " jetpack_relatedposts[show_context] " type = " checkbox " value = " 1 " % s /> % s </ label >
</ li >
</ ul >
< div id = 'settings-reading-relatedposts-preview' >
% s
< div id = " jp-relatedposts " class = " jp-relatedposts " ></ div >
</ div >
EOT ;
2022-06-16 14:01:47 +02:00
$ui_settings = sprintf (
2019-11-15 23:26:29 +01:00
$ui_settings_template ,
esc_html__ ( 'The following settings will impact all related posts on your site, except for those you created via the block editor:' , 'jetpack' ),
checked ( $options [ 'show_headline' ], true , false ),
esc_html__ ( 'Highlight related content with a heading' , 'jetpack' ),
checked ( $options [ 'show_thumbnails' ], true , false ),
esc_html__ ( 'Show a thumbnail image where available' , 'jetpack' ),
checked ( $options [ 'show_date' ], true , false ),
esc_html__ ( 'Show entry date' , 'jetpack' ),
checked ( $options [ 'show_context' ], true , false ),
esc_html__ ( 'Show context (category or tag)' , 'jetpack' ),
esc_html__ ( 'Preview:' , 'jetpack' )
);
2022-06-16 14:01:47 +02:00
if ( ! $this -> allow_feature_toggle () ) {
2019-11-15 23:26:29 +01:00
$template = <<< EOT
< input type = " hidden " name = " jetpack_relatedposts[enabled] " value = " 1 " />
% s
EOT ;
printf (
2022-06-16 14:01:47 +02:00
$template , // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$ui_settings // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- data is escaped when variable is set.
2019-11-15 23:26:29 +01:00
);
} else {
$template = <<< EOT
< ul id = " settings-reading-relatedposts " >
< li >
< label >< input type = " radio " name = " jetpack_relatedposts[enabled] " value = " 0 " class = " tog " % s /> % s </ label >
</ li >
< li >
< label >< input type = " radio " name = " jetpack_relatedposts[enabled] " value = " 1 " class = " tog " % s /> % s </ label >
% s
</ li >
</ ul >
EOT ;
printf (
2022-06-16 14:01:47 +02:00
$template , // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
2019-11-15 23:26:29 +01:00
checked ( $options [ 'enabled' ], false , false ),
esc_html__ ( 'Hide related content after posts' , 'jetpack' ),
checked ( $options [ 'enabled' ], true , false ),
esc_html__ ( 'Show related content after posts' , 'jetpack' ),
2022-06-16 14:01:47 +02:00
$ui_settings // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- data is escaped when variable is set.
2019-11-15 23:26:29 +01:00
);
}
}
/**
* Head JS / CSS for admin settings page .
*
* @ uses esc_html__
* @ returns null
*/
public function print_setting_head () {
2022-06-16 14:01:47 +02:00
// only dislay the Related Posts JavaScript on the Reading Settings Admin Page.
$current_screen = get_current_screen ();
2019-11-15 23:26:29 +01:00
2022-06-16 14:01:47 +02:00
if ( $current_screen === null ) {
2019-11-15 23:26:29 +01:00
return ;
}
2022-06-16 14:01:47 +02:00
if ( 'options-reading' !== $current_screen -> id ) {
2019-11-15 23:26:29 +01:00
return ;
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
$related_headline = sprintf (
'<h3 class="jp-relatedposts-headline"><em>%s</em></h3>' ,
esc_html__ ( 'Related' , 'jetpack' )
);
2022-06-16 14:01:47 +02:00
$href_params = 'class="jp-relatedposts-post-a" href="#jetpack_relatedposts" rel="nofollow" data-origin="0" data-position="0"' ;
$related_with_images = <<< EOT
2019-11-15 23:26:29 +01:00
< div class = " jp-relatedposts-items jp-relatedposts-items-visual " >
< div class = " jp-relatedposts-post jp-relatedposts-post0 jp-relatedposts-post-thumbs " data - post - id = " 0 " data - post - format = " image " >
< a $href_params >
< 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 " >
</ a >
< h4 class = " jp-relatedposts-post-title " >
< a $href_params > Big iPhone / iPad Update Now Available </ a >
</ h4 >
< p class = " jp-relatedposts-post-excerpt " > Big iPhone / iPad Update Now Available </ p >
< p class = " jp-relatedposts-post-context " > In " Mobile " </ p >
</ div >
< div class = " jp-relatedposts-post jp-relatedposts-post1 jp-relatedposts-post-thumbs " data - post - id = " 0 " data - post - format = " image " >
< a $href_params >
< 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 " >
</ a >
< h4 class = " jp-relatedposts-post-title " >
< a $href_params > The WordPress for Android App Gets a Big Facelift </ a >
</ h4 >
< p class = " jp-relatedposts-post-excerpt " > The WordPress for Android App Gets a Big Facelift </ p >
< p class = " jp-relatedposts-post-context " > In " Mobile " </ p >
</ div >
< div class = " jp-relatedposts-post jp-relatedposts-post2 jp-relatedposts-post-thumbs " data - post - id = " 0 " data - post - format = " image " >
< a $href_params >
< 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 " >
</ a >
< h4 class = " jp-relatedposts-post-title " >
< a $href_params > Upgrade Focus : VideoPress For Weddings </ a >
</ h4 >
< p class = " jp-relatedposts-post-excerpt " > Upgrade Focus : VideoPress For Weddings </ p >
< p class = " jp-relatedposts-post-context " > In " Upgrade " </ p >
</ div >
</ div >
EOT ;
2022-06-16 14:01:47 +02:00
$related_with_images = str_replace ( " \n " , '' , $related_with_images );
2019-11-15 23:26:29 +01:00
$related_without_images = <<< EOT
< div class = " jp-relatedposts-items jp-relatedposts-items-minimal " >
< p class = " jp-relatedposts-post jp-relatedposts-post0 " data - post - id = " 0 " data - post - format = " image " >
< span class = " jp-relatedposts-post-title " >< a $href_params > Big iPhone / iPad Update Now Available </ a ></ span >
< span class = " jp-relatedposts-post-context " > In " Mobile " </ span >
</ p >
< p class = " jp-relatedposts-post jp-relatedposts-post1 " data - post - id = " 0 " data - post - format = " image " >
< span class = " jp-relatedposts-post-title " >< a $href_params > The WordPress for Android App Gets a Big Facelift </ a ></ span >
< span class = " jp-relatedposts-post-context " > In " Mobile " </ span >
</ p >
< p class = " jp-relatedposts-post jp-relatedposts-post2 " data - post - id = " 0 " data - post - format = " image " >
< span class = " jp-relatedposts-post-title " >< a $href_params > Upgrade Focus : VideoPress For Weddings </ a ></ span >
< span class = " jp-relatedposts-post-context " > In " Upgrade " </ span >
</ p >
</ div >
EOT ;
$related_without_images = str_replace ( " \n " , '' , $related_without_images );
2022-06-16 14:01:47 +02:00
if ( $this -> allow_feature_toggle () ) {
2019-11-15 23:26:29 +01:00
$extra_css = '#settings-reading-relatedposts-customize { padding-left:2em; margin-top:.5em; }' ;
} else {
$extra_css = '' ;
}
2023-09-26 10:24:36 +02:00
// phpcs:disable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped -- Escaped above where needed.
2019-11-15 23:26:29 +01:00
echo <<< EOT
< style type = " text/css " >
#settings-reading-relatedposts .disabled { opacity:.5; filter:Alpha(opacity=50); }
#settings-reading-relatedposts-preview .jp-relatedposts { background:#fff; padding:.5em; width:75%; }
$extra_css
</ style >
< script type = " text/javascript " >
jQuery ( document ) . ready ( function ( $ ) {
var update_ui = function () {
var is_enabled = true ;
if ( 'radio' == $ ( 'input[name="jetpack_relatedposts[enabled]"]' ) . attr ( 'type' ) ) {
if ( '0' == $ ( 'input[name="jetpack_relatedposts[enabled]"]:checked' ) . val () ) {
is_enabled = false ;
}
}
if ( is_enabled ) {
$ ( '#settings-reading-relatedposts-customize' )
. removeClass ( 'disabled' )
. find ( 'input' )
. attr ( 'disabled' , false );
$ ( '#settings-reading-relatedposts-preview' )
. removeClass ( 'disabled' );
} else {
$ ( '#settings-reading-relatedposts-customize' )
. addClass ( 'disabled' )
. find ( 'input' )
. attr ( 'disabled' , true );
$ ( '#settings-reading-relatedposts-preview' )
. addClass ( 'disabled' );
}
};
var update_preview = function () {
var html = '' ;
if ( $ ( 'input[name="jetpack_relatedposts[show_headline]"]:checked' ) . length ) {
html += '$related_headline' ;
}
if ( $ ( 'input[name="jetpack_relatedposts[show_thumbnails]"]:checked' ) . length ) {
html += '$related_with_images' ;
} else {
html += '$related_without_images' ;
}
$ ( '#settings-reading-relatedposts-preview .jp-relatedposts' ) . html ( html );
if ( $ ( 'input[name="jetpack_relatedposts[show_date]"]:checked' ) . length ) {
$ ( '.jp-relatedposts-post-title' ) . each ( function () {
$ ( this ) . after ( $ ( '<span>August 8, 2005</span>' ) );
} );
}
if ( $ ( 'input[name="jetpack_relatedposts[show_context]"]:checked' ) . length ) {
$ ( '.jp-relatedposts-post-context' ) . show ();
} else {
$ ( '.jp-relatedposts-post-context' ) . hide ();
}
$ ( '#settings-reading-relatedposts-preview .jp-relatedposts' ) . show ();
};
// Update on load
update_preview ();
update_ui ();
// Update on change
$ ( '#settings-reading-relatedposts-customize input' )
. change ( update_preview );
$ ( '#settings-reading-relatedposts' )
. find ( 'input.tog' )
. change ( update_ui );
});
</ script >
EOT ;
2023-09-26 10:24:36 +02:00
// phpcs:enable WordPress.Security.EscapeOutput.HeredocOutputNotEscaped
2019-11-15 23:26:29 +01:00
}
/**
* Gets an array of related posts that match the given post_id .
*
* @ param int $post_id Post which we want to find related posts for .
* @ param array $args - params to use when building Elasticsearch filters to narrow down the search domain .
* @ uses self :: get_options , get_post_type , wp_parse_args , apply_filters
* @ return array
*/
public function get_for_post_id ( $post_id , array $args ) {
$options = $this -> get_options ();
if ( ! empty ( $args [ 'size' ] ) ) {
$options [ 'size' ] = $args [ 'size' ];
}
if (
! $options [ 'enabled' ]
|| 0 === ( int ) $post_id
|| empty ( $options [ 'size' ] )
) {
return array ();
}
$defaults = array (
'size' => ( int ) $options [ 'size' ],
'post_type' => get_post_type ( $post_id ),
'post_formats' => array (),
'has_terms' => array (),
'date_range' => array (),
'exclude_post_ids' => array (),
);
$args = wp_parse_args ( $args , $defaults );
/**
* Filter the arguments used to retrieve a list of Related Posts .
*
* @ module related - posts
*
* @ since 2.8 . 0
*
* @ param array $args Array of options to retrieve Related Posts .
* @ param string $post_id Post ID of the post for which we are retrieving Related Posts .
*/
$args = apply_filters ( 'jetpack_relatedposts_filter_args' , $args , $post_id );
2022-06-16 14:01:47 +02:00
$filters = $this -> get_es_filters_from_args ( $post_id , $args );
2019-11-15 23:26:29 +01:00
/**
* Filter Elasticsearch options used to calculate Related Posts .
*
* @ module related - posts
*
* @ since 2.8 . 0
*
* @ param array $filters Array of Elasticsearch filters based on the post_id and args .
* @ param string $post_id Post ID of the post for which we are retrieving Related Posts .
*/
$filters = apply_filters ( 'jetpack_relatedposts_filter_filters' , $filters , $post_id );
2022-06-16 14:01:47 +02:00
$results = $this -> get_related_posts ( $post_id , $args [ 'size' ], $filters );
2019-11-15 23:26:29 +01:00
/**
* Filter the array of related posts matched by Elasticsearch .
*
* @ module related - posts
*
* @ since 2.8 . 0
*
* @ param array $results Array of related posts matched by Elasticsearch .
2022-04-02 10:26:41 +02:00
* @ param int $post_id Post ID of the post for which we are retrieving Related Posts .
2019-11-15 23:26:29 +01:00
*/
return apply_filters ( 'jetpack_relatedposts_returned_results' , $results , $post_id );
}
/**
* =========================
* PRIVATE UTILITY FUNCTIONS
* =========================
*/
/**
* Creates an array of Elasticsearch filters based on the post_id and args .
*
2022-06-16 14:01:47 +02:00
* @ param int $post_id - the post ID .
* @ param array $args - the arguments .
2019-11-15 23:26:29 +01:00
* @ uses apply_filters , get_post_types , get_post_format_strings
* @ return array
*/
2022-06-16 14:01:47 +02:00
protected function get_es_filters_from_args ( $post_id , array $args ) {
2019-11-15 23:26:29 +01:00
$filters = array ();
/**
* Filter the terms used to search for Related Posts .
*
* @ module related - posts
*
* @ since 2.8 . 0
*
* @ param array $args [ 'has_terms' ] Array of terms associated to the Related Posts .
* @ param string $post_id Post ID of the post for which we are retrieving Related Posts .
*/
$args [ 'has_terms' ] = apply_filters ( 'jetpack_relatedposts_filter_has_terms' , $args [ 'has_terms' ], $post_id );
if ( ! empty ( $args [ 'has_terms' ] ) ) {
2022-06-16 14:01:47 +02:00
foreach ( ( array ) $args [ 'has_terms' ] as $term ) {
2019-11-15 23:26:29 +01:00
if ( mb_strlen ( $term -> taxonomy ) ) {
switch ( $term -> taxonomy ) {
case 'post_tag' :
$tax_fld = 'tag.slug' ;
break ;
case 'category' :
$tax_fld = 'category.slug' ;
break ;
default :
$tax_fld = 'taxonomy.' . $term -> taxonomy . '.slug' ;
break ;
}
$filters [] = array ( 'term' => array ( $tax_fld => $term -> slug ) );
}
}
}
/**
* Filter the Post Types where we search Related Posts .
*
* @ module related - posts
*
* @ since 2.8 . 0
*
* @ param array $args [ 'post_type' ] Array of Post Types .
* @ param string $post_id Post ID of the post for which we are retrieving Related Posts .
*/
$args [ 'post_type' ] = apply_filters ( 'jetpack_relatedposts_filter_post_type' , $args [ 'post_type' ], $post_id );
2022-06-16 14:01:47 +02:00
$valid_post_types = get_post_types ();
2019-11-15 23:26:29 +01:00
if ( is_array ( $args [ 'post_type' ] ) ) {
$sanitized_post_types = array ();
foreach ( $args [ 'post_type' ] as $pt ) {
2022-06-16 14:01:47 +02:00
if ( in_array ( $pt , $valid_post_types , true ) ) {
2019-11-15 23:26:29 +01:00
$sanitized_post_types [] = $pt ;
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
}
2022-06-16 14:01:47 +02:00
if ( ! empty ( $sanitized_post_types ) ) {
2019-11-15 23:26:29 +01:00
$filters [] = array ( 'terms' => array ( 'post_type' => $sanitized_post_types ) );
2022-06-16 14:01:47 +02:00
}
} elseif ( in_array ( $args [ 'post_type' ], $valid_post_types , true ) && 'all' !== $args [ 'post_type' ] ) {
$filters [] = array ( 'term' => array ( 'post_type' => $args [ 'post_type' ] ) );
2019-11-15 23:26:29 +01:00
}
/**
* Filter the Post Formats where we search Related Posts .
*
* @ module related - posts
*
* @ since 3.3 . 0
*
* @ param array $args [ 'post_formats' ] Array of Post Formats .
* @ param string $post_id Post ID of the post for which we are retrieving Related Posts .
*/
2022-06-16 14:01:47 +02:00
$args [ 'post_formats' ] = apply_filters ( 'jetpack_relatedposts_filter_post_formats' , $args [ 'post_formats' ], $post_id );
$valid_post_formats = get_post_format_strings ();
2019-11-15 23:26:29 +01:00
$sanitized_post_formats = array ();
foreach ( $args [ 'post_formats' ] as $pf ) {
if ( array_key_exists ( $pf , $valid_post_formats ) ) {
$sanitized_post_formats [] = $pf ;
}
}
if ( ! empty ( $sanitized_post_formats ) ) {
$filters [] = array ( 'terms' => array ( 'post_format' => $sanitized_post_formats ) );
}
/**
* Filter the date range used to search Related Posts .
*
* @ module related - posts
*
* @ since 2.8 . 0
*
* @ param array $args [ 'date_range' ] Array of a month interval where we search Related Posts .
* @ param string $post_id Post ID of the post for which we are retrieving Related Posts .
*/
$args [ 'date_range' ] = apply_filters ( 'jetpack_relatedposts_filter_date_range' , $args [ 'date_range' ], $post_id );
if ( is_array ( $args [ 'date_range' ] ) && ! empty ( $args [ 'date_range' ] ) ) {
$args [ 'date_range' ] = array_map ( 'intval' , $args [ 'date_range' ] );
2022-06-16 14:01:47 +02:00
if ( ! empty ( $args [ 'date_range' ][ 'from' ] ) && ! empty ( $args [ 'date_range' ][ 'to' ] ) ) {
2019-11-15 23:26:29 +01:00
$filters [] = array (
'range' => array (
2022-06-16 14:01:47 +02:00
'date_gmt' => $this -> get_coalesced_range ( $args [ 'date_range' ] ),
),
2019-11-15 23:26:29 +01:00
);
}
}
/**
* Filter the Post IDs excluded from appearing in Related Posts .
*
* @ module related - posts
*
* @ since 2.9 . 0
*
* @ param array $args [ 'exclude_post_ids' ] Array of Post IDs .
* @ param string $post_id Post ID of the post for which we are retrieving Related Posts .
*/
$args [ 'exclude_post_ids' ] = apply_filters ( 'jetpack_relatedposts_filter_exclude_post_ids' , $args [ 'exclude_post_ids' ], $post_id );
2022-06-16 14:01:47 +02:00
if ( ! empty ( $args [ 'exclude_post_ids' ] ) && is_array ( $args [ 'exclude_post_ids' ] ) ) {
2019-11-15 23:26:29 +01:00
$excluded_post_ids = array ();
2022-06-16 14:01:47 +02:00
foreach ( $args [ 'exclude_post_ids' ] as $exclude_post_id ) {
$exclude_post_id = ( int ) $exclude_post_id ;
if ( $exclude_post_id > 0 ) {
2019-11-15 23:26:29 +01:00
$excluded_post_ids [] = $exclude_post_id ;
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
}
$filters [] = array ( 'not' => array ( 'terms' => array ( 'post_id' => $excluded_post_ids ) ) );
}
return $filters ;
}
/**
* Takes a range and coalesces it into a month interval bracketed by a time as determined by the blog_id to enhance caching .
*
2022-06-16 14:01:47 +02:00
* @ todo Rewrite this function with proper date handling rather than `strtotime()` and `date()` .
*
* @ param array $date_range - the date range .
2019-11-15 23:26:29 +01:00
* @ return array
*/
2022-06-16 14:01:47 +02:00
protected function get_coalesced_range ( array $date_range ) {
$now = time ();
2019-11-15 23:26:29 +01:00
$coalesce_time = $this -> get_blog_id () % 86400 ;
2022-06-16 14:01:47 +02:00
$current_time = $now - strtotime ( 'today' , $now );
2019-11-15 23:26:29 +01:00
2022-06-16 14:01:47 +02:00
if ( $current_time < $coalesce_time && '01' === date ( 'd' , $now ) ) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
// Move back 1 period.
2019-11-15 23:26:29 +01:00
return array (
2022-06-16 14:01:47 +02:00
'from' => date ( 'Y-m-01' , strtotime ( '-1 month' , $date_range [ 'from' ] ) ) . ' ' . date ( 'H:i:s' , $coalesce_time ), //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
'to' => date ( 'Y-m-01' , $date_range [ 'to' ] ) . ' ' . date ( 'H:i:s' , $coalesce_time ), //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
2019-11-15 23:26:29 +01:00
);
} else {
2022-06-16 14:01:47 +02:00
// Use current period.
2019-11-15 23:26:29 +01:00
return array (
2022-06-16 14:01:47 +02:00
'from' => date ( 'Y-m-01' , $date_range [ 'from' ] ) . ' ' . date ( 'H:i:s' , $coalesce_time ), //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
'to' => date ( 'Y-m-01' , strtotime ( '+1 month' , $date_range [ 'to' ] ) ) . ' ' . date ( 'H:i:s' , $coalesce_time ), //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
2019-11-15 23:26:29 +01:00
);
}
}
/**
* Generate and output ajax response for related posts API call .
* NOTE : Calls exit () to end all further processing after payload has been outputed .
*
2022-06-16 14:01:47 +02:00
* @ param array $excludes array of post_ids to exclude .
2019-11-15 23:26:29 +01:00
* @ uses send_nosniff_header , self :: get_for_post_id , get_the_ID
2024-04-17 11:32:24 +02:00
* @ return never
2019-11-15 23:26:29 +01:00
*/
2022-06-16 14:01:47 +02:00
protected function action_frontend_init_ajax ( array $excludes ) {
2019-11-15 23:26:29 +01:00
define ( 'DOING_AJAX' , true );
2022-06-16 14:01:47 +02:00
header ( 'Content-type: application/json; charset=utf-8' ); // JSON can only be UTF-8.
2019-11-15 23:26:29 +01:00
send_nosniff_header ();
$options = $this -> get_options ();
2022-06-16 14:01:47 +02:00
if ( isset ( $_GET [ 'jetpackrpcustomize' ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- adds dummy content if we're in the customizer.
2019-11-15 23:26:29 +01:00
// If we're in the customizer, add dummy content.
2022-06-16 14:01:47 +02:00
$date_now = current_time ( get_option ( 'date_format' ) );
2019-11-15 23:26:29 +01:00
$related_posts = array (
array (
'id' => - 1 ,
'url' => 'https://jetpackme.files.wordpress.com/2019/03/cat-blog.png' ,
'url_meta' => array (
'origin' => 0 ,
2022-06-16 14:01:47 +02:00
'position' => 0 ,
2019-11-15 23:26:29 +01:00
),
'title' => esc_html__ ( 'Big iPhone/iPad Update Now Available' , 'jetpack' ),
'date' => $date_now ,
'format' => false ,
'excerpt' => esc_html__ ( 'It is that time of the year when devices are shiny again.' , 'jetpack' ),
'rel' => 'nofollow' ,
'context' => esc_html__ ( 'In "Mobile"' , 'jetpack' ),
'img' => array (
'src' => 'https://jetpackme.files.wordpress.com/2019/03/cat-blog.png' ,
'width' => 350 ,
2022-06-16 14:01:47 +02:00
'height' => 200 ,
2019-11-15 23:26:29 +01:00
),
2022-06-16 14:01:47 +02:00
'classes' => array (),
2019-11-15 23:26:29 +01:00
),
array (
'id' => - 1 ,
'url' => 'https://jetpackme.files.wordpress.com/2019/03/devices.jpg' ,
'url_meta' => array (
'origin' => 0 ,
2022-06-16 14:01:47 +02:00
'position' => 0 ,
2019-11-15 23:26:29 +01:00
),
'title' => esc_html__ ( 'The WordPress for Android App Gets a Big Facelift' , 'jetpack' ),
'date' => $date_now ,
'format' => false ,
'excerpt' => esc_html__ ( 'Writing is new again in Android with the new WordPress app.' , 'jetpack' ),
'rel' => 'nofollow' ,
'context' => esc_html__ ( 'In "Mobile"' , 'jetpack' ),
'img' => array (
'src' => 'https://jetpackme.files.wordpress.com/2019/03/devices.jpg' ,
'width' => 350 ,
2022-06-16 14:01:47 +02:00
'height' => 200 ,
2019-11-15 23:26:29 +01:00
),
2022-06-16 14:01:47 +02:00
'classes' => array (),
2019-11-15 23:26:29 +01:00
),
array (
'id' => - 1 ,
'url' => 'https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg' ,
'url_meta' => array (
'origin' => 0 ,
2022-06-16 14:01:47 +02:00
'position' => 0 ,
2019-11-15 23:26:29 +01:00
),
'title' => esc_html__ ( 'Upgrade Focus, VideoPress for weddings' , 'jetpack' ),
'date' => $date_now ,
'format' => false ,
'excerpt' => esc_html__ ( 'Weddings are in the spotlight now with VideoPress for weddings.' , 'jetpack' ),
'rel' => 'nofollow' ,
'context' => esc_html__ ( 'In "Mobile"' , 'jetpack' ),
'img' => array (
'src' => 'https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg' ,
'width' => 350 ,
2022-06-16 14:01:47 +02:00
'height' => 200 ,
2019-11-15 23:26:29 +01:00
),
2022-06-16 14:01:47 +02:00
'classes' => array (),
2019-11-15 23:26:29 +01:00
),
);
for ( $total = 0 ; $total < $options [ 'size' ] - 3 ; $total ++ ) {
$related_posts [] = $related_posts [ $total ];
}
$current_post = get_post ();
// Exclude current post after filtering to make sure it's excluded and not lost during filtering.
$excluded_posts = array_merge (
/** This filter is already documented in modules/related-posts/jetpack-related-posts.php */
apply_filters ( 'jetpack_relatedposts_filter_exclude_post_ids' , array () ),
array ( $current_post -> ID )
);
// Fetch posts with featured image.
2022-06-16 14:01:47 +02:00
$with_post_thumbnails = get_posts (
array (
'posts_per_page' => $options [ 'size' ],
'post__not_in' => $excluded_posts ,
'post_type' => $current_post -> post_type ,
'meta_key' => '_thumbnail_id' ,
'suppress_filters' => false ,
)
);
2019-11-15 23:26:29 +01:00
// If we don't have enough, fetch posts without featured image.
2023-09-26 10:24:36 +02:00
$count_post_with_thumbnails = is_countable ( $with_post_thumbnails ) ? count ( $with_post_thumbnails ) : 0 ;
$more = $options [ 'size' ] - $count_post_with_thumbnails ;
2022-06-16 14:01:47 +02:00
if ( 0 < $more ) {
$no_post_thumbnails = get_posts (
array (
'posts_per_page' => $more ,
'post__not_in' => $excluded_posts ,
'post_type' => $current_post -> post_type ,
'meta_query' => array (
array (
'key' => '_thumbnail_id' ,
'compare' => 'NOT EXISTS' ,
),
2019-11-15 23:26:29 +01:00
),
2022-06-16 14:01:47 +02:00
'suppress_filters' => false ,
)
);
2019-11-15 23:26:29 +01:00
} else {
$no_post_thumbnails = array ();
}
foreach ( array_merge ( $with_post_thumbnails , $no_post_thumbnails ) as $index => $real_post ) {
$related_posts [ $index ][ 'id' ] = $real_post -> ID ;
$related_posts [ $index ][ 'url' ] = esc_url ( get_permalink ( $real_post ) );
2022-06-16 14:01:47 +02:00
$related_posts [ $index ][ 'title' ] = $this -> to_utf8 ( $this -> get_title ( $real_post -> post_title , $real_post -> post_content , $real_post -> ID ) );
2019-11-15 23:26:29 +01:00
$related_posts [ $index ][ 'date' ] = get_the_date ( '' , $real_post );
2022-06-16 14:01:47 +02:00
$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' );
$related_posts [ $index ][ 'img' ] = $this -> generate_related_post_image_params ( $real_post -> ID );
$related_posts [ $index ][ 'context' ] = $this -> generate_related_post_context ( $real_post -> ID );
2019-11-15 23:26:29 +01:00
}
} else {
$related_posts = $this -> get_for_post_id (
get_the_ID (),
array (
'exclude_post_ids' => $excludes ,
)
);
}
$response = array (
2022-06-16 14:01:47 +02:00
'version' => self :: VERSION ,
2019-11-15 23:26:29 +01:00
'show_thumbnails' => ( bool ) $options [ 'show_thumbnails' ],
2022-06-16 14:01:47 +02:00
'show_date' => ( bool ) $options [ 'show_date' ],
'show_context' => ( bool ) $options [ 'show_context' ],
'layout' => ( string ) $options [ 'layout' ],
'headline' => ( string ) $options [ 'headline' ],
'items' => array (),
2019-11-15 23:26:29 +01:00
);
2022-06-16 14:01:47 +02:00
if ( count ( $related_posts ) === $options [ 'size' ] ) {
2019-11-15 23:26:29 +01:00
$response [ 'items' ] = $related_posts ;
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
2022-06-16 14:01:47 +02:00
echo wp_json_encode ( $response );
2019-11-15 23:26:29 +01:00
exit ();
}
/**
* Returns a UTF - 8 encoded array of post information for the given post_id
*
2022-06-16 14:01:47 +02:00
* @ param int $post_id - the post ID .
* @ param int $position - position of the post .
* @ param int $origin - The post id that this is related to .
2019-11-15 23:26:29 +01:00
* @ uses get_post , get_permalink , remove_query_arg , get_post_format , apply_filters
* @ return array
*/
public function get_related_post_data_for_post ( $post_id , $position , $origin ) {
2022-06-16 14:01:47 +02:00
$post = get_post ( $post_id );
return array (
2023-09-26 10:24:36 +02:00
'id' => $post -> ID ,
'url' => get_permalink ( $post -> ID ),
'url_meta' => array (
2021-09-07 21:40:48 +02:00
'origin' => $origin ,
'position' => $position ,
),
2023-09-26 10:24:36 +02:00
'title' => $this -> to_utf8 ( $this -> get_title ( $post -> post_title , $post -> post_content , $post -> ID ) ),
'author' => $this -> generate_related_post_display_author ( $post -> ID ),
'date' => get_the_date ( '' , $post -> ID ),
'format' => get_post_format ( $post -> ID ),
'excerpt' => html_entity_decode ( $this -> to_utf8 ( $this -> get_excerpt ( $post -> post_excerpt , $post -> post_content ) ), ENT_QUOTES , 'UTF-8' ),
2019-11-15 23:26:29 +01:00
/**
* Filters the rel attribute for the Related Posts ' links .
*
* @ module related - posts
*
* @ since 3.7 . 0
* @ since 7.9 . 0 - Change Default value to empty .
*
* @ param string $link_rel Link rel attribute for Related Posts ' link . Default is empty .
* @ param int $post -> ID Post ID .
*/
2023-09-26 10:24:36 +02:00
'rel' => apply_filters ( 'jetpack_relatedposts_filter_post_link_rel' , '' , $post -> ID ),
2019-11-15 23:26:29 +01:00
/**
* Filter the context displayed below each Related Post .
*
2023-09-26 10:24:36 +02:00
* This context is used when rendering the legacy 'widget' version of Related Posts .
* It is not used when rendering the block - based version . See 'block_context' below for that .
*
2019-11-15 23:26:29 +01:00
* @ module related - posts
*
* @ since 3.0 . 0
*
2022-06-16 14:01:47 +02:00
* @ param string $this -> to_utf8 ( $this -> generate_related_post_context ( $post -> ID ) ) Context displayed below each related post .
2022-04-02 10:26:41 +02:00
* @ param int $post_id Post ID of the post for which we are retrieving Related Posts .
2019-11-15 23:26:29 +01:00
*/
2023-09-26 10:24:36 +02:00
'context' => apply_filters (
2019-11-15 23:26:29 +01:00
'jetpack_relatedposts_filter_post_context' ,
2022-06-16 14:01:47 +02:00
$this -> to_utf8 ( $this -> generate_related_post_context ( $post -> ID ) ),
2019-11-15 23:26:29 +01:00
$post -> ID
),
2023-09-26 10:24:36 +02:00
// The context used when rendering as a Block. No filtering applied.
'block_context' => $this -> generate_related_post_context_block ( $post -> ID ),
'img' => $this -> generate_related_post_image_params ( $post -> ID ),
2019-11-15 23:26:29 +01:00
/**
* Filter the post css classes added on HTML markup .
*
* @ module related - posts
*
* @ since 3.8 . 0
*
* @ param array array () CSS classes added on post HTML markup .
* @ param string $post_id Post ID .
*/
2023-09-26 10:24:36 +02:00
'classes' => apply_filters (
2019-11-15 23:26:29 +01:00
'jetpack_relatedposts_filter_post_css_classes' ,
array (),
$post -> ID
),
);
}
/**
* Returns either the title or a small excerpt to use as title for post .
*
2021-09-07 21:40:48 +02:00
* @ uses strip_shortcodes , wp_trim_words , __ , apply_filters
*
* @ param string $post_title Post title .
* @ param string $post_content Post content .
* @ param int $post_id Post ID .
*
2019-11-15 23:26:29 +01:00
* @ return string
*/
2021-09-07 21:40:48 +02:00
protected function get_title ( $post_title , $post_content , $post_id ) {
2019-11-15 23:26:29 +01:00
if ( ! empty ( $post_title ) ) {
2021-09-07 21:40:48 +02:00
return wp_strip_all_tags (
/** This filter is documented in core/src/wp-includes/post-template.php */
apply_filters ( 'the_title' , $post_title , $post_id )
);
2019-11-15 23:26:29 +01:00
}
$post_title = wp_trim_words ( wp_strip_all_tags ( strip_shortcodes ( $post_content ) ), 5 , '…' );
if ( ! empty ( $post_title ) ) {
return $post_title ;
}
return __ ( 'Untitled Post' , 'jetpack' );
}
/**
* Returns a plain text post excerpt for title attribute of links .
*
2022-06-16 14:01:47 +02:00
* @ param string $post_excerpt - the post excerpt .
* @ param string $post_content - the post content .
2019-11-15 23:26:29 +01:00
* @ uses strip_shortcodes , wp_strip_all_tags , wp_trim_words
* @ return string
*/
2022-06-16 14:01:47 +02:00
protected function get_excerpt ( $post_excerpt , $post_content ) {
if ( empty ( $post_excerpt ) ) {
2019-11-15 23:26:29 +01:00
$excerpt = $post_content ;
2022-06-16 14:01:47 +02:00
} else {
2019-11-15 23:26:29 +01:00
$excerpt = $post_excerpt ;
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
return wp_trim_words ( wp_strip_all_tags ( strip_shortcodes ( $excerpt ) ), 50 , '…' );
}
/**
* Generates the thumbnail image to be used for the post . Uses the
* image as returned by Jetpack_PostImages :: get_image ()
*
2022-06-16 14:01:47 +02:00
* @ param int $post_id - the post ID .
2019-11-15 23:26:29 +01:00
* @ uses self :: get_options , apply_filters , Jetpack_PostImages :: get_image , Jetpack_PostImages :: fit_image_url
* @ return string
*/
2022-06-16 14:01:47 +02:00
protected function generate_related_post_image_params ( $post_id ) {
2019-11-15 23:26:29 +01:00
$image_params = array (
'alt_text' => '' ,
'src' => '' ,
'width' => 0 ,
'height' => 0 ,
);
/**
* Filter the size of the Related Posts images .
*
* @ module related - posts
*
* @ since 2.8 . 0
*
* @ param array array ( 'width' => 350 , 'height' => 200 ) Size of the images displayed below each Related Post .
*/
$thumbnail_size = apply_filters (
'jetpack_relatedposts_filter_thumbnail_size' ,
2022-06-16 14:01:47 +02:00
array (
'width' => 350 ,
'height' => 200 ,
)
2019-11-15 23:26:29 +01:00
);
2022-06-16 14:01:47 +02:00
if ( ! is_array ( $thumbnail_size ) ) {
2019-11-15 23:26:29 +01:00
$thumbnail_size = array (
2022-06-16 14:01:47 +02:00
'width' => ( int ) $thumbnail_size ,
'height' => ( int ) $thumbnail_size ,
2019-11-15 23:26:29 +01:00
);
}
2022-06-16 14:01:47 +02:00
// Try to get post image.
2019-11-15 23:26:29 +01:00
if ( class_exists ( 'Jetpack_PostImages' ) ) {
$img_url = '' ;
$post_image = Jetpack_PostImages :: get_image (
$post_id ,
$thumbnail_size
);
2022-06-16 14:01:47 +02:00
if ( is_array ( $post_image ) ) {
2019-11-15 23:26:29 +01:00
$img_url = $post_image [ 'src' ];
} elseif ( class_exists ( 'Jetpack_Media_Summary' ) ) {
$media = Jetpack_Media_Summary :: get ( $post_id );
2022-06-16 14:01:47 +02:00
if ( is_array ( $media ) && ! empty ( $media [ 'image' ] ) ) {
2019-11-15 23:26:29 +01:00
$img_url = $media [ 'image' ];
}
}
if ( ! empty ( $img_url ) ) {
if ( ! empty ( $post_image [ 'alt_text' ] ) ) {
$image_params [ 'alt_text' ] = $post_image [ 'alt_text' ];
} else {
$image_params [ 'alt_text' ] = '' ;
}
2023-09-26 10:24:36 +02:00
$thumbnail_width = 0 ;
$thumbnail_height = 0 ;
if ( ! empty ( $thumbnail_size [ 'width' ] ) ) {
$thumbnail_width = $thumbnail_size [ 'width' ];
$image_params [ 'width' ] = $thumbnail_width ;
}
if ( ! empty ( $thumbnail_size [ 'height' ] ) ) {
$thumbnail_height = $thumbnail_size [ 'height' ];
$image_params [ 'height' ] = $thumbnail_height ;
}
$image_params [ 'src' ] = Jetpack_PostImages :: fit_image_url (
2019-11-15 23:26:29 +01:00
$img_url ,
2023-09-26 10:24:36 +02:00
$thumbnail_width ,
$thumbnail_height
);
// Add a srcset to handle zoomed views and high-density screens.
$srcset = Jetpack_PostImages :: generate_cropped_srcset (
$post_image ,
$thumbnail_width ,
$thumbnail_height
2019-11-15 23:26:29 +01:00
);
2023-09-26 10:24:36 +02:00
if ( ! empty ( $srcset ) ) {
$image_params [ 'srcset' ] = $srcset ;
}
2019-11-15 23:26:29 +01:00
}
}
return $image_params ;
}
/**
* Returns the string UTF - 8 encoded
*
2022-06-16 14:01:47 +02:00
* @ param string $text - the text we want to convert .
2019-11-15 23:26:29 +01:00
* @ return string
*/
2022-06-16 14:01:47 +02:00
protected function to_utf8 ( $text ) {
if ( $this -> convert_charset ) {
return iconv ( $this -> blog_charset , 'UTF-8' , $text );
2019-11-15 23:26:29 +01:00
} else {
return $text ;
}
}
/**
* =============================================
* PROTECTED UTILITY FUNCTIONS EXTENDED BY WPCOM
* =============================================
*/
/**
* Workhorse method to return array of related posts matched by Elasticsearch .
*
2022-06-16 14:01:47 +02:00
* @ param int $post_id - the ID of the post .
* @ param int $size - the size of the post .
* @ param array $filters - filters .
2019-11-15 23:26:29 +01:00
* @ 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
* @ return array
*/
2022-06-16 14:01:47 +02:00
protected function get_related_posts ( $post_id , $size , array $filters ) {
$hits = $this -> filter_non_public_posts (
$this -> get_related_post_ids (
2019-11-15 23:26:29 +01:00
$post_id ,
$size ,
$filters
)
);
/**
* Filter the Related Posts matched by Elasticsearch .
*
* @ module related - posts
*
* @ since 2.9 . 0
*
* @ param array $hits Array of Post IDs matched by Elasticsearch .
* @ param string $post_id Post ID of the post for which we are retrieving Related Posts .
*/
$hits = apply_filters ( 'jetpack_relatedposts_filter_hits' , $hits , $post_id );
$related_posts = array ();
foreach ( $hits as $i => $hit ) {
$related_posts [] = $this -> get_related_post_data_for_post ( $hit [ 'id' ], $i , $post_id );
}
return $related_posts ;
}
/**
* Get array of related posts matched by Elasticsearch .
*
2022-06-16 14:01:47 +02:00
* @ param int $post_id - the post ID .
* @ param int $size - the size .
* @ param array $filters - some filters .
2019-11-15 23:26:29 +01:00
* @ uses wp_remote_post , is_wp_error , wp_remote_retrieve_body , get_post_meta , update_post_meta
* @ return array
*/
2022-06-16 14:01:47 +02:00
protected function get_related_post_ids ( $post_id , $size , array $filters ) {
2023-09-26 10:24:36 +02:00
$transient_name = null ;
2022-06-16 14:01:47 +02:00
$now_ts = time ();
2019-11-15 23:26:29 +01:00
$cache_meta_key = '_jetpack_related_posts_cache' ;
$body = array (
'size' => ( int ) $size ,
);
2022-06-16 14:01:47 +02:00
if ( ! empty ( $filters ) ) {
2019-11-15 23:26:29 +01:00
$body [ 'filter' ] = array ( 'and' => $filters );
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
2022-06-16 14:01:47 +02:00
// Build cache key.
$cache_key = md5 ( serialize ( $body ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- this is used for caching.
2019-11-15 23:26:29 +01:00
2022-06-16 14:01:47 +02:00
// Load all cached values.
2019-11-15 23:26:29 +01:00
if ( wp_using_ext_object_cache () ) {
$transient_name = " { $cache_meta_key } _ { $cache_key } _ { $post_id } " ;
2022-06-16 14:01:47 +02:00
$cache = get_transient ( $transient_name );
2019-11-15 23:26:29 +01:00
if ( false !== $cache ) {
return $cache ;
}
} else {
$cache = get_post_meta ( $post_id , $cache_meta_key , true );
2022-06-16 14:01:47 +02:00
if ( empty ( $cache ) ) {
2019-11-15 23:26:29 +01:00
$cache = array ();
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
// Cache is valid! Return cached value.
2022-06-16 14:01:47 +02:00
if ( isset ( $cache [ $cache_key ] ) && is_array ( $cache [ $cache_key ] ) && $cache [ $cache_key ][ 'expires' ] > $now_ts ) {
return $cache [ $cache_key ][ 'payload' ];
2019-11-15 23:26:29 +01:00
}
}
2022-12-15 17:41:53 +01:00
$user_agent = '' ;
if ( isset ( $_SERVER [ 'HTTP_USER_AGENT' ] ) ) {
$user_agent = strtolower ( filter_var ( wp_unslash ( $_SERVER [ 'HTTP_USER_AGENT' ] ) ) );
}
2019-11-15 23:26:29 +01:00
$response = wp_remote_post (
" https://public-api.wordpress.com/rest/v1/sites/ { $this -> get_blog_id () } /posts/ $post_id /related/ " ,
array (
2022-06-16 14:01:47 +02:00
'timeout' => 10 ,
2022-12-15 17:41:53 +01:00
'user-agent' => " jetpack_related_posts, $user_agent " ,
2022-06-16 14:01:47 +02:00
'sslverify' => true ,
'body' => $body ,
2019-11-15 23:26:29 +01:00
)
);
2022-12-15 17:41:53 +01:00
// Oh no... return nothing don't cache errors. Also, don't cache HTTP 409 conflict responses.
if ( is_wp_error ( $response ) || WP_Http :: CONFLICT === wp_remote_retrieve_response_code ( $response ) ) {
2022-06-16 14:01:47 +02:00
if ( isset ( $cache [ $cache_key ] ) && is_array ( $cache [ $cache_key ] ) ) {
return $cache [ $cache_key ][ 'payload' ]; // return stale.
} else {
2019-11-15 23:26:29 +01:00
return array ();
2022-06-16 14:01:47 +02:00
}
2019-11-15 23:26:29 +01:00
}
2022-06-16 14:01:47 +02:00
$results = json_decode ( wp_remote_retrieve_body ( $response ), true );
2019-11-15 23:26:29 +01:00
$related_posts = array ();
2022-06-16 14:01:47 +02:00
if ( is_array ( $results ) && ! empty ( $results [ 'hits' ] ) ) {
foreach ( $results [ 'hits' ] as $hit ) {
2019-11-15 23:26:29 +01:00
$related_posts [] = array (
'id' => $hit [ 'fields' ][ 'post_id' ],
);
}
}
// An empty array might indicate no related posts or that posts
// are not yet synced to WordPress.com, so we cache for only 1
2022-06-16 14:01:47 +02:00
// minute in this case.
2019-11-15 23:26:29 +01:00
if ( empty ( $related_posts ) ) {
$cache_ttl = 60 ;
} else {
$cache_ttl = 12 * HOUR_IN_SECONDS ;
}
2022-06-16 14:01:47 +02:00
// Update cache.
2019-11-15 23:26:29 +01:00
if ( wp_using_ext_object_cache () ) {
set_transient ( $transient_name , $related_posts , $cache_ttl );
} else {
2022-06-16 14:01:47 +02:00
// Copy all valid cache values.
2019-11-15 23:26:29 +01:00
$new_cache = array ();
foreach ( $cache as $k => $v ) {
2022-06-16 14:01:47 +02:00
if ( is_array ( $v ) && $v [ 'expires' ] > $now_ts ) {
2019-11-15 23:26:29 +01:00
$new_cache [ $k ] = $v ;
}
}
2022-06-16 14:01:47 +02:00
// Set new cache value.
$cache_expires = $cache_ttl + $now_ts ;
2019-11-15 23:26:29 +01:00
$new_cache [ $cache_key ] = array (
'expires' => $cache_expires ,
'payload' => $related_posts ,
);
update_post_meta ( $post_id , $cache_meta_key , $new_cache );
}
return $related_posts ;
}
/**
* Filter out any hits that are not public anymore .
*
2022-06-16 14:01:47 +02:00
* @ param array $related_posts - the related posts .
2019-11-15 23:26:29 +01:00
* @ uses get_post_stati , get_post_status
* @ return array
*/
2022-06-16 14:01:47 +02:00
protected function filter_non_public_posts ( array $related_posts ) {
2019-11-15 23:26:29 +01:00
$public_stati = get_post_stati ( array ( 'public' => true ) );
$filtered = array ();
foreach ( $related_posts as $hit ) {
2022-06-16 14:01:47 +02:00
if ( in_array ( get_post_status ( $hit [ 'id' ] ), $public_stati , true ) ) {
2019-11-15 23:26:29 +01:00
$filtered [] = $hit ;
}
}
return $filtered ;
}
2023-09-26 10:24:36 +02:00
/**
* Generates the author byline for the related post .
*
* @ param int $post_id - the post ID .
* @ uses get_post_field , get_the_author_meta
* @ return string
*/
protected function generate_related_post_display_author ( $post_id ) {
$post_author = get_post_field ( 'post_author' , $post_id );
$author_display_name = get_the_author_meta ( 'display_name' , $post_author );
if ( ! empty ( $author_display_name ) ) {
return $author_display_name ;
}
return '' ;
}
/**
* Generates a context for the related content ( second line in related post output ) .
* Order of importance :
* - First category ( Not 'Uncategorized' )
* - First post tag
* - Number of comments
*
* @ param int $post_id - the post ID .
* @ uses get_the_category , get_the_terms , get_comments_number , number_format_i18n , __ , _n
* @ return string
*/
protected function generate_related_post_context_block ( $post_id ) {
$categories = get_the_category ( $post_id );
if ( is_array ( $categories ) ) {
foreach ( $categories as $category ) {
$cat_link = get_category_link ( $category );
if ( 'uncategorized' !== $category -> slug && '' !== trim ( $category -> name ) ) {
return array (
'text' => trim ( $category -> name ),
'link' => $cat_link ,
);
}
}
}
$tags = get_the_terms ( $post_id , 'post_tag' );
if ( is_array ( $tags ) ) {
foreach ( $tags as $tag ) {
$tag_link = get_tag_link ( $tag );
if ( '' !== trim ( $tag -> name ) ) {
return array (
'text' => trim ( $tag -> name ),
'link' => $tag_link ,
);
}
}
}
$comment_count = get_comments_number ( $post_id );
if ( $comment_count > 0 ) {
$comments_string = sprintf (
// Translators: amount of comments.
_n ( 'With %s comment' , 'With %s comments' , $comment_count , 'jetpack' ),
number_format_i18n ( $comment_count )
);
$comments_link = get_comments_link ( $post_id );
return array (
'text' => $comments_string ,
'link' => $comments_link ,
);
}
$fallback_string = __ ( 'Similar post' , 'jetpack' );
return array (
'text' => $fallback_string ,
'link' => '' ,
);
}
2019-11-15 23:26:29 +01:00
/**
* Generates a context for the related content ( second line in related post output ) .
* Order of importance :
* - First category ( Not 'Uncategorized' )
* - First post tag
* - Number of comments
*
2022-06-16 14:01:47 +02:00
* @ param int $post_id - the post ID .
2019-11-15 23:26:29 +01:00
* @ uses get_the_category , get_the_terms , get_comments_number , number_format_i18n , __ , _n
* @ return string
*/
2022-06-16 14:01:47 +02:00
protected function generate_related_post_context ( $post_id ) {
2019-11-15 23:26:29 +01:00
$categories = get_the_category ( $post_id );
if ( is_array ( $categories ) ) {
foreach ( $categories as $category ) {
2022-06-16 14:01:47 +02:00
if ( 'uncategorized' !== $category -> slug && '' !== trim ( $category -> name ) ) {
2019-11-15 23:26:29 +01:00
$post_cat_context = sprintf (
2022-06-16 14:01:47 +02:00
// Translators: The category or tag name.
2019-11-15 23:26:29 +01:00
esc_html_x ( 'In "%s"' , 'in {category/tag name}' , 'jetpack' ),
$category -> name
);
/**
* Filter the " In Category " line displayed in the post context below each Related Post .
*
* @ module related - posts
*
* @ since 3.2 . 0
*
* @ param string $post_cat_context " In Category " line displayed in the post context below each Related Post .
* @ param array $category Array containing information about the category .
*/
return apply_filters ( 'jetpack_relatedposts_post_category_context' , $post_cat_context , $category );
}
}
}
$tags = get_the_terms ( $post_id , 'post_tag' );
if ( is_array ( $tags ) ) {
foreach ( $tags as $tag ) {
2022-06-16 14:01:47 +02:00
if ( '' !== trim ( $tag -> name ) ) {
2019-11-15 23:26:29 +01:00
$post_tag_context = sprintf (
2022-06-16 14:01:47 +02:00
// Translators: the category or tag name.
2019-11-15 23:26:29 +01:00
_x ( 'In "%s"' , 'in {category/tag name}' , 'jetpack' ),
$tag -> name
);
/**
* Filter the " In Tag " line displayed in the post context below each Related Post .
*
* @ module related - posts
*
* @ since 3.2 . 0
*
* @ param string $post_tag_context " In Tag " line displayed in the post context below each Related Post .
* @ param array $tag Array containing information about the tag .
*/
return apply_filters ( 'jetpack_relatedposts_post_tag_context' , $post_tag_context , $tag );
}
}
}
$comment_count = get_comments_number ( $post_id );
if ( $comment_count > 0 ) {
return sprintf (
2022-06-16 14:01:47 +02:00
// Translators: amount of comments.
_n ( 'With %s comment' , 'With %s comments' , $comment_count , 'jetpack' ),
2019-11-15 23:26:29 +01:00
number_format_i18n ( $comment_count )
);
}
return __ ( 'Similar post' , 'jetpack' );
}
/**
* Logs clicks for clickthrough analysis and related result tuning .
*
2022-06-16 14:01:47 +02:00
* @ param int $post_id - the post ID .
* @ param int $to_post_id - the to post ID .
* @ param int $link_position - the link position .
2019-11-15 23:26:29 +01:00
*/
2022-06-16 14:01:47 +02:00
protected function log_click ( $post_id , $to_post_id , $link_position ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
2019-11-15 23:26:29 +01:00
}
/**
* Determines if the current post is able to use related posts .
*
* @ uses self :: get_options , is_admin , is_single , apply_filters
* @ return bool
*/
2022-06-16 14:01:47 +02:00
protected function enabled_for_request () {
2019-11-15 23:26:29 +01:00
$enabled = is_single ()
&& ! is_attachment ()
&& ! is_admin ()
2020-06-23 13:49:54 +02:00
&& ! is_embed ()
2022-06-16 14:01:47 +02:00
&& ( ! $this -> allow_feature_toggle () || $this -> get_option ( 'enabled' ) );
2019-11-15 23:26:29 +01:00
/**
* Filter the Enabled value to allow related posts to be shown on pages as well .
*
* @ module related - posts
*
* @ since 3.3 . 0
*
* @ param bool $enabled Should Related Posts be enabled on the current page .
*/
return apply_filters ( 'jetpack_relatedposts_filter_enabled_for_request' , $enabled );
}
/**
2021-07-23 11:58:50 +02:00
* Adds filters .
2019-11-15 23:26:29 +01:00
*
2022-06-16 14:01:47 +02:00
* @ uses self :: enqueue_assets , self :: setup_shortcode , add_filter
2019-11-15 23:26:29 +01:00
*/
2022-06-16 14:01:47 +02:00
protected function action_frontend_init_page () {
$this -> enqueue_assets ( true , true );
$this -> setup_shortcode ();
2019-11-15 23:26:29 +01:00
add_filter ( 'the_content' , array ( $this , 'filter_add_target_to_dom' ), 40 );
}
2021-09-07 21:40:48 +02:00
/**
* Determines if the scripts need be enqueued .
*
* @ return bool
*/
protected function requires_scripts () {
return (
! ( class_exists ( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support :: is_amp_request () ) &&
! has_block ( 'jetpack/related-posts' ) &&
! Blocks :: is_fse_theme ()
);
}
2019-11-15 23:26:29 +01:00
/**
* Enqueues assets needed to do async loading of related posts .
*
2022-06-16 14:01:47 +02:00
* @ param string $script - the script we ' re enqueing .
* @ param string $style - the style we ' re enqueing .
*
2019-11-15 23:26:29 +01:00
* @ uses wp_enqueue_script , wp_enqueue_style , plugins_url
*/
2022-06-16 14:01:47 +02:00
protected function enqueue_assets ( $script , $style ) {
2021-04-27 08:32:47 +02:00
$dependencies = is_customize_preview () ? array ( 'customize-base' ) : array ();
2021-09-07 21:40:48 +02:00
// Do not enqueue scripts unless they are required.
if ( $script && $this -> requires_scripts () ) {
2019-11-15 23:26:29 +01:00
wp_enqueue_script (
'jetpack_related-posts' ,
Assets :: get_file_url_for_environment (
'_inc/build/related-posts/related-posts.min.js' ,
'modules/related-posts/related-posts.js'
),
$dependencies ,
2022-06-16 14:01:47 +02:00
self :: VERSION ,
false
2019-11-15 23:26:29 +01:00
);
$related_posts_js_options = array (
/**
* Filter each Related Post Heading structure .
*
* @ since 4.0 . 0
*
* @ param string $str Related Post Heading structure . Default to h4 .
*/
'post_heading' => apply_filters ( 'jetpack_relatedposts_filter_post_heading' , esc_attr ( 'h4' ) ),
);
wp_localize_script ( 'jetpack_related-posts' , 'related_posts_js_options' , $related_posts_js_options );
}
2022-06-16 14:01:47 +02:00
if ( $style ) {
2019-11-15 23:26:29 +01:00
wp_enqueue_style ( 'jetpack_related-posts' , plugins_url ( 'related-posts.css' , __FILE__ ), array (), self :: VERSION );
wp_style_add_data ( 'jetpack_related-posts' , 'rtl' , 'replace' );
add_action ( 'amp_post_template_css' , array ( $this , 'render_amp_reader_mode_css' ) );
}
}
2022-06-16 14:01:47 +02:00
/**
* Render AMP ' s reader mode CSS .
*/
2019-11-15 23:26:29 +01:00
public function render_amp_reader_mode_css () {
2023-12-07 09:44:11 +01:00
echo file_get_contents ( __DIR__ . '/related-posts.css' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped, WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- this is loading a CSS file.
2019-11-15 23:26:29 +01:00
}
/**
* Sets up the shortcode processing .
*
* @ uses add_filter , add_shortcode
*/
2022-06-16 14:01:47 +02:00
protected function setup_shortcode () {
2019-11-15 23:26:29 +01:00
add_filter ( 'the_content' , array ( $this , 'test_for_shortcode' ), 0 );
add_shortcode ( self :: SHORTCODE , array ( $this , 'get_client_rendered_html' ) );
}
2022-06-16 14:01:47 +02:00
/**
* Return status of related posts toggle .
*/
protected function allow_feature_toggle () {
if ( null === $this -> allow_feature_toggle ) {
2019-11-15 23:26:29 +01:00
/**
* Filter the display of the Related Posts toggle in Settings > Reading .
*
* @ module related - posts
*
* @ since 2.8 . 0
*
2022-06-16 14:01:47 +02:00
* @ param bool $allow_feature_toggle Display a feature toggle . Default to false .
2019-11-15 23:26:29 +01:00
*/
2022-06-16 14:01:47 +02:00
$this -> allow_feature_toggle = apply_filters ( 'jetpack_relatedposts_filter_allow_feature_toggle' , false );
2019-11-15 23:26:29 +01:00
}
2022-06-16 14:01:47 +02:00
return $this -> allow_feature_toggle ;
2019-11-15 23:26:29 +01:00
}
/**
* ===================================================
* FUNCTIONS EXPOSING RELATED POSTS IN THE WP REST API
* ===================================================
*/
/**
* Add Related Posts to the REST API Post response .
*
* @ since 4.4 . 0
*
* @ action rest_api_init
* @ uses register_rest_field , self :: rest_get_related_posts
*/
public function rest_register_related_posts () {
/** This filter is already documented in class.json-api-endpoints.php */
$post_types = apply_filters ( 'rest_api_allowed_post_types' , array ( 'post' , 'page' , 'revision' ) );
foreach ( $post_types as $post_type ) {
register_rest_field (
$post_type ,
'jetpack-related-posts' ,
array (
'get_callback' => array ( $this , 'rest_get_related_posts' ),
'update_callback' => null ,
'schema' => null ,
)
);
}
}
/**
* Build an array of Related Posts .
* By default returns cached results that are stored for up to 12 hours .
*
* @ since 4.4 . 0
*
* @ param array $object Details of current post .
*
* @ uses self :: get_for_post_id
*
* @ return array
*/
2022-06-16 14:01:47 +02:00
public function rest_get_related_posts ( $object ) {
2024-04-17 11:32:24 +02:00
if ( ! isset ( $object [ 'id' ] ) ) {
return array ();
}
2024-02-16 11:03:01 +01:00
// If the Related Posts option is turned off, don't get the related posts.
$options = \Jetpack_Options :: get_option ( 'relatedposts' , array () );
if ( empty ( $options [ 'enabled' ] ) || ! $options [ 'enabled' ] ) {
return array ();
}
// If the current post don't contain a Related Posts block, don't get the related posts.
if ( ! has_block ( 'jetpack/related-posts' ) ) {
return array ();
}
2019-11-15 23:26:29 +01:00
return $this -> get_for_post_id ( $object [ 'id' ], array ( 'size' => 6 ) );
}
}
2022-06-16 14:01:47 +02:00
/**
* The raw related posts class can be used by other plugins or themes
* to get related content . This class wraps the existing RelatedPosts
* logic thus we never want to add anything to the DOM or do anything
* for event hooks . We will also not present any settings for this
* class and keep it enabled as calls to this class are done
* programmatically .
*/
class Jetpack_RelatedPosts_Raw extends Jetpack_RelatedPosts { //phpcs:ignore Generic.Classes.OpeningBraceSameLine.ContentAfterBrace, Generic.Files.OneObjectStructurePerFile.MultipleFound
/**
* The query name we want to look up .
*
* @ var string
*/
protected $query_name ;
2019-11-15 23:26:29 +01:00
/**
* Allows callers of this class to tag each query with a unique name for tracking purposes .
*
2022-06-16 14:01:47 +02:00
* @ param string $name - the name of the query .
2019-11-15 23:26:29 +01:00
* @ return Jetpack_RelatedPosts_Raw
*/
public function set_query_name ( $name ) {
2022-06-16 14:01:47 +02:00
$this -> query_name = ( string ) $name ;
2019-11-15 23:26:29 +01:00
return $this ;
}
/**
2022-06-16 14:01:47 +02:00
* Initialize admin .
2019-11-15 23:26:29 +01:00
*/
public function action_admin_init () {}
2022-06-16 14:01:47 +02:00
/**
* Initialize front end .
*/
2019-11-15 23:26:29 +01:00
public function action_frontend_init () {}
2022-06-16 14:01:47 +02:00
/**
* Get options .
*/
2019-11-15 23:26:29 +01:00
public function get_options () {
return array (
'enabled' => true ,
);
}
/**
* Workhorse method to return array of related posts ids matched by Elasticsearch .
*
2022-06-16 14:01:47 +02:00
* @ param int $post_id - the post ID .
* @ param int $size - size of the post .
* @ param array $filters - filters we ' re using .
2019-11-15 23:26:29 +01:00
* @ uses wp_remote_post , is_wp_error , wp_remote_retrieve_body
* @ return array
*/
2022-06-16 14:01:47 +02:00
protected function get_related_posts ( $post_id , $size , array $filters ) {
$hits = $this -> filter_non_public_posts (
$this -> get_related_post_ids (
2019-11-15 23:26:29 +01:00
$post_id ,
$size ,
$filters
)
);
/** This filter is already documented in modules/related-posts/related-posts.php */
$hits = apply_filters ( 'jetpack_relatedposts_filter_hits' , $hits , $post_id );
return $hits ;
}
}