Your IP : 216.73.216.196
<?php
/**
* Theme Options handler on admin side
*/
class Bunyad_Admin_Options
{
public $option_key;
public $options;
public function __construct()
{
$this->option_key = Bunyad::options()->get_config('theme_prefix') .'_theme_options';
// Initialize.
add_action('admin_init', [$this, 'init']);
add_action('after_switch_theme', [$this, 'check_version']);
// Cleanup defaults. Caps will be pre-checked.
add_action('customize_save_after', [$this, 'customizer_save_process']);
}
/**
* Initialize at admin_init hook.
*/
public function init()
{
// Current user should have sufficient priveleges to continue.
if (!current_user_can('edit_theme_options')) {
return;
}
// Check for a changed version in non-AJAX only.
if (!wp_doing_ajax()) {
$this->check_version();
}
}
/**
* Check current theme version and run an update hook if necessary
*/
public function check_version()
{
$option = Bunyad::options()->get_config('theme_prefix') . '_theme_version';
// Stored version info
$version_info = (array) get_option($option);
$stored_version = !empty($version_info['current']) ? $version_info['current'] : null;
// Legacy compat: Get from options.
if (!$stored_version && Bunyad::options()->theme_version) {
$stored_version = Bunyad::options()->theme_version;
}
// Update version if necessary.
if (!$stored_version || !version_compare($stored_version, Bunyad::options()->get_config('theme_version'), '==')) {
// This shouldn't happen, but just in case the previous version is known and we're in customizer.
// We can't update while in customizer preview.
if ($stored_version && function_exists('is_customize_preview') && is_customize_preview()) {
return wp_die(
'The theme has a pending update that may cause fatal errors on customizer. Please go back to your WordPress admin area first and then open Customizer again.',
'Pending Update!'
);
}
// Fire up the hook.
do_action('bunyad_theme_version_change', $stored_version);
// Can be filtered to stop the version update in db.
if (!apply_filters('bunyad_theme_version_update_done', true)) {
return;
}
// Update the theme version.
$version_info['current'] = Bunyad::options()->get_config('theme_version');
// Add in first install data if missing.
$version_info['first_install'] = $version_info['first_install'] ?? time();
if ($stored_version) {
$version_info['previous'] = $stored_version;
}
// Update changes in database.
update_option($option, array_filter($version_info));
/**
* Force recheck plugin updates to get update prompts for required plugins.
*/
$transient = (object) get_site_transient('update_plugins');
$transient->last_checked = time() - DAY_IN_SECONDS;
set_site_transient('update_plugins', $transient);
do_action('wp_update_plugins');
/**
* Refresh TGMPA notices to alert of new plugin updates.
*/
$id = Bunyad::options()->get_config('theme_prefix') . '_tgmpa';
delete_metadata('user', null, 'tgmpa_dismissed_notice_' . $id, null, true);
}
}
/**
* Load options locally for the class
*
* @deprecated No longer in use.
*/
public function set_options($options = null)
{
if ($options) {
$this->options = $options;
}
else if (!$this->options) {
// Get default options if empty
$this->options = include get_template_directory() . '/admin/options.php';
}
return $this;
}
/**
* Extract elements/fields from the options tree.
*
* @param array $options Options tree.
* @param boolean $sub Deprecated. Add partial sub-elements in the list.
* @param string $tab_id Filter using a tab id.
*/
public function get_elements_from_tree(array $options, $sub = false, $tab_id = null)
{
$elements = [];
foreach ($options as $tab) {
if ($tab_id != null && $tab['id'] !== $tab_id) {
continue;
}
if (empty($tab['sections'])) {
continue;
}
foreach ($tab['sections'] as $section) {
foreach ($section['fields'] as $element) {
// Pseudo element?
if (empty($element['name'])) {
continue;
}
$elements[$element['name']] = $element;
}
}
}
return $elements;
}
/**
* Post-process Save customizer options.
*
* This is needed to fix the defaults on customizer as it saves values in DB even when default is used.
*
* @todo Refactor to Bunyad_Options::update()
*/
public function customizer_save_process()
{
// The save options (Just saved by Customizer before this hook)
$options = get_option($this->option_key);
if (empty($options)) {
return;
}
$options = $this->remove_defaults($options);
// Remove dependencies disabled via context.
// @deprecated Remove on runtime instead to preserve entered data in customizer.
// $options = $this->remove_disabled_contexts($options, $elements);
// Save the updated options
update_option($this->option_key, $options);
}
/**
* Remove options that have default value based on provided elements tree.
*
* @uses Bunyad::options()
* @param array $options Key => value array.
* @param array $elements Optional options tree.
*/
public function remove_defaults($options, $elements = [])
{
if (!$elements) {
// Get options and process them to add group pseudo-options and defaults.
$elements = Bunyad::options()->load_elements(false);
}
// Remove defaults
foreach ($options as $key => $value) {
// Reset default as isset() is used later.
unset($default);
// Unrecognized element. Skip.
// Note: Not unsetting as it might be a legacy value still needed in options storage.
if (!isset($elements[$key])) {
continue;
}
// Skip groups and other fields that should not have a value. This is also removed on runtime.
if (isset($elements[$key]['type']) && in_array($elements[$key]['type'], ['group', 'message'])) {
unset($options[$key]);
continue;
}
// Default unspecified?
if (isset($elements[$key]) && isset($elements[$key]['value'])) {
$default = $elements[$key]['value'];
}
if (is_array($value)) {
foreach ($value as $k => $v) {
/**
* For special arrays that have keys in options as social_profile[facebook]
*/
$ele_key = "{$key}[{$k}]";
if (isset($elements[$ele_key])) {
$ele_default = $elements[$ele_key]['value'];
if ($ele_default == $v) {
unset($options[$key][$k]);
}
}
}
// Filter empty entries from devices array. If the default 'value' is known and available.
if (!empty($elements[$key]['devices']) && isset($default)) {
$filtered = array_filter($value);
// If default value is empty, customizer module hasn't run process_options()
// and there was no manual default value. Remove empty keys / use filtered.
if (empty($default)) {
$value = $filtered;
}
else {
// If array is empty or the only value is limit, set to default / remove.
if (!$filtered || (count($filtered) === 1 && isset($filtered['limit']))) {
$value = $default;
}
}
}
// Empty arrays are removed only if the default value is empty too. (below)
// Otherwise, selecting no checkboxes for example can be a problem.
}
// Remove default values.
// Note: Arrays with same keys are equal as it's a loose match. Sortables shouldn't
// use string keys but dynamic integer keys.
//
// Caveat: Casting to int would mean, '#222' or 'e' == 0 in loose match, pre-php8.
if (isset($default)) {
// BC: PHP 8+ doesn't match 0 == '' anymore, so do int match.
// Customizer often may save toggles as '' or 1.
// We're using floatval() so a value of 0.x won't be stripped when casted to int.
// Using floatval() is non-locale-aware unlike (float) and works fine with non-decimals.
$value = $default === 0 ? floatval($value) : $value;
if ($default == $value) {
unset($options[$key]);
}
}
}
return $options;
}
/**
* Remove disabled dependencies based on context.
*
* @param array $options
* @return array
*/
public function remove_disabled_contexts($options, $elements)
{
// Override default values on elements.
foreach ($options as $key => $value) {
if (isset($elements[$key])) {
$elements[$key]['value'] = $value;
// Remove groups and other fields that should not have a value.
if (isset($elements[$key]['type']) && in_array($elements[$key]['type'], ['group', 'message'])) {
unset($options[$key]);
}
}
}
// Note: Separate from previous loop as all values need to be made available beforehand
// for the method is_context_active.
foreach ($options as $key => $value) {
if (!isset($elements[$key]) || !array_key_exists('context', $elements[$key])) {
continue;
}
// Preserved flag - do not remove.
if (!empty($elements[$key]['preserve'])) {
continue;
}
$context = (array) $elements[$key]['context'];
if (!$this->is_context_active($context, $elements)) {
unset($options[$key]);
}
}
return $options;
}
/**
* Check if an element is contextually active.
*
* @param array $contexts Context tests to conduct.
* @param array $elements Elements expected with values overriden (not defaults).
*
* @return boolean
*/
public function is_context_active($contexts, $elements)
{
$active = null;
// Empty context means active.
if (!$contexts) {
return true;
}
foreach ($contexts as $data) {
$data['relation'] = isset($data['relation']) ? $data['relation'] : 'AND';
// Previous condition failed, continue no more with AND relation.
if ($active === false && $data['relation'] !== 'OR') {
return false;
}
// Previous condition passed, stop with OR relation.
if ($active === true && $data['relation'] === 'OR') {
return true;
}
$expected = $data['value'];
$value = $elements[ $data['key'] ]['value'] ?? false;
$compare = isset($data['compare']) ? $data['compare'] : '';
$active = $this->context_compare($value, $expected, $compare);
}
return $active;
}
/**
* Compare current with expected value.
*
* @return boolean
*/
public function context_compare($value, $expected, $compare)
{
if (is_array($expected)) {
$compare = $compare == '!=' ? 'not in' : 'in';
}
else {
// BC: PHP 8+ doesn't match 0 == '' anymore, so int convert.
$value = $expected === 0 ? (int) $value : $value;
$expected = $value === 0 ? (int) $expected : $expected;
}
switch ($compare) {
case 'in':
case 'not in':
$return = in_array($value, $expected);
return $compare == 'in' ? $return : !$return;
case '!=':
return $value != $expected;
default:
return $value == $expected;
}
}
/**
* Delete / reset options - security checks done outside.
*/
public function delete_options($type = null)
{
// Empty it all.
Bunyad::options()
->set_all([])
->update();
do_action('bunyad_options_reset');
return true;
}
}