PluginProbe
ElasticPress / 4.4.0
ElasticPress v4.4.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.4.0, at includes/classes/Feature/RelatedPosts/RelatedPosts.php

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