PluginProbe
ElasticPress / 4.3.0
ElasticPress v4.3.0
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / includes / classes / Feature / RelatedPosts / RelatedPosts.php

RelatedPosts.php in ElasticPress 4.3.0, at includes/classes/Feature/RelatedPosts/RelatedPosts.php

339 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ElasticPress related posts feature
4 *
5 * @since 2.1
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Feature\RelatedPosts;
10
11 use ElasticPress\Feature as Feature;
12 use ElasticPress\Elasticsearch as Elasticsearch;
13 use ElasticPress\Post\Post as Post;
14 use \WP_Query as WP_Query;
15
16 /**
17 * Related posts feature class
18 */
19 class RelatedPosts extends Feature {
20 /**
21 * Initialize feature setting it's config
22 *
23 * @since 3.0
24 */
25 public function __construct() {
26 $this->slug = 'related_posts';
27
28 $this->title = esc_html__( 'Related Posts', 'elasticpress' );
29
30 $this->summary = __( 'ElasticPress understands data in real time, so it can instantly deliver engaging and precise related content with no impact on site performance.', 'elasticpress' );
31
32 $this->docs_url = __( 'https://elasticpress.zendesk.com/hc/en-us/articles/360050447492-Configuring-ElasticPress-via-the-Plugin-Dashboard#related-posts', 'elasticpress' );
33
34 $this->requires_install_reindex = false;
35
36 parent::__construct();
37 }
38
39 /**
40 * Format args for related posts
41 *
42 * @param array $formatted_args Formatted ES args
43 * @param array $args WP_Query args
44 * @return array
45 */
46 public function formatted_args( $formatted_args, $args ) {
47 if ( ! empty( $args['more_like'] ) ) {
48 // lets compare ES version to see if new MLT structure applies
49 $new_mlt = version_compare( Elasticsearch::factory()->get_elasticsearch_version(), 6.0, '>=' );
50
51 if ( $new_mlt && is_array( $args['more_like'] ) ) {
52 foreach ( $args['more_like'] as $id ) {
53 $ids[] = array( '_id' => $id );
54 }
55 } elseif ( $new_mlt && ! is_array( $args['more_like'] ) ) {
56 $ids = array( '_id' => $args['more_like'] );
57 } else {
58 $ids = is_array( $args['more_like'] ) ? $args['more_like'] : array( $args['more_like'] );
59 }
60
61 $mlt_key = ( $new_mlt ) ? 'like' : 'ids';
62
63 $formatted_args['query'] = array(
64 'more_like_this' => array(
65 $mlt_key => $ids,
66 /**
67 * Filter fields used to determine related posts
68 *
69 * @hook ep_related_posts_fields
70 * @param {array} $fields Related post fields
71 * @return {array} New fields
72 */
73 'fields' => apply_filters(
74 'ep_related_posts_fields',
75 array(
76 'post_title',
77 'post_content',
78 'terms.post_tag.name',
79 )
80 ),
81 /**
82 * Filter related posts minimum term frequency
83 *
84 * @hook ep_related_posts_min_term_freq
85 * @param {int} $minimum Minimum term frequency
86 * @return {array} New value
87 */
88 'min_term_freq' => apply_filters( 'ep_related_posts_min_term_freq', 1 ),
89 /**
90 * Filter related posts maximum query terms
91 *
92 * @hook ep_related_posts_max_query_terms
93 * @param {int} $maximum Maximum query terms
94 * @return {array} New value
95 */
96 'max_query_terms' => apply_filters( 'ep_related_posts_max_query_terms', 12 ),
97 /**
98 * Filter related posts minimum document frequency
99 *
100 * @hook ep_related_posts_min_doc_freq
101 * @param {int} $minimum Minimum document frequency
102 * @return {array} New value
103 */
104 'min_doc_freq' => apply_filters( 'ep_related_posts_min_doc_freq', 1 ),
105 ),
106 );
107 }
108
109 return $formatted_args;
110 }
111
112 /**
113 * Search Elasticsearch for related content
114 *
115 * @param int $post_id Post ID
116 * @param int $return Return code
117 * @since 4.1.0
118 * @return WP_Query
119 */
120 public function get_related_query( $post_id, $return = 5 ) {
121 $args = array(
122 'more_like' => $post_id,
123 'posts_per_page' => $return,
124 'ep_integrate' => true,
125 'ignore_sticky_posts' => true,
126 );
127
128 /**
129 * Filter WP Query related post arguments
130 *
131 * @hook ep_find_related_args
132 * @param {array} $args WP Query arguments
133 * @since 2.1
134 * @return {array} New arguments
135 */
136 return new WP_Query( apply_filters( 'ep_find_related_args', $args ) );
137 }
138
139 /**
140 * Search Elasticsearch for related content
141 *
142 * @param int $post_id Post ID
143 * @param int $return Return code
144 *
145 * @since 2.1
146 * @uses get_related_query
147 *
148 * @return array|bool
149 */
150 public function find_related( $post_id, $return = 5 ) {
151 $query = $this->get_related_query( $post_id, $return );
152
153 if ( ! $query->have_posts() ) {
154 return false;
155 }
156 return $query->posts;
157 }
158
159 /**
160 * Setup all feature filters
161 *
162 * @since 2.1
163 */
164 public function setup() {
165 add_action( 'widgets_init', [ $this, 'register_widget' ] );
166 add_filter( 'widget_types_to_hide_from_legacy_widget_block', [ $this, 'hide_legacy_widget' ] );
167 add_filter( 'ep_formatted_args', [ $this, 'formatted_args' ], 10, 2 );
168 add_action( 'init', [ $this, 'register_block' ] );
169 add_action( 'rest_api_init', [ $this, 'setup_endpoint' ] );
170 }
171
172 /**
173 * Setup REST endpoints
174 *
175 * @since 3.2
176 */
177 public function setup_endpoint() {
178 register_rest_route(
179 'wp/v2',
180 '/posts/(?P<id>[0-9]+)/related',
181 [
182 'methods' => 'GET',
183 'callback' => [ $this, 'output_endpoint' ],
184 'permission_callback' => '__return_true',
185 'args' => [
186 'id' => [
187 'description' => 'Post ID.',
188 'type' => 'numeric',
189 ],
190 'number' => [
191 'description' => 'Number of posts',
192 'type' => 'numeric',
193 'default' => 5,
194 ],
195 ],
196 ]
197 );
198 }
199
200 /**
201 * Output related posts endpoint
202 *
203 * @param \WP_REST_Request $request REST request
204 * @since 3.2
205 * @return \WP_REST_Response
206 */
207 public function output_endpoint( $request ) {
208 $id = $request['id'];
209
210 $posts = $this->find_related( $id, (int) $request['number'] );
211 $prepared_posts = [];
212
213 if ( ! empty( $posts ) ) {
214 foreach ( $posts as $post ) {
215 $prepared_post = [];
216
217 $prepared_post['id'] = $post->ID;
218 $prepared_post['link'] = get_permalink( $post->ID );
219 $prepared_post['status'] = $post->post_status;
220 $prepared_post['title'] = [
221 'raw' => $post->post_title,
222 'rendered' => get_the_title( $post->ID ),
223 ];
224 $prepared_post['author'] = (int) $post->post_author;
225 $prepared_post['parent'] = (int) $post->post_parent;
226 $prepared_post['menu_order'] = (int) $post->menu_order;
227 $prepared_post['content'] = [
228 'rendered' => post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ),
229 ];
230 $prepared_post['date'] = $post->post_date;
231 $prepared_post['date_gmt'] = $post->post_date_gmt;
232 $prepared_post['modified'] = $post->post_modified;
233 $prepared_post['modified_gmt'] = $post->post_modified_gmt;
234
235 $prepared_posts[] = $prepared_post;
236 }
237 }
238
239 $response = new \WP_REST_Response();
240 $response->set_data( $prepared_posts );
241
242 return $response;
243 }
244
245 /**
246 * Register gutenberg block
247 *
248 * @since 3.2
249 */
250 public function register_block() {
251 register_block_type_from_metadata(
252 EP_PATH . 'assets/js/blocks/related-posts',
253 [
254 'render_callback' => [ $this, 'render_block' ],
255 ]
256 );
257 }
258
259 /**
260 * Render Gutenberg block
261 *
262 * @param array $attributes Block attributes
263 * @since 3.2
264 * @return string
265 */
266 public function render_block( $attributes ) {
267 $posts = $this->find_related( get_the_ID(), $attributes['number'] );
268
269 if ( empty( $posts ) ) {
270 return '';
271 }
272
273 $class = 'wp-block-elasticpress-related-posts';
274
275 if ( ! empty( $attributes['align'] ) ) {
276 $class .= ' align' . $attributes['align'];
277 }
278
279 if ( ! empty( $attributes['className'] ) ) {
280 $class .= ' ' . $attributes['className'];
281 }
282
283 ob_start();
284 ?>
285 <section class="<?php echo esc_attr( $class ); ?>">
286 <ul>
287 <?php foreach ( $posts as $related_post ) : ?>
288 <li>
289 <a href="<?php echo esc_url( get_permalink( $related_post->ID ) ); ?>">
290 <?php echo wp_kses( get_the_title( $related_post->ID ), 'ep-html' ); ?>
291 </a>
292 </li>
293 <?php endforeach; ?>
294 </ul>
295 </section>
296 <?php
297
298 $block_content = ob_get_clean();
299
300 return $block_content;
301 }
302
303 /**
304 * Register related posts widget
305 *
306 * @since 2.2
307 */
308 public function register_widget() {
309 register_widget( __NAMESPACE__ . '\Widget' );
310 }
311
312 /**
313 * Hide the legacy widget.
314 *
315 * Hides the legacy widget in favor of the Block when the block editor
316 * is in use and the legacy widget has not been used.
317 *
318 * @since 4.3
319 * @param array $widgets An array of excluded widget-type IDs.
320 * @return array array of excluded widget-type IDs to hide.
321 */
322 public function hide_legacy_widget( $widgets ) {
323 $widgets[] = 'ep-related-posts';
324
325 return $widgets;
326 }
327
328 /**
329 * Output feature box long
330 *
331 * @since 2.1
332 */
333 public function output_feature_box_long() {
334 ?>
335 <p><?php echo wp_kses_post( __( 'Output related content using our Widget or directly in your theme using our <a href="https://10up.github.io/ElasticPress/tutorial-features.html#related-posts">API functions.</a>', 'elasticpress' ) ); ?></p>
336 <?php
337 }
338 }
339