/home/silvwabw/www/wp-content/plugins/post-duplicator/includes/mtphr-settings/index.php
<?php
namespace Mtphr\PostDuplicator;
/**
* Create the class
*/
final class Settings {
private static $instance;
private $version = '1.1.3.1';
private $id = '';
private $textdomain = 'mtphr-settings';
private $settings_dir = '';
private $settings_url = '';
private $settings_ready = false;
private $fields_ready = false;
private $admin_pages = [];
private $options = [];
private $sections = [];
private $settings = [];
private $values = [];
private $default_values = [];
private $sanitize_settings = [];
private $encryption_settings = [];
private $type_settings = [];
private $noupdate_settings = [];
private $admin_notices = [];
private $sidebar_items = [];
private $sidebar_width = '320px';
private $main_content_max_width = '1000px';
private $default_sanitizer = 'wp_kses_post';
private $encryption_key_1 = '7Q@_DvLVTiHPEA';
private $encryption_key_2 = 'YgM2iCX-BtoBpJ';
/**
* Set up the instance
*/
public static function instance() {
if ( ! isset( self::$instance ) && ! ( self::$instance instanceof Settings ) ) {
self::$instance = new Settings;
// Initialize the ID based on namespace
if ( empty( self::$instance->id ) ) {
self::$instance->id = self::$instance->get_namespace_identifier();
}
// Register WordPress hooks for admin functionality
add_action( 'admin_menu', array( self::$instance, 'create_admin_pages' ) );
add_action( 'admin_enqueue_scripts', array( self::$instance, 'enqueue_scripts' ) );
add_action( 'rest_api_init', array( self::$instance, 'register_routes' ) );
add_action( 'admin_notices', array( self::$instance, 'admin_notices' ) );
// Register initialization hooks - fires namespace-specific action hooks
add_action( 'rest_api_init', array( self::$instance, 'initialize_settings' ), 20 );
add_action( 'init', array( self::$instance, 'initialize_settings' ), 20 );
add_action( 'init', array( self::$instance, 'initialize_fields' ), 20 );
list( $path, $url ) = self::$instance->get_path( dirname( __FILE__ ) );
self::$instance->settings_dir = $path;
self::$instance->settings_url = $url;
}
return self::$instance;
}
/**
* Throw error on object clone
*/
public function __clone() {
_doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', self::$instance->textdomain ), '1.0.0' );
}
/**
* Disable unserializing of the class
*/
public function __wakeup() {
_doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', self::$instance->textdomain ), '1.0.0' );
}
private function get_path( $path = '' ) {
// Plugin base path.
$path = wp_normalize_path( untrailingslashit( $path ) );
$themes_dir = wp_normalize_path( untrailingslashit( dirname( get_stylesheet_directory() ) ) );
// Default URL.
$url = plugins_url( '', $path . '/' . basename( $path ) . '.php' );
// Included into themes.
if (
0 !== strpos( $path, wp_normalize_path( WP_PLUGIN_DIR ) )
&& 0 !== strpos( $path, wp_normalize_path( WPMU_PLUGIN_DIR ) )
&& 0 === strpos( $path, $themes_dir )
) {
$themes_url = untrailingslashit( dirname( get_stylesheet_directory_uri() ) );
$url = str_replace( $themes_dir, $themes_url, $path );
}
$path = trailingslashit( $path );
$url = trailingslashit( $url );
return array( $path, $url );
}
/**
* Init settings
*/
// public function init_settings( $option, $settings ) {
// if ( ! is_array( $settings ) ) {
// return false;
// }
// if ( is_array( $settings ) && ! empty( $settings ) ) {
// foreach ( $settings as $setting ) {
// $setting['option'] = $option;
// self::$instance->process_setting_data( $setting );
// }
// }
// }
/**
* Set the settings ready
*/
public function set_settings_ready( $ready ) {
return self::$instance->settings_ready = boolval( $ready );
}
/**
* Get the settings ready
*/
public function get_settings_ready() {
return self::$instance->settings_ready;
}
/**
* Set the fields ready
*/
public function set_fields_ready( $ready ) {
return self::$instance->fields_ready = boolval( $ready );
}
/**
* Get the fields ready
*/
public function get_fields_ready() {
return self::$instance->fields_ready;
}
/**
* Get namespace identifier from the class namespace
*/
private function get_namespace_identifier() {
$reflection = new \ReflectionClass( $this );
$namespace = $reflection->getNamespaceName();
// Convert namespace to identifier by removing backslashes
// e.g., "Mtphr\PostDuplicator" -> "MtphrPostDuplicator"
$identifier = str_replace( '\\', '', $namespace );
// Fallback to 'mtphr' if namespace is empty (shouldn't happen, but safety check)
return ! empty( $identifier ) ? $identifier : 'mtphr';
}
/**
* Force a string to camel case
*/
private function to_camel_case( $string ) {
// Replace non-alphanumeric characters with spaces
$string = preg_replace( '/[^a-zA-Z0-9]+/', ' ', $string );
// Uppercase first letter of each word, then remove spaces
$string = str_replace( ' ', '', ucwords( strtolower( $string ) ) );
// Lowercase first character for camelCase
return lcfirst( $string );
}
/**
* Get the custom id for the settings
*/
public function get_id() {
return self::$instance->id . 'Settings';
}
/**
* Set a textdomain for the settings
*/
public function set_textdomain( $textdomain ) {
self::$instance->textdomain = $textdomain;
}
/**
* Get the textdomain for the settings
*/
public function get_textdomain() {
return self::$instance->textdomain;
}
/**
* Set the default sanitizer
*/
public function set_default_sanitizer( $sanitizer ) {
self::$instance->default_sanitizer = $sanitizer;
}
/**
* Get the default sanitizer
*/
public function get_default_sanitizer() {
return self::$instance->default_sanitizer;
}
/**
* Set the default sanitizer
*/
public function set_default_encryption_keys( $keys = [] ) {
if ( isset( $keys['key_1'] ) ) {
self::$instance->encryption_key_1 = esc_attr( $keys['key_1'] );
}
if ( isset( $keys['key_2'] ) ) {
self::$instance->encryption_key_1 = esc_attr( $keys['key_2'] );
}
}
/**
* Add an option key
*/
public function add_admin_page( $admin_page ) {
if ( ! is_array( $admin_page ) ) {
return false;
}
if ( ! isset( $admin_page['page_title'] ) || ! isset( $admin_page['menu_title'] ) || ! isset( $admin_page['capability'] ) || ! isset( $admin_page['menu_slug'] ) ) {
return false;
}
$admin_pages = self::$instance->get_admin_pages();
// Check if top level and slug already exists
if ( ! isset( $admin_page['parent_slug'] ) ) {
if ( in_array( $admin_page['menu_slug'], array_column( array_filter( $admin_pages, fn( $page ) => ! isset( $page['parent_slug'] ) ), 'menu_slug' ) ) ) {
return false;
}
// Check if submenu and same slug exists with same parent
} else {
$exists = array_filter( $admin_pages, function ( $page ) use ( $admin_page ) {
return isset( $page['parent_slug'] )
&& $page['parent_slug'] === $admin_page['parent_slug']
&& $page['menu_slug'] === $admin_page['menu_slug'];
} );
if ( ! empty( $exists ) ) {
return false;
}
}
$admin_pages[] = $admin_page;
self::$instance->admin_pages = $admin_pages;
return self::$instance->admin_pages;
}
/**
* Return available options keys
*/
public function get_admin_pages( $admin_page = false ) {
$admin_pages = self::$instance->admin_pages;
if ( ! is_array( $admin_pages ) ) {
return false;
}
if ( $admin_page ) {
if ( isset( $admin_pages[$admin_page] ) ) {
return $admin_pages[$admin_page];
}
} else {
return $admin_pages;
}
}
/**
* Return available options keys
*/
public function get_options( $option = false ) {
$options = self::$instance->options;
if ( ! is_array( $options ) ) {
return false;
}
if ( $option ) {
if ( isset( $options[$option] ) ) {
return $options[$option];
}
} else {
return $options;
}
}
/**
* Return option keys by sections
*/
public function get_option_keys( $sections = false ) {
if ( $sections ) {
if ( is_array( $sections ) ) {
if ( ! empty( $sections ) ) {
$option_keys = [];
foreach ( $sections as $section ) {
if ( isset( $section['option'] ) ) {
$option_keys[$section['option']] = $section['option'];
}
}
return array_values( $option_keys );
}
} else {
if ( isset( $sections['option'] ) ) {
return $section['option'];
}
}
}
}
/**
* Add an option key
*/
public function add_section( $section ) {
$sections = self::$instance->get_sections();
$options = self::$instance->get_options();
$id = false;
$label = false;
$order = 10;
if ( ! is_array( $section ) ) {
return false;
}
if ( ! isset( $section['id'] ) || ! isset( $section['slug'] ) || ! isset( $section['menu_slug'] ) ) {
return false;
}
// Check if a section with the same ID already exists
$ids = array_column( $sections, 'id' ); // Extract all existing IDs
if ( in_array( $section['id'], $ids ) ) {
$message = "<p><strong>{$section['id']}</strong> can not be added for <strong>{$section['menu_slug']}</strong>. This section id is already being used with mtphr-settings.</p>";
self::$instance->add_admin_notice( 'error', $message );
return false;
}
if ( ! isset( $section['label'] ) ) {
$section['label'] = ucfirst( $section['id'] );
}
if ( ! isset( $section['order'] ) ) {
$section['order'] = $order;
}
if ( ! isset( $section['type'] ) ) {
$section['type'] = 'primary';
}
// Check if top level and slug already exists
if ( ! isset( $section['parent_slug'] ) ) {
$exists = array_filter( $sections, function ( $s ) use ( $section ) {
return $s['menu_slug'] === $section['menu_slug']
&& $s['id'] === $section['id'];
} );
if ( ! empty( $exists ) ) {
return false;
}
// Check if submenu and same slug exists with same parent
} else {
$exists = array_filter( $sections, function ( $s ) use ( $section ) {
return isset( $s['parent_slug'] ) && $s['parent_slug'] === $section['parent_slug']
&& $s['menu_slug'] === $section['menu_slug']
&& $s['id'] === $section['id'];
} );
if ( ! empty( $exists ) ) {
return false;
}
}
$sections[] = $section;
self::$instance->sections = $sections;
$section_option = $section['option'];
// Add to the options array
if ( ! in_array( $section_option, $options ) ) {
$options[] = $section_option;
self::$instance->options = $options;
}
return self::$instance->sections;
}
/**
* Return available options keys
*/
public function get_sections( $page = false ) {
$sections = self::$instance->sections;
if ( ! is_array( $sections ) ) {
return false;
}
if ( $page ) {
$menu_slug = $page['menu_slug'];
$parent_slug = isset( $page['parent_slug'] ) ? $page['parent_slug'] : false;
$page_sections = [];
if ( is_array( $sections ) && ! empty( $sections ) ) {
foreach ( $sections as $section ) {
if ( $menu_slug == $section['menu_slug'] ) {
if ( $parent_slug ) {
if ( isset( $section['parent_slug'] ) && $parent_slug = $section['parent_slug'] ) {
$page_sections[] = $section;
}
} else {
$page_sections[] = $section;
}
}
}
}
return $page_sections;
} else {
return $sections;
}
}
/**
* Return available options keys
*/
public function get_section( $id ) {
$sections = self::$instance->get_sections();
if ( ! is_array( $sections ) ) {
return false;
}
if ( is_array( $sections ) && ! empty( $sections ) ) {
foreach ( $sections as $section ) {
if ( $id == $section['id'] ) {
return $section;
}
}
}
}
/**
* Add a single field
*/
private function add_field( $setting ) {
if ( ! is_array( $setting ) ) {
return false;
}
$sections = self::$instance->get_sections();
$settings = self::$instance->get_settings();
if ( empty( $sections ) ) {
return false;
}
$default_section = $sections[0]['id'];
$default_order = 10;
if ( ! isset( $setting['section'] ) ) {
$setting['section'] = $default_section;
}
if ( ! isset( $setting['option'] ) ) {
$section = self::$instance->get_section( $setting['section'] );
if ( ! $section ) {
$message = "<p>Section <strong>{$setting['section']}</strong> does not exist.</p>";
self::$instance->add_admin_notice( 'error', $message );
return false;
}
$setting['option'] = $section['option'];
}
if ( ! isset( $setting['order'] ) ) {
$setting['order'] = $default_order;
}
// Make sure the section exists
if ( ! in_array( $setting['section'], array_column( $sections, 'id' ) ) ) {
return false;
}
$settings[] = $setting;
self::$instance->settings = $settings;
self::$instance->process_setting_data( $setting );
return $setting;
}
/**
* Add fields
*/
public function add_fields( $data ) {
if ( ! is_array( $data ) ) {
return false;
}
if ( ! isset( $data['section'] ) || ! isset( $data['fields'] ) ) {
return false;
}
$updated_settings = [];
$section = isset( $data['section'] ) ? $data['section'] : false;
if ( is_array( $data['fields'] ) && ! empty( $data['fields'] ) ) {
foreach ( $data['fields'] as $key => $field ) {
$field['section'] = $data['section'];
if ( $setting = self::$instance->add_field( $field ) ) {
$updated_settings[] = $setting;
}
}
}
return $updated_settings;
}
/**
* Add sidebar
*/
public function add_sidebar( $data ) {
if ( ! is_array( $data ) ) {
return false;
}
if ( ! isset( $data['items'] ) || ! is_array( $data['items'] ) ) {
return false;
}
// Store sidebar items
self::$instance->sidebar_items = $data['items'];
// Store sidebar width if provided
if ( isset( $data['width'] ) && ! empty( $data['width'] ) ) {
self::$instance->sidebar_width = esc_attr( $data['width'] );
}
// Store main content max-width if provided
if ( isset( $data['main_max_width'] ) && ! empty( $data['main_max_width'] ) ) {
self::$instance->main_content_max_width = esc_attr( $data['main_max_width'] );
}
return true;
}
/**
* Get sidebar items
*/
public function get_sidebar_items() {
return self::$instance->sidebar_items;
}
/**
* Get sidebar width
*/
public function get_sidebar_width() {
return self::$instance->sidebar_width;
}
/**
* Get main content max-width
*/
public function get_main_content_max_width() {
return self::$instance->main_content_max_width;
}
/**
* Return all settings
*/
public function get_settings( $sections = false ) {
$settings = self::$instance->settings;
if ( $sections ) {
$section_settings = [];
if ( is_array( $sections ) && ! empty( $sections ) ) {
foreach ( $sections as $section ) {
if ( is_array( $settings ) && ! empty( $settings ) ) {
foreach ( $settings as $setting ) {
if ( $setting['section'] == $section['id'] ) {
$section_settings[] = $setting;
}
}
}
}
}
return $section_settings;
}
return $settings;
}
/**
* Process settings data
*/
private function process_setting_data( $setting, $option = false, $parents = [] ) {
// Reference to instance storage
$type_storage =& self::$instance->type_settings;
//$default_storage =& self::$instance->default_values;
//$sanitize_storage =& self::$instance->sanitize_settings;
$encryption_storage =& self::$instance->encryption_settings;
$noupdate_storage =& self::$instance->noupdate_settings;
// Extract setting data
$id = isset( $setting['id'] ) ? $setting['id'] : false;
$type = isset( $setting['type'] ) ? $setting['type'] : false;
//$default = isset( $setting['default'] ) ? $setting['default'] : null;
//$sanitize = isset( $setting['sanitize'] ) ? $setting['sanitize'] : self::$instance->get_default_sanitizer();
$encrypt = isset( $setting['encrypt'] ) ? $setting['encrypt'] : false;
$noupdate = isset( $setting['noupdate'] ) ? $setting['noupdate'] : false;
$option = isset( $setting['option'] ) ? $setting['option'] : $option;
if ( $option && $id !== false ) {
// Ensure option storage exists
if ( !isset( $type_storage[$option] ) ) {
$type_storage[$option] = [];
}
// if ( !isset( $default_storage[$option] ) ) {
// $default_storage[$option] = [];
// }
// if ( !isset( $sanitize_storage[$option] ) ) {
// $sanitize_storage[$option] = [];
// }
if ( !isset( $encryption_storage[$option] ) ) {
$encryption_storage[$option] = [];
}
if ( !isset( $noupdate_storage[$option] ) ) {
$noupdate_storage[$option] = [];
}
// Reference correct location in storage
$type_target =& $type_storage[$option];
//$default_target =& $default_storage[$option];
//$sanitize_target =& $sanitize_storage[$option];
$encryption_target =& $encryption_storage[$option];
$noupdate_target =& $noupdate_storage[$option];
// Traverse parents to ensure proper nesting
if ( !empty( $parents ) ) {
foreach ( $parents as $parent ) {
if ( !isset( $type_target[$parent] ) || !is_array( $type_target[$parent] ) ) {
$type_target[$parent] = [
'__type' => $type
];
}
// if ( !isset( $default_target[$parent] ) || !is_array( $default_target[$parent] ) ) {
// $default_target[$parent] = [];
// }
// if ( !isset( $sanitize_target[$parent] ) || !is_array( $sanitize_target[$parent] ) ) {
// $sanitize_target[$parent] = [];
// }
if ( !isset( $encryption_target[$parent] ) || !is_array( $encryption_target[$parent] ) ) {
$encryption_target[$parent] = [];
}
if ( !isset( $noupdate_target[$parent] ) || !is_array( $noupdate_target[$parent] ) ) {
$noupdate_target[$parent] = [];
}
$type_target =& $type_target[$parent];
//$default_target =& $default_target[$parent];
//$sanitize_target =& $sanitize_target[$parent];
$encryption_target =& $encryption_target[$parent];
$noupdate_target =& $noupdate_target[$parent];
}
}
// Assign values at the correct depth
$type_target[$id] = $type;
//$default_target[$id] = $default;
//$sanitize_target[$id] = $sanitize;
$noupdate_target[$id] = $noupdate;
// Handle encryption settings
if ( $encrypt !== false ) {
if ( is_array( $encrypt ) ) {
$encryption_target[$id] = [
'key_1' => isset( $encrypt['key_1'] ) ? esc_attr( $encrypt['key_1'] ) : self::$instance->encryption_key_1,
'key_2' => isset( $encrypt['key_2'] ) ? esc_attr( $encrypt['key_2'] ) : self::$instance->encryption_key_2,
];
} elseif ( $encrypt === true || $encrypt === 1 ) {
$encryption_target[$id] = [
'key_1' => self::$instance->encryption_key_1,
'key_2' => self::$instance->encryption_key_2,
];
}
}
}
// Process sub-fields if they exist
if ( !empty( $setting['fields'] ) && is_array( $setting['fields'] ) ) {
$new_parents = $parents;
if ( $id !== false ) {
$new_parents[] = $id;
}
foreach ( $setting['fields'] as $field ) {
self::$instance->process_setting_data( $field, $option, $new_parents );
}
}
}
/**
* Add default values
*/
public function add_default_values( $option, $values = [] ) {
$default_values = self::$instance->default_values;
$default_option_values = isset( $default_values[$option] ) ? $default_values[$option] : [];
if ( is_array( $values ) && ! empty( $values ) ) {
foreach ( $values as $key => $value ) {
$default_option_values[$key] = $value;
}
}
$default_values[$option] = $default_option_values;
self::$instance->default_values = $default_values;
return $default_values;
}
/**
* Get default values
*/
public function get_default_values( $options = false ) {
$default_values = self::$instance->default_values;
if ( $options ) {
if ( is_array( $options ) ) {
if ( ! empty( $options ) ) {
$values = [];
foreach ( $options as $option ) {
$values[$option] = isset( $default_values[$option] ) ? $default_values[$option] : [];
}
return $values;
}
} else {
return isset( $default_values[$options] ) ? $default_values[$options] : [];
}
}
return $default_values;
}
/**
* Add sanitize settings
*/
public function add_sanitize_settings( $option, $values = [] ) {
$sanitize_settings = self::$instance->sanitize_settings;
$sanitize_option_settings = isset( $sanitize_settings[$option] ) ? $sanitize_settings[$option] : [];
if ( is_array( $values ) && ! empty( $values ) ) {
foreach ( $values as $key => $value ) {
$sanitize_option_settings[$key] = $value;
}
}
$sanitize_settings[$option] = $sanitize_option_settings;
self::$instance->sanitize_settings = $sanitize_settings;
return $sanitize_settings;
}
/**
* Get sanitize settings
*/
public function get_sanitize_settings( $options = false ) {
$sanitize_settings = self::$instance->sanitize_settings;
if ( $options ) {
if ( is_array( $options ) ) {
if ( ! empty( $options ) ) {
$settings = [];
foreach ( $options as $option ) {
$settings[$option] = isset( $sanitize_settings[$option] ) ? $sanitize_settings[$option] : [];
}
return $settings;
}
} else {
return isset( $sanitize_settings[$options] ) ? $sanitize_settings[$options] : [];
}
}
return $sanitize_settings;
}
/**
* Add encryption settings
*/
public function add_encryption_settings( $option, $values = [] ) {
$encryption_settings = self::$instance->encryption_settings;
$encryption_option_settings = isset( $encryption_settings[$option] ) ? $encryption_settings[$option] : [];
if ( is_array( $values ) && ! empty( $values ) ) {
foreach ( $values as $key => $value ) {
if ( is_array( $value ) ) {
$encryption_option_settings[$key] = [
'key_1' => isset( $value['key_1'] ) ? esc_attr( $value['key_1'] ) : self::$instance->encryption_key_1,
'key_2' => isset( $value['key_2'] ) ? esc_attr( $value['key_2'] ) : self::$instance->encryption_key_2,
];
} elseif ( $value === true || $value === 1 ) {
$encryption_option_settings[$key] = [
'key_1' => self::$instance->encryption_key_1,
'key_2' => self::$instance->encryption_key_2,
];
}
}
}
$encryption_settings[$option] = $encryption_option_settings;
self::$instance->encryption_settings = $encryption_settings;
return $encryption_settings;
}
/**
* Get sanitize settings
*/
public function get_encryption_settings( $options = false ) {
$encryption_settings = self::$instance->encryption_settings;
if ( $options ) {
if ( is_array( $options ) ) {
if ( ! empty( $options ) ) {
$settings = [];
foreach ( $options as $option ) {
$settings[$option] = isset( $encryption_settings[$option] ) ? $encryption_settings[$option] : [];
}
return $settings;
}
} else {
return isset( $encryption_settings[$options] ) ? $encryption_settings[$options] : [];
}
}
return $encryption_settings;
}
/**
* Get field type settings
*/
public function get_type_settings( $options = false ) {
$type_settings = self::$instance->type_settings;
if ( $options ) {
if ( is_array( $options ) ) {
if ( ! empty( $options ) ) {
$settings = [];
foreach ( $options as $option ) {
$settings[$option] = isset( $type_settings[$option] ) ? $type_settings[$option] : [];
}
return $settings;
}
} else {
return isset( $type_settings[$options] ) ? $type_settings[$options] : [];
}
}
return $type_settings;
}
/**
* Get no update settings
*/
public function get_noupdate_settings( $options = false ) {
$noupdate_settings = self::$instance->noupdate_settings;
if ( $options ) {
if ( is_array( $options ) ) {
if ( ! empty( $options ) ) {
$settings = [];
foreach ( $options as $option ) {
$settings[$option] = isset( $noupdate_settings[$option] ) ? $noupdate_settings[$option] : [];
}
return $settings;
}
} else {
return isset( $noupdate_settings[$options] ) ? $noupdate_settings[$options] : [];
}
}
return $noupdate_settings;
}
/**
* Set option values
*/
public function set_option_values( $option, $set_values = [] ) {
$values = self::$instance->values;
$option_values = isset( $values[$option] ) ? $values[$option] : [];
if ( is_array( $set_values ) && ! empty( $set_values ) ) {
foreach ( $set_values as $key => $value ) {
$option_values[$key] = $value;
}
}
// Update the option
update_option( $option, $option_values );
// Update the
$values[$option] = $option_values;
self::$instance->values = $value;
return $values;
}
/**
* Return an individual options values
*/
public function get_option_values( $option, $cache = true ) {
// Make sure values is an array
if ( ! is_array( self::$instance->values ) ) {
self::$instance->values = [];
}
// If the option values have been compiled, return them
$cache = false;
if ( isset( self::$instance->values[$option] ) && $cache ) {
return self::$instance->values[$option];
}
// Get the option
$settings = get_option( $option, [] );
if ( ! is_array( $settings ) ) {
$settings = [];
}
// Decrypt
$enc_settings = $this->get_encryption_settings( $option );
$settings = $this->decrypt_values( $settings, $enc_settings );
// Inject defaults
$parsed_settings = self::$instance->inject_default_values( $settings, $option );
// Sanitize
$sanitized_settings = self::$instance->sanitize_values( $parsed_settings, $option, 'get' );
// Add to the global values array
self::$instance->values[$option] = $sanitized_settings;
return $sanitized_settings;
}
/**
* Return values
*/
public function get_values( $options = false ) {
$options = $options ? $options : self::$instance->get_options();
if ( is_array( $options ) ) {
if ( ! empty( $options ) ) {
$values = [];
foreach ( $options as $option ) {
$values[$option] = self::$instance->get_option_values( $option );
}
return $values;
}
} else {
return self::$instance->get_option_values( $options );
}
// If empty, just return an empty array or something suitable
return [];
}
/**
* Create admin pages
*/
public function create_admin_pages() {
$admin_pages = self::$instance->get_admin_pages();
$updated_admin_pages = [];
if ( is_array( $admin_pages ) && ! empty( $admin_pages ) ) {
foreach ( $admin_pages as &$admin_page ) {
if ( isset( $admin_page['parent_slug'] ) ) {
$id = add_submenu_page(
$admin_page['parent_slug'],
$admin_page['page_title'],
$admin_page['menu_title'],
$admin_page['capability'],
$admin_page['menu_slug'],
function () use ( $admin_page ) {
echo '<div class="wrap">';
echo '<div id="mtphr-settings-app" data-id="' . self::$instance->get_id() . '" data-title="' . esc_attr( $admin_page['page_title'] ) . '"></div>'; // React App will be injected here
echo '</div>';
},
isset( $admin_page['position'] ) ? $admin_page['position'] : null,
);
$updated_admin_pages[$id] = $admin_page;
} else {
$id = add_menu_page(
$admin_page['page_title'],
$admin_page['menu_title'],
$admin_page['capability'],
$admin_page['menu_slug'],
function () use ( $admin_page ) {
echo '<div class="wrap">';
echo '<div id="mtphr-settings-app" data-id="' . self::$instance->get_id() . '" data-title="' . esc_attr( $admin_page['page_title'] ) . '"></div>'; // React App will be injected here
echo '</div>';
},
isset( $admin_page['icon'] ) ? $admin_page['icon'] : null,
isset( $admin_page['position'] ) ? $admin_page['position'] : null,
);
$updated_admin_pages[$id] = $admin_page;
}
}
}
self::$instance->admin_pages = $updated_admin_pages;
}
/**
* Enqueue admin scripts
*/
public function enqueue_scripts() {
// Only load script if on an admin page
$current_screen = get_current_screen();
$admin_pages = self::$instance->get_admin_pages();
if ( ! isset( $admin_pages[$current_screen->id] ) ) {
return false;
}
$admin_page = $admin_pages[$current_screen->id];
$sections = self::$instance->get_sections( $admin_page );
$settings = self::$instance->get_settings( $sections );
$options = self::$instance->get_option_keys( $sections );
$values = self::$instance->get_values( $options );
// Get header metadata (icon, description, version) from admin page
// Only escape URL if it's actually a URL (starts with http:// or https://)
// Otherwise, pass it as-is for dashicons or WordPress icon names
$header_icon_raw = isset( $admin_page['header_icon'] ) ? $admin_page['header_icon'] : '';
$header_icon = '';
if ( $header_icon_raw ) {
if ( strpos( $header_icon_raw, 'http://' ) === 0 || strpos( $header_icon_raw, 'https://' ) === 0 ) {
$header_icon = esc_url( $header_icon_raw );
} else {
// For dashicons or WordPress icon names, pass as-is (but sanitize)
$header_icon = sanitize_text_field( $header_icon_raw );
}
}
$header_description = isset( $admin_page['header_description'] ) ? $admin_page['header_description'] : '';
$header_version = isset( $admin_page['header_version'] ) ? esc_html( $admin_page['header_version'] ) : '';
// Load the Component Registry first
$asset_file = include( self::$instance->settings_dir . 'assets/build/mtphrSettingsRegistry.asset.php' );
wp_enqueue_script(
self::$instance->get_id() . 'Registry',
self::$instance->settings_url . 'assets/build/mtphrSettingsRegistry.js',
$asset_file['dependencies'],
filemtime( self::$instance->settings_dir . 'assets/build/mtphrSettingsRegistry.js' ),
true
);
// Add a hook for other scripts to register custom fields
do_action( self::$instance->get_id() . '/enqueue_fields', self::$instance->get_id() . 'Registry' );
$asset_file = include( self::$instance->settings_dir . 'assets/build/mtphrSettings.asset.php' );
wp_enqueue_style(
self::$instance->get_id(),
self::$instance->settings_url . 'assets/build/mtphrSettings.css',
['wp-components'],
filemtime( self::$instance->settings_dir . 'assets/build/mtphrSettings.css' ),
);
wp_enqueue_script(
self::$instance->get_id(),
self::$instance->settings_url . 'assets/build/mtphrSettings.js',
array_unique( array_merge( $asset_file['dependencies'], ['wp-element', 'wp-data', 'wp-components', 'wp-notices'] ) ),
filemtime( self::$instance->settings_dir . 'assets/build/mtphrSettings.js' ),
true
);
wp_add_inline_script( self::$instance->get_id(), self::$instance->get_id() . 'Vars = ' . wp_json_encode( array(
'siteUrl' => site_url(),
'restUrl' => esc_url_raw( rest_url( self::$instance->get_id() . '\/v1/' ) ),
'values' => $values,
'fields' => $settings,
'field_sections' => $sections,
'sidebar_items' => self::$instance->get_sidebar_items(),
'sidebar_width' => self::$instance->get_sidebar_width(),
'main_max_width' => self::$instance->get_main_content_max_width(),
'header_icon' => $header_icon,
'header_description' => $header_description,
'header_version' => $header_version,
'nonce' => wp_create_nonce( 'wp_rest' )
) ), 'before' ) . ';';
}
/**
* Register rest routes
*/
public function register_routes() {
register_rest_route( self::$instance->get_id() . '/v1', 'settings', array(
'methods' => 'POST',
'permission_callback' => function () {
return current_user_can('manage_options');
},
'callback' => function( $request ) {
$args = $request->get_params();
$data = $request->get_json_params();
$value_keys = isset( $data['valueKeys'] ) ? $data['valueKeys'] : [];
$values = isset( $data['values'] ) ? $data['values'] : [];
if ( is_array( $value_keys ) && ! empty( $value_keys ) ) {
foreach ( $value_keys as $option => $keys ) {
$values_to_update = array_intersect_key( $values[$option], array_flip( $keys ) );
$updated_values = self::$instance->update_values( $option, $values_to_update );
$values[$option] = $updated_values;
}
}
//self::$instance->update_values( $values );
return rest_ensure_response( $values , 200);
},
) );
}
/**
* Inject default values into $values if they do not exist.
*/
private function inject_default_values( $values, $option ) {
$default_values = self::$instance->get_default_values( $option );
return $this->recursive_inject_default_values( $values, $default_values );
}
/**
* Recursively inject default values into the values array.
*/
private function recursive_inject_default_values( $values, $default_values ) {
if ( ! is_array( $default_values ) || empty( $default_values ) ) {
return $values; // No defaults to inject
}
foreach ( $default_values as $key => $default_value ) {
if ( is_array( $default_value ) ) {
// Ensure $values[$key] is an array before merging
if ( !isset( $values[$key] ) || !is_array( $values[$key] ) ) {
$values[$key] = [];
}
// Recursively process nested arrays
$values[$key] = $this->recursive_inject_default_values(
$values[$key],
$default_value
);
} else {
// Only inject default if key does not exist in $values
if ( is_array( $values ) && ! array_key_exists( $key, $values ) ) {
$values[$key] = $default_value;
}
}
}
return $values;
}
/**
* Update the settings
*/
public function update_values( $option, $values = [] ) {
$settings = self::$instance->get_settings( $option );
$sanitize_settings = self::$instance->get_sanitize_settings( $option );
$option_values = self::$instance->get_option_values( $option );
$type_settings = self::$instance->get_type_settings( $option );
// 1) Recursively merge and sanitize
$updated_values = $this->recursive_update_values(
$option_values,
$values,
$sanitize_settings,
$type_settings,
$option
);
// 2) Retrieve the encryption settings for this option
$encryption_settings = $this->get_encryption_settings( $option );
// 3) Recursively encrypt the updated values
$updated_values = $this->encrypt_values( $updated_values, $encryption_settings );
// 4) Finally save the updated (and possibly encrypted) array
if ( 'disabled' != $option ) {
update_option( $option, $updated_values );
}
// Add to the global values array
self::$instance->values[$option] = $updated_values;
return $updated_values;
}
/**
* Recursively update and sanitize values.
*/
private function recursive_update_values( $existing_values, $new_values, $sanitize_settings, $type_settings, $option ) {
if ( ! is_array( $new_values ) || empty( $new_values ) ) {
return $existing_values;
}
foreach ( $new_values as $key => $value ) {
// Get the field type data for this key at the current level
$current_field_type = isset( $type_settings[$key] ) ? $type_settings[$key] : null;
$has_child_fields = is_array( $current_field_type ) && isset( $current_field_type['__type'] );
if ( is_array( $value ) && $has_child_fields ) {
// Recursively update only if this key actually has child fields
$existing_values[$key] = $this->recursive_update_values(
isset( $existing_values[$key] ) ? $existing_values[$key] : [],
$value,
isset( $sanitize_settings[$key] ) ? $sanitize_settings[$key] : [],
$current_field_type, // Pass only the relevant section of type_settings
$option
);
} else {
// Get the sanitizer for this specific key, or fallback to default
$sanitizer = isset( $sanitize_settings[$key] )
? $sanitize_settings[$key]
: self::$instance->get_default_sanitizer();
$existing_values[$key] = self::$instance->sanitize_value( $value, $sanitizer, $key, $option, 'update' );
}
}
return $existing_values;
}
/**
* Sanitize a value
*/
private function sanitize_values( $values, $option, $type = false ) {
$sanitize_settings = self::$instance->get_sanitize_settings( $option );
$type_settings = self::$instance->get_type_settings( $option );
return $this->recursive_sanitize_values( $values, $sanitize_settings, $option, $type_settings, $type );
}
/**
* Recursively sanitize values based on the sanitize settings structure.
*/
private function recursive_sanitize_values( $values, $sanitize_settings, $option, $type_settings, $type = false ) {
$sanitized_values = [];
if ( is_array( $values ) && ! empty( $values ) ) {
foreach ( $values as $key => $value ) {
// Get the field type data for this key at the current level
$current_field_type = isset( $type_settings[$key] ) ? $type_settings[$key] : null;
$has_child_fields = is_array( $current_field_type ) && isset( $current_field_type['__type'] );
if ( is_array( $value ) && $has_child_fields ) {
// Recursively sanitize nested arrays using the same key structure
$sanitized_values[$key] = $this->recursive_sanitize_values(
$value,
isset( $sanitize_settings[$key] ) ? $sanitize_settings[$key] : [],
$option,
$current_field_type,
$type
);
} else {
// Retrieve the appropriate sanitizer or fallback to the default
$sanitizer = isset( $sanitize_settings[$key] )
? $sanitize_settings[$key]
: self::$instance->get_default_sanitizer();
$sanitized_values[$key] = self::$instance->sanitize_value( $value, $sanitizer, $key, $option, $type );
}
}
}
return $sanitized_values;
}
/**
* Apply a sanitizer to a scalar value.
* Handles core simple sanitizers, 'none', custom callbacks, and fallback default.
*/
private function apply_sanitizer( $sanitizer, $value, $key, $option, $type ) {
switch ( $sanitizer ) {
case 'esc_attr':
case 'sanitize_text_field':
case 'wp_kses_post':
case 'intval':
case 'floatval':
case 'boolval':
return $sanitizer( $value );
case 'none':
return $value;
default:
if ( is_callable( $sanitizer ) ) {
// Custom sanitizer gets full context.
return call_user_func( $sanitizer, $value, $key, $option, $type );
}
// Fallback to your default sanitizer.
$default = self::$instance->get_default_sanitizer();
return is_callable( $default ) ? $default( $value ) : $value;
}
}
/**
* Recursively sanitize arrays of any depth.
*/
private function loop_sanitize_value( $value, $sanitizer, $key, $option, $type = false ) {
$sanitized = [];
foreach ( (array) $value as $k => $v ) {
if ( is_array( $v ) ) {
$sanitized[ $k ] = $this->loop_sanitize_value( $v, $sanitizer, $key, $option, $type );
} else {
$sanitized[ $k ] = $this->apply_sanitizer( $sanitizer, $v, $key, $option, $type );
}
}
return $sanitized;
}
/**
* Sanitize a value (scalar or multi-level array).
*/
private function sanitize_value( $value, $sanitizer, $key, $option, $type = false ) {
// If it's an array, recurse no matter which sanitizer we use.
if ( is_array( $value ) ) {
return $this->loop_sanitize_value( $value, $sanitizer, $key, $option, $type );
}
// If it's an object, return as-is (can't sanitize objects with string sanitizers).
if ( is_object( $value ) ) {
return $value;
}
// Scalar: just apply.
return $this->apply_sanitizer( $sanitizer, $value, $key, $option, $type );
}
/**
* Recursively encrypt values based on encryption settings.
*
* - If `['key_1' => X, 'key_2' => Y]` is found at `$encryption_settings[$key]`,
* encrypt the entire `$values[$key]` with those keys and **do not** recurse deeper.
* - Otherwise, if `$encryption_settings[$key]` is an array but lacks `key_1`/`key_2`,
* we assume it's a nested structure and continue recursing.
*/
private function encrypt_values( $values, $encryption_settings ) {
// If there is no encryption to apply or $values isn't an array, bail early.
if ( ! is_array( $encryption_settings ) || ! is_array( $values ) ) {
return $values;
}
foreach ( $encryption_settings as $key => $enc_info ) {
// Skip if $values does not have this key at all.
if ( ! is_array( $values ) || ! array_key_exists( $key, $values ) ) {
continue;
}
// Check if we have an actual encryption array with 'key_1'/'key_2'.
if ( is_array( $enc_info ) && isset( $enc_info['key_1'] ) && isset( $enc_info['key_2'] ) ) {
// Encrypt this entire branch with the custom keys and skip children.
$values[$key] = $this->encrypt( $values[$key], $enc_info['key_1'], $enc_info['key_2'] );
}
// If $enc_info is an array but no direct 'key_1'/'key_2',
// it's likely nested encryption settings. Recurse deeper if $values[$key] is array.
elseif ( is_array( $enc_info ) ) {
if ( is_array( $values[$key] ) ) {
$values[$key] = $this->encrypt_values( $values[$key], $enc_info );
}
}
}
return $values;
}
/**
* Encrypt data
*/
private function encrypt( $string = '', $custom_key_1 = null, $custom_key_2 = null ) {
if ( ! $string || '' === $string ) {
return $string;
}
// Convert arrays to JSON so we can encrypt them as strings.
if ( is_array( $string ) ) {
$string = json_encode( $string );
}
// Use custom keys if provided, otherwise fall back to defaults.
$key_1 = $custom_key_1 ?: $this->encryption_key_1;
$key_2 = $custom_key_2 ?: $this->encryption_key_2;
$key = hash( 'sha256', $key_1 );
$iv = substr( hash( 'sha256', $key_2 ), 0, 16 );
return base64_encode(
openssl_encrypt( $string, 'AES-256-CBC', $key, 0, $iv )
);
}
/**
* Recursively decrypt values based on the same encryption settings structure.
*
* - If we find `['key_1' => ..., 'key_2' => ...]` at `$encryption_settings[$key]`,
* we decrypt the entire `$values[$key]` with those keys, and do NOT recurse deeper.
* - If `$enc_info` is an array but lacks `key_1`/`key_2`, we assume it's nested settings.
* We keep recursing into `$values[$key]` if it's an array.
*/
private function decrypt_values( $values, $encryption_settings ) {
if ( ! is_array( $values ) || ! is_array( $encryption_settings ) ) {
return $values;
}
foreach ( $encryption_settings as $key => $enc_info ) {
// Skip if $values does not have this key at all
if ( ! is_array( $values ) || ! array_key_exists( $key, $values ) ) {
continue;
}
// If this level has explicit 'key_1' and 'key_2', decrypt the entire branch
if ( is_array( $enc_info ) && isset( $enc_info['key_1'] ) && isset( $enc_info['key_2'] ) ) {
$values[$key] = $this->decrypt( $values[$key], $enc_info['key_1'], $enc_info['key_2'] );
}
// If $enc_info is an array but no direct 'key_1'/'key_2',
// it might be nested encryption settings. Recurse deeper if $values[$key] is array
elseif ( is_array( $enc_info ) ) {
if ( is_array( $values[$key] ) ) {
$values[$key] = $this->decrypt_values( $values[$key], $enc_info );
}
}
}
return $values;
}
/**
* Decrypt data with optional custom keys.
*/
private function decrypt( $string, $custom_key_1 = null, $custom_key_2 = null ) {
// If already an array, it might have been double-processed or not encrypted at all
if ( is_array( $string ) || '' === $string ) {
return $string;
}
// Use custom keys if provided; otherwise, use defaults
$key_1 = $custom_key_1 ?: $this->encryption_key_1;
$key_2 = $custom_key_2 ?: $this->encryption_key_2;
$key = hash( 'sha256', $key_1 );
$iv = substr( hash( 'sha256', $key_2 ), 0, 16 );
$output = openssl_decrypt(
base64_decode( $string ),
'AES-256-CBC',
$key,
0,
$iv
);
// Attempt to JSON-decode the result to restore arrays if originally encrypted from an array
$decoded = json_decode( $output, true );
return (json_last_error() === JSON_ERROR_NONE) ? $decoded : $output;
}
private function get_admin_notices() {
$admin_notices = self::$instance->admin_notices;
if ( ! is_array( $admin_notices ) ) {
return [];
}
return $admin_notices;
}
private function add_admin_notice( $type, $message ) {
$admin_notices = self::$instance->get_admin_notices();
$admin_notices[] = [
'type' => $type,
'message' => $message,
];
self::$instance->admin_notices = $admin_notices;
}
/**
* Display admin notices
*/
public function admin_notices() {
$admin_notices = self::$instance->get_admin_notices();
if ( is_array( $admin_notices ) && ! empty( $admin_notices ) ) {
foreach ( $admin_notices as $notice ) {
echo '<div class="' . $notice['type'] . '">';
echo wp_kses_post( $notice['message'] );
echo '</div>';
}
}
}
/**
* Initialize settings - fires the init_settings action hook
*/
public function initialize_settings() {
if ( ! $this->settings_ready ) {
do_action( $this->get_id() . '/init_settings' );
$this->settings_ready = true;
}
}
/**
* Initialize fields - fires the init_fields action hook
*/
public function initialize_fields() {
if ( ! $this->fields_ready ) {
do_action( $this->get_id() . '/init_fields' );
$this->fields_ready = true;
}
}
/*--------------------------------------------------------------------------
* Static API Methods
* These provide a cleaner interface: Settings::add_admin_page($data)
*------------------------------------------------------------------------*/
/**
* Static: Add an admin page
*/
public static function admin_page( $data ) {
return self::instance()->add_admin_page( $data );
}
/**
* Static: Add a section
*/
public static function section( $data ) {
return self::instance()->add_section( $data );
}
/**
* Static: Add fields
*/
public static function fields( $data ) {
return self::instance()->add_fields( $data );
}
/**
* Static: Add sidebar
*/
public static function sidebar( $data ) {
return self::instance()->add_sidebar( $data );
}
/**
* Static: Add default values
*/
public static function default_values( $option, $values = [] ) {
return self::instance()->add_default_values( $option, $values );
}
/**
* Static: Add sanitize settings
*/
public static function sanitize_settings( $option, $values = [] ) {
return self::instance()->add_sanitize_settings( $option, $values );
}
/**
* Static: Add encryption settings
*/
public static function encryption_settings( $option, $values = [] ) {
return self::instance()->add_encryption_settings( $option, $values );
}
/**
* Static: Get an option value
*/
public static function get_value( $option, $key = false ) {
$values = self::instance()->get_option_values( $option );
if ( $key ) {
if ( isset( $values[$key] ) ) {
return $values[$key];
}
return null;
}
return $values;
}
/**
* Static: Set an option value
*/
public static function set_value( $option, $key, $value = false ) {
if ( is_array( $key ) ) {
$updated_values = $key;
} else {
$updated_values = [
$key => $value,
];
}
return self::instance()->update_values( $option, $updated_values );
}
}