PluginProbe
ElasticPress / 4.2.0
ElasticPress v4.2.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.2.0, at includes/classes/Feature/RelatedPosts/RelatedPosts.php

363 lines 9.2 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( 'ep_formatted_args', [ $this, 'formatted_args' ], 10, 2 );
167 add_action( 'init', [ $this, 'register_block' ] );
168 add_action( 'rest_api_init', [ $this, 'setup_endpoint' ] );
169 }
170
171 /**
172 * Setup REST endpoints
173 *
174 * @since 3.2
175 */
176 public function setup_endpoint() {
177 register_rest_route(
178 'wp/v2',
179 '/posts/(?P<id>[0-9]+)/related',
180 [
181 'methods' => 'GET',
182 'callback' => [ $this, 'output_endpoint' ],
183 'permission_callback' => '__return_true',
184 'args' => [
185 'id' => [
186 'description' => 'Post ID.',
187 'type' => 'numeric',
188 ],
189 'number' => [
190 'description' => 'Number of posts',
191 'type' => 'numeric',
192 'default' => 5,
193 ],
194 ],
195 ]
196 );
197 }
198
199 /**
200 * Output related posts endpoint
201 *
202 * @param \WP_REST_Request $request REST request
203 * @since 3.2
204 * @return \WP_REST_Response
205 */
206 public function output_endpoint( $request ) {
207 $id = $request['id'];
208
209 $posts = $this->find_related( $id, (int) $request['number'] );
210 $prepared_posts = [];
211
212 if ( ! empty( $posts ) ) {
213 foreach ( $posts as $post ) {
214 $prepared_post = [];
215
216 $prepared_post['id'] = $post->ID;
217 $prepared_post['link'] = get_permalink( $post->ID );
218 $prepared_post['status'] = $post->post_status;
219 $prepared_post['title'] = [
220 'raw' => $post->post_title,
221 'rendered' => get_the_title( $post->ID ),
222 ];
223 $prepared_post['author'] = (int) $post->post_author;
224 $prepared_post['parent'] = (int) $post->post_parent;
225 $prepared_post['menu_order'] = (int) $post->menu_order;
226 $prepared_post['content'] = [
227 'rendered' => post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ),
228 ];
229 $prepared_post['date'] = $post->post_date;
230 $prepared_post['date_gmt'] = $post->post_date_gmt;
231 $prepared_post['modified'] = $post->post_modified;
232 $prepared_post['modified_gmt'] = $post->post_modified_gmt;
233
234 $prepared_posts[] = $prepared_post;
235 }
236 }
237
238 $response = new \WP_REST_Response();
239 $response->set_data( $prepared_posts );
240
241 return $response;
242 }
243
244 /**
245 * Register gutenberg block
246 *
247 * @since 3.2
248 */
249 public function register_block() {
250 // Must be WP 5.0+
251 if ( ! function_exists( 'register_block_type' ) ) {
252 return;
253 }
254
255 wp_register_script(
256 'elasticpress-related-posts-block',
257 EP_URL . 'dist/js/related-posts-block-script.min.js',
258 [
259 'wp-blocks',
260 'wp-element',
261 'wp-editor',
262 'wp-api-fetch',
263 ],
264 EP_VERSION,
265 true
266 );
267
268 // The wp-edit-blocks style dependency is not needed on the front end of the site.
269 $style_dependencies = is_admin() ? [ 'wp-edit-blocks' ] : [];
270
271 wp_register_style(
272 'elasticpress-related-posts-block',
273 EP_URL . 'dist/css/related-posts-block-styles.min.css',
274 $style_dependencies,
275 EP_VERSION
276 );
277
278 register_block_type(
279 'elasticpress/related-posts',
280 [
281 'attributes' => [
282 'number' => [
283 'type' => 'number',
284 'default' => 5,
285 ],
286 'align' => [
287 'type' => 'string',
288 'enum' => [ 'left', 'center', 'right', 'wide', 'full' ],
289 ],
290 ],
291 'editor_script' => 'elasticpress-related-posts-block',
292 'editor_style' => 'elasticpress-related-posts-block',
293 'style' => 'elasticpress-related-posts-block',
294 'render_callback' => [ $this, 'render_block' ],
295 ]
296 );
297 }
298
299 /**
300 * Render Gutenberg block
301 *
302 * @param array $attributes Block attributes
303 * @since 3.2
304 * @return string
305 */
306 public function render_block( $attributes ) {
307 $posts = $this->find_related( get_the_ID(), $attributes['number'] );
308
309 if ( empty( $posts ) ) {
310 return '';
311 }
312
313 $class = 'wp-block-elasticpress-related-posts';
314
315 if ( ! empty( $attributes['align'] ) ) {
316 $class .= ' align' . $attributes['align'];
317 }
318
319 if ( ! empty( $attributes['className'] ) ) {
320 $class .= ' ' . $attributes['className'];
321 }
322
323 ob_start();
324 ?>
325 <section class="<?php echo esc_attr( $class ); ?>">
326 <ul>
327 <?php foreach ( $posts as $related_post ) : ?>
328 <li>
329 <a href="<?php echo esc_url( get_permalink( $related_post->ID ) ); ?>">
330 <?php echo wp_kses( get_the_title( $related_post->ID ), 'ep-html' ); ?>
331 </a>
332 </li>
333 <?php endforeach; ?>
334 </ul>
335 </section>
336 <?php
337
338 $block_content = ob_get_clean();
339
340 return $block_content;
341 }
342
343 /**
344 * Register related posts widget
345 *
346 * @since 2.2
347 */
348 public function register_widget() {
349 register_widget( __NAMESPACE__ . '\Widget' );
350 }
351
352 /**
353 * Output feature box long
354 *
355 * @since 2.1
356 */
357 public function output_feature_box_long() {
358 ?>
359 <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>
360 <?php
361 }
362 }
363