PluginProbe ʕ •ᴥ•ʔ
Smush – Image Optimization, Compression, Lazy Load, WebP & CDN / 4.1.0
Smush – Image Optimization, Compression, Lazy Load, WebP & CDN v4.1.0
4.1.0 4.0.3 4.0.2 2.8.1 2.9.1 3.0.0 3.0.1 3.0.2 3.1.1 3.10.1 3.10.2 3.10.3 3.11.1 3.12.3 3.12.4 3.12.5 3.12.6 3.13.0 3.13.1 3.13.2 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.2 3.16.4 3.16.5 3.16.6 3.17.0 3.17.1 3.18.0 3.18.1 3.2.0.1 3.2.1 3.2.2.1 3.2.4 3.20.0 3.21.1 3.22.1 3.22.3 3.23.0 3.23.1 3.23.2 3.23.3 3.23.4 3.24.0 3.24.0-beta.2 3.3.0 3.3.1 3.3.2 3.4.1 3.4.2 3.6.1 3.6.3 3.7.0 3.7.1 3.7.2 3.7.3 3.8.2 3.8.3 3.8.4 3.8.5 3.8.7 3.8.8 3.9.0 3.9.1 3.9.11 3.9.2 3.9.4 3.9.5 3.9.8 3.9.9 trunk 1.0.0 1.0.1 1.0.2 1.1 1.1.1 1.1.2 1.1.3 1.2 1.2.1 1.2.10 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.5.1 1.6.5.2 1.6.5.3 1.6.5.4 1.7 1.7.1 1.7.1.1 2.0 2.0.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.6.2 2.0.6.3 2.0.6.5 2.0.7 2.0.7.1 2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2 2.2.1 2.2.2 2.3 2.3.1 2.4 2.4.2 2.4.3 2.4.4 2.4.5 2.5.2 2.5.3 2.6.1 2.6.2 2.6.3 2.7 2.7.1 2.7.4 2.7.4.1 2.7.5 2.7.6 2.7.8 2.7.8.1 2.7.9.1 2.8.0 2.8.0.1
wp-smushit / core / class-settings.php
wp-smushit / core Last commit date
api 2 days ago background 2 days ago backups 2 days ago bulk 2 days ago cache 2 days ago cli 2 days ago external 2 days ago frontend 2 days ago integrations 2 days ago lazy-load 2 days ago media 2 days ago media-library 2 days ago membership 2 days ago modules 2 days ago parser 2 days ago photon 2 days ago png2jpg 2 days ago product-analytics 2 days ago rating-notification 2 days ago resize 2 days ago security 2 days ago smush 2 days ago srcset 2 days ago stats 2 days ago threads 2 days ago transform 2 days ago class-abstract-settings-dto.php 2 days ago class-activity-log-controller.php 2 days ago class-animated-status-controller.php 2 days ago class-array-utils.php 2 days ago class-attachment-id-list.php 2 days ago class-backup-size.php 2 days ago class-configs.php 2 days ago class-controller.php 2 days ago class-core.php 2 days ago class-cron-controller.php 2 days ago class-deprecated-hooks.php 2 days ago class-error-handler.php 2 days ago class-file-system.php 2 days ago class-file-utils.php 2 days ago class-format-utils.php 2 days ago class-helper.php 2 days ago class-hub-connector.php 2 days ago class-installer.php 2 days ago class-keyword-exclusions.php 2 days ago class-modules.php 2 days ago class-multisite-utils.php 2 days ago class-optimization-controller.php 2 days ago class-optimizer.php 2 days ago class-plugin-settings-watcher.php 2 days ago class-rest.php 2 days ago class-server-utils.php 2 days ago class-settings-controller.php 2 days ago class-settings-dto.php 2 days ago class-settings-sanitizer.php 2 days ago class-settings.php 2 days ago class-shim.php 2 days ago class-smush-file.php 2 days ago class-stats.php 2 days ago class-string-utils.php 2 days ago class-time-utils.php 2 days ago class-timer.php 2 days ago class-upload-dir.php 2 days ago class-url-utils.php 2 days ago class-urls-exclusions.php 2 days ago class-wp-query-utils.php 2 days ago wp-compat.php 2 days ago
class-settings.php
1865 lines
1 <?php
2 /**
3 * Smush Settings class: Settings
4 *
5 * @since 3.0 Migrated from old settings class.
6 * @package Smush\Core
7 */
8
9 namespace Smush\Core;
10
11 use Smush\Core\CDN\CDN_Helper;
12 use Smush\Core\LCP\LCP_Helper;
13 use Smush\Core\Membership\Membership;
14 use Smush\Core\Next_Gen\Next_Gen_Manager;
15 use Smush\Core\Stats\Global_Stats;
16 use WP_Smush;
17
18 if ( ! defined( 'WPINC' ) ) {
19 die;
20 }
21
22 /**
23 * Class Settings
24 *
25 * @since 3.0
26 */
27 class Settings {
28
29 private static $subsite_controls_option_id = 'wp-smush-networkwide';
30 private static $lazy_preload_module_name = 'lazy_load';
31 protected static $settings_option_id = 'wp-smush-settings';
32 private static $next_gen_cdn_key = 'webp';
33 private static $level_lossless = 0;
34 protected static $level_super_lossy = 1;
35 protected static $level_ultra_lossy = 2;
36 private static $none_cdn_mode = 0;
37 private static $webp_cdn_mode = 1;
38 private static $avif_cdn_mode = 2;
39 protected static $dir_settings_option_id = 'wp-smush-dir-settings';
40
41 /**
42 * Plugin instance.
43 *
44 * @since 3.0
45 *
46 * @var null|Settings
47 */
48 private static $instance = null;
49
50 /**
51 * Settings array.
52 *
53 * @since 3.2.2
54 * @var array $settings
55 */
56 private $settings = array();
57
58 /**
59 * Default settings array.
60 *
61 * We don't want it to be edited directly, so we use public get_*, set_* and delete_* methods.
62 *
63 * @since 3.0 Improved structure.
64 * @since 3.2.2 Changed to be a default array.
65 * @since 3.8.0 Added webp_mod.
66 *
67 * @var array
68 */
69 public function get_defaults() {
70 return array(
71 'auto' => true, // works with CDN.
72 'lossy' => 0, // works with CDN.
73 'strip_exif' => true, // works with CDN.
74 'resize' => false,
75 'original' => true,
76 'backup' => true,
77 'no_scale' => false,
78 'png_to_jpg' => false, // works with CDN.
79 'nextgen' => false,
80 's3' => false,
81 'gutenberg' => false,
82 'js_builder' => false,
83 'gform' => false,
84 'cdn' => false,
85 'auto_resizing' => false,
86 'cdn_dynamic_sizes' => false,
87 self::$next_gen_cdn_key => self::$webp_cdn_mode,
88 'usage' => false,
89 'accessible_colors' => false,
90 'keep_data' => true,
91 'lazy_load' => false,
92 'background_images' => true,
93 'rest_api_support' => false, // CDN option.
94 'webp_mod' => false, // WebP module.
95 'background_email' => false,
96 'webp_direct_conversion' => false,
97 'webp_fallback' => false,
98 'disable_streams' => false,
99 'avif_mod' => false,
100 'avif_fallback' => false,
101 'image_dimensions' => false,
102 'preload_images' => false,
103 );
104 }
105
106 /**
107 * Available modules.
108 *
109 * @since 3.2.2
110 * @since 3.8.0 Added webp.
111 * @var array $modules
112 */
113 private function get_modules() {
114 return array( 'bulk', 'integrations', self::$lazy_preload_module_name, 'cdn', 'next_gen', 'settings' );
115 }
116
117 /**
118 * List of features/settings that are free.
119 *
120 * @var array $basic_features
121 */
122 public static $basic_features = array( 'bulk', 'auto', 'strip_exif', 'resize', 'original', 'directory_smush', 'gutenberg', 'js_builder', 'gform', 'lazy_load', 'lossy', 'png_to_jpg' );
123
124 /**
125 * List of fields in bulk smush form.
126 *
127 * @used-by save_settings()
128 *
129 * @var array
130 */
131 private $bulk_fields = array( 'lossy', 'bulk', 'auto', 'strip_exif', 'resize', 'original', 'backup', 'png_to_jpg', 'no_scale', 'background_email' );
132
133 /**
134 * @since 3.12.6
135 *
136 * Upsell fields.
137 */
138 private $upsell_fields = array( 'background_email' );
139
140 /**
141 * List of fields in integration form.
142 *
143 * @used-by save_settings()
144 *
145 * @var array
146 */
147 private $integrations_fields = array( 'gutenberg', 'gform', 'js_builder', 's3', 'nextgen' );
148
149 /**
150 * List of fields in CDN form.
151 *
152 * @used-by save_settings()
153 *
154 * @var array
155 */
156 public function get_cdn_fields() {
157 return array( 'cdn', 'background_images', 'cdn_dynamic_sizes', self::$next_gen_cdn_key, 'rest_api_support' );
158 }
159
160 /**
161 * List of fields in CDN form.
162 *
163 * @used-by save_settings()
164 *
165 * @since 3.8.0
166 *
167 * @var array
168 */
169 private $webp_fields = array( 'webp_mod', 'webp_direct_conversion', 'webp_fallback' );
170
171 /**
172 * @var array
173 */
174 private $avif_fields = array( 'avif_mod', 'avif_fallback' );
175
176 /**
177 * List of fields in Settings form.
178 *
179 * @used-by save_settings()
180 *
181 * @var array
182 */
183 private $settings_fields = array( 'accessible_colors', 'usage', 'keep_data', 'api_auth', 'disable_streams' );
184
185 /**
186 * List of fields in lazy loading form.
187 *
188 * @used-by save_settings()
189 *
190 * @var array
191 */
192 private $lazy_load_fields = array( 'lazy_load', 'auto_resizing', 'image_dimensions' );
193
194 /**
195 * @var array
196 */
197 private $preload_fields = array( 'preload_images' );
198
199 /**
200 * @var array
201 */
202 private $activated_subsite_modules;
203
204 /**
205 * @var bool
206 */
207 private $is_switching_subsite = false;
208
209 /**
210 * Return the plugin instance.
211 *
212 * @since 3.0
213 *
214 * @return Settings
215 */
216 public static function get_instance() {
217 if ( empty( self::$instance ) ) {
218 $pro_file = __DIR__ . '/class-settings-pro.php';
219 if ( ! class_exists( '\\Smush\\Core\\Settings_Pro' ) && file_exists( $pro_file ) ) {
220 require_once $pro_file;
221 }
222 if ( class_exists( '\\Smush\\Core\\Settings_Pro' ) ) {
223 self::$instance = new Settings_Pro();
224 } else {
225 self::$instance = new self();
226 }
227 }
228 return self::$instance;
229 }
230
231 public function __call( $method_name, $arguments ) {
232 _deprecated_function( esc_html( $method_name ), '3.24.0' );
233 }
234
235 /**
236 * WP_Smush_Settings constructor.
237 *
238 * WARNING: Any new class added to this constructor must be loaded before use.
239 * This constructor is called when the plugin is activated.
240 */
241 protected function __construct() {
242 // Handle settings cache and subsite switching when switching between sites in a multisite network.
243 add_action( 'switch_blog', array( $this, 'maybe_reset_cache_site_settings' ), 10, 2 );
244 add_action( 'switch_blog', array( $this, 'toggle_switching_subsite' ) );
245
246 // Do not initialize if not in admin area
247 // wp_head runs specifically in the frontend, good check to make sure we're accidentally not loading settings on required pages.
248 if ( ! is_admin() && ! wp_doing_ajax() && did_action( 'wp_head' ) ) {
249 return;
250 }
251
252 // Save Settings.
253 add_action( 'wp_ajax_smush_save_settings', array( $this, 'save_settings' ) );
254 // Reset Settings.
255 add_action( 'wp_ajax_reset_settings', array( $this, 'reset' ) );
256
257 add_filter( 'wp_smush_settings', array( $this, 'remove_unavailable' ) );
258
259 $this->init();
260 }
261
262 public function toggle_switching_subsite() {
263 $this->is_switching_subsite = ! $this->is_switching_subsite;
264 }
265
266 /**
267 * Remove settings that are not available on a specific version of WordPress.
268 *
269 * @since 3.9.1
270 *
271 * @param array $settings Current settings.
272 *
273 * @return array
274 */
275 public function remove_unavailable( $settings ) {
276 global $wp_version;
277
278 if ( version_compare( $wp_version, '5.3', '<' ) ) {
279 if ( isset( $this->bulk_fields['no_scale'] ) ) {
280 unset( $this->bulk_fields['no_scale'] );
281 }
282
283 if ( isset( $settings['no_scale'] ) ) {
284 unset( $settings['no_scale'] );
285 }
286 }
287
288 return $settings;
289 }
290
291 /**
292 * Get descriptions for all settings.
293 *
294 * @since 3.8.6 Moved from Core
295 *
296 * @param string $id Setting ID to get data for.
297 * @param string $type What value to get. Accepts: label, short_label or desc.
298 *
299 * @return string
300 */
301 public static function get_setting_data( $id, $type = '' ) {
302 $s3_plugin_url = esc_url( 'https://wordpress.org/plugins/amazon-s3-and-cloudfront/' );
303 $mail_recipient = get_option( 'admin_email' );
304 $bg_email_desc = sprintf(
305 /* translators: %s Email address */
306 esc_html__( "Be notified via email about the bulk smush status when the process has completed. You'll receive an email at %s.", 'wp-smushit' ),
307 '<strong>' . $mail_recipient . '</strong>'
308 );
309 $settings = array(
310 'background_email' => array(
311 'label' => esc_html__( 'Enable email notification', 'wp-smushit' ),
312 'short_label' => esc_html__( 'Email Notification', 'wp-smushit' ),
313 'desc' => $bg_email_desc,
314 ),
315 'bulk' => array(
316 'short_label' => esc_html__( 'Image Sizes', 'wp-smushit' ),
317 'desc' => esc_html__( 'WordPress creates multiple thumbnails for each uploaded image. Select which sizes to include in bulk smushing.', 'wp-smushit' ),
318 ),
319 'auto' => array(
320 'label' => esc_html__( 'Automatically compress my images on upload', 'wp-smushit' ),
321 'short_label' => esc_html__( 'Automatic compression', 'wp-smushit' ),
322 'desc' => esc_html__( 'When you upload images to your site, we will automatically optimize and compress them for you.', 'wp-smushit' ),
323 ),
324 'lossy' => array(
325 'label' => esc_html__( 'Choose Compression Level', 'wp-smushit' ),
326 'short_label' => esc_html__( 'Smush Mode', 'wp-smushit' ),
327 'desc' => sprintf(
328 /* translators: 1: Opening <strong> 2: Closing </strong> */
329 esc_html__( 'Choose the level of compression that suits your needs. We recommend %1$sUltra%2$s for faster sites and impressive image quality.', 'wp-smushit' ),
330 '<strong>',
331 '</strong>'
332 ),
333 ),
334 'strip_exif' => array(
335 'label' => esc_html__( 'Remove image metadata', 'wp-smushit' ),
336 'short_label' => esc_html__( 'Metadata', 'wp-smushit' ),
337 'desc' => esc_html__( 'Photos can include camera settings, date or location. Removing this EXIF data reduces the file size.', 'wp-smushit' ),
338 ),
339 'resize' => array(
340 'label' => esc_html__( 'Resize large images', 'wp-smushit' ),
341 'short_label' => esc_html__( 'Large Image Resizing', 'wp-smushit' ),
342 'desc' => esc_html__( 'WordPress scales large images (over 2560px) and keeps the originals as a backup. You can adjust the size limit or turn scaling off entirely.', 'wp-smushit' ),
343 ),
344 'no_scale' => array(
345 'label' => esc_html__( 'Disable scaled images', 'wp-smushit' ),
346 'short_label' => esc_html__( 'Disable Scaled Images', 'wp-smushit' ),
347 'desc' => esc_html__( 'When enabled, WordPress won’t create scaled versions of large images; only your original upload is kept.', 'wp-smushit' ),
348 ),
349 'original' => array(
350 'label' => esc_html__( 'Optimize original images', 'wp-smushit' ),
351 'short_label' => esc_html__( 'Original Images', 'wp-smushit' ),
352 'desc' => esc_html__( 'Control how Smush processes your original image files when running bulk smush.', 'wp-smushit' ),
353 ),
354 'backup' => array(
355 'label' => esc_html__( 'Backup original images', 'wp-smushit' ),
356 'short_label' => esc_html__( 'Backup Original Images', 'wp-smushit' ),
357 'desc' => esc_html__( 'Keep a backup of your original images so you can restore them anytime. Be aware this may increase the size of your uploads folder.', 'wp-smushit' ),
358 ),
359 'png_to_jpg' => array(
360 'label' => esc_html__( 'Auto-convert PNGs to JPEGs (lossy)', 'wp-smushit' ),
361 'short_label' => esc_html__( 'PNG to JPEG Conversion', 'wp-smushit' ),
362 'desc' => esc_html__( 'When you compress a PNG, Smush will check if converting it to JPEG could further reduce its size.', 'wp-smushit' ),
363 ),
364 'accessible_colors' => array(
365 'label' => esc_html__( 'Enable high contrast mode', 'wp-smushit' ),
366 'short_label' => esc_html__( 'Color Accessibility', 'wp-smushit' ),
367 'desc' => esc_html__( 'Increase the visibility and accessibility of elements and components to meet WCAG AAA requirements.', 'wp-smushit' ),
368 ),
369 'usage' => array(
370 'label' => esc_html__( 'Allow usage tracking', 'wp-smushit' ),
371 'short_label' => esc_html__( 'Usage Tracking', 'wp-smushit' ),
372 'desc' => esc_html__( 'Help make Smush better by letting our designers learn how you’re using the plugin.', 'wp-smushit' ),
373 ),
374 'image_dimensions' => array(
375 'label' => esc_html__( 'Automatically add missing image dimensions', 'wp-smushit' ),
376 'short_label' => esc_html__( 'Add Missing Image Dimensions', 'wp-smushit' ),
377 'desc' => esc_html__( 'Automatically add width and height attributes to images missing dimensions for better layout stability and performance.', 'wp-smushit' ),
378 ),
379 'nextgen' => array(
380 'label' => esc_html__( 'Enable NextGen Gallery integration', 'wp-smushit' ),
381 'short_label' => esc_html__( 'NextGen Gallery', 'wp-smushit' ),
382 'desc' => esc_html__( 'Allow smushing images directly through NextGen Gallery settings.', 'wp-smushit' ),
383 ),
384 's3' => array(
385 'label' => __( 'Enable Amazon S3 support', 'wp-smushit' ),
386 'short_label' => __( 'Amazon S3', 'wp-smushit' ),
387 'desc' => sprintf( /* translators: %1$s - <a>, %2$s - </a> */
388 esc_html__(
389 "Storing your image on S3 buckets using %1\$sWP Offload Media%2\$s? Smush can detect and smush those assets for you, including when you're removing files from your host server.",
390 'wp-smushit'
391 ),
392 "<a href='$s3_plugin_url' target = '_blank'>",
393 '</a>'
394 ),
395 ),
396 'gform' => array(
397 'label' => esc_html__( 'Enable Gravity Forms integration', 'wp-smushit' ),
398 'short_label' => esc_html__( 'Gravity Forms', 'wp-smushit' ),
399 'desc' => esc_html__( 'Allow compressing images uploaded with Gravity Forms.', 'wp-smushit' ),
400 ),
401 'js_builder' => array(
402 'label' => esc_html__( 'Enable WPBakery Page Builder integration', 'wp-smushit' ),
403 'short_label' => esc_html__( 'WPBakery Page Builder', 'wp-smushit' ),
404 'desc' => esc_html__( 'Allow smushing images resized in WPBakery Page Builder editor.', 'wp-smushit' ),
405 ),
406 'gutenberg' => array(
407 'label' => esc_html__( 'Show Smush stats in Gutenberg blocks', 'wp-smushit' ),
408 'short_label' => esc_html__( 'Gutenberg Support', 'wp-smushit' ),
409 'desc' => esc_html__(
410 'Add statistics and the manual smush button to Gutenberg blocks that display images.',
411 'wp-smushit'
412 ),
413 ),
414 );
415
416 $settings = apply_filters( 'wp_smush_settings', $settings );
417
418 if ( ! isset( $settings[ $id ] ) ) {
419 return '';
420 }
421
422 if ( 'short-label' === $type ) {
423 return ! empty( $settings[ $id ]['short_label'] ) ? $settings[ $id ]['short_label'] : $settings[ $id ]['label'];
424 }
425
426 if ( 'label' === $type ) {
427 return ! empty( $settings[ $id ]['label'] ) ? $settings[ $id ]['label'] : $settings[ $id ]['short_label'];
428 }
429
430 if ( 'desc' === $type ) {
431 return $settings[ $id ]['desc'];
432 }
433
434 return $settings[ $id ];
435 }
436
437 /**
438 * Getter method for bulk settings fields.
439 *
440 * @since 3.2.2
441 * @return array
442 */
443 public function get_bulk_fields() {
444 if ( $this->is_directory_smush_active() ) {
445 $this->bulk_fields[] = 'directory_smush';
446 }
447
448 return $this->bulk_fields;
449 }
450
451 /**
452 * Getter method for integration fields.
453 *
454 * @since 3.2.2
455 * @return array
456 */
457 public function get_integrations_fields() {
458 return $this->integrations_fields;
459 }
460
461 public function is_upsell_field( $field ) {
462 return in_array( $field, $this->upsell_fields, true );
463 }
464
465 public function is_pro_field( $field ) {
466 return ! in_array( $field, self::$basic_features, true );
467 }
468
469 public function can_access_pro_field( $field ) {
470 return false;
471 }
472
473 public function should_enforce_bulk_limit() {
474 return true;
475 }
476
477 public function get_api_key() {
478 return '';
479 }
480
481 /**
482 * Getter method for settings fields.
483 *
484 * @since 3.2.2
485 * @return array
486 */
487 public function get_settings_fields() {
488 return $this->settings_fields;
489 }
490
491 /**
492 * Getter method for lazy loading fields.
493 *
494 * @since 3.3.0
495 * @return array
496 */
497 public function get_lazy_load_fields() {
498 return $this->lazy_load_fields;
499 }
500
501 public function get_preload_fields() {
502 return $this->preload_fields;
503 }
504
505 public function get_webp_fields() {
506 return $this->webp_fields;
507 }
508
509 public function get_avif_fields() {
510 return $this->avif_fields;
511 }
512
513 public function get_next_gen_fields() {
514 return array_merge( $this->get_webp_fields(), $this->get_avif_fields() );
515 }
516
517 /**
518 * Init settings.
519 *
520 * If there are no settings in the database, populate it with the defaults, if settings are present
521 */
522 public function init() {
523 }
524
525 /**
526 * Checks whether the settings are applicable for the whole network/site or sitewise (multisite).
527 */
528 public function is_network_enabled() {
529 return $this->is_network_setting( self::$settings_option_id );
530 }
531
532 public function is_network_setting( $option_id ) {
533 if ( ! is_multisite() ) {
534 return false;
535 }
536
537 $global_setting_keys = array(
538 'wp_smush_api_auth',
539 self::$subsite_controls_option_id,
540 );
541
542 if ( in_array( $option_id, $global_setting_keys, true ) ) {
543 return true;
544 }
545
546 $subsite_modules = $this->get_activated_subsite_modules();
547 if ( empty( $subsite_modules ) ) {
548 return true;
549 }
550
551 $module_option_keys = array(
552 'wp-smush-image_sizes' => 'bulk',
553 'wp-smush-resize_sizes' => 'bulk',
554 'wp-smush-lazy_load' => self::$lazy_preload_module_name,
555 'wp-smush-preload' => self::$lazy_preload_module_name,
556 'wp-smush-cdn_status' => 'cdn',
557 );
558
559 if ( ! isset( $module_option_keys[ $option_id ] ) ) {
560 if ( $this->is_switching_subsite ) {
561 return false;
562 }
563
564 return self::is_ajax_network_admin() || is_network_admin();
565 }
566
567 $module = $module_option_keys[ $option_id ];
568
569 return ! in_array( $module, $subsite_modules, true );
570 }
571
572 /**
573 * Check if user is able to access the page.
574 *
575 * @since 3.2.2
576 *
577 * @param string|bool $module Check if a specific module is allowed.
578 * @param bool $top_menu Is this a top level menu point? Defaults to a Smush sub page.
579 *
580 * @return bool|array Can access page or not. If custom access rules defined - return custom rules array.
581 */
582 public static function can_access( $module = false, $top_menu = false ) {
583 // Allow all access on single site installs.
584 if ( ! is_multisite() ) {
585 return true;
586 }
587
588 $access = get_site_option( self::$subsite_controls_option_id );
589
590 // Check to if the settings update is network-wide or not ( only if in network admin ).
591 $action = filter_input( INPUT_POST, 'action', FILTER_SANITIZE_SPECIAL_CHARS );
592
593 $is_network_admin = is_network_admin() || 'save_settings' === $action;
594
595 if ( self::is_ajax_network_admin() ) {
596 $is_network_admin = true;
597 }
598
599 if ( $is_network_admin && ! $access && $top_menu ) {
600 return true;
601 }
602
603 if ( current_user_can( 'manage_options' ) && ( '1' === $access || 'custom' === $access && $top_menu ) ) {
604 return true;
605 }
606
607 if ( is_array( $access ) && current_user_can( 'manage_options' ) ) {
608 if ( ! $module ) {
609 return $access;
610 }
611
612 if ( $is_network_admin && ! in_array( $module, $access, true ) ) {
613 return true;
614 } elseif ( ! $is_network_admin && in_array( $module, $access, true ) ) {
615 return true;
616 }
617
618 return false;
619 }
620
621 return false;
622 }
623
624 public function maybe_reset_cache_site_settings( $new_blog_id, $prev_blog_id ) {
625 $this->reset_cache_site_settings();
626 }
627
628 public function reset_cache_site_settings() {
629 $this->settings = array();// Reset settings, leave force update the settings for get_site_settings.
630 }
631
632 private function update_site_settings( $new_settings ) {
633 $new_settings = (array) $new_settings;
634 $site_settings = $this->get_site_settings();
635
636 foreach ( $new_settings as $setting => $value ) {
637 if ( isset( $site_settings[ $setting ], $value ) ) {
638 $site_settings[ $setting ] = $value;
639 }
640 }
641
642 $this->update_site_option( self::$settings_option_id, $site_settings );
643 $this->reset_cache_site_settings();
644 }
645
646 public function get_site_settings() {
647 if ( empty( $this->settings ) ) {
648 $this->settings = $this->prepare_site_settings();
649 }
650
651 return $this->settings;
652 }
653
654 private function prepare_site_settings() {
655 $is_multisite = is_multisite();
656 if ( ! $is_multisite ) {
657 // Make sure the new default settings are included into the old configs.
658 $site_settings = get_option( self::$settings_option_id, array() );
659 return wp_parse_args( $this->ensure_array( $site_settings ), $this->get_defaults() );
660 }
661
662 $network_settings = get_site_option( self::$settings_option_id, array() );
663 $network_settings = $this->ensure_array( $network_settings );
664 $network_settings = wp_parse_args( $network_settings, $this->get_defaults() );
665 if ( $this->is_network_enabled() ) {
666 return $network_settings;
667 }
668
669 $subsite_modules = $this->get_activated_subsite_modules();
670 $network_modules = array_diff( $this->get_modules(), $subsite_modules );
671 if ( in_array( self::$lazy_preload_module_name, $network_modules, true ) ) {
672 // Lazy & preload modules include 2 modules: lazy_load and preload.
673 $network_modules[] = 'preload';
674 }
675 $subsite_settings = get_option( self::$settings_option_id, array() );
676 $subsite_settings = $this->ensure_array( $subsite_settings );
677
678 foreach ( $network_modules as $key ) {
679 // Remove values that are network wide from subsite settings.
680 $get_module_fields = "get_{$key}_fields";
681 if ( method_exists( $this, $get_module_fields ) ) {
682 $subsite_settings = array_diff_key( $subsite_settings, array_flip( $this->$get_module_fields() ) );
683 }
684 }
685
686 // And append subsite settings to the site settings.
687 $network_settings = array_merge( $network_settings, $subsite_settings );
688
689 return $network_settings;
690 }
691
692 /**
693 * Ensure the input is an array.
694 *
695 * @param mixed $array_value Array value.
696 * @return array
697 */
698 private function ensure_array( $array_value ) {
699 return empty( $array_value ) || ! is_array( $array_value )
700 ? array()
701 : $array_value;
702 }
703
704 /**
705 * Getter method for $settings.
706 *
707 * @since 3.0
708 *
709 * @param string $setting Setting to get. Default: get all settings.
710 *
711 * @return array|bool Return either a setting value or array of settings.
712 */
713 public function get( $setting = '' ) {
714 $settings = $this->get_site_settings();
715
716 if ( 'lossy' === $setting && isset( $settings['lossy'] ) ) {
717 return $this->sanitize_lossy_level( $settings['lossy'] );
718 }
719
720 if ( ! empty( $setting ) ) {
721 return isset( $settings[ $setting ] ) ? $settings[ $setting ] : false;
722 }
723
724 return $settings;
725 }
726
727 /**
728 * Setter method for $settings.
729 *
730 * @since 3.0
731 *
732 * @param string $setting Setting to update.
733 * @param bool $value Value to set. Default: false.
734 */
735 public function set( $setting = '', $value = false ) {
736 if ( empty( $setting ) ) {
737 return;
738 }
739
740 $this->update_site_settings( array( $setting => $value ) );
741 }
742
743 public function delete( $setting ) {
744 if ( empty( $setting ) ) {
745 return;
746 }
747
748 $settings = $this->get_site_settings();
749 if ( isset( $settings[ $setting ] ) ) {
750 unset( $settings[ $setting ] );
751 $this->update_site_settings( $settings );
752 }
753 }
754
755 /**
756 * Get all Smush settings, based on if network settings are enabled or not.
757 *
758 * @param string $name Setting to fetch.
759 * @param mixed $default Default value.
760 *
761 * @return bool|mixed
762 */
763 public function get_setting( $name = '', $default = false ) {
764 if ( empty( $name ) ) {
765 return false;
766 }
767
768 if ( ! is_multisite() ) {
769 return get_option( $name, $default );
770 }
771
772 $global = $this->is_network_setting( $name );
773 $global_settings = get_site_option( $name, $default );
774 if ( $global ) {
775 return $global_settings;
776 }
777
778 $subsite_settings = get_option( $name, $default );
779 $subsite_settings = false !== $subsite_settings ? $subsite_settings : $global_settings;
780
781 return $subsite_settings;
782 }
783
784 /**
785 * Update value for given setting key
786 *
787 * @param string $name Key.
788 * @param mixed $value Value.
789 *
790 * @return bool If the setting was updated or not
791 */
792 public function set_setting( $name = '', $value = '' ) {
793 if ( empty( $name ) ) {
794 return false;
795 }
796
797 if ( self::$settings_option_id === $name ) {
798 return $this->update_site_settings( $value );
799 }
800
801 return $this->update_site_option( $name, $value );
802 }
803
804 private function update_site_option( $name, $value ) {
805 $global = $this->is_network_setting( $name );
806
807 return $global ? update_site_option( $name, $value ) : update_option( $name, $value );
808 }
809
810 /**
811 * Delete the given key name.
812 *
813 * @param string $name Key.
814 *
815 * @return bool If the setting was updated or not
816 */
817 public function delete_setting( $name = '' ) {
818 if ( empty( $name ) ) {
819 return false;
820 }
821
822 $global = $this->is_network_setting( $name );
823
824 return $global ? delete_site_option( $name ) : delete_option( $name );
825 }
826
827 /**
828 * Reset settings to defaults.
829 *
830 * @since 3.2.0
831 */
832 public function reset() {
833 check_ajax_referer( 'wp_smush_reset' );
834
835 // Check capability.
836 if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
837 wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
838 }
839
840 delete_site_option( self::$subsite_controls_option_id );
841 delete_site_option( 'wp-smush-webp_hide_wizard' );
842 delete_site_option( 'wp-smush-preset_configs' );
843
844 // Reset rating notification flags.
845 $this->delete_setting( 'wp-smush-rating-status' );
846
847 $this->delete_setting( 'wp-smush-image_sizes' );
848 $this->delete_setting( 'wp-smush-resize_sizes' );
849 $this->delete_setting( 'wp-smush-cdn_status' );
850 $this->delete_setting( 'wp-smush-lazy_load' );
851 $this->delete_setting( 'wp-smush-cdn-advanced-settings' );
852 $this->delete_setting( 'wp-smush-hide-tutorials' );
853 $this->delete_setting( self::$dir_settings_option_id );
854 delete_option( 'wp-smush-png2jpg-rewrite-rules-flushed' );
855 delete_option( 'wp_smush_scan_slice_size' );
856
857 LCP_Helper::delete_all_lcp_data();
858
859 // Delete activity log notifications.
860 delete_option( 'wp_smush_notifications' );
861
862 // Delete dismissed notices.
863 delete_option( 'wp-smush-dismissed-notices' );
864
865 // We used update_option for skip-smush-setup,
866 // so let's reset it with delete_option instead of delete_site_option for MU site.
867 delete_option( 'skip-smush-setup' );
868
869 // Reset site settings.
870 $this->reset_site_settings();
871
872 // Reset sub-sites.
873 $this->reset_sub_sites();
874
875 wp_send_json_success();
876 }
877
878 private function reset_site_settings() {
879 $this->delete_setting( self::$settings_option_id );
880 $this->reset_cache_site_settings();
881 // The action wp_smush_settings_updated only triggers after option is updated, does not trigger on add_(site_)option.
882 // So to support this, we need to add the default option first.
883 $this->add_default_site_settings();
884 }
885
886 private function add_default_site_settings() {
887 $this->update_site_settings( $this->get_defaults() );
888 }
889
890 public function initial_default_site_settings() {
891 if ( false === $this->get_setting( self::$settings_option_id, false ) ) {
892 $this->add_default_site_settings();
893 }
894 }
895
896 private function reset_sub_sites() {
897 if ( ! is_multisite() ) {
898 return;
899 }
900
901 $site_args = array(
902 'fields' => 'ids',
903 'public' => 1,
904 'number' => 250, // Limit to 250 sites to avoid performance issues.
905 );
906
907 $site_ids = get_sites( $site_args );
908 if ( empty( $site_ids ) ) {
909 return;
910 }
911
912 foreach ( $site_ids as $site_id ) {
913 switch_to_blog( $site_id );
914 $this->reset_sub_site_settings();
915 restore_current_blog();
916 }
917 }
918
919 private function reset_sub_site_settings() {
920 delete_option( self::$settings_option_id );
921 delete_option( 'wp-smush-image_sizes' );
922 delete_option( 'wp-smush-resize_sizes' );
923 delete_option( 'wp-smush-cdn_status' );
924 delete_option( 'wp-smush-lazy_load' );
925 delete_option( 'wp-smush-cdn-advanced-settings' );
926 delete_option( 'wp-smush-hide-tutorials' );
927 delete_option( 'skip-smush-setup' );
928 delete_option( 'wp_smush_scan_slice_size' );
929
930 LCP_Helper::delete_all_lcp_data();
931 }
932
933 /**
934 * Save settings.
935 *
936 * @since 3.8.6
937 */
938 public function save_settings() {
939 check_ajax_referer( 'wp-smush-ajax' );
940
941 if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
942 wp_send_json_error(
943 array(
944 'message' => esc_html__( "You don't have permission to do this.", 'wp-smushit' ),
945 )
946 );
947 }
948
949 // Delete S3 alert flag, if S3 option is disabled again.
950 if ( ! isset( $_POST['wp-smush-s3'] ) && isset( $settings['integration']['s3'] ) && $settings['integration']['s3'] ) {
951 delete_site_option( 'wp-smush-hide_s3support_alert' );
952 }
953
954 $page = filter_input( INPUT_POST, 'page', FILTER_SANITIZE_SPECIAL_CHARS );
955
956 if ( ! isset( $page ) ) {
957 wp_send_json_error(
958 array( 'message' => __( 'The page these settings belong to is missing.', 'wp-smushit' ) )
959 );
960 }
961
962 $new_settings = array();
963 $status = array(
964 'is_outdated_stats' => false,
965 'page' => $page,
966 );
967
968 if ( 'bulk' === $page ) {
969 foreach ( $this->get_bulk_fields() as $field ) {
970 if ( ! isset( $this->get_defaults()[ $field ] ) ) {
971 continue;
972 }
973 if ( 'lossy' == $field ) {
974 $new_settings['lossy'] = filter_input( INPUT_POST, $field, FILTER_SANITIZE_NUMBER_INT );
975 continue;
976 }
977 $new_settings[ $field ] = (bool) filter_input( INPUT_POST, $field, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
978 }
979 $this->parse_bulk_settings();
980 }
981
982 if ( 'lazy-load' === $page ) {
983 $this->parse_lazy_load_settings();
984 $new_settings['auto_resizing'] = (bool) filter_input( INPUT_POST, 'auto_resizing', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
985 $new_settings['image_dimensions'] = (bool) filter_input( INPUT_POST, 'image_dimensions', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
986 } elseif ( 'preload' === $page ) {
987 $preload_images = filter_input( INPUT_POST, 'preload_images', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
988 $new_settings['preload_images'] = (bool) $preload_images;
989 $this->parse_preload_settings();
990 }
991
992 if ( 'cdn' === $page ) {
993 foreach ( $this->get_cdn_fields() as $field ) {
994 // Skip the module enable/disable option.
995 if ( 'cdn' === $field ) {
996 continue;
997 }
998
999 if ( self::$next_gen_cdn_key === $field ) {
1000 $new_settings[ self::$next_gen_cdn_key ] = $this->parse_next_gen_cdn_from_input();
1001 continue;
1002 }
1003
1004 $new_settings[ $field ] = (bool) filter_input( INPUT_POST, $field, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
1005 }
1006 $this->parse_cdn_settings();
1007 }
1008
1009 if ( 'next-gen' === $page ) {
1010 $this->parse_next_gen_settings();
1011 // Check whether Next-Gen Formats have changed (WebP <-> AVIF).
1012 $status['next_gen_format_changed'] = did_action( 'wp_smush_next_gen_after_format_switch' );
1013 // Check whether WebP method is changed (Direct Conversion <-> Server Configuration).
1014 $status['webp_method_changed'] = did_action( 'wp_smush_webp_method_changed' );
1015 }
1016
1017 if ( 'integrations' === $page ) {
1018 foreach ( $this->get_integrations_fields() as $field ) {
1019 $new_settings[ $field ] = (bool) filter_input( INPUT_POST, $field, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
1020 }
1021 }
1022
1023 if ( 'settings' === $page ) {
1024 $tab = filter_input( INPUT_POST, 'tab', FILTER_SANITIZE_SPECIAL_CHARS );
1025 if ( ! isset( $tab ) ) {
1026 wp_send_json_error(
1027 array( 'message' => __( 'The tab these settings belong to is missing.', 'wp-smushit' ) )
1028 );
1029 }
1030
1031 if ( 'general' === $tab ) {
1032 $new_settings['usage'] = (bool) filter_input( INPUT_POST, 'usage', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
1033 $new_settings['image_dimensions'] = (bool) filter_input( INPUT_POST, 'image_dimensions', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
1034 }
1035 if ( 'permissions' === $tab ) {
1036 $new_settings['networkwide'] = $this->parse_access_settings();
1037 }
1038 if ( 'data' === $tab ) {
1039 $new_settings['keep_data'] = (bool) filter_input( INPUT_POST, 'keep_data', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
1040 }
1041 if ( 'accessibility' === $tab ) {
1042 $new_settings['accessible_colors'] = (bool) filter_input( INPUT_POST, 'accessible_colors', FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE );
1043 }
1044 }
1045
1046 $this->update_site_settings( $new_settings );
1047 $status['is_outdated_stats'] = Global_Stats::get()->is_outdated();
1048 wp_send_json_success( $status );
1049 }
1050
1051 private function parse_next_gen_cdn_from_input() {
1052 $cdn_next_gen_mode = filter_input( INPUT_POST, 'next-gen-cdn', FILTER_VALIDATE_INT );
1053
1054 return $this->sanitize_cdn_next_gen_conversion_mode( $cdn_next_gen_mode );
1055 }
1056
1057 /**
1058 * Parse bulk Smush specific settings.
1059 *
1060 * Nonce processed in parent method.
1061 *
1062 * @since 3.2.0 Moved from save method.
1063 */
1064 private function parse_bulk_settings() {
1065 // Save the selected image sizes.
1066 if ( isset( $_POST['wp-smush-auto-image-sizes'] ) && 'all' === $_POST['wp-smush-auto-image-sizes'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1067 $this->delete_setting( 'wp-smush-image_sizes' );
1068 } else {
1069 if ( ! isset( $_POST['wp-smush-image_sizes'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1070 $image_sizes = array();
1071 } else {
1072 $image_sizes = array_filter( array_map( 'sanitize_text_field', wp_unslash( $_POST['wp-smush-image_sizes'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1073 }
1074
1075 $this->set_setting( 'wp-smush-image_sizes', $image_sizes );
1076 }
1077
1078 // Update Resize width and height settings if set.
1079 $resize_sizes['width'] = isset( $_POST['wp-smush-resize_width'] ) ? (int) $_POST['wp-smush-resize_width'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
1080 $resize_sizes['height'] = isset( $_POST['wp-smush-resize_height'] ) ? (int) $_POST['wp-smush-resize_height'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing
1081
1082 $this->set_setting( 'wp-smush-resize_sizes', $resize_sizes );
1083 }
1084
1085 /**
1086 * Parse CDN specific settings.
1087 *
1088 * @since 3.2.0 Moved from save method.
1089 */
1090 private function parse_cdn_settings() {
1091 // $status = connect to CDN.
1092 if ( ! CDN_Helper::get_instance()->is_cdn_active() ) {
1093 $response = WP_Smush::get_instance()->api()->enable();
1094
1095 // Probably an exponential back-off.
1096 if ( is_wp_error( $response ) ) {
1097 sleep( 1 ); // This is needed so we don't trigger the 597 API response.
1098 $response = WP_Smush::get_instance()->api()->enable( true );
1099 }
1100
1101 // Logged error inside API.
1102 if ( ! is_wp_error( $response ) ) {
1103 $response = json_decode( $response['body'] );
1104 $this->set_setting( 'wp-smush-cdn_status', $response->data );
1105 }
1106 }
1107
1108 $cdn_advanced_settings = $this->get_setting( 'wp-smush-cdn-advanced-settings', array() );
1109 if ( isset( $_POST['excluded-keywords'] ) ) {
1110 $exclusion_keywords = filter_input(
1111 INPUT_POST,
1112 'excluded-keywords',
1113 FILTER_CALLBACK,
1114 array(
1115 'options' => 'sanitize_text_field',
1116 )
1117 );
1118
1119 $exclusion_keywords = preg_split( '/[\r\n\t ]+/', trim( $exclusion_keywords ) );
1120 $cdn_advanced_settings['excluded-keywords'] = $exclusion_keywords;
1121
1122 $this->set_setting( 'wp-smush-cdn-advanced-settings', $cdn_advanced_settings );
1123 }
1124 }
1125
1126 /**
1127 * Parse lazy loading specific settings.
1128 *
1129 * @since 3.2.0
1130 */
1131 private function parse_lazy_load_settings() {
1132 $previous_settings = $this->get_setting( 'wp-smush-lazy_load' );
1133
1134 $args = array(
1135 'format' => array(
1136 'filter' => FILTER_VALIDATE_BOOLEAN,
1137 'flags' => FILTER_REQUIRE_ARRAY,
1138 ),
1139 'output' => array(
1140 'filter' => FILTER_VALIDATE_BOOLEAN,
1141 'flags' => FILTER_REQUIRE_ARRAY,
1142 ),
1143 'include' => array(
1144 'filter' => FILTER_VALIDATE_BOOLEAN,
1145 'flags' => FILTER_REQUIRE_ARRAY,
1146 ),
1147 'exclude-pages' => array(
1148 'filter' => FILTER_CALLBACK,
1149 'options' => 'sanitize_text_field',
1150 ),
1151 'exclude-classes' => array(
1152 'filter' => FILTER_CALLBACK,
1153 'options' => 'sanitize_text_field',
1154 ),
1155 'footer' => FILTER_VALIDATE_BOOLEAN,
1156 'native' => FILTER_VALIDATE_BOOLEAN,
1157 'noscript_fallback' => FILTER_VALIDATE_BOOLEAN,
1158 );
1159
1160 $settings = filter_input_array( INPUT_POST, $args );
1161
1162 // Verify lazyload.
1163 if ( ! empty( $_POST['animation'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
1164 $settings['animation'] = map_deep( wp_unslash( $_POST['animation'] ), 'sanitize_text_field' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1165 }
1166
1167 // Fade-in settings.
1168 $settings['animation']['fadein']['duration'] = 0;
1169 if ( isset( $settings['animation']['duration'] ) ) {
1170 $settings['animation']['fadein']['duration'] = absint( $settings['animation']['duration'] );
1171 unset( $settings['animation']['duration'] );
1172 }
1173
1174 $settings['animation']['fadein']['delay'] = 0;
1175 if ( isset( $settings['animation']['delay'] ) ) {
1176 $settings['animation']['fadein']['delay'] = absint( $settings['animation']['delay'] );
1177 unset( $settings['animation']['delay'] );
1178 }
1179
1180 /**
1181 * Spinner and placeholder settings.
1182 */
1183 $items = array( 'spinner', 'placeholder' );
1184 foreach ( $items as $item ) {
1185 $settings['animation'][ $item ]['selected'] = isset( $settings['animation']["$item-icon"] ) ? $settings['animation']["$item-icon"] : 1;
1186 unset( $settings['animation']["$item-icon"] );
1187
1188 // Custom spinners.
1189 if ( ! isset( $previous_settings['animation'][ $item ]['custom'] ) || ! is_array( $previous_settings['animation'][ $item ]['custom'] ) ) {
1190 $settings['animation'][ $item ]['custom'] = array();
1191 } else {
1192 // Remove empty values.
1193 $settings['animation'][ $item ]['custom'] = array_filter( $previous_settings['animation'][ $item ]['custom'] );
1194 }
1195
1196 // Add uploaded custom spinner.
1197 if ( isset( $settings['animation']["custom-$item"] ) ) {
1198 if ( ! empty( $settings['animation']["custom-$item"] ) && ! in_array( $settings['animation']["custom-$item"], $settings['animation'][ $item ]['custom'], true ) ) {
1199 $settings['animation'][ $item ]['custom'][] = $settings['animation']["custom-$item"];
1200 $settings['animation'][ $item ]['selected'] = $settings['animation']["custom-$item"];
1201 }
1202 unset( $settings['animation']["custom-$item"] );
1203 }
1204 }
1205
1206 // Custom color for placeholder.
1207 if ( ! isset( $settings['animation']['color'] ) ) {
1208 $settings['animation']['placeholder']['color'] = $previous_settings['animation']['placeholder']['color'];
1209 } else {
1210 $settings['animation']['placeholder']['color'] = $settings['animation']['color'];
1211 unset( $settings['animation']['color'] );
1212 }
1213
1214 /**
1215 * Exclusion rules.
1216 */
1217 // Convert to array.
1218 if ( ! empty( $settings['exclude-pages'] ) ) {
1219 $settings['exclude-pages'] = preg_split( '/[\r\n\t ]+/', $settings['exclude-pages'] );
1220 } else {
1221 $settings['exclude-pages'] = array();
1222 }
1223 if ( ! empty( $settings['exclude-classes'] ) ) {
1224 $settings['exclude-classes'] = preg_split( '/[\r\n\t ]+/', $settings['exclude-classes'] );
1225 } else {
1226 $settings['exclude-classes'] = array();
1227 }
1228
1229 $this->set_setting( 'wp-smush-lazy_load', $settings );
1230 }
1231
1232 /**
1233 * Parse preload specific settings.
1234 *
1235 * @since 3.20.0
1236 */
1237 private function parse_preload_settings() {
1238
1239 $args = array(
1240 'exclude-pages' => array(
1241 'filter' => FILTER_CALLBACK,
1242 'options' => 'sanitize_text_field',
1243 ),
1244 'lcp_fetchpriority' => FILTER_VALIDATE_BOOLEAN,
1245 );
1246
1247 $settings = filter_input_array( INPUT_POST, $args );
1248
1249 /**
1250 * Exclusion rules.
1251 */
1252 // Convert to array.
1253 if ( ! empty( $settings['exclude-pages'] ) ) {
1254 $settings['exclude-pages'] = array_filter( preg_split( '/[\r\n\t ]+/', $settings['exclude-pages'] ) );
1255 } else {
1256 $settings['exclude-pages'] = array();
1257 }
1258
1259 $this->set_setting( 'wp-smush-preload', $settings );
1260 }
1261
1262 private function parse_next_gen_settings() {
1263 $next_gen_manager = Next_Gen_Manager::get_instance();
1264
1265 $next_gen_format = filter_input( INPUT_POST, 'next-gen-format', FILTER_SANITIZE_SPECIAL_CHARS );
1266 $next_gen_method = filter_input( INPUT_POST, 'next-gen-method', FILTER_SANITIZE_SPECIAL_CHARS );
1267 $next_gen_manager->activate_format( $next_gen_format );
1268 $next_gen_configuration = $next_gen_manager->get_active_format_configuration();
1269
1270 // Update Next-Gen method.
1271 $next_gen_configuration->set_next_gen_method( $next_gen_method );
1272 // Update Next-Gen fallback.
1273 if ( $next_gen_configuration->direct_conversion_enabled() ) {
1274 $next_gen_fallback_active = filter_input( INPUT_POST, 'next-gen-fallback', FILTER_VALIDATE_BOOLEAN );
1275 $next_gen_configuration->set_next_gen_fallback( (bool) $next_gen_fallback_active );
1276 }
1277 }
1278
1279 /**
1280 * Parse access control settings on multisite.
1281 *
1282 * @since 3.2.2
1283 *
1284 * @return mixed
1285 */
1286 private function parse_access_settings() {
1287 $current_value = get_site_option( self::$subsite_controls_option_id );
1288
1289 $new_value = filter_input( INPUT_POST, 'wp-smush-subsite-access', FILTER_SANITIZE_SPECIAL_CHARS );
1290 $access = filter_input( INPUT_POST, 'wp-smush-access', FILTER_SANITIZE_SPECIAL_CHARS, FILTER_REQUIRE_ARRAY );
1291
1292 if ( 'custom' === $new_value ) {
1293 $new_value = $access;
1294 }
1295
1296 if ( $current_value !== $new_value ) {
1297 update_site_option( self::$subsite_controls_option_id, $new_value );
1298 }
1299
1300 return $new_value;
1301 }
1302
1303 /**
1304 * Apply a default configuration to lazy loading on first activation.
1305 *
1306 * @since 3.2.0
1307 */
1308 public function init_lazy_load_defaults() {
1309 $defaults = $this->get_lazy_load_defaults();
1310
1311 $this->set_setting( 'wp-smush-lazy_load', $defaults );
1312 }
1313
1314 /**
1315 * Check if in network admin.
1316 *
1317 * The is_network_admin() check does not work in ajax calls.
1318 *
1319 * @since 3.10.3
1320 *
1321 * @return bool
1322 */
1323 public static function is_ajax_network_admin() {
1324 return defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_SERVER['HTTP_REFERER'] ) && preg_match( '#^' . network_admin_url() . '#i', wp_unslash( $_SERVER['HTTP_REFERER'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1325 }
1326
1327 public function is_optimize_original_images_active() {
1328 return ! empty( self::get_instance()->get( 'original' ) );
1329 }
1330
1331 public function is_png2jpg_module_active() {
1332 return $this->is_module_active( 'png_to_jpg' );
1333 }
1334
1335 public function is_webp_module_active() {
1336 return $this->is_module_active( 'webp_mod' );
1337 }
1338
1339 public function is_avif_module_active() {
1340 return $this->is_module_active( 'avif_mod' );
1341 }
1342
1343 public function is_avif_fallback_active() {
1344 return $this->is_avif_module_active()
1345 && ! empty( self::get_instance()->get( 'avif_fallback' ) );
1346 }
1347
1348 public function is_resize_module_active() {
1349 return $this->is_module_active( 'resize' );
1350 }
1351
1352 public function is_backup_active() {
1353 return $this->is_module_active( 'backup' );
1354 }
1355
1356 public function is_s3_active() {
1357 return $this->is_module_active( 's3' );
1358 }
1359
1360 public function is_cdn_webp_conversion_active() {
1361 return $this->is_cdn_active()
1362 && self::$webp_cdn_mode === $this->get_cdn_next_gen_conversion_mode();
1363 }
1364
1365 public function is_cdn_avif_conversion_active() {
1366 return $this->is_cdn_active()
1367 && self::$avif_cdn_mode === $this->get_cdn_next_gen_conversion_mode();
1368 }
1369
1370 public function is_cdn_next_gen_conversion_active() {
1371 return $this->is_cdn_active()
1372 && ! empty( $this->get_cdn_next_gen_conversion_mode() );
1373 }
1374
1375 public function get_cdn_next_gen_conversion_mode() {
1376 $cdn_next_gen_mode = (int) self::get_instance()->get( self::$next_gen_cdn_key );
1377
1378 return $this->sanitize_cdn_next_gen_conversion_mode( $cdn_next_gen_mode );
1379 }
1380
1381 public function get_cdn_next_gen_conversion_label( $cdn_next_gen_mode ) {
1382 $cdn_next_gen_mode = $this->sanitize_cdn_next_gen_conversion_mode( $cdn_next_gen_mode );
1383 $cdn_next_gen_modes = $this->get_cdn_next_gen_modes();
1384
1385 return $cdn_next_gen_modes[ $cdn_next_gen_mode ];
1386 }
1387
1388 public function sanitize_cdn_next_gen_conversion_mode( $cdn_next_gen_mode ) {
1389 $cdn_next_gen_mode = (int) $cdn_next_gen_mode;
1390 $cdn_next_gen_modes = $this->get_cdn_next_gen_modes();
1391
1392 if ( ! isset( $cdn_next_gen_modes[ $cdn_next_gen_mode ] ) ) {
1393 $cdn_next_gen_mode = self::$none_cdn_mode;
1394 }
1395
1396 return $cdn_next_gen_mode;
1397 }
1398
1399 private function get_cdn_next_gen_modes() {
1400 return array(
1401 self::$none_cdn_mode => __( 'None', 'wp-smushit' ),
1402 self::$webp_cdn_mode => __( 'WebP', 'wp-smushit' ),
1403 self::$avif_cdn_mode => __( 'AVIF', 'wp-smushit' ),
1404 );
1405 }
1406
1407 public function is_webp_direct_conversion_active() {
1408 return $this->is_webp_module_active()
1409 && ! empty( self::get_instance()->get( 'webp_direct_conversion' ) );
1410 }
1411
1412 public function is_automatic_compression_active() {
1413 return self::get_instance()->get( 'auto' );
1414 }
1415
1416 public function is_cdn_active() {
1417 return $this->is_module_active( 'cdn' );
1418 }
1419
1420 public function is_webp_fallback_active() {
1421 return $this->is_webp_module_active()
1422 && ! empty( self::get_instance()->get( 'webp_fallback' ) );
1423 }
1424
1425 public function is_lazyload_active() {
1426 return self::get_instance()->get( 'lazy_load' );
1427 }
1428
1429 public function is_auto_resizing_active() {
1430 return $this->is_module_active( 'auto_resizing' );
1431 }
1432
1433 public function should_add_missing_dimensions() {
1434 return self::get_instance()->get( 'image_dimensions' );
1435 }
1436
1437 protected function get_placeholder_modules() {
1438 return array(
1439 'cdn',
1440 'webp_mod',
1441 'avif_mod',
1442 's3',
1443 'nextgen',
1444 'ultra',
1445 'preload_images',
1446 'auto_resizing',
1447 'image_dimensions',
1448 );
1449 }
1450
1451 public function is_module_active( $module ) {
1452 $advanced_modules = $this->get_placeholder_modules();
1453
1454 if ( in_array( $module, $advanced_modules, true ) ) {
1455 return false;
1456 }
1457
1458 return self::get_instance()->get( $module );
1459 }
1460
1461 public function get_lossy_level_setting() {
1462 $current_level = self::get_instance()->get( 'lossy' );
1463 return $this->sanitize_lossy_level( $current_level );
1464 }
1465
1466 public function update_dir_settings( $settings ) {
1467 $this->set_setting( self::$dir_settings_option_id, $settings );
1468 }
1469
1470 public function get_dir_lossy_level_setting() {
1471 $dir_settings = $this->get_setting( self::$dir_settings_option_id, array() );
1472 if ( isset( $dir_settings['dir_lossy'] ) ) {
1473 return $this->sanitize_lossy_level( $dir_settings['dir_lossy'] );
1474 }
1475 // Fallback to global lossy setting
1476 return $this->get_lossy_level_setting();
1477 }
1478
1479 public function get_dir_strip_exif_setting() {
1480 $dir_settings = $this->get_setting( self::$dir_settings_option_id, array() );
1481 if ( isset( $dir_settings['dir_strip_exif'] ) ) {
1482 return (bool) $dir_settings['dir_strip_exif'];
1483 }
1484 // Fallback to global strip_exif setting
1485 return (bool) $this->get( 'strip_exif' );
1486 }
1487
1488 public function sanitize_lossy_level( $lossy_level ) {
1489 $highest_level = $this->get_highest_lossy_level();
1490
1491 if ( $lossy_level > $highest_level ) {
1492 return $highest_level;
1493 }
1494
1495 if ( $lossy_level > self::$level_lossless ) {
1496 return (int) $lossy_level;
1497 }
1498
1499 return self::$level_lossless;
1500 }
1501
1502 public function get_highest_lossy_level() {
1503 if ( is_multisite() && ! Membership::get_instance()->has_access_to_hub() ) {
1504 return self::$level_lossless;
1505 }
1506 return self::$level_super_lossy;
1507 }
1508
1509 public function get_current_lossy_level_label() {
1510 $current_level = $this->get_lossy_level_setting();
1511 return $this->get_lossy_level_label( $current_level );
1512 }
1513
1514 public function get_lossy_level_label( $lossy_level ) {
1515 $smush_modes = array(
1516 self::$level_lossless => __( 'Basic', 'wp-smushit' ),
1517 self::$level_super_lossy => __( 'Super', 'wp-smushit' ),
1518 self::$level_ultra_lossy => __( 'Ultra', 'wp-smushit' ),
1519 );
1520 if ( ! isset( $smush_modes[ $lossy_level ] ) ) {
1521 $lossy_level = self::$level_lossless;
1522 }
1523
1524 return $smush_modes[ $lossy_level ];
1525 }
1526
1527 public function get_large_file_cutoff() {
1528 return apply_filters( 'wp_smush_large_file_cut_off', 32 * 1024 * 1024 );
1529 }
1530
1531 public function has_bulk_smush_page() {
1532 return $this->is_page_active( 'bulk' );
1533 }
1534
1535 public function has_cdn_page() {
1536 return $this->is_page_active( 'cdn' );
1537 }
1538
1539 public function has_webp_page() {
1540 _deprecated_function( __METHOD__, '3.8.0', 'Settings::has_next_gen_page()' );
1541 return $this->has_next_gen_page();
1542 }
1543
1544 public function has_next_gen_page() {
1545 return $this->is_page_active( 'next-gen' );
1546 }
1547
1548 public function has_lazy_preload_page() {
1549 return $this->is_page_active( self::$lazy_preload_module_name );
1550 }
1551
1552 public function streaming_enabled() {
1553 if ( defined( 'WP_SMUSH_USE_STREAMS' ) ) {
1554 return (bool) WP_SMUSH_USE_STREAMS;
1555 }
1556
1557 return self::get_instance()->get( 'disable_streams' ) != WP_SMUSH_VERSION;
1558 }
1559
1560 public function is_lcp_preload_enabled() {
1561 return $this->is_module_active( 'preload_images' );
1562 }
1563
1564 private function is_page_active( $page_slug ) {
1565 if ( ! is_multisite() ) {
1566 return true;
1567 }
1568
1569 $module = $this->slug_to_module( $page_slug );
1570 $is_page_active_on_subsite = in_array( $module, $this->get_activated_subsite_modules(), true );
1571
1572 if ( is_network_admin() ) {
1573 return ! $is_page_active_on_subsite;
1574 }
1575
1576 return $is_page_active_on_subsite;
1577 }
1578
1579 private function slug_to_module( $page_slug ) {
1580 return str_replace( '-', '_', $page_slug );
1581 }
1582
1583 /**
1584 * Check if the directory smush module is active.
1585 *
1586 * @return bool
1587 */
1588 public function is_directory_smush_active() {
1589 if ( ! is_multisite() || is_super_admin() ) {
1590 return true;
1591 }
1592
1593 $activated_subsite_modules = $this->get_activated_subsite_modules();
1594
1595 return in_array( 'directory_smush', $activated_subsite_modules, true ) && in_array( 'bulk', $activated_subsite_modules, true );
1596 }
1597
1598 /**
1599 * @return array
1600 */
1601 private function get_activated_subsite_modules() {
1602 if ( ! is_array( $this->activated_subsite_modules ) ) {
1603 $this->activated_subsite_modules = $this->get_activated_subsite_modules_list();
1604 }
1605
1606 return $this->activated_subsite_modules;
1607 }
1608
1609 /**
1610 * @return array
1611 */
1612 public function get_activated_subsite_modules_list() {
1613 $subsite_controls = get_site_option( self::$subsite_controls_option_id );
1614 // None:false|All:1|Custom:array list page modules.
1615 if ( empty( $subsite_controls ) ) {
1616 return array();
1617 }
1618
1619 $subsite_modules = $this->get_subsite_modules();
1620 if ( is_array( $subsite_controls ) ) {
1621 $subsite_modules = $subsite_controls;
1622 }
1623
1624 return $subsite_modules;
1625 }
1626
1627 private function get_subsite_modules() {
1628 return array(
1629 'bulk',
1630 'directory_smush',
1631 'integrations',
1632 self::$lazy_preload_module_name,
1633 'cdn',
1634 );
1635 }
1636
1637 /**
1638 * // TODO: [WPMUDEV SMUSH UI] there is another method above that does the same thing. Merge the two methods.
1639 */
1640 public function is_auto_smush_enabled() {
1641 $auto_smush = $this->get( 'auto' );
1642
1643 // Keep the auto smush on by default.
1644 if ( ! isset( $auto_smush ) ) {
1645 $auto_smush = 1;
1646 }
1647
1648 return $auto_smush;
1649 }
1650
1651 /**
1652 * Get the maximum content width for images.
1653 *
1654 * @return int
1655 */
1656 public function max_content_width() {
1657 // Get global content width (if content width is empty, set 2560).
1658 $content_width = isset( $GLOBALS['content_width'] ) ? (int) $GLOBALS['content_width'] : $this->get_default_size_threshold();
1659
1660 // Avoid situations, when themes misuse the global.
1661 if ( 0 === $content_width ) {
1662 $content_width = $this->get_default_size_threshold();
1663 }
1664
1665 $resize_module_active = $this->is_resize_module_active();
1666 if ( ! $resize_module_active ) {
1667 return $content_width;
1668 }
1669
1670 // Check to see if we are resizing the images (can not go over that value).
1671 $resize_sizes = $this->get_setting( 'wp-smush-resize_sizes' );
1672
1673 if ( isset( $resize_sizes['width'] ) && $resize_sizes['width'] < $content_width ) {
1674 return $resize_sizes['width'];
1675 }
1676
1677 return $content_width;
1678 }
1679
1680 /**
1681 * Get the default size threshold for images.
1682 *
1683 * WordPress sets the default threshold value to 2560 pixels.
1684 *
1685 * @return int
1686 */
1687 public function get_default_size_threshold() {
1688 return apply_filters( 'wp_smush_default_size_threshold', 2560 );
1689 }
1690
1691 /**
1692 * Get avif_cdn_mode.
1693 *
1694 * @return int
1695 */
1696 public static function get_avif_cdn_mode() {
1697 return self::$avif_cdn_mode;
1698 }
1699
1700
1701 /**
1702 * Get lazy_preload_module_name.
1703 *
1704 * @return string
1705 */
1706 public static function get_lazy_preload_module_name() {
1707 return self::$lazy_preload_module_name;
1708 }
1709
1710
1711 /**
1712 * Get level_lossless.
1713 *
1714 * @return int
1715 */
1716 public static function get_level_lossless() {
1717 return self::$level_lossless;
1718 }
1719
1720 /**
1721 * Mark the current setting as level lossless.
1722 */
1723 public static function set_lossless_level() {
1724 return self::get_instance()->set( 'lossy', self::get_level_lossless() );
1725 }
1726
1727
1728 /**
1729 * Get level_super_lossy.
1730 *
1731 * @return int
1732 */
1733 public static function get_level_super_lossy() {
1734 return self::$level_super_lossy;
1735 }
1736
1737
1738 /**
1739 * Get level_ultra_lossy.
1740 *
1741 * @return int
1742 */
1743 public static function get_level_ultra_lossy() {
1744 return self::$level_ultra_lossy;
1745 }
1746
1747
1748 /**
1749 * Get next_gen_cdn_key.
1750 *
1751 * @return string
1752 */
1753 public static function get_next_gen_cdn_key() {
1754 return self::$next_gen_cdn_key;
1755 }
1756
1757
1758 /**
1759 * Get none_cdn_mode.
1760 *
1761 * @return int
1762 */
1763 public static function get_none_cdn_mode() {
1764 return self::$none_cdn_mode;
1765 }
1766
1767
1768 /**
1769 * Get settings_key.
1770 *
1771 * @return string
1772 */
1773 public static function get_settings_option_id() {
1774 return self::$settings_option_id;
1775 }
1776
1777
1778 /**
1779 * Get subsite_controls_option_key.
1780 *
1781 * @return string
1782 */
1783 public static function get_subsite_controls_option_id() {
1784 return self::$subsite_controls_option_id;
1785 }
1786
1787
1788 /**
1789 * Get webp_cdn_mode.
1790 *
1791 * @return int
1792 */
1793 public static function get_webp_cdn_mode() {
1794 return self::$webp_cdn_mode;
1795 }
1796
1797 /**
1798 * @return array
1799 */
1800 public function get_lazy_load_defaults() {
1801 $defaults = array(
1802 'format' => array(
1803 'jpeg' => true,
1804 'png' => true,
1805 'webp' => true,
1806 'gif' => true,
1807 'svg' => true,
1808 'iframe' => true,
1809 'embed_video' => false,
1810 ),
1811 'output' => array(
1812 'content' => true,
1813 'widgets' => true,
1814 'thumbnails' => true,
1815 'gravatars' => true,
1816 ),
1817 'animation' => array(
1818 'selected' => 'fadein', // Accepts: fadein, spinner, placeholder, false.
1819 'fadein' => array(
1820 'duration' => 400,
1821 'delay' => 0,
1822 ),
1823 'spinner' => array(
1824 'selected' => 1,
1825 'custom' => array(),
1826 ),
1827 'placeholder' => array(
1828 'selected' => 1,
1829 'custom' => array(),
1830 'color' => '#F3F3F3',
1831 ),
1832 ),
1833 'include' => array(
1834 'frontpage' => true,
1835 'home' => true,
1836 'page' => true,
1837 'single' => true,
1838 'archive' => true,
1839 'category' => true,
1840 'tag' => true,
1841 ),
1842 'exclude-pages' => array(),
1843 'exclude-classes' => array(),
1844 'footer' => true,
1845 'native' => false,
1846 'noscript_fallback' => false,
1847 );
1848 return $defaults;
1849 }
1850
1851 /**
1852 * Get the maximum file size (in bytes) that can be optimized.
1853 *
1854 * @return mixed
1855 */
1856 public function get_file_size_limit() {
1857 $file_size_limit = 5 * 1024 * 1024; // 5 MB
1858 if ( defined( 'WP_SMUSH_MAX_BYTES' ) && WP_SMUSH_MAX_BYTES > 0 ) {
1859 $file_size_limit = min( $file_size_limit, WP_SMUSH_MAX_BYTES );
1860 }
1861 return $file_size_limit;
1862 }
1863 }
1864
1865