PluginProbe
ElasticPress / 4.2.2
ElasticPress v4.2.2
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 / ProtectedContent / ProtectedContent.php

ProtectedContent.php in ElasticPress 4.2.2, at includes/classes/Feature/ProtectedContent/ProtectedContent.php

425 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ElasticPress Protected Content feature
4 *
5 * @since 2.2
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Feature\ProtectedContent;
10
11 use ElasticPress\Utils as Utils;
12 use ElasticPress\Feature as Feature;
13 use ElasticPress\Features as Features;
14 use ElasticPress\FeatureRequirementsStatus as FeatureRequirementsStatus;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit; // Exit if accessed directly.
18 }
19
20 /**
21 * Protected content feature
22 */
23 class ProtectedContent extends Feature {
24
25 /**
26 * Initialize feature setting its config
27 *
28 * @since 3.0
29 */
30 public function __construct() {
31 $this->slug = 'protected_content';
32
33 $this->title = esc_html__( 'Protected Content', 'elasticpress' );
34
35 $this->summary = __( 'Optionally index all of your content, including private and unpublished content, to speed up searches and queries in places like the administrative dashboard.', 'elasticpress' );
36
37 $this->docs_url = __( 'https://elasticpress.zendesk.com/hc/en-us/articles/360050447492-Configuring-ElasticPress-via-the-Plugin-Dashboard#protected-content', 'elasticpress' );
38
39 $this->requires_install_reindex = true;
40
41 $this->available_during_installation = true;
42
43 parent::__construct();
44 }
45
46 /**
47 * Setup all feature filters
48 *
49 * @since 2.1
50 */
51 public function setup() {
52 add_filter( 'ep_indexable_post_status', [ $this, 'get_statuses' ] );
53 add_filter( 'ep_indexable_post_types', [ $this, 'post_types' ], 10, 1 );
54 add_filter( 'ep_post_formatted_args', [ $this, 'exclude_protected_posts' ], 10, 2 );
55 add_filter( 'ep_index_posts_args', [ $this, 'query_password_protected_posts' ] );
56 add_filter( 'ep_post_sync_args', [ $this, 'include_post_password' ], 10, 2 );
57 add_filter( 'ep_post_sync_args', [ $this, 'remove_fields_from_password_protected' ], 11, 2 );
58 add_filter( 'ep_search_post_return_args', [ $this, 'return_post_password' ] );
59
60 if ( is_admin() ) {
61 add_filter( 'ep_admin_wp_query_integration', '__return_true' );
62 add_action( 'pre_get_posts', [ $this, 'integrate' ] );
63 add_filter( 'ep_post_query_db_args', [ $this, 'query_password_protected_posts' ] );
64 }
65
66 if ( Features::factory()->get_registered_feature( 'comments' )->is_active() ) {
67 add_filter( 'ep_indexable_comment_status', [ $this, 'get_comment_statuses' ] );
68 add_action( 'pre_get_comments', [ $this, 'integrate_comments_query' ] );
69 }
70 }
71
72 /**
73 * Index all post types
74 *
75 * @param array $post_types Existing post types.
76 * @since 2.2
77 * @return array
78 */
79 public function post_types( $post_types ) {
80 // Let's get non public post types first
81 $pc_post_types = get_post_types( array( 'public' => false ) );
82
83 // We don't want to deal with nav menus
84 if ( $pc_post_types['nav_menu_item'] ) {
85 unset( $pc_post_types['nav_menu_item'] );
86 }
87
88 if ( ! empty( $pc_post_types['revision'] ) ) {
89 unset( $pc_post_types['revision'] );
90 }
91
92 if ( ! empty( $pc_post_types['custom_css'] ) ) {
93 unset( $pc_post_types['custom_css'] );
94 }
95
96 if ( ! empty( $pc_post_types['customize_changeset'] ) ) {
97 unset( $pc_post_types['customize_changeset'] );
98 }
99
100 if ( ! empty( $pc_post_types['oembed_cache'] ) ) {
101 unset( $pc_post_types['oembed_cache'] );
102 }
103
104 if ( ! empty( $pc_post_types['wp_block'] ) ) {
105 unset( $pc_post_types['wp_block'] );
106 }
107
108 if ( ! empty( $pc_post_types['user_request'] ) ) {
109 unset( $pc_post_types['user_request'] );
110 }
111
112 // By default, attachments are not indexed, we have to make sure they are included (Could already be included by documents feature).
113 $post_types['attachment'] = 'attachment';
114
115 // Merge non public post types with any pre-filtered post_type
116 return array_merge( $post_types, $pc_post_types );
117 }
118
119 /**
120 * Integrate EP into proper queries
121 *
122 * @param WP_Query $query WP Query
123 * @since 2.1
124 */
125 public function integrate( $query ) {
126 if ( ! Utils\is_integrated_request( $this->slug, [ 'admin' ] ) ) {
127 return;
128 }
129
130 // Lets make sure this doesn't interfere with the CLI
131 if ( defined( 'WP_CLI' ) && WP_CLI ) {
132 return;
133 }
134
135 if ( ! $query->is_main_query() ) {
136 return;
137 }
138
139 /**
140 * We limit to these post types to not conflict with other features like WooCommerce
141 *
142 * @since 2.1
143 * @var array
144 */
145 $post_types = array(
146 'post' => 'post',
147 'attachment' => 'attachment',
148 );
149
150 /**
151 * Filter protected content supported post types. For backwards compatibility.
152 *
153 * @hook ep_admin_supported_post_types
154 * @param {array} $post_types Post types
155 * @return {array} New post types
156 */
157 $supported_post_types = apply_filters( 'ep_admin_supported_post_types', $post_types );
158
159 /**
160 * Filter protected content supported post types.
161 *
162 * @hook ep_pc_supported_post_types
163 * @param {array} $supported_post_types Supported post types
164 * @return {array} New post types
165 */
166 $supported_post_types = apply_filters( 'ep_pc_supported_post_types', $supported_post_types );
167
168 $post_type = $query->get( 'post_type' );
169
170 if ( empty( $post_type ) ) {
171 $post_type = 'post';
172 }
173
174 if ( is_array( $post_type ) ) {
175 foreach ( $post_type as $pt ) {
176 if ( empty( $supported_post_types[ $pt ] ) ) {
177 return;
178 }
179 }
180
181 $query->set( 'ep_integrate', true );
182 } else {
183 if ( ! empty( $supported_post_types[ $post_type ] ) ) {
184 $query->set( 'ep_integrate', true );
185 }
186 }
187
188 /**
189 * Remove articles weighting by date in admin.
190 *
191 * @since 3.0
192 */
193 $search_feature = Features::factory()->get_registered_feature( 'search' );
194
195 remove_filter( 'ep_formatted_args', [ $search_feature, 'weight_recent' ], 10 );
196 }
197
198 /**
199 * Query all posts with and without password for indexing.
200 *
201 * @since 4.0.0
202 *
203 * @param array $args Database arguments
204 * @return array
205 */
206 public function query_password_protected_posts( $args ) {
207 $args['has_password'] = null;
208
209 return $args;
210 }
211
212 /**
213 * Include post password when indexing.
214 *
215 * @since 4.0.0
216 *
217 * @param array $post_args Post arguments
218 * @param int $post_id Post ID
219 * @return array
220 */
221 public function include_post_password( $post_args, $post_id ) {
222 $post = get_post( $post_id );
223
224 // Assign null value so we can use the EXISTS filter.
225 $post_args['post_password'] = ! empty( $post->post_password ) ? $post->post_password : null;
226
227 return $post_args;
228 }
229
230 /**
231 * Prevent some fields in password protected posts from being indexed.
232 *
233 * As some solutions publicly expose full post contents, this method prevents password
234 * protected posts to have their full content and their meta fields indexed. Developers
235 * wanting to bypass this behavior can use the `ep_pc_skip_post_content_cleanup` filter.
236 *
237 * @param array $post_args Post arguments
238 * @param int $post_id Post ID
239 * @return array
240 */
241 public function remove_fields_from_password_protected( $post_args, $post_id ) {
242 if ( empty( $post_args['post_password'] ) ) {
243 return $post_args;
244 }
245
246 /**
247 * Filter to skip the password protected content clean up.
248 *
249 * @hook ep_pc_skip_post_content_cleanup
250 * @since 4.0.0, 4.2.0 added $post_args and $post_id
251 * @param {bool} $skip Whether the password protected content should have their content, and meta removed
252 * @param {array} $post_args Post arguments
253 * @param {int} $post_id Post ID
254 * @return {bool}
255 */
256 if ( apply_filters( 'ep_pc_skip_post_content_cleanup', false, $post_args, $post_id ) ) {
257 return $post_args;
258 }
259
260 $fields_to_remove = [
261 'post_content_filtered',
262 'post_content',
263 'meta',
264 'thumbnail',
265 'post_content_plain',
266 'price_html',
267 ];
268
269 foreach ( $fields_to_remove as $field ) {
270 if ( ! empty( $post_args[ $field ] ) ) {
271 if ( is_array( $post_args[ $field ] ) ) {
272 $post_args[ $field ] = [];
273 } else {
274 $post_args[ $field ] = '';
275 }
276 }
277 }
278
279 return $post_args;
280 }
281
282 /**
283 * Exclude proctected post from the frontend queries.
284 *
285 * @since 4.0.0
286 *
287 * @param array $formatted_args Formatted Elasticsearch query
288 * @param array $args Query variables
289 * @return array
290 */
291 public function exclude_protected_posts( $formatted_args, $args ) {
292 if ( empty( $args['has_password'] ) ) {
293 /**
294 * Filter to exclude protected posts from search.
295 *
296 * @hook ep_exclude_password_protected_from_search
297 * @since 4.0.0
298 * @param {bool} $exclude Exclude post from search.
299 * @return {bool}
300 */
301 if ( ( ! is_user_logged_in() && ! empty( $args['s'] ) ) || apply_filters( 'ep_exclude_password_protected_from_search', false ) ) {
302 $formatted_args['post_filter']['bool']['must_not'][] = array(
303 'exists' => array(
304 'field' => 'post_password',
305 ),
306 );
307 }
308 }
309
310 return $formatted_args;
311 }
312
313 /**
314 * Add post_password to post object properties set after query
315 *
316 * @since 4.0.0
317 *
318 * @param array $properties Post properties
319 * @return array
320 */
321 public function return_post_password( $properties ) {
322 $properties[] = 'post_password';
323 return $properties;
324 }
325
326 /**
327 * Integrate EP into comment queries
328 *
329 * @param WP_Comment_Query $comment_query WP Comment Query
330 * @since 3.6.0
331 */
332 public function integrate_comments_query( $comment_query ) {
333 if ( ! Utils\is_integrated_request( $this->slug, [ 'admin' ] ) ) {
334 return;
335 }
336
337 // Lets make sure this doesn't interfere with the CLI
338 if ( defined( 'WP_CLI' ) && WP_CLI ) {
339 return;
340 }
341
342 $comment_types = array( 'comment', 'review' );
343
344 /**
345 * Filter protected content supported comment types.
346 *
347 * @hook ep_pc_supported_comment_types
348 * @since 3.6.0
349 * @param {array} $comment_types Comment types
350 * @return {array} New comment types
351 */
352 $supported_comment_types = apply_filters( 'ep_pc_supported_comment_types', $comment_types );
353
354 $comment_type = $comment_query->query_vars['type'];
355
356 if ( is_array( $comment_type ) ) {
357 foreach ( $comment_type as $comment_type_value ) {
358 if ( ! in_array( $comment_type_value, $supported_comment_types, true ) ) {
359 return;
360 }
361 }
362
363 $comment_query->query_vars['ep_integrate'] = true;
364 } else {
365 if ( in_array( $comment_type, $supported_comment_types, true ) ) {
366 $comment_query->query_vars['ep_integrate'] = true;
367 }
368 }
369
370 }
371
372 /**
373 * Output feature box long
374 *
375 * @since 2.1
376 */
377 public function output_feature_box_long() {
378 ?>
379 <p><?php echo wp_kses_post( __( 'Securely indexes unpublished content—including private, draft, and scheduled posts —improving load times in places like the administrative dashboard where WordPress needs to include protected content in a query. <em>We recommend using a secured Elasticsearch setup, such as ElasticPress.io, to prevent potential exposure of content not intended for the public.</em>', 'elasticpress' ) ); ?></p>
380 <?php
381 }
382
383 /**
384 * Fetches all post statuses we need to index
385 *
386 * @since 2.1
387 * @param array $statuses Post statuses array
388 * @return array
389 */
390 public function get_statuses( $statuses ) {
391 $post_statuses = get_post_stati();
392
393 unset( $post_statuses['auto-draft'] );
394
395 return array_unique( array_merge( $statuses, array_values( $post_statuses ) ) );
396 }
397
398 /**
399 * Fetches all comment statuses we need to index
400 *
401 * @since 3.6.0
402 * @param array $comment_statuses Post statuses array
403 * @return array
404 */
405 public function get_comment_statuses( $comment_statuses ) {
406 return [ 'all' ];
407 }
408
409 /**
410 * Determine feature reqs status
411 *
412 * @since 2.2
413 * @return FeatureRequirementsStatus
414 */
415 public function requirements_status() {
416 $status = new FeatureRequirementsStatus( 1 );
417
418 if ( ! Utils\is_epio() ) {
419 $status->message = __( "You aren't using <a href='https://elasticpress.io'>ElasticPress.io</a> so we can't be sure your Elasticsearch instance is secure.", 'elasticpress' );
420 }
421
422 return $status;
423 }
424 }
425