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-settings.cls.php
403 lines
| 1 | <?php |
| 2 | /** |
| 3 | * The admin settings handler of the plugin. |
| 4 | * |
| 5 | * Handles saving and validating settings from the admin UI and network admin. |
| 6 | * |
| 7 | * @since 1.1.0 |
| 8 | * @package LiteSpeed |
| 9 | */ |
| 10 | |
| 11 | namespace LiteSpeed; |
| 12 | |
| 13 | defined( 'WPINC' ) || exit(); |
| 14 | |
| 15 | /** |
| 16 | * Class Admin_Settings |
| 17 | * |
| 18 | * Saves, sanitizes, and validates LiteSpeed Cache settings. |
| 19 | */ |
| 20 | class Admin_Settings extends Base { |
| 21 | const LOG_TAG = '[Settings]'; |
| 22 | |
| 23 | const ENROLL = '_settings-enroll'; |
| 24 | |
| 25 | /** |
| 26 | * Save settings (single site). |
| 27 | * |
| 28 | * Accepts data from $_POST or WP-CLI. |
| 29 | * Importers may call the Conf class directly. |
| 30 | * |
| 31 | * @since 3.0 |
| 32 | * |
| 33 | * @param array<string,mixed> $raw_data Raw data from request/CLI. |
| 34 | * @return void |
| 35 | */ |
| 36 | public function save( $raw_data ) { |
| 37 | self::debug( 'saving' ); |
| 38 | |
| 39 | if ( empty( $raw_data[ self::ENROLL ] ) ) { |
| 40 | wp_die( esc_html__( 'No fields', 'litespeed-cache' ) ); |
| 41 | } |
| 42 | |
| 43 | $raw_data = Admin::cleanup_text( $raw_data ); |
| 44 | |
| 45 | // Convert data to config format. |
| 46 | $the_matrix = []; |
| 47 | foreach ( array_unique( $raw_data[ self::ENROLL ] ) as $id ) { |
| 48 | $child = false; |
| 49 | |
| 50 | // Drop array format. |
| 51 | if ( false !== strpos( $id, '[' ) ) { |
| 52 | if ( 0 === strpos( $id, self::O_CDN_MAPPING ) || 0 === strpos( $id, self::O_CRAWLER_COOKIES ) ) { |
| 53 | // CDN child | Cookie Crawler settings. |
| 54 | $child = substr( $id, strpos( $id, '[' ) + 1, strpos( $id, ']' ) - strpos( $id, '[' ) - 1 ); |
| 55 | // Drop ending []; Compatible with xx[0] way from CLI. |
| 56 | $id = substr( $id, 0, strpos( $id, '[' ) ); |
| 57 | } else { |
| 58 | // Drop ending []. |
| 59 | $id = substr( $id, 0, strpos( $id, '[' ) ); |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | if ( ! array_key_exists( $id, self::$_default_options ) ) { |
| 64 | continue; |
| 65 | } |
| 66 | |
| 67 | // Validate $child. |
| 68 | if ( self::O_CDN_MAPPING === $id ) { |
| 69 | 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 ) ) { |
| 70 | continue; |
| 71 | } |
| 72 | } |
| 73 | if ( self::O_CRAWLER_COOKIES === $id ) { |
| 74 | if ( ! in_array( $child, [ self::CRWL_COOKIE_NAME, self::CRWL_COOKIE_VALS ], true ) ) { |
| 75 | continue; |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | // Pull value from request. |
| 80 | if ( $child ) { |
| 81 | // []=xxx or [0]=xxx |
| 82 | $data = ! empty( $raw_data[ $id ][ $child ] ) ? $raw_data[ $id ][ $child ] : $this->type_casting(false, $id); |
| 83 | } else { |
| 84 | $data = ! empty( $raw_data[ $id ] ) ? $raw_data[ $id ] : $this->type_casting(false, $id); |
| 85 | } |
| 86 | |
| 87 | // Sanitize/normalize complex fields. |
| 88 | if ( self::O_CDN_MAPPING === $id || self::O_CRAWLER_COOKIES === $id ) { |
| 89 | // Use existing queued data if available (only when $child != false). |
| 90 | $data2 = array_key_exists( $id, $the_matrix ) |
| 91 | ? $the_matrix[ $id ] |
| 92 | : ( defined( 'WP_CLI' ) && WP_CLI ? $this->conf( $id ) : [] ); |
| 93 | } |
| 94 | |
| 95 | switch ( $id ) { |
| 96 | // Don't allow Editor/admin to be used in crawler role simulator. |
| 97 | case self::O_CRAWLER_ROLES: |
| 98 | $data = Utility::sanitize_lines( $data ); |
| 99 | if ( $data ) { |
| 100 | foreach ( $data as $k => $v ) { |
| 101 | if ( user_can( $v, 'edit_posts' ) ) { |
| 102 | /* translators: %s: user id in <code> tags */ |
| 103 | $msg = sprintf( |
| 104 | esc_html__( 'The user with id %s has editor access, which is not allowed for the role simulator.', 'litespeed-cache' ), |
| 105 | '<code>' . esc_html( $v ) . '</code>' |
| 106 | ); |
| 107 | Admin_Display::error( $msg ); |
| 108 | unset( $data[ $k ] ); |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | break; |
| 113 | |
| 114 | case self::O_CDN_MAPPING: |
| 115 | /** |
| 116 | * CDN setting |
| 117 | * |
| 118 | * Raw data format: |
| 119 | * cdn-mapping[url][] = 'xxx' |
| 120 | * cdn-mapping[url][2] = 'xxx2' |
| 121 | * cdn-mapping[inc_js][] = 1 |
| 122 | * |
| 123 | * Final format: |
| 124 | * cdn-mapping[0][url] = 'xxx' |
| 125 | * cdn-mapping[2][url] = 'xxx2' |
| 126 | */ |
| 127 | if ( $data ) { |
| 128 | foreach ( $data as $k => $v ) { |
| 129 | if ( self::CDN_MAPPING_FILETYPE === $child ) { |
| 130 | $v = Utility::sanitize_lines( $v ); |
| 131 | } |
| 132 | |
| 133 | if ( self::CDN_MAPPING_URL === $child ) { |
| 134 | // If not a valid URL, turn off CDN. |
| 135 | if ( 0 !== strpos( $v, 'https://' ) ) { |
| 136 | self::debug( '❌ CDN mapping set to OFF due to invalid URL' ); |
| 137 | $the_matrix[ self::O_CDN ] = false; |
| 138 | } |
| 139 | $v = trailingslashit( $v ); |
| 140 | } |
| 141 | |
| 142 | if ( in_array( $child, [ self::CDN_MAPPING_INC_IMG, self::CDN_MAPPING_INC_CSS, self::CDN_MAPPING_INC_JS ], true ) ) { |
| 143 | // Because these can't be auto detected in `config->update()`, need to format here. |
| 144 | $v = 'false' === $v ? 0 : (bool) $v; |
| 145 | } |
| 146 | |
| 147 | if ( empty( $data2[ $k ] ) ) { |
| 148 | $data2[ $k ] = []; |
| 149 | } |
| 150 | |
| 151 | $data2[ $k ][ $child ] = $v; |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | $data = $data2; |
| 156 | break; |
| 157 | |
| 158 | case self::O_CRAWLER_COOKIES: |
| 159 | /** |
| 160 | * Cookie Crawler setting |
| 161 | * Raw Format: |
| 162 | * crawler-cookies[name][] = xxx |
| 163 | * crawler-cookies[name][2] = xxx2 |
| 164 | * crawler-cookies[vals][] = xxx |
| 165 | * |
| 166 | * Final format: |
| 167 | * crawler-cookie[0][name] = 'xxx' |
| 168 | * crawler-cookie[0][vals] = 'xxx' |
| 169 | * crawler-cookie[2][name] = 'xxx2' |
| 170 | * |
| 171 | * Empty line for `vals` uses literal `_null`. |
| 172 | */ |
| 173 | if ( $data ) { |
| 174 | foreach ( $data as $k => $v ) { |
| 175 | if ( self::CRWL_COOKIE_VALS === $child ) { |
| 176 | $v = Utility::sanitize_lines( $v ); |
| 177 | } |
| 178 | |
| 179 | if ( empty( $data2[ $k ] ) ) { |
| 180 | $data2[ $k ] = []; |
| 181 | } |
| 182 | |
| 183 | $data2[ $k ][ $child ] = $v; |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | $data = $data2; |
| 188 | break; |
| 189 | |
| 190 | // Cache exclude category. |
| 191 | case self::O_CACHE_EXC_CAT: |
| 192 | $data2 = []; |
| 193 | $data = Utility::sanitize_lines( $data ); |
| 194 | foreach ( $data as $v ) { |
| 195 | $cat_id = get_cat_ID( $v ); |
| 196 | if ( ! $cat_id ) { |
| 197 | continue; |
| 198 | } |
| 199 | $data2[] = $cat_id; |
| 200 | } |
| 201 | $data = $data2; |
| 202 | break; |
| 203 | |
| 204 | // Cache exclude tag. |
| 205 | case self::O_CACHE_EXC_TAG: |
| 206 | $data2 = []; |
| 207 | $data = Utility::sanitize_lines( $data ); |
| 208 | foreach ( $data as $v ) { |
| 209 | $term = get_term_by( 'name', $v, 'post_tag' ); |
| 210 | if ( ! $term ) { |
| 211 | // Could surface an admin error here if desired. |
| 212 | continue; |
| 213 | } |
| 214 | $data2[] = $term->term_id; |
| 215 | } |
| 216 | $data = $data2; |
| 217 | break; |
| 218 | |
| 219 | case self::O_IMG_OPTM_SIZES_SKIPPED: // Skip image sizes |
| 220 | $image_sizes = Utility::prepare_image_sizes_array(); |
| 221 | $saved_sizes = isset( $raw_data[$id] ) ? $raw_data[$id] : []; |
| 222 | $data = array_diff( $image_sizes, $saved_sizes ); |
| 223 | break; |
| 224 | |
| 225 | default: |
| 226 | break; |
| 227 | } |
| 228 | |
| 229 | $the_matrix[ $id ] = $data; |
| 230 | } |
| 231 | |
| 232 | // Special handler for CDN/Crawler 2d list to drop empty rows. |
| 233 | foreach ( $the_matrix as $id => $data ) { |
| 234 | /** |
| 235 | * Format: |
| 236 | * cdn-mapping[0][url] = 'xxx' |
| 237 | * cdn-mapping[2][url] = 'xxx2' |
| 238 | * crawler-cookie[0][name] = 'xxx' |
| 239 | * crawler-cookie[0][vals] = 'xxx' |
| 240 | * crawler-cookie[2][name] = 'xxx2' |
| 241 | */ |
| 242 | if ( self::O_CDN_MAPPING === $id || self::O_CRAWLER_COOKIES === $id ) { |
| 243 | // Drop row if all children are empty. |
| 244 | foreach ( $data as $k => $v ) { |
| 245 | foreach ( $v as $v2 ) { |
| 246 | if ( $v2 ) { |
| 247 | continue 2; |
| 248 | } |
| 249 | } |
| 250 | // All empty. |
| 251 | unset( $the_matrix[ $id ][ $k ] ); |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | // Don't allow repeated cookie names. |
| 256 | if ( self::O_CRAWLER_COOKIES === $id ) { |
| 257 | $existed = []; |
| 258 | foreach ( $the_matrix[ $id ] as $k => $v ) { |
| 259 | if ( empty( $v[ self::CRWL_COOKIE_NAME ] ) || in_array( $v[ self::CRWL_COOKIE_NAME ], $existed, true ) ) { |
| 260 | // Filter repeated or empty name. |
| 261 | unset( $the_matrix[ $id ][ $k ] ); |
| 262 | continue; |
| 263 | } |
| 264 | |
| 265 | $existed[] = $v[ self::CRWL_COOKIE_NAME ]; |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | // tmp fix the 3rd part woo update hook issue when enabling vary cookie. |
| 270 | if ( 'wc_cart_vary' === $id ) { |
| 271 | if ( $data ) { |
| 272 | add_filter( |
| 273 | 'litespeed_vary_cookies', |
| 274 | function ( $arr ) { |
| 275 | $arr[] = 'woocommerce_cart_hash'; |
| 276 | return array_unique( $arr ); |
| 277 | } |
| 278 | ); |
| 279 | } else { |
| 280 | add_filter( |
| 281 | 'litespeed_vary_cookies', |
| 282 | function ( $arr ) { |
| 283 | $key = array_search( 'woocommerce_cart_hash', $arr, true ); |
| 284 | if ( false !== $key ) { |
| 285 | unset( $arr[ $key ] ); |
| 286 | } |
| 287 | return array_unique( $arr ); |
| 288 | } |
| 289 | ); |
| 290 | } |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | // id validation will be inside. |
| 295 | $this->cls( 'Conf' )->update_confs( $the_matrix ); |
| 296 | |
| 297 | $msg = __( 'Options saved.', 'litespeed-cache' ); |
| 298 | Admin_Display::success( $msg ); |
| 299 | } |
| 300 | |
| 301 | /** |
| 302 | * Parses any changes made by the network admin on the network settings. |
| 303 | * |
| 304 | * @since 3.0 |
| 305 | * |
| 306 | * @param array<string,mixed> $raw_data Raw data from request/CLI. |
| 307 | * @return void |
| 308 | */ |
| 309 | public function network_save( $raw_data ) { |
| 310 | self::debug( 'network saving' ); |
| 311 | |
| 312 | if ( empty( $raw_data[ self::ENROLL ] ) ) { |
| 313 | wp_die( esc_html__( 'No fields', 'litespeed-cache' ) ); |
| 314 | } |
| 315 | |
| 316 | $raw_data = Admin::cleanup_text( $raw_data ); |
| 317 | |
| 318 | foreach ( array_unique( $raw_data[ self::ENROLL ] ) as $id ) { |
| 319 | // Append current field to setting save. |
| 320 | if ( ! array_key_exists( $id, self::$_default_site_options ) ) { |
| 321 | continue; |
| 322 | } |
| 323 | |
| 324 | $data = ! empty( $raw_data[ $id ] ) ? $raw_data[ $id ] : false; |
| 325 | |
| 326 | // id validation will be inside. |
| 327 | $this->cls( 'Conf' )->network_update( $id, $data ); |
| 328 | } |
| 329 | |
| 330 | // Update related files. |
| 331 | Activation::cls()->update_files(); |
| 332 | |
| 333 | $msg = __( 'Options saved.', 'litespeed-cache' ); |
| 334 | Admin_Display::success( $msg ); |
| 335 | } |
| 336 | |
| 337 | /** |
| 338 | * Hooked to the wp_redirect filter when saving widgets fails validation. |
| 339 | * |
| 340 | * @since 1.1.3 |
| 341 | * |
| 342 | * @param string $location The redirect location. |
| 343 | * @return string Updated location string. |
| 344 | */ |
| 345 | public static function widget_save_err( $location ) { |
| 346 | return str_replace( '?message=0', '?error=0', $location ); |
| 347 | } |
| 348 | |
| 349 | /** |
| 350 | * Validate the LiteSpeed Cache settings on widget save. |
| 351 | * |
| 352 | * @since 1.1.3 |
| 353 | * |
| 354 | * @param array $instance The new settings. |
| 355 | * @param array $new_instance The raw submitted settings. |
| 356 | * @param array $old_instance The original settings. |
| 357 | * @param \WP_Widget $widget The widget instance. |
| 358 | * @return array|false Updated settings on success, false on error. |
| 359 | */ |
| 360 | public static function validate_widget_save( $instance, $new_instance, $old_instance, $widget ) { |
| 361 | if ( empty( $new_instance ) ) { |
| 362 | return $instance; |
| 363 | } |
| 364 | |
| 365 | if ( ! isset( $new_instance[ ESI::WIDGET_O_ESIENABLE ], $new_instance[ ESI::WIDGET_O_TTL ] ) ) { |
| 366 | return $instance; |
| 367 | } |
| 368 | |
| 369 | $esi = (int) $new_instance[ ESI::WIDGET_O_ESIENABLE ] % 3; |
| 370 | $ttl = (int) $new_instance[ ESI::WIDGET_O_TTL ]; |
| 371 | |
| 372 | if ( 0 !== $ttl && $ttl < 30 ) { |
| 373 | add_filter( 'wp_redirect', __CLASS__ . '::widget_save_err' ); |
| 374 | return false; // Invalid ttl. |
| 375 | } |
| 376 | |
| 377 | if ( empty( $instance[ Conf::OPTION_NAME ] ) ) { |
| 378 | // @todo to be removed. |
| 379 | $instance[ Conf::OPTION_NAME ] = []; |
| 380 | } |
| 381 | $instance[ Conf::OPTION_NAME ][ ESI::WIDGET_O_ESIENABLE ] = $esi; |
| 382 | $instance[ Conf::OPTION_NAME ][ ESI::WIDGET_O_TTL ] = $ttl; |
| 383 | |
| 384 | $current = ! empty( $old_instance[ Conf::OPTION_NAME ] ) ? $old_instance[ Conf::OPTION_NAME ] : false; |
| 385 | |
| 386 | // Avoid unsanitized superglobal usage. |
| 387 | $referrer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; |
| 388 | |
| 389 | // Only purge when not in the Customizer. |
| 390 | if ( false === strpos( $referrer, '/wp-admin/customize.php' ) ) { |
| 391 | if ( ! $current || $esi !== (int) $current[ ESI::WIDGET_O_ESIENABLE ] ) { |
| 392 | Purge::purge_all( 'Widget ESI_enable changed' ); |
| 393 | } elseif ( 0 !== $ttl && $ttl !== (int) $current[ ESI::WIDGET_O_TTL ] ) { |
| 394 | Purge::add( Tag::TYPE_WIDGET . $widget->id ); |
| 395 | } |
| 396 | |
| 397 | Purge::purge_all( 'Widget saved' ); |
| 398 | } |
| 399 | |
| 400 | return $instance; |
| 401 | } |
| 402 | } |
| 403 |