PluginProbe ʕ •ᴥ•ʔ
LiteSpeed Cache / 7.6.1
LiteSpeed Cache v7.6.1
trunk 1.0.15 1.9.1.1 2.9.9.2 3.6.4 4.6 5.7.0.1 6.5.4 7.0.0.1 7.0.1 7.1 7.2 7.3 7.3.0.1 7.4 7.5 7.5.0.1 7.6 7.6.1 7.6.2 7.7 7.8 7.8.0.1 7.8.1
litespeed-cache / src / admin-display.cls.php
litespeed-cache / src Last commit date
cdn 7 months ago data_structure 7 months ago activation.cls.php 7 months ago admin-display.cls.php 7 months ago admin-settings.cls.php 7 months ago admin.cls.php 7 months ago api.cls.php 7 months ago avatar.cls.php 7 months ago base.cls.php 7 months ago cdn.cls.php 7 months ago cloud.cls.php 7 months ago conf.cls.php 7 months ago control.cls.php 7 months ago core.cls.php 7 months ago crawler-map.cls.php 7 months ago crawler.cls.php 7 months ago css.cls.php 7 months ago data.cls.php 7 months ago data.upgrade.func.php 7 months ago db-optm.cls.php 7 months ago debug2.cls.php 7 months ago doc.cls.php 7 months ago error.cls.php 7 months ago esi.cls.php 7 months ago file.cls.php 7 months ago gui.cls.php 7 months ago health.cls.php 7 months ago htaccess.cls.php 7 months ago img-optm.cls.php 7 months ago import.cls.php 7 months ago import.preset.cls.php 7 months ago lang.cls.php 7 months ago localization.cls.php 7 months ago media.cls.php 7 months ago metabox.cls.php 7 months ago object-cache-wp.cls.php 7 months ago object-cache.cls.php 7 months ago object.lib.php 7 months ago optimize.cls.php 7 months ago optimizer.cls.php 7 months ago placeholder.cls.php 7 months ago purge.cls.php 7 months ago report.cls.php 7 months ago rest.cls.php 7 months ago root.cls.php 7 months ago router.cls.php 7 months ago str.cls.php 7 months ago tag.cls.php 7 months ago task.cls.php 7 months ago tool.cls.php 7 months ago ucss.cls.php 7 months ago utility.cls.php 7 months ago vary.cls.php 7 months ago vpi.cls.php 7 months ago
admin-display.cls.php
1659 lines
1 <?php
2 /**
3 * The admin-panel specific functionality of the plugin.
4 *
5 * Provides admin page rendering, notices, enqueueing of assets,
6 * menu registrations, and various admin utilities.
7 *
8 * @since 1.0.0
9 * @package LiteSpeed
10 * @subpackage LiteSpeed/admin
11 * @author LiteSpeed Technologies <info@litespeedtech.com>
12 */
13
14 namespace LiteSpeed;
15
16 defined( 'WPINC' ) || exit();
17
18 /**
19 * Class Admin_Display
20 *
21 * Handles WP-Admin UI for LiteSpeed Cache.
22 */
23 class Admin_Display extends Base {
24
25 /**
26 * Log tag for Admin_Display.
27 *
28 * @var string
29 */
30 const LOG_TAG = '👮‍♀️';
31
32 /**
33 * Notice class (info/blue).
34 *
35 * @var string
36 */
37 const NOTICE_BLUE = 'notice notice-info';
38 /**
39 * Notice class (success/green).
40 *
41 * @var string
42 */
43 const NOTICE_GREEN = 'notice notice-success';
44 /**
45 * Notice class (error/red).
46 *
47 * @var string
48 */
49 const NOTICE_RED = 'notice notice-error';
50 /**
51 * Notice class (warning/yellow).
52 *
53 * @var string
54 */
55 const NOTICE_YELLOW = 'notice notice-warning';
56 /**
57 * Option key for one-time messages.
58 *
59 * @var string
60 */
61 const DB_MSG = 'messages';
62 /**
63 * Option key for pinned messages.
64 *
65 * @var string
66 */
67 const DB_MSG_PIN = 'msg_pin';
68
69 /**
70 * Purge by: category.
71 *
72 * @var string
73 */
74 const PURGEBY_CAT = '0';
75 /**
76 * Purge by: post ID.
77 *
78 * @var string
79 */
80 const PURGEBY_PID = '1';
81 /**
82 * Purge by: tag.
83 *
84 * @var string
85 */
86 const PURGEBY_TAG = '2';
87 /**
88 * Purge by: URL.
89 *
90 * @var string
91 */
92 const PURGEBY_URL = '3';
93
94 /**
95 * Purge selection field name.
96 *
97 * @var string
98 */
99 const PURGEBYOPT_SELECT = 'purgeby';
100 /**
101 * Purge list field name.
102 *
103 * @var string
104 */
105 const PURGEBYOPT_LIST = 'purgebylist';
106
107 /**
108 * Dismiss key for messages.
109 *
110 * @var string
111 */
112 const DB_DISMISS_MSG = 'dismiss';
113 /**
114 * Rule conflict flag (on).
115 *
116 * @var string
117 */
118 const RULECONFLICT_ON = 'ExpiresDefault_1';
119 /**
120 * Rule conflict dismissed flag.
121 *
122 * @var string
123 */
124 const RULECONFLICT_DISMISSED = 'ExpiresDefault_0';
125
126 /**
127 * Router type for QC hide banner.
128 *
129 * @var string
130 */
131 const TYPE_QC_HIDE_BANNER = 'qc_hide_banner';
132 /**
133 * Cookie name for QC hide banner.
134 *
135 * @var string
136 */
137 const COOKIE_QC_HIDE_BANNER = 'litespeed_qc_hide_banner';
138
139 /**
140 * Internal messages cache.
141 *
142 * @var array<string,string>
143 */
144 protected $messages = array();
145
146 /**
147 * Cached default settings.
148 *
149 * @var array<string,mixed>
150 */
151 protected $default_settings = array();
152
153 /**
154 * Whether current context is network admin.
155 *
156 * @var bool
157 */
158 protected $_is_network_admin = false;
159
160 /**
161 * Whether multisite is enabled.
162 *
163 * @var bool
164 */
165 protected $_is_multisite = false;
166
167 /**
168 * Incremental form submit button index.
169 *
170 * @var int
171 */
172 private $_btn_i = 0;
173
174 /**
175 * List of settings with filters and return type.
176 *
177 * @since 7.4
178 *
179 * @var array<string,array<string,mixed>>
180 */
181 protected static $settings_filters = [
182 // Crawler - Blocklist.
183 'crawler-blocklist' => [
184 'filter' => 'litespeed_crawler_disable_blocklist',
185 'type' => 'boolean',
186 ],
187 // Crawler - Settings.
188 self::O_CRAWLER_LOAD_LIMIT => [
189 'filter' => [ Base::ENV_CRAWLER_LOAD_LIMIT_ENFORCE, Base::ENV_CRAWLER_LOAD_LIMIT ],
190 'type' => 'input',
191 ],
192 // Cache - ESI.
193 self::O_ESI_NONCE => [
194 'filter' => 'litespeed_esi_nonces',
195 ],
196 // Page Optimization - CSS.
197 'optm-ucss_per_pagetype' => [
198 'filter' => 'litespeed_ucss_per_pagetype',
199 'type' => 'boolean',
200 ],
201 // Page Optimization - Media.
202 self::O_MEDIA_ADD_MISSING_SIZES => [
203 'filter' => 'litespeed_media_ignore_remote_missing_sizes',
204 'type' => 'boolean',
205 ],
206 // Page Optimization - Media Exclude.
207 self::O_MEDIA_LAZY_EXC => [
208 'filter' => 'litespeed_media_lazy_img_excludes',
209 ],
210 // Page Optimization - Tuning (JS).
211 self::O_OPTM_JS_DELAY_INC => [
212 'filter' => 'litespeed_optm_js_delay_inc',
213 ],
214 self::O_OPTM_JS_EXC => [
215 'filter' => 'litespeed_optimize_js_excludes',
216 ],
217 self::O_OPTM_JS_DEFER_EXC => [
218 'filter' => 'litespeed_optm_js_defer_exc',
219 ],
220 self::O_OPTM_GM_JS_EXC => [
221 'filter' => 'litespeed_optm_gm_js_exc',
222 ],
223 self::O_OPTM_EXC => [
224 'filter' => 'litespeed_optm_uri_exc',
225 ],
226 // Page Optimization - Tuning (CSS).
227 self::O_OPTM_CSS_EXC => [
228 'filter' => 'litespeed_optimize_css_excludes',
229 ],
230 self::O_OPTM_UCSS_EXC => [
231 'filter' => 'litespeed_ucss_exc',
232 ],
233 ];
234
235 /**
236 * Flat pages map: menu slug to template metadata.
237 *
238 * @var array<string,array{title:string,tpl:string,network?:bool}>
239 */
240 private $_pages = [];
241
242 /**
243 * Initialize the class and set its properties.
244 *
245 * @since 1.0.7
246 */
247 public function __construct() {
248 $this->_pages = [
249 // Site-level pages
250 'litespeed' => [ 'title' => __( 'Dashboard', 'litespeed-cache' ), 'tpl' => 'dash/entry.tpl.php' ],
251 'litespeed-optimax' => [ 'title' => __( 'OptimaX', 'litespeed-cache' ), 'tpl' => 'optimax/entry.tpl.php', 'scope' => 'site' ],
252 'litespeed-presets' => [ 'title' => __( 'Presets', 'litespeed-cache' ), 'tpl' => 'presets/entry.tpl.php', 'scope' => 'site' ],
253 'litespeed-general' => [ 'title' => __( 'General', 'litespeed-cache' ), 'tpl' => 'general/entry.tpl.php' ],
254 'litespeed-cache' => [ 'title' => __( 'Cache', 'litespeed-cache' ), 'tpl' => 'cache/entry.tpl.php' ],
255 'litespeed-cdn' => [ 'title' => __( 'CDN', 'litespeed-cache' ), 'tpl' => 'cdn/entry.tpl.php', 'scope' => 'site' ],
256 'litespeed-img_optm' => [ 'title' => __( 'Image Optimization', 'litespeed-cache'), 'tpl' => 'img_optm/entry.tpl.php' ],
257 'litespeed-page_optm' => [ 'title' => __( 'Page Optimization', 'litespeed-cache' ), 'tpl' => 'page_optm/entry.tpl.php', 'scope' => 'site' ],
258 'litespeed-db_optm' => [ 'title' => __( 'Database', 'litespeed-cache' ), 'tpl' => 'db_optm/entry.tpl.php' ],
259 'litespeed-crawler' => [ 'title' => __( 'Crawler', 'litespeed-cache' ), 'tpl' => 'crawler/entry.tpl.php', 'scope' => 'site' ],
260 'litespeed-toolbox' => [ 'title' => __( 'Toolbox', 'litespeed-cache' ), 'tpl' => 'toolbox/entry.tpl.php' ],
261 ];
262
263 // main css
264 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_style' ) );
265 // Main js
266 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
267
268 $this->_is_network_admin = is_network_admin();
269 $this->_is_multisite = is_multisite();
270
271 // Quick access menu
272 $manage = ( $this->_is_multisite && $this->_is_network_admin ) ? 'manage_network_options' : 'manage_options';
273
274 if ( current_user_can( $manage ) ) {
275 add_action( 'wp_before_admin_bar_render', array( GUI::cls(), 'backend_shortcut' ) );
276
277 // `admin_notices` is after `admin_enqueue_scripts`.
278 add_action( $this->_is_network_admin ? 'network_admin_notices' : 'admin_notices', array( $this, 'display_messages' ) );
279 }
280
281 /**
282 * In case this is called outside the admin page.
283 *
284 * @see https://codex.wordpress.org/Function_Reference/is_plugin_active_for_network
285 * @since 2.0
286 */
287 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
288 require_once ABSPATH . 'wp-admin/includes/plugin.php';
289 }
290
291 // add menus (Also check for mu-plugins)
292 if ( $this->_is_network_admin && ( is_plugin_active_for_network( LSCWP_BASENAME ) || defined( 'LSCWP_MU_PLUGIN' ) ) ) {
293 add_action( 'network_admin_menu', array( $this, 'register_admin_menu' ) );
294 } else {
295 add_action( 'admin_menu', array( $this, 'register_admin_menu' ) );
296 }
297
298 $this->cls( 'Metabox' )->register_settings();
299 }
300
301 /**
302 * Echo a translated section title.
303 *
304 * @since 3.0
305 *
306 * @param string $id Language key.
307 * @return void
308 */
309 public function title( $id ) {
310 echo wp_kses_post( Lang::title( $id ) );
311 }
312
313 /**
314 * Bind per-page admin hooks for a given page hook.
315 *
316 * Adds footer text filter and preview banner when loading the page.
317 *
318 * @param string $hook Page hook suffix returned by add_*_page().
319 * @return void
320 */
321 private function bind_page( $hook ) {
322 add_action( "load-$hook", function () {
323 add_filter(
324 'admin_footer_text',
325 function ( $footer_text ) {
326 $this->cls( 'Cloud' )->maybe_preview_banner();
327 require_once LSCWP_DIR . 'tpl/inc/admin_footer.php';
328 return $footer_text;
329 },
330 1
331 );
332 } );
333 }
334
335 /**
336 * Render an admin page by slug using its mapped template file.
337 *
338 * @param string $slug The menu slug registered in $_pages.
339 * @return void
340 */
341 private function render_page( $slug ) {
342 $tpl = LSCWP_DIR . 'tpl/' . $this->_pages[ $slug ]['tpl'];
343 is_file( $tpl ) ? require $tpl : wp_die( 'Template not found' );
344 }
345
346 /**
347 * Register the admin menu display.
348 *
349 * @since 1.0.0
350 * @return void
351 */
352 public function register_admin_menu() {
353 $capability = $this->_is_network_admin ? 'manage_network_options' : 'manage_options';
354 $scope = $this->_is_network_admin ? 'network' : 'site';
355
356 add_menu_page(
357 'LiteSpeed Cache',
358 'LiteSpeed Cache',
359 $capability,
360 'litespeed'
361 );
362
363 foreach ( $this->_pages as $slug => $meta ) {
364 if ( 'litespeed-optimax' === $slug && !defined( 'LITESPEED_OX' ) ) {
365 continue;
366 }
367 if ( ! empty( $meta['scope'] ) && $meta['scope'] !== $scope ) {
368 continue;
369 }
370 $hook = add_submenu_page(
371 'litespeed',
372 $meta['title'],
373 $meta['title'],
374 $capability,
375 $slug,
376 function () use ( $slug ) {
377 $this->render_page( $slug );
378 }
379 );
380 $this->bind_page( $hook );
381 }
382
383 // sub menus under options.
384 $hook = add_options_page(
385 'LiteSpeed Cache',
386 'LiteSpeed Cache',
387 $capability,
388 'litespeed-cache-options',
389 function () {
390 $this->render_page( 'litespeed-cache' );
391 }
392 );
393 $this->bind_page( $hook );
394 }
395
396 /**
397 * Register the stylesheets for the admin area.
398 *
399 * @since 1.0.14
400 * @return void
401 */
402 public function enqueue_style() {
403 wp_enqueue_style( Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/css/litespeed.css', array(), Core::VER, 'all' );
404 wp_enqueue_style( Core::PLUGIN_NAME . '-dark-mode', LSWCP_PLUGIN_URL . 'assets/css/litespeed-dark-mode.css', array(), Core::VER, 'all' );
405 }
406
407 /**
408 * Register/enqueue the JavaScript for the admin area.
409 *
410 * @since 1.0.0
411 * @since 7.3 Added deactivation modal code.
412 * @return void
413 */
414 public function enqueue_scripts() {
415 wp_register_script( Core::PLUGIN_NAME, LSWCP_PLUGIN_URL . 'assets/js/litespeed-cache-admin.js', array(), Core::VER, false );
416
417 $localize_data = array();
418 if ( GUI::has_whm_msg() ) {
419 $ajax_url_dismiss_whm = Utility::build_url( Core::ACTION_DISMISS, GUI::TYPE_DISMISS_WHM, true );
420 $localize_data['ajax_url_dismiss_whm'] = $ajax_url_dismiss_whm;
421 }
422
423 if ( GUI::has_msg_ruleconflict() ) {
424 $ajax_url = Utility::build_url( Core::ACTION_DISMISS, GUI::TYPE_DISMISS_EXPIRESDEFAULT, true );
425 $localize_data['ajax_url_dismiss_ruleconflict'] = $ajax_url;
426 }
427
428 // Injection to LiteSpeed pages
429 global $pagenow;
430 $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
431
432 if ( 'admin.php' === $pagenow && $page && ( 0 === strpos( $page, 'litespeed-' ) || 'litespeed' === $page ) ) {
433 if ( in_array( $page, array( 'litespeed-crawler', 'litespeed-cdn' ), true ) ) {
434 // Babel JS type correction
435 add_filter( 'script_loader_tag', array( $this, 'babel_type' ), 10, 3 );
436
437 wp_enqueue_script( Core::PLUGIN_NAME . '-lib-react', LSWCP_PLUGIN_URL . 'assets/js/react.min.js', array(), Core::VER, false );
438 wp_enqueue_script( Core::PLUGIN_NAME . '-lib-babel', LSWCP_PLUGIN_URL . 'assets/js/babel.min.js', array(), Core::VER, false );
439 }
440
441 // Crawler Cookie Simulation
442 if ( 'litespeed-crawler' === $page ) {
443 wp_enqueue_script( Core::PLUGIN_NAME . '-crawler', LSWCP_PLUGIN_URL . 'assets/js/component.crawler.js', array(), Core::VER, false );
444
445 $localize_data['lang'] = array();
446 $localize_data['lang']['cookie_name'] = __( 'Cookie Name', 'litespeed-cache' );
447 $localize_data['lang']['cookie_value'] = __( 'Cookie Values', 'litespeed-cache' );
448 $localize_data['lang']['one_per_line'] = Doc::one_per_line( true );
449 $localize_data['lang']['remove_cookie_simulation'] = __( 'Remove cookie simulation', 'litespeed-cache' );
450 $localize_data['lang']['add_cookie_simulation_row'] = __( 'Add new cookie to simulate', 'litespeed-cache' );
451 if ( empty( $localize_data['ids'] ) ) {
452 $localize_data['ids'] = array();
453 }
454 $localize_data['ids']['crawler_cookies'] = self::O_CRAWLER_COOKIES;
455 }
456
457 // CDN mapping
458 if ( 'litespeed-cdn' === $page ) {
459 $home_url = home_url( '/' );
460 $parsed = wp_parse_url( $home_url );
461 if ( ! empty( $parsed['scheme'] ) ) {
462 $home_url = str_replace( $parsed['scheme'] . ':', '', $home_url );
463 }
464 $cdn_url = 'https://cdn.' . substr( $home_url, 2 );
465
466 wp_enqueue_script( Core::PLUGIN_NAME . '-cdn', LSWCP_PLUGIN_URL . 'assets/js/component.cdn.js', array(), Core::VER, false );
467 $localize_data['lang'] = array();
468 $localize_data['lang']['cdn_mapping_url'] = Lang::title( self::CDN_MAPPING_URL );
469 $localize_data['lang']['cdn_mapping_inc_img'] = Lang::title( self::CDN_MAPPING_INC_IMG );
470 $localize_data['lang']['cdn_mapping_inc_css'] = Lang::title( self::CDN_MAPPING_INC_CSS );
471 $localize_data['lang']['cdn_mapping_inc_js'] = Lang::title( self::CDN_MAPPING_INC_JS );
472 $localize_data['lang']['cdn_mapping_filetype'] = Lang::title( self::CDN_MAPPING_FILETYPE );
473 $localize_data['lang']['cdn_mapping_url_desc'] = sprintf( __( 'CDN URL to be used. For example, %s', 'litespeed-cache' ), '<code>' . esc_html( $cdn_url ) . '</code>' );
474 $localize_data['lang']['one_per_line'] = Doc::one_per_line( true );
475 $localize_data['lang']['cdn_mapping_remove'] = __( 'Remove CDN URL', 'litespeed-cache' );
476 $localize_data['lang']['add_cdn_mapping_row'] = __( 'Add new CDN URL', 'litespeed-cache' );
477 $localize_data['lang']['on'] = __( 'ON', 'litespeed-cache' );
478 $localize_data['lang']['off'] = __( 'OFF', 'litespeed-cache' );
479 if ( empty( $localize_data['ids'] ) ) {
480 $localize_data['ids'] = array();
481 }
482 $localize_data['ids']['cdn_mapping'] = self::O_CDN_MAPPING;
483 }
484 }
485
486 // Load iziModal JS and CSS
487 $show_deactivation_modal = ( is_multisite() && ! is_network_admin() ) ? false : true;
488 if ( $show_deactivation_modal && 'plugins.php' === $pagenow ) {
489 wp_enqueue_script( Core::PLUGIN_NAME . '-iziModal', LSWCP_PLUGIN_URL . 'assets/js/iziModal.min.js', array(), Core::VER, true );
490 wp_enqueue_style( Core::PLUGIN_NAME . '-iziModal', LSWCP_PLUGIN_URL . 'assets/css/iziModal.min.css', array(), Core::VER, 'all' );
491 add_action( 'admin_footer', array( $this, 'add_deactivation_html' ) );
492 }
493
494 if ( $localize_data ) {
495 wp_localize_script( Core::PLUGIN_NAME, 'litespeed_data', $localize_data );
496 }
497
498 wp_enqueue_script( Core::PLUGIN_NAME );
499 }
500
501 /**
502 * Add modal HTML on Plugins screen.
503 *
504 * @since 7.3
505 * @return void
506 */
507 public function add_deactivation_html() {
508 require LSCWP_DIR . 'tpl/inc/modal.deactivation.php';
509 }
510
511 /**
512 * Filter the script tag for specific handles to set Babel type.
513 *
514 * @since 3.6
515 *
516 * @param string $tag The script tag.
517 * @param string $handle Script handle.
518 * @param string $src Script source URL.
519 * @return string The filtered script tag.
520 */
521 public function babel_type( $tag, $handle, $src ) {
522 if ( Core::PLUGIN_NAME . '-crawler' !== $handle && Core::PLUGIN_NAME . '-cdn' !== $handle ) {
523 return $tag;
524 }
525
526 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
527 return '<script src="' . Str::trim_quotes( $src ) . '" type="text/babel"></script>';
528 }
529
530 /**
531 * Callback that adds LiteSpeed Cache's action links.
532 *
533 * @since 1.0.0
534 *
535 * @param array<string> $links Previously added links from other plugins.
536 * @return array<string> Links with the LiteSpeed Cache one appended.
537 */
538 public function add_plugin_links( $links ) {
539 $links[] = '<a href="' . esc_url( admin_url( 'admin.php?page=litespeed-cache' ) ) . '">' . esc_html__( 'Settings', 'litespeed-cache' ) . '</a>';
540
541 return $links;
542 }
543
544 /**
545 * Build a single notice HTML string.
546 *
547 * @since 1.0.7
548 *
549 * @param string $color The color CSS class for the notice.
550 * @param string $str The notice message.
551 * @param bool $irremovable If true, the notice cannot be dismissed.
552 * @param string $additional_classes Additional classes to add to the wrapper.
553 * @return string The built notice HTML.
554 */
555 public static function build_notice( $color, $str, $irremovable = false, $additional_classes = '' ) {
556 $cls = $color;
557 if ( $irremovable ) {
558 $cls .= ' litespeed-irremovable';
559 } else {
560 $cls .= ' is-dismissible';
561 }
562 if ( $additional_classes ) {
563 $cls .= ' ' . $additional_classes;
564 }
565
566 // possible translation
567 $str = Lang::maybe_translate( $str );
568
569 return '<div class="litespeed_icon ' . esc_attr( $cls ) . '"><p>' . wp_kses_post( $str ) . '</p></div>';
570 }
571
572 /**
573 * Display info notice.
574 *
575 * @since 1.6.5
576 *
577 * @param string|array<string> $msg Message or list of messages.
578 * @param bool $do_echo Echo immediately instead of storing.
579 * @param bool $irremovable If true, cannot be dismissed.
580 * @param string $additional_classes Extra CSS classes.
581 * @return void
582 */
583 public static function info( $msg, $do_echo = false, $irremovable = false, $additional_classes = '' ) {
584 self::add_notice( self::NOTICE_BLUE, $msg, $do_echo, $irremovable, $additional_classes );
585 }
586
587 /**
588 * Display note (warning) notice.
589 *
590 * @since 1.6.5
591 *
592 * @param string|array<string> $msg Message or list of messages.
593 * @param bool $do_echo Echo immediately instead of storing.
594 * @param bool $irremovable If true, cannot be dismissed.
595 * @param string $additional_classes Extra CSS classes.
596 * @return void
597 */
598 public static function note( $msg, $do_echo = false, $irremovable = false, $additional_classes = '' ) {
599 self::add_notice( self::NOTICE_YELLOW, $msg, $do_echo, $irremovable, $additional_classes );
600 }
601
602 /**
603 * Display success notice.
604 *
605 * @since 1.6
606 *
607 * @param string|array<string> $msg Message or list of messages.
608 * @param bool $do_echo Echo immediately instead of storing.
609 * @param bool $irremovable If true, cannot be dismissed.
610 * @param string $additional_classes Extra CSS classes.
611 * @return void
612 */
613 public static function success( $msg, $do_echo = false, $irremovable = false, $additional_classes = '' ) {
614 self::add_notice( self::NOTICE_GREEN, $msg, $do_echo, $irremovable, $additional_classes );
615 }
616
617 /**
618 * Deprecated alias for success().
619 *
620 * @deprecated 4.7 Will drop in v7.5. Use success().
621 *
622 * @param string|array<string> $msg Message or list of messages.
623 * @param bool $do_echo Echo immediately instead of storing.
624 * @param bool $irremovable If true, cannot be dismissed.
625 * @param string $additional_classes Extra CSS classes.
626 * @return void
627 */
628 public static function succeed( $msg, $do_echo = false, $irremovable = false, $additional_classes = '' ) {
629 self::success( $msg, $do_echo, $irremovable, $additional_classes );
630 }
631
632 /**
633 * Display error notice.
634 *
635 * @since 1.6
636 *
637 * @param string|array<string> $msg Message or list of messages.
638 * @param bool $do_echo Echo immediately instead of storing.
639 * @param bool $irremovable If true, cannot be dismissed.
640 * @param string $additional_classes Extra CSS classes.
641 * @return void
642 */
643 public static function error( $msg, $do_echo = false, $irremovable = false, $additional_classes = '' ) {
644 self::add_notice( self::NOTICE_RED, $msg, $do_echo, $irremovable, $additional_classes );
645 }
646
647 /**
648 * Add unique (irremovable optional) messages.
649 *
650 * @since 4.7
651 *
652 * @param string $color_mode One of info|note|success|error.
653 * @param string|array<string> $msgs Message(s).
654 * @param bool $irremovable If true, cannot be dismissed.
655 * @return void
656 */
657 public static function add_unique_notice( $color_mode, $msgs, $irremovable = false ) {
658 if ( ! is_array( $msgs ) ) {
659 $msgs = array( $msgs );
660 }
661
662 $color_map = array(
663 'info' => self::NOTICE_BLUE,
664 'note' => self::NOTICE_YELLOW,
665 'success' => self::NOTICE_GREEN,
666 'error' => self::NOTICE_RED,
667 );
668 if ( empty( $color_map[ $color_mode ] ) ) {
669 self::debug( 'Wrong admin display color mode!' );
670 return;
671 }
672 $color = $color_map[ $color_mode ];
673
674 // Go through to make sure unique.
675 $filtered_msgs = array();
676 foreach ( $msgs as $k => $str ) {
677 if ( is_numeric( $k ) ) {
678 $k = md5( $str );
679 } // Use key to make it overwritable to previous same msg.
680 $filtered_msgs[ $k ] = $str;
681 }
682
683 self::add_notice( $color, $filtered_msgs, false, $irremovable );
684 }
685
686 /**
687 * Add a notice to display on the admin page (store or echo).
688 *
689 * @since 1.0.7
690 *
691 * @param string $color Notice color CSS class.
692 * @param string|array<string> $msg Message(s).
693 * @param bool $do_echo Echo immediately instead of storing.
694 * @param bool $irremovable If true, cannot be dismissed.
695 * @param string $additional_classes Extra classes for wrapper.
696 * @return void
697 */
698 public static function add_notice( $color, $msg, $do_echo = false, $irremovable = false, $additional_classes = '' ) {
699 // Bypass adding for CLI or cron
700 if ( defined( 'LITESPEED_CLI' ) || wp_doing_cron() ) {
701 // WP CLI will show the info directly
702 if ( defined( 'WP_CLI' ) && constant('WP_CLI') ) {
703 if ( ! is_array( $msg ) ) {
704 $msg = array( $msg );
705 }
706 foreach ( $msg as $v ) {
707 $v = wp_strip_all_tags( $v );
708 if ( self::NOTICE_RED === $color ) {
709 \WP_CLI::error( $v, false );
710 } else {
711 \WP_CLI::success( $v );
712 }
713 }
714 }
715 return;
716 }
717
718 if ( $do_echo ) {
719 echo self::build_notice( $color, $msg, $irremovable, $additional_classes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
720 return;
721 }
722
723 $msg_name = $irremovable ? self::DB_MSG_PIN : self::DB_MSG;
724
725 $messages = self::get_option( $msg_name, array() );
726 if ( ! is_array( $messages ) ) {
727 $messages = array();
728 }
729
730 if ( is_array( $msg ) ) {
731 foreach ( $msg as $k => $str ) {
732 $messages[ $k ] = self::build_notice( $color, $str, $irremovable, $additional_classes );
733 }
734 } else {
735 $messages[] = self::build_notice( $color, $msg, $irremovable, $additional_classes );
736 }
737 $messages = array_unique( $messages );
738 self::update_option( $msg_name, $messages );
739 }
740
741 /**
742 * Display notices and errors in dashboard.
743 *
744 * @since 1.1.0
745 * @return void
746 */
747 public function display_messages() {
748 if ( ! defined( 'LITESPEED_CONF_LOADED' ) ) {
749 $this->_in_upgrading();
750 }
751
752 if ( GUI::has_whm_msg() ) {
753 $this->show_display_installed();
754 }
755
756 Data::cls()->check_upgrading_msg();
757
758 // If is in dev version, always check latest update
759 Cloud::cls()->check_dev_version();
760
761 // One time msg
762 $messages = self::get_option( self::DB_MSG, array() );
763 $added_thickbox = false;
764 if ( is_array( $messages ) ) {
765 foreach ( $messages as $msg ) {
766 // Added for popup links
767 if ( strpos( $msg, 'TB_iframe' ) && ! $added_thickbox ) {
768 add_thickbox();
769 $added_thickbox = true;
770 }
771 echo wp_kses_post( $msg );
772 }
773 }
774 if ( -1 !== $messages ) {
775 self::update_option( self::DB_MSG, -1 );
776 }
777
778 // Pinned msg
779 $messages = self::get_option( self::DB_MSG_PIN, array() );
780 if ( is_array( $messages ) ) {
781 foreach ( $messages as $k => $msg ) {
782 // Added for popup links
783 if ( strpos( $msg, 'TB_iframe' ) && ! $added_thickbox ) {
784 add_thickbox();
785 $added_thickbox = true;
786 }
787
788 // Append close btn
789 if ( '</div>' === substr( $msg, -6 ) ) {
790 $link = Utility::build_url( Core::ACTION_DISMISS, GUI::TYPE_DISMISS_PIN, false, null, array( 'msgid' => $k ) );
791 $msg =
792 substr( $msg, 0, -6 ) .
793 '<p><a href="' .
794 esc_url( $link ) .
795 '" class="button litespeed-btn-primary litespeed-btn-mini">' .
796 esc_html__( 'Dismiss', 'litespeed-cache' ) .
797 '</a>' .
798 '</p></div>';
799 }
800 echo wp_kses_post( $msg );
801 }
802 }
803
804 if ( empty( $_GET['page'] ) || 0 !== strpos( sanitize_text_field( wp_unslash( $_GET['page'] ) ), 'litespeed' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
805 global $pagenow;
806 if ( 'plugins.php' !== $pagenow ) {
807 return;
808 }
809 }
810
811 if ( ! $this->conf( self::O_NEWS ) ) {
812 return;
813 }
814
815 // Show promo from cloud
816 Cloud::cls()->show_promo();
817
818 /**
819 * Check promo msg first
820 *
821 * @since 2.9
822 */
823 GUI::cls()->show_promo();
824
825 // Show version news
826 Cloud::cls()->news();
827 }
828
829 /**
830 * Dismiss pinned msg.
831 *
832 * @since 3.5.2
833 * @return void
834 */
835 public static function dismiss_pin() {
836 if ( ! isset( $_GET['msgid'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
837 return;
838 }
839
840 $messages = self::get_option( self::DB_MSG_PIN, array() );
841 $msgid = sanitize_text_field( wp_unslash( $_GET['msgid'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
842
843 if ( ! is_array( $messages ) || empty( $messages[ $msgid ] ) ) {
844 return;
845 }
846
847 unset( $messages[ $msgid ] );
848 if ( ! $messages ) {
849 $messages = -1;
850 }
851 self::update_option( self::DB_MSG_PIN, $messages );
852 }
853
854 /**
855 * Dismiss pinned msg by msg content.
856 *
857 * @since 7.0
858 *
859 * @param string $content Message content.
860 * @param string $color Color CSS class.
861 * @param bool $irremovable Is irremovable.
862 * @return void
863 */
864 public static function dismiss_pin_by_content( $content, $color, $irremovable ) {
865 $content = self::build_notice( $color, $content, $irremovable );
866 $messages = self::get_option( self::DB_MSG_PIN, array() );
867 $hit = false;
868 if ( -1 !== $messages ) {
869 foreach ( $messages as $k => $v ) {
870 if ( $v === $content ) {
871 unset( $messages[ $k ] );
872 $hit = true;
873 self::debug( '�
874 pinned msg content hit. Removed' );
875 break;
876 }
877 }
878 }
879 if ( $hit ) {
880 if ( ! $messages ) {
881 $messages = -1;
882 }
883 self::update_option( self::DB_MSG_PIN, $messages );
884 } else {
885 self::debug( ' No pinned msg content hit' );
886 }
887 }
888
889 /**
890 * Hooked to the in_widget_form action.
891 * Appends LiteSpeed Cache settings to the widget edit settings screen.
892 * This will append the esi on/off selector and ttl text.
893 *
894 * @since 1.1.0
895 *
896 * @param \WP_Widget $widget The widget instance (passed by reference).
897 * @param mixed $return_val Return param (unused).
898 * @param array $instance The widget instance's settings.
899 * @return void
900 */
901 public function show_widget_edit( $widget, $return_val, $instance ) {
902 require LSCWP_DIR . 'tpl/esi_widget_edit.php';
903 }
904
905 /**
906 * Outputs a notice when the plugin is installed via WHM.
907 *
908 * @since 1.0.12
909 * @return void
910 */
911 public function show_display_installed() {
912 require_once LSCWP_DIR . 'tpl/inc/show_display_installed.php';
913 }
914
915 /**
916 * Display error cookie msg.
917 *
918 * @since 1.0.12
919 * @return void
920 */
921 public static function show_error_cookie() {
922 require_once LSCWP_DIR . 'tpl/inc/show_error_cookie.php';
923 }
924
925 /**
926 * Display warning if lscache is disabled.
927 *
928 * @since 2.1
929 * @return void
930 */
931 public function cache_disabled_warning() {
932 include LSCWP_DIR . 'tpl/inc/check_cache_disabled.php';
933 }
934
935 /**
936 * Display conf data upgrading banner.
937 *
938 * @since 2.1
939 * @access private
940 * @return void
941 */
942 private function _in_upgrading() {
943 include LSCWP_DIR . 'tpl/inc/in_upgrading.php';
944 }
945
946 /**
947 * Output LiteSpeed form open tag and hidden fields.
948 *
949 * @since 3.0
950 *
951 * @param string|false $action Router action.
952 * @param string|false $type Router type.
953 * @param bool $has_upload Whether form has file uploads.
954 * @return void
955 */
956 public function form_action( $action = false, $type = false, $has_upload = false ) {
957 if ( ! $action ) {
958 $action = Router::ACTION_SAVE_SETTINGS;
959 }
960
961 if ( ! defined( 'LITESPEED_CONF_LOADED' ) ) {
962 echo '<div class="litespeed-relative">';
963 } else {
964 $current = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
965 if ( $has_upload ) {
966 echo '<form method="post" action="' . esc_url( $current ) . '" class="litespeed-relative" enctype="multipart/form-data">';
967 } else {
968 echo '<form method="post" action="' . esc_url( $current ) . '" class="litespeed-relative">';
969 }
970 }
971
972 echo '<input type="hidden" name="' . esc_attr( Router::ACTION ) . '" value="' . esc_attr( $action ) . '" />';
973 if ( $type ) {
974 echo '<input type="hidden" name="' . esc_attr( Router::TYPE ) . '" value="' . esc_attr( $type ) . '" />';
975 }
976 wp_nonce_field( $action, Router::NONCE );
977 }
978
979 /**
980 * Output LiteSpeed form end (submit + closing tags).
981 *
982 * @since 3.0
983 *
984 * @return void
985 */
986 public function form_end() {
987 echo "<div class='litespeed-top20'></div>";
988
989 if ( ! defined( 'LITESPEED_CONF_LOADED' ) ) {
990 submit_button( __( 'Save Changes', 'litespeed-cache' ), 'secondary litespeed-duplicate-float', 'litespeed-submit', true, array( 'disabled' => 'disabled' ) );
991
992 echo '</div>';
993 } else {
994 submit_button(
995 __( 'Save Changes', 'litespeed-cache' ),
996 'primary litespeed-duplicate-float',
997 'litespeed-submit',
998 true,
999 array(
1000 'id' => 'litespeed-submit-' . $this->_btn_i++,
1001 )
1002 );
1003
1004 echo '</form>';
1005 }
1006 }
1007
1008 /**
1009 * Register a setting for saving.
1010 *
1011 * @since 3.0
1012 *
1013 * @param string $id Setting ID.
1014 * @return void
1015 */
1016 public function enroll( $id ) {
1017 echo '<input type="hidden" name="' . esc_attr( Admin_Settings::ENROLL ) . '[]" value="' . esc_attr( $id ) . '" />';
1018 }
1019
1020 /**
1021 * Build a textarea input.
1022 *
1023 * @since 1.1.0
1024 *
1025 * @param string $id Setting ID.
1026 * @param int|false $cols Columns count.
1027 * @param string|nil $val Pre-set value.
1028 * @return void
1029 */
1030 public function build_textarea( $id, $cols = false, $val = null ) {
1031 if ( null === $val ) {
1032 $val = $this->conf( $id, true );
1033
1034 if ( is_array( $val ) ) {
1035 $val = implode( "\n", $val );
1036 }
1037 }
1038
1039 if ( ! $cols ) {
1040 $cols = 80;
1041 }
1042
1043 $rows = $this->get_textarea_rows( $val );
1044
1045 $this->enroll( $id );
1046
1047 echo "<textarea name='" . esc_attr( $id ) . "' rows='" . (int) $rows . "' cols='" . (int) $cols . "'>" . esc_textarea( $val ) . '</textarea>';
1048
1049 $this->_check_overwritten( $id );
1050 }
1051
1052 /**
1053 * Calculate textarea rows.
1054 *
1055 * @since 7.4
1056 *
1057 * @param string $val Text area value.
1058 * @return int Number of rows to use.
1059 */
1060 public function get_textarea_rows( $val ) {
1061 $rows = 5;
1062 $lines = substr_count( (string) $val, "\n" ) + 2;
1063 if ( $lines > $rows ) {
1064 $rows = $lines;
1065 }
1066 if ( $rows > 40 ) {
1067 $rows = 40;
1068 }
1069
1070 return $rows;
1071 }
1072
1073 /**
1074 * Build a text input field.
1075 *
1076 * @since 1.1.0
1077 *
1078 * @param string $id Setting ID.
1079 * @param string|null $cls CSS class.
1080 * @param string|null $val Value.
1081 * @param string $type Input type.
1082 * @param bool $disabled Whether disabled.
1083 * @return void
1084 */
1085 public function build_input( $id, $cls = null, $val = null, $type = 'text', $disabled = false ) {
1086 if ( null === $val ) {
1087 $val = $this->conf( $id, true );
1088
1089 // Mask passwords.
1090 if ( $this->_conf_pswd( $id ) && $val ) {
1091 $val = str_repeat( '*', strlen( $val ) );
1092 }
1093 }
1094
1095 $label_id = preg_replace( '/\W/', '', $id );
1096
1097 if ( 'text' === $type ) {
1098 $cls = "regular-text $cls";
1099 }
1100
1101 if ( $disabled ) {
1102 echo "<input type='" . esc_attr( $type ) . "' class='" . esc_attr( $cls ) . "' value='" . esc_attr( $val ) . "' id='input_" . esc_attr( $label_id ) . "' disabled /> ";
1103 } else {
1104 $this->enroll( $id );
1105 echo "<input type='" . esc_attr( $type ) . "' class='" . esc_attr( $cls ) . "' name='" . esc_attr( $id ) . "' value='" . esc_attr( $val ) . "' id='input_" . esc_attr( $label_id ) . "' /> ";
1106 }
1107
1108 $this->_check_overwritten( $id );
1109 }
1110
1111 /**
1112 * Build a checkbox HTML snippet.
1113 *
1114 * @since 1.1.0
1115 *
1116 * @param string $id Setting ID.
1117 * @param string $title Checkbox label (HTML allowed).
1118 * @param bool|null $checked Whether checked.
1119 * @param int|string $value Checkbox value.
1120 * @return void
1121 */
1122 public function build_checkbox( $id, $title, $checked = null, $value = 1 ) {
1123 if ( null === $checked && $this->conf( $id, true ) ) {
1124 $checked = true;
1125 }
1126
1127 $label_id = preg_replace( '/\W/', '', $id );
1128
1129 if ( 1 !== $value ) {
1130 $label_id .= '_' . $value;
1131 }
1132
1133 $this->enroll( $id );
1134
1135 echo "<div class='litespeed-tick'>
1136 <input type='checkbox' name='" . esc_attr( $id ) . "' id='input_checkbox_" . esc_attr( $label_id ) . "' value='" . esc_attr( $value ) . "' " . checked( (bool) $checked, true, false ) . " />
1137 <label for='input_checkbox_" . esc_attr( $label_id ) . "'>" . wp_kses_post( $title ) . '</label>
1138 </div>';
1139
1140 $this->_check_overwritten( $id );
1141 }
1142
1143 /**
1144 * Build a toggle checkbox snippet.
1145 *
1146 * @since 1.7
1147 *
1148 * @param string $id Setting ID.
1149 * @param bool|null $checked Whether enabled.
1150 * @param string|null $title_on Label when on.
1151 * @param string|null $title_off Label when off.
1152 * @return void
1153 */
1154 public function build_toggle( $id, $checked = null, $title_on = null, $title_off = null ) {
1155 if ( null === $checked && $this->conf( $id, true ) ) {
1156 $checked = true;
1157 }
1158 if ( null === $title_on ) {
1159 $title_on = __( 'ON', 'litespeed-cache' );
1160 $title_off = __( 'OFF', 'litespeed-cache' );
1161 }
1162 $cls = $checked ? 'primary' : 'default litespeed-toggleoff';
1163 echo "<div class='litespeed-toggle litespeed-toggle-btn litespeed-toggle-btn-" . esc_attr( $cls ) . "' data-litespeed-toggle-on='primary' data-litespeed-toggle-off='default' data-litespeed_toggle_id='" . esc_attr( $id ) . "' >
1164 <input name='" . esc_attr( $id ) . "' type='hidden' value='" . esc_attr( $checked ) . "' />
1165 <div class='litespeed-toggle-group'>
1166 <label class='litespeed-toggle-btn litespeed-toggle-btn-primary litespeed-toggle-on'>" . esc_html( $title_on ) . "</label>
1167 <label class='litespeed-toggle-btn litespeed-toggle-btn-default litespeed-toggle-active litespeed-toggle-off'>" . esc_html( $title_off ) . "</label>
1168 <span class='litespeed-toggle-handle litespeed-toggle-btn litespeed-toggle-btn-default'></span>
1169 </div>
1170 </div>";
1171 }
1172
1173 /**
1174 * Build a switch (radio) field.
1175 *
1176 * @since 1.1.0
1177 * @since 1.7 Removed $disable param.
1178 *
1179 * @param string $id Setting ID.
1180 * @param array<int,mixed>|false $title_list Labels for options (OFF/ON).
1181 * @return void
1182 */
1183 public function build_switch( $id, $title_list = false ) {
1184 $this->enroll( $id );
1185
1186 echo '<div class="litespeed-switch">';
1187
1188 if ( ! $title_list ) {
1189 $title_list = array( __( 'OFF', 'litespeed-cache' ), __( 'ON', 'litespeed-cache' ) );
1190 }
1191
1192 foreach ( $title_list as $k => $v ) {
1193 $this->_build_radio( $id, $k, $v );
1194 }
1195
1196 echo '</div>';
1197
1198 $this->_check_overwritten( $id );
1199 }
1200
1201 /**
1202 * Build a radio input and echo it.
1203 *
1204 * @since 1.1.0
1205 * @access private
1206 *
1207 * @param string $id Setting ID.
1208 * @param int|string $val Value for the radio.
1209 * @param string $txt Label HTML.
1210 * @return void
1211 */
1212 private function _build_radio( $id, $val, $txt ) {
1213 $id_attr = 'input_radio_' . preg_replace( '/\W/', '', $id ) . '_' . $val;
1214
1215 $default = isset( self::$_default_options[ $id ] ) ? self::$_default_options[ $id ] : self::$_default_site_options[ $id ];
1216
1217 $is_checked = ! is_string( $default )
1218 ? ( (int) $this->conf( $id, true ) === (int) $val )
1219 : ( $this->conf( $id, true ) === $val );
1220
1221 echo "<input type='radio' autocomplete='off' name='" . esc_attr( $id ) . "' id='" . esc_attr( $id_attr ) . "' value='" . esc_attr( $val ) . "' " . checked( $is_checked, true, false ) . " /> <label for='" . esc_attr( $id_attr ) . "'>" . wp_kses_post( $txt ) . '</label>';
1222 }
1223
1224 /**
1225 * Show overwritten info if value comes from const/primary/filter/server.
1226 *
1227 * @since 3.0
1228 * @since 7.4 Show value from filters. Added type parameter.
1229 *
1230 * @param string $id Setting ID.
1231 * @return void
1232 */
1233 protected function _check_overwritten( $id ) {
1234 $const_val = $this->const_overwritten( $id );
1235 $primary_val = $this->primary_overwritten( $id );
1236 $filter_val = $this->filter_overwritten( $id );
1237 $server_val = $this->server_overwritten( $id );
1238
1239 if ( null === $const_val && null === $primary_val && null === $filter_val && null === $server_val ) {
1240 return;
1241 }
1242
1243 // Get value to display.
1244 $val = null !== $const_val ? $const_val : $primary_val;
1245 // If we have filter_val will set as new val.
1246 if ( null !== $filter_val ) {
1247 $val = $filter_val;
1248 }
1249 // If we have server_val will set as new val.
1250 if ( null !== $server_val ) {
1251 $val = $server_val;
1252 }
1253
1254 // Get type (used for display purpose).
1255 $type = ( isset( self::$settings_filters[ $id ] ) && isset( self::$settings_filters[ $id ]['type'] ) ) ? self::$settings_filters[ $id ]['type'] : 'textarea';
1256 if ( ( null !== $const_val || null !== $primary_val ) && null === $filter_val ) {
1257 $type = 'setting';
1258 }
1259
1260 // Get default setting: if settings exist, use default setting, otherwise use filter/server value.
1261 $default = '';
1262 if ( isset( self::$_default_options[ $id ] ) || isset( self::$_default_site_options[ $id ] ) ) {
1263 $default = isset( self::$_default_options[ $id ] ) ? self::$_default_options[ $id ] : self::$_default_site_options[ $id ];
1264 }
1265 if ( null !== $filter_val || null !== $server_val ) {
1266 $default = null !== $filter_val ? $filter_val : $server_val;
1267 }
1268
1269 // Set value to display, will be a string.
1270 if ( is_bool( $default ) ) {
1271 $val = $val ? __( 'ON', 'litespeed-cache' ) : __( 'OFF', 'litespeed-cache' );
1272 } else {
1273 if ( is_array( $val ) ) {
1274 $val = implode( "\n", $val );
1275 }
1276 $val = esc_textarea( $val );
1277 }
1278
1279 // Show warning for all types except textarea.
1280 if ( 'textarea' !== $type ) {
1281 echo '<div class="litespeed-desc litespeed-warning litespeed-overwrite">⚠️ ';
1282
1283 if ( null !== $server_val ) {
1284 // Show $_SERVER value.
1285 printf( esc_html__( 'This value is overwritten by the %s variable.', 'litespeed-cache' ), '$_SERVER' );
1286 $val = '$_SERVER["' . $server_val[0] . '"] = ' . $server_val[1];
1287 } elseif ( null !== $filter_val ) {
1288 // Show filter value.
1289 echo esc_html__( 'This value is overwritten by the filter.', 'litespeed-cache' );
1290 } elseif ( null !== $const_val ) {
1291 // Show CONSTANT value.
1292 printf( esc_html__( 'This value is overwritten by the PHP constant %s.', 'litespeed-cache' ), '<code>' . esc_html( Base::conf_const( $id ) ) . '</code>' );
1293 } elseif ( is_multisite() ) {
1294 // Show multisite overwrite.
1295 if ( get_current_blog_id() !== BLOG_ID_CURRENT_SITE && $this->conf( self::NETWORK_O_USE_PRIMARY ) ) {
1296 echo esc_html__( 'This value is overwritten by the primary site setting.', 'litespeed-cache' );
1297 } else {
1298 echo esc_html__( 'This value is overwritten by the Network setting.', 'litespeed-cache' );
1299 }
1300 }
1301
1302 echo ' ' . sprintf( esc_html__( 'Currently set to %s', 'litespeed-cache' ), '<code>' . esc_html( $val ) . '</code>' ) . '</div>';
1303 } elseif ( 'textarea' === $type && null !== $filter_val ) {
1304 // Show warning for textarea.
1305 // Textarea sizes.
1306 $cols = 30;
1307 $rows = $this->get_textarea_rows( $val );
1308 $rows_current_val = $this->get_textarea_rows( implode( "\n", $this->conf( $id, true ) ) );
1309 // If filter rows is bigger than textarea size, equalize them.
1310 if ( $rows > $rows_current_val ) {
1311 $rows = $rows_current_val;
1312 }
1313 ?>
1314 <div class="litespeed-desc-wrapper">
1315 <div class="litespeed-desc"><?php echo esc_html__( 'Value from filter applied', 'litespeed-cache' ); ?>:</div>
1316 <textarea readonly rows="<?php echo (int) $rows; ?>" cols="<?php echo (int) $cols; ?>"><?php echo esc_textarea( $val ); ?></textarea>
1317 </div>
1318 <?php
1319 }
1320 }
1321
1322 /**
1323 * Display seconds label and readable span.
1324 *
1325 * @since 3.0
1326 * @return void
1327 */
1328 public function readable_seconds() {
1329 echo esc_html__( 'seconds', 'litespeed-cache' );
1330 echo ' <span data-litespeed-readable=""></span>';
1331 }
1332
1333 /**
1334 * Display default value for a setting.
1335 *
1336 * @since 1.1.1
1337 *
1338 * @param string $id Setting ID.
1339 * @return void
1340 */
1341 public function recommended( $id ) {
1342 if ( ! $this->default_settings ) {
1343 $this->default_settings = $this->load_default_vals();
1344 }
1345
1346 $val = $this->default_settings[ $id ];
1347
1348 if ( ! $val ) {
1349 return;
1350 }
1351
1352 if ( ! is_array( $val ) ) {
1353 printf(
1354 '%s: <code>%s</code>',
1355 esc_html__( 'Default value', 'litespeed-cache' ),
1356 esc_html( $val )
1357 );
1358 return;
1359 }
1360
1361 $rows = 5;
1362 $cols = 30;
1363 // Flexible rows/cols.
1364 $lines = count( $val ) + 1;
1365 $rows = min( max( $lines, $rows ), 40 );
1366 foreach ( $val as $v ) {
1367 $cols = max( strlen( $v ), $cols );
1368 }
1369 $cols = min( $cols, 150 );
1370
1371 $val = implode( "\n", $val );
1372 printf(
1373 '<div class="litespeed-desc">%s:</div><textarea readonly rows="%d" cols="%d">%s</textarea>',
1374 esc_html__( 'Default value', 'litespeed-cache' ),
1375 (int) $rows,
1376 (int) $cols,
1377 esc_textarea( $val )
1378 );
1379 }
1380
1381 /**
1382 * Validate rewrite rules regex syntax.
1383 *
1384 * @since 3.0
1385 *
1386 * @param string $id Setting ID.
1387 * @return void
1388 */
1389 protected function _validate_syntax( $id ) {
1390 $val = $this->conf( $id, true );
1391
1392 if ( ! $val ) {
1393 return;
1394 }
1395
1396 if ( ! is_array( $val ) ) {
1397 $val = array( $val );
1398 }
1399
1400 foreach ( $val as $v ) {
1401 if ( ! Utility::syntax_checker( $v ) ) {
1402 echo '<br /><span class="litespeed-warning"> ❌ ' . esc_html__( 'Invalid rewrite rule', 'litespeed-cache' ) . ': <code>' . wp_kses_post( $v ) . '</code></span>';
1403 }
1404 }
1405 }
1406
1407 /**
1408 * Validate if the .htaccess path is valid.
1409 *
1410 * @since 3.0
1411 *
1412 * @param string $id Setting ID.
1413 * @return void
1414 */
1415 protected function _validate_htaccess_path( $id ) {
1416 $val = $this->conf( $id, true );
1417 if ( ! $val ) {
1418 return;
1419 }
1420
1421 if ( '/.htaccess' !== substr( $val, -10 ) ) {
1422 echo '<br /><span class="litespeed-warning"> ❌ ' . sprintf( esc_html__( 'Path must end with %s', 'litespeed-cache' ), '<code>/.htaccess</code>' ) . '</span>';
1423 }
1424 }
1425
1426 /**
1427 * Check TTL ranges and show tips.
1428 *
1429 * @since 3.0
1430 *
1431 * @param string $id Setting ID.
1432 * @param int|bool $min Minimum value (or false).
1433 * @param int|bool $max Maximum value (or false).
1434 * @param bool $allow_zero Whether zero is allowed.
1435 * @return void
1436 */
1437 protected function _validate_ttl( $id, $min = false, $max = false, $allow_zero = false ) {
1438 $val = $this->conf( $id, true );
1439
1440 $tip = array();
1441 if ( $min && $val < $min && ( ! $allow_zero || 0 !== $val ) ) {
1442 $tip[] = esc_html__( 'Minimum value', 'litespeed-cache' ) . ': <code>' . $min . '</code>.';
1443 }
1444 if ( $max && $val > $max ) {
1445 $tip[] = esc_html__( 'Maximum value', 'litespeed-cache' ) . ': <code>' . $max . '</code>.';
1446 }
1447
1448 echo '<br />';
1449
1450 if ( $tip ) {
1451 echo '<span class="litespeed-warning"> ❌ ' . wp_kses_post( implode( ' ', $tip ) ) . '</span>';
1452 }
1453
1454 $range = '';
1455
1456 if ( $allow_zero ) {
1457 $range .= esc_html__( 'Zero, or', 'litespeed-cache' ) . ' ';
1458 }
1459
1460 if ( $min && $max ) {
1461 $range .= $min . ' - ' . $max;
1462 } elseif ( $min ) {
1463 $range .= esc_html__( 'Larger than', 'litespeed-cache' ) . ' ' . $min;
1464 } elseif ( $max ) {
1465 $range .= esc_html__( 'Smaller than', 'litespeed-cache' ) . ' ' . $max;
1466 }
1467
1468 echo esc_html__( 'Value range', 'litespeed-cache' ) . ': <code>' . esc_html( $range ) . '</code>';
1469 }
1470
1471 /**
1472 * Validate IPs in a list.
1473 *
1474 * @since 3.0
1475 *
1476 * @param string $id Setting ID.
1477 * @return void
1478 */
1479 protected function _validate_ip( $id ) {
1480 $val = $this->conf( $id, true );
1481 if ( ! $val ) {
1482 return;
1483 }
1484
1485 if ( ! is_array( $val ) ) {
1486 $val = array( $val );
1487 }
1488
1489 $tip = array();
1490 foreach ( $val as $v ) {
1491 if ( ! $v ) {
1492 continue;
1493 }
1494
1495 if ( ! \WP_Http::is_ip_address( $v ) ) {
1496 $tip[] = esc_html__( 'Invalid IP', 'litespeed-cache' ) . ': <code>' . esc_html( $v ) . '</code>.';
1497 }
1498 }
1499
1500 if ( $tip ) {
1501 echo '<br /><span class="litespeed-warning"> ❌ ' . wp_kses_post( implode( ' ', $tip ) ) . '</span>';
1502 }
1503 }
1504
1505 /**
1506 * Display API environment variable support.
1507 *
1508 * @since 1.8.3
1509 * @access protected
1510 *
1511 * @param string ...$args Server variable names.
1512 * @return void
1513 */
1514 protected function _api_env_var( ...$args ) {
1515 echo '<span class="litespeed-success"> ' .
1516 esc_html__( 'API', 'litespeed-cache' ) . ': ' .
1517 sprintf(
1518 /* translators: %s: list of server variables in <code> tags */
1519 esc_html__( 'Server variable(s) %s available to override this setting.', 'litespeed-cache' ),
1520 '<code>' . implode( '</code>, <code>', array_map( 'esc_html', $args ) ) . '</code>'
1521 ) .
1522 '</span>';
1523
1524 Doc::learn_more( 'https://docs.litespeedtech.com/lscache/lscwp/admin/#limiting-the-crawler' );
1525 }
1526
1527 /**
1528 * Display URI setting example.
1529 *
1530 * @since 2.6.1
1531 * @access protected
1532 * @return void
1533 */
1534 protected function _uri_usage_example() {
1535 echo esc_html__( 'The URLs will be compared to the REQUEST_URI server variable.', 'litespeed-cache' );
1536 /* translators: 1: example URL, 2: pattern example */
1537 echo ' ' . sprintf( esc_html__( 'For example, for %1$s, %2$s can be used here.', 'litespeed-cache' ), '<code>/mypath/mypage?aa=bb</code>', '<code>mypage?aa=</code>' );
1538 echo '<br /><i>';
1539 /* translators: %s: caret symbol */
1540 printf( esc_html__( 'To match the beginning, add %s to the beginning of the item.', 'litespeed-cache' ), '<code>^</code>' );
1541 /* translators: %s: dollar symbol */
1542 echo ' ' . sprintf( esc_html__( 'To do an exact match, add %s to the end of the URL.', 'litespeed-cache' ), '<code>$</code>' );
1543 echo ' ' . esc_html__( 'One per line.', 'litespeed-cache' );
1544 echo '</i>';
1545 }
1546
1547 /**
1548 * Return pluralized strings.
1549 *
1550 * @since 2.0
1551 *
1552 * @param int $num Number.
1553 * @param string $kind Kind of item (group|image).
1554 * @return string
1555 */
1556 public static function print_plural( $num, $kind = 'group' ) {
1557 if ( $num > 1 ) {
1558 switch ( $kind ) {
1559 case 'group':
1560 return sprintf( esc_html__( '%s groups', 'litespeed-cache' ), $num );
1561
1562 case 'image':
1563 return sprintf( esc_html__( '%s images', 'litespeed-cache' ), $num );
1564
1565 default:
1566 return $num;
1567 }
1568 }
1569
1570 switch ( $kind ) {
1571 case 'group':
1572 return sprintf( esc_html__( '%s group', 'litespeed-cache' ), $num );
1573
1574 case 'image':
1575 return sprintf( esc_html__( '%s image', 'litespeed-cache' ), $num );
1576
1577 default:
1578 return $num;
1579 }
1580 }
1581
1582 /**
1583 * Return guidance HTML.
1584 *
1585 * @since 2.0
1586 *
1587 * @param string $title Title HTML.
1588 * @param array<int,string> $steps Steps list (HTML allowed).
1589 * @param int|string $current_step Current step number or 'done'.
1590 * @return string HTML for guidance widget.
1591 */
1592 public static function guidance( $title, $steps, $current_step ) {
1593 if ( 'done' === $current_step ) {
1594 $current_step = count( $steps ) + 1;
1595 }
1596
1597 $percentage = ' (' . floor( ( ( $current_step - 1 ) * 100 ) / count( $steps ) ) . '%)';
1598
1599 $html = '<div class="litespeed-guide"><h2>' . $title . $percentage . '</h2><ol>';
1600 foreach ( $steps as $k => $v ) {
1601 $step = $k + 1;
1602 if ( $current_step > $step ) {
1603 $html .= '<li class="litespeed-guide-done">';
1604 } else {
1605 $html .= '<li>';
1606 }
1607 $html .= $v . '</li>';
1608 }
1609
1610 $html .= '</ol></div>';
1611
1612 return $html;
1613 }
1614
1615 /**
1616 * Check whether has QC hide banner cookie.
1617 *
1618 * @since 7.1
1619 *
1620 * @return bool
1621 */
1622 public static function has_qc_hide_banner() {
1623 return isset( $_COOKIE[ self::COOKIE_QC_HIDE_BANNER ] ) && ( time() - (int) $_COOKIE[ self::COOKIE_QC_HIDE_BANNER ] ) < 86400 * 90;
1624 }
1625
1626 /**
1627 * Set QC hide banner cookie.
1628 *
1629 * @since 7.1
1630 * @return void
1631 */
1632 public static function set_qc_hide_banner() {
1633 $expire = time() + 86400 * 365;
1634 self::debug( 'Set qc hide banner cookie' );
1635 setcookie( self::COOKIE_QC_HIDE_BANNER, time(), $expire, COOKIEPATH, COOKIE_DOMAIN );
1636 }
1637
1638 /**
1639 * Handle all request actions from main cls.
1640 *
1641 * @since 7.1
1642 * @return void
1643 */
1644 public function handler() {
1645 $type = Router::verify_type();
1646
1647 switch ( $type ) {
1648 case self::TYPE_QC_HIDE_BANNER:
1649 self::set_qc_hide_banner();
1650 break;
1651
1652 default:
1653 break;
1654 }
1655
1656 Admin::redirect();
1657 }
1658 }
1659