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 / ProtectedContent / ProtectedContent.php

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

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