array(
// 'type' => 'checkbox',
// 'default' => '1',
// 'value' => '1',
// ),
// 'optionkey2' => array(
// 'type' => 'radio',
// 'default' => 'on',
// 'options' => 'on/off',
// ),
// 'optionkey3' => array(
// 'type' => 'special',
// ),
// );
// -----------------------
// Example Plugin Settings
// -----------------------
// $slug = 'plugin-name'; // plugin slug (usually same as filename)
// $args = array(
// // --- Plugin Info ---
// 'slug' => $slug, // (uses slug above)
// 'file' => __FILE__, // path to main plugin file (required!)
// 'version' => '0.0.1', // * rechecked later (pulled from plugin header) *
//
// // --- Menus and Links ---
// 'title' => 'Plugin Name', // plugin title
// 'parentmenu' => 'wordquest', // parent menu slug
// 'home' => 'http://mysite.com/plugins/plugin/',
// 'support' => 'http://mysite.com/plugins/plugin/support/',
// 'ratetext' => __('Rate on WordPress.org'), // (overrides default rate text)
// 'share' => 'http://mysites.com/plugins/plugin/#share', // (set sharing URL)
// 'sharetext' => __('Share the Plugin Love'), // (overrides default sharing text)
// 'donate' => 'https://patreon.com/pagename', // (overrides plugin Donate URI)
// 'donatetext' => __('Support this Plugin'), // (overrides default donate text)
// 'readme' => false, // to not link to popup readme in settings page header
// 'settingsmenu' => false, // to not automatically add a settings menu [non-WQ]
//
// // --- Options ---
// 'namespace' => 'plugin_name', // plugin namespace (function prefix)
// 'settings' => 'pn', // input settings prefix
// 'option' => 'plugin_key', // plugin option key
// 'options' => $options, // plugin options array set above
//
// // --- WordPress.Org ---
// 'wporgslug' => 'plugin-slug', // WordPress.org plugin slug
// 'wporg' => false, // * rechecked later (via presence of updatechecker.php) *
// 'textdomain' => 'text-domain', // translation text domain (usually same as plugin slug)
//
// // --- Freemius ---
// 'freemius_id' => '', // Freemius plugin ID
// 'freemius_key' => '', // Freemius public key
// 'hasplans' => false, // if plugin has paid plans
// 'hasaddons' => false, // if plugin has add ons
// 'plan' => 'free', // * rechecked later (if premium version found) *
// );
//
// ------------------------------------
// Example Start Plugin Loader Instance
// ------------------------------------
// (add this to your main plugin file to run this loader)
// require(dirname(__FILE__).'/loader.php'); // requires this file!
// $instance = new teleporter_loader($args); // instantiates loader class
// (ie. search and replace 'teleporter_' with 'my_plugin_' function namespace)
// ===========================
// --- Plugin Loader Class ---
// ===========================
// usage: change class prefix to the plugin function prefix
if ( !class_exists( 'teleporter_loader' ) ) {
// phpcs:ignore PEAR.NamingConventions.ValidClassName.Invalid,PEAR.NamingConventions.ValidClassName.StartWithCapital
class teleporter_loader {
public $args = null;
public $namespace = null;
public $data = null;
public $version = null;
public $options = null;
public $defaults = null;
public $tabs = array();
public $sections = array();
public $scripts = array();
// 1.1.2: added menu added switch
// 1.1.2: added debug switch
public $menu_added = false;
public $debug = false;
// -----------------
// Initialize Loader
// -----------------
public function __construct( $args ) {
if ( !is_array( $args ) ) {
return;
}
// --- set debug switch ---
// 1.1.2: added debug switch check
$prefix = '';
if ( $args['settings'] ) {
// 1.1.4: fix to debug prefix key
$prefix = $args['settings'] . '-';
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST[$prefix . 'debug'] ) ) {
// 1.2.5: use sanitize_text_field on request variable
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( in_array( sanitize_text_field( $_REQUEST[$prefix . 'debug'] ), array( '1', 'yes' ) ) ) {
$this->debug = true;
}
}
// --- set plugin options ---
// 1.0.6: added options filter
$args['options'] = apply_filters( $args['namespace'] . '_options', $args['options'] );
// 1.0.9: maybe get tabs and sections from options array
if ( isset( $args['options']['tabs'] ) ) {
$this->tabs = $args['options']['tabs'];
unset( $args['options']['tabs'] );
}
if ( isset( $args['options']['sections'] ) ) {
$this->sections = $args['options']['sections'];
unset( $args['options']['sections'] );
}
$this->options = $args['options'];
unset( $args['options'] );
// --- set plugin args and namespace ---
// 1.1.9: filter all arguments
$args = apply_filters( $args['namespace'] . '_args', $args );
$this->args = $args;
$this->namespace = $args['namespace'];
// --- setup plugin values ---
$this->setup_plugin();
// --- maybe transfer settings ---
$this->maybe_transfer_settings();
// --- load settings ---
$this->load_settings();
// --- load actions ---
$this->add_actions();
// --- load helper libraries ---
$this->load_helpers();
// --- autoset class instance global for accessibility ---
$GLOBALS[$args['namespace'] . '_instance'] = $this;
}
// ------------
// Setup Plugin
// ------------
public function setup_plugin() {
$args = $this->args;
$namespace = $this->namespace;
// --- Read Plugin Header ---
if ( !isset( $args['dir'] ) ) {
$args['dir'] = dirname( $args['file'] );
}
// phpcs:ignore WordPress.WP.AlternativeFunctions
$fh = fopen( $args['file'], 'r' );
// phpcs:ignore WordPress.WP.AlternativeFunctions
$data = fread( $fh, 2048 );
$this->data = str_replace( "\r", "\n", $data );
// phpcs:ignore WordPress.WP.AlternativeFunctions
fclose( $fh );
// --- Version ---
$this->version = $args['version'] = $this->plugin_data( 'Version:' );
// --- Title ---
if ( !isset( $args['title'] ) ) {
$args['title'] = $this->plugin_data( 'Plugin Name:' );
}
// --- Plugin Home ---
if ( !isset( $args['home'] ) ) {
$args['home'] = $this->plugin_data( 'Plugin URI:' );
}
// --- Author ---
if ( !isset( $args['author'] ) ) {
$args['author'] = $this->plugin_data( 'Author:' );
}
// --- Author URL ---
if ( !isset( $args['author_url'] ) ) {
$args['author_url'] = $this->plugin_data( 'Author URI:' );
}
// --- Pro Functions ---
if ( !isset( $args['proslug'] ) ) {
$proslug = $this->plugin_data( '@fs_premium_only' );
// 1.0.1: if more than one file, extract pro slug based on the first filename
if ( !strstr( $proslug, ',' ) ) {
$profiles = array( $proslug );
$proslug = trim( $proslug );
} else {
$profiles = explode( ',', $proslug );
$proslug = trim( $profiles[0] );
}
$args['proslug'] = substr( $proslug, 0, - 4 ); // strips .php extension
$args['profiles'] = $profiles;
}
// --- Update Loader Args ---
$this->args = $args;
}
// ---------------
// Get Plugin Data
// ---------------
public function plugin_data( $key ) {
$data = $this->data;
$value = null;
$pos = strpos( $data, $key );
if ( false !== $pos ) {
$pos = $pos + strlen( $key ) + 1;
$tmp = substr( $data, $pos );
$pos = strpos( $tmp, "\n" );
$value = trim( substr( $tmp, 0, $pos ) );
}
return $value;
}
// ------------------
// Get Plugin Version
// ------------------
// 1.1.2: added get plugin version function
public function plugin_version() {
$args = $this->args;
return $args['version'];
}
// -----------------
// Set Pro Namespace
// -----------------
public function pro_namespace( $pronamespace ) {
$this->args['pronamespace'] = $pronamespace;
}
// =======================
// --- Plugin Settings ---
// =======================
// --------------------
// Get Default Settings
// --------------------
public function default_settings( $dkey = false ) {
// --- return defaults if already set ---
$defaults = $this->defaults;
if ( !is_null( $defaults ) ) {
if ( $dkey && isset( $defaults[$dkey] ) ) {
return $defaults[$dkey];
}
return $defaults;
}
// --- filter and store the plugin default settings ---
// 1.1.2: fix to apply options filter
$namespace = $this->namespace;
$options = $this->options;
$options = apply_filters( $namespace . '_options', $options );
$defaults = array();
foreach ( $options as $key => $values ) {
// 1.0.9: set default to null if default value not set
if ( isset( $values['default'] ) ) {
$defaults[$key] = $values['default'];
} else {
$defaults[$key] = null;
}
}
$defaults = apply_filters( $namespace . '_default_settings', $defaults );
$this->defaults = $defaults;
if ( $dkey && isset( $defaults[$dkey] ) ) {
return $defaults[$dkey];
}
return $defaults;
}
// ------------
// Add Settings
// ------------
public function add_settings() {
// --- add the default plugin settings ---
$args = $this->args;
$defaults = $this->default_settings();
$added = add_option( $args['option'], $defaults );
// --- if newly added, make the defaults current settings ---
if ( $added ) {
$namespace = $this->namespace;
foreach ( $defaults as $key => $value ) {
$GLOBALS[$namespace][$key] = $value;
}
// --- record first installed version ---
// 1.1.0: added record for tracking first install version
add_option( $args['option'] . '_first_install', $args['version'] );
}
// 1.0.9: trigger add settings action
// 1.2.1: fix to namespace typo (nsmespace)
do_action( $args['namespace'] . '_add_settings', $args );
}
// -----------------------
// Maybe Transfer Settings
// -----------------------
public function maybe_transfer_settings() {
$namespace = $this->namespace;
$funcname = $namespace . '_transfer_settings';
// --- check for either function prefixed or class extended method ---
if ( method_exists( $this, 'transfer_settings' ) ) {
$this->transfer_settings();
} elseif ( function_exists( $funcname ) ) {
call_user_func( $funcname );
}
}
// -----------------------
// Get All Plugin Settings
// -----------------------
public function get_settings( $filter = true ) {
$namespace = $this->namespace;
$settings = $GLOBALS[$namespace];
if ( $filter ) {
// 1.0.8: only apply all settings filter if filter is true
$settings = apply_filters( $namespace . '_settings', $settings );
// 1.0.8: maybe apply all individual key value filters
foreach ( $settings as $key => $value ) {
$settings[$key] = apply_filters( $namespace . '_' . $key, $value );
}
}
return $settings;
}
// ------------------
// Get Plugin Setting
// ------------------
public function get_setting( $key, $filter = true ) {
$args = $this->args;
$namespace = $this->namespace;
$settings = $GLOBALS[$namespace];
$settings = apply_filters( $namespace . '_settings', $settings );
// --- maybe strip settings prefix ---
// 1.0.4: added for backwards compatibility
if ( substr( $key, 0, strlen( $args['settings'] ) ) == $args['settings'] ) {
$key = substr( $key, strlen( $args['settings'] ) + 1, strlen( $key ) );
}
// --- get plugin setting ---
// 1.1.4: fix for weird isset failing glitch
// if ( isset( $settings[$key] ) ) {
if ( array_key_exists( $key, $settings ) ) {
$value = $settings[$key];
} else {
$defaults = $this->default_settings();
// 1.1.4: fix for weird isset failing glitch
if ( array_key_exists( $key, $defaults ) ) {
$value = $defaults[$key];
} else {
$value = null;
}
}
if ( $filter ) {
$value = apply_filters( $namespace . '_' . $key, $value );
}
return $value;
}
// ---------------------
// Reset Plugin Settings
// ---------------------
public function reset_settings() {
$args = $this->args;
$namespace = $this->namespace;
// --- check reset triggers ---
// 1.0.2: fix to namespace key typo in isset check
// 1.0.3: only use namespace not settings key
// 1.0.9: check page is set and matches slug
if ( !isset( $_REQUEST['page'] ) ) {
return;
}
// 1.0.5: use sanitize_title on request variables
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( sanitize_title( $_REQUEST['page'] ) != $args['slug'] ) {
return;
}
if ( !isset( $_POST[$args['namespace'] . '_update_settings'] ) ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( 'reset' != sanitize_title( $_POST[$args['namespace'] . '_update_settings'] ) ) {
return;
}
// --- check reset permissions ---
$capability = apply_filters( $args['namespace'] . '_manage_options_capability', 'manage_options' );
if ( !current_user_can( $capability ) ) {
return;
}
// --- verify nonce ---
// $noncecheck = wp_verify_nonce( $_POST['_wpnonce', $args['slug'].'_update_settings' );
check_admin_referer( $args['slug'] . '_update_settings' );
// --- reset plugin settings ---
$defaults = $this->default_settings();
$defaults['savetime'] = time();
update_option( $args['option'], $defaults );
// --- loop to remerge with settings global ---
foreach ( $defaults as $key => $value ) {
$GLOBALS[$namespace][$key] = $value;
}
// --- set settings reset message flag ---
$_GET['updated'] = 'reset';
}
// ----------------------
// Update Plugin Settings
// ----------------------
public function update_settings() {
$args = $this->args;
$namespace = $this->namespace;
$settings = $GLOBALS[$namespace];
// --- check update triggers ---
// 1.0.2: fix to namespace key typo in isset check
// 1.0.3: only use namespace not settings key
// 1.0.9: check page is set and matches slug
if ( !isset( $_REQUEST['page'] ) || ( $_REQUEST['page'] != $args['slug'] ) ) {
return;
}
$updatekey = $args['namespace'] . '_update_settings';
if ( !isset( $_POST[$updatekey] ) || ( 'yes' != $_POST[$args['namespace'] . '_update_settings'] ) ) {
return;
}
// --- check update permissions ---
$capability = apply_filters( $namespace . '_manage_options_capability', 'manage_options' );
if ( !current_user_can( $capability ) ) {
return;
}
// --- verify nonce ---
// $noncecheck = wp_verify_nonce( $_POST['_wpnonce', $args['slug'].'_update_settings' );
check_admin_referer( $args['slug'] . '_update_settings' );
// --- get plugin options and default settings ---
// 1.0.9: allow filtering of plugin options (eg. for Pro/Add Ons)
$options = $this->options;
$options = apply_filters( $namespace . '_options', $options );
$defaults = $this->default_settings();
// --- maybe use custom method or function ---
$funcname = $namespace . '_process_settings';
if ( method_exists( $this, 'process_settings' ) ) {
// --- use class extended method if found ---
$settings = $this->process_settings();
} elseif ( function_exists( $funcname ) && is_callable( $funcname ) ) {
// --- use namespace prefixed function if found ---
$settings = call_user_func( $funcname );
} else {
// --- loop plugin options to get new settings ---
foreach ( $options as $key => $values ) {
// --- get option type and options ---
$type = $values['type'];
$valid = $validate_args = array();
if ( isset( $values['options'] ) ) {
$valid = $values['options'];
}
// --- get posted value ---
// 1.0.6: set null value for unchecked checkbox fix
// 1.2.5: moved get posted value to within each type with sanitization
$postkey = $args['settings'] . '_' . $key;
$newsettings = null;
// --- maybe validate special options ---
// 1.0.9: check for special options to prepare
if ( is_string( $valid ) ) {
// --- maybe get public post type slugs ---
if ( in_array( $valid, array( 'PUBLICTYPE', 'PUBLICTYPES' ) ) ) {
$valid = array();
if ( !isset( $public ) ) {
$cpts = array( 'page', 'post' );
$cptargs = array( 'public' => true, '_builtin' => false );
$cptlist = get_post_types( $cptargs, 'names', 'and' );
$public = array_merge( $cpts, $cptlist );
}
foreach ( $public as $cpt ) {
$valid[$cpt] = '';
}
}
// --- maybe get post type slugs ---
if ( in_array( $valid, array( 'POSTTYPE', 'POSTTYPES' ) ) ) {
$valid = array();
if ( !isset( $cpts ) ) {
$cpts = array( 'page', 'post' );
$cptargs = array( 'public' => true, '_builtin' => false );
$cptlist = get_post_types( $cptargs, 'names', 'and' );
$cpts = array_merge( $cpts, $cptlist );
}
foreach ( $cpts as $cpt ) {
$valid[$cpt] = '';
}
}
// --- maybe get all post type slugs ---
if ( in_array( $valid, array( 'ALLTYPE', 'ALLTYPES' ) ) ) {
$valid = array();
if ( !isset( $allcpts ) ) {
$cptargs = array( '_builtin' => false );
$allcpts = get_post_types( $cptargs, 'names', 'and' );
}
foreach ( $allcpts as $cpt ) {
$valid[$cpt] = '';
}
}
}
if ( $this->debug ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo 'Saving Setting Key ' . esc_html( $key ) . ' (' . esc_html( $postkey ) . ') ' . "\n";
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo 'Type: ' . esc_html( $type ) . ' - Valid Options ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . ' ' . "\n";
}
// --- sanitize value according to type ---
if ( strstr( $type, '/' ) ) {
// --- implicit radio / select ---
$posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null;
$valid = explode( '/', $type );
if ( in_array( $posted, $valid ) ) {
$settings[$key] = $posted;
}
} elseif ( ( 'checkbox' == $type ) || ( 'toggle' == $type ) ) {
// --- checkbox / toggle ---
// 1.0.6: fix to new unchecked checkbox value
// 1.0.9: maybe validate to specified checkbox value
$posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null;
if ( isset( $values['value'] ) ) {
$valid = array( $values['value'] );
} else {
$valid = array( 'yes', '1', 'checked', 'on' );
}
if ( in_array( $posted, $valid ) ) {
$settings[$key] = $posted;
} elseif ( is_null( $posted ) ) {
$settings[$key] = '';
}
} elseif ( 'textarea' == $type ) {
// --- text area ---
// 1.2.5: use sanitize_textarea_field with stripslashes
$posted = isset( $_POST[$postkey] ) ? sanitize_textarea_field( stripslashes( $_POST[$postkey] ) ) : null;
$settings[$key] = $posted;
} elseif ( 'text' == $type ) {
// --- text field (slug) ---
// 1.0.9: move text field sanitization to validation
$posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null;
if ( !is_string( $valid ) ) {
$valid = 'TEXT';
}
$newsettings = $posted;
} elseif ( ( 'number' == $type ) || ( 'numeric' == $type ) ) {
// --- number field value ---
// 1.0.9: added support for number step, minimum and maximum
$posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null;
$newsettings = $posted;
$valid = 'NUMERIC';
if ( isset( $values['step'] ) ) {
$validate_args['step'] = $values['step'];
}
if ( isset( $values['min'] ) ) {
$validate_args['min'] = $values['min'];
}
if ( isset( $values['max'] ) ) {
$validate_args['max'] = $values['max'];
}
} elseif ( 'multicheck' == $type ) {
// --- process multicheck boxes ---
// 1.0.9: added multicheck input type
// note: needs defined options (but works with post types)
$posted = array();
foreach ( $valid as $option => $label ) {
$optionkey = $args['settings'] . '_' . $key . '-' . $option;
if ( isset( $_POST[$optionkey] ) ) {
// 1.1.2: check for value if specified
// 1.2.5: apply sanitize_text_field to posted value
if ( ( isset( $values['value'] ) && ( sanitize_text_field( $_POST[$optionkey] ) == $values['value'] ) )
|| ( !isset( $values['value'] ) && ( 'yes' == sanitize_text_field( $_POST[$optionkey] ) ) ) ) {
// 1.1.0: fixed to save only array of key values
$posted[] = $option;
}
}
}
$settings[$key] = $posted;
} elseif ( 'csv' == $type ) {
// -- comma separated values ---
// 1.0.4: added comma separated values option
$posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null;
if ( strstr( $posted, ',' ) ) {
$posted = explode( ',', $posted );
} else {
// 1.2.8: fix to convert string to array
$posted = array( $posted );
}
foreach ( $posted as $i => $value ) {
$posted[$i] = trim( $value );
}
if ( is_string( $valid ) ) {
$newsettings = $posted;
} elseif ( is_array( $valid ) && ( count( $valid ) > 0 ) ) {
// 1.2.0: fix to check for empty valid array
foreach ( $posted as $i => $value ) {
if ( !in_array( $value, $valid ) ) {
unset( $posted[$i] );
}
}
$settings[$key] = implode( ',', $posted );
} else {
$settings[$key] = implode( ',', $posted );
}
} elseif ( ( 'radio' == $type ) || ( 'select' == $type ) ) {
// --- explicit radio or select value ---
$posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null;
if ( is_string( $valid ) ) {
$newsettings = $posted;
} elseif ( is_array( $valid ) && array_key_exists( $posted, $valid ) ) {
$settings[$key] = $posted;
}
} elseif ( 'multiselect' == $type ) {
// --- multiselect values ---
// 1.0.9: added multiselect value saving
$posted = isset( $_POST[$postkey] ) ? array_map( 'sanitize_text_field', $_POST[$postkey] ) : array();
$newsettings = array_values( $posted );
} elseif ( 'image' == $type ) {
// --- check attachment ID value ---
// 1.1.7: add image attachment ID saving
$posted = isset( $_POST[$postkey] ) ? absint( $_POST[$postkey] ) : null;
if ( $posted ) {
$attachment = wp_get_attachment_image_src( $posted, 'full' );
if ( is_array( $attachment ) ) {
$settings[$key] = $posted;
}
}
} elseif ( 'color' == $type ) {
// --- hex color setting ---
// 1.1.7: added color picker value saving
// 1.2.5: use sanitize_hex_color on color field
$posted = isset( $_POST[$postkey] ) ? sanitize_hex_color( $_POST[$postkey] ) : null;
$settings[$key] = $posted;
} elseif ( 'coloralpha' == $type ) {
// --- color alpha setting ---
// 1.2.5: separated color alpha setting condition
// 1.2.5: added rgba version of sanitization
// ref: https://wordpress.stackexchange.com/a/262578/76440
$posted = isset( $_POST[$postkey] ) ? sanitize_text_field( $_POST[$postkey] ) : null;
if ( !is_null( $posted ) ) {
$posted = str_replace( ' ', '', $posted );
$values = array();
// 1.2.7: fix color variable to posted
// 1.2.7: make alpha a value key not separate
// 1.2.7: check number of commas to see if alpha is set
$commas = substr_count( $posted, ',' );
if ( 3 == $commas ) {
sscanf( $posted, 'rgba(%d,%d,%d,%f)', $values['red'], $values['green'], $values['blue'], $values['alpha'] );
} elseif ( 2 == $commas ) {
// 1.2.8: remove a from rgba (failing for non-alpha selections)
sscanf( $posted, 'rgb(%d,%d,%d)', $values['red'], $values['green'], $values['blue'] );
}
// echo 'rgba sscanf values: ' . print_r( $values, true ) . "\n";
// 1.2.7: fix for use of duplicate key variable
foreach ( $values as $k => $v ) {
if ( 'alpha' != $k ) {
// --- sanitize rgb values ---
$v = absint( $v );
if ( $v < 0 ) {
$values[$k] = 0;
} elseif ( $v > 255 ) {
$values[$k] = 255;
}
} else {
// --- sanitize alpha value ---
if ( $v < 0 ) {
$values['alpha'] = 0;
} elseif ( $v > 1 ) {
$values['alpha'] = 1;
}
}
}
if ( 3 == $commas ) {
$posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ',' . $values['alpha'] . ')';
} elseif ( 2 == $commas ) {
// 1.2.8: remove a from rgba (for non-alpha selections)
$posted = 'rgb(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ')';
}
}
$settings[$key] = $posted;
}
if ( $this->debug ) {
echo 'New Settings for Key ' . esc_html( $key ) . ': ';
// 1.2.0: added isset check for newsetting
if ( !is_null( $newsettings ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo '(To-validate) ' . esc_html( print_r( $newsettings, true ) ) . ' ' . "\n";
} else {
// 1.1.7 handle if (new) key not set yet
if ( isset( $settings[$key] ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo '(Validated) ' . esc_html( print_r( $settings[$key], true ) ) . ' ' . PHP_EOL;
} else {
echo 'No setting yet for key ' . esc_html( $key ) . ' ' . "\n";
}
}
}
// --- maybe validate new settings ---
// 1.1.9: fix to allow saving of zero value
// 1.2.1: fix to allow saving of empty value
if ( !is_null( $newsettings ) ) {
if ( is_array( $newsettings ) ) {
// --- validate array of settings ---
// 1.1.9: fix to allow saving of zero value
// 1.2.1: fix to allow saving of empty value
foreach ( $newsettings as $newkey => $newvalue ) {
$newsetting = $this->validate_setting( $newvalue, $valid, $validate_args );
if ( $this->debug ) {
echo 'Validated Setting array value ' . esc_html( $newvalue ) . ' to ' . esc_html( $newsetting );
}
if ( $newsetting || ( '' == $newsetting ) ) {
$newsettings[$newkey] = $newsetting;
} elseif ( ( 0 == $newsetting ) || ( '0' == $newsetting ) ) {
$newsettings[$newkey] = $newsetting;
} else {
unset( $newsettings[$newkey] );
}
}
if ( 'csv' == $type ) {
$settings[$key] = implode( ',', $newsettings );
} else {
$settings[$key] = $newsettings;
}
} elseif ( $newsettings || ( '' == $newsettings ) || ( 0 === $newsettings ) || ( '0' === $newsettings ) ) {
// --- validate single setting ---
if ( 'csv' == $type ) {
// 1.1.5: fix to validate each of multiple CSV values
$values = explode( ',', $newsettings );
$newvalues = array();
foreach ( $values as $value ) {
$newvalue = $this->validate_setting( $value, $valid, $validate_args );
$newvalues[] = $newvalue;
if ( $this->debug ) {
echo 'Validated Setting value ' . esc_html( $value ) . ' to ' . esc_html( $newvalue ) . ' ' . "\n";
}
}
$newsettings = implode( ',', $newvalues );
$settings[$key] = $newsettings;
} else {
$newsetting = $this->validate_setting( $newsettings, $valid, $validate_args );
// 1.1.9: fix to allow saving of zero value
// 1.2.1: fix to allow saving of empty value
if ( $this->debug ) {
echo 'Validated Setting single value ' . esc_html( $newsettings ) . ' to ' . esc_html( $newsetting ) . ' ' . "\n";
}
if ( $newsetting || ( '' == $newsetting ) || ( 0 == $newsetting ) || ( '0' == $newsetting ) ) {
$settings[$key] = $newsetting;
}
}
}
if ( $this->debug ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo 'Valid Options for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . ' ' . "\n";
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo 'Validated Settings for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $settings[$key], true ) ) . ' ' . "\n";
}
}
}
}
// --- process special settings ---
// 1.0.2: added for processing special settings separately
$funcname = $namespace . '_process_special';
if ( method_exists( $this, 'process_special' ) ) {
// --- use class extended method if found ---
$settings = $this->process_special( $settings );
} elseif ( function_exists( $funcname ) && is_callable( $funcname ) ) {
// --- use namespace prefixed function if found ---
$settings = call_user_func( $funcname, $settings );
}
// --- output new settings ---
if ( $this->debug ) {
echo 'All New Settings: ';
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo esc_html( print_r( $settings, true ) );
echo ' ';
}
if ( $settings && is_array( $settings ) ) {
// --- loop default keys to remove others ---
$settings_keys = array_keys( $defaults );
foreach ( $settings as $key => $value ) {
if ( !in_array( $key, $settings_keys ) ) {
unset( $settings[$key] );
}
}
// --- save settings tab ---
// 1.2.0: validate and save current settings tab
if ( isset( $_POST['settingstab'] ) ) {
$tabs = array();
foreach ( $options as $key => $option ) {
if ( isset( $option['tab'] ) && !in_array( $option['tab'], $tabs ) ) {
$tabs[] = $option['tab'];
}
}
if ( count( $tabs ) > 0 ) {
// 1.2.5: sanitize current tab value before validating
$currenttab = sanitize_title( $_POST['settingstab'] );
if ( in_array( $currenttab, $tabs ) ) {
$settings['settingstab'] = $currenttab;
} elseif ( in_array( 'general', $tabs ) ) {
$settings['settingstab'] = 'general';
} else {
$settings['settingstab'] = $tabs[0];
}
}
}
// --- update the plugin settings ---
$settings['savetime'] = time();
update_option( $args['option'], $settings );
// --- merge with existing settings for pageload ---
foreach ( $settings as $key => $value ) {
$GLOBALS[$namespace][$key] = $value;
}
// --- set settings update message flag ---
$_GET['updated'] = 'yes';
} else {
$_GET['updated'] = 'no';
}
// --- maybe trigger update of Pro settings ---
if ( method_exists( $this, 'pro_update_settings' ) ) {
$this->pro_update_settings();
} else {
if ( isset( $args['pronamespace'] ) ) {
$funcname = $args['pronamespace'] . '_update_settings';
} else {
$funcname = $args['namespace'] . '_pro_update_settings';
}
if ( function_exists( $funcname ) ) {
call_user_func( $funcname );
}
}
}
// -----------------------
// Validate Plugin Setting
// -----------------------
// 1.1.5: plural options now use extra validation
public function validate_setting( $posted, $valid, $args ) {
// --- allow for clearing of a field ---
// note: array values are cleared if empty, single values are set empty
if ( trim( $posted ) == '' ) {
return '';
}
// --- validate different data types ---
if ( 'TEXT' == $valid ) {
// --- sanitize text field ---
$posted = sanitize_text_field( $posted );
return $posted;
} elseif ( 'ALPHABETIC' == $valid ) {
// --- alphabetic ---
// 1.0.9: added alphabetic-only for completeness
$posted = trim( $posted );
$checkposted = preg_match( '/^[a-zA-Z]+$/', $posted );
if ( $checkposted ) {
return $posted;
}
} elseif ( 'NUMERIC' == $valid ) {
// --- number (numeric text) ---
// note: step key is only used for controls, not for validation
// 1.0.9: cast to integer - not absolute integer
// TODO: validate step value match ?
$posted = floatval( trim( $posted ) );
if ( isset( $args['min'] ) && ( $posted < $args['min'] ) ) {
return $args['min'];
} elseif ( isset( $args['max'] ) && ( $posted > $args['max'] ) ) {
return $args['max'];
} elseif ( 0 === $posted ) {
return 0;
}
return $posted;
} elseif ( 'ALPHANUMERIC' == $valid ) {
// --- alphanumeric text only ---
$posted = trim( $posted );
// TODO: maybe remove underscore from validation regex ?
$checkposted = preg_match( '/^[a-zA-Z0-9_]+$/', $posted );
if ( $checkposted ) {
return $posted;
}
} elseif ( 'PHONE' == $valid ) {
// --- phone number characters only ---
$posted = trim( $posted );
// $checkposted = preg_match( '/^[0-9+\(\)#\.\s\-]+$/', $posted );
// if ( $checkposted ) {
// return $posted;
// }
if ( strlen( $posted ) > 0 ) {
$posted = str_split( $posted, 1 );
$posted = preg_filter( '/^[0-9+\(\)#\.\s\-]+$/', '$0', $posted );
if ( count( $posted ) > 0 ) {
$posted = implode( '', $posted );
return $posted;
} else {
return '';
}
}
} elseif ( in_array( $valid, array( 'URL', 'URLS' ) ) ) {
// --- URL address ---
// 1.0.6: fix to type variable typo (vtype)
// 1.0.4: added validated URL option
// 1.0.6: fix to posted variable type (vposted)
// 1.0.9: remove check for http prefix to allow other protocols
// 1.1.7: use FILTER_SANITIZE_URL not FILTER_SANITIZE_STRING
// 1.2.1: allow for clearing URL by saving empty value
$posted = trim( $posted );
if ( '' != $posted ) {
$posted = filter_var( $posted, FILTER_SANITIZE_URL );
}
// 1.1.7: remove FILTER_VALIDATE_URL check - not working!?
if ( $this->debug ) {
$check = filter_var( $posted, FILTER_VALIDATE_URL );
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo 'Validated URL: ' . esc_html( print_r( $check, true ) ) . ' ';
}
// if ( !filter_var( $url, FILTER_VALIDATE_URL ) ) {
// $posted = '';
// }
return $posted;
} elseif ( in_array( $valid, array( 'EMAIL', 'EMAILS' ) ) ) {
// --- email address ---
// 1.0.3: added email option type checking
$email = sanitize_email( trim( $posted ) );
if ( $email ) {
$posted = $email;
} else {
$posted = '';
}
return $posted;
} elseif ( in_array( $valid, array( 'USEREMAIL', 'USEREMAILS' ) ) ) {
// --- email address with user validation ---
// 1.1.5: added option for validated user emails
$email = sanitize_email( trim( $posted ) );
if ( !$email ) {
return '';
}
$user = get_user_by( 'email', $email );
if ( $user ) {
// 1.2.3: fix to set email value
$posted = $email;
} else {
$posted = '';
}
return $posted;
} elseif ( 'USERNAME' == $valid ) {
// --- username ---
$username = sanitize_user( trim( $posted ) );
if ( !$username ) {
return '';
}
return $username;
} elseif ( 'USERNAMES' == $valid ) {
// --- username with check for existing user ---
// 1.1.5: separated check from singular
$username = sanitize_user( trim( $posted ) );
if ( !$username ) {
return '';
}
$user = get_user_by( 'login', $username );
if ( $user ) {
$posted = $username;
} else {
$posted = '';
}
return $posted;
} elseif ( 'USERID' == $valid ) {
// --- user ID ---
$userid = intval( trim( $posted ) );
if ( 0 === $userid ) {
return '';
}
} elseif ( 'USERIDS' == $valid ) {
// --- user ID with check for existing user ---
// 1.1.5: separated check from singular
$userid = intval( trim( $posted ) );
if ( 0 === $userid ) {
return '';
}
$user = get_user_by( 'ID', $userid );
if ( $user ) {
$posted = $userid;
} else {
$posted = '';
}
return $posted;
} elseif ( in_array( $valid, array( 'SLUG', 'SLUGS' ) ) ) {
// -- post slugs ---
$posted = sanitize_title( trim( $posted ) );
return $posted;
} elseif ( in_array( $valid, array( 'PAGEID', 'POSTID' ) ) ) {
$posted = intval( trim( $posted ) );
if ( 0 === $posted ) {
return '';
}
return $posted;
} elseif ( in_array( $valid, array( 'PAGEIDS', 'POSTIDS' ) ) ) {
$posted = intval( trim( $posted ) );
if ( 0 === $posted ) {
return '';
}
$post = get_post( $posted );
if ( $post ) {
return $posted;
}
}
// TODO: return validation error ?
return false;
}
// ---------------
// Delete Settings
// ---------------
public function delete_settings() {
// TODO: check for plugin settings flag to delete settings ?
// $delete_settings = $this->get_setting( 'delete_settings' );
// if ( $delete_settings ) {
// $args = $this->args;
// delete_option( $args['option'] );
// }
// TODO: check for plugin settings flag to delete data?
// $delete_data = $this->get_setting( 'delete_data' );
// if ( $delete_data ) {
// do_action( $this->namespace . '_delete_data' );
// }
}
// ======================
// --- Plugin Loading ---
// ======================
// --------------------
// Load Plugin Settings
// --------------------
public function load_settings() {
$args = $this->args;
$namespace = $this->namespace;
$GLOBALS[$namespace] = $args;
$settings = get_option( $args['option'], false );
if ( $settings && is_array( $settings ) ) {
foreach ( $settings as $key => $value ) {
$GLOBALS[$namespace][$key] = $value;
}
} else {
$defaults = $this->default_settings();
foreach ( $defaults as $key => $value ) {
$GLOBALS[$namespace][$key] = $value;
}
}
}
// -----------
// Add Actions
// -----------
public function add_actions() {
$args = $this->args;
$namespace = $this->namespace;
// --- add settings on activation ---
register_activation_hook( $args['file'], array( $this, 'add_settings' ) );
// --- always check for update and reset of settings ---
add_action( 'admin_init', array( $this, 'update_settings' ) );
add_action( 'admin_init', array( $this, 'reset_settings' ) );
// --- add plugin submenu ---
add_action( 'admin_menu', array( $this, 'settings_menu' ), 1 );
// --- add plugin settings page link ---
add_filter( 'plugin_action_links', array( $this, 'plugin_links' ), 10, 2 );
// --- maybe delete settings on deactivation ---
register_deactivation_hook( $args['file'], array( $this, 'delete_settings' ) );
// --- maybe load thickbox ---
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_load_thickbox' ) );
// --- AJAX readme viewer ---
add_action( 'wp_ajax_' . $namespace . '_readme_viewer', array( $this, 'readme_viewer' ) );
// --- load Freemius (requires PHP 5.4+) ---
// 1.2.1: move Freemius loading to plugins_loaded hook
if ( version_compare( PHP_VERSION, '5.4.0' ) >= 0 ) {
add_action( 'plugins_loaded', array( $this, 'load_freemius' ), 5 );
}
}
// ---------------------
// Load Helper Libraries
// ---------------------
public function load_helpers() {
$args = $this->args;
$file = $args['file'];
$dir = $args['dir'];
// --- Plugin Slug ---
if ( !isset( $args['slug'] ) ) {
$args['slug'] = substr( $file, 0, - 4 );
$this->args = $args;
}
// --- Pro Functions ---
// 1.0.2: added prototype auto-loading of Pro file(s)
// 1.2.1: fix overriding of plan arg to free
// (to work with @fs_premium_only file list)
if ( isset( $args['profiles'] ) && count( $args['profiles'] ) > 0 ) {
$included = get_included_files();
foreach ( $args['profiles'] as $profile ) {
// --- chech for php extension ---
if ( substr( '.php' == $profile, - 4, 4 ) ) {
$filepath = $dir . '/' . $profile;
if ( file_exists( $filepath ) ) {
$plan = 'premium';
// 1.0.9: add check if file already included
if ( !in_array( $filepath, $included ) ) {
include $filepath;
}
}
}
}
}
// 1.2.0: only change plan setting if premium files found
if ( isset( $plan ) ) {
$args['plan'] = $plan;
$this->args = $args;
}
// --- Plugin Update Checker ---
// note: lack of updatechecker.php file indicates WordPress.Org SVN repo version
// presence of updatechecker.php indicates direct site download or GitHub version
$wporg = true;
$updatechecker = $dir . '/updatechecker.php';
if ( file_exists( $updatechecker ) ) {
$wporg = false;
$slug = $args['slug'];
// note: requires $file and $slug to be defined
include $updatechecker;
}
$args['wporg'] = $wporg;
$this->args = $args;
// --- Trigger Loader Helpers Action ---
// 1.0.9: added action for extra loader helpers (eg. WordQuest)
do_action( $args['namespace'] . '_loader_helpers', $args );
// --- load Freemius (requires PHP 5.4+) ---
// 1.2.1: moved to plugins_loaded action hook
}
// -------------------
// Maybe Load Thickbox
// -------------------
public function maybe_load_thickbox() {
$args = $this->args;
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['page'] ) && ( sanitize_title( $_REQUEST['page'] ) == $args['slug'] ) ) {
add_thickbox();
}
}
// ------------------
// Readme Viewer AJAX
// ------------------
public function readme_viewer() {
$args = $this->args;
$dir = $args['dir'];
echo '
';
// 1.0.7: changed readme.php to reader.php (for Github)
$readme = $dir . '/readme.txt';
$contents = file_get_contents( $readme );
$parser = $dir . '/reader.php';
if ( file_exists( $parser ) ) {
// --- include Markdown Readme Parser ---
include $parser;
// --- remove license info as causes breakage! ---
// TODO: find line start and end to handle other possible licenses
$contents = str_replace( 'License: GPLv2 or later', '', $contents );
$contents = str_replace( 'License URI: http://www.gnu.org/licenses/gpl-2.0.html', '', $contents );
// --- instantiate Parser class ---
$readme = new WordPress_Readme_Parser();
$parsed = $readme->parse_readme_contents( $contents );
// --- output plugin info ---
echo '' . esc_html( __( 'Plugin Name' ) ) . ' : ' . esc_html( $parsed['name'] ) . ' ' . PHP_EOL;
// echo '' . esc_html( __( 'Tags' ) ) . ' : ' . esc_html( implode( ', ', $parsed['tags'] ) ) . ' ' . PHP_EOL;
echo '' . esc_html( __( 'Requires at least' ) ) . ' : ' . esc_html( __( 'WordPress' ) ) . ' v' . esc_html( $parsed['requires_at_least'] ) . ' ' . PHP_EOL;
echo '' . esc_html( __( 'Tested up to' ) ) . ' : ' . esc_html( __( 'WordPress' ) ) . ' v' . esc_html( $parsed['tested_up_to'] ) . ' ' . PHP_EOL;
if ( isset( $parsed['stable_tag'] ) ) {
echo '' . esc_html( __( 'Stable Tag' ) ) . ' : ' . esc_html( $parsed['stable_tag'] ) . ' ' . PHP_EOL;
}
echo '' . esc_html( __( 'Contributors' ) ) . ' : ' . esc_html( implode( ', ', $parsed['contributors'] ) ) . ' ' . PHP_EOL;
// echo 'Donate Link : ' . esc_html( $parsed['donate_link'] ) . ' ';
// 1.2.5: use wp_kses_post on plugin short description markup
echo ' ' . wp_kses_post( $parsed['short_description'] ) . ' ' . PHP_EOL;
// --- output sections ---
// possible sections: 'description', 'installation', 'frequently_asked_questions',
// 'screenshots', 'changelog', 'change_log', 'upgrade_notice'
$strip = array( 'installation', 'screenshots' );
foreach ( $parsed['sections'] as $key => $section ) {
if ( !empty( $section ) && !in_array( $key, $strip ) ) {
if ( strstr( $key, '_' ) ) {
$parts = explode( '_', $key );
} else {
$parts = array( $key );
}
foreach ( $parts as $i => $part ) {
$parts[$i] = strtoupper( substr( $part, 0, 1 ) ) . substr( $part, 1 );
}
$title = implode( ' ', $parts );
echo '' . esc_html( $title ) . ' ' . PHP_EOL;
// 1.2.5: use wp_kses_post on readme section output
echo wp_kses_post( $section );
}
}
if ( isset( $parsed['remaining_content'] ) && !empty( $remaining_content ) ) {
echo '' . esc_html( __( 'Extra Notes' ) ) . ' ' . PHP_EOL;
// 1.2.5: use wp_kses_post on readme extra notes output
echo wp_kses_post( $parsed['remaining_content'] );
}
} else {
// --- fallback text-only display ---
$contents = str_replace( "\n", ' ', $contents );
echo wp_kses_post( $contents );
}
echo '';
exit;
}
// =======================
// --- Freemius Loader ---
// =======================
//
// required settings keys:
// -----------------------
// freemius_id - plugin ID from Freemius plugin dashboard
// freemius_key - public key from Freemius plugin dashboard
//
// optional settings keys:
// -----------------------
// plan - (string) curent plugin plan (value of 'free' or 'premium')
// hasplans - (boolean) switch for whether plugin has premium plans
// hasaddons - (boolean) switch for whether plugin has premium addons
// wporg - (boolean) switch for whether free plugin is WordPress.org compliant
// contact - (boolean) submenu switch for plugin Contact (defaults to on for premium only)
// support - (boolean) submenu switch for plugin Support (default on)
// account - (boolean) submenu switch for plugin Account (default on)
// parentmenu - (string) optional slug for plugin parent menu
//
// okay lets do this...
// ====================
public function load_freemius() {
// 1.2.1: no need to load if not in admin area
// if ( !is_admin() ) {
// return;
// }
// echo 'Freemius Loading... ';
$args = $this->args;
$namespace = $this->namespace;
// --- check for required Freemius keys ---
if ( !isset( $args['freemius_id'] ) || !isset( $args['freemius_key'] ) ) {
return;
}
// --- check for free / premium plan ---
// convert plan string value of 'free' or 'premium' to boolean premium switch
// TODO: check for active addons also ?
$premium = false;
if ( isset( $args['plan'] ) && ( 'premium' == $args['plan'] ) ) {
$premium = true;
} else {
// 1.2.1: added filter for premium init
$premium = apply_filters( 'freemius_init_premium_' . $args['namespace'], $premium );
}
// --- maybe redirect link to plugin support forum ---
// TODO: change to use new Freemius 2.3.0 support link filter ?
// 1.0.5: use sanitize_text_field on request variable
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['page'] ) && ( sanitize_title( $_REQUEST['page'] ) == $args['slug'] . '-wp-support-forum' ) && is_admin() ) {
if ( !function_exists( 'wp_redirect' ) ) {
include ABSPATH . WPINC . '/pluggable.php';
}
if ( isset( $args['support'] ) ) {
// changes the support forum slug for premium based on the pro plugin file slug
// 1.0.7: fix support URL undefined variable warning
$support_url = $args['support'];
// 1.2.1: removed in favour of filtering via Pro
// if ( $premium && isset( $args['proslug'] ) ) {
// $support_url = str_replace( $args['slug'], $args['proslug'], $support_url );
// }
$support_url = apply_filters( 'freemius_plugin_support_url_redirect', $support_url, $args['slug'] );
// phpcs:ignore WordPress.Security.SafeRedirect
wp_redirect( $support_url );
exit;
}
}
// --- do the Freemius Loading boogie ---
if ( !isset( $args['freemius'] ) ) {
// --- start the Freemius SDK ---
if ( !class_exists( 'Freemius' ) ) {
$freemiuspath = dirname( __FILE__ ) . '/freemius/start.php';
if ( !file_exists( $freemiuspath ) ) {
return;
}
require_once $freemiuspath;
}
// --- set defaults for optional key values ---
if ( !isset( $args['type'] ) ) {
$args['type'] = 'plugin';
}
if ( !isset( $args['wporg'] ) ) {
$args['wporg'] = false;
}
if ( !isset( $args['hasplans'] ) ) {
$args['hasplans'] = false;
}
if ( !isset( $args['hasaddons'] ) ) {
$args['hasaddons'] = false;
}
// 1.2.1: add filter for addons init
$args['hasaddons'] = apply_filters( 'freemius_init_addons_' . $args['namespace'], $args['hasaddons'] );
// --- set defaults for options submenu key values ---
// 1.0.2: fix to isset check keys
// 1.0.5: fix to set args subkeys for support and account
if ( !isset( $args['support'] ) ) {
$args['support'] = true;
}
if ( !isset( $args['account'] ) ) {
$args['account'] = true;
}
// by default, enable contact submenu item for premium plugins only
if ( !isset( $args['contact'] ) ) {
$args['contact'] = $premium;
}
// --- set Freemius settings from plugin settings ---
// 1.1.1: remove admin_url wrapper on Freemius first-path value
// TODO: further possible args for Freemius init (eg. bundle_id)
// ref: https://freemius.com/help/documentation/wordpress-sdk/integrating-freemius-sdk/
$first_path = add_query_arg( 'page', $args['slug'], 'admin.php' );
$first_path = add_query_arg( 'welcome', 'true', $first_path );
$settings = array(
'type' => $args['type'],
'slug' => $args['slug'],
'id' => $args['freemius_id'],
'public_key' => $args['freemius_key'],
'has_addons' => $args['hasaddons'],
'has_paid_plans' => $args['hasplans'],
'is_org_compliant' => $args['wporg'],
'is_premium' => $premium,
'menu' => array(
'slug' => $args['slug'],
'first-path' => $first_path,
'contact' => $args['contact'],
'support' => $args['support'],
'account' => $args['account'],
),
);
// --- maybe add plugin submenu to parent menu ---
if ( isset( $args['parentmenu'] ) ) {
$settings['menu']['parent'] = array( 'slug' => $args['parentmenu'] );
}
// --- filter settings before initializing ---
$settings = apply_filters( 'freemius_init_settings_' . $args['namespace'], $settings );
if ( $this->debug ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo 'Freemius Settings: ' . esc_html( print_r( $settings, true ) ) . ' ' . PHP_EOL;
}
if ( !$settings || !is_array( $settings ) ) {
return;
}
// --- initialize Freemius now ---
$freemius = $GLOBALS[$namespace . '_freemius'] = fs_dynamic_init( $settings );
if ( $this->debug ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo 'Freemius Object: ' . esc_html( print_r( $freemius, true ) ) . ' ' . PHP_EOL;
}
// --- set plugin basename ---
// 1.0.1: set free / premium plugin basename
if ( method_exists( $freemius, 'set_basename' ) ) {
$freemius->set_basename( $premium, $args['file'] );
}
// --- add Freemius connect message filter ---
$this->freemius_connect();
// --- fire Freemius loaded action ---
do_action( $args['namespace'] . '_loaded' );
}
}
// -----------------------
// Filter Freemius Connect
// -----------------------
public function freemius_connect() {
$namespace = $this->args['namespace'];
$freemius = $GLOBALS[$namespace . '_freemius'];
if ( isset( $settings['freemius'] ) && is_object( $freemius ) && method_exists( $freemius, 'add_filter' ) ) {
$freemius->add_filter( 'connect_message', array( $this, 'freemius_connect_message' ), WP_FS__DEFAULT_PRIORITY, 6 );
}
}
// ------------------------
// Freemius Connect Message
// ------------------------
public function freemius_connect_message( $message, $user_first_name, $plugin_title, $user_login, $site_link, $freemius_link ) {
// default: 'Never miss an important update - opt-in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.'
// 1.0.9: fix to remove incorrect first argument in string replacement
$message = __fs( 'hey-x' ) . ' ';
// 1.2.4: added ordering to replacement arguments
$message .= sprintf(
// Translators: plugin title, user name, site link, freemius link
__( 'If you want to more easily access support and feedback for this plugins features and functionality, %1$s can connect your user, %2$s at %3$s, to %4$s' ),
'' . $plugin_title . ' ',
'' . $user_login . ' ',
$site_link,
$freemius_link
);
return $message;
}
// ----------------------
// Connect Update Message
// ----------------------
// TODO: message for connect updates
public function freemius_update_message( $message, $user_first_name, $plugin_title, $user_login, $site_link, $freemius_link ) {
// default: 'Please help us improve %1$s! If you opt-in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.'
// $message = freemius_message( $message, $user_first_name, $plugin_title, $user_login, $site_link, $freemius_link );
// TODO: check if message needs return here ?
return $message;
}
// ====================
// --- Plugin Admin ---
// ====================
// -----------------
// Add Settings Menu
// -----------------
public function settings_menu() {
$args = $this->args;
$namespace = $this->namespace;
$settings = $GLOBALS[$namespace];
// --- filter capability ---
$args['capability'] = apply_filters( $args['namespace'] . '_manage_options_capability', 'manage_options' );
if ( !isset( $args['pagetitle'] ) ) {
$args['pagetitle'] = $args['title'];
}
if ( !isset( $args['menutitle'] ) ) {
$args['menutitle'] = $args['title'];
}
// 1.1.9: added filters for page and menu titles
$pagetitle = apply_filters( $namespace . '_settings_page_title', $args['pagetitle'] );
$menutitle = apply_filters( $namespace . '_settings_menu_title', $args['menutitle'] );
// --- trigger filter plugin menu action ---
// (can hook into this to add an admin menu manually using the provided loader args)
// return true from filter function to not add a submenu item in admin Settings menu
// 1.0.8: change from function exists check
// 1.0.9: change to filter usage to check if menu is manually added
$menu_added = apply_filters( $args['namespace'] . '_admin_menu_added', false, $args );
// 1.1.1: record to admin menu added switch
$this->menu_added = $menu_added;
// --- maybe auto-add standalone options page ---
if ( !$menu_added ) {
// 1.0.8: check settingsmenu switch that disables automatic settings menu adding
if ( !isset( $args['settingsmenu'] ) || ( false !== $args['settingsmenu'] ) ) {
add_options_page( $args['pagetitle'], $args['menutitle'], $args['capability'], $args['slug'], $args['namespace'] . '_settings_page' );
}
}
// 1.1.0: add admin notices boxer
add_action( 'all_admin_notices', array( $this, 'notice_boxer' ), 999 );
}
// -----------------
// Plugin Page Links
// -----------------
// 1.2.2: merge in plugin links instead of using array_unshift
public function plugin_links( $links, $file ) {
$args = $this->args;
if ( plugin_basename( $args['file'] ) == $file ) {
// --- add settings link ---
// 1.1.1: fix to settings page link URL
// (depending on whether top level menu or Settings submenu item)
$page = $this->menu_added ? 'admin.php' : 'options-general.php';
$settings_url = add_query_arg( 'page', $args['slug'], admin_url( $page ) );
$settings_link = '' . esc_html( __( 'Settings' ) ) . ' ';
$link = array( 'settings' => $settings_link );
$links = array_merge( $link, $links );
// --- maybe add Pro upgrade link ---
if ( isset( $args['hasplans'] ) && $args['hasplans'] ) {
// 1.2.1: add check if premium is already installed
if ( !isset( $args['plan'] ) || ( 'premium' != $args['plan'] ) ) {
// -- internal upgrade link ---
if ( isset( $args['upgrade_link'] ) ) {
$upgrade_url = $args['upgrade_link'];
$upgrade_target = '';
} else {
$upgrade_url = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) );
$upgrade_target = !strstr( $upgrade_url, '/wp-admin/' ) ? ' target="_blank"' : '';
}
$upgrade_link = '" . esc_html( __( 'Upgrade' ) ) . ' ';
$link = array( 'upgrade' => $upgrade_link );
$links = array_merge( $link, $links );
// --- external pro link ---
// 1.2.0: added separate pro details link
if ( isset( $args['pro_link'] ) ) {
$pro_target = !strstr( $args['pro_link'], '/wp-admin/' ) ? ' target="_blank"' : '';
$pro_link = '' . esc_html( __( 'Pro Details' ) ) . ' ';
$link = array( 'pro-details' => $pro_link );
$links = array_merge( $link, $links );
}
}
}
// --- maybe add Addons link ---
// 1.2.0: activated add-ons link
// 1.2.2: remove duplication of addons link
if ( !isset( $args['hasaddons'] ) || !$args['hasaddons'] ) {
if ( isset( $args['addons_link'] ) ) {
$addons_url = $args['addons_link'];
$addons_target = !strstr( $addons_url, '/wp-admin/' ) ? ' target="_blank"' : '';
$addons_link = '' . esc_html( __( 'Add Ons' ) ) . ' ';
$link = array( 'addons' => $addons_link );
$links = array_merge( $link, $links );
}
}
if ( $this->debug ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo 'Plugin Links for ' . esc_html( $file ) . ': ' . esc_html( print_r( $links, true ) ) . ' ' . PHP_EOL;
}
}
return $links;
}
// -----------
// Message Box
// -----------
// 1.2.5: use output buffering for no-echo
public function message_box( $message, $echo ) {
if ( !$echo ) {
ob_start();
}
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
// 1.2.5: added wp_kses_post to message output
echo wp_kses_post( $message ) . PHP_EOL;
echo '
' . PHP_EOL;
echo ' ' . PHP_EOL;
echo '
' . PHP_EOL;
if ( !$echo ) {
$box = ob_get_contents();
ob_end_clean();
return $box;
}
}
// ------------
// Notice Boxer
// ------------
// 1.1.0: added admin notices boxer to settings pages
public function notice_boxer() {
$args = $this->args;
// --- bug out if not on radio station pages ---
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( !isset( $_REQUEST['page'] ) ) {
return;
}
// 1.0.5: use sanitize_title on request variable
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( substr( sanitize_title( $_REQUEST['page'] ), 0, strlen( $args['slug'] ) ) != $args['slug'] ) {
return;
}
// 1.2.2: bug out if adminsanity notices are loaded
if ( isset( $GLOBALS['teleporter_data']['load']['notices'] ) && $GLOBALS['teleporter_data']['load']['notices'] ) {
return;
}
// --- output notice box ---
echo '' . PHP_EOL;
echo '
' . PHP_EOL;
echo '▸ ' . PHP_EOL;
echo '' . esc_html( __( 'Notices' ) ) . ' ' . PHP_EOL;
echo ' ' . PHP_EOL;
echo '
' . PHP_EOL;
echo '
' . PHP_EOL;
// --- toggle notice box script ---
echo "";
}
// ------------------
// Plugin Page Header
// ------------------
public function settings_header() {
$args = $this->args;
$namespace = $this->namespace;
$settings = $GLOBALS[$namespace];
// --- output debug values ---
if ( $this->debug ) {
echo 'Current Settings: ';
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo esc_html( print_r( $settings, true ) );
echo ' ' . PHP_EOL;
echo 'Plugin Options: ';
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo esc_html( print_r( $this->options, true ) );
echo ' ' . PHP_EOL;
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( isset( $_POST ) ) {
echo 'Posted Values: ';
// phpcs:ignore WordPress.Security.NonceVerification.Missing
foreach ( $_POST as $key => $value ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo esc_attr( $key ) . ': ' . esc_html( print_r( $value, true ) ) . ' ' . PHP_EOL;
}
}
}
// --- check for animated gif icon with fallback to normal icon ---
// 1.0.9: fix to check if PNG file exists
$icon_url = false;
if ( file_exists( $this->args['dir'] . '/images/' . $args['slug'] . '.gif' ) ) {
$icon_url = plugins_url( 'images/' . $args['slug'] . '.gif', $args['file'] );
} elseif ( file_exists( $this->args['dir'] . '/images/' . $args['slug'] . '.png' ) ) {
$icon_url = plugins_url( 'images/' . $args['slug'] . '.png', $args['file'] );
}
$icon_url = apply_filters( $namespace . '_plugin_icon_url', $icon_url );
// --- check for author icon based on provided author name ---
// 1.0.2: check if author icon file exists and fallback
$author_icon_url = false;
$author_slug = strtolower( str_replace( ' ', '', $args['author'] ) );
if ( file_exists( $this->args['dir'] . '/images/' . $author_slug . '.png' ) ) {
$author_icon_url = plugins_url( 'images/' . $author_slug . '.png', $args['file'] );
} elseif ( file_exists( $this->args['dir'] . '/images/wordquest.png' ) ) {
$author_icon_url = plugins_url( 'images/wordquest.png', $args['file'] );
}
$author_icon_url = apply_filters( $namespace . '_author_icon_url', $author_icon_url );
// --- open header table ---
echo ' ' . PHP_EOL;
}
// -------------
// Settings Page
// -------------
public function settings_page() {
$namespace = $this->namespace;
// --- open page wrapper ---
echo '' . PHP_EOL;
do_action( $namespace . '_admin_page_top' );
// --- output settings header ---
$this->settings_header();
do_action( $namespace . '_admin_page_middle' );
// --- output settings table ---
$this->settings_table();
do_action( $namespace . '_admin_page_bottom' );
// --- close page wrapper ---
echo '
' . PHP_EOL;
}
// --------------
// Settings Table
// --------------
// 1.0.9: created automatic Plugin Settings page
// (based on passed plugin options and default settings)
public function settings_table() {
// --- get all options and settings (unfiltered) ---
$args = $this->args;
$namespace = $this->namespace;
$options = $this->options;
// --- get plugin options and default settings ---
// 1.1.2: fix for filtering of plugin options
$options = $this->options;
$options = apply_filters( $namespace . '_options', $options );
// --- maybe enqueue media scripts ---
// 1.1.7: added media gallery script enqueueing for image field
// 1.1.7: added color picker and color picker alpha script enqueueing
$enqueued_media = $enqueued_color_picker = $enqueue_color_picker = $enqueue_color_picker_alpha = false;
foreach ( $options as $option ) {
if ( ( 'image' == $option['type'] ) && !$enqueued_media ) {
wp_enqueue_media();
$enqueued_media = true;
} elseif ( 'color' == $option['type'] ) {
$enqueue_color_picker = true;
} elseif ( 'coloralpha' == $option['type'] ) {
$enqueue_color_picker_alpha = true;
}
}
// 1.2.5: moved out of
if ( $enqueue_color_picker_alpha ) {
wp_enqueue_style( 'wp-color-picker' );
$suffix = '.min';
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
$suffix = '';
}
$url = plugins_url( '/js/wp-color-picker-alpha' . $suffix . '.js', $args['file'] );
wp_enqueue_script( 'wp-color-picker-a', $url, array( 'wp-color-picker' ), '3.0.0', true );
$enqueued_color_picker = true;
} elseif ( $enqueue_color_picker ) {
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_script( 'wp-color-picker' );
$enqueued_color_picker = true;
}
$defaults = $this->default_settings();
$settings = $this->get_settings( false );
// --- output saved settings ---
if ( $this->debug ) {
echo 'Saved Settings: ';
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
echo esc_html( print_r( $settings, true ) );
echo ' ';
}
// --- get option tabs and sections ---
$tabs = $this->tabs;
$sections = $this->sections;
$currenttab = '';
if ( isset( $settings['settingstab'] ) ) {
$currenttab = $settings['settingstab'];
}
// --- loop options to maybe get tabbed groupings ---
$taboptions = array();
foreach ( $options as $key => $option ) {
if ( isset( $option['tab'] ) && array_key_exists( $option['tab'], $tabs ) ) {
if ( ( count( $sections ) > 0 ) && isset( $option['section'] ) ) {
$taboptions[$option['tab']][$option['section']][$key] = $option;
} else {
$taboptions[$option['tab']]['general'][$key] = $option;
}
} else {
if ( ( count( $sections ) > 0 ) && isset( $option['section'] ) ) {
$taboptions['general'][$option['section']][$key] = $option;
} else {
$taboptions['general']['general'][$key] = $option;
}
}
}
// --- remove unused tabs automatically ---
// 1.1.7: added for cleaner interface while developing
foreach ( $tabs as $tab => $tablabel ) {
if ( !isset( $taboptions[$tab] ) ) {
unset( $tabs[$tab] );
}
}
// --- maybe push general section to top of tab ---
foreach ( $taboptions as $tab => $tabsections ) {
if ( isset( $tabsections['general'] ) && ( count( $tabsections ) > 1 ) ) {
$general = $tabsections['general'];
unset( $tabsections['general'] );
$taboptions[$tab] = array_merge( $general, $tabsections );
}
}
// --- maybe output tab groupings ---
if ( count( $tabs ) > 0 ) {
// --- output tab switcher script ---
// 1.0.9: add to settings scripts
// 1.2.5: only store script reference
$this->scripts[] = 'tab_switcher';
$i = 0;
echo '' . PHP_EOL;
} else {
$tabs = array( 'general' => __( 'General' ) );
}
// --- reset to default script ---
// 1.0.9: add to settings scripts
$this->scripts[] = 'settings_reset';
// --- start settings form ---
// 1.2.0: remove unused prefix on settings tab name attribute
echo '' . PHP_EOL;
// --- enqueue settings resources ---
$this->settings_resources( $enqueued_media, $enqueued_color_picker );
}
// ---------------------
// Allowed Inputs Filter
// ---------------------
// 1.2.5: added allowed inputs filter
public function allowed_html( $option = false ) {
$namespace = $this->namespace;
// --- get default allowed post HTML ---
$allowed = wp_kses_allowed_html( 'post' );
// --- input ---
// 1.2.6: add missing checked attribute
$allowed['input'] = array(
'id' => array(),
'class' => array(),
'name' => array(),
'value' => array(),
'type' => array(),
'data' => array(),
'placeholder' => array(),
'checked' => array(),
'data-alpha-enabled' => array(),
'data-default-color' => array(),
);
// --- textarea ---
$allowed['textarea'] = array(
'id' => array(),
'class' => array(),
'name' => array(),
'value' => array(),
'type' => array(),
'placeholder' => array(),
);
// --- select ---
// 1.2.6: fix multiselect to multiple
$allowed['select'] = array(
'id' => array(),
'class' => array(),
'name' => array(),
'value' => array(),
'type' => array(),
'multiple' => array(),
);
// --- select option ---
// 1.2.6: added missing value attribute
$allowed['option'] = array(
'selected' => array(),
'value' => array(),
);
// --- option group ---
$allowed['optgroup'] = array(
'label' => array(),
);
$allowed = apply_filters( $namespace . '_settings_allowed_html', $allowed, $option );
return $allowed;
}
// ------------------
// Settings Resources
// ------------------
// 1.2.3: added for standalone enqueueing of resources from table
// 1.2.4: added missing public visibility declaration
public function settings_resources( $media = true, $color_picker = true ) {
// --- number input step script ---
// 1.0.9: added to script array
// 1.1.8: fix to check for no mix or max value
$this->scripts[] = 'number_step';
// --- image selection script ---
// 1.1.7: added for image field type
if ( $media ) {
$this->scripts[] = 'media_functions';
}
// --- color picker script ---
if ( $color_picker ) {
$this->scripts[] = 'colorpicker_init';
}
// --- enqueue settings scripts ---
add_action( 'admin_footer', array( $this, 'setting_scripts' ) );
// --- enqueue settings styles ---
add_action( 'admin_footer', array( $this, 'setting_styles' ) );
}
// -----------
// Setting Row
// -----------
// 1.0.9: added for automatic Settings table generation
public function setting_row( $option ) {
// --- prepare setting keys ---
$args = $this->args;
$namespace = $this->namespace;
$postkey = $args['settings'];
$name = $postkey . '_' . $option['key'];
$type = $option['type'];
$setting = $this->get_setting( $option['key'], false );
// --- convert old option type names ---
if ( 'email' == $type ) {
$type = 'text';
$option['options'] = 'EMAIL';
} elseif ( 'emails' == $type ) {
$type = 'text';
$option['options'] = 'EMAIL';
} elseif ( 'url' == $type ) {
$type = 'text';
$option['options'] = 'URL';
} elseif ( 'urls' == $type ) {
$type = 'text';
$option['options'] = 'URL';
} elseif ( 'alpabetic' == $type ) {
$type = 'text';
$option['options'] = 'ALPHABETIC';
} elseif ( 'alphanumeric' == $type ) {
$type = 'text';
$option['options'] = 'ALPHANUMERIC';
} elseif ( 'numeric' == $type ) {
$option['options'] = 'NUMERIC';
} elseif ( 'numeric' == $type ) {
$option['options'] = 'NUMERIC';
} elseif ( 'usernames' == $type ) {
$type = 'text';
$option['options'] = 'USERNAME';
} elseif ( 'csvslugs' == $type ) {
$type = 'text';
$option['options'] = 'SLUG';
} elseif ( 'pageid' == $type ) {
$type = 'select';
$option['options'] = 'PAGEID';
} elseif ( 'postid' == $type ) {
$type = 'select';
$option['options'] = 'POSTID';
} elseif ( 'pageids' == $type ) {
$type = 'multiselect';
$option['options'] = 'PAGEIDS';
} elseif ( 'postids' == $type ) {
$type = 'multiselect';
$option['options'] = 'POSTIDS';
}
// --- prepare row output ---
$row = '' . PHP_EOL;
$row .= '' . $option['label'] . PHP_EOL;
if ( 'multiselect' == $type ) {
$row .= '' . esc_html( __( 'Use Ctrl and Click to Select' ) ) . ' ' . PHP_EOL;
}
$row .= ' ' . PHP_EOL;
// 1.0.9: added multiple cell spanning note type
if ( ( 'note' == $type ) || ( 'info' == $type ) || ( 'helper' == $type ) ) {
$row .= '' . PHP_EOL;
if ( isset( $option['helper'] ) ) {
$row .= $option['helper'];
}
$row .= ' ' . PHP_EOL;
} else {
// TODO: add check if already Pro version ?
// (currently done by removing pro key/value pair in Pro code)
if ( isset( $option['pro'] ) && $option['pro'] ) {
// --- Pro version setting (teaser) ---
// 1.2.0: improved handling of upgrade links
$row .= '';
$upgrade_link = false;
$upgrade_target = '';
if ( ( isset( $args['hasplans'] ) && $args['hasplans'] )
|| ( isset( $args['hasaddons'] ) && $args['hasaddons'] ) ) {
$upgrade_link = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) );
}
if ( isset( $args['upgrade_link'] ) ) {
$upgrade_link = $args['upgrade_link'];
$upgrade_target = !strstr( $upgrade_link, '/wp-admin/' ) ? ' target="_blank"' : '';
}
// 1.2.1: fix to check pro_link not upgrade_link
if ( isset( $args['pro_link'] ) ) {
$pro_link = $args['pro_link'];
$pro_target = !strstr( $pro_link, '/wp-admin/' ) ? ' target="_blank"' : '';
}
if ( $upgrade_link || isset( $pro_link ) ) {
// 1.2.2: change text from Available in Pro
$row .= __( 'Premium Feature.' ) . ' ';
if ( $upgrade_link ) {
$row .= '' . esc_html( __( 'Upgrade Now' ) ) . ' ';
}
if ( $upgrade_link && isset( $pro_link ) ) {
$row .= ' | ';
}
if ( isset( $pro_link ) ) {
// 1.2.2: change text from Pro details
$row .= '' . esc_html( __( 'Details' ) ) . ' ' . PHP_EOL;
}
} else {
$row .= esc_html( __( 'Coming soon in Pro version!' ) );
}
$row .= ' ' . PHP_EOL;
} else {
$row .= '' . PHP_EOL;
// --- maybe prepare special options ---
if ( isset( $option['options'] ) && is_string( $option['options'] ) ) {
// --- maybe prepare post/page options (once) ---
if ( in_array( $option['options'], array( 'POSTID', 'POSTIDS', 'PAGEID', 'PAGEIDS' ) ) ) {
$posttype = strtolower( substr( $option['options'], 0, 4 ) );
if ( ( ( 'page' == $posttype ) && !isset( $pageoptions ) )
|| ( ( 'post' == $posttype ) && !isset( $postoptions ) ) ) {
global $wpdb;
$query = "SELECT ID,post_title,post_status FROM " . $wpdb->prefix . "posts";
$query .= " WHERE post_type = %s AND post_status != 'auto-draft'";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$query = $wpdb->prepare( $query, $posttype );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$results = $wpdb->get_results( $query, ARRAY_A );
// 1.2.7: fix by moving page/post options variable here
$pageoptions = $postoptions = array( '' => '' );
if ( $results && ( count( $results ) > 0 ) ) {
foreach ( $results as $result ) {
if ( strlen( $result['post_title'] ) > 35 ) {
$result['post_title'] = substr( $result['post_title'], 0, 35 ) . '...';
}
$label = $result['ID'] . ': ' . $result['post_title'];
if ( 'publish' != $result['post_status'] ) {
$label .= ' (' . $result['post_status'] . ')';
}
if ( 'page' == $posttype ) {
$pageoptions[$result['ID']] = $label;
} elseif ( 'post' == $posttype ) {
$postoptions[$result['ID']] = $label;
}
}
}
}
if ( 'page' == $posttype ) {
$option['options'] = $pageoptions;
} elseif ( 'post' == $posttype ) {
$option['options'] = $postoptions;
}
}
// --- maybe prepare public post type options (once) ---
if ( in_array( $option['options'], array( 'PUBLICTYPE', 'PUBLICTYPES' ) ) ) {
$publicoptions = array();
$cpts = array( 'page', 'post' );
$args = array( 'public' => true, '_builtin' => false );
$cptlist = get_post_types( $args, 'names', 'and' );
$cpts = array_merge( $cpts, $cptlist );
foreach ( $cpts as $cpt ) {
$posttypeobject = get_post_type_object( $cpt );
$label = $posttypeobject->labels->singular_name;
$publicoptions[$cpt] = $label;
}
$option['options'] = $publicoptions;
}
// --- maybe prepare post type options (once) ---
if ( in_array( $option['options'], array( 'POSTTYPE', 'POSTTYPES' ) ) ) {
$cptoptions = array();
$cpts = array( 'page', 'post' );
$args = array( '_builtin' => false );
$cptlist = get_post_types( $args, 'names', 'and' );
$cpts = array_merge( $cpts, $cptlist );
foreach ( $cpts as $cpt ) {
$posttypeobject = get_post_type_object( $cpt );
$label = $posttypeobject->labels->singular_name;
$cptoptions[$cpt] = $label;
}
$option['options'] = $cptoptions;
}
// --- maybe prepare all post type options (once) ---
if ( in_array( $option['options'], array( 'ALLTYPE', 'ALLTYPES' ) ) ) {
$allcptoptions = array();
$args = array( '_builtin' => true );
$cpts = get_post_types( $args, 'names', 'and' );
foreach ( $cpts as $cpt ) {
$posttypeobject = get_post_type_object( $cpt );
$label = $posttypeobject->labels->singular_name;
$allcptoptions[$cpt] = $label;
}
$option['options'] = $allcptoptions;
}
// --- maybe prepare user options (once) ---
if ( in_array( $option['options'], array( 'USERID', 'USERIDS', 'USERNAME', 'USERNAMES' ) ) ) {
if ( in_array( $option['options'], array( 'USERID', 'USERIDS' ) ) ) {
$userkey = 'userid';
} else {
$userkey = 'username';
}
$useroptions = array( '' => '' );
global $wpdb;
$query = "SELECT ID,user_login,display_name FROM " . $wpdb->prefix . "users";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$results = $wpdb->get_results( $query, ARRAY_A );
if ( $results && ( count( $results ) > 0 ) ) {
foreach ( $results as $result ) {
$label = $result['ID'] . ': ' . $result['display_name'];
if ( $result['display_name'] != $result['user_login'] ) {
$label .= ' (' . $result['user_login'] . ')';
}
if ( 'userid' == $userkey ) {
$useroptions[$result['ID']] = $label;
} elseif ( 'username' == $userkey ) {
$useroptions[$result['user_login']] = $label;
}
}
}
$option['options'] = $useroptions;
}
}
// --- output option input types ---
if ( 'toggle' == $type ) {
// --- toggle ---
// 1.0.9: add toggle input (styled checkbox)
// 1.1.7: set default option value if not set
if ( !isset( $option['value'] ) ) {
$option['value'] = '1';
}
$checked = ( $setting == $option['value'] ) ? ' checked="checked"' : '';
$row .= '';
$row .= ' ' . PHP_EOL;
$row .= ' ' . PHP_EOL;
$row .= ' ' . PHP_EOL;
if ( isset( $option['suffix'] ) ) {
$row .= ' ' . $option['suffix'];
}
} elseif ( 'checkbox' == $type ) {
// --- checkbox ---
// 1.1.7: set default option value if not set
if ( !isset( $option['value'] ) ) {
$option['value'] = '1';
}
$checked = ( $setting == $option['value'] ) ? ' checked="checked"' : '';
$row .= ' ' . PHP_EOL;
if ( isset( $option['suffix'] ) ) {
$row .= ' ' . $option['suffix'];
}
} elseif ( 'multicheck' == $type ) {
// --- multicheck boxes ---
$checkboxes = array();
foreach ( $option['options'] as $key => $label ) {
$checked = '';
if ( is_array( $setting ) && in_array( $key, $setting ) ) {
$checked = ' checked="checked"';
}
$checkboxes[] = ' ' . esc_html( $label ) . PHP_EOL;
}
$row .= implode( ' ', $checkboxes );
} elseif ( 'radio' == $type ) {
// --- radio buttons ---
$radios = array();
foreach ( $option['options'] as $value => $label ) {
$checked = ( $setting == $value ) ? ' checked="checked"' : '';
$radios[] = ' ' . esc_html( $label ) . PHP_EOL;
}
$row .= implode( ' ', $radios );
} elseif ( 'select' == $type ) {
// --- select dropdown ---
$row .= '' . PHP_EOL;
foreach ( $option['options'] as $value => $label ) {
// 1.0.9: support option grouping (set unique key containing OPTGROUP-)
if ( strstr( $value, '*OPTGROUP*' ) ) {
$row .= '' . esc_html( $label ) . ' ' . PHP_EOL;
} else {
// 1.1.3: remove strict value checking
$row .= '' . PHP_EOL;
}
}
$row .= ' ';
if ( isset( $option['suffix'] ) ) {
$row .= ' ' . $option['suffix'];
}
} elseif ( 'multiselect' == $type ) {
// --- multiselect dropdown ---
$row .= '' . PHP_EOL;
foreach ( $option['options'] as $value => $label ) {
if ( '' != $value ) {
// 1.1.3: check for OPTGROUP instead of *OPTGROUP*
if ( strstr( $value, 'OPTGROUP' ) ) {
$row .= '' . PHP_EOL;
} else {
if ( is_array( $setting ) && in_array( $value, $setting ) ) {
$selected = ' selected="selected"';
} else {
$selected = '';
}
$row .= '" . esc_html( $label ) . ' ' . PHP_EOL;
}
}
}
$row .= ' ';
if ( isset( $option['suffix'] ) ) {
$row .= ' ' . $option['suffix'];
}
} elseif ( ( 'text' == $type ) || ( 'csv' == $type ) ) {
// 1.2.0: re-added missing csv field type
// --- text inputs ---
$class = 'setting-text';
if ( 'text' != $type ) {
$class .= ' setting-' . $type;
}
if ( isset( $option['placeholder'] ) ) {
$placeholder = $option['placeholder'];
} else {
$placeholder = '';
}
// 1.1.7: fix to attribute quoting output
$row .= ' ' . PHP_EOL;
if ( isset( $option['suffix'] ) ) {
$row .= ' ' . $option['suffix'];
}
} elseif ( 'textarea' == $type ) {
// --- textarea input ---
if ( isset( $option['rows'] ) ) {
$rows = $option['rows'];
} else {
$rows = '6';
}
if ( isset( $option['placeholder'] ) ) {
$placeholder = $option['placeholder'];
} else {
$placeholder = '';
}
// 1.2.4: added missing esc_textarea on value
$row .= '' . PHP_EOL;
} elseif ( ( 'numeric' == $type ) || ( 'number' == $type ) ) {
// --- numeric text input ---
// note: step key is only used for controls, not for validation
if ( isset( $option['placeholder'] ) ) {
$placeholder = $option['placeholder'];
} else {
$placeholder = '';
}
if ( isset( $option['min'] ) ) {
$min = $option['min'];
} else {
$min = 'false';
}
if ( isset( $option['max'] ) ) {
$max = $option['max'];
} else {
$max = 'false';
}
if ( isset( $option['step'] ) ) {
$step = $option['step'];
} else {
$step = 1;
}
// 1.1.7: remove esc_js from onclick attributes
// $onclickdown = "plugin_panel_number_step('down', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");" . PHP_EOL;
// $row .= ' ' . PHP_EOL;
$row .= ' ' . PHP_EOL;
if ( isset( $option['prefix'] ) ) {
$row .= ' ' . $option['prefix'];
}
$data = esc_attr( $min ) . "," . esc_attr( $max ) . "," . esc_attr( $step );
$row .= ' ' . PHP_EOL;
if ( isset( $option['suffix'] ) ) {
$row .= ' ' . $option['suffix'];
}
// $onclickup = "plugin_panel_number_step('up', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");" . PHP_EOL;
// $row .= ' ' . PHP_EOL;
$row .= ' ' . PHP_EOL;
} elseif ( 'image' == $type ) {
// 1.1.7: added image attachment selection from media library
// --- get current image ---
$image = wp_get_attachment_image_src( $setting, 'full' );
$has_image = is_array( $image );
// --- image container ---
$row .= '';
if ( $has_image ) {
$row .= '
' . PHP_EOL;
}
$row .= '
' . PHP_EOL;
// --- add and remove links ---
$upload_link = get_upload_iframe_src( 'image' );
$row .= '' . PHP_EOL;
$hidden = '';
if ( $has_image ) {
$hidden = ' hidden';
}
$row .= '' . PHP_EOL;
$row .= esc_html( __( 'Add Image' ) );
$row .= ' ' . PHP_EOL;
$hidden = '';
if ( !$has_image ) {
$hidden = ' hidden';
}
$row .= '' . PHP_EOL;
$row .= esc_html( __( 'Remove Image' ) );
$row .= ' ' . PHP_EOL;
$row .= '
' . PHP_EOL;
// --- hidden input for image ID ---
$row .= ' ' . PHP_EOL;
} elseif ( 'color' == $type ) {
// 1.1.7: added color picker field
$row .= ' ' . PHP_EOL;
} elseif ( 'coloralpha' == $type ) {
// 1.1.7: added color picker alpha field
$row .= ' ' . PHP_EOL;
}
$row .= ' ';
}
// --- setting helper text ---
if ( isset( $option['helper'] ) ) {
$row .= ' ' . PHP_EOL;
$row .= '' . esc_html( $option['helper'] ) . ' ' . PHP_EOL;
}
}
$row .= ' ' . PHP_EOL;
// --- settings row spacer ---
$row .= ' ' . PHP_EOL;
// --- filter and return setting row ---
$row = apply_filters( $namespace . '_setting_row', $row, $option );
return $row;
}
// ---------------
// Setting Scripts
// ---------------
// 1.0.9: added settings page scripts
public function setting_scripts() {
$args = $this->args;
$scripts = $this->scripts;
if ( count( $scripts ) > 0 ) {
echo "";
}
}
// --------------
// Setting Styles
// --------------
public function setting_styles() {
$styles = array();
// --- page styles ---
// 1.2.9: add padding to bottom of settings form
$styles[] = '#wrapbox {margin-right: 20px; padding-bottom: 20px;}';
// --- plugin header styles ---
// 1.2.0: moved from plugin header section
// 1.2.3: remove underline from plugin icon spans
$styles[] = '.pluginlink, .pluginlink span {text-decoration:none;}';
$styles[] = '.smalllink {font-size:11px;}';
$styles[] = '.readme:hover {text-decoration:underline;}';
// --- settings tab styles ---
// 1.1.0: added max-width:100% to select input
// 1.2.7: add pointer cursor to inactive tabs
$styles[] = '.settings-tab-button {display:inline-block; font-size:15px; padding:7px 14px; margin-right:20px; border-radius:7px;}';
$styles[] = '.settings-tab-button.active {font-weight:bold; background-color:#0073aa; color:#FFF; border:1px solid #FFF;}';
$styles[] = '.settings-tab-button.inactive {font-weight:bold; background-color:#F5F5F5; color:#0073aa; border:1px solid #000; cursor:pointer;}';
$styles[] = '.settings-tab-button.inactive:hover {background-color:#FFFFFF; color:#00a0d2;}';
$styles[] = '.settings-tab.active {display:block;}';
$styles[] = '.settings-tab.inactive {display:none;}';
// --- setting row styles ---
$styles[] = '.settings-label {vertical-align:top; font-weight:bold; min-width:100px; max-width:200px;}';
$styles[] = '.settings-label span {font-weight: normal; font-size:10px;}';
$styles[] = '.settings-helper {vertical-align:top; font-style:italic; min-width:200px; max-width:300px;}';
$styles[] = '.settings-spacer {height: 7px;}';
// --- setting input styles ---
$styles[] = '.settings-input {vertical-align:top; min-width:100px; max-width:300px;}';
// $styles[] = '.settings-input input.setting-radio {}';
// $styles[] = '.settings-input input.setting-checkbox {}';
$styles[] = '.settings-input input.setting-text {width:100%;}';
$styles[] = '.settings-input input.setting-numeric {display:inline-block; width:50%; text-align:center; vertical-align:middle;}';
$styles[] = '.settings-input input.setting-button {display:inline-block; padding:0px 5px;}';
$styles[] = '.settings-input input.setting-button.number-down-button {padding:0px 7px; font-weight:bold;}';
$styles[] = '.settings-input input.setting-textarea {width:100%;}';
$styles[] = '.settings-input select.setting-select {min-width:100px; max-width:100%;}';
// --- toggle input styles ---
// Ref: https://www.w3schools.com/howto/howto_css_switch.asp
$styles[] = '.setting-toggle {position:relative; display:inline-block; width:30px; height:17px;}
.setting-toggle input {opacity:0; width:0; height:0;}
.setting-slider {position:absolute; cursor:pointer;
top:0; left:0; right:0; bottom:0; background-color:#ccc;
-webkit-transition:.4s; transition:.4s;
}
.setting-slider:before {position:absolute; content:""; height:13px; width:13px;
left:2px; bottom:2px; background-color:white; -webkit-transition:.4s; transition:.4s;
}
input:checked + .setting-slider {background-color:#2196F3;}
input:focus + .setting-slider {box-shadow:0 0 1px #2196F3;}
input:checked + .setting-slider:before {
-webkit-transform:translateX(13px); -ms-transform:translateX(13px); transform:translateX(13px);
}
.setting-slider.round {border-radius: 17px;}
.setting-slider.round:before {border-radius: 50%;}';
// --- color picker styles ---
// 1.2.7: added to overlay active color picker
$styles[] = '.wp-picker-active {position:absolute; z-index:999; background-color:#FFF; border:1px solid #999; padding:5px;}';
$styles[] = '.wp-picker-active .wp-picker-input-wrap {display:block;}';
$styles[] = '.wp-color-picker {max-width:200px;}';
// --- filter and output styles ---
$namespace = $this->namespace;
$styles = apply_filters( $namespace . '_admin_page_styles', $styles );
echo "";
}
}
} // end Plugin Loader Class
// ----------------------------------
// Load Namespaced Prefixed Functions
// ----------------------------------
// [Optional] rename functions prefix to your plugin namespace
// these functions will then be available within your plugin
// to more easily call the matching plugin loader class methods
// 1.0.3: added priority of 0 to prefixed function loading action
add_action( 'plugins_loaded', 'teleporter_load_prefixed_functions', 0 );
if ( !function_exists( 'teleporter_load_prefixed_functions' ) ) {
function teleporter_load_prefixed_functions() {
// ------------------
// Get Namespace Slug
// ------------------
// --- Auto-magic Mamespacing Slug Note ---
// the below functions use the function name to grab and load the corresponding class method
// all function name suffixes here must be two words for the magic namespace grabber to work
// ie. _add_settings, because the namespace is taken from *before the second-last underscore*
if ( !function_exists( 'teleporter_get_namespace_from_function' ) ) {
function teleporter_get_namespace_from_function( $f ) {
return substr( $f, 0, strrpos( $f, '_', ( strrpos( $f, '_' ) - strlen( $f ) - 1 ) ) );
}
}
// -------------------
// Get Loader Instance
// -------------------
// 2.3.0: added function for getting loader class instance
if ( !function_exists( 'teleporter_loader_instance' ) ) {
function teleporter_loader_instance() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
return $GLOBALS[$namespace . '_instance'];
}
}
// ---------------------
// Get Freemius Instance
// ---------------------
// 2.3.0: added function for getting Freemius class instance
if ( !function_exists( 'teleporter_freemius_instance' ) ) {
function teleporter_freemius_instance() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
return $GLOBALS[$namespace . '_freemius'];
}
}
// ---------------
// Get Plugin Data
// ---------------
// 1.1.1: added function for getting plugin data
if ( !function_exists( 'teleporter_plugin_data' ) ) {
function teleporter_plugin_data() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
return $instance->plugin_data();
}
}
// ------------------
// Get Plugin Version
// ------------------
// 1.1.2: added function for getting plugin version
if ( !function_exists( 'teleporter_plugin_version' ) ) {
function teleporter_plugin_version() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
return $instance->plugin_version();
}
}
// -----------------
// Set Pro Namespace
// -----------------
if ( !function_exists( 'teleporter_pro_namespace' ) ) {
function teleporter_pro_namespace( $pronamespace ) {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->pro_namespace( $pronamespace );
}
}
// ===============
// Plugin Settings
// ===============
// ------------
// Add Settings
// ------------
if ( !function_exists( 'teleporter_add_settings' ) ) {
function teleporter_add_settings() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->add_settings();
}
}
// ------------
// Get Defaults
// ------------
if ( !function_exists( 'teleporter_default_settings' ) ) {
function teleporter_default_settings( $key = false ) {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
return $instance->default_settings( $key );
}
}
// -----------
// Get Options
// -----------
if ( !function_exists( 'teleporter_get_options' ) ) {
function teleporter_get_options() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
return $instance->options;
}
}
// -----------
// Get Setting
// -----------
if ( !function_exists( 'teleporter_get_setting' ) ) {
function teleporter_get_setting( $key, $filter = true ) {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
return $instance->get_setting( $key, $filter );
}
}
// ----------------
// Get All Settings
// ----------------
// 1.0.9: added missing get_settings prefixed function
if ( !function_exists( 'teleporter_get_settings' ) ) {
function teleporter_get_settings( $filter = true ) {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
return $instance->get_settings( $filter );
}
}
// --------------
// Reset Settings
// --------------
if ( !function_exists( 'teleporter_reset_settings' ) ) {
function teleporter_reset_settings() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->reset_settings();
}
}
// ---------------
// Update Settings
// ---------------
if ( !function_exists( 'teleporter_update_settings' ) ) {
function teleporter_update_settings() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->update_settings();
}
}
// ---------------
// Delete Settings
// ---------------
if ( !function_exists( 'teleporter_delete_settings' ) ) {
function teleporter_delete_settings() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->delete_settings();
}
}
// -----------
// Message Box
// -----------
if ( !function_exists( 'teleporter_message_box' ) ) {
function teleporter_message_box( $message, $echo = false ) {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
return $instance->message_box( $message, $echo );
}
}
// ---------------
// Settings Header
// ---------------
if ( !function_exists( 'teleporter_settings_header' ) ) {
function teleporter_settings_header() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->settings_header();
}
}
// -------------
// Settings Page
// -------------
if ( !function_exists( 'teleporter_settings_page' ) ) {
function teleporter_settings_page() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->settings_page();
}
}
// --------------
// Settings Table
// --------------
// 1.0.9: added for standalone setting table output
if ( !function_exists( 'teleporter_settings_table' ) ) {
function teleporter_settings_table() {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->settings_table();
}
}
// ------------
// Settings Row
// ------------
// 1.0.9: added for standalone setting row output
if ( !function_exists( 'teleporter_settings_row' ) ) {
function teleporter_settings_row( $option, $setting ) {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->settings_row( $option, $setting );
}
}
// ------------------
// Settings Resources
// ------------------
// 1.2.3: added for separate enqueueing of resources from table
if ( !function_exists( 'teleporter_settings_resources' ) ) {
function teleporter_settings_resources( $media, $color_picker ) {
$namespace = teleporter_get_namespace_from_function( __FUNCTION__ );
$instance = $GLOBALS[$namespace . '_instance'];
$instance->settings_resources( $media, $color_picker );
}
}
}
}
// fully loaded
// ------------
// =========
// STRUCTURE
// =========
//
// === Loader Class ===
// - Initialize Loader
// - Setup Plugin
// - Get Plugin Data
// - Get Plugin Version
// - Set Pro Namespace
// === Plugin Settings ===
// - Get Default Settings
// - Add Settings
// - Maybe Transfer Settings
// - Get All Plugin Settings
// - Get Plugin Setting
// - Reset Plugin Settings
// - Update Plugin Settings
// - Validate Plugin Setting
// === Plugin Loading ===
// - Load Plugin Settings
// - Add Actions
// - Load Helper Libraries
// - Maybe Load Thickbox
// - Readme Viewer AJAX
// === Freemius Loading ===
// - Load Freemius
// - Filter Freemius Connect
// - Freemius Connect Message
// - Connect Update Message
// === Plugin Admin ===
// - Add Settings Menu
// - Plugin Page Links
// - Message Box
// - Notice Boxer
// - Plugin Page Header
// - Settings Page
// - Settings Table
// - Setting Row
// - Settings Scripts
// - Settings Styles
// === Namespaced Functions ===
// =========
// CHANGELOG
// =========
// == 1.2.9 ==
// - fix empty number field converting to NaN value
// - add bottom padding to settings form wrap box
// == 1.2.8 ==
// - fix saving non-alpha colours in coloralpha fields
// - fix saving of single value in CSV field
// == 1.2.7 ==
// - fix color picker alpha sanitization / saving
// - allow color picker alpha to not include alpha
// - added color picker dropdown overlay styling
// - added pointer cursor style to inactive tab
// == 1.2.6 ==
// - expanded wp_kses allowed input attributes
// == 1.2.5 ==
// - improved posted value input sanitization
// - corrected textarea field sanitization
// - added color and color alpha sanitization
// - use wp_kses and allowed HTML on outputs
// == 1.2.4 ==
// - fix to missing declaration on new settings_resources function
// == 1.2.3 ==
// - added separate enqueueing of settings resources
// - remove underline from plugin link icon spans
// - skip welcome message output if empty
// == 1.2.2 ==
// - merge in plugin links instead of using array_unshift
// - update plugin repository rating URL
// - remove duplication of addons link
// - no notice boxer if adminsanity notices loaded
// - change upgrade texts
// == 1.2.1 ==
// - added filters for premium and addons init
// - fix overriding of plan arg to free
// - add check if premium already installed
// - fix namespace typo in add settings action
// == 1.2.0 ==
// - fix missing CSV field type row output condition
// - fix to saving of current settings tab (if any)
// - improved handling of Pro Upgrade and Details links
// == 1.1.9 ==
// - fix to allow saving of zero value
// - added filters for plugin page settings header sections
// == 1.1.8 ==
// - fix to number step if no min or max value
// == 1.1.7 ==
// - added media library upload image field type
// - added color picker and color picker alpha field types
// - automatically remove unused settings tabs
// - fix to text field attribute quoting
// - fix to not escape number step button function
// - remove FILTER_VALIDATE_URL from URL saving (not working)
// == 1.1.6 ==
// - added phone number character validation
// == 1.1.5 ==
// - fix to validate multiple CSV values
// == 1.1.4 ==
// - fix for debug prefix key dash
// - fix for weird get_settings glitch bug (isset failing?!)
// == 1.1.3 ==
// - remove strict value checking on select input
// - change OPTGROUP string check in select options
// == 1.1.2 ==
// - fix for filtering of plugin options
// - fix for plugin page link URL
// - added menu added and debug switches
// - added get plugin version function
// == 1.1.1 ==
// - remove admin_url wrapper on Freemius first-path value
// == 1.1.0 ==
// - fix to saving multicheck as single array of values
// - added admin notices boxer to settings pages
// - added record for tracking first install version
// - added explicit home link to plugin page links
// == 1.0.9 ==
// - added automated Settings table and Admin Page output
// - added tab and section support to options array
// - added tab and section bottom and top action hooks
// - added multicheck, multiselect and toggle input types
// - added post type and post ID input validation
// - added _loader_instance and _get_settings prefixed functions
// - added helper function to get loader and Freemius instances
// - added development link to plugin page header links
// - added missing output sanitization wrappers
// - removed WordQuest specific functionality from loader
// - refactor separate setting update inputs and validation
// - fix to check if plugin icon PNG exists
// == 1.0.8 ==
// - added WordQuest plugin flag (for use by non-WQ plugins)
// - apply settings key filters when getting all settings
// - added filters for rate/share/donate links
// - added page ID to settings input types
// == 1.0.7 ==
// - fix support URL undefined variable warning
// - change readme.php to reader.php (for GitHub)
// == 1.0.6 ==
// - added global options filter
// - added 'emails' option type for multiple email saving
// - fix for new unchecked checkbox value
// - fix for typos in URL option type saving
// == 1.0.5 ==
// - fix for undefined account and support variables
// == 1.0.4 ==
// - added 'url' option type checking and saving
// - added 'csv' option type checking and saving
// - added 'csvslugs' option type checking and saving
// == 1.0.3 ==
// - added priority of 0 to prefixed function loading action
// - only use namespace not settings key for updates
// - added 'email' option type checking and save
// - added 'usernames' option type checking and save
// == 1.0.2 ==
// - fix some Freemius loading argument checks
// - fix namespace typo in update/reset triggers
// - add check for author icon file and fileback
// - add allowance for special settings processing
// - add allowance for auto-load of multiple Pro files
// == 1.0.1 ==
// - set_basename for Freemius free / premium plugins
// - set rate / share / donate links and text anchors
// - check for multiple Pro filenames and use first one
// == 1.0.0 ==
// - Working Release version
// == 0.9.0 ==
// - Development Version