472 lines
12 KiB
PHP
472 lines
12 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace DeliciousBrains\WP_Offload_Media;
|
||
|
|
|
||
|
|
use AS3CF_Utils;
|
||
|
|
|
||
|
|
trait Settings_Trait {
|
||
|
|
/**
|
||
|
|
* Are only legacy defines in use?
|
||
|
|
*
|
||
|
|
* @var bool
|
||
|
|
*/
|
||
|
|
protected static $legacy_defines = false;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @var array
|
||
|
|
*/
|
||
|
|
private $settings = array();
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @var array
|
||
|
|
*/
|
||
|
|
private $defined_settings = array();
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @var bool
|
||
|
|
*/
|
||
|
|
private $saving_settings = false;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns keyed array of all settings values regardless of whether explicitly set or not.
|
||
|
|
*
|
||
|
|
* @param bool $pseudo Include pseudo settings that are derived rather than saved?
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function get_all_settings( bool $pseudo = true ): array {
|
||
|
|
$settings = array();
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Settings that can be defined one way or another.
|
||
|
|
*/
|
||
|
|
|
||
|
|
foreach ( $this->get_allowed_settings_keys() as $key ) {
|
||
|
|
$settings[ $key ] = $this->sanitize_setting( $key, $this->get_setting( $key ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
return $settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the plugin's settings array.
|
||
|
|
*
|
||
|
|
* @param bool $force
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function get_settings( bool $force = false ): array {
|
||
|
|
if ( empty( $this->settings ) || $force ) {
|
||
|
|
$this->settings = array();
|
||
|
|
|
||
|
|
$saved_settings = get_site_option( static::SETTINGS_KEY );
|
||
|
|
$saved_settings = ! empty( $saved_settings ) && is_array( $saved_settings ) ? $saved_settings : array();
|
||
|
|
|
||
|
|
$this->settings = $this->filter_settings( $saved_settings );
|
||
|
|
|
||
|
|
// Now that we have merged database and defined settings, sanitize them before use.
|
||
|
|
if ( ! empty( $this->settings ) ) {
|
||
|
|
foreach ( $this->settings as $key => $val ) {
|
||
|
|
$this->settings[ $key ] = $this->sanitize_setting( $key, $val );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// If defined settings keys have changed since last time settings were saved to database,
|
||
|
|
// re-save to remove the new keys.
|
||
|
|
if (
|
||
|
|
! empty( $saved_settings ) &&
|
||
|
|
! empty( $this->defined_settings ) &&
|
||
|
|
! empty( array_intersect_key( $saved_settings, $this->defined_settings ) )
|
||
|
|
) {
|
||
|
|
$this->save_settings();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Gets a single setting that has been defined in the plugin settings constant.
|
||
|
|
*
|
||
|
|
* @param string $key
|
||
|
|
* @param mixed $default
|
||
|
|
*
|
||
|
|
* @return mixed
|
||
|
|
*/
|
||
|
|
public function get_defined_setting( string $key, $default = '' ) {
|
||
|
|
$defined_settings = $this->get_defined_settings();
|
||
|
|
|
||
|
|
return isset( $defined_settings[ $key ] ) ? $defined_settings[ $key ] : $default;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all settings that have been defined via constant for the plugin.
|
||
|
|
*
|
||
|
|
* @param bool $force
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function get_defined_settings( bool $force = false ): array {
|
||
|
|
if ( empty( $this->defined_settings ) || $force ) {
|
||
|
|
$this->defined_settings = array();
|
||
|
|
|
||
|
|
if ( ! static::settings_constant() ) {
|
||
|
|
$unserialized = array();
|
||
|
|
} else {
|
||
|
|
$unserialized = AS3CF_Utils::maybe_unserialize( constant( static::settings_constant() ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
$unserialized = is_array( $unserialized ) ? $unserialized : array();
|
||
|
|
|
||
|
|
if ( method_exists( $this, 'get_legacy_defined_settings' ) ) {
|
||
|
|
$unserialized = $this->get_legacy_defined_settings( $unserialized );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Nothing in new style or legacy defines, we're done.
|
||
|
|
if ( empty( $unserialized ) ) {
|
||
|
|
return $this->defined_settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ( $unserialized as $key => $value ) {
|
||
|
|
if ( ! in_array( $key, $this->get_allowed_settings_keys( true ) ) ) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$value = $this->sanitize_setting( $key, $value );
|
||
|
|
|
||
|
|
$this->defined_settings[ $key ] = $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Normalize the defined settings before saving, so we can detect when a real change happens.
|
||
|
|
ksort( $this->defined_settings );
|
||
|
|
|
||
|
|
// If only legacy defines are in use, we can fake new style to allow for key based monitoring and db settings cleanup.
|
||
|
|
if ( ! static::settings_constant() ) {
|
||
|
|
static::$legacy_defines = true;
|
||
|
|
define( static::preferred_settings_constant(), serialize( $this->defined_settings ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->listen_for_settings_constant_changes();
|
||
|
|
|
||
|
|
update_site_option( 'as3cf_constant_' . static::settings_constant(), array_diff_key( $this->defined_settings, array_flip( $this->get_monitored_settings_blacklist() ) ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->defined_settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Are only legacy defines in use?
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public static function using_legacy_defines(): bool {
|
||
|
|
return static::$legacy_defines;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns first (preferred) settings constant that can be defined, otherwise blank.
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public static function preferred_settings_constant(): string {
|
||
|
|
if ( ! empty( static::$settings_constants ) ) {
|
||
|
|
return static::$settings_constants[0];
|
||
|
|
} else {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the constant used to define the settings.
|
||
|
|
*
|
||
|
|
* @return string|false Constant name if defined, otherwise false
|
||
|
|
*/
|
||
|
|
public static function settings_constant() {
|
||
|
|
return AS3CF_Utils::get_first_defined_constant( static::$settings_constants );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Subscribe to a change of the site option used to store the constant-defined settings.
|
||
|
|
*/
|
||
|
|
protected function listen_for_settings_constant_changes() {
|
||
|
|
if (
|
||
|
|
false !== static::settings_constant() &&
|
||
|
|
! has_action(
|
||
|
|
'update_site_option_as3cf_constant_' . static::settings_constant(),
|
||
|
|
array(
|
||
|
|
$this,
|
||
|
|
'settings_constant_changed',
|
||
|
|
)
|
||
|
|
)
|
||
|
|
) {
|
||
|
|
add_action( 'add_site_option_as3cf_constant_' . static::settings_constant(), array(
|
||
|
|
$this,
|
||
|
|
'settings_constant_added',
|
||
|
|
), 10, 3 );
|
||
|
|
add_action( 'update_site_option_as3cf_constant_' . static::settings_constant(), array(
|
||
|
|
$this,
|
||
|
|
'settings_constant_changed',
|
||
|
|
), 10, 4 );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Translate a settings constant option addition into a change.
|
||
|
|
*
|
||
|
|
* @param string $option Name of the option.
|
||
|
|
* @param mixed $value Value the option is being initialized with.
|
||
|
|
* @param int $network_id ID of the network.
|
||
|
|
*/
|
||
|
|
public function settings_constant_added( string $option, $value, int $network_id ) {
|
||
|
|
$db_settings = get_site_option( static::SETTINGS_KEY, array() );
|
||
|
|
$this->settings_constant_changed( $option, $value, $db_settings, $network_id );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Callback for announcing when settings-defined values change.
|
||
|
|
*
|
||
|
|
* @param string $option Name of the option.
|
||
|
|
* @param mixed $new_settings Current value of the option.
|
||
|
|
* @param mixed $old_settings Old value of the option.
|
||
|
|
* @param int $network_id ID of the network.
|
||
|
|
*/
|
||
|
|
public function settings_constant_changed( string $option, $new_settings, $old_settings, int $network_id ) {
|
||
|
|
if ( ! static::settings_constant() ) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$old_settings = $old_settings ? $old_settings : array();
|
||
|
|
|
||
|
|
foreach ( $this->get_allowed_settings_keys( true ) as $setting ) {
|
||
|
|
$old_value = isset( $old_settings[ $setting ] ) ? $old_settings[ $setting ] : null;
|
||
|
|
$new_value = isset( $new_settings[ $setting ] ) ? $new_settings[ $setting ] : null;
|
||
|
|
|
||
|
|
if ( $old_value !== $new_value ) {
|
||
|
|
/**
|
||
|
|
* Setting-specific hook for setting change.
|
||
|
|
*
|
||
|
|
* @param mixed $new_value
|
||
|
|
* @param mixed $old_value
|
||
|
|
* @param string $setting
|
||
|
|
*/
|
||
|
|
do_action( 'as3cf_constant_' . static::settings_constant() . '_changed_' . $setting, $new_value, $old_value, $setting );
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Generic hook for setting change.
|
||
|
|
*
|
||
|
|
* @param mixed $new_value
|
||
|
|
* @param mixed $old_value
|
||
|
|
* @param string $setting
|
||
|
|
*/
|
||
|
|
do_action( 'as3cf_constant_' . static::settings_constant() . '_changed', $new_value, $old_value, $setting );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Filter in defined settings with sensible defaults.
|
||
|
|
*
|
||
|
|
* @param array $settings
|
||
|
|
*
|
||
|
|
* @return array $settings
|
||
|
|
*/
|
||
|
|
public function filter_settings( array $settings ): array {
|
||
|
|
$defined_settings = $this->get_defined_settings();
|
||
|
|
|
||
|
|
// Bail early if there are no defined settings.
|
||
|
|
if ( empty( $defined_settings ) ) {
|
||
|
|
return $settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ( $defined_settings as $key => $value ) {
|
||
|
|
$allowed_values = array();
|
||
|
|
|
||
|
|
if ( in_array( $key, $this->get_boolean_format_settings() ) ) {
|
||
|
|
$allowed_values = array( true, false, '0', '1' );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Unexpected value, remove from defined_settings array.
|
||
|
|
if ( ! empty( $allowed_values ) && ! in_array( $value, $allowed_values ) ) {
|
||
|
|
$this->remove_defined_setting( $key );
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Value defined successfully.
|
||
|
|
$settings[ $key ] = $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Ensure that sensitive settings are obfuscated.
|
||
|
|
*
|
||
|
|
* @param array $settings
|
||
|
|
*
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function obfuscate_sensitive_settings( array $settings ): array {
|
||
|
|
$sensitive_settings = $this->get_sensitive_settings();
|
||
|
|
|
||
|
|
if ( empty( $sensitive_settings ) ) {
|
||
|
|
return $settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ( $settings as $key => $value ) {
|
||
|
|
if ( ! empty( $value ) && in_array( $key, $sensitive_settings ) ) {
|
||
|
|
$settings[ $key ] = _x( '-- not shown --', 'placeholder for sensitive setting, e.g. secret access key', 'amazon-s3-and-cloudfront' );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Sanitize a setting value, maybe.
|
||
|
|
*
|
||
|
|
* @param string $key
|
||
|
|
* @param mixed $value
|
||
|
|
*
|
||
|
|
* @return array|bool|string
|
||
|
|
*/
|
||
|
|
public function sanitize_setting( string $key, $value ) {
|
||
|
|
$skip_sanitize = $this->get_skip_sanitize_settings();
|
||
|
|
$boolean_settings = $this->get_boolean_format_settings();
|
||
|
|
|
||
|
|
if ( in_array( $key, $skip_sanitize ) ) {
|
||
|
|
if ( is_array( $value ) ) {
|
||
|
|
$result = array();
|
||
|
|
foreach ( $value as $k => $v ) {
|
||
|
|
$result[ $k ] = wp_strip_all_tags( $v );
|
||
|
|
}
|
||
|
|
$value = $result;
|
||
|
|
} else {
|
||
|
|
$value = wp_strip_all_tags( $value );
|
||
|
|
}
|
||
|
|
} elseif ( in_array( $key, $boolean_settings ) ) {
|
||
|
|
$value = (bool) $value;
|
||
|
|
} elseif ( is_numeric( $value ) ) {
|
||
|
|
$value = strval( $value );
|
||
|
|
} else {
|
||
|
|
$value = sanitize_text_field( $value );
|
||
|
|
|
||
|
|
// Make sure path setting is absolute and not just "/".
|
||
|
|
// But not on Windows as it can have various forms of path, e.g. C:\Sites and \\shared\sites.
|
||
|
|
if ( '/' === DIRECTORY_SEPARATOR && in_array( $key, $this->get_path_format_settings() ) ) {
|
||
|
|
$value = trim( AS3CF_Utils::unleadingslashit( $value ) );
|
||
|
|
$value = empty( $value ) ? '' : AS3CF_Utils::leadingslashit( $value );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Make sure prefix setting is relative with trailing slash for visibility.
|
||
|
|
if ( in_array( $key, $this->get_prefix_format_settings() ) ) {
|
||
|
|
$value = trim( untrailingslashit( $value ) );
|
||
|
|
$value = empty( $value ) ? '' : AS3CF_Utils::trailingslash_prefix( $value );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set a setting.
|
||
|
|
*
|
||
|
|
* @param string $key
|
||
|
|
* @param mixed $value
|
||
|
|
*/
|
||
|
|
public function set_setting( string $key, $value ) {
|
||
|
|
$this->get_settings();
|
||
|
|
|
||
|
|
$this->settings[ $key ] = $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Bulk set the settings array.
|
||
|
|
*
|
||
|
|
* @param array $settings
|
||
|
|
*/
|
||
|
|
public function set_settings( array $settings ) {
|
||
|
|
$this->settings = $settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Save the settings to the database.
|
||
|
|
*/
|
||
|
|
public function save_settings() {
|
||
|
|
if ( is_array( $this->settings ) ) {
|
||
|
|
ksort( $this->settings );
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->update_site_option( static::SETTINGS_KEY, array_diff_key( $this->settings, $this->defined_settings ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update site option.
|
||
|
|
*
|
||
|
|
* @param string $option
|
||
|
|
* @param mixed $value
|
||
|
|
* @param bool $autoload
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function update_site_option( string $option, $value, bool $autoload = true ): bool {
|
||
|
|
if ( is_multisite() ) {
|
||
|
|
return update_site_option( $option, $value );
|
||
|
|
}
|
||
|
|
|
||
|
|
return update_option( $option, $value, $autoload );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Delete a setting.
|
||
|
|
*
|
||
|
|
* @param string $key
|
||
|
|
*/
|
||
|
|
public function remove_setting( string $key ) {
|
||
|
|
$this->get_settings();
|
||
|
|
unset( $this->settings[ $key ] );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Removes a defined setting from the defined_settings array.
|
||
|
|
*
|
||
|
|
* Does not unset the actual constant.
|
||
|
|
*
|
||
|
|
* @param string $key
|
||
|
|
*/
|
||
|
|
public function remove_defined_setting( string $key ) {
|
||
|
|
$this->get_defined_settings();
|
||
|
|
unset( $this->defined_settings[ $key ] );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Helper for displaying boolean settings.
|
||
|
|
*
|
||
|
|
* @param string $key setting key
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function on_off( string $key ): string {
|
||
|
|
$value = $this->get_setting( $key, false );
|
||
|
|
|
||
|
|
return true === $value ? 'On' : 'Off';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Getter for $saving_settings.
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function saving_settings(): bool {
|
||
|
|
return $this->saving_settings;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Setter for $saving_settings.
|
||
|
|
*
|
||
|
|
* @param bool $saving
|
||
|
|
*/
|
||
|
|
public function set_saving_settings( bool $saving ) {
|
||
|
|
$this->saving_settings = $saving;
|
||
|
|
}
|
||
|
|
}
|