PluginProbe
ElasticPress / 4.3.1
ElasticPress v4.3.1
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.3.1, at includes/classes/Feature/ProtectedContent/ProtectedContent.php

426 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 add_filter( 'ep_skip_autosave_sync', '__return_false' );
60
61 if ( is_admin() ) {
62 add_filter( 'ep_admin_wp_query_integration', '__return_true' );
63 add_action( 'pre_get_posts', [ $this, 'integrate' ] );
64 add_filter( 'ep_post_query_db_args', [ $this, 'query_password_protected_posts' ] );
65 }
66
67 if ( Features::factory()->get_registered_feature( 'comments' )->is_active() ) {
68 add_filter( 'ep_indexable_comment_status', [ $this, 'get_comment_statuses' ] );
69 add_action( 'pre_get_comments', [ $this, 'integrate_comments_query' ] );
70 }
71 }
72
73 /**
74 * Index all post types
75 *
76 * @param array $post_types Existing post types.
77 * @since 2.2
78 * @return array
79 */
80 public function post_types( $post_types ) {
81 // Let's get non public post types first
82 $pc_post_types = get_post_types( array( 'public' => false ) );
83
84 // We don't want to deal with nav menus
85 if ( $pc_post_types['nav_menu_item'] ) {
86 unset( $pc_post_types['nav_menu_item'] );
87 }
88
89 if ( ! empty( $pc_post_types['revision'] ) ) {
90 unset( $pc_post_types['revision'] );
91 }
92
93 if ( ! empty( $pc_post_types['custom_css'] ) ) {
94 unset( $pc_post_types['custom_css'] );
95 }
96
97 if ( ! empty( $pc_post_types['customize_changeset'] ) ) {
98 unset( $pc_post_types['customize_changeset'] );
99 }
100
101 if ( ! empty( $pc_post_types['oembed_cache'] ) ) {
102 unset( $pc_post_types['oembed_cache'] );
103 }
104
105 if ( ! empty( $pc_post_types['wp_block'] ) ) {
106 unset( $pc_post_types['wp_block'] );
107 }
108
109 if ( ! empty( $pc_post_types['user_request'] ) ) {
110 unset( $pc_post_types['user_request'] );
111 }
112
113 // By default, attachments are not indexed, we have to make sure they are included (Could already be included by documents feature).
114 $post_types['attachment'] = 'attachment';
115
116 // Merge non public post types with any pre-filtered post_type
117 return array_merge( $post_types, $pc_post_types );
118 }
119
120 /**
121 * Integrate EP into proper queries
122 *
123 * @param WP_Query $query WP Query
124 * @since 2.1
125 */
126 public function integrate( $query ) {
127 if ( ! Utils\is_integrated_request( $this->slug, [ 'admin' ] ) ) {
128 return;
129 }
130
131 // Lets make sure this doesn't interfere with the CLI
132 if ( defined( 'WP_CLI' ) && WP_CLI ) {
133 return;
134 }
135
136 if ( ! $query->is_main_query() ) {
137 return;
138 }
139
140 /**
141 * We limit to these post types to not conflict with other features like WooCommerce
142 *
143 * @since 2.1
144 * @var array
145 */
146 $post_types = array(
147 'post' => 'post',
148 'attachment' => 'attachment',
149 );
150
151 /**
152 * Filter protected content supported post types. For backwards compatibility.
153 *
154 * @hook ep_admin_supported_post_types
155 * @param {array} $post_types Post types
156 * @return {array} New post types
157 */
158 $supported_post_types = apply_filters( 'ep_admin_supported_post_types', $post_types );
159
160 /**
161 * Filter protected content supported post types.
162 *
163 * @hook ep_pc_supported_post_types
164 * @param {array} $supported_post_types Supported post types
165 * @return {array} New post types
166 */
167 $supported_post_types = apply_filters( 'ep_pc_supported_post_types', $supported_post_types );
168
169 $post_type = $query->get( 'post_type' );
170
171 if ( empty( $post_type ) ) {
172 $post_type = 'post';
173 }
174
175 if ( is_array( $post_type ) ) {
176 foreach ( $post_type as $pt ) {
177 if ( empty( $supported_post_types[ $pt ] ) ) {
178 return;
179 }
180 }
181
182 $query->set( 'ep_integrate', true );
183 } else {
184 if ( ! empty( $supported_post_types[ $post_type ] ) ) {
185 $query->set( 'ep_integrate', true );
186 }
187 }
188
189 /**
190 * Remove articles weighting by date in admin.
191 *
192 * @since 3.0
193 */
194 $search_feature = Features::factory()->get_registered_feature( 'search' );
195
196 remove_filter( 'ep_formatted_args', [ $search_feature, 'weight_recent' ], 10 );
197 }
198
199 /**
200 * Query all posts with and without password for indexing.
201 *
202 * @since 4.0.0
203 *
204 * @param array $args Database arguments
205 * @return array
206 */
207 public function query_password_protected_posts( $args ) {
208 $args['has_password'] = null;
209
210 return $args;
211 }
212
213 /**
214 * Include post password when indexing.
215 *
216 * @since 4.0.0
217 *
218 * @param array $post_args Post arguments
219 * @param int $post_id Post ID
220 * @return array
221 */
222 public function include_post_password( $post_args, $post_id ) {
223 $post = get_post( $post_id );
224
225 // Assign null value so we can use the EXISTS filter.
226 $post_args['post_password'] = ! empty( $post->post_password ) ? $post->post_password : null;
227
228 return $post_args;
229 }
230
231 /**
232 * Prevent some fields in password protected posts from being indexed.
233 *
234 * As some solutions publicly expose full post contents, this method prevents password
235 * protected posts to have their full content and their meta fields indexed. Developers
236 * wanting to bypass this behavior can use the `ep_pc_skip_post_content_cleanup` filter.
237 *
238 * @param array $post_args Post arguments
239 * @param int $post_id Post ID
240 * @return array
241 */
242 public function remove_fields_from_password_protected( $post_args, $post_id ) {
243 if ( empty( $post_args['post_password'] ) ) {
244 return $post_args;
245 }
246
247 /**
248 * Filter to skip the password protected content clean up.
249 *
250 * @hook ep_pc_skip_post_content_cleanup
251 * @since 4.0.0, 4.2.0 added $post_args and $post_id
252 * @param {bool} $skip Whether the password protected content should have their content, and meta removed
253 * @param {array} $post_args Post arguments
254 * @param {int} $post_id Post ID
255 * @return {bool}
256 */
257 if ( apply_filters( 'ep_pc_skip_post_content_cleanup', false, $post_args, $post_id ) ) {
258 return $post_args;
259 }
260
261 $fields_to_remove = [
262 'post_content_filtered',
263 'post_content',
264 'meta',
265 'thumbnail',
266 'post_content_plain',
267 'price_html',
268 ];
269
270 foreach ( $fields_to_remove as $field ) {
271 if ( ! empty( $post_args[ $field ] ) ) {
272 if ( is_array( $post_args[ $field ] ) ) {
273 $post_args[ $field ] = [];
274 } else {
275 $post_args[ $field ] = '';
276 }
277 }
278 }
279
280 return $post_args;
281 }
282
283 /**
284 * Exclude proctected post from the frontend queries.
285 *
286 * @since 4.0.0
287 *
288 * @param array $formatted_args Formatted Elasticsearch query
289 * @param array $args Query variables
290 * @return array
291 */
292 public function exclude_protected_posts( $formatted_args, $args ) {
293 if ( empty( $args['has_password'] ) ) {
294 /**
295 * Filter to exclude protected posts from search.
296 *
297 * @hook ep_exclude_password_protected_from_search
298 * @since 4.0.0
299 * @param {bool} $exclude Exclude post from search.
300 * @return {bool}
301 */
302 if ( ( ! is_user_logged_in() && ! empty( $args['s'] ) ) || apply_filters( 'ep_exclude_password_protected_from_search', false ) ) {
303 $formatted_args['post_filter']['bool']['must_not'][] = array(
304 'exists' => array(
305 'field' => 'post_password',
306 ),
307 );
308 }
309 }
310
311 return $formatted_args;
312 }
313
314 /**
315 * Add post_password to post object properties set after query
316 *
317 * @since 4.0.0
318 *
319 * @param array $properties Post properties
320 * @return array
321 */
322 public function return_post_password( $properties ) {
323 $properties[] = 'post_password';
324 return $properties;
325 }
326
327 /**
328 * Integrate EP into comment queries
329 *
330 * @param WP_Comment_Query $comment_query WP Comment Query
331 * @since 3.6.0
332 */
333 public function integrate_comments_query( $comment_query ) {
334 if ( ! Utils\is_integrated_request( $this->slug, [ 'admin' ] ) ) {
335 return;
336 }
337
338 // Lets make sure this doesn't interfere with the CLI
339 if ( defined( 'WP_CLI' ) && WP_CLI ) {
340 return;
341 }
342
343 $comment_types = array( 'comment', 'review' );
344
345 /**
346 * Filter protected content supported comment types.
347 *
348 * @hook ep_pc_supported_comment_types
349 * @since 3.6.0
350 * @param {array} $comment_types Comment types
351 * @return {array} New comment types
352 */
353 $supported_comment_types = apply_filters( 'ep_pc_supported_comment_types', $comment_types );
354
355 $comment_type = $comment_query->query_vars['type'];
356
357 if ( is_array( $comment_type ) ) {
358 foreach ( $comment_type as $comment_type_value ) {
359 if ( ! in_array( $comment_type_value, $supported_comment_types, true ) ) {
360 return;
361 }
362 }
363
364 $comment_query->query_vars['ep_integrate'] = true;
365 } else {
366 if ( in_array( $comment_type, $supported_comment_types, true ) ) {
367 $comment_query->query_vars['ep_integrate'] = true;
368 }
369 }
370
371 }
372
373 /**
374 * Output feature box long
375 *
376 * @since 2.1
377 */
378 public function output_feature_box_long() {
379 ?>
380 <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>
381 <?php
382 }
383
384 /**
385 * Fetches all post statuses we need to index
386 *
387 * @since 2.1
388 * @param array $statuses Post statuses array
389 * @return array
390 */
391 public function get_statuses( $statuses ) {
392 $post_statuses = get_post_stati();
393
394 unset( $post_statuses['auto-draft'] );
395
396 return array_unique( array_merge( $statuses, array_values( $post_statuses ) ) );
397 }
398
399 /**
400 * Fetches all comment statuses we need to index
401 *
402 * @since 3.6.0
403 * @param array $comment_statuses Post statuses array
404 * @return array
405 */
406 public function get_comment_statuses( $comment_statuses ) {
407 return [ 'all' ];
408 }
409
410 /**
411 * Determine feature reqs status
412 *
413 * @since 2.2
414 * @return FeatureRequirementsStatus
415 */
416 public function requirements_status() {
417 $status = new FeatureRequirementsStatus( 1 );
418
419 if ( ! Utils\is_epio() ) {
420 $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' );
421 }
422
423 return $status;
424 }
425 }
426