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 }