697 lines
18 KiB
PHP
697 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Options collection
|
|
* @package Simple Lightbox
|
|
* @subpackage Options
|
|
* @author Archetyped
|
|
* @uses SLB_Field_Collection
|
|
*/
|
|
class SLB_Options extends SLB_Field_Collection {
|
|
|
|
/* Properties */
|
|
|
|
public $hook_prefix = 'options';
|
|
|
|
public $item_type = 'SLB_Option';
|
|
|
|
/**
|
|
* Key for saving version to DB
|
|
* @var string
|
|
*/
|
|
private $version_key = 'version';
|
|
|
|
/**
|
|
* Whether version has been checked
|
|
* @var bool
|
|
*/
|
|
public $version_checked = false;
|
|
|
|
public $items_migrated = false;
|
|
|
|
public $build_vars = array(
|
|
'validate_pre' => false,
|
|
'validate_post' => false,
|
|
'save_pre' => false,
|
|
'save_post' => false,
|
|
);
|
|
|
|
/* Init */
|
|
|
|
function __construct( $id = '', $props = array() ) {
|
|
// Validate arguments
|
|
$args = func_get_args();
|
|
// Set default ID
|
|
if ( ! $this->validate_id( $id ) ) {
|
|
$id = 'options';
|
|
}
|
|
$defaults = $this->integrate_id( $id );
|
|
$props = $this->make_properties( $args, $defaults );
|
|
parent::__construct( $props );
|
|
$this->add_prefix_ref( $this->version_key );
|
|
}
|
|
|
|
protected function _hooks() {
|
|
parent::_hooks();
|
|
// Register fields
|
|
$this->util->add_action( 'register_fields', $this->m( 'register_fields' ), 10, 1, false );
|
|
// Set option parents
|
|
$this->util->add_action( 'fields_registered', $this->m( 'set_parents' ), 10, 1, false );
|
|
// Building
|
|
$this->util->add_action( 'build_init', $this->m( 'build_init' ) );
|
|
// Admin
|
|
$this->util->add_action( 'admin_page_render_content', $this->m( 'admin_page_render_content' ), 10, 3, false );
|
|
$this->util->add_filter( 'admin_action_reset', $this->m( 'admin_action_reset' ), 10, 3, false );
|
|
}
|
|
|
|
/* Legacy/Migration */
|
|
|
|
/**
|
|
* Checks whether new version has been installed and migrates necessary settings
|
|
* @uses $version_key as option name
|
|
* @uses get_option() to retrieve saved version number
|
|
* @uses SLB_Utilities::get_plugin_version() to retrieve current version
|
|
* @return bool TRUE if version has been changed
|
|
*/
|
|
function check_update() {
|
|
if ( ! $this->version_checked ) {
|
|
$this->version_checked = true;
|
|
$version_changed = false;
|
|
// Get version from DB
|
|
$vo = $this->get_version();
|
|
// Get current version
|
|
$vn = $this->util->get_plugin_version();
|
|
// Compare versions
|
|
if ( $vo !== $vn ) {
|
|
// Update saved version
|
|
$this->set_version( $vn );
|
|
// Migrate old version to new version
|
|
if ( strcasecmp( $vo, $vn ) < 0 ) {
|
|
// Force full migration
|
|
$version_changed = true;
|
|
}
|
|
}
|
|
// Migrate
|
|
$this->migrate( $version_changed );
|
|
}
|
|
|
|
return $this->version_checked;
|
|
}
|
|
|
|
/**
|
|
* Save plugin version to DB
|
|
* If no version supplied, will fetch plugin data to determine version
|
|
* @uses $version_key as option name
|
|
* @uses update_option() to save version to options table
|
|
* @param string $ver (optional) Plugin version
|
|
*/
|
|
function set_version( $ver = null ) {
|
|
if ( empty( $ver ) ) {
|
|
$ver = $this->util->get_plugin_version();
|
|
}
|
|
return update_option( $this->version_key, $ver );
|
|
}
|
|
|
|
/**
|
|
* Retrieve saved version data
|
|
* @return string Saved version
|
|
*/
|
|
function get_version() {
|
|
return get_option( $this->version_key, '' );
|
|
}
|
|
|
|
/**
|
|
* Migrate options from old versions to current version
|
|
* @uses self::items_migrated to determine if simple migration has been performed in current request or not
|
|
* @uses self::save() to save data after migration
|
|
* @param bool $full Whether to perform a full migration or not (Default: No)
|
|
*/
|
|
function migrate( $full = false ) {
|
|
if ( ! $full && $this->items_migrated ) {
|
|
return false;
|
|
}
|
|
|
|
// Legacy options
|
|
$d = null;
|
|
$this->load_data();
|
|
|
|
$items = $this->get_items();
|
|
|
|
// Migrate separate options to unified option
|
|
if ( $full ) {
|
|
foreach ( $items as $opt => $props ) {
|
|
$oid = $this->add_prefix( $opt );
|
|
$o = get_option( $oid, $d );
|
|
if ( $o !== $d ) {
|
|
// Migrate value to data array
|
|
$this->set_data( $opt, $o, false );
|
|
// Delete legacy option
|
|
delete_option( $oid );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Migrate legacy items
|
|
if ( is_array( $this->properties_init ) && isset( $this->properties_init['legacy'] ) && is_array( $this->properties_init['legacy'] ) ) {
|
|
$l =& $this->properties_init['legacy'];
|
|
// Normalize legacy map
|
|
foreach ( $l as $opt => $dest ) {
|
|
if ( ! is_array( $dest ) ) {
|
|
if ( is_string( $dest ) ) {
|
|
$l[ $opt ] = array( $dest );
|
|
} else {
|
|
unset( $l[ $opt ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Separate options */
|
|
if ( $full ) {
|
|
foreach ( $l as $opt => $dest ) {
|
|
$oid = $this->add_prefix( $opt );
|
|
$o = get_option( $oid, $d );
|
|
// Only migrate valid values
|
|
if ( $o !== $d ) {
|
|
// Process destinations
|
|
foreach ( $dest as $id ) {
|
|
$this->set_data( $id, $o, false, true );
|
|
}
|
|
}
|
|
// Remove legacy option
|
|
delete_option( $oid );
|
|
}
|
|
}
|
|
|
|
/* Simple Migration (Internal options only) */
|
|
|
|
// Get existing items that are also legacy items
|
|
$opts = array_intersect_key( $this->get_data(), $l );
|
|
foreach ( $opts as $opt => $val ) {
|
|
$d = $this->get_data( $opt );
|
|
// Migrate data from old option to new option
|
|
$dest = $l[ $opt ];
|
|
// Validate new options to send data to
|
|
foreach ( $dest as $id ) {
|
|
$this->set_data( $id, $d, false, true );
|
|
}
|
|
// Remove legacy option
|
|
$this->remove( $opt, false );
|
|
}
|
|
}
|
|
// Save changes
|
|
$this->save();
|
|
// Set flag
|
|
$this->items_migrated = true;
|
|
}
|
|
|
|
/* Option setup */
|
|
|
|
/**
|
|
* Get elements for creating fields
|
|
* @return obj
|
|
*/
|
|
function get_field_elements() {
|
|
static $o = null;
|
|
if ( empty( $o ) ) {
|
|
$o = new stdClass();
|
|
/* Layout */
|
|
$layout = new stdClass();
|
|
$layout->label = '<label for="{field_id}" class="title block">{label}</label>';
|
|
$layout->label_ref = '{label ref_base="layout"}';
|
|
$layout->field_pre = '<div class="input block">';
|
|
$layout->field_post = '</div>';
|
|
$layout->opt_pre = '<div class="' . $this->add_prefix( 'option_item' ) . '">';
|
|
$layout->opt_post = '</div>';
|
|
$layout->form = '<{form_attr ref_base="layout"} /> <span class="description">(' . __( 'Default', 'simple-lightbox' ) . ': {data context="display" top="0"})</span>';
|
|
/* Combine */
|
|
$o->layout =& $layout;
|
|
}
|
|
return $o;
|
|
}
|
|
|
|
/**
|
|
* Register option-specific fields
|
|
* @param SLB_Fields $fields Reference to global fields object
|
|
* @return void
|
|
*/
|
|
function register_fields( $fields ) {
|
|
// Layouts
|
|
$o = $this->get_field_elements();
|
|
$l =& $o->layout;
|
|
|
|
$form = implode(
|
|
'',
|
|
array(
|
|
$l->opt_pre,
|
|
$l->label_ref,
|
|
$l->field_pre,
|
|
$l->form,
|
|
$l->field_post,
|
|
$l->opt_post,
|
|
)
|
|
);
|
|
|
|
// Text input
|
|
$otxt = new SLB_Field_Type( 'option_text', 'text' );
|
|
$otxt->set_property( 'class', '{inherit} code' );
|
|
$otxt->set_property( 'size', null );
|
|
$otxt->set_property( 'value', '{data}' );
|
|
$otxt->set_layout( 'label', $l->label );
|
|
$otxt->set_layout( 'form', $form );
|
|
$fields->add( $otxt );
|
|
|
|
// Checkbox
|
|
$ocb = new SLB_Field_Type( 'option_checkbox', 'checkbox' );
|
|
$ocb->set_layout( 'label', $l->label );
|
|
$ocb->set_layout( 'field_reference', sprintf( '<input type="hidden" name="%s" value="{field_name format="raw"}" />', "{$this->get_id('formatted')}_items[]" ) );
|
|
$ocb->set_layout( 'form', '{field_reference ref_base="layout"}' . $form );
|
|
$fields->add( $ocb );
|
|
|
|
// Select
|
|
$othm = new SLB_Field_Type( 'option_select', 'select' );
|
|
$othm->set_layout( 'label', $l->label );
|
|
$othm->set_layout( 'form_start', $l->field_pre . '{inherit}' );
|
|
$othm->set_layout( 'form_end', '{inherit}' . $l->field_post );
|
|
$othm->set_layout( 'form', $l->opt_pre . '{inherit}' . $l->opt_post );
|
|
$fields->add( $othm );
|
|
}
|
|
|
|
/**
|
|
* Set parent field types for options
|
|
* Parent only set for Admin pages
|
|
* @uses SLB_Option::set_parent() to set parent field for each option item
|
|
* @uses is_admin() to determine if current request is admin page
|
|
* @param object $fields Collection of default field types
|
|
* @return void
|
|
*/
|
|
function set_parents( $fields ) {
|
|
if ( ! is_admin() ) {
|
|
return false;
|
|
}
|
|
$items = &$this->get_items();
|
|
foreach ( array_keys( $items ) as $opt ) {
|
|
$items[ $opt ]->set_parent();
|
|
}
|
|
foreach ( $this->items as $opt ) {
|
|
$p = $opt->parent;
|
|
if ( is_object( $p ) ) {
|
|
$p = 'o:' . $p->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Processing */
|
|
|
|
/**
|
|
* Validates option data.
|
|
*
|
|
* Validates option values (e.g. prior to saving to DB, after form submission, etc.).
|
|
* Values are formatted and sanitized according to corresponding option's data type
|
|
* (e.g. boolean, string, number, etc.).
|
|
*
|
|
* @since 1.5.5
|
|
*
|
|
* @param array<string,scalar> $values Optional. Option data to validate.
|
|
* Indexed by option ID.
|
|
* Default form-submission data used.
|
|
* @return array<string,scalar> Validated data. Indexed by option ID.
|
|
*/
|
|
function validate( $values = null ) {
|
|
/** @var array<string,scalar> $values_valid Validated option data. Indexed by option ID. */
|
|
$values_valid = [];
|
|
// Enforce values data type.
|
|
if ( ! is_array( $values ) ) {
|
|
/** @var array<string,scalar> $values */
|
|
$values = [];
|
|
}
|
|
/**
|
|
* Generates query variable using common base.
|
|
*
|
|
* @since 2.8.0
|
|
*
|
|
* @param string $text Optional. Text to append to base.
|
|
*
|
|
* @return string Query variable name. Format: "{base}_{text}". Default "{base}".
|
|
*/
|
|
$qv = function ( $text = '' ) {
|
|
static $base;
|
|
// Get base.
|
|
if ( empty( $base ) ) {
|
|
$base = $this->get_id( 'formatted' );
|
|
}
|
|
$out = $base;
|
|
// Append text to base.
|
|
if ( is_string( $text ) && ! empty( $text ) ) {
|
|
$out .= "_{$text}";
|
|
}
|
|
return $out;
|
|
};
|
|
// Get options form field group ID.
|
|
$qvar = $qv();
|
|
// Use form submission data when no values provided.
|
|
if ( empty( $values ) && isset( $_POST[ $qvar ] ) && check_admin_referer( $qvar, $qv( 'nonce' ) ) ) {
|
|
/** @var array<string,scalar> $values */
|
|
$values = $_POST[ $qvar ];
|
|
// Append non-submitted, but rendered fields (e.g. unchecked checkboxes)
|
|
$qvar_items = $qv( 'items' );
|
|
/** @var string[] $items_bool Boolean options rendered in submitted form. */
|
|
$items_bool = ( isset( $_POST[ $qvar_items ] ) ) ? $_POST[ $qvar_items ] : null;
|
|
if ( ! empty( $items_bool ) && is_array( $items_bool ) ) {
|
|
foreach ( $items_bool as $item_id ) {
|
|
// Add missing boolean options (false == unchecked).
|
|
if ( ! array_key_exists( $item_id, $values ) && $this->has( $item_id ) && is_bool( $this->get_default( $item_id ) ) ) {
|
|
$values_valid[ $item_id ] = false;
|
|
}
|
|
}
|
|
}
|
|
unset( $qvar, $qvar_items, $items_bool, $item_id );
|
|
}
|
|
// Process values.
|
|
/**
|
|
* @var string $id Option ID.
|
|
* @var mixed $val Option value (raw/unsanitized).
|
|
*/
|
|
foreach ( $values as $id => $val ) {
|
|
// Do not process invalid option IDs or invalid (non-scalar) data.
|
|
if ( ! $this->has( $id ) || ! is_scalar( $val ) ) {
|
|
continue;
|
|
}
|
|
// Conform to option's data type and sanitize.
|
|
/** @var scalar $d Option's default data. */
|
|
$d = $this->get_default( $id );
|
|
if ( is_bool( $d ) ) {
|
|
// Boolean.
|
|
$val = ! ! $val;
|
|
} elseif ( ( is_int( $d ) || is_float( $d ) ) && ! is_numeric( $val ) ) {
|
|
// Numeric - do not process non-numeric values for int/float fields.
|
|
continue;
|
|
} elseif ( is_int( $d ) ) {
|
|
// Integer.
|
|
$val = (int) $val;
|
|
} elseif ( is_float( $d ) ) {
|
|
// Float.
|
|
$val = (float) $val;
|
|
} else {
|
|
// Defaut: Handle as string.
|
|
$val = sanitize_text_field( wp_unslash( $val ) );
|
|
}
|
|
// Add to validated data.
|
|
$values_valid[ $id ] = $val;
|
|
}
|
|
unset( $id, $val );
|
|
|
|
// Return validated values.
|
|
return $values_valid;
|
|
}
|
|
|
|
/* Data */
|
|
|
|
/**
|
|
* Retrieve options from database
|
|
* @uses get_option to retrieve option data
|
|
* @return array Options data
|
|
*/
|
|
function fetch_data( $sanitize = true ) {
|
|
// Get data
|
|
$data = get_option( $this->get_key(), null );
|
|
if ( $sanitize && is_array( $data ) ) {
|
|
// Sanitize loaded data based on default values
|
|
foreach ( $data as $id => $val ) {
|
|
if ( $this->has( $id ) ) {
|
|
$opt = $this->get( $id );
|
|
if ( is_bool( $opt->get_default() ) ) {
|
|
$data[ $id ] = ! ! $val;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Retrieves option data for collection
|
|
* @see SLB_Field_Collection::load_data()
|
|
*/
|
|
function load_data() {
|
|
if ( ! $this->data_loaded ) {
|
|
// Retrieve data
|
|
$this->data = $this->fetch_data();
|
|
parent::load_data();
|
|
// Check update
|
|
$this->check_update();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets option values to their default values
|
|
* @param bool $hard Reset all options if TRUE (default), Reset only unset options if FALSE
|
|
*/
|
|
function reset( $hard = true ) {
|
|
$this->load_data();
|
|
// Reset data
|
|
if ( $hard ) {
|
|
$this->data = null;
|
|
}
|
|
// Save
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Save options data to database
|
|
*/
|
|
function save() {
|
|
$this->normalize_data();
|
|
update_option( $this->get_key(), $this->data );
|
|
}
|
|
|
|
/**
|
|
* Normalize data
|
|
* Assures that data in collection match items
|
|
* @uses self::data to reset and save collection data after normalization
|
|
*/
|
|
function normalize_data() {
|
|
$data = array();
|
|
foreach ( $this->get_items() as $id => $opt ) {
|
|
$data[ $id ] = $opt->get_data();
|
|
}
|
|
$this->data =& $data;
|
|
return $data;
|
|
}
|
|
|
|
/* Collection */
|
|
|
|
/**
|
|
* Build key for saving/retrieving data to options table
|
|
* @return string Key
|
|
*/
|
|
function get_key() {
|
|
return $this->add_prefix( $this->get_id() );
|
|
}
|
|
|
|
/**
|
|
* Add option to collection
|
|
* @uses SLB_Field_Collection::add() to add item
|
|
* @param string $id Unique item ID
|
|
* @param array $properties Item properties
|
|
* @param bool $update (optional) Should item be updated or overwritten (Default: FALSE)
|
|
* @return SLB_Option Option instance
|
|
*/
|
|
function &add( $id, $properties = array(), $update = false ) {
|
|
// Create item
|
|
$args = func_get_args();
|
|
$ret = call_user_func_array( array( 'parent', 'add' ), $args );
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Retrieve option value
|
|
* @uses get_data() to retrieve option data
|
|
* @param string $option Option ID to retrieve value for
|
|
* @param string $context (optional) Context for formatting data
|
|
* @return mixed Option value
|
|
*/
|
|
function get_value( $option, $context = '' ) {
|
|
return $this->get_data( $option, $context );
|
|
}
|
|
|
|
/**
|
|
* Retrieve option value as boolean (true/false)
|
|
* @uses get_data() to retrieve option data
|
|
* @param string $option Option ID to retrieve value for
|
|
* @return bool Option value
|
|
*/
|
|
function get_bool( $option ) {
|
|
return $this->get_value( $option, 'bool' );
|
|
}
|
|
|
|
function get_string( $option ) {
|
|
return $this->get_value( $option, 'string' );
|
|
}
|
|
|
|
/**
|
|
* Retrieve option's default value
|
|
* @uses get_data() to retrieve option data
|
|
* @param string $option Option ID to retrieve value for
|
|
* @param string $context (optional) Context for formatting data
|
|
* @return mixed Option's default value
|
|
*/
|
|
function get_default( $option, $context = '' ) {
|
|
return $this->get_data( $option, $context, false );
|
|
}
|
|
|
|
/* Output */
|
|
|
|
function build_init() {
|
|
if ( $this->build_vars['validate_pre'] ) {
|
|
$values = $this->validate();
|
|
if ( $this->build_vars['save_pre'] ) {
|
|
$this->set_data( $values );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build array of option values for client output
|
|
* @return array Associative array of options
|
|
*/
|
|
function build_client_output() {
|
|
$items = $this->get_items();
|
|
$out = array();
|
|
foreach ( $items as $option ) {
|
|
if ( ! $option->get_in_client() ) {
|
|
continue;
|
|
}
|
|
$out[ $option->get_id() ] = $option->get_data( 'default' );
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
/* Admin */
|
|
|
|
/**
|
|
* Handles output building for options on admin pages
|
|
* @param obj|array $opts Options instance or Array of options instance and groups to build
|
|
* @param obj $page Admin Page instance
|
|
* @param obj $state Admin Page state properties
|
|
*/
|
|
public function admin_page_render_content( $opts, $page, $state ) {
|
|
$groups = null;
|
|
if ( is_array( $opts ) && count( $opts ) === 2 ) {
|
|
$groups = $opts[1];
|
|
$opts = $opts[0];
|
|
}
|
|
if ( $opts === $this ) {
|
|
// Set build variables and callbacks
|
|
$this->set_build_var( 'admin_page', $page );
|
|
$this->set_build_var( 'admin_state', $state );
|
|
if ( ! empty( $groups ) ) {
|
|
$this->set_build_var( 'groups', $groups );
|
|
}
|
|
$hooks = array(
|
|
'filter' => array(
|
|
'parse_build_vars' => array( $this->m( 'admin_parse_build_vars' ), 10, 2 ),
|
|
),
|
|
);
|
|
|
|
// Add hooks
|
|
foreach ( $hooks as $type => $hook ) {
|
|
$m = 'add_' . $type;
|
|
foreach ( $hook as $tag => $args ) {
|
|
array_unshift( $args, $tag );
|
|
call_user_func_array( $this->util->m( $m ), $args );
|
|
}
|
|
}
|
|
|
|
// Build output
|
|
$this->build( array( 'build_groups' => $this->m( 'admin_build_groups' ) ) );
|
|
|
|
// Remove hooks
|
|
foreach ( $hooks as $type => $hook ) {
|
|
$m = 'remove_' . $type;
|
|
foreach ( $hook as $tag => $args ) {
|
|
call_user_func( $this->util->m( $m ), $tag, $args[0] );
|
|
}
|
|
}
|
|
// Clear custom build vars
|
|
$this->delete_build_var( 'admin_page' );
|
|
$this->delete_build_var( 'admin_state' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds option groups output
|
|
*/
|
|
public function admin_build_groups() {
|
|
$page = $this->get_build_var( 'admin_page' );
|
|
$state = $this->get_build_var( 'admin_state' );
|
|
$groups = $this->get_build_var( 'groups' );
|
|
|
|
// Get all groups
|
|
$groups_all = $this->get_groups();
|
|
if ( empty( $groups ) ) {
|
|
$groups = array_keys( $groups_all );
|
|
}
|
|
// Iterate through groups
|
|
foreach ( $groups as $gid ) {
|
|
// Validate
|
|
if ( ! isset( $groups_all[ $gid ] ) || ! count( $this->get_items( $gid ) ) ) {
|
|
continue;
|
|
}
|
|
// Add meta box for each group
|
|
$g = $groups_all[ $gid ];
|
|
add_meta_box(
|
|
$g->id,
|
|
$g->title,
|
|
$this->m( 'admin_build_group' ),
|
|
$state->screen,
|
|
$state->context,
|
|
$state->priority,
|
|
array(
|
|
'group' => $g->id,
|
|
'page' => $page,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Group output handler for admin pages
|
|
* @param obj $obj Object passed by `do_meta_boxes()` call (Default: NULL)
|
|
* @param array $box Meta box properties
|
|
*/
|
|
public function admin_build_group( $obj, $box ) {
|
|
$a = $box['args'];
|
|
$group = $a['group'];
|
|
$this->build_group( $group );
|
|
}
|
|
|
|
/**
|
|
* Parse build vars
|
|
* @uses `options_parse_build_vars` filter hook
|
|
*/
|
|
public function admin_parse_build_vars( $vars, $opts ) {
|
|
// Handle form submission
|
|
if ( isset( $_POST[ $opts->get_id( 'formatted' ) ] ) ) {
|
|
$vars['save_pre'] = true;
|
|
$vars['validate_pre'] = true;
|
|
}
|
|
return $vars;
|
|
}
|
|
|
|
/**
|
|
* Admin reset handler
|
|
* @param bool $res Current result
|
|
* @param obj $opts Options instance
|
|
* @param obj $reset Admin Reset instance
|
|
*/
|
|
public function admin_action_reset( $res, $opts, $reset ) {
|
|
// Only process matching options instance
|
|
if ( $opts === $this ) {
|
|
// Reset options
|
|
$this->reset();
|
|
// Set result
|
|
$res = true;
|
|
}
|
|
return $res;
|
|
}
|
|
}
|