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

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