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 / esi.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
esi.cls.php
1041 lines
1 <?php
2 // phpcs:ignoreFile
3
4 /**
5 * The ESI class.
6 *
7 * This is used to define all esi related functions.
8 *
9 * @since 1.1.3
10 * @package LiteSpeed
11 */
12
13 namespace LiteSpeed;
14
15 defined('WPINC') || exit();
16
17 class ESI extends Root {
18
19 const LOG_TAG = '';
20
21 private static $has_esi = false;
22 private static $_combine_ids = array();
23 private $esi_args = null;
24 private $_esi_preserve_list = array();
25 private $_nonce_actions = array( -1 => '' ); // val is cache control
26
27 const QS_ACTION = 'lsesi';
28 const QS_PARAMS = 'esi';
29 const COMBO = '__combo'; // ESI include combine='main' handler
30
31 const PARAM_ARGS = 'args';
32 const PARAM_ID = 'id';
33 const PARAM_INSTANCE = 'instance';
34 const PARAM_NAME = 'name';
35
36 const WIDGET_O_ESIENABLE = 'widget_esi_enable';
37 const WIDGET_O_TTL = 'widget_ttl';
38
39 /**
40 * Confructor of ESI
41 *
42 * @since 1.2.0
43 * @since 4.0 Change to be after Vary init in hook 'after_setup_theme'
44 */
45 public function init() {
46 /**
47 * Bypass ESI related funcs if disabled ESI to fix potential DIVI compatibility issue
48 *
49 * @since 2.9.7.2
50 */
51 if (Router::is_ajax() || !$this->cls('Router')->esi_enabled()) {
52 return;
53 }
54
55 // Guest mode, don't need to use ESI
56 if (defined('LITESPEED_GUEST') && LITESPEED_GUEST) {
57 return;
58 }
59
60 if (defined('LITESPEED_ESI_OFF')) {
61 return;
62 }
63
64 // If page is not cacheable
65 if (defined('DONOTCACHEPAGE') && apply_filters('litespeed_const_DONOTCACHEPAGE', DONOTCACHEPAGE)) {
66 return;
67 }
68
69 // Init ESI in `after_setup_theme` hook after detected if LITESPEED_DISABLE_ALL is ON or not
70 $this->_hooks();
71
72 /**
73 * Overwrite wp_create_nonce func
74 *
75 * @since 2.9.5
76 */
77 $this->_transform_nonce();
78
79 !defined('LITESPEED_ESI_INITED') && define('LITESPEED_ESI_INITED', true);
80 }
81
82 /**
83 * Init ESI related hooks
84 *
85 * Load delayed by hook to give the ability to bypass by LITESPEED_DISABLE_ALL const
86 *
87 * @since 2.9.7.2
88 * @since 4.0 Changed to private from public
89 * @access private
90 */
91 private function _hooks() {
92 add_filter('template_include', array( $this, 'esi_template' ), 99999);
93
94 add_action('load-widgets.php', __NAMESPACE__ . '\Purge::purge_widget');
95 add_action('wp_update_comment_count', __NAMESPACE__ . '\Purge::purge_comment_widget');
96
97 /**
98 * Recover REQUEST_URI
99 *
100 * @since 1.8.1
101 */
102 if (!empty($_GET[self::QS_ACTION])) {
103 self::debug('ESI req');
104 $this->_register_esi_actions();
105 }
106
107 /**
108 * Shortcode ESI
109 *
110 * To use it, just change the original shortcode as below:
111 * old: [someshortcode aa='bb']
112 * new: [esi someshortcode aa='bb' cache='private,no-vary' ttl='600']
113 *
114 * 1. `cache` attribute is optional, default to 'public,no-vary'.
115 * 2. `ttl` attribute is optional, default is your public TTL setting.
116 * 3. `_ls_silence` attribute is optional, default is false.
117 *
118 * @since 2.8
119 * @since 2.8.1 Check is_admin for Elementor compatibility #726013
120 */
121 if (!is_admin()) {
122 add_shortcode('esi', array( $this, 'shortcode' ));
123 }
124 }
125
126 /**
127 * Take over all nonce calls and transform to ESI
128 *
129 * @since 2.9.5
130 */
131 private function _transform_nonce() {
132 if (is_admin()) {
133 return;
134 }
135
136 // Load ESI nonces in conf
137 $nonces = $this->conf(Base::O_ESI_NONCE);
138 add_filter('litespeed_esi_nonces', array( $this->cls('Data'), 'load_esi_nonces' ));
139 if ($nonces = apply_filters('litespeed_esi_nonces', $nonces)) {
140 foreach ($nonces as $action) {
141 $this->nonce_action($action);
142 }
143 }
144
145 add_action('litespeed_nonce', array( $this, 'nonce_action' ));
146 }
147
148 /**
149 * Register a new nonce action to convert it to ESI
150 *
151 * @since 2.9.5
152 */
153 public function nonce_action( $action ) {
154 // Split the Cache Control
155 $action = explode(' ', $action);
156 $control = !empty($action[1]) ? $action[1] : '';
157 $action = $action[0];
158
159 // Wildcard supported
160 $action = Utility::wildcard2regex($action);
161
162 if (array_key_exists($action, $this->_nonce_actions)) {
163 return;
164 }
165
166 $this->_nonce_actions[$action] = $control;
167
168 // Debug2::debug('[ESI] Appended nonce action to nonce list [action] ' . $action);
169 }
170
171 /**
172 * Check if an action is registered to replace ESI
173 *
174 * @since 2.9.5
175 */
176 public function is_nonce_action( $action ) {
177 // If GM not run yet, then ESI not init yet, then ESI nonce will not be allowed even nonce func replaced.
178 if (!defined('LITESPEED_ESI_INITED')) {
179 return null;
180 }
181
182 if (is_admin()) {
183 return null;
184 }
185
186 if (defined('LITESPEED_ESI_OFF')) {
187 return null;
188 }
189
190 foreach ($this->_nonce_actions as $k => $v) {
191 if (strpos($k, '*') !== false) {
192 if (preg_match('#' . $k . '#iU', $action)) {
193 return $v;
194 }
195 } elseif ($k == $action) {
196 return $v;
197 }
198 }
199
200 return null;
201 }
202
203 /**
204 * Shortcode ESI
205 *
206 * @since 2.8
207 * @access public
208 */
209 public function shortcode( $atts ) {
210 if (empty($atts[0])) {
211 Debug2::debug('[ESI] ===shortcode wrong format', $atts);
212 return 'Wrong shortcode esi format';
213 }
214
215 $cache = 'public,no-vary';
216 if (!empty($atts['cache'])) {
217 $cache = $atts['cache'];
218 unset($atts['cache']);
219 }
220
221 $silence = false;
222 if (!empty($atts['_ls_silence'])) {
223 $silence = true;
224 }
225
226 do_action('litespeed_esi_shortcode-' . $atts[0]);
227
228 // Show ESI link
229 return $this->sub_esi_block('esi', 'esi-shortcode', $atts, $cache, $silence);
230 }
231
232 /**
233 * Check if the requested page has esi elements. If so, return esi on
234 * header.
235 *
236 * @since 1.1.3
237 * @access public
238 * @return string Esi On header if request has esi, empty string otherwise.
239 */
240 public static function has_esi() {
241 return self::$has_esi;
242 }
243
244 /**
245 * Sets that the requested page has esi elements.
246 *
247 * @since 1.1.3
248 * @access public
249 */
250 public static function set_has_esi() {
251 self::$has_esi = true;
252 }
253
254 /**
255 * Register all of the hooks related to the esi logic of the plugin.
256 * Specifically when the page IS an esi page.
257 *
258 * @since 1.1.3
259 * @access private
260 */
261 private function _register_esi_actions() {
262 /**
263 * This hook is in `init`
264 * For any plugin need to check if page is ESI, use `LSCACHE_IS_ESI` check after `init` hook
265 */
266 !defined('LSCACHE_IS_ESI') && define('LSCACHE_IS_ESI', $_GET[self::QS_ACTION]); // Reused this to ESI block ID
267
268 !empty($_SERVER['ESI_REFERER']) && defined('LSCWP_LOG') && Debug2::debug('[ESI] ESI_REFERER: ' . $_SERVER['ESI_REFERER']);
269
270 /**
271 * Only when ESI's parent is not REST, replace REQUEST_URI to avoid breaking WP5 editor REST call
272 *
273 * @since 2.9.3
274 */
275 if (!empty($_SERVER['ESI_REFERER']) && !$this->cls('REST')->is_rest($_SERVER['ESI_REFERER'])) {
276 self::debug('overwrite REQUEST_URI to ESI_REFERER [from] ' . $_SERVER['REQUEST_URI'] . ' [to] ' . $_SERVER['ESI_REFERER']);
277 if (!empty($_SERVER['ESI_REFERER'])) {
278 $_SERVER['REQUEST_URI'] = $_SERVER['ESI_REFERER'];
279 if (substr(get_option('permalink_structure'), -1) === '/' && strpos($_SERVER['ESI_REFERER'], '?') === false) {
280 $_SERVER['REQUEST_URI'] = trailingslashit($_SERVER['ESI_REFERER']);
281 }
282 }
283 // Prevent from 301 redirecting
284 if (!empty($_SERVER['SCRIPT_URI'])) {
285 $SCRIPT_URI = parse_url($_SERVER['SCRIPT_URI']);
286 $SCRIPT_URI['path'] = $_SERVER['REQUEST_URI'];
287 Utility::compatibility();
288 $_SERVER['SCRIPT_URI'] = http_build_url($SCRIPT_URI);
289 }
290 }
291
292 if (!empty($_SERVER['ESI_CONTENT_TYPE']) && strpos($_SERVER['ESI_CONTENT_TYPE'], 'application/json') === 0) {
293 add_filter('litespeed_is_json', '__return_true');
294 }
295
296 /**
297 * Make REST call be able to parse ESI
298 * NOTE: Not effective due to ESI req are all to `/` yet
299 *
300 * @since 2.9.4
301 */
302 add_action('rest_api_init', array( $this, 'load_esi_block' ), 101);
303
304 // Register ESI blocks
305 add_action('litespeed_esi_load-widget', array( $this, 'load_widget_block' ));
306 add_action('litespeed_esi_load-admin-bar', array( $this, 'load_admin_bar_block' ));
307 add_action('litespeed_esi_load-comment-form', array( $this, 'load_comment_form_block' ));
308
309 add_action('litespeed_esi_load-nonce', array( $this, 'load_nonce_block' ));
310 add_action('litespeed_esi_load-esi', array( $this, 'load_esi_shortcode' ));
311
312 add_action('litespeed_esi_load-' . self::COMBO, array( $this, 'load_combo' ));
313 }
314
315 /**
316 * Hooked to the template_include action.
317 * Selects the esi template file when the post type is a LiteSpeed ESI page.
318 *
319 * @since 1.1.3
320 * @access public
321 * @param string $template The template path filtered.
322 * @return string The new template path.
323 */
324 public function esi_template( $template ) {
325 // Check if is an ESI request
326 if (defined('LSCACHE_IS_ESI')) {
327 self::debug('calling ESI template');
328
329 return LSCWP_DIR . 'tpl/esi.tpl.php';
330 }
331 self::debug('calling default template');
332 $this->_register_not_esi_actions();
333 return $template;
334 }
335
336 /**
337 * Register all of the hooks related to the esi logic of the plugin.
338 * Specifically when the page is NOT an esi page.
339 *
340 * @since 1.1.3
341 * @access private
342 */
343 private function _register_not_esi_actions() {
344 do_action('litespeed_tpl_normal');
345
346 if (!Control::is_cacheable()) {
347 return;
348 }
349
350 if (Router::is_ajax()) {
351 return;
352 }
353
354 add_filter('widget_display_callback', array( $this, 'sub_widget_block' ), 0, 3);
355
356 // Add admin_bar esi
357 if (Router::is_logged_in()) {
358 remove_action('wp_body_open', 'wp_admin_bar_render', 0); // Remove default Admin bar. Fix https://github.com/elementor/elementor/issues/25198
359 remove_action('wp_footer', 'wp_admin_bar_render', 1000);
360 add_action('wp_footer', array( $this, 'sub_admin_bar_block' ), 1000);
361 }
362
363 // Add comment forum esi for logged-in user or commenter
364 if (!Router::is_ajax() && Vary::has_vary()) {
365 add_filter('comment_form_defaults', array( $this, 'register_comment_form_actions' ));
366 }
367 }
368
369 /**
370 * Set an ESI to be combine='sub'
371 *
372 * @since 3.4.2
373 */
374 public static function combine( $block_id ) {
375 if (!isset($_SERVER['X-LSCACHE']) || strpos($_SERVER['X-LSCACHE'], 'combine') === false) {
376 return;
377 }
378
379 if (in_array($block_id, self::$_combine_ids)) {
380 return;
381 }
382
383 self::$_combine_ids[] = $block_id;
384 }
385
386 /**
387 * Load combined ESI
388 *
389 * @since 3.4.2
390 */
391 public function load_combo() {
392 Control::set_nocache('ESI combine request');
393
394 if (empty($_POST['esi_include'])) {
395 return;
396 }
397
398 self::set_has_esi();
399
400 Debug2::debug('[ESI] 🍔 Load combo', $_POST['esi_include']);
401
402 $output = '';
403 foreach ($_POST['esi_include'] as $url) {
404 $qs = parse_url(htmlspecialchars_decode($url), PHP_URL_QUERY);
405 parse_str($qs, $qs);
406 if (empty($qs[self::QS_ACTION])) {
407 continue;
408 }
409 $esi_id = $qs[self::QS_ACTION];
410 $esi_param = !empty($qs[self::QS_PARAMS]) ? $this->_parse_esi_param($qs[self::QS_PARAMS]) : false;
411 $inline_param = apply_filters('litespeed_esi_inline-' . $esi_id, array(), $esi_param); // Returned array need to be [ val, control, tag ]
412 if ($inline_param) {
413 $output .= self::_build_inline($url, $inline_param);
414 }
415 }
416
417 echo $output;
418 }
419
420 /**
421 * Build a whole inline segment
422 *
423 * @since 3.4.2
424 */
425 private static function _build_inline( $url, $inline_param ) {
426 if (!$url || empty($inline_param['val']) || empty($inline_param['control']) || empty($inline_param['tag'])) {
427 return '';
428 }
429
430 $url = esc_attr($url);
431 $control = esc_attr($inline_param['control']);
432 $tag = esc_attr($inline_param['tag']);
433
434 return "<esi:inline name='$url' cache-control='" . $control . "' cache-tag='" . $tag . "'>" . $inline_param['val'] . '</esi:inline>';
435 }
436
437 /**
438 * Build the esi url. This method will build the html comment wrapper as well as serialize and encode the parameter array.
439 *
440 * The block_id parameter should contain alphanumeric and '-_' only.
441 *
442 * @since 1.1.3
443 * @access private
444 * @param string $block_id The id to use to display the correct esi block.
445 * @param string $wrapper The wrapper for the esi comments.
446 * @param array $params The esi parameters.
447 * @param string $control The cache control attribute if any.
448 * @param bool $silence If generate wrapper comment or not
449 * @param bool $preserved If this ESI block is used in any filter, need to temporarily convert it to a string to avoid the HTML tag being removed/filtered.
450 * @param bool $svar If store the value in memory or not, in memory will be faster
451 * @param array $inline_param If show the current value for current request( this can avoid multiple esi requests in first time cache generating process )
452 */
453 public function sub_esi_block(
454 $block_id,
455 $wrapper,
456 $params = array(),
457 $control = 'private,no-vary',
458 $silence = false,
459 $preserved = false,
460 $svar = false,
461 $inline_param = array()
462 ) {
463 if (empty($block_id) || !is_array($params) || preg_match('/[^\w-]/', $block_id)) {
464 return false;
465 }
466
467 if (defined('LITESPEED_ESI_OFF')) {
468 Debug2::debug('[ESI] ESI OFF so force loading [block_id] ' . $block_id);
469 do_action('litespeed_esi_load-' . $block_id, $params);
470 return;
471 }
472
473 if ($silence) {
474 // Don't add comment to esi block ( original for nonce used in tag property data-nonce='esi_block' )
475 $params['_ls_silence'] = true;
476 }
477
478 if ($this->cls('REST')->is_rest() || $this->cls('REST')->is_internal_rest()) {
479 $params['is_json'] = 1;
480 }
481
482 $params = apply_filters('litespeed_esi_params', $params, $block_id);
483 $control = apply_filters('litespeed_esi_control', $control, $block_id);
484
485 if (!is_array($params) || !is_string($control)) {
486 defined('LSCWP_LOG') && Debug2::debug("[ESI] 🛑 Sub hooks returned Params: \n" . var_export($params, true) . "\ncache control: \n" . var_export($control, true));
487
488 return false;
489 }
490
491 // Build params for URL
492 $appended_params = array(
493 self::QS_ACTION => $block_id,
494 );
495 if (!empty($control)) {
496 $appended_params['_control'] = $control;
497 }
498 if ($params) {
499 $appended_params[self::QS_PARAMS] = base64_encode(\json_encode($params));
500 Debug2::debug2('[ESI] param ', $params);
501 }
502
503 // Append hash
504 $appended_params['_hash'] = $this->_gen_esi_md5($appended_params);
505
506 /**
507 * Escape potential chars
508 *
509 * @since 2.9.4
510 */
511 $appended_params = array_map('urlencode', $appended_params);
512
513 // Generate ESI URL
514 $url = add_query_arg($appended_params, trailingslashit(wp_make_link_relative(home_url())));
515
516 $output = '';
517 if ($inline_param) {
518 $output .= self::_build_inline($url, $inline_param);
519 }
520
521 $output .= "<esi:include src='$url'";
522 if (!empty($control)) {
523 $control = esc_attr($control);
524 $output .= " cache-control='$control'";
525 }
526 if ($svar) {
527 $output .= " as-var='1'";
528 }
529 if (in_array($block_id, self::$_combine_ids)) {
530 $output .= " combine='sub'";
531 }
532 if ($block_id == self::COMBO && isset($_SERVER['X-LSCACHE']) && strpos($_SERVER['X-LSCACHE'], 'combine') !== false) {
533 $output .= " combine='main'";
534 }
535 $output .= ' />';
536
537 if (!$silence) {
538 $output = "<!-- lscwp $wrapper -->$output<!-- lscwp $wrapper esi end -->";
539 }
540
541 self::debug("💕 [BLock_ID] $block_id \t[wrapper] $wrapper \t\t[Control] $control");
542 self::debug2($output);
543
544 self::set_has_esi();
545
546 // Convert to string to avoid html chars filter when using
547 // Will reverse the buffer when output in self::finalize()
548 if ($preserved) {
549 $hash = md5($output);
550 $this->_esi_preserve_list[$hash] = $output;
551 self::debug("Preserved to $hash");
552
553 return $hash;
554 }
555
556 return $output;
557 }
558
559 /**
560 * Generate ESI hash md5
561 *
562 * @since 2.9.6
563 * @access private
564 */
565 private function _gen_esi_md5( $params ) {
566 $keys = array( self::QS_ACTION, '_control', self::QS_PARAMS );
567
568 $str = '';
569 foreach ($keys as $v) {
570 if (isset($params[$v]) && is_string($params[$v])) {
571 $str .= $params[$v];
572 }
573 }
574 Debug2::debug2('[ESI] md5_string=' . $str);
575
576 return md5($this->conf(Base::HASH) . $str);
577 }
578
579 /**
580 * Parses the request parameters on an ESI request
581 *
582 * @since 1.1.3
583 * @access private
584 */
585 private function _parse_esi_param( $qs_params = false ) {
586 $req_params = false;
587 if ($qs_params) {
588 $req_params = $qs_params;
589 } elseif (isset($_REQUEST[self::QS_PARAMS])) {
590 $req_params = $_REQUEST[self::QS_PARAMS];
591 }
592
593 if (!$req_params) {
594 return false;
595 }
596
597 $unencrypted = base64_decode($req_params);
598 if ($unencrypted === false) {
599 return false;
600 }
601
602 Debug2::debug2('[ESI] params', $unencrypted);
603 // $unencoded = urldecode($unencrypted); no need to do this as $_GET is already parsed
604 $params = \json_decode($unencrypted, true);
605
606 return $params;
607 }
608
609 /**
610 * Select the correct esi output based on the parameters in an ESI request.
611 *
612 * @since 1.1.3
613 * @access public
614 */
615 public function load_esi_block() {
616 /**
617 * Validate if is a legal ESI req
618 *
619 * @since 2.9.6
620 */
621 if (empty($_GET['_hash']) || $this->_gen_esi_md5($_GET) != $_GET['_hash']) {
622 Debug2::debug('[ESI] ❌ Failed to validate _hash');
623 return;
624 }
625
626 $params = $this->_parse_esi_param();
627
628 if (defined('LSCWP_LOG')) {
629 $logInfo = '[ESI] ⭕ ';
630 if (!empty($params[self::PARAM_NAME])) {
631 $logInfo .= ' Name: ' . $params[self::PARAM_NAME] . ' ----- ';
632 }
633 $logInfo .= ' [ID] ' . LSCACHE_IS_ESI;
634 Debug2::debug($logInfo);
635 }
636
637 if (!empty($params['_ls_silence'])) {
638 !defined('LSCACHE_ESI_SILENCE') && define('LSCACHE_ESI_SILENCE', true);
639 }
640
641 /**
642 * Buffer needs to be JSON format
643 *
644 * @since 2.9.4
645 */
646 if (!empty($params['is_json'])) {
647 add_filter('litespeed_is_json', '__return_true');
648 }
649
650 Tag::add(rtrim(Tag::TYPE_ESI, '.'));
651 Tag::add(Tag::TYPE_ESI . LSCACHE_IS_ESI);
652
653 // Debug2::debug(var_export($params, true ));
654
655 /**
656 * Handle default cache control 'private,no-vary' for sub_esi_block() @ticket #923505
657 *
658 * @since 2.2.3
659 */
660 if (!empty($_GET['_control'])) {
661 $control = explode(',', $_GET['_control']);
662 if (in_array('private', $control)) {
663 Control::set_private();
664 }
665
666 if (in_array('no-vary', $control)) {
667 Control::set_no_vary();
668 }
669 }
670
671 do_action('litespeed_esi_load-' . LSCACHE_IS_ESI, $params);
672 }
673
674 // The *_sub_* functions are helpers for the sub_* functions.
675 // The *_load_* functions are helpers for the load_* functions.
676
677 /**
678 * Loads the default options for default WordPress widgets.
679 *
680 * @since 1.1.3
681 * @access public
682 */
683 public static function widget_default_options( $options, $widget ) {
684 if (!is_array($options)) {
685 return $options;
686 }
687
688 $widget_name = get_class($widget);
689 switch ($widget_name) {
690 case 'WP_Widget_Recent_Posts':
691 case 'WP_Widget_Recent_Comments':
692 $options[self::WIDGET_O_ESIENABLE] = Base::VAL_OFF;
693 $options[self::WIDGET_O_TTL] = 86400;
694 break;
695 default:
696 break;
697 }
698 return $options;
699 }
700
701 /**
702 * Hooked to the widget_display_callback filter.
703 * If the admin configured the widget to display via esi, this function
704 * will set up the esi request and cancel the widget display.
705 *
706 * @since 1.1.3
707 * @access public
708 * @param array $instance Parameter used to build the widget.
709 * @param \WP_Widget $widget The widget to build.
710 * @param array $args Parameter used to build the widget.
711 * @return mixed Return false if display through esi, instance otherwise.
712 */
713 public function sub_widget_block( $instance, $widget, $args ) {
714 // #210407
715 if (!is_array($instance)) {
716 return $instance;
717 }
718
719 $name = get_class($widget);
720 if (!isset($instance[Base::OPTION_NAME])) {
721 return $instance;
722 }
723 $options = $instance[Base::OPTION_NAME];
724 if (!isset($options) || !$options[self::WIDGET_O_ESIENABLE]) {
725 defined('LSCWP_LOG') && Debug2::debug('ESI 0 ' . $name . ': ' . (!isset($options) ? 'not set' : 'set off'));
726
727 return $instance;
728 }
729
730 $esi_private = $options[self::WIDGET_O_ESIENABLE] == Base::VAL_ON2 ? 'private,' : '';
731
732 $params = array(
733 self::PARAM_NAME => $name,
734 self::PARAM_ID => $widget->id,
735 self::PARAM_INSTANCE => $instance,
736 self::PARAM_ARGS => $args,
737 );
738
739 echo $this->sub_esi_block('widget', 'widget ' . $name, $params, $esi_private . 'no-vary');
740
741 return false;
742 }
743
744 /**
745 * Hooked to the wp_footer action.
746 * Sets up the ESI request for the admin bar.
747 *
748 * @access public
749 * @since 1.1.3
750 * @global type $wp_admin_bar
751 */
752 public function sub_admin_bar_block() {
753 global $wp_admin_bar;
754
755 if (!is_admin_bar_showing() || !is_object($wp_admin_bar)) {
756 return;
757 }
758
759 // To make each admin bar ESI request different for `Edit` button different link
760 $params = array(
761 'ref' => $_SERVER['REQUEST_URI'],
762 );
763
764 echo $this->sub_esi_block('admin-bar', 'adminbar', $params);
765 }
766
767 /**
768 * Parses the esi input parameters and generates the widget for esi display.
769 *
770 * @access public
771 * @since 1.1.3
772 * @global $wp_widget_factory
773 * @param array $params Input parameters needed to correctly display widget
774 */
775 public function load_widget_block( $params ) {
776 // global $wp_widget_factory;
777 // $widget = $wp_widget_factory->widgets[ $params[ self::PARAM_NAME ] ];
778 $option = $params[self::PARAM_INSTANCE];
779 $option = $option[Base::OPTION_NAME];
780
781 // Since we only reach here via esi, safe to assume setting exists.
782 $ttl = $option[self::WIDGET_O_TTL];
783 defined('LSCWP_LOG') && Debug2::debug('ESI widget render: name ' . $params[self::PARAM_NAME] . ', id ' . $params[self::PARAM_ID] . ', ttl ' . $ttl);
784 if ($ttl == 0) {
785 Control::set_nocache('ESI Widget time to live set to 0');
786 } else {
787 Control::set_custom_ttl($ttl);
788
789 if ($option[self::WIDGET_O_ESIENABLE] == Base::VAL_ON2) {
790 Control::set_private();
791 }
792 Control::set_no_vary();
793 Tag::add(Tag::TYPE_WIDGET . $params[self::PARAM_ID]);
794 }
795 the_widget($params[self::PARAM_NAME], $params[self::PARAM_INSTANCE], $params[self::PARAM_ARGS]);
796 }
797
798 /**
799 * Generates the admin bar for esi display.
800 *
801 * @access public
802 * @since 1.1.3
803 */
804 public function load_admin_bar_block( $params ) {
805 global $wp_the_query;
806
807 if (!empty($params['ref'])) {
808 $ref_qs = parse_url($params['ref'], PHP_URL_QUERY);
809 if (!empty($ref_qs)) {
810 parse_str($ref_qs, $ref_qs_arr);
811
812 if (!empty($ref_qs_arr)) {
813 foreach ($ref_qs_arr as $k => $v) {
814 $_GET[$k] = $v;
815 }
816 }
817 }
818 }
819
820 // Needed when permalink structure is "Plain"
821 if (!isset($wp_the_query)) {
822 wp();
823 }
824
825 wp_admin_bar_render();
826 if (!$this->conf(Base::O_ESI_CACHE_ADMBAR)) {
827 Control::set_nocache('build-in set to not cacheable');
828 } else {
829 Control::set_private();
830 Control::set_no_vary();
831 }
832
833 defined('LSCWP_LOG') && Debug2::debug('ESI: adminbar ref: ' . $_SERVER['REQUEST_URI']);
834 }
835
836 /**
837 * Parses the esi input parameters and generates the comment form for esi display.
838 *
839 * @access public
840 * @since 1.1.3
841 * @param array $params Input parameters needed to correctly display comment form
842 */
843 public function load_comment_form_block( $params ) {
844 comment_form($params[self::PARAM_ARGS], $params[self::PARAM_ID]);
845
846 if (!$this->conf(Base::O_ESI_CACHE_COMMFORM)) {
847 Control::set_nocache('build-in set to not cacheable');
848 } else {
849 // by default comment form is public
850 if (Vary::has_vary()) {
851 Control::set_private();
852 Control::set_no_vary();
853 }
854 }
855 }
856
857 /**
858 * Generate nonce for certain action
859 *
860 * @access public
861 * @since 2.6
862 */
863 public function load_nonce_block( $params ) {
864 $action = $params['action'];
865
866 Debug2::debug('[ESI] load_nonce_block [action] ' . $action);
867
868 // set nonce TTL to half day
869 Control::set_custom_ttl(43200);
870
871 if (Router::is_logged_in()) {
872 Control::set_private();
873 }
874
875 if (function_exists('wp_create_nonce_litespeed_esi')) {
876 echo wp_create_nonce_litespeed_esi($action);
877 } else {
878 echo wp_create_nonce($action);
879 }
880 }
881
882 /**
883 * Show original shortcode
884 *
885 * @access public
886 * @since 2.8
887 */
888 public function load_esi_shortcode( $params ) {
889 if (isset($params['ttl'])) {
890 if (!$params['ttl']) {
891 Control::set_nocache('ESI shortcode att ttl=0');
892 } else {
893 Control::set_custom_ttl($params['ttl']);
894 }
895 unset($params['ttl']);
896 }
897
898 // Replace to original shortcode
899 $shortcode = $params[0];
900 $atts_ori = array();
901 foreach ($params as $k => $v) {
902 if ($k === 0) {
903 continue;
904 }
905
906 $atts_ori[] = is_string($k) ? "$k='" . addslashes($v) . "'" : $v;
907 }
908
909 Tag::add(Tag::TYPE_ESI . "esi.$shortcode");
910
911 // Output original shortcode final content
912 echo do_shortcode("[$shortcode " . implode(' ', $atts_ori) . ' ]');
913 }
914
915 /**
916 * Hooked to the comment_form_defaults filter.
917 * Stores the default comment form settings.
918 * If sub_comment_form_block is triggered, the output buffer is cleared and an esi block is added. The remaining comment form is also buffered and cleared.
919 * Else there is no need to make the comment form ESI.
920 *
921 * @since 1.1.3
922 * @access public
923 */
924 public function register_comment_form_actions( $defaults ) {
925 $this->esi_args = $defaults;
926 echo GUI::clean_wrapper_begin();
927 add_filter('comment_form_submit_button', array( $this, 'sub_comment_form_btn' ), 1000, 2); // To save the params passed in
928 add_action('comment_form', array( $this, 'sub_comment_form_block' ), 1000);
929 return $defaults;
930 }
931
932 /**
933 * Store the args passed in comment_form for the ESI comment param usage in `$this->sub_comment_form_block()`
934 *
935 * @since 3.4
936 * @access public
937 */
938 public function sub_comment_form_btn( $unused, $args ) {
939 if (empty($args) || empty($this->esi_args)) {
940 Debug2::debug('comment form args empty?');
941 return $unused;
942 }
943 $esi_args = array();
944
945 // compare current args with default ones
946 foreach ($args as $k => $v) {
947 if (!isset($this->esi_args[$k])) {
948 $esi_args[$k] = $v;
949 } elseif (is_array($v)) {
950 $diff = array_diff_assoc($v, $this->esi_args[$k]);
951 if (!empty($diff)) {
952 $esi_args[$k] = $diff;
953 }
954 } elseif ($v !== $this->esi_args[$k]) {
955 $esi_args[$k] = $v;
956 }
957 }
958
959 $this->esi_args = $esi_args;
960
961 return $unused;
962 }
963
964 /**
965 * Hooked to the comment_form_submit_button filter.
966 *
967 * This method will compare the used comment form args against the default args. The difference will be passed to the esi request.
968 *
969 * @access public
970 * @since 1.1.3
971 */
972 public function sub_comment_form_block( $post_id ) {
973 echo GUI::clean_wrapper_end();
974 $params = array(
975 self::PARAM_ID => $post_id,
976 self::PARAM_ARGS => $this->esi_args,
977 );
978
979 echo $this->sub_esi_block('comment-form', 'comment form', $params);
980 echo GUI::clean_wrapper_begin();
981 add_action('comment_form_after', array( $this, 'comment_form_sub_clean' ));
982 }
983
984 /**
985 * Hooked to the comment_form_after action.
986 * Cleans up the remaining comment form output.
987 *
988 * @since 1.1.3
989 * @access public
990 */
991 public function comment_form_sub_clean() {
992 echo GUI::clean_wrapper_end();
993 }
994
995 /**
996 * Replace preserved blocks
997 *
998 * @since 2.6
999 * @access public
1000 */
1001 public function finalize( $buffer ) {
1002 // Prepend combo esi block
1003 if (self::$_combine_ids) {
1004 Debug2::debug('[ESI] 🍔 Enabled combo');
1005 $esi_block = $this->sub_esi_block(self::COMBO, '__COMBINE_MAIN__', array(), 'no-cache', true);
1006 $buffer = $esi_block . $buffer;
1007 }
1008
1009 // Bypass if no preserved list to be replaced
1010 if (!$this->_esi_preserve_list) {
1011 return $buffer;
1012 }
1013
1014 $keys = array_keys($this->_esi_preserve_list);
1015
1016 Debug2::debug('[ESI] replacing preserved blocks', $keys);
1017
1018 $buffer = str_replace($keys, $this->_esi_preserve_list, $buffer);
1019
1020 return $buffer;
1021 }
1022
1023 /**
1024 * Check if the content contains preserved list or not
1025 *
1026 * @since 3.3
1027 */
1028 public function contain_preserve_esi( $content ) {
1029 $hit_list = array();
1030 foreach ($this->_esi_preserve_list as $k => $v) {
1031 if (strpos($content, '"' . $k . '"') !== false) {
1032 $hit_list[] = '"' . $k . '"';
1033 }
1034 if (strpos($content, "'" . $k . "'") !== false) {
1035 $hit_list[] = "'" . $k . "'";
1036 }
1037 }
1038 return $hit_list;
1039 }
1040 }
1041