balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class.mtekk_adminkit.php (50430B)


      1 <?php
      2 /*
      3   Copyright 2015-2018  John Havlik  (email : john.havlik@mtekk.us)
      4 
      5   This program is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU General Public License as published by
      7   the Free Software Foundation; either version 2 of the License, or
      8   (at your option) any later version.
      9 
     10   This program is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public License
     16   along with this program; if not, write to the Free Software
     17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     18  */
     19 require_once(dirname(__FILE__) . '/block_direct_access.php');
     20 //Include admin base class
     21 if (!class_exists('mtekk_adminKit_message')) {
     22     require_once(dirname(__FILE__) . '/class.mtekk_adminkit_message.php');
     23 }
     24 
     25 abstract class mtekk_adminKit {
     26 
     27     const version = '2.0.2';
     28 
     29     protected $full_name;
     30     protected $short_name;
     31     protected $plugin_basename;
     32     protected $access_level = 'manage_options';
     33     protected $identifier;
     34     protected $unique_prefix;
     35     protected $opt = array();
     36     protected $messages;
     37     protected $message;
     38     protected $support_url;
     39     protected $allowed_html;
     40 
     41     function __construct() {
     42         $this->message = array();
     43         $this->messages = array();
     44         //Admin Init Hook
     45         add_action('admin_init', array($this, 'init'));
     46         //WordPress Admin interface hook
     47         add_action('admin_menu', array($this, 'add_page'));
     48         //Installation Script hook
     49         add_action('activate_' . $this->plugin_basename, array($this, 'install'));
     50         //Initilizes l10n domain
     51         $this->local();
     52         add_action('wp_loaded', array($this, 'wp_loaded'));
     53         //Register Help Output
     54         //add_action('add_screen_help_and_options', array($this, 'help'));
     55     }
     56 
     57     function wp_loaded() {
     58         //Filter our allowed html tags
     59         $this->allowed_html = apply_filters($this->unique_prefix . '_allowed_html', wp_kses_allowed_html('post'));
     60     }
     61 
     62     /**
     63      * Returns the internal mtekk_admin_class version
     64      */
     65     function get_admin_class_version() {
     66         return mtekk_adminKit::version;
     67     }
     68 
     69     /**
     70      * Return the URL of the settings page for the plugin
     71      */
     72     function admin_url() {
     73         return admin_url('options-general.php?page=' . $this->identifier);
     74     }
     75 
     76     /**
     77      * A wrapper for nonced_anchor returns a nonced anchor for admin pages
     78      * 
     79      * @param string $mode The nonce "mode", a unique string at the end of the standardized nonce identifier
     80      * @param string $title (optional) The text to use in the title portion of the anchor
     81      * @param string $text (optional) The text that will be surrounded by the anchor tags
     82      * @return string the assembled anchor
     83      */
     84     function admin_anchor($mode, $title = '', $text = '') {
     85         return $this->nonced_anchor($this->admin_url(), 'admin_' . $mode, 'true', $title, $text);
     86     }
     87 
     88     /**
     89      * Returns a properly formed nonced anchor to the specified URI
     90      * 
     91      * @param string $uri The URI that the anchor should be for
     92      * @param string $mode The nonce "mode", a unique string at the end of the standardized nonce identifier
     93      * @param mixed $value (optional) The value to place in the query string 
     94      * @param string $title (optional) The text to use in the title portion of the anchor
     95      * @param string $text (optional) The text that will be surrounded by the anchor tags
     96      * @param string $anchor_extras (optional) This text is placed within the opening anchor tag, good for adding id, classe, rel field
     97      * @return string the assembled anchor
     98      */
     99     function nonced_anchor($uri, $mode, $value = 'true', $title = '', $text = '', $anchor_extras = '') {
    100         //Assemble our url, nonce and all
    101         $url = wp_nonce_url(add_query_arg($this->unique_prefix . '_' . $mode, $value, $uri), $this->unique_prefix . '_' . $mode);
    102         //Return a valid anchor
    103         return ' <a title="' . esc_attr($title) . '" href="' . $url . '" ' . $anchor_extras . '>' . esc_html($text) . '</a>';
    104     }
    105 
    106     /**
    107      * Abstracts the check_admin_referer so that all the end user has to supply is the mode
    108      * 
    109      * @param string $mode The specific nonce "mode" (see nonced_anchor) that is being checked
    110      */
    111     function check_nonce($mode) {
    112         check_admin_referer($this->unique_prefix . '_' . $mode);
    113     }
    114 
    115     /**
    116      * Makes sure the current user can manage options to proceed
    117      */
    118     function security() {
    119         //If the user can not manage options we will die on them
    120         if (!current_user_can($this->access_level)) {
    121             wp_die(__('Insufficient privileges to proceed.', $this->identifier));
    122         }
    123     }
    124 
    125     function init() {
    126         //Admin Options reset hook
    127         if (isset($_POST[$this->unique_prefix . '_admin_reset'])) {
    128             //Run the reset function on init if reset form has been submitted
    129             $this->opts_reset();
    130         }
    131         //Admin Options export hook
    132         else if (isset($_POST[$this->unique_prefix . '_admin_export'])) {
    133             //Run the export function on init if export form has been submitted
    134             $this->opts_export();
    135         }
    136         //Admin Options import hook
    137         else if (isset($_FILES[$this->unique_prefix . '_admin_import_file']) && !empty($_FILES[$this->unique_prefix . '_admin_import_file']['name'])) {
    138             //Run the import function on init if import form has been submitted
    139             $this->opts_import();
    140         }
    141         //Admin Options rollback hook
    142         else if (isset($_GET[$this->unique_prefix . '_admin_undo'])) {
    143             //Run the rollback function on init if undo button has been pressed
    144             $this->opts_undo();
    145         }
    146         //Admin Options upgrade hook
    147         else if (isset($_GET[$this->unique_prefix . '_admin_upgrade'])) {
    148             //Run the upgrade function on init if upgrade button has been pressed
    149             $this->opts_upgrade_wrapper();
    150         }
    151         //Admin Options fix hook
    152         else if (isset($_GET[$this->unique_prefix . '_admin_fix'])) {
    153             //Run the options fix function on init if fix button has been pressed
    154             $this->opts_upgrade_wrapper();
    155         }
    156         //Admin Options update hook
    157         else if (isset($_POST[$this->unique_prefix . '_admin_options'])) {
    158             //Temporarily add update function on init if form has been submitted
    159             $this->opts_update();
    160         }
    161         //Add in the nice "settings" link to the plugins page
    162         add_filter('plugin_action_links', array($this, 'filter_plugin_actions'), 10, 2);
    163         if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) {
    164             $suffix = '';
    165         } else {
    166             $suffix = '.min';
    167         }
    168         //Register JS for more permanently dismissing messages
    169         wp_register_script('mtekk_adminkit_messages', plugins_url('/mtekk_adminkit_messages' . $suffix . '.js', dirname(__FILE__) . '/mtekk_adminkit_messages' . $suffix . '.js'), array('jquery'), self::version, true);
    170         //Register JS for enable/disable settings groups
    171         wp_register_script('mtekk_adminkit_engroups', plugins_url('/mtekk_adminkit_engroups' . $suffix . '.js', dirname(__FILE__) . '/mtekk_adminkit_engroups' . $suffix . '.js'), array('jquery'), self::version, true);
    172         //Register JS for tabs
    173         wp_register_script('mtekk_adminkit_tabs', plugins_url('/mtekk_adminkit_tabs' . $suffix . '.js', dirname(__FILE__) . '/mtekk_adminkit_tabs' . $suffix . '.js'), array('jquery-ui-tabs'), self::version, true);
    174         //Register CSS for tabs
    175         wp_register_style('mtekk_adminkit_tabs', plugins_url('/mtekk_adminkit_tabs' . $suffix . '.css', dirname(__FILE__) . '/mtekk_adminkit_tabs' . $suffix . '.css'));
    176         //Register options
    177         register_setting($this->unique_prefix . '_options', $this->unique_prefix . '_options', '');
    178         //Synchronize up our settings with the database as we're done modifying them now
    179         $this->opt = $this::parse_args($this->get_option($this->unique_prefix . '_options'), $this->opt);
    180         //Run the opts fix filter
    181         $this->opts_fix($this->opt);
    182         add_action('wp_ajax_mtekk_admin_message_dismiss', array($this, 'dismiss_message'));
    183     }
    184 
    185     /**
    186      * Adds the adminpage the menu and the nice little settings link
    187      * TODO: make this more generic for easier extension
    188      */
    189     function add_page() {
    190         //Add the submenu page to "settings" menu
    191         $hookname = add_submenu_page('options-general.php', $this->full_name, $this->short_name, $this->access_level, $this->identifier, array($this, 'admin_page'));
    192         // check capability of user to manage options (access control)
    193         if (current_user_can($this->access_level)) {
    194             //Register admin_head-$hookname callback
    195             add_action('admin_head-' . $hookname, array($this, 'admin_head'));
    196             //Register admin_print_styles-$hookname callback
    197             add_action('admin_print_styles-' . $hookname, array($this, 'admin_styles'));
    198             //Register admin_print_scripts-$hookname callback
    199             add_action('admin_print_scripts-' . $hookname, array($this, 'admin_scripts'));
    200             //Register Help Output
    201             add_action('load-' . $hookname, array($this, 'help'));
    202         }
    203     }
    204 
    205     /**
    206      * Initilizes localization textdomain for translations (if applicable)
    207      * 
    208      * Will conditionally load the textdomain for translations. This is here for
    209      * plugins that span multiple files and have localization in more than one file
    210      * 
    211      * @return void
    212      */
    213     function local() {
    214         global $l10n;
    215     }
    216 
    217     /**
    218      * Places in a link to the settings page in the plugins listing entry
    219      * 
    220      * @param  array  $links An array of links that are output in the listing
    221      * @param  string $file The file that is currently in processing
    222      * @return array  Array of links that are output in the listing.
    223      */
    224     function filter_plugin_actions($links, $file) {
    225         //Make sure we are adding only for the current plugin
    226         if ($file == $this->plugin_basename) {
    227             //Add our link to the end of the array to better integrate into the WP 2.8 plugins page
    228             $links[] = '<a href="' . $this->admin_url() . '">' . esc_html__('Settings') . '</a>';
    229         }
    230         return $links;
    231     }
    232 
    233     /**
    234      * Checks to see if the plugin has been fully installed
    235      *
    236      * @return bool whether or not the plugin has been installed
    237      */
    238     function is_installed() {
    239         
    240     }
    241 
    242     /**
    243      * This sets up and upgrades the database settings, runs on every activation
    244      */
    245     function install() {
    246         //Call our little security function
    247         $this->security();
    248         //Try retrieving the options from the database
    249         $opts = $this->get_option($this->unique_prefix . '_options');
    250         //If there are no settings, copy over the default settings
    251         if (!is_array($opts)) {
    252             //Grab defaults from the object
    253             $opts = $this->opt;
    254             //Add the options
    255             $this->add_option($this->unique_prefix . '_options', $opts);
    256             $this->add_option($this->unique_prefix . '_options_bk', $opts, '', 'no');
    257             //Add the version, no need to autoload the db version
    258             $this->update_option($this->unique_prefix . '_version', $this::version, 'no');
    259         } else {
    260             //Retrieve the database version
    261             $db_version = $this->get_option($this->unique_prefix . '_version');
    262             if ($this::version !== $db_version) {
    263                 //Run the settings update script
    264                 $this->opts_upgrade($opts, $db_version);
    265                 //Always have to update the version
    266                 $this->update_option($this->unique_prefix . '_version', $this::version);
    267                 //Store the options
    268                 $this->update_option($this->unique_prefix . '_options', $this->opt);
    269             }
    270         }
    271     }
    272 
    273     /**
    274      * This removes database settings upon deletion of the plugin from WordPress
    275      */
    276     function uninstall() {
    277         //Remove the option array setting
    278         $this->delete_option($this->unique_prefix . '_options');
    279         //Remove the option backup array setting
    280         $this->delete_option($this->unique_prefix . '_options_bk');
    281         //Remove the version setting
    282         $this->delete_option($this->unique_prefix . '_version');
    283     }
    284 
    285     /**
    286      * Compares the supplided version with the internal version, places an upgrade warning if there is a missmatch
    287      * TODO: change this to being auto called in admin_init action
    288      */
    289     function version_check($version) {
    290         //If we didn't get a version, setup
    291         if ($version === false) {
    292             //Add the version, no need to autoload the db version
    293             $this->add_option($this->unique_prefix . '_version', $this::version, '', 'no');
    294         }
    295         //Do a quick version check
    296         if ($version && version_compare($version, $this::version, '<') && is_array($this->opt)) {
    297             //Throw an error since the DB version is out of date
    298             $this->messages[] = new mtekk_adminKit_message(esc_html__('Your settings are for an older version of this plugin and need to be migrated.', $this->identifier , 'Notice')
    299                     . $this->admin_anchor('upgrade', __('Migrate the settings now.', $this->identifier), __('Migrate now.', $this->identifier)), 'warning');
    300             //Output any messages that there may be
    301             $this->messages();
    302             return false;
    303         }
    304         //Do a quick version check
    305         else if ($version && version_compare($version, $this::version, '>') && is_array($this->opt)) {
    306             //Let the user know that their settings are for a newer version
    307             $this->messages[] = new mtekk_adminKit_message(esc_html__('Your settings are for a newer version of this plugin.', $this->identifier, 'warning')
    308                     . $this->admin_anchor('upgrade', __('Migrate the settings now.', $this->identifier), __('Attempt back migration now.', $this->identifier)), 'warning');
    309             //Output any messages that there may be
    310             $this->messages();
    311             return true;
    312         } else if (!is_array($this->opt)) {
    313             //Throw an error since it appears the options were never registered
    314             $this->messages[] = new mtekk_adminKit_message(esc_html__('Your plugin install is incomplete.', $this->identifier, 'warning')
    315                     . $this->admin_anchor('upgrade', __('Load default settings now.', $this->identifier), __('Complete now.', $this->identifier)), 'error');
    316             //Output any messages that there may be
    317             $this->messages();
    318             return false;
    319         } else if (!$this->opts_validate($this->opt)) {
    320             //Throw an error since it appears the options contain invalid data
    321             $this->messages[] = new mtekk_adminKit_message(esc_html__('One or more of your plugin settings are invalid.', $this->identifier)
    322                     . $this->admin_anchor('fix', __('Attempt to fix settings now.', $this->identifier), __('Fix now.', $this->identifier)), 'error');
    323             //Output any messages that there may be
    324             $this->messages();
    325             return false;
    326         }
    327         return true;
    328     }
    329 
    330     /**
    331      * A prototype function. End user should override if they need this feature.
    332      */
    333     function opts_validate(&$opts) {
    334         return true;
    335     }
    336 
    337     /**
    338      * A prototype function. End user should override if they need this feature.
    339      * 
    340      * @param array $opts
    341      */
    342     function opts_fix(&$opts) {
    343         
    344     }
    345 
    346     /**
    347      * Synchronizes the backup options entry with the current options entry
    348      */
    349     function opts_backup() {
    350         //Set the backup options in the DB to the current options
    351         $this->update_option($this->unique_prefix . '_options_bk', $this->get_option($this->unique_prefix . '_options'));
    352     }
    353 
    354     /**
    355      * Runs recursivly through the opts array, sanitizing and merging in updates from the $input array
    356      * 
    357      * @param array $opts good, clean array
    358      * @param array $input unsanitzed input array, not trusted at all
    359      * @todo This function should probably get a filter thrown within it to be more extensible
    360      */
    361     protected function opts_update_loop(&$opts, $input) {
    362         //Loop through all of the existing options (avoids random setting injection)
    363         foreach ($opts as $option => $value) {
    364             //If we have an array, dive into another recursive loop
    365             if (isset($input[$option]) && is_array($value)) {
    366                 $this->opts_update_loop($opts[$option], $input[$option]);
    367             }
    368             //We must check for unset settings, but booleans are ok to be unset
    369             else if (isset($input[$option]) || $option[0] == 'b') {
    370                 switch ($option[0]) {
    371                     //Handle the boolean options
    372                     case 'b':
    373                         $opts[$option] = isset($input[$option]);
    374                         break;
    375                     //Handle the integer options
    376                     case 'i':
    377                         $opts[$option] = (int) $input[$option];
    378                         break;
    379                     //Handle the absolute integer options
    380                     case 'a':
    381                         $opts[$option] = (int) abs($input[$option]);
    382                         break;
    383                     //Handle the floating point options
    384                     case 'f':
    385                         $opts[$option] = (float) $input[$option];
    386                         break;
    387                     //Handle the HTML options
    388                     case 'h':
    389                         $opts[$option] = wp_kses(stripslashes($input[$option]), $this->allowed_html);
    390                         break;
    391                     //Handle the HTML options that must not be null
    392                     case 'H':
    393                         if (isset($input[$option])) {
    394                             $opts[$option] = wp_kses(stripslashes($input[$option]), $this->allowed_html);
    395                         }
    396                         break;
    397                     //Handle the text options that must not be null
    398                     case 'S':
    399                         if (isset($input[$option])) {
    400                             $opts[$option] = esc_html($input[$option]);
    401                         }
    402                         break;
    403                     //Deal with strings that can be null
    404                     case 's':
    405                         $opts[$option] = esc_html($input[$option]);
    406                         break;
    407                     //Deal with enumerated types
    408                     case 'E':
    409                         $opts[$option] = $this->opts_sanitize_enum($input[$option], $option);
    410                         break;
    411                     //By default we have nothing to do, allows for internal settings
    412                     default:
    413                         break;
    414                 }
    415             }
    416         }
    417     }
    418 
    419     /**
    420      * Simple sanitization function for enumerated types, end users should overload this
    421      * with something more usefull
    422      * 
    423      * @param string $value The input value from the form
    424      * @param string $option The option name
    425      * @return string The sanitized enumerated string
    426      */
    427     private function opts_sanitize_enum($value, $option) {
    428         return esc_html($value);
    429     }
    430 
    431     /**
    432      * A better version of parse_args, will recrusivly follow arrays
    433      * 
    434      * @param mixed $args The arguments to be parsed
    435      * @param mixed $defaults (optional) The default values to validate against
    436      * @return mixed
    437      */
    438     static function parse_args($args, $defaults = '') {
    439         if (is_object($args)) {
    440             $r = get_object_vars($args);
    441         } else if (is_array($args)) {
    442             $r = & $args;
    443         } else {
    444             wp_parse_str($args, $r);
    445         }
    446         if (is_array($defaults)) {
    447             return mtekk_adminKit::array_merge_recursive($defaults, $r);
    448         }
    449         return $r;
    450     }
    451 
    452     /**
    453      * An alternate version of array_merge_recursive, less flexible
    454      * still recursive, ~2x faster than the more flexible version
    455      * 
    456      * @param array $arg1 first array
    457      * @param array $arg2 second array to merge into $arg1
    458      * @return array
    459      */
    460     static function array_merge_recursive($arg1, $arg2) {
    461         foreach ($arg2 as $key => $value) {
    462             if (array_key_exists($key, $arg1) && is_array($value)) {
    463                 $arg1[$key] = mtekk_adminKit::array_merge_recursive($arg1[$key], $value);
    464             } else {
    465                 $arg1[$key] = $value;
    466             }
    467         }
    468         return $arg1;
    469     }
    470 
    471     /**
    472      * An action that fires just before the options backup, use to add in dynamically detected options
    473      * 
    474      * @param array $opts the options array, passed in by reference
    475      * @return null
    476      */
    477     function opts_update_prebk(&$opts) {
    478         //Just a prototype function
    479     }
    480 
    481     /**
    482      * Updates the database settings from the webform
    483      */
    484     function opts_update() {
    485         //Do some security related thigns as we are not using the normal WP settings API
    486         $this->security();
    487         //Do a nonce check, prevent malicious link/form problems
    488         check_admin_referer($this->unique_prefix . '_options-options');
    489         //Update local options from database
    490         $this->opt = $this::parse_args($this->get_option($this->unique_prefix . '_options'), $this->opt);
    491         $this->opts_update_prebk($this->opt);
    492         //Update our backup options
    493         $this->update_option($this->unique_prefix . '_options_bk', $this->opt);
    494         $opt_prev = $this->opt;
    495         //Grab our incomming array (the data is dirty)
    496         $input = $_POST[$this->unique_prefix . '_options'];
    497         //Run the update loop
    498         $this->opts_update_loop($this->opt, $input);
    499         //Commit the option changes
    500         $updated = $this->update_option($this->unique_prefix . '_options', $this->opt);
    501         //Check if known settings match attempted save
    502         if ($updated && count(array_diff_key($input, $this->opt)) == 0) {
    503             //Let the user know everything went ok
    504             $this->messages[] = new mtekk_adminKit_message(esc_html__('Settings successfully saved.', $this->identifier)
    505                     . $this->admin_anchor('undo', __('Undo the options save.', $this->identifier), __('Undo', $this->identifier)), 'success');
    506         } else if (!$updated && count(array_diff_key($opt_prev, $this->opt)) == 0) {
    507             $this->messages[] = new mtekk_adminKit_message(esc_html__('Settings did not change, nothing to save.', $this->identifier), 'info');
    508         } else if (!$updated) {
    509             $this->messages[] = new mtekk_adminKit_message(esc_html__('Settings were not saved.', $this->identifier), 'error');
    510         } else {
    511             //Let the user know the following were not saved
    512             $this->messages[] = new mtekk_adminKit_message(esc_html__('Some settings were not saved.', $this->identifier)
    513                     . $this->admin_anchor('undo', __('Undo the options save.', $this->identifier), __('Undo', $this->identifier)), 'warning');
    514             $temp = esc_html__('The following settings were not saved:', $this->identifier);
    515             foreach (array_diff_key($input, $this->opt) as $setting => $value) {
    516                 $temp .= '<br />' . $setting;
    517             }
    518             $this->messages[] = new mtekk_adminKit_message($temp . '<br />' . sprintf(esc_html__('Please include this message in your %sbug report%s.', $this->identifier), '<a title="' . sprintf(esc_attr__('Go to the %s support forum.', $this->identifier), $this->short_name) . '" href="' . $this->support_url . '">', '</a>'), 'info');
    519         }
    520         add_action('admin_notices', array($this, 'messages'));
    521     }
    522 
    523     /**
    524      * Exports a XML options document
    525      */
    526     function opts_export() {
    527         //Do a nonce check, prevent malicious link/form problems 
    528         check_admin_referer($this->unique_prefix . '_admin_import_export');
    529         //Update our internal settings
    530         $this->opt = $this->get_option($this->unique_prefix . '_options');
    531         //Create a DOM document
    532         $dom = new DOMDocument('1.0', 'UTF-8');
    533         //Adds in newlines and tabs to the output
    534         $dom->formatOutput = true;
    535         //We're not using a DTD therefore we need to specify it as a standalone document
    536         $dom->xmlStandalone = true;
    537         //Add an element called options
    538         $node = $dom->createElement('options');
    539         $parnode = $dom->appendChild($node);
    540         //Add a child element named plugin
    541         $node = $dom->createElement('plugin');
    542         $plugnode = $parnode->appendChild($node);
    543         //Add some attributes that identify the plugin and version for the options export
    544         $plugnode->setAttribute('name', $this->short_name);
    545         $plugnode->setAttribute('version', $this::version);
    546         //Change our headder to text/xml for direct save
    547         header('Cache-Control: public');
    548         //The next two will cause good browsers to download instead of displaying the file
    549         header('Content-Description: File Transfer');
    550         header('Content-disposition: attachemnt; filename=' . $this->unique_prefix . '_settings.xml');
    551         header('Content-Type: text/xml');
    552         //Loop through the options array
    553         foreach ($this->opt as $key => $option) {
    554             //Add a option tag under the options tag, store the option value
    555             $node = $dom->createElement('option', htmlentities($option, ENT_COMPAT, 'UTF-8'));
    556             $newnode = $plugnode->appendChild($node);
    557             //Change the tag's name to that of the stored option
    558             $newnode->setAttribute('name', $key);
    559         }
    560         //Prepair the XML for output
    561         $output = $dom->saveXML();
    562         //Let the browser know how long the file is
    563         header('Content-Length: ' . strlen($output)); // binary length
    564         //Output the file
    565         printf ($output);
    566         //Prevent WordPress from continuing on
    567         die();
    568     }
    569 
    570     /**
    571      * Imports a XML options document
    572      */
    573     function opts_import() {
    574 
    575         //Our quick and dirty error supressor
    576         function error($errno, $errstr, $eerfile, $errline) {
    577             return true;
    578         }
    579 
    580         //Do a nonce check, prevent malicious link/form problems
    581         check_admin_referer($this->unique_prefix . '_admin_import_export');
    582         //Set the backup options in the DB to the current options
    583         $this->opts_backup();
    584         //Create a DOM document
    585         $dom = new DOMDocument('1.0', 'UTF-8');
    586         //We want to catch errors ourselves
    587         set_error_handler('error');
    588         //Load the user uploaded file, handle failure gracefully
    589         if ($dom->load($_FILES[$this->unique_prefix . '_admin_import_file']['tmp_name'])) {
    590             $opts_temp = array();
    591             $version = '';
    592             //Have to use an xpath query otherwise we run into problems
    593             $xpath = new DOMXPath($dom);
    594             $option_sets = $xpath->query('plugin');
    595             //Loop through all of the xpath query results
    596             foreach ($option_sets as $options) {
    597                 //We only want to import options for only this plugin
    598                 if ($options->getAttribute('name') === $this->short_name) {
    599                     //Grab the file version
    600                     $version = $options->getAttribute('version');
    601                     //Loop around all of the options
    602                     foreach ($options->getelementsByTagName('option') as $child) {
    603                         //Place the option into the option array, DOMDocument decodes html entities for us
    604                         $opts_temp[$child->getAttribute('name')] = $child->nodeValue;
    605                     }
    606                 }
    607             }
    608             //Make sure we safely import and upgrade settings if needed
    609             $this->opts_upgrade($opts_temp, $version);
    610             //Commit the loaded options to the database
    611             $this->update_option($this->unique_prefix . '_options', $this->opt);
    612             //Everything was successful, let the user know
    613             $this->messages[] = new mtekk_adminKit_message(esc_html__('Settings successfully imported from the uploaded file.', $this->identifier)
    614                     . $this->admin_anchor('undo', __('Undo the options import.', $this->identifier), __('Undo', $this->identifier)), 'success');
    615         } else {
    616             //Throw an error since we could not load the file for various reasons
    617             $this->messages[] = new mtekk_adminKit_message(esc_html__('Importing settings from file failed.', $this->identifier), 'error');
    618         }
    619         //Reset to the default error handler after we're done
    620         restore_error_handler();
    621         //Output any messages that there may be
    622         add_action('admin_notices', array($this, 'messages'));
    623     }
    624 
    625     /**
    626      * Resets the database settings array to the default set in opt
    627      */
    628     function opts_reset() {
    629         //Do a nonce check, prevent malicious link/form problems
    630         check_admin_referer($this->unique_prefix . '_admin_import_export');
    631         //Set the backup options in the DB to the current options
    632         $this->opts_backup();
    633         //Load in the hard coded default option values
    634         $this->update_option($this->unique_prefix . '_options', $this->opt);
    635         //Reset successful, let the user know
    636         $this->messages[] = new mtekk_adminKit_message(esc_html__('Settings successfully reset to the default values.', $this->identifier)
    637                 . $this->admin_anchor('undo', __('Undo the options reset.', $this->identifier), __('Undo', $this->identifier)), 'success');
    638         add_action('admin_notices', array($this, 'messages'));
    639     }
    640 
    641     /**
    642      * Undos the last settings save/reset/import
    643      */
    644     function opts_undo() {
    645         //Do a nonce check, prevent malicious link/form problems
    646         check_admin_referer($this->unique_prefix . '_admin_undo');
    647         //Set the options array to the current options
    648         $opt = $this->get_option($this->unique_prefix . '_options');
    649         //Set the options in the DB to the backup options
    650         $this->update_option($this->unique_prefix . '_options', $this->get_option($this->unique_prefix . '_options_bk'));
    651         //Set the backup options to the undone options
    652         $this->update_option($this->unique_prefix . '_options_bk', $opt);
    653         //Send the success/undo message
    654         $this->messages[] = new mtekk_adminKit_message(esc_html__('Settings successfully undid the last operation.', $this->identifier)
    655                 . $this->admin_anchor('undo', __('Undo the last undo operation.', $this->identifier), __('Undo', $this->identifier)), 'success');
    656         add_action('admin_notices', array($this, 'messages'));
    657     }
    658 
    659     /**
    660      * Upgrades input options array, sets to $this->opt, designed to be overwritten
    661      * 
    662      * @param array $opts
    663      * @param string $version the version of the passed in options
    664      */
    665     function opts_upgrade($opts, $version) {
    666         //We don't support using newer versioned option files in older releases
    667         if (version_compare($this::version, $version, '>=')) {
    668             $this->opt = $opts;
    669         }
    670     }
    671 
    672     /**
    673      * Forces a database settings upgrade
    674      */
    675     function opts_upgrade_wrapper() {
    676         //Do a nonce check, prevent malicious link/form problems
    677         check_admin_referer($this->unique_prefix . '_admin_upgrade');
    678         //Grab the database options
    679         $opts = $this->get_option($this->unique_prefix . '_options');
    680         if (is_array($opts)) {
    681             //Feed the just read options into the upgrade function
    682             $this->opts_upgrade($opts, $this->get_option($this->unique_prefix . '_version'));
    683             //Always have to update the version
    684             $this->update_option($this->unique_prefix . '_version', $this::version);
    685             //Store the options
    686             $this->update_option($this->unique_prefix . '_options', $this->opt);
    687             //Send the success message
    688             $this->messages[] = new mtekk_adminKit_message(esc_html__('Settings successfully migrated.', $this->identifier), 'success');
    689         } else {
    690             //Run the install script
    691             $this->install();
    692             //Send the success message
    693             $this->messages[] = new mtekk_adminKit_message(esc_html__('Default settings successfully installed.', $this->identifier), 'success');
    694         }
    695         add_action('admin_notices', array($this, 'messages'));
    696     }
    697 
    698     /**
    699      * help action hook function, meant to be overridden
    700      * 
    701      * @return string
    702      * 
    703      */
    704     function help() {
    705         $screen = get_current_screen();
    706         //Add contextual help on current screen
    707         if ($screen->id == 'settings_page_' . $this->identifier) {
    708             
    709         }
    710     }
    711 
    712     function dismiss_message() {
    713         //Grab the submitted UID
    714         $uid = esc_attr($_POST['uid']);
    715         //Create a dummy message, with the discovered UID
    716         $message = new mtekk_adminKit_message('', '', true, $uid);
    717         //Dismiss the message
    718         $message->dismiss();
    719         wp_die();
    720     }
    721 
    722     /**
    723      * Prints to screen all of the messages stored in the message member variable
    724      */
    725     function messages() {
    726         foreach ($this->messages as $message) {
    727             $message->render();
    728         }
    729         //Old deprecated messages
    730         if (count($this->message)) {
    731             _deprecated_function(__FUNCTION__, '2.0.0', __('adminKit::message is deprecated, use new adminkit_messages instead.', $this->identifier));
    732             //Loop through our message classes
    733             foreach ($this->message as $key => $class) {
    734                 //Loop through the messages in the current class
    735                 foreach ($class as $message) {
    736                     printf('<div class="%s"><p>%s</p></div>', esc_attr($key), $message);
    737                 }
    738             }
    739             $this->message = array();
    740         }
    741         $this->messages = array();
    742     }
    743 
    744     /**
    745      * Function prototype to prevent errors
    746      */
    747     function admin_styles() {
    748         
    749     }
    750 
    751     /**
    752      * Function prototype to prevent errors
    753      */
    754     function admin_scripts() {
    755         
    756     }
    757 
    758     /**
    759      * Function prototype to prevent errors
    760      */
    761     function admin_head() {
    762         
    763     }
    764 
    765     /**
    766      * Function prototype to prevent errors
    767      */
    768     function admin_page() {
    769         
    770     }
    771 
    772     /**
    773      * Function prototype to prevent errors
    774      */
    775     protected function _get_help_text() {
    776         
    777     }
    778 
    779     /**
    780      * Returns a valid xHTML element ID
    781      *
    782      * @param object $option
    783      */
    784     static public function get_valid_id($option) {
    785         if (is_numeric($option[0])) {
    786             return 'p' . $option;
    787         } else {
    788             return $option;
    789         }
    790     }
    791 
    792     function import_form() {
    793         $form = '<div id="mtekk_admin_import_export_relocate">';
    794         $form .= sprintf('<form action="options-general.php?page=%s" method="post" enctype="multipart/form-data" id="%s_admin_upload">', esc_attr($this->identifier), esc_attr($this->unique_prefix));
    795         $form .= wp_nonce_field($this->unique_prefix . '_admin_import_export', '_wpnonce', true, false);
    796         $form .= sprintf('<fieldset id="import_export" class="%s_options">', esc_attr($this->unique_prefix));
    797         $form .= '<p>' . esc_html__('Import settings from a XML file, export the current settings to a XML file, or reset to the default settings.', $this->identifier) . '</p>';
    798         $form .= '<table class="form-table"><tr valign="top"><th scope="row">';
    799         $form .= sprintf('<label for="%s_admin_import_file">', esc_attr($this->unique_prefix));
    800         $form .= esc_html__('Settings File', $this->identifier);
    801         $form .= '</label></th><td>';
    802         $form .= sprintf('<input type="file" name="%1$s_admin_import_file" id="%1$s_admin_import_file" size="32" /><p class="description">', esc_attr($this->unique_prefix));
    803         $form .= esc_html__('Select a XML settings file to upload and import settings from.', 'breadcrumb_navxt');
    804         $form .= '</p></td></tr></table><p class="submit">';
    805         $form .= sprintf('<input type="submit" class="button" name="%1$s_admin_import" value="%2$s"/>', $this->unique_prefix, esc_attr__('Import', $this->identifier));
    806         $form .= sprintf('<input type="submit" class="button" name="%1$s_admin_export" value="%2$s"/>', $this->unique_prefix, esc_attr__('Export', $this->identifier));
    807         $form .= sprintf('<input type="submit" class="button" name="%1$s_admin_reset" value="%2$s"/>', $this->unique_prefix, esc_attr__('Reset', $this->identifier));
    808         $form .= '</p></fieldset></form></div>';
    809         return $form;
    810     }
    811 
    812     /**
    813      * This will output a well formed hidden option
    814      *
    815      * @param string $option
    816      */
    817     function input_hidden($option) {
    818         $opt_id = mtekk_adminKit::get_valid_id($option);
    819         $opt_name = $this->unique_prefix . '_options[' . $option . ']';
    820         printf('<input type="hidden" name="%1$s" id="%2$s" value="%3$s" />', esc_attr($opt_name), esc_attr($opt_id), esc_attr($this->opt[$option]));
    821     }
    822 
    823     /**
    824      * This will output a well formed option label
    825      *
    826      * @param string $opt_id
    827      * @param string $label
    828      */
    829     function label($opt_id, $label) {
    830         printf('<label for="%1$s">%2$s</label>', esc_attr($opt_id), $label);
    831     }
    832 
    833     /**
    834      * This will output a well formed table row for a text input
    835      *
    836      * @param string $label
    837      * @param string $option
    838      * @param string $class (optional)
    839      * @param bool $disable (optional)
    840      * @param string $description (optional)
    841      */
    842     function input_text($label, $option, $class = 'regular-text', $disable = false, $description = '') {
    843         $opt_id = mtekk_adminKit::get_valid_id($option);
    844         $opt_name = $this->unique_prefix . '_options[' . $option . ']';
    845         if ($disable) {
    846             $this->input_hidden($option);
    847             $class .= ' disabled';
    848         }
    849         ?>
    850         <tr valign="top">
    851             <th scope="row">
    852                 <?php $this->label($opt_id, $label); ?>
    853             </th>
    854             <td>
    855                 <?php printf('<input type="text" name="%1$s" id="%2$s" value="%3$s" class="%4$s" %5$s/><br />', esc_attr($opt_name), esc_attr($opt_id), esc_attr($this->opt[$option]), esc_attr($class), disabled($disable, true, false)); ?>
    856                 <?php if ($description !== '') { ?><p class="description"><?php printf ($description); ?></p><?php } ?>
    857             </td>
    858         </tr>
    859         <?php
    860     }
    861 
    862     /**
    863      * This will output a well formed table row for a HTML5 number input
    864      *
    865      * @param string $label
    866      * @param string $option
    867      * @param string $class (optional)
    868      * @param bool $disable (optional)
    869      * @param string $description (optional)
    870      * @param int|string $min (optional) 
    871      * @param int|string $max (optional)
    872      * @param int|string $step (optional)
    873      */
    874     function input_number($label, $option, $class = 'small-text', $disable = false, $description = '', $min = '', $max = '', $step = '') {
    875         $opt_id = mtekk_adminKit::get_valid_id($option);
    876         $opt_name = $this->unique_prefix . '_options[' . $option . ']';
    877         $extras = '';
    878         if ($min !== '') {
    879             $extras .= 'min="' . esc_attr($min) . '" ';
    880         }
    881         if ($max !== '') {
    882             $extras .= 'max="' . esc_attr($max) . '" ';
    883         }
    884         if ($step !== '') {
    885             $extras .= 'step="' . esc_attr($step) . '" ';
    886         }
    887         if ($disable) {
    888             $this->input_hidden($option);
    889             $class .= ' disabled';
    890         }
    891         ?>
    892         <tr valign="top">
    893             <th scope="row">
    894                 <?php $this->label($opt_id, $label); ?>
    895             </th>
    896             <td>
    897                 <?php printf('<input type="number" name="%1$s" id="%2$s" value="%3$s" class="%4$s" %6$s%5$s/><br />', esc_attr($opt_name), esc_attr($opt_id), esc_attr($this->opt[$option]), esc_attr($class), disabled($disable, true, false), $extras); ?>
    898                 <?php if ($description !== '') { ?><p class="description"><?php printf ($description); ?></p><?php } ?>
    899             </td>
    900         </tr>
    901         <?php
    902     }
    903 
    904     /**
    905      * This will output a well formed textbox
    906      *
    907      * @param string $label
    908      * @param string $option
    909      * @param string $rows (optional)
    910      * @param bool $disable (optional)
    911      * @param string $description (optional)
    912      */
    913     function textbox($label, $option, $height = '3', $disable = false, $description = '', $class = '') {
    914         $opt_id = mtekk_adminKit::get_valid_id($option);
    915         $opt_name = $this->unique_prefix . '_options[' . $option . ']';
    916         $class .= ' large-text';
    917         if ($disable) {
    918             $this->input_hidden($option);
    919             $class .= ' disabled';
    920         }
    921         ?>
    922         <tr valign="top">
    923             <th scope="row">
    924                 <?php $this->label($opt_id, $label); ?>
    925             </th>
    926             <td>
    927                 <?php printf('<textarea rows="%6$s" name="%1$s" id="%2$s" class="%4$s" %5$s/>%3$s</textarea><br />', esc_attr($opt_name), esc_attr($opt_id), esc_textarea($this->opt[$option]), esc_attr($class), disabled($disable, true, false), esc_attr($height)); ?>
    928                 <?php if ($description !== '') { ?><p class="description"><?php printf ($description); ?></p><?php } ?>
    929             </td>
    930         </tr>
    931         <?php
    932     }
    933 
    934     /**
    935      * This will output a well formed tiny mce ready textbox
    936      *
    937      * @param string $label
    938      * @param string $option
    939      * @param string $rows (optional)
    940      * @param bool $disable (optional)
    941      * @param string $description (optional)
    942      */
    943     function tinymce($label, $option, $height = '3', $disable = false, $description = '') {
    944         $opt_id = mtekk_adminKit::get_valid_id($option);
    945         $class = 'mtekk_mce';
    946         if ($disable) {
    947             $this->input_hidden($option);
    948             $class .= ' disabled';
    949         }
    950         ?>
    951         <tr valign="top">
    952             <th scope="row">
    953                 <?php $this->label($opt_id, $label); ?>
    954             </th>
    955             <td>
    956                 <?php printf('<textarea rows="%6$s" name="%1$s" id="%2$s" class="%4$s" %5$s/>%3$s</textarea><br />', esc_attr($opt_name), esc_attr($opt_id), esc_textarea($this->opt[$option]), esc_attr($class), disabled($disable, true, false), esc_attr($height)); ?>
    957                 <?php if ($description !== '') { ?><p class="description"><?php printf ($description); ?></p><?php } ?>
    958             </td>
    959         </tr>
    960         <?php
    961     }
    962 
    963     /**
    964      * This will output a well formed table row for a checkbox input
    965      *
    966      * @param string $label
    967      * @param string $option
    968      * @param string $instruction
    969      * @param bool $disable (optional)
    970      * @param string $description (optional)
    971      * @param string $class (optional)
    972      */
    973     function input_check($label, $option, $instruction, $disable = false, $description = '', $class = '') {
    974         $opt_id = mtekk_adminKit::get_valid_id($option);
    975         $opt_name = $this->unique_prefix . '_options[' . $option . ']';
    976         if ($disable) {
    977             $this->input_hidden($option);
    978             $class .= ' disabled';
    979         }
    980         ?>
    981         <tr valign="top">
    982             <th scope="row">
    983                 <?php $this->label($opt_id, $label); ?>
    984             </th>
    985             <td>	
    986                 <label>
    987                     <?php printf('<input type="checkbox" name="%1$s" id="%2$s" value="%3$s" class="%4$s" %5$s %6$s/>', esc_attr($opt_name), esc_attr($opt_id), esc_attr($this->opt[$option]), esc_attr($class), disabled($disable, true, false), checked($this->opt[$option], true, false)); ?>
    988                     <?php printf ($instruction); ?>
    989                 </label><br />
    990                 <?php if ($description !== '') { ?><p class="description"><?php printf ($description); ?></p><?php } ?>
    991             </td>
    992         </tr>
    993         <?php
    994     }
    995 
    996     /**
    997      * This will output a singular radio type form input field
    998      *
    999      * @param string $option
   1000      * @param string $value
   1001      * @param string $instruction
   1002      * @param object $disable (optional)
   1003      * @param string $class (optional)
   1004      */
   1005     function input_radio($option, $value, $instruction, $disable = false, $class = '') {
   1006         $opt_id = mtekk_adminKit::get_valid_id($option);
   1007         $opt_name = $this->unique_prefix . '_options[' . $option . ']';
   1008         $class .= ' togx';
   1009         if ($disable) {
   1010             $this->input_hidden($option);
   1011             $class .= ' disabled';
   1012         }
   1013         ?>
   1014         <label>
   1015             <?php printf('<input type="radio" name="%1$s" id="%2$s" value="%3$s" class="%4$s" %5$s %6$s/>', esc_attr($opt_name), esc_attr($opt_id), esc_attr($value), esc_attr($class), disabled($disable, true, false), checked($value, $this->opt[$option], false)); ?>
   1016             <?php printf ($instruction); ?>
   1017         </label><br/>
   1018         <?php
   1019     }
   1020 
   1021     /**
   1022      * This will output a well formed table row for a select input
   1023      *
   1024      * @param string $label
   1025      * @param string $option
   1026      * @param array $values
   1027      * @param bool $disable (optional)
   1028      * @param string $description (optional)
   1029      * @param array $titles (optional) The array of titiles for the options, if they should be different from the values
   1030      * @param string $class (optional) Extra class to apply to the elements
   1031      */
   1032     function input_select($label, $option, $values, $disable = false, $description = '', $titles = false, $class = '') {
   1033         //If we don't have titles passed in, we'll use option names as values
   1034         if (!$titles) {
   1035             $titles = $values;
   1036         }
   1037         $opt_id = mtekk_adminKit::get_valid_id($option);
   1038         $opt_name = $this->unique_prefix . '_options[' . $option . ']';
   1039         if ($disable) {
   1040             $this->input_hidden($option);
   1041             $class .= ' disabled';
   1042         }
   1043         ?>
   1044         <tr valign="top">
   1045             <th scope="row">
   1046                 <?php $this->label($opt_id, $label); ?>
   1047             </th>
   1048             <td>
   1049                 <?php printf('<select name="%1$s" id="%2$s" class="%4$s" %5$s>%3$s</select><br />', esc_attr($opt_name), esc_attr($opt_id), $this->select_options($option, $titles, $values), esc_attr($class), disabled($disable, true, false)); ?>
   1050                 <?php if ($description !== '') { ?><p class="description"><?php printf ($description); ?></p><?php } ?>
   1051             </td>
   1052         </tr>
   1053         <?php
   1054     }
   1055 
   1056     /**
   1057      * Displays wordpress options as <seclect>
   1058      *
   1059      * @param string $optionname name of wordpress options store
   1060      * @param array $options array of names of options that can be selected
   1061      * @param array $values array of the values of the options that can be selected
   1062      * @param array $exclude(optional) array of names in $options array to be excluded
   1063      * 
   1064      * @return string The assembled HTML for the select options
   1065      */
   1066     function select_options($optionname, $options, $values, $exclude = array()) {
   1067         $options_html = '';
   1068         $value = $this->opt[$optionname];
   1069         //Now do the rest
   1070         foreach ($options as $key => $option) {
   1071             if (!in_array($option, $exclude)) {
   1072                 $options_html .= sprintf('<option value="%1$s" %2$s>%3$s</option>', esc_attr($values[$key]), selected($value, $values[$key], false), $option);
   1073             }
   1074         }
   1075         return $options_html;
   1076     }
   1077 
   1078     /**
   1079      * A local pass through for get_option so that we can hook in and pick the correct method if needed
   1080      *
   1081      * @param string $option The name of the option to retrieve
   1082      * @return mixed The value of the option
   1083      */
   1084     function get_option($option) {
   1085         return get_option($option);
   1086     }
   1087 
   1088     /**
   1089      * A local pass through for update_option so that we can hook in and pick the correct method if needed
   1090      *
   1091      * @param string $option The name of the option to update
   1092      * @param mixed $newvalue The new value to set the option to
   1093      */
   1094     function update_option($option, $newvalue) {
   1095         return update_option($option, $newvalue);
   1096     }
   1097 
   1098     /**
   1099      * A local pass through for add_option so that we can hook in and pick the correct method if needed
   1100      *
   1101      * @param string $option The name of the option to update
   1102      * @param mixed $value The new value to set the option to
   1103      * @param null $deprecated Deprecated parameter
   1104      * @param string $autoload Whether or not to autoload the option, it's a string because WP is special
   1105      */
   1106     function add_option($option, $value = '', $deprecated = '', $autoload = 'yes') {
   1107         return add_option($option, $value, null, $autoload);
   1108     }
   1109 
   1110     /**
   1111      * A local pass through for delete_option so that we can hook in and pick the correct method if needed
   1112      *
   1113      * @param string $option The name of the option to delete
   1114      */
   1115     function delete_option($option) {
   1116         return delete_option($option);
   1117     }
   1118 
   1119 }