PluginProbe
ElasticPress / 4.4.1
ElasticPress v4.4.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 / dashboard.php

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

949 lines 26.6 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 } else {
207 $redirect_url = admin_url( 'admin.php?page=elasticpress' );
208 }
209 Utils\update_option( 'ep_skip_install', true );
210
211 wp_safe_redirect( $redirect_url );
212 exit;
213 }
214
215 /**
216 * Clear ES info cache whenever EP dash or settings page is viewed. Also clear cache
217 * when "try again" notification link is clicked.
218 *
219 * @since 2.3.1
220 */
221 function maybe_clear_es_info_cache() {
222 if ( ! is_admin() && ! is_network_admin() ) {
223 return;
224 }
225
226 if ( empty( $_GET['ep-retry'] ) && ! in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'settings', 'install' ], true ) ) { // phpcs:ignore WordPress.Security.NonceVerification
227 return;
228 }
229
230 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
231 delete_site_transient( 'ep_es_info' );
232 } else {
233 delete_transient( 'ep_es_info' );
234 }
235
236 if ( ! empty( $_GET['ep-retry'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
237 wp_safe_redirect( remove_query_arg( 'ep-retry' ) );
238 }
239 }
240
241 /**
242 * Show ElasticPress in network admin menu bar
243 *
244 * @param object $admin_bar WP_Admin Bar reference.
245 * @since 2.2
246 */
247 function action_network_admin_bar_menu( $admin_bar ) {
248 $admin_bar->add_menu(
249 array(
250 'id' => 'network-admin-elasticpress',
251 'parent' => 'network-admin',
252 'title' => 'ElasticPress',
253 'href' => esc_url( network_admin_url( 'admin.php?page=elasticpress' ) ),
254 )
255 );
256 }
257
258 /**
259 * Output dashboard link in plugin actions
260 *
261 * @param array $plugin_actions Array of HTML.
262 * @param string $plugin_file Path to plugin file.
263 * @since 2.1
264 * @return array
265 */
266 function filter_plugin_action_links( $plugin_actions, $plugin_file ) {
267
268 if ( is_network_admin() ) {
269 $url = admin_url( 'network/admin.php?page=elasticpress' );
270
271 if ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) {
272 return $plugin_actions;
273 }
274 } else {
275 $url = admin_url( 'admin.php?page=elasticpress' );
276
277 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
278 return $plugin_actions;
279 }
280 }
281
282 $new_actions = [];
283
284 if ( basename( EP_PATH ) . '/elasticpress.php' === $plugin_file ) {
285 $new_actions['ep_dashboard'] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), __( 'Dashboard', 'elasticpress' ) );
286 }
287
288 return array_merge( $new_actions, $plugin_actions );
289 }
290
291 /**
292 * Output variety of dashboard notices.
293 *
294 * @param bool $force Force ES info hard lookup.
295 * @since 3.0
296 */
297 function maybe_notice( $force = false ) {
298 // Admins only.
299 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
300 if ( ! is_super_admin() ) {
301 return false;
302 }
303 } else {
304 if ( ! current_user_can( 'manage_options' ) ) {
305 return false;
306 }
307 }
308
309 // If in network mode, don't output notice in admin and vice-versa.
310 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
311 if ( ! is_network_admin() ) {
312 return false;
313 }
314 } else {
315 if ( is_network_admin() ) {
316 return false;
317 }
318 }
319
320 /**
321 * Filter how long results of Elasticsearch version query are stored
322 *
323 * @since 23.0
324 * @hook ep_es_info_cache_expiration
325 * @param {int} Time in seconds
326 * @return {int} New time in seconds
327 */
328 $cache_time = apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) );
329
330 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
331 set_site_transient(
332 'logging_ep_es_info',
333 '1',
334 $cache_time
335 );
336 } else {
337 $a = set_transient(
338 'logging_ep_es_info',
339 '1',
340 $cache_time
341 );
342 }
343
344 // Fetch ES version
345 Elasticsearch::factory()->get_elasticsearch_version( $force );
346
347 AdminNotices::factory()->process_notices();
348
349 $notices = AdminNotices::factory()->get_notices();
350
351 foreach ( $notices as $notice_key => $notice ) {
352 ?>
353 <div data-ep-notice="<?php echo esc_attr( $notice_key ); ?>" class="notice notice-<?php echo esc_attr( $notice['type'] ); ?> <?php
354 if ( $notice['dismiss'] ) :
355 ?>
356 is-dismissible<?php endif; ?>">
357 <p>
358 <?php echo wp_kses( $notice['html'], 'ep-html' ); ?>
359 </p>
360 </div>
361 <?php
362 }
363
364 wp_enqueue_script( 'ep_notice_script' );
365
366 return $notices;
367 }
368
369 /**
370 * Dismiss notice via ajax
371 *
372 * @since 2.2
373 */
374 function action_wp_ajax_ep_notice_dismiss() {
375 if ( empty( $_POST['notice'] ) || ! check_ajax_referer( 'ep_admin_nonce', 'nonce', false ) ) {
376 wp_send_json_error();
377 exit;
378 }
379
380 if ( ! current_user_can( 'manage_options' ) ) {
381 wp_send_json_error();
382 exit;
383 }
384
385 AdminNotices::factory()->dismiss_notice( $_POST['notice'] );
386
387 wp_send_json_success();
388 }
389
390 /**
391 * Getting the status of ongoing index fired by WP CLI
392 *
393 * @since 2.1
394 */
395 function action_wp_ajax_ep_cli_index() {
396 _deprecated_function( __CLASS__, '3.6.0', '\ElasticPress\Screen::factory()->sync_screen->action_wp_ajax_ep_cli_index()' );
397 }
398
399 /**
400 * Continue index
401 *
402 * @since 2.1
403 */
404 function action_wp_ajax_ep_index() {
405 _deprecated_function( __CLASS__, '3.6.0', '\ElasticPress\Screen::factory()->sync_screen->action_wp_ajax_ep_index()' );
406 }
407
408 /**
409 * Cancel index
410 *
411 * @since 2.1
412 */
413 function action_wp_ajax_ep_cancel_index() {
414 _deprecated_function( __CLASS__, '3.6.0', '\ElasticPress\Screen::factory()->sync_screen->action_wp_ajax_ep_cancel_index()' );
415 }
416
417 /**
418 * Save individual feature settings
419 *
420 * @since 2.2
421 */
422 function action_wp_ajax_ep_save_feature() {
423 $_POST = wp_unslash( $_POST );
424
425 if ( empty( $_POST['feature'] ) || empty( $_POST['settings'] ) || ! check_ajax_referer( 'ep_dashboard_nonce', 'nonce', false ) ) {
426 wp_send_json_error();
427 exit;
428 }
429
430 if ( Utils\is_indexing() ) {
431 $error = new \WP_Error( 'is_indexing' );
432
433 wp_send_json_error( $error );
434 exit;
435 }
436
437 $data = Features::factory()->update_feature( $_POST['feature'], $_POST['settings'] );
438
439 // Since we deactivated, delete auto activate notice.
440 if ( empty( $_POST['settings']['active'] ) ) {
441 Utils\delete_option( 'ep_feature_auto_activated_sync' );
442 }
443
444 wp_send_json_success( $data );
445 }
446
447 /**
448 * Register and Enqueue JavaScripts for dashboard
449 *
450 * @since 2.2
451 */
452 function action_admin_enqueue_dashboard_scripts() {
453 if ( isset( get_current_screen()->id ) && strpos( get_current_screen()->id, 'sites-network' ) !== false ) {
454 wp_enqueue_style( 'wp-components' );
455
456 wp_enqueue_script(
457 'ep_admin_sites_scripts',
458 EP_URL . 'dist/js/sites-admin-script.js',
459 Utils\get_asset_info( 'sites-admin-script', 'dependencies' ),
460 Utils\get_asset_info( 'sites-admin-script', 'version' ),
461 true
462 );
463
464 wp_set_script_translations( 'ep_admin_sites_scripts', 'elasticpress' );
465
466 $data = [
467 'ajax_url' => admin_url( 'admin-ajax.php' ),
468 'nonce' => wp_create_nonce( 'epsa' ),
469 ];
470
471 wp_localize_script( 'ep_admin_sites_scripts', 'epsa', $data );
472 }
473
474 if ( in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'settings', 'install', 'health', 'weighting', 'synonyms', 'sync', 'status-report' ], true ) ) {
475 wp_enqueue_style(
476 'ep_admin_styles',
477 EP_URL . 'dist/css/dashboard-styles.css',
478 Utils\get_asset_info( 'dashboard-styles', 'dependencies' ),
479 Utils\get_asset_info( 'dashboard-styles', 'version' )
480 );
481 wp_enqueue_script(
482 'ep_admin_script',
483 EP_URL . 'dist/js/admin-script.js',
484 Utils\get_asset_info( 'admin-script', 'dependencies' ),
485 Utils\get_asset_info( 'admin-script', 'version' ),
486 true
487 );
488
489 wp_set_script_translations( 'ep_admin_script', 'elasticpress' );
490 }
491
492 if ( in_array( Screen::factory()->get_current_screen(), [ 'weighting', 'install' ], true ) ) {
493 wp_enqueue_script(
494 'ep_weighting_script',
495 EP_URL . 'dist/js/weighting-script.js',
496 Utils\get_asset_info( 'weighting-script', 'dependencies' ),
497 Utils\get_asset_info( 'weighting-script', 'version' ),
498 true
499 );
500
501 wp_set_script_translations( 'ep_weighting_script', 'elasticpress' );
502 }
503
504 if ( in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'install' ], true ) ) {
505 wp_enqueue_script(
506 'ep_dashboard_scripts',
507 EP_URL . 'dist/js/dashboard-script.js',
508 Utils\get_asset_info( 'dashboard-script', 'dependencies' ),
509 Utils\get_asset_info( 'dashboard-script', 'version' ),
510 true
511 );
512
513 wp_set_script_translations( 'ep_dashboard_scripts', 'elasticpress' );
514
515 $sync_url = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ?
516 network_admin_url( 'admin.php?page=elasticpress-sync&do_sync' ) :
517 admin_url( 'admin.php?page=elasticpress-sync&do_sync' );
518
519 $skip_url = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ?
520 network_admin_url( 'admin.php?page=elasticpress' ) :
521 admin_url( 'admin.php?page=elasticpress' );
522
523 $data = array(
524 'skipUrl' => add_query_arg(
525 array(
526 'ep-skip-install' => 1,
527 'ep-skip-features' => 1,
528 'nonce' => wp_create_nonce( 'ep-skip-install' ),
529 ),
530 $skip_url
531 ),
532 'syncUrl' => $sync_url,
533 );
534
535 wp_localize_script( 'ep_dashboard_scripts', 'epDash', $data );
536 }
537
538 if ( in_array( Screen::factory()->get_current_screen(), [ 'settings' ], true ) ) {
539 wp_enqueue_script(
540 'ep_settings_scripts',
541 EP_URL . 'dist/js/settings-script.js',
542 Utils\get_asset_info( 'settings-script', 'dependencies' ),
543 Utils\get_asset_info( 'settings-script', 'version' ),
544 true
545 );
546
547 wp_set_script_translations( 'ep_settings_scripts', 'elasticpress' );
548 }
549
550 if ( in_array( Screen::factory()->get_current_screen(), [ 'health' ], true ) && ! empty( Utils\get_host() ) ) {
551 Stats::factory()->build_stats();
552
553 $data = Stats::factory()->get_localized();
554
555 wp_enqueue_script(
556 'ep_stats',
557 EP_URL . 'dist/js/stats-script.js',
558 Utils\get_asset_info( 'stats-script', 'dependencies' ),
559 Utils\get_asset_info( 'stats-script', 'version' ),
560 true
561 );
562
563 wp_set_script_translations( 'ep_stats', 'elasticpress' );
564
565 wp_localize_script( 'ep_stats', 'epChartData', $data );
566 }
567
568 wp_register_script(
569 'ep_notice_script',
570 EP_URL . 'dist/js/notice-script.js',
571 Utils\get_asset_info( 'notice-script', 'dependencies' ),
572 Utils\get_asset_info( 'notice-script', 'version' ),
573 true
574 );
575
576 wp_set_script_translations( 'ep_notice_script', 'elasticpress' );
577
578 wp_localize_script(
579 'ep_notice_script',
580 'epAdmin',
581 array(
582 'nonce' => wp_create_nonce( 'ep_admin_nonce' ),
583 )
584 );
585 }
586
587 /**
588 * Admin-init actions
589 *
590 * Sets up Settings API.
591 *
592 * @since 1.9
593 * @return void
594 */
595 function action_admin_init() {
596
597 // Save options for multisite.
598 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK && isset( $_POST['ep_language'] ) ) {
599 check_admin_referer( 'elasticpress-options' );
600
601 $language = sanitize_text_field( $_POST['ep_language'] );
602 Utils\update_option( 'ep_language', $language );
603
604 if ( isset( $_POST['ep_host'] ) ) {
605 $host = esc_url_raw( trim( $_POST['ep_host'] ) );
606 Utils\update_option( 'ep_host', $host );
607 }
608
609 if ( isset( $_POST['ep_credentials'] ) ) {
610 $credentials = ( isset( $_POST['ep_credentials'] ) ) ? Utils\sanitize_credentials( $_POST['ep_credentials'] ) : [
611 'username' => '',
612 'token' => '',
613 ];
614
615 Utils\update_option( 'ep_credentials', $credentials );
616 }
617
618 if ( isset( $_POST['ep_bulk_setting'] ) ) {
619 Utils\update_option( 'ep_bulk_setting', intval( $_POST['ep_bulk_setting'] ) );
620 }
621 } else {
622 register_setting( 'elasticpress', 'ep_host', 'esc_url_raw' );
623 register_setting( 'elasticpress', 'ep_credentials', 'ep_sanitize_credentials' );
624 register_setting( 'elasticpress', 'ep_language', 'sanitize_text_field' );
625 register_setting(
626 'elasticpress',
627 'ep_bulk_setting',
628 [
629 'type' => 'integer',
630 'sanitize_callback' => __NAMESPACE__ . '\sanitize_bulk_settings',
631 ]
632 );
633 }
634 }
635
636 /**
637 * Sanitize bulk settings.
638 *
639 * @param int $bulk_settings Number of bulk content items
640 * @return int
641 */
642 function sanitize_bulk_settings( $bulk_settings = 350 ) {
643 $bulk_settings = absint( $bulk_settings );
644
645 return ( 0 === $bulk_settings ) ? 350 : $bulk_settings;
646 }
647
648 /**
649 * Output current ElasticPress dashboard screen
650 *
651 * @since 3.0
652 */
653 function resolve_screen() {
654 Screen::factory()->output();
655 }
656
657 /**
658 * Admin menu actions
659 *
660 * Adds options page to admin menu.
661 *
662 * @since 1.9
663 * @return void
664 */
665 function action_admin_menu() {
666 $capability = 'manage_options';
667
668 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
669 $capability = 'manage_network';
670 }
671
672 add_menu_page(
673 'ElasticPress',
674 'ElasticPress',
675 $capability,
676 'elasticpress',
677 __NAMESPACE__ . '\resolve_screen',
678 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNzMgNzEuMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNzMgNzEuMzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGQ9Ik0zNi41LDQuN0MxOS40LDQuNyw1LjYsMTguNiw1LjYsMzUuN2MwLDEwLDQuNywxOC45LDEyLjEsMjQuNWw0LjUtNC41YzAuMS0wLjEsMC4xLTAuMiwwLjItMC4zbDAuNy0wLjdsNi40LTYuNGMyLjEsMS4yLDQuNSwxLjksNy4xLDEuOWM4LDAsMTQuNS02LjUsMTQuNS0xNC41cy02LjUtMTQuNS0xNC41LTE0LjVTMjIsMjcuNiwyMiwzNS42YzAsMi44LDAuOCw1LjMsMi4xLDcuNWwtNi40LDYuNGMtMi45LTMuOS00LjYtOC43LTQuNi0xMy45YzAtMTIuOSwxMC41LTIzLjQsMjMuNC0yMy40czIzLjQsMTAuNSwyMy40LDIzLjRTNDkuNCw1OSwzNi41LDU5Yy0yLjEsMC00LjEtMC4zLTYtMC44bC0wLjYsMC42bC01LjIsNS40YzMuNiwxLjUsNy42LDIuMywxMS44LDIuM2MxNy4xLDAsMzAuOS0xMy45LDMwLjktMzAuOVM1My42LDQuNywzNi41LDQuN3oiLz48L3N2Zz4='
679 );
680
681 add_submenu_page(
682 'elasticpress',
683 esc_html__( 'ElasticPress Features', 'elasticpress' ),
684 esc_html__( 'Features', 'elasticpress' ),
685 $capability,
686 'elasticpress',
687 __NAMESPACE__ . '\resolve_screen'
688 );
689
690 add_submenu_page(
691 'elasticpress',
692 esc_html__( 'ElasticPress Settings', 'elasticpress' ),
693 esc_html__( 'Settings', 'elasticpress' ),
694 $capability,
695 'elasticpress-settings',
696 __NAMESPACE__ . '\resolve_screen'
697 );
698
699 add_submenu_page(
700 'elasticpress',
701 'ElasticPress ' . esc_html__( 'Sync', 'elasticpress' ),
702 esc_html__( 'Sync', 'elasticpress' ),
703 $capability,
704 'elasticpress-sync',
705 __NAMESPACE__ . '\resolve_screen'
706 );
707
708 add_submenu_page(
709 'elasticpress',
710 esc_html__( 'ElasticPress Index Health', 'elasticpress' ),
711 esc_html__( 'Index Health', 'elasticpress' ),
712 $capability,
713 'elasticpress-health',
714 __NAMESPACE__ . '\resolve_screen'
715 );
716
717 add_submenu_page(
718 'elasticpress',
719 esc_html__( 'ElasticPress Status Report', 'elasticpress' ),
720 esc_html__( 'Status Report', 'elasticpress' ),
721 $capability,
722 'elasticpress-status-report',
723 __NAMESPACE__ . '\resolve_screen'
724 );
725 }
726
727 /**
728 * Uses the language from EP settings in mapping.
729 *
730 * @param string $language The current language.
731 * @param string $context The context where the function is running.
732 * @return string The updated language.
733 */
734 function use_language_in_setting( $language = 'english', $context = '' ) {
735 // Get the currently set language.
736 $ep_language = Utils\get_language();
737
738 // Bail early if no EP language is set.
739 if ( empty( $ep_language ) ) {
740 return $language;
741 }
742
743 require_once ABSPATH . 'wp-admin/includes/translation-install.php';
744 $translations = wp_get_available_translations();
745
746 // Bail early if not in the array of available translations.
747 if ( empty( $translations[ $ep_language ]['english_name'] ) ) {
748 return $language;
749 }
750
751 $wp_language = $translations[ $ep_language ]['language'];
752
753 /**
754 * Languages supported in Elasticsearch mappings.
755 * Array format: Elasticsearch analyzer name => WordPress language package name
756 *
757 * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lang-analyzer.html
758 */
759 $es_languages = [
760 'arabic' => [ 'ar', 'ary' ],
761 'armenian' => [ 'hy' ],
762 'basque' => [ 'eu' ],
763 'bengali' => [ 'bn', 'bn_BD' ],
764 'brazilian' => [ 'pt_BR' ],
765 'bulgarian' => [ 'bg' ],
766 'catalan' => [ 'ca' ],
767 'cjk' => [], // CJK characters (not a language)
768 'czech' => [ 'cs' ],
769 'danish' => [ 'da' ],
770 'dutch' => [ 'nl_NL_formal', 'nl_NL', 'nl_BE' ],
771 'english' => [ 'en', 'en_AU', 'en_GB', 'en_NZ', 'en_CA', 'en_ZA' ],
772 'estonian' => [ 'et' ],
773 'finnish' => [ 'fi' ],
774 'french' => [ 'fr', 'fr_CA', 'fr_FR', 'fr_BE' ],
775 'galician' => [ 'gl_ES' ],
776 'german' => [ 'de', 'de_DE', 'de_DE_formal', 'de_CH', 'de_CH_informal', 'de_AT' ],
777 'greek' => [ 'el' ],
778 'hindi' => [ 'hi_IN' ],
779 'hungarian' => [ 'hu_HU' ],
780 'indonesian' => [ 'id_ID' ],
781 'irish' => [], // WordPress doesn't support Irish as an active locale currently
782 'italian' => [ 'it_IT' ],
783 'latvian' => [ 'lv' ],
784 'lithuanian' => [ 'lt_LT' ],
785 'norwegian' => [ 'nb_NO' ],
786 'persian' => [ 'fa_IR' ],
787 'portuguese' => [ 'pt', 'pt_AO', 'pt_PT', 'pt_PT_ao90' ],
788 'romanian' => [ 'ro_RO' ],
789 'russian' => [ 'ru_RU' ],
790 'sorani' => [ 'ckb' ],
791 'spanish' => [ 'es_CR', 'es_MX', 'es_VE', 'es_AR', 'es_CL', 'es_GT', 'es_PE', 'es_ES', 'es_UY', 'es_CO' ],
792 'swedish' => [ 'sv_SE' ],
793 'turkish' => [ 'tr_TR' ],
794 'thai' => [ 'th' ],
795 ];
796
797 /**
798 * Languages supported in Elasticsearch snowball token filters.
799 *
800 * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-snowball-tokenfilter.html
801 */
802 $es_snowball_languages = [
803 'Armenian',
804 'Basque',
805 'Catalan',
806 'Danish',
807 'Dutch',
808 'English',
809 'Finnish',
810 'French',
811 'German',
812 'German2', // currently unused
813 'Hungarian',
814 'Italian',
815 'Kp', // currently unused
816 'Lithuanian',
817 'Lovins', // currently unused
818 'Norwegian',
819 'Porter', // currently unused
820 'Portuguese',
821 'Romanian',
822 'Russian',
823 'Spanish',
824 'Swedish',
825 'Turkish',
826 ];
827
828 foreach ( $es_languages as $analyzer_name => $analyzer_language_codes ) {
829 if ( in_array( $wp_language, $analyzer_language_codes, true ) ) {
830 $language = $analyzer_name;
831 break;
832 }
833 }
834
835 if ( 'filter_ewp_snowball' === $context ) {
836 if ( in_array( ucfirst( $language ), $es_snowball_languages, true ) ) {
837 return ucfirst( $language );
838 }
839
840 return 'English';
841 }
842
843 return $language;
844 }
845
846 /**
847 * Add column to sites admin table.
848 *
849 * @param string[] $columns Array of columns.
850 *
851 * @return string[]
852 */
853 function filter_blogs_columns( $columns ) {
854 $columns['elasticpress'] = esc_html__( 'ElasticPress Indexing', 'elasticpress' );
855
856 return $columns;
857 }
858
859 /**
860 * Populate column with checkbox/switch.
861 *
862 * @param string $column_name The name of the current column.
863 * @param int $blog_id The blog ID.
864 *
865 * @return void | string
866 */
867 function add_blogs_column( $column_name, $blog_id ) {
868 $site = get_site( $blog_id );
869 if ( $site->deleted || $site->archived || $site->spam ) {
870 return;
871 }
872 if ( 'elasticpress' === $column_name ) {
873 $is_indexable = get_blog_option( $blog_id, 'ep_indexable', 'yes' );
874
875 printf(
876 '<input %1$s class="index-toggle" data-blog-id="%2$s" disabled type="checkbox">',
877 checked( $is_indexable, 'yes', false ),
878 esc_attr( $blog_id )
879 );
880 }
881
882 return $column_name;
883 }
884
885 /**
886 * AJAX callback to update ep_indexable site option.
887 */
888 function action_wp_ajax_ep_site_admin() {
889 $blog_id = ( ! empty( $_POST['blog_id'] ) ) ? absint( wp_unslash( $_POST['blog_id'] ) ) : - 1;
890 $checked = ( ! empty( $_POST['checked'] ) ) ? sanitize_text_field( wp_unslash( $_POST['checked'] ) ) : 'no';
891
892 if ( - 1 === $blog_id || ! check_ajax_referer( 'epsa', 'nonce', false ) ) {
893 return wp_send_json_error();
894 }
895 $old = get_blog_option( $blog_id, 'ep_indexable' );
896 $result = update_blog_option( $blog_id, 'ep_indexable', $checked );
897 $data = [
898 'blog_id' => $blog_id,
899 'result' => $result,
900 ];
901
902 return wp_send_json_success( $data );
903 }
904
905 /**
906 * Registers the API endpoint
907 */
908 function setup_endpoint() {
909 register_rest_route(
910 'elasticpress/v1',
911 'indexing_status',
912 [
913 'methods' => 'GET',
914 'callback' => __NAMESPACE__ . '\handle_indexing_status',
915 'permission_callback' => '__return_true',
916 ]
917 );
918 }
919
920 /**
921 * Handle the fetch for indexing status
922 */
923 function handle_indexing_status() {
924 $indexing_status = \ElasticPress\Utils\get_indexing_status();
925
926 $status = array(
927 'method' => '',
928 'items_indexed' => 0,
929 'total_items' => 0,
930 'indexable' => '',
931 );
932
933 if ( ! empty( $indexing_status ) ) {
934 if ( isset( $indexing_status['method'] ) && 'cli' === $indexing_status['method'] ) {
935 $status['method'] = $indexing_status['method'];
936 $status['items_indexed'] = $indexing_status['items_indexed'];
937 $status['total_items'] = $indexing_status['total_items'];
938 $status['indexable'] = $indexing_status['slug'];
939 } else {
940 $status['method'] = 'dashboard';
941 $status['items_indexed'] = isset( $indexing_status['offset'] ) ? $indexing_status['offset'] : 0;
942 $status['total_items'] = isset( $indexing_status['found_items'] ) ? $indexing_status['found_items'] : 0;
943 $status['indexable'] = isset( $indexing_status['current_sync_item']['indexable'] ) ? $indexing_status['current_sync_item']['indexable'] : '';
944 }
945 }
946
947 return $status;
948 }
949