<?php
/**
* Settings schema.
*
* @package PoweredCache
*/
namespace PoweredCache;
/**
* Read-only schema for existing Powered Cache settings.
*
* The schema intentionally describes the current option array without changing
* storage or save behavior. It is the foundation for 4.0 migrations, REST
* responses, UI grouping, and recommended mode.
*
* @since 4.0.0
*/
class SettingsSchema {
const TYPE_BOOLEAN = 'boolean';
const TYPE_INTEGER = 'integer';
const TYPE_STRING = 'string';
const TYPE_ARRAY = 'array';
const TYPE_ENUM = 'enum';
const SANITIZE_BOOLEAN = 'boolean';
const SANITIZE_INTEGER = 'integer';
const SANITIZE_TEXT = 'text';
const SANITIZE_TEXTAREA = 'textarea';
const SANITIZE_ARRAY = 'array';
const SANITIZE_ENUM = 'enum';
const SANITIZE_CSS = 'css';
/**
* Return the schema for all current settings.
*
* @param array $context Runtime context used by dynamic defaults.
*
* @return array<string,array<string,mixed>>
*/
public static function fields( array $context = array() ) {
$is_apache = isset( $context['is_apache'] ) ? (bool) $context['is_apache'] : false;
$object_cache_backends = self::object_cache_backends( $context );
$object_cache_backend_enum = array_merge( array( 'off' ), $object_cache_backends );
return array(
'enable_page_cache' => self::field( self::TYPE_BOOLEAN, true, 'cache', self::SANITIZE_BOOLEAN ),
'object_cache' => self::field( self::TYPE_ENUM, 'off', 'cache', self::SANITIZE_ENUM, false, array(), $object_cache_backend_enum ),
'cache_mobile' => self::field( self::TYPE_BOOLEAN, true, 'cache', self::SANITIZE_BOOLEAN ),
'cache_mobile_separate_file' => self::field( self::TYPE_BOOLEAN, false, 'cache', self::SANITIZE_BOOLEAN, false, array( 'cache_mobile' ) ),
'loggedin_user_cache' => self::field( self::TYPE_BOOLEAN, false, 'cache', self::SANITIZE_BOOLEAN ),
'ssl_cache' => self::field( self::TYPE_BOOLEAN, true, 'cache', self::SANITIZE_BOOLEAN, false, array(), array(), true ),
'gzip_compression' => self::field( self::TYPE_BOOLEAN, false, 'cache', self::SANITIZE_BOOLEAN ),
'cache_timeout' => self::field( self::TYPE_INTEGER, 1440, 'cache', self::SANITIZE_INTEGER ),
'auto_configure_htaccess' => self::field( self::TYPE_BOOLEAN, $is_apache, 'advanced', self::SANITIZE_BOOLEAN ),
'rejected_user_agents' => self::field( self::TYPE_STRING, '', 'advanced', self::SANITIZE_TEXTAREA ),
'rejected_cookies' => self::field( self::TYPE_STRING, '', 'advanced', self::SANITIZE_TEXTAREA ),
'rejected_referrers' => self::field( self::TYPE_STRING, '', 'advanced', self::SANITIZE_TEXTAREA ),
'vary_cookies' => self::field( self::TYPE_STRING, '', 'advanced', self::SANITIZE_TEXTAREA ),
'rejected_uri' => self::field( self::TYPE_STRING, '', 'advanced', self::SANITIZE_TEXTAREA ),
'ignored_query_strings' => self::field( self::TYPE_STRING, '', 'advanced', self::SANITIZE_TEXTAREA ),
'cache_query_strings' => self::field( self::TYPE_STRING, '', 'advanced', self::SANITIZE_TEXTAREA ),
'purge_additional_pages' => self::field( self::TYPE_STRING, '', 'advanced', self::SANITIZE_TEXTAREA ),
'minify_html' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN ),
'minify_html_dom_optimization' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN, false, array( 'minify_html' ) ),
'combine_google_fonts' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN ),
'swap_google_fonts_display' => self::field( self::TYPE_BOOLEAN, true, 'file_optimization', self::SANITIZE_BOOLEAN ),
'use_bunny_fonts' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN ),
'self_host_google_fonts' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN, true ),
'minify_css' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN ),
'combine_css' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN ),
'critical_css' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN, true ),
'critical_css_additional_files' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_TEXTAREA, true, array( 'critical_css' ) ),
'critical_css_excluded_files' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_TEXTAREA, true, array( 'critical_css' ) ),
'critical_css_appended_content' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_CSS, true, array( 'critical_css' ) ),
'critical_css_fallback' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_CSS, true, array( 'critical_css' ) ),
'excluded_css_files' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_TEXTAREA ),
'remove_unused_css' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN, true ),
'ucss_safelist' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_TEXTAREA, true, array( 'remove_unused_css' ) ),
'ucss_excluded_files' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_TEXTAREA, true, array( 'remove_unused_css' ) ),
'css_optimization_disabled_sources' => self::field( self::TYPE_ARRAY, array(), 'file_optimization', self::SANITIZE_ARRAY, true ),
'minify_js' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN ),
'combine_js' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN ),
'excluded_js_files' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_TEXTAREA ),
'js_execution_method' => self::field( self::TYPE_ENUM, 'blocking', 'file_optimization', self::SANITIZE_ENUM, false, array(), array( 'blocking', 'async', 'defer', 'delay', 'delayed' ), true ),
'js_defer' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN ),
'js_defer_exclusions' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_TEXTAREA, false, array( 'js_defer' ) ),
'js_delay' => self::field( self::TYPE_BOOLEAN, false, 'file_optimization', self::SANITIZE_BOOLEAN ),
'js_delay_exclusions' => self::field( self::TYPE_STRING, '', 'file_optimization', self::SANITIZE_TEXTAREA, false, array( 'js_delay' ) ),
'js_delay_timeout' => self::field( self::TYPE_INTEGER, 0, 'file_optimization', self::SANITIZE_INTEGER, false, array( 'js_delay' ) ),
'js_execution_optimized_only' => self::field( self::TYPE_BOOLEAN, true, 'file_optimization', self::SANITIZE_BOOLEAN, false, array(), array(), true ),
'rewrite_file_optimizer' => self::field( self::TYPE_BOOLEAN, $is_apache, 'file_optimization', self::SANITIZE_BOOLEAN ),
'enable_image_optimization' => self::field( self::TYPE_BOOLEAN, false, 'media', self::SANITIZE_BOOLEAN, true ),
'image_optimizer_preferred_format' => self::field( self::TYPE_STRING, '', 'media', self::SANITIZE_TEXT, true, array( 'enable_image_optimization' ) ),
'add_missing_image_dimensions' => self::field( self::TYPE_BOOLEAN, false, 'media', self::SANITIZE_BOOLEAN, true ),
'enable_lazy_load' => self::field( self::TYPE_BOOLEAN, false, 'media', self::SANITIZE_BOOLEAN ),
'lazy_load_post_content' => self::field( self::TYPE_BOOLEAN, true, 'media', self::SANITIZE_BOOLEAN, false, array( 'enable_lazy_load' ) ),
'lazy_load_images' => self::field( self::TYPE_BOOLEAN, true, 'media', self::SANITIZE_BOOLEAN, false, array( 'enable_lazy_load' ) ),
'lazy_load_background_images' => self::field( self::TYPE_BOOLEAN, false, 'media', self::SANITIZE_BOOLEAN, false, array( 'enable_lazy_load' ) ),
'lazy_load_iframes' => self::field( self::TYPE_BOOLEAN, true, 'media', self::SANITIZE_BOOLEAN, false, array( 'enable_lazy_load' ) ),
'lazy_load_widgets' => self::field( self::TYPE_BOOLEAN, true, 'media', self::SANITIZE_BOOLEAN, false, array( 'enable_lazy_load' ) ),
'lazy_load_post_thumbnail' => self::field( self::TYPE_BOOLEAN, true, 'media', self::SANITIZE_BOOLEAN, false, array( 'enable_lazy_load' ) ),
'lazy_load_avatars' => self::field( self::TYPE_BOOLEAN, true, 'media', self::SANITIZE_BOOLEAN, false, array( 'enable_lazy_load' ) ),
'lazy_load_youtube' => self::field( self::TYPE_BOOLEAN, false, 'media', self::SANITIZE_BOOLEAN, false, array( 'enable_lazy_load' ) ),
'lazy_load_skip_first_nth_img' => self::field( self::TYPE_INTEGER, 3, 'media', self::SANITIZE_INTEGER, false, array( 'enable_lazy_load' ) ),
'lazy_load_exclusions' => self::field( self::TYPE_STRING, '', 'media', self::SANITIZE_TEXTAREA, false, array( 'enable_lazy_load' ) ),
'disable_wp_lazy_load' => self::field( self::TYPE_BOOLEAN, false, 'media', self::SANITIZE_BOOLEAN ),
'disable_wp_embeds' => self::field( self::TYPE_BOOLEAN, false, 'media', self::SANITIZE_BOOLEAN ),
'disable_emoji_scripts' => self::field( self::TYPE_BOOLEAN, false, 'media', self::SANITIZE_BOOLEAN ),
'enable_cdn' => self::field( self::TYPE_BOOLEAN, false, 'cdn', self::SANITIZE_BOOLEAN ),
'cdn_hostname' => self::field( self::TYPE_ARRAY, array( '' ), 'cdn', self::SANITIZE_ARRAY, false, array( 'enable_cdn' ) ),
'cdn_zone' => self::field( self::TYPE_ARRAY, array( '' ), 'cdn', self::SANITIZE_ARRAY, false, array( 'enable_cdn' ) ),
'cdn_rejected_files' => self::field( self::TYPE_STRING, '', 'cdn', self::SANITIZE_TEXTAREA, false, array( 'enable_cdn' ) ),
'enable_cache_preload' => self::field( self::TYPE_BOOLEAN, false, 'preload', self::SANITIZE_BOOLEAN ),
'preload_homepage' => self::field( self::TYPE_BOOLEAN, true, 'preload', self::SANITIZE_BOOLEAN, false, array( 'enable_cache_preload' ) ),
'preload_public_posts' => self::field( self::TYPE_BOOLEAN, true, 'preload', self::SANITIZE_BOOLEAN, false, array( 'enable_cache_preload' ) ),
'preload_public_tax' => self::field( self::TYPE_BOOLEAN, true, 'preload', self::SANITIZE_BOOLEAN, false, array( 'enable_cache_preload' ) ),
'enable_sitemap_preload' => self::field( self::TYPE_BOOLEAN, false, 'preload', self::SANITIZE_BOOLEAN, true ),
'preload_request_interval' => self::field( self::TYPE_INTEGER, 2, 'preload', self::SANITIZE_INTEGER ),
'preload_sitemap' => self::field( self::TYPE_STRING, '', 'preload', self::SANITIZE_TEXTAREA, true, array( 'enable_sitemap_preload' ) ),
'prefetch_dns' => self::field( self::TYPE_STRING, '', 'preload', self::SANITIZE_TEXTAREA ),
'preconnect_resource' => self::field( self::TYPE_STRING, '', 'preload', self::SANITIZE_TEXTAREA ),
'preload_fonts' => self::field( self::TYPE_STRING, '', 'preload', self::SANITIZE_TEXTAREA ),
'prefetch_links' => self::field( self::TYPE_BOOLEAN, false, 'preload', self::SANITIZE_BOOLEAN, true ),
'enable_lcp_optimization' => self::field( self::TYPE_BOOLEAN, false, 'preload', self::SANITIZE_BOOLEAN, true ),
'db_cleanup_post_revisions' => self::field( self::TYPE_BOOLEAN, false, 'database', self::SANITIZE_BOOLEAN ),
'db_cleanup_auto_drafts' => self::field( self::TYPE_BOOLEAN, false, 'database', self::SANITIZE_BOOLEAN ),
'db_cleanup_trashed_posts' => self::field( self::TYPE_BOOLEAN, false, 'database', self::SANITIZE_BOOLEAN ),
'db_cleanup_spam_comments' => self::field( self::TYPE_BOOLEAN, false, 'database', self::SANITIZE_BOOLEAN ),
'db_cleanup_trashed_comments' => self::field( self::TYPE_BOOLEAN, false, 'database', self::SANITIZE_BOOLEAN ),
'db_cleanup_expired_transients' => self::field( self::TYPE_BOOLEAN, false, 'database', self::SANITIZE_BOOLEAN ),
'db_cleanup_all_transients' => self::field( self::TYPE_BOOLEAN, false, 'database', self::SANITIZE_BOOLEAN ),
'db_cleanup_optimize_tables' => self::field( self::TYPE_BOOLEAN, false, 'database', self::SANITIZE_BOOLEAN ),
'enable_scheduled_db_cleanup' => self::field( self::TYPE_BOOLEAN, false, 'database', self::SANITIZE_BOOLEAN, true ),
'scheduled_db_cleanup_frequency' => self::field( self::TYPE_ENUM, 'daily', 'database', self::SANITIZE_ENUM, true, array( 'enable_scheduled_db_cleanup' ), array( 'hourly', 'twicedaily', 'daily', 'weekly', 'monthly' ) ),
'enable_cloudflare' => self::field( self::TYPE_BOOLEAN, false, 'integrations', self::SANITIZE_BOOLEAN ),
'cloudflare_api_token' => self::field( self::TYPE_STRING, '', 'integrations', self::SANITIZE_TEXT, false, array( 'enable_cloudflare' ) ),
'cloudflare_email' => self::field( self::TYPE_STRING, '', 'integrations', self::SANITIZE_TEXT, false, array( 'enable_cloudflare' ) ),
'cloudflare_api_key' => self::field( self::TYPE_STRING, '', 'integrations', self::SANITIZE_TEXT, false, array( 'enable_cloudflare' ) ),
'cloudflare_zone' => self::field( self::TYPE_STRING, '', 'integrations', self::SANITIZE_TEXT, false, array( 'enable_cloudflare' ) ),
'enable_sucuri' => self::field( self::TYPE_BOOLEAN, false, 'integrations', self::SANITIZE_BOOLEAN, true ),
'sucuri_api_key' => self::field( self::TYPE_STRING, '', 'integrations', self::SANITIZE_TEXT, true, array( 'enable_sucuri' ) ),
'sucuri_api_secret' => self::field( self::TYPE_STRING, '', 'integrations', self::SANITIZE_TEXT, true, array( 'enable_sucuri' ) ),
'enable_heartbeat' => self::field( self::TYPE_BOOLEAN, false, 'integrations', self::SANITIZE_BOOLEAN ),
'heartbeat_dashboard_status' => self::field( self::TYPE_ENUM, 'enable', 'integrations', self::SANITIZE_ENUM, false, array( 'enable_heartbeat' ), array( 'enable', 'disable', 'modify' ) ),
'heartbeat_dashboard_interval' => self::field( self::TYPE_INTEGER, 60, 'integrations', self::SANITIZE_INTEGER, false, array( 'enable_heartbeat' ) ),
'heartbeat_editor_status' => self::field( self::TYPE_ENUM, 'enable', 'integrations', self::SANITIZE_ENUM, false, array( 'enable_heartbeat' ), array( 'enable', 'disable', 'modify' ) ),
'heartbeat_editor_interval' => self::field( self::TYPE_INTEGER, 15, 'integrations', self::SANITIZE_INTEGER, false, array( 'enable_heartbeat' ) ),
'heartbeat_frontend_status' => self::field( self::TYPE_ENUM, 'enable', 'integrations', self::SANITIZE_ENUM, false, array( 'enable_heartbeat' ), array( 'enable', 'disable', 'modify' ) ),
'heartbeat_frontend_interval' => self::field( self::TYPE_INTEGER, 60, 'integrations', self::SANITIZE_INTEGER, false, array( 'enable_heartbeat' ) ),
'enable_varnish' => self::field( self::TYPE_BOOLEAN, false, 'integrations', self::SANITIZE_BOOLEAN, true ),
'varnish_ip' => self::field( self::TYPE_STRING, '', 'integrations', self::SANITIZE_TEXTAREA, true, array( 'enable_varnish' ) ),
'cache_footprint' => self::field( self::TYPE_BOOLEAN, true, 'misc', self::SANITIZE_BOOLEAN ),
'async_cache_cleaning' => self::field( self::TYPE_BOOLEAN, false, 'misc', self::SANITIZE_BOOLEAN ),
'dev_mode' => self::field( self::TYPE_BOOLEAN, false, 'misc', self::SANITIZE_BOOLEAN ),
'enable_google_tracking' => self::field( self::TYPE_BOOLEAN, false, 'integrations', self::SANITIZE_BOOLEAN, true ),
'enable_fb_tracking' => self::field( self::TYPE_BOOLEAN, false, 'integrations', self::SANITIZE_BOOLEAN, true ),
);
}
/**
* Return defaults keyed by legacy option name.
*
* @param array $context Runtime context used by dynamic defaults.
*
* @return array<string,mixed>
*/
public static function defaults( array $context = array() ) {
$defaults = array();
foreach ( self::fields( $context ) as $key => $field ) {
$defaults[ $key ] = $field['default'];
}
return $defaults;
}
/**
* Return recommended setup values keyed by legacy option name.
*
* This intentionally keeps the first recommended profile conservative:
* page cache, safe minification, font display behavior, lazy loading, and
* preload are enabled while combining and delayed JavaScript remain manual.
*
* @param array $context Runtime context used by dynamic defaults.
* @param bool|null $premium_available Whether Premium fields can be edited.
*
* @return array<string,mixed>
*/
public static function recommended( array $context = array(), $premium_available = null ) {
$is_apache = isset( $context['is_apache'] ) ? (bool) $context['is_apache'] : false;
$recommended = array(
'enable_page_cache' => true,
'cache_mobile' => true,
'cache_mobile_separate_file' => false,
'loggedin_user_cache' => false,
'gzip_compression' => true,
'cache_timeout' => 1440,
'auto_configure_htaccess' => $is_apache,
'minify_html' => true,
'minify_html_dom_optimization' => true,
'combine_google_fonts' => true,
'swap_google_fonts_display' => true,
'minify_css' => true,
'combine_css' => false,
'minify_js' => true,
'combine_js' => false,
'js_defer' => false,
'js_delay' => false,
'rewrite_file_optimizer' => $is_apache,
'enable_lazy_load' => true,
'lazy_load_post_content' => true,
'lazy_load_images' => true,
'lazy_load_background_images' => false,
'lazy_load_iframes' => true,
'lazy_load_widgets' => true,
'lazy_load_post_thumbnail' => true,
'lazy_load_avatars' => true,
'lazy_load_youtube' => true,
'lazy_load_skip_first_nth_img' => 3,
'disable_wp_embeds' => true,
'disable_emoji_scripts' => true,
'enable_cache_preload' => true,
'preload_homepage' => true,
'preload_public_posts' => true,
'preload_public_tax' => true,
'preload_request_interval' => 2,
'cache_footprint' => true,
'async_cache_cleaning' => false,
);
if ( self::premium_available( $premium_available ) ) {
$recommended['enable_image_optimization'] = true;
$recommended['image_optimizer_preferred_format'] = '';
$recommended['self_host_google_fonts'] = true;
}
$recommended = self::apply_recommended_safeguards( $recommended, $context );
/**
* Filter recommended settings before they are applied.
*
* @hook powered_cache_recommended_settings
*
* @param {array} $recommended Recommended settings.
* @param {array} $context Runtime context used by dynamic defaults.
* @param {bool} $premium_available Whether Premium fields can be edited.
*
* @return {array} New value.
* @since 4.0.0
*/
$recommended = apply_filters( 'powered_cache_recommended_settings', $recommended, $context, self::premium_available( $premium_available ) );
if ( ! is_array( $recommended ) ) {
return array();
}
return array_intersect_key( $recommended, self::fields( $context ) );
}
/**
* Keep one-click setup from enabling optimizations that are likely owned by
* another active layer.
*
* @param array $recommended Recommended settings.
* @param array $context Runtime context.
*
* @return array
*/
private static function apply_recommended_safeguards( array $recommended, array $context ) {
$active_plugins = isset( $context['active_plugins'] ) && is_array( $context['active_plugins'] ) ? $context['active_plugins'] : array();
if ( ! empty( $context['is_local_site'] ) ) {
$recommended['enable_image_optimization'] = false;
}
if ( self::has_active_plugin(
$active_plugins,
array(
'autoptimize/autoptimize.php',
'perfmatters/perfmatters.php',
'phastpress/phastpress.php',
)
) ) {
$recommended['minify_html'] = false;
$recommended['minify_html_dom_optimization'] = false;
$recommended['combine_google_fonts'] = false;
$recommended['minify_css'] = false;
$recommended['minify_js'] = false;
$recommended['disable_wp_embeds'] = false;
$recommended['disable_emoji_scripts'] = false;
}
if ( self::has_active_plugin(
$active_plugins,
array(
'a3-lazy-load/a3-lazy-load.php',
'bj-lazy-load/bj-lazy-load.php',
'jetpack-boost/jetpack-boost.php',
'lazy-load/lazy-load.php',
)
) ) {
$recommended['enable_lazy_load'] = false;
$recommended['lazy_load_post_content'] = false;
$recommended['lazy_load_images'] = false;
$recommended['lazy_load_background_images'] = false;
$recommended['lazy_load_iframes'] = false;
$recommended['lazy_load_widgets'] = false;
$recommended['lazy_load_post_thumbnail'] = false;
$recommended['lazy_load_avatars'] = false;
$recommended['lazy_load_youtube'] = false;
$recommended['lazy_load_skip_first_nth_img'] = 3;
}
if ( self::has_active_plugin(
$active_plugins,
array(
'breeze/breeze.php',
'litespeed-cache/litespeed-cache.php',
'sg-cachepress/sg-cachepress.php',
'w3-total-cache/w3-total-cache.php',
'wp-fastest-cache/wpFastestCache.php',
'wp-super-cache/wp-cache.php',
)
) ) {
$recommended['enable_page_cache'] = false;
$recommended['enable_cache_preload'] = false;
$recommended['preload_homepage'] = false;
$recommended['preload_public_posts'] = false;
$recommended['preload_public_tax'] = false;
}
return $recommended;
}
/**
* Determine whether one of the given plugin basenames is active.
*
* @param array $active_plugins Active plugin basenames.
* @param array $candidates Plugin basenames to match.
*
* @return bool
*/
private static function has_active_plugin( array $active_plugins, array $candidates ) {
foreach ( $candidates as $candidate ) {
if ( in_array( $candidate, $active_plugins, true ) ) {
return true;
}
}
return false;
}
/**
* Return a single field definition.
*
* @param string $key Setting key.
* @param array $context Runtime context used by dynamic defaults.
*
* @return array<string,mixed>|null
*/
public static function get( $key, array $context = array() ) {
$fields = self::fields( $context );
return isset( $fields[ $key ] ) ? $fields[ $key ] : null;
}
/**
* Return all fields belonging to a section.
*
* @param string $section Section identifier.
* @param array $context Runtime context used by dynamic defaults.
*
* @return array<string,array<string,mixed>>
*/
public static function section( $section, array $context = array() ) {
return array_filter(
self::fields( $context ),
static function ( $field ) use ( $section ) {
return $section === $field['section'];
}
);
}
/**
* Determine whether a setting key can be edited.
*
* Unknown keys stay editable for backward compatibility with extensions.
*
* @param string $key Setting key.
* @param array $context Runtime context used by dynamic defaults.
* @param bool|null $premium_available Whether Premium fields can be edited.
*
* @return bool
*/
public static function can_edit( $key, array $context = array(), $premium_available = null ) {
$field = self::get( $key, $context );
if ( ! $field || empty( $field['premium'] ) ) {
return true;
}
return self::premium_available( $premium_available );
}
/**
* Return why a setting is locked.
*
* @param string $key Setting key.
* @param array $context Runtime context used by dynamic defaults.
* @param bool|null $premium_available Whether Premium fields can be edited.
*
* @return string
*/
public static function lock_reason( $key, array $context = array(), $premium_available = null ) {
if ( self::can_edit( $key, $context, $premium_available ) ) {
return '';
}
$field = self::get( $key, $context );
return $field && ! empty( $field['premium'] ) ? 'premium' : '';
}
/**
* Preserve locked settings while applying an incoming settings payload.
*
* @param array $settings Incoming settings payload.
* @param array $current Current stored settings.
* @param array $context Runtime context used by dynamic defaults.
* @param bool|null $premium_available Whether Premium fields can be edited.
*
* @return array
*/
public static function enforce_editable( array $settings, array $current = array(), array $context = array(), $premium_available = null ) {
if ( self::premium_available( $premium_available ) ) {
return $settings;
}
foreach ( self::fields( $context ) as $key => $field ) {
if ( empty( $field['premium'] ) || ! array_key_exists( $key, $settings ) ) {
continue;
}
$settings[ $key ] = array_key_exists( $key, $current ) ? $current[ $key ] : $field['default'];
}
return $settings;
}
/**
* Determine whether the current runtime has Premium available.
*
* @param bool|null $premium_available Whether Premium fields can be edited.
*
* @return bool
*/
private static function premium_available( $premium_available = null ) {
if ( null !== $premium_available ) {
return (bool) $premium_available;
}
if ( function_exists( '\PoweredCache\Utils\is_premium' ) ) {
return (bool) \PoweredCache\Utils\is_premium();
}
return false;
}
/**
* Return object cache backends supported by the current PHP runtime.
*
* @param array $context Runtime context used by dynamic defaults.
*
* @return array<string>
*/
private static function object_cache_backends( array $context = array() ) {
if ( isset( $context['object_cache_backends'] ) && is_array( $context['object_cache_backends'] ) ) {
$backends = $context['object_cache_backends'];
} elseif ( function_exists( '\PoweredCache\Utils\get_available_object_caches' ) ) {
$backends = \PoweredCache\Utils\get_available_object_caches();
} else {
$backends = array( 'memcache', 'memcached', 'redis', 'apcu' );
}
$backends = array_map( 'strval', $backends );
$backends = array_filter(
$backends,
static function ( $backend ) {
return '' !== $backend && 'off' !== $backend;
}
);
return array_values( array_unique( $backends ) );
}
/**
* Return schema metadata for one field.
*
* @param string $type Setting value type.
* @param mixed $default Default value.
* @param string $section UI/product section.
* @param string $sanitizer Sanitizer identifier.
* @param bool $premium Whether the field controls a Premium feature.
* @param array $dependencies Field keys this setting depends on.
* @param array $enum_values Valid enum values.
* @param bool $deprecated Whether the field is deprecated.
*
* @return array<string,mixed>
*/
private static function field( $type, $default, $section, $sanitizer, $premium = false, array $dependencies = array(), array $enum_values = array(), $deprecated = false ) {
return array(
'type' => $type,
'default' => $default,
'section' => $section,
'sanitizer' => $sanitizer,
'premium' => (bool) $premium,
'dependencies' => $dependencies,
'enum' => $enum_values,
'deprecated' => (bool) $deprecated,
);
}
}