JFIF $ d d 7 * Z qn )ZP 0 !1 A "#a$2Qr D8 a Ri [f \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H ~ , l s / 9 ć: [=/#f ; ) x { ٛE Q )~ = 𘙲r*2~ a _V = ' ku m F D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq 1dđ #̩ ͑ 0 ; H ] u 搂 @:~ ו K L N s h } O I R* 8 :2 ! lD JV o ( 3 = M ( z Ȱ+i *N A r6K n S l ) ! J J Ӂ* % ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c P A\IL Nj! J ۱+ O\ ͔ ] ޒ S J ȧ c%A N ol ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0 'G=i 2N +0 e 2 ]] 9 VT P O 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P < 0 x < !1AQ "Raq 02Br #S CTb ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV YT D I 6 2 '8Y2 7'\T P.6 d &˦ @V q i |8 -O Ε ] ʔ U= T L 8= ;6c | !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U#6w + +s &r+n ڐ { @ 29gL u " T Ù M=6 (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 ! OS r D 5 7 % | ?x> L | / nD 6?/8 w # [ ) L 7 +6〼T A T g ! %5 Mm Z /c -{1_Je "| ^$ 'O & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@ u6 BԒ i YN * f 0 _ GhKW u lT <nj- 0{ M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q $ H8o Q˼EV ~ k 0 h' L Pp M -M" m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @~p # !1QAa q ? AL0L x LU 4 d ; x " 16ad0 7.\ߋ 0 = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X &Q З non]я u ג./ ; gZ$ M QР 2M ]h ni 0 P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _ 1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3 6 #Ik+ v { v i ( ވ ? a e) X xy/*@Q Ǽ Ծ ) $ IWO l c G; !i k{ gOŀ L ('x T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ) X 5 c QɎMy W ? Q *!R > 6=7)Xj 5`J ] e 8%t ! + ' ! 1Q 5 !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $ v , ,m 㵜5u bV =sY+ @d { N ! dn O < .- B; _w J t 6;Q Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш e黅X ۢ F :)[ F G X ƹ / w _c MeD ,ʡc c . W D tA $ j@ : ) - #u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P 6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L ! ~ y5 g9T̅J kbM' +s :S +B) v @ M j e C f j E 0 Y\Qn z G 1 д~ W o{T 9?` Rm yhsy3 !HAD ] m c1 ~ 2 L S u7x T ;j$ `} 4-> L# vzŏ I L S ֭ T { r jGK C ; b pU= - ` Bs K. S F w4 M q] Z dH S 0 ) tLg
<?php
/**
* The admin settings handler of the plugin.
*
* Handles saving and validating settings from the admin UI and network admin.
*
* @since 1.1.0
* @package LiteSpeed
*/
namespace LiteSpeed;
defined( 'WPINC' ) || exit();
/**
* Class Admin_Settings
*
* Saves, sanitizes, and validates LiteSpeed Cache settings.
*/
class Admin_Settings extends Base {
const LOG_TAG = '[Settings]';
const ENROLL = '_settings-enroll';
/**
* Save settings (single site).
*
* Accepts data from $_POST or WP-CLI.
* Importers may call the Conf class directly.
*
* @since 3.0
*
* @param array<string,mixed> $raw_data Raw data from request/CLI.
* @return void
*/
public function save( $raw_data ) {
self::debug( 'saving' );
if ( empty( $raw_data[ self::ENROLL ] ) ) {
wp_die( esc_html__( 'No fields', 'litespeed-cache' ) );
}
$raw_data = Admin::cleanup_text( $raw_data );
// Convert data to config format.
$the_matrix = [];
foreach ( array_unique( $raw_data[ self::ENROLL ] ) as $id ) {
$child = false;
// Drop array format.
if ( false !== strpos( $id, '[' ) ) {
if ( 0 === strpos( $id, self::O_CDN_MAPPING ) || 0 === strpos( $id, self::O_CRAWLER_COOKIES ) ) {
// CDN child | Cookie Crawler settings.
$child = substr( $id, strpos( $id, '[' ) + 1, strpos( $id, ']' ) - strpos( $id, '[' ) - 1 );
// Drop ending []; Compatible with xx[0] way from CLI.
$id = substr( $id, 0, strpos( $id, '[' ) );
} else {
// Drop ending [].
$id = substr( $id, 0, strpos( $id, '[' ) );
}
}
if ( ! array_key_exists( $id, self::$_default_options ) ) {
continue;
}
// Validate $child.
if ( self::O_CDN_MAPPING === $id ) {
if ( ! in_array( $child, [ self::CDN_MAPPING_URL, self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS, self::CDN_MAPPING_FILETYPE ], true ) ) {
continue;
}
}
if ( self::O_CRAWLER_COOKIES === $id ) {
if ( ! in_array( $child, [ self::CRWL_COOKIE_NAME, self::CRWL_COOKIE_VALS ], true ) ) {
continue;
}
}
// Pull value from request.
if ( $child ) {
// []=xxx or [0]=xxx
$data = ! empty( $raw_data[ $id ][ $child ] ) ? $raw_data[ $id ][ $child ] : $this->type_casting(false, $id);
} else {
$data = ! empty( $raw_data[ $id ] ) ? $raw_data[ $id ] : $this->type_casting(false, $id);
}
// Sanitize/normalize complex fields.
if ( self::O_CDN_MAPPING === $id || self::O_CRAWLER_COOKIES === $id ) {
// Use existing queued data if available (only when $child != false).
$data2 = array_key_exists( $id, $the_matrix )
? $the_matrix[ $id ]
: ( defined( 'WP_CLI' ) && WP_CLI ? $this->conf( $id ) : [] );
}
switch ( $id ) {
// Don't allow Editor/admin to be used in crawler role simulator.
case self::O_CRAWLER_ROLES:
$data = Utility::sanitize_lines( $data );
if ( $data ) {
foreach ( $data as $k => $v ) {
if ( user_can( $v, 'edit_posts' ) ) {
/* translators: %s: user id in <code> tags */
$msg = sprintf(
esc_html__( 'The user with id %s has editor access, which is not allowed for the role simulator.', 'litespeed-cache' ),
'<code>' . esc_html( $v ) . '</code>'
);
Admin_Display::error( $msg );
unset( $data[ $k ] );
}
}
}
break;
case self::O_CDN_MAPPING:
/**
* CDN setting
*
* Raw data format:
* cdn-mapping[url][] = 'xxx'
* cdn-mapping[url][2] = 'xxx2'
* cdn-mapping[inc_js][] = 1
*
* Final format:
* cdn-mapping[0][url] = 'xxx'
* cdn-mapping[2][url] = 'xxx2'
*/
if ( $data ) {
foreach ( $data as $k => $v ) {
if ( self::CDN_MAPPING_FILETYPE === $child ) {
$v = Utility::sanitize_lines( $v );
}
if ( self::CDN_MAPPING_URL === $child ) {
// If not a valid URL, turn off CDN.
if ( 0 !== strpos( $v, 'https://' ) ) {
self::debug( '❌ CDN mapping set to OFF due to invalid URL' );
$the_matrix[ self::O_CDN ] = false;
}
$v = trailingslashit( $v );
}
if ( in_array( $child, [ self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS ], true ) ) {
// Because these can't be auto detected in `config->update()`, need to format here.
$v = 'false' === $v ? 0 : (bool) $v;
}
if ( empty( $data2[ $k ] ) ) {
$data2[ $k ] = [];
}
$data2[ $k ][ $child ] = $v;
}
}
$data = $data2;
break;
case self::O_CRAWLER_COOKIES:
/**
* Cookie Crawler setting
* Raw Format:
* crawler-cookies[name][] = xxx
* crawler-cookies[name][2] = xxx2
* crawler-cookies[vals][] = xxx
*
* Final format:
* crawler-cookie[0][name] = 'xxx'
* crawler-cookie[0][vals] = 'xxx'
* crawler-cookie[2][name] = 'xxx2'
*
* Empty line for `vals` uses literal `_null`.
*/
if ( $data ) {
foreach ( $data as $k => $v ) {
if ( self::CRWL_COOKIE_VALS === $child ) {
$v = Utility::sanitize_lines( $v );
}
if ( empty( $data2[ $k ] ) ) {
$data2[ $k ] = [];
}
$data2[ $k ][ $child ] = $v;
}
}
$data = $data2;
break;
// Cache exclude category.
case self::O_CACHE_EXC_CAT:
$data2 = [];
$data = Utility::sanitize_lines( $data );
foreach ( $data as $v ) {
$cat_id = get_cat_ID( $v );
if ( ! $cat_id ) {
continue;
}
$data2[] = $cat_id;
}
$data = $data2;
break;
// Cache exclude tag.
case self::O_CACHE_EXC_TAG:
$data2 = [];
$data = Utility::sanitize_lines( $data );
foreach ( $data as $v ) {
$term = get_term_by( 'name', $v, 'post_tag' );
if ( ! $term ) {
// Could surface an admin error here if desired.
continue;
}
$data2[] = $term->term_id;
}
$data = $data2;
break;
case self::O_IMG_OPTM_SIZES_SKIPPED: // Skip image sizes
$image_sizes = Utility::prepare_image_sizes_array();
$saved_sizes = isset( $raw_data[$id] ) ? $raw_data[$id] : [];
$data = array_diff( $image_sizes, $saved_sizes );
break;
case self::O_OBJECT_HOST: // Accepts an IP, a hostname, or an absolute UNIX socket path.
$data = trim( (string) $data );
$first = substr( $data, 0, 1 );
if ( '' === $data || '/' === $first ) {
// Empty means unset/use default; a leading slash is an absolute UNIX socket path.
$valid = true;
} else {
// Validates both hostname and IP; values starting with ~ are rejected by filter_var.
$valid = false !== filter_var( $data, FILTER_VALIDATE_IP ) || false !== filter_var( $data, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME );
}
if ( ! $valid ) {
Admin_Display::error( __( 'Object Cache: Invalid host. Use an IP address, a hostname or an absolute UNIX socket path. Old value was restored.', 'litespeed-cache' ) );
$data = $this->conf( $id ); // Revert to existing value to avoid saving invalid data.
}
break;
default:
break;
}
$the_matrix[ $id ] = $data;
}
// Special handler for CDN/Crawler 2d list to drop empty rows.
foreach ( $the_matrix as $id => $data ) {
/**
* Format:
* cdn-mapping[0][url] = 'xxx'
* cdn-mapping[2][url] = 'xxx2'
* crawler-cookie[0][name] = 'xxx'
* crawler-cookie[0][vals] = 'xxx'
* crawler-cookie[2][name] = 'xxx2'
*/
if ( self::O_CDN_MAPPING === $id || self::O_CRAWLER_COOKIES === $id ) {
// Drop row if all children are empty.
foreach ( $data as $k => $v ) {
foreach ( $v as $v2 ) {
if ( $v2 ) {
continue 2;
}
}
// All empty.
unset( $the_matrix[ $id ][ $k ] );
}
}
// Auto-strip filetypes whose row toggle is ON, mirroring CDN::init() so the saved list stays clean while preserving opt-out for toggles that are OFF.
if ( self::O_CDN_MAPPING === $id ) {
$auto_managed = CDN::auto_managed_filetypes();
foreach ( $the_matrix[ $id ] as $k => $row ) {
if ( empty( $row[ self::CDN_MAPPING_FILETYPE ] ) || ! is_array( $row[ self::CDN_MAPPING_FILETYPE ] ) ) {
continue;
}
$strip = [];
foreach ( $auto_managed as $toggle => $exts ) {
if ( ! empty( $row[ $toggle ] ) ) {
$strip = array_merge( $strip, $exts );
}
}
if ( ! empty( $strip ) ) {
$the_matrix[ $id ][ $k ][ self::CDN_MAPPING_FILETYPE ] = array_values(
array_diff( $row[ self::CDN_MAPPING_FILETYPE ], $strip )
);
}
}
}
// Don't allow repeated cookie names.
if ( self::O_CRAWLER_COOKIES === $id ) {
$existed = [];
foreach ( $the_matrix[ $id ] as $k => $v ) {
if ( empty( $v[ self::CRWL_COOKIE_NAME ] ) || in_array( $v[ self::CRWL_COOKIE_NAME ], $existed, true ) ) {
// Filter repeated or empty name.
unset( $the_matrix[ $id ][ $k ] );
continue;
}
$existed[] = $v[ self::CRWL_COOKIE_NAME ];
}
}
// tmp fix the 3rd part woo update hook issue when enabling vary cookie.
if ( 'wc_cart_vary' === $id ) {
if ( $data ) {
add_filter(
'litespeed_vary_cookies',
function ( $arr ) {
$arr[] = 'woocommerce_cart_hash';
return array_unique( $arr );
}
);
} else {
add_filter(
'litespeed_vary_cookies',
function ( $arr ) {
$key = array_search( 'woocommerce_cart_hash', $arr, true );
if ( false !== $key ) {
unset( $arr[ $key ] );
}
return array_unique( $arr );
}
);
}
}
}
// id validation will be inside.
$this->cls( 'Conf' )->update_confs( $the_matrix );
$msg = __( 'Options saved.', 'litespeed-cache' );
Admin_Display::success( $msg );
}
/**
* Parses any changes made by the network admin on the network settings.
*
* @since 3.0
*
* @param array<string,mixed> $raw_data Raw data from request/CLI.
* @return void
*/
public function network_save( $raw_data ) {
self::debug( 'network saving' );
if ( empty( $raw_data[ self::ENROLL ] ) ) {
wp_die( esc_html__( 'No fields', 'litespeed-cache' ) );
}
$raw_data = Admin::cleanup_text( $raw_data );
foreach ( array_unique( $raw_data[ self::ENROLL ] ) as $id ) {
// Append current field to setting save.
if ( ! array_key_exists( $id, self::$_default_site_options ) ) {
continue;
}
$data = ! empty( $raw_data[ $id ] ) ? $raw_data[ $id ] : false;
// id validation will be inside.
$this->cls( 'Conf' )->network_update( $id, $data );
}
// Update related files.
Activation::cls()->update_files();
$msg = __( 'Options saved.', 'litespeed-cache' );
Admin_Display::success( $msg );
}
/**
* Hooked to the wp_redirect filter when saving widgets fails validation.
*
* @since 1.1.3
*
* @param string $location The redirect location.
* @return string Updated location string.
*/
public static function widget_save_err( $location ) {
return str_replace( '?message=0', '?error=0', $location );
}
/**
* Validate the LiteSpeed Cache settings on widget save.
*
* @since 1.1.3
*
* @param array $instance The new settings.
* @param array $new_instance The raw submitted settings.
* @param array $old_instance The original settings.
* @param \WP_Widget $widget The widget instance.
* @return array|false Updated settings on success, false on error.
*/
public static function validate_widget_save( $instance, $new_instance, $old_instance, $widget ) {
if ( empty( $new_instance ) ) {
return $instance;
}
if ( ! isset( $new_instance[ ESI::WIDGET_O_ESIENABLE ], $new_instance[ ESI::WIDGET_O_TTL ] ) ) {
return $instance;
}
$esi = (int) $new_instance[ ESI::WIDGET_O_ESIENABLE ] % 3;
$ttl = (int) $new_instance[ ESI::WIDGET_O_TTL ];
if ( 0 !== $ttl && $ttl < 30 ) {
add_filter( 'wp_redirect', __CLASS__ . '::widget_save_err' );
return false; // Invalid ttl.
}
if ( empty( $instance[ Conf::OPTION_NAME ] ) ) {
// @todo to be removed.
$instance[ Conf::OPTION_NAME ] = [];
}
$instance[ Conf::OPTION_NAME ][ ESI::WIDGET_O_ESIENABLE ] = $esi;
$instance[ Conf::OPTION_NAME ][ ESI::WIDGET_O_TTL ] = $ttl;
$current = ! empty( $old_instance[ Conf::OPTION_NAME ] ) ? $old_instance[ Conf::OPTION_NAME ] : false;
// Avoid unsanitized superglobal usage.
$referrer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '';
// Only purge when not in the Customizer.
if ( false === strpos( $referrer, '/wp-admin/customize.php' ) ) {
if ( ! $current || $esi !== (int) $current[ ESI::WIDGET_O_ESIENABLE ] ) {
Purge::purge_all( 'Widget ESI_enable changed' );
} elseif ( 0 !== $ttl && $ttl !== (int) $current[ ESI::WIDGET_O_TTL ] ) {
Purge::add( Tag::TYPE_WIDGET . $widget->id );
}
Purge::purge_all( 'Widget saved' );
}
return $instance;
}
}