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

dashboard.php in ElasticPress 4.2.2, at includes/dashboard.php

930 lines 26.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Create an ElasticPress dashboard page.
4 *
5 * @package elasticpress
6 * @since 1.9
7 */
8
9 namespace ElasticPress\Dashboard;
10
11 use ElasticPress\Utils as Utils;
12 use ElasticPress\Elasticsearch;
13 use ElasticPress\Features;
14 use ElasticPress\Indexables;
15 use ElasticPress\Installer;
16 use ElasticPress\AdminNotices;
17 use ElasticPress\Screen;
18 use ElasticPress\Stats;
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit; // Exit if accessed directly.
22 }
23
24 /**
25 * Setup actions and filters for all things settings
26 *
27 * @since 2.1
28 */
29 function setup() {
30 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { // Must be network admin in multisite.
31 add_action( 'network_admin_menu', __NAMESPACE__ . '\action_admin_menu' );
32 add_action( 'admin_bar_menu', __NAMESPACE__ . '\action_network_admin_bar_menu', 50 );
33 } else {
34 add_action( 'admin_menu', __NAMESPACE__ . '\action_admin_menu' );
35 }
36
37 add_action( 'wp_ajax_ep_save_feature', __NAMESPACE__ . '\action_wp_ajax_ep_save_feature' );
38 add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\action_admin_enqueue_dashboard_scripts' );
39 add_action( 'admin_init', __NAMESPACE__ . '\action_admin_init' );
40 add_action( 'admin_init', __NAMESPACE__ . '\maybe_clear_es_info_cache' );
41 add_action( 'admin_init', __NAMESPACE__ . '\maybe_skip_install' );
42 add_action( 'wp_ajax_ep_notice_dismiss', __NAMESPACE__ . '\action_wp_ajax_ep_notice_dismiss' );
43 add_action( 'admin_notices', __NAMESPACE__ . '\maybe_notice' );
44 add_action( 'network_admin_notices', __NAMESPACE__ . '\maybe_notice' );
45 add_filter( 'plugin_action_links', __NAMESPACE__ . '\filter_plugin_action_links', 10, 2 );
46 add_filter( 'network_admin_plugin_action_links', __NAMESPACE__ . '\filter_plugin_action_links', 10, 2 );
47 add_action( 'ep_add_query_log', __NAMESPACE__ . '\log_version_query_error' );
48 add_filter( 'ep_analyzer_language', __NAMESPACE__ . '\use_language_in_setting', 10, 2 );
49 add_filter( 'wp_kses_allowed_html', __NAMESPACE__ . '\filter_allowed_html', 10, 2 );
50 add_action( 'manage_blogs_custom_column', __NAMESPACE__ . '\add_blogs_column', 10, 2 );
51 add_action( 'rest_api_init', __NAMESPACE__ . '\setup_endpoint' );
52
53 /**
54 * Filter whether to show 'ElasticPress Indexing' option on Multisite in admin UI or not.
55 *
56 * @since 3.6.0
57 * @hook ep_show_indexing_option_on_multisite
58 * @param {bool} $show True to show.
59 * @return {bool} New value
60 */
61 $show_indexing_option_on_multisite = apply_filters( 'ep_show_indexing_option_on_multisite', true );
62
63 if ( $show_indexing_option_on_multisite ) {
64 add_filter( 'wpmu_blogs_columns', __NAMESPACE__ . '\filter_blogs_columns', 10, 1 );
65 add_action( 'manage_sites_custom_column', __NAMESPACE__ . '\add_blogs_column', 10, 2 );
66 add_action( 'wp_ajax_ep_site_admin', __NAMESPACE__ . '\action_wp_ajax_ep_site_admin' );
67 }
68 }
69
70 /**
71 * Add ep-html kses context
72 *
73 * @param array $allowedtags HTML tags
74 * @param string $context Context string
75 * @since 3.0
76 * @return array
77 */
78 function filter_allowed_html( $allowedtags, $context ) {
79 global $allowedposttags;
80
81 if ( 'ep-html' === $context ) {
82 $ep_tags = $allowedposttags;
83
84 $atts = [
85 'type' => true,
86 'checked' => true,
87 'selected' => true,
88 'disabled' => true,
89 'value' => true,
90 'href' => true,
91 'class' => true,
92 'data-*' => true,
93 'data-field-name' => true,
94 'data-ep-notice' => true,
95 'data-feature' => true,
96 'id' => true,
97 'style' => true,
98 'title' => true,
99 'name' => true,
100 'placeholder' => '',
101 ];
102
103 $ep_tags['input'] = $atts;
104 $ep_tags['select'] = $atts;
105 $ep_tags['textarea'] = $atts;
106 $ep_tags['option'] = $atts;
107
108 $ep_tags['form'] = [
109 'action' => true,
110 'accept' => true,
111 'accept-charset' => true,
112 'enctype' => true,
113 'method' => true,
114 'name' => true,
115 'target' => true,
116 ];
117
118 $ep_tags['a'] = $atts;
119
120 return $ep_tags;
121 }
122
123 return $allowedtags;
124 }
125
126 /**
127 * Stores the results of the version query.
128 *
129 * @param array $query The version query.
130 * @since 3.0
131 */
132 function log_version_query_error( $query ) {
133 $is_network = defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK;
134
135 $logging_key = 'logging_ep_es_info';
136
137 if ( $is_network ) {
138 $logging = get_site_transient( $logging_key );
139 } else {
140 $logging = get_transient( $logging_key );
141 }
142
143 // Are we logging the version query results?
144 if ( '1' === $logging ) {
145 /**
146 * Filter how long results of Elasticsearch version query are stored
147 *
148 * @since 23.0
149 * @hook ep_es_info_cache_expiration
150 * @param {int} Time in seconds
151 * @return {int} New time in seconds
152 */
153 $cache_time = apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) );
154 $response_code_key = 'ep_es_info_response_code';
155 $response_error_key = 'ep_es_info_response_error';
156 $response_code = 0;
157 $response_error = '';
158
159 if ( ! empty( $query['request'] ) ) {
160 $response_code = absint( wp_remote_retrieve_response_code( $query['request'] ) );
161 $response_error = wp_remote_retrieve_response_message( $query['request'] );
162 if ( empty( $response_error ) && is_wp_error( $query['request'] ) ) {
163 $response_error = $query['request']->get_error_message();
164 }
165 }
166
167 // Store the response code, and remove the flag that says
168 // we're logging the response code so we don't log additional
169 // queries.
170 if ( $is_network ) {
171 set_site_transient( $response_code_key, $response_code, $cache_time );
172 set_site_transient( $response_error_key, $response_error, $cache_time );
173 delete_site_transient( $logging_key );
174 } else {
175 set_transient( $response_code_key, $response_code, $cache_time );
176 set_transient( $response_error_key, $response_error, $cache_time );
177 delete_transient( $logging_key );
178 }
179 }
180 }
181
182 /**
183 * Allow user to skip install process.
184 *
185 * @since 3.5
186 */
187 function maybe_skip_install() {
188 if ( ! is_admin() && ! is_network_admin() ) {
189 return;
190 }
191
192 if ( empty( $_GET['ep-skip-install'] ) || empty( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], 'ep-skip-install' ) || ! in_array( Screen::factory()->get_current_screen(), [ 'install' ], true ) ) { // phpcs:ignore WordPress.Security.NonceVerification
193 return;
194 }
195
196 if ( ! empty( $_GET['ep-skip-features'] ) ) {
197 $features = \ElasticPress\Features::factory()->registered_features;
198
199 foreach ( $features as $slug => $feature ) {
200 \ElasticPress\Features::factory()->deactivate_feature( $slug );
201 }
202 }
203
204 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
205 $redirect_url = network_admin_url( 'admin.php?page=elasticpress' );
206 update_site_option( 'ep_skip_install', true );
207 } else {
208 $redirect_url = admin_url( 'admin.php?page=elasticpress' );
209 update_option( 'ep_skip_install', true );
210 }
211
212 wp_safe_redirect( $redirect_url );
213 exit;
214 }
215
216 /**
217 * Clear ES info cache whenever EP dash or settings page is viewed. Also clear cache
218 * when "try again" notification link is clicked.
219 *
220 * @since 2.3.1
221 */
222 function maybe_clear_es_info_cache() {
223 if ( ! is_admin() && ! is_network_admin() ) {
224 return;
225 }
226
227 if ( empty( $_GET['ep-retry'] ) && ! in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'settings', 'install' ], true ) ) { // phpcs:ignore WordPress.Security.NonceVerification
228 return;
229 }
230
231 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
232 delete_site_transient( 'ep_es_info' );
233 } else {
234 delete_transient( 'ep_es_info' );
235 }
236
237 if ( ! empty( $_GET['ep-retry'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
238 wp_safe_redirect( remove_query_arg( 'ep-retry' ) );
239 }
240 }
241
242 /**
243 * Show ElasticPress in network admin menu bar
244 *
245 * @param object $admin_bar WP_Admin Bar reference.
246 * @since 2.2
247 */
248 function action_network_admin_bar_menu( $admin_bar ) {
249 $admin_bar->add_menu(
250 array(
251 'id' => 'network-admin-elasticpress',
252 'parent' => 'network-admin',
253 'title' => 'ElasticPress',
254 'href' => esc_url( network_admin_url( 'admin.php?page=elasticpress' ) ),
255 )
256 );
257 }
258
259 /**
260 * Output dashboard link in plugin actions
261 *
262 * @param array $plugin_actions Array of HTML.
263 * @param string $plugin_file Path to plugin file.
264 * @since 2.1
265 * @return array
266 */
267 function filter_plugin_action_links( $plugin_actions, $plugin_file ) {
268
269 if ( is_network_admin() ) {
270 $url = admin_url( 'network/admin.php?page=elasticpress' );
271
272 if ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) {
273 return $plugin_actions;
274 }
275 } else {
276 $url = admin_url( 'admin.php?page=elasticpress' );
277
278 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
279 return $plugin_actions;
280 }
281 }
282
283 $new_actions = [];
284
285 if ( basename( EP_PATH ) . '/elasticpress.php' === $plugin_file ) {
286 $new_actions['ep_dashboard'] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), __( 'Dashboard', 'elasticpress' ) );
287 }
288
289 return array_merge( $new_actions, $plugin_actions );
290 }
291
292 /**
293 * Output variety of dashboard notices.
294 *
295 * @param bool $force Force ES info hard lookup.
296 * @since 3.0
297 */
298 function maybe_notice( $force = false ) {
299 // Admins only.
300 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
301 if ( ! is_super_admin() ) {
302 return false;
303 }
304 } else {
305 if ( ! current_user_can( 'manage_options' ) ) {
306 return false;
307 }
308 }
309
310 // If in network mode, don't output notice in admin and vice-versa.
311 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
312 if ( ! is_network_admin() ) {
313 return false;
314 }
315 } else {
316 if ( is_network_admin() ) {
317 return false;
318 }
319 }
320
321 /**
322 * Filter how long results of Elasticsearch version query are stored
323 *
324 * @since 23.0
325 * @hook ep_es_info_cache_expiration
326 * @param {int} Time in seconds
327 * @return {int} New time in seconds
328 */
329 $cache_time = apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) );
330
331 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
332 set_site_transient(
333 'logging_ep_es_info',
334 '1',
335 $cache_time
336 );
337 } else {
338 $a = set_transient(
339 'logging_ep_es_info',
340 '1',
341 $cache_time
342 );
343 }
344
345 // Fetch ES version
346 Elasticsearch::factory()->get_elasticsearch_version( $force );
347
348 AdminNotices::factory()->process_notices();
349
350 $notices = AdminNotices::factory()->get_notices();
351
352 foreach ( $notices as $notice_key => $notice ) {
353 ?>
354 <div data-ep-notice="<?php echo esc_attr( $notice_key ); ?>" class="notice notice-<?php echo esc_attr( $notice['type'] ); ?> <?php
355 if ( $notice['dismiss'] ) :
356 ?>
357 is-dismissible<?php endif; ?>">
358 <p>
359 <?php echo wp_kses( $notice['html'], 'ep-html' ); ?>
360 </p>
361 </div>
362 <?php
363 }
364
365 wp_enqueue_script( 'ep_notice_script' );
366
367 return $notices;
368 }
369
370 /**
371 * Dismiss notice via ajax
372 *
373 * @since 2.2
374 */
375 function action_wp_ajax_ep_notice_dismiss() {
376 if ( empty( $_POST['notice'] ) || ! check_ajax_referer( 'ep_admin_nonce', 'nonce', false ) ) {
377 wp_send_json_error();
378 exit;
379 }
380
381 if ( ! current_user_can( 'manage_options' ) ) {
382 wp_send_json_error();
383 exit;
384 }
385
386 AdminNotices::factory()->dismiss_notice( $_POST['notice'] );
387
388 wp_send_json_success();
389 }
390
391 /**
392 * Getting the status of ongoing index fired by WP CLI
393 *
394 * @since 2.1
395 */
396 function action_wp_ajax_ep_cli_index() {
397 _deprecated_function( __CLASS__, '3.6.0', '\ElasticPress\Screen::factory()->sync_screen->action_wp_ajax_ep_cli_index()' );
398 }
399
400 /**
401 * Continue index
402 *
403 * @since 2.1
404 */
405 function action_wp_ajax_ep_index() {
406 _deprecated_function( __CLASS__, '3.6.0', '\ElasticPress\Screen::factory()->sync_screen->action_wp_ajax_ep_index()' );
407 }
408
409 /**
410 * Cancel index
411 *
412 * @since 2.1
413 */
414 function action_wp_ajax_ep_cancel_index() {
415 _deprecated_function( __CLASS__, '3.6.0', '\ElasticPress\Screen::factory()->sync_screen->action_wp_ajax_ep_cancel_index()' );
416 }
417
418 /**
419 * Save individual feature settings
420 *
421 * @since 2.2
422 */
423 function action_wp_ajax_ep_save_feature() {
424 $_POST = wp_unslash( $_POST );
425
426 if ( empty( $_POST['feature'] ) || empty( $_POST['settings'] ) || ! check_ajax_referer( 'ep_dashboard_nonce', 'nonce', false ) ) {
427 wp_send_json_error();
428 exit;
429 }
430
431 if ( Utils\is_indexing() ) {
432 $error = new \WP_Error( 'is_indexing' );
433
434 wp_send_json_error( $error );
435 exit;
436 }
437
438 $data = Features::factory()->update_feature( $_POST['feature'], $_POST['settings'] );
439
440 // Since we deactivated, delete auto activate notice.
441 if ( empty( $_POST['settings']['active'] ) ) {
442 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
443 delete_site_option( 'ep_feature_auto_activated_sync' );
444 } else {
445 delete_option( 'ep_feature_auto_activated_sync' );
446 }
447 }
448
449 wp_send_json_success( $data );
450 }
451
452 /**
453 * Register and Enqueue JavaScripts for dashboard
454 *
455 * @since 2.2
456 */
457 function action_admin_enqueue_dashboard_scripts() {
458 if ( isset( get_current_screen()->id ) && strpos( get_current_screen()->id, 'sites-network' ) !== false ) {
459 wp_enqueue_style( 'wp-components' );
460
461 wp_enqueue_script(
462 'ep_admin_sites_scripts',
463 EP_URL . 'dist/js/sites-admin-script.min.js',
464 Utils\get_asset_info( 'sites-admin-script', 'dependencies' ),
465 Utils\get_asset_info( 'sites-admin-script', 'version' ),
466 true
467 );
468
469 $data = [
470 'ajax_url' => admin_url( 'admin-ajax.php' ),
471 'nonce' => wp_create_nonce( 'epsa' ),
472 ];
473
474 wp_localize_script( 'ep_admin_sites_scripts', 'epsa', $data );
475 }
476
477 if ( in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'settings', 'install', 'health', 'weighting', 'synonyms', 'sync' ], true ) ) {
478 wp_enqueue_style(
479 'ep_admin_styles',
480 EP_URL . 'dist/css/dashboard-styles.min.css',
481 Utils\get_asset_info( 'dashboard-styles', 'dependencies' ),
482 Utils\get_asset_info( 'dashboard-styles', 'version' )
483 );
484 }
485
486 if ( in_array( Screen::factory()->get_current_screen(), [ 'weighting', 'install' ], true ) ) {
487 wp_enqueue_script(
488 'ep_weighting_script',
489 EP_URL . 'dist/js/weighting-script.min.js',
490 Utils\get_asset_info( 'weighting-script', 'dependencies' ),
491 Utils\get_asset_info( 'weighting-script', 'version' ),
492 true
493 );
494 }
495
496 if ( in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'install' ], true ) ) {
497 wp_enqueue_script(
498 'ep_dashboard_scripts',
499 EP_URL . 'dist/js/dashboard-script.min.js',
500 Utils\get_asset_info( 'dashboard-script', 'dependencies' ),
501 Utils\get_asset_info( 'dashboard-script', 'version' ),
502 true
503 );
504
505 $sync_url = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ?
506 network_admin_url( 'admin.php?page=elasticpress-sync&do_sync' ) :
507 admin_url( 'admin.php?page=elasticpress-sync&do_sync' );
508
509 $skip_url = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ?
510 network_admin_url( 'admin.php?page=elasticpress' ) :
511 admin_url( 'admin.php?page=elasticpress' );
512
513 $data = array(
514 'skipUrl' => add_query_arg(
515 array(
516 'ep-skip-install' => 1,
517 'ep-skip-features' => 1,
518 'nonce' => wp_create_nonce( 'ep-skip-install' ),
519 ),
520 $skip_url
521 ),
522 'syncUrl' => $sync_url,
523 );
524
525 wp_localize_script( 'ep_dashboard_scripts', 'epDash', $data );
526 }
527
528 if ( in_array( Screen::factory()->get_current_screen(), [ 'settings' ], true ) ) {
529 wp_enqueue_script(
530 'ep_settings_scripts',
531 EP_URL . 'dist/js/settings-script.min.js',
532 Utils\get_asset_info( 'settings-script', 'dependencies' ),
533 Utils\get_asset_info( 'settings-script', 'version' ),
534 true
535 );
536 }
537
538 if ( in_array( Screen::factory()->get_current_screen(), [ 'health' ], true ) && ! empty( Utils\get_host() ) ) {
539 Stats::factory()->build_stats();
540
541 $data = Stats::factory()->get_localized();
542
543 wp_enqueue_script(
544 'ep_stats',
545 EP_URL . 'dist/js/stats-script.min.js',
546 Utils\get_asset_info( 'stats-script', 'dependencies' ),
547 Utils\get_asset_info( 'stats-script', 'version' ),
548 true
549 );
550
551 wp_localize_script( 'ep_stats', 'epChartData', $data );
552 }
553
554 wp_register_script(
555 'ep_notice_script',
556 EP_URL . 'dist/js/notice-script.min.js',
557 Utils\get_asset_info( 'notice-script', 'dependencies' ),
558 Utils\get_asset_info( 'notice-script', 'version' ),
559 true
560 );
561
562 wp_localize_script(
563 'ep_notice_script',
564 'epAdmin',
565 array(
566 'nonce' => wp_create_nonce( 'ep_admin_nonce' ),
567 )
568 );
569 }
570
571 /**
572 * Admin-init actions
573 *
574 * Sets up Settings API.
575 *
576 * @since 1.9
577 * @return void
578 */
579 function action_admin_init() {
580
581 // Save options for multisite.
582 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK && isset( $_POST['ep_language'] ) ) {
583 check_admin_referer( 'elasticpress-options' );
584
585 $language = sanitize_text_field( $_POST['ep_language'] );
586 update_site_option( 'ep_language', $language );
587
588 if ( isset( $_POST['ep_host'] ) ) {
589 $host = esc_url_raw( trim( $_POST['ep_host'] ) );
590 update_site_option( 'ep_host', $host );
591 }
592
593 if ( isset( $_POST['ep_prefix'] ) ) {
594 $prefix = ( isset( $_POST['ep_prefix'] ) ) ? sanitize_text_field( wp_unslash( $_POST['ep_prefix'] ) ) : '';
595 update_site_option( 'ep_prefix', $prefix );
596 }
597
598 if ( isset( $_POST['ep_credentials'] ) ) {
599 $credentials = ( isset( $_POST['ep_credentials'] ) ) ? Utils\sanitize_credentials( $_POST['ep_credentials'] ) : [
600 'username' => '',
601 'token' => '',
602 ];
603
604 update_site_option( 'ep_credentials', $credentials );
605 }
606
607 if ( isset( $_POST['ep_bulk_setting'] ) ) {
608 update_site_option( 'ep_bulk_setting', intval( $_POST['ep_bulk_setting'] ) );
609 }
610 } else {
611 register_setting( 'elasticpress', 'ep_host', 'esc_url_raw' );
612 register_setting( 'elasticpress', 'ep_prefix', 'sanitize_text_field' );
613 register_setting( 'elasticpress', 'ep_credentials', 'ep_sanitize_credentials' );
614 register_setting( 'elasticpress', 'ep_language', 'sanitize_text_field' );
615 register_setting(
616 'elasticpress',
617 'ep_bulk_setting',
618 [
619 'type' => 'integer',
620 'sanitize_callback' => __NAMESPACE__ . '\sanitize_bulk_settings',
621 ]
622 );
623 }
624 }
625
626 /**
627 * Sanitize bulk settings.
628 *
629 * @param int $bulk_settings Number of bulk content items
630 * @return int
631 */
632 function sanitize_bulk_settings( $bulk_settings = 350 ) {
633 $bulk_settings = absint( $bulk_settings );
634
635 return ( 0 === $bulk_settings ) ? 350 : $bulk_settings;
636 }
637
638 /**
639 * Output current ElasticPress dashboard screen
640 *
641 * @since 3.0
642 */
643 function resolve_screen() {
644 Screen::factory()->output();
645 }
646
647 /**
648 * Admin menu actions
649 *
650 * Adds options page to admin menu.
651 *
652 * @since 1.9
653 * @return void
654 */
655 function action_admin_menu() {
656 $capability = 'manage_options';
657
658 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
659 $capability = 'manage_network';
660 }
661
662 add_menu_page(
663 'ElasticPress',
664 'ElasticPress',
665 $capability,
666 'elasticpress',
667 __NAMESPACE__ . '\resolve_screen',
668 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNzMgNzEuMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNzMgNzEuMzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGQ9Ik0zNi41LDQuN0MxOS40LDQuNyw1LjYsMTguNiw1LjYsMzUuN2MwLDEwLDQuNywxOC45LDEyLjEsMjQuNWw0LjUtNC41YzAuMS0wLjEsMC4xLTAuMiwwLjItMC4zbDAuNy0wLjdsNi40LTYuNGMyLjEsMS4yLDQuNSwxLjksNy4xLDEuOWM4LDAsMTQuNS02LjUsMTQuNS0xNC41cy02LjUtMTQuNS0xNC41LTE0LjVTMjIsMjcuNiwyMiwzNS42YzAsMi44LDAuOCw1LjMsMi4xLDcuNWwtNi40LDYuNGMtMi45LTMuOS00LjYtOC43LTQuNi0xMy45YzAtMTIuOSwxMC41LTIzLjQsMjMuNC0yMy40czIzLjQsMTAuNSwyMy40LDIzLjRTNDkuNCw1OSwzNi41LDU5Yy0yLjEsMC00LjEtMC4zLTYtMC44bC0wLjYsMC42bC01LjIsNS40YzMuNiwxLjUsNy42LDIuMywxMS44LDIuM2MxNy4xLDAsMzAuOS0xMy45LDMwLjktMzAuOVM1My42LDQuNywzNi41LDQuN3oiLz48L3N2Zz4='
669 );
670
671 add_submenu_page(
672 'elasticpress',
673 esc_html__( 'ElasticPress Features', 'elasticpress' ),
674 esc_html__( 'Features', 'elasticpress' ),
675 $capability,
676 'elasticpress',
677 __NAMESPACE__ . '\resolve_screen'
678 );
679
680 add_submenu_page(
681 'elasticpress',
682 esc_html__( 'ElasticPress Settings', 'elasticpress' ),
683 esc_html__( 'Settings', 'elasticpress' ),
684 $capability,
685 'elasticpress-settings',
686 __NAMESPACE__ . '\resolve_screen'
687 );
688
689 add_submenu_page(
690 'elasticpress',
691 'ElasticPress ' . esc_html__( 'Sync', 'elasticpress' ),
692 esc_html__( 'Sync', 'elasticpress' ),
693 $capability,
694 'elasticpress-sync',
695 __NAMESPACE__ . '\resolve_screen'
696 );
697
698 add_submenu_page(
699 'elasticpress',
700 esc_html__( 'ElasticPress Index Health', 'elasticpress' ),
701 esc_html__( 'Index Health', 'elasticpress' ),
702 $capability,
703 'elasticpress-health',
704 __NAMESPACE__ . '\resolve_screen'
705 );
706 }
707
708 /**
709 * Uses the language from EP settings in mapping.
710 *
711 * @param string $language The current language.
712 * @param string $context The context where the function is running.
713 * @return string The updated language.
714 */
715 function use_language_in_setting( $language = 'english', $context = '' ) {
716 // Get the currently set language.
717 $ep_language = Utils\get_language();
718
719 // Bail early if no EP language is set.
720 if ( empty( $ep_language ) ) {
721 return $language;
722 }
723
724 require_once ABSPATH . 'wp-admin/includes/translation-install.php';
725 $translations = wp_get_available_translations();
726
727 // Bail early if not in the array of available translations.
728 if ( empty( $translations[ $ep_language ]['english_name'] ) ) {
729 return $language;
730 }
731
732 $wp_language = $translations[ $ep_language ]['language'];
733
734 /**
735 * Languages supported in Elasticsearch mappings.
736 * Array format: Elasticsearch analyzer name => WordPress language package name
737 *
738 * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lang-analyzer.html
739 */
740 $es_languages = [
741 'arabic' => [ 'ar', 'ary' ],
742 'armenian' => [ 'hy' ],
743 'basque' => [ 'eu' ],
744 'bengali' => [ 'bn', 'bn_BD' ],
745 'brazilian' => [ 'pt_BR' ],
746 'bulgarian' => [ 'bg' ],
747 'catalan' => [ 'ca' ],
748 'cjk' => [], // CJK characters (not a language)
749 'czech' => [ 'cs' ],
750 'danish' => [ 'da' ],
751 'dutch' => [ 'nl_NL_formal', 'nl_NL', 'nl_BE' ],
752 'english' => [ 'en', 'en_AU', 'en_GB', 'en_NZ', 'en_CA', 'en_ZA' ],
753 'estonian' => [ 'et' ],
754 'finnish' => [ 'fi' ],
755 'french' => [ 'fr', 'fr_CA', 'fr_FR', 'fr_BE' ],
756 'galician' => [ 'gl_ES' ],
757 'german' => [ 'de', 'de_DE', 'de_DE_formal', 'de_CH', 'de_CH_informal', 'de_AT' ],
758 'greek' => [ 'el' ],
759 'hindi' => [ 'hi_IN' ],
760 'hungarian' => [ 'hu_HU' ],
761 'indonesian' => [ 'id_ID' ],
762 'irish' => [], // WordPress doesn't support Irish as an active locale currently
763 'italian' => [ 'it_IT' ],
764 'latvian' => [ 'lv' ],
765 'lithuanian' => [ 'lt_LT' ],
766 'norwegian' => [ 'nb_NO' ],
767 'persian' => [ 'fa_IR' ],
768 'portuguese' => [ 'pt', 'pt_AO', 'pt_PT', 'pt_PT_ao90' ],
769 'romanian' => [ 'ro_RO' ],
770 'russian' => [ 'ru_RU' ],
771 'sorani' => [ 'ckb' ],
772 'spanish' => [ 'es_CR', 'es_MX', 'es_VE', 'es_AR', 'es_CL', 'es_GT', 'es_PE', 'es_ES', 'es_UY', 'es_CO' ],
773 'swedish' => [ 'sv_SE' ],
774 'turkish' => [ 'tr_TR' ],
775 'thai' => [ 'th' ],
776 ];
777
778 /**
779 * Languages supported in Elasticsearch snowball token filters.
780 *
781 * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-snowball-tokenfilter.html
782 */
783 $es_snowball_languages = [
784 'Armenian',
785 'Basque',
786 'Catalan',
787 'Danish',
788 'Dutch',
789 'English',
790 'Finnish',
791 'French',
792 'German',
793 'German2', // currently unused
794 'Hungarian',
795 'Italian',
796 'Kp', // currently unused
797 'Lithuanian',
798 'Lovins', // currently unused
799 'Norwegian',
800 'Porter', // currently unused
801 'Portuguese',
802 'Romanian',
803 'Russian',
804 'Spanish',
805 'Swedish',
806 'Turkish',
807 ];
808
809 foreach ( $es_languages as $analyzer_name => $analyzer_language_codes ) {
810 if ( in_array( $wp_language, $analyzer_language_codes, true ) ) {
811 $language = $analyzer_name;
812 break;
813 }
814 }
815
816 if ( 'filter_ewp_snowball' === $context ) {
817 if ( in_array( ucfirst( $language ), $es_snowball_languages, true ) ) {
818 return ucfirst( $language );
819 }
820
821 return 'English';
822 }
823
824 return $language;
825 }
826
827 /**
828 * Add column to sites admin table.
829 *
830 * @param string[] $columns Array of columns.
831 *
832 * @return string[]
833 */
834 function filter_blogs_columns( $columns ) {
835 $columns['elasticpress'] = esc_html__( 'ElasticPress Indexing', 'elasticpress' );
836
837 return $columns;
838 }
839
840 /**
841 * Populate column with checkbox/switch.
842 *
843 * @param string $column_name The name of the current column.
844 * @param int $blog_id The blog ID.
845 *
846 * @return void | string
847 */
848 function add_blogs_column( $column_name, $blog_id ) {
849 $site = get_site( $blog_id );
850 if ( $site->deleted || $site->archived || $site->spam ) {
851 return;
852 }
853 if ( 'elasticpress' === $column_name ) {
854 $is_indexable = get_blog_option( $blog_id, 'ep_indexable', 'yes' );
855
856 printf(
857 '<input %1$s class="index-toggle" data-blog-id="%2$s" disabled type="checkbox">',
858 checked( $is_indexable, 'yes', false ),
859 esc_attr( $blog_id )
860 );
861 }
862
863 return $column_name;
864 }
865
866 /**
867 * AJAX callback to update ep_indexable site option.
868 */
869 function action_wp_ajax_ep_site_admin() {
870 $blog_id = ( ! empty( $_POST['blog_id'] ) ) ? absint( wp_unslash( $_POST['blog_id'] ) ) : - 1;
871 $checked = ( ! empty( $_POST['checked'] ) ) ? sanitize_text_field( wp_unslash( $_POST['checked'] ) ) : 'no';
872
873 if ( - 1 === $blog_id || ! check_ajax_referer( 'epsa', 'nonce', false ) ) {
874 return wp_send_json_error();
875 }
876 $old = get_blog_option( $blog_id, 'ep_indexable' );
877 $result = update_blog_option( $blog_id, 'ep_indexable', $checked );
878 $data = [
879 'blog_id' => $blog_id,
880 'result' => $result,
881 ];
882
883 return wp_send_json_success( $data );
884 }
885
886 /**
887 * Registers the API endpoint
888 */
889 function setup_endpoint() {
890 register_rest_route(
891 'elasticpress/v1',
892 'indexing_status',
893 [
894 'methods' => 'GET',
895 'callback' => __NAMESPACE__ . '\handle_indexing_status',
896 'permission_callback' => '__return_true',
897 ]
898 );
899 }
900
901 /**
902 * Handle the fetch for indexing status
903 */
904 function handle_indexing_status() {
905 $indexing_status = \ElasticPress\Utils\get_indexing_status();
906
907 $status = array(
908 'method' => '',
909 'items_indexed' => 0,
910 'total_items' => 0,
911 'indexable' => '',
912 );
913
914 if ( ! empty( $indexing_status ) ) {
915 if ( isset( $indexing_status['method'] ) && 'cli' === $indexing_status['method'] ) {
916 $status['method'] = $indexing_status['method'];
917 $status['items_indexed'] = $indexing_status['items_indexed'];
918 $status['total_items'] = $indexing_status['total_items'];
919 $status['indexable'] = $indexing_status['slug'];
920 } else {
921 $status['method'] = 'dashboard';
922 $status['items_indexed'] = isset( $indexing_status['offset'] ) ? $indexing_status['offset'] : 0;
923 $status['total_items'] = isset( $indexing_status['found_items'] ) ? $indexing_status['found_items'] : 0;
924 $status['indexable'] = isset( $indexing_status['current_sync_item']['indexable'] ) ? $indexing_status['current_sync_item']['indexable'] : '';
925 }
926 }
927
928 return $status;
929 }
930