PluginProbe
Radio Station by netmix® – Manage and play your Show Schedule in WordPress! / 2.7.1
Radio Station by netmix® – Manage and play your Show Schedule in WordPress! v2.7.1
2.7.1 trunk 2.2.6 2.2.7 2.2.8 2.3.0 2.3.1 2.3.2 2.3.3 2.3.3.1 2.3.3.2 2.3.3.3 2.3.3.4 2.3.3.5 2.3.3.6 2.3.3.7 2.3.3.8 2.3.3.9 2.4.0 2.4.0.3 2.4.0.4 2.4.0.5 2.4.0.6 2.4.0.7 2.4.0.8 All 44 releases
radio-station / loader.php

loader.php in Radio Station by netmix® – Manage and play your Show Schedule in WordPress! 2.7.1, at loader.php

4,140 lines 150.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 // =================================
4 // === Plugin Panel Loader Class ===
5 // =================================
6
7 // -------------
8 // Loader v1.3.8
9 // -------------
10 // Note: Changelog at end of file.
11
12 if ( !defined( 'ABSPATH' ) ) exit;
13
14 // === Loader Class ===
15 // - Initialize Loader
16 // - Setup Plugin
17 // - Get Plugin Data
18 // - Get Plugin Version
19 // - Set Pro Namespace
20 // === Plugin Settings ===
21 // - Get Default Settings
22 // - Add Settings
23 // - Maybe Transfer Settings
24 // - Get All Plugin Settings
25 // - Get Plugin Setting
26 // - Reset Plugin Settings
27 // - Update Plugin Settings
28 // - Validate Plugin Setting
29 // === Plugin Loading ===
30 // - Load Plugin Settings
31 // - Add Actions
32 // - Load Helper Libraries
33 // - Maybe Load Thickbox
34 // - Readme Viewer AJAX
35 // === Freemius Loading ===
36 // - Load Freemius
37 // - Filter Freemius Connect
38 // - Freemius Connect Message
39 // - Connect Update Message
40 // === Plugin Admin ===
41 // - Add Settings Menu
42 // - Plugin Page Links
43 // - Message Box
44 // - Notice Boxer
45 // - Plugin Page Header
46 // - Settings Page
47 // - Settings Table
48 // - Setting Row
49 // - Settings Scripts
50 // - Settings Styles
51 // === Namespaced Functions ===
52
53
54 // ============
55 // Loader Usage
56 // ============
57 // 1. replace all occurrences of radio_station_ in this file with the plugin namespace prefix eg. my_plugin_
58 // 2. replace all occurrences of 'radio-station' in this file with the plugin's translation text domain
59 // 2. define plugin options, default settings, and setup arguments your main plugin file
60 // 3. require this file in the main plugin file and instantiate the loader class (see example below)
61 //
62 // ---------------
63 // Updating Loader
64 // ---------------
65 // Repeat Step 1 above with the new version of loader.php and replace in your plugin.
66 //
67 // -----------------------------------
68 // Example Plugin Options and Defaults
69 // -----------------------------------
70 // array of plugin option keys, with input types and defaults
71 // $options = array(
72 // 'optionkey1' => array(
73 // 'type' => 'checkbox',
74 // 'default' => '1',
75 // 'value' => '1',
76 // ),
77 // 'optionkey2' => array(
78 // 'type' => 'radio',
79 // 'default' => 'on',
80 // 'options' => 'on/off',
81 // ),
82 // 'optionkey3' => array(
83 // 'type' => 'special',
84 // ),
85 // );
86
87 // -----------------------
88 // Example Plugin Settings
89 // -----------------------
90 // $slug = 'plugin-name'; // plugin slug (usually same as filename)
91 // $args = array(
92 // // --- Plugin Info ---
93 // 'slug' => $slug, // (uses slug above)
94 // 'file' => __FILE__, // path to main plugin file (required!)
95 // 'version' => '0.0.1', // * rechecked later (pulled from plugin header) *
96 //
97 // // --- Menus and Links ---
98 // 'title' => 'Plugin Name', // plugin title
99 // 'parentmenu' => 'wordquest', // parent menu slug
100 // 'home' => 'http://mysite.com/plugins/plugin/',
101 // 'support' => 'http://mysite.com/plugins/plugin/support/',
102 // 'ratetext' => __( 'Rate on WordPress.org', 'radio-station' ), // (overrides default rate text)
103 // 'share' => 'http://mysites.com/plugins/plugin/#share', // (set sharing URL)
104 // 'sharetext' => __( 'Share the Plugin Love', 'radio-station' ), // (overrides default sharing text)
105 // 'donate' => 'https://patreon.com/pagename', // (overrides plugin Donate URI)
106 // 'donatetext' => __( 'Support this Plugin', 'radio-station' ), // (overrides default donate text)
107 // 'readme' => false, // to not link to popup readme in settings page header
108 // 'settingsmenu' => false, // to not automatically add a settings menu [non-WQ]
109 //
110 // // --- Options ---
111 // 'namespace' => 'PLUGIN_PREFIX', // plugin namespace (function prefix, minus trailing _)
112 // 'settings' => 'pn', // input settings prefix
113 // 'option' => 'plugin_key', // plugin option key
114 // 'options' => $options, // plugin options array set above
115 //
116 // // --- WordPress.Org ---
117 // 'wporgslug' => 'plugin-slug', // WordPress.org plugin slug
118 // 'wporg' => false, // * rechecked later (via presence of updatechecker.php) *
119 // 'textdomain' => 'plugin-domain', // translation text domain (usually same as plugin slug)
120 //
121 // // --- Freemius ---
122 // 'freemius_id' => '', // Freemius plugin ID
123 // 'freemius_key' => '', // Freemius public key
124 // 'hasplans' => false, // if plugin has paid plans
125 // 'hasaddons' => false, // if plugin has add ons
126 // 'plan' => 'free', // * rechecked later (if premium version found) *
127 // );
128
129 // ------------------------------------
130 // Example Start Plugin Loader Instance
131 // ------------------------------------
132 // (add this to your main plugin file to run this loader)
133 // require(dirname(__FILE__).'/loader.php'); // requires this file!
134 // $instance = new radio_station_loader($args); // instantiates loader class
135 // (ie. search and replace all 'radio_station_' with 'my_plugin_' function namespace)
136 // and then search and replace 'text-domain' with your plugin's text domain.
137
138
139 // ---------------------------------------------
140 // 1.3.7: Translated String Settings Update Note
141 // ---------------------------------------------
142 // Since loader is typically initiated directly within a plugin, this means string translations are "too early".
143 // A little bit of an annoying WordPress quirk to have to get around but it is achieved with the following steps:
144
145 // 1. Fix is to remove the translated strings (ratetext, sharetext, donatetext) to a later filter added for this purpose:
146 /*
147 add_filter( 'radio_station_admin_args', 'radio_station_settings_texts' );
148 function radio_station_settings_texts( $args ) {
149 $texts = array(
150 'sharetext' => __( 'Share the Plugin Love', 'radio-station' ),
151 'ratetext' => __( 'Rate on WordPress.org', 'radio-station' ),
152 'donatetext' => __( 'Support this Plugin', 'radio-station' ),
153 );
154 $args = array_merge( $args, $texts );
155 return $args;
156 } */
157
158 // 2. Instead of passing options directly to the loader, create a function with an admin argument.
159 // eg. function radio_station_get_options( $admin ) {}
160 // and translate any labels or helpers values conditionally using an inline if statement, ie. ? and :
161 // 'label' => __( 'Label Test', 'radio-station' ),
162 // becomes
163 // 'label' => $admin ? __( 'Label Test', 'radio-station' ) : '',
164
165 // 3. Then add a filter that runs after the main plugin loader is instantiated:
166 /*
167 add_filter( 'radio_station_plugin_options', 'radio_station_get_options' );
168 add_action( 'plugins_loaded', 'radio_station_admin_options_init' );
169 function radio_station_admin_options_init() {
170 add_filter( 'radio-station_options', 'radio_station_admin_options' );
171 function radio_station_admin_options( $options ) {
172 $admin = is_admin();
173 $options = radio_station_get_options( $admin );
174 return $options;
175 }
176 }
177 */
178
179
180 // ===========================
181 // --- Plugin Loader Class ---
182 // ===========================
183 // usage: change class prefix to the plugin function prefix
184 if ( !class_exists( 'radio_station_loader' ) ) {
185 // phpcs:ignore PEAR.NamingConventions.ValidClassName.Invalid,PEAR.NamingConventions.ValidClassName.StartWithCapital
186 class radio_station_loader {
187
188 public $args = null;
189 public $namespace = null;
190 public $data = null;
191 public $version = null;
192
193 public $options = null;
194 public $defaults = null;
195 public $tabs = array();
196 public $sections = array();
197 public $scripts = array();
198
199 // 1.1.2: added menu added switch
200 // 1.1.2: added debug switch
201 public $menu_added = false;
202 public $debug = false;
203
204 // -----------------
205 // Initialize Loader
206 // -----------------
207 public function __construct( $args ) {
208
209 if ( !is_array( $args ) ) {
210 return;
211 }
212
213 // --- set debug switch ---
214 // 1.1.2: added debug switch check
215 $prefix = '';
216 if ( $args['settings'] ) {
217 // 1.1.4: fix to debug prefix key
218 $prefix = $args['settings'] . '-';
219 }
220 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
221 if ( isset( $_REQUEST[$prefix . 'debug'] ) ) {
222 // 1.2.5: use sanitize_text_field on request variable
223 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
224 if ( in_array( sanitize_text_field( $_REQUEST[$prefix . 'debug'] ), array( '1', 'yes' ) ) ) {
225 $this->debug = true;
226 }
227 }
228
229 // --- set plugin options ---
230 // 1.0.6: added options filter
231 // 1.3.8: fix to set class options on construct
232 $args['options'] = apply_filters( $args['namespace'] . '_options', $args['options'] );
233 $this->options = $args['options'];
234
235 // --- set plugin args and namespace ---
236 // 1.1.9: filter all arguments
237 $args = apply_filters( $args['namespace'] . '_args', $args );
238 $this->args = $args;
239 $this->namespace = $args['namespace'];
240
241 // --- setup plugin values ---
242 $this->setup_plugin();
243
244 // --- maybe transfer settings ---
245 $this->maybe_transfer_settings();
246
247 // --- load settings ---
248 $this->load_settings();
249
250 // --- load actions ---
251 $this->add_actions();
252
253 // --- load helper libraries ---
254 $this->load_helpers();
255
256 // --- autoset class instance global for accessibility ---
257 $GLOBALS[$args['namespace'] . '_instance'] = $this;
258 }
259
260 // -------------
261 // Admin Options
262 // -------------
263 // 1.3.7: added method for loading delayed translation strings
264 function admin_options() {
265
266 $namespace = $this->namespace;
267 $args = $this->args;
268 $args = apply_filters( $args['namespace'] . '_admin_args', $args );
269 $this->args = $args;
270
271 $options = $this->options;
272 $options = apply_filters( $namespace . '_options', $options );
273
274 // 1.0.9: maybe get tabs and sections from options array
275 if ( isset( $options['tabs'] ) ) {
276 $this->tabs = $options['tabs'];
277 unset( $options['tabs'] );
278 }
279 if ( isset( $options['sections'] ) ) {
280 $this->sections = $options['sections'];
281 unset( $options['sections'] );
282 }
283 $this->options = $options;
284 }
285
286 // ------------
287 // Setup Plugin
288 // ------------
289 public function setup_plugin() {
290
291 $args = $this->args;
292 $namespace = $this->namespace;
293
294 // --- Read Plugin Header ---
295 if ( !isset( $args['dir'] ) ) {
296 $args['dir'] = dirname( $args['file'] );
297 }
298 // phpcs:ignore WordPress.WP.AlternativeFunctions
299 $fh = fopen( $args['file'], 'r' );
300 // phpcs:ignore WordPress.WP.AlternativeFunctions
301 $data = fread( $fh, 2048 );
302 $this->data = str_replace( "\r", "\n", $data );
303 // phpcs:ignore WordPress.WP.AlternativeFunctions
304 fclose( $fh );
305
306 // --- Version ---
307 $this->version = $args['version'] = $this->plugin_data( 'Version:' );
308
309 // --- Title ---
310 if ( !isset( $args['title'] ) ) {
311 $args['title'] = $this->plugin_data( 'Plugin Name:' );
312 }
313
314 // --- Plugin Home ---
315 if ( !isset( $args['home'] ) ) {
316 $args['home'] = $this->plugin_data( 'Plugin URI:' );
317 }
318
319 // --- Author ---
320 if ( !isset( $args['author'] ) ) {
321 $args['author'] = $this->plugin_data( 'Author:' );
322 }
323
324 // --- Author URL ---
325 if ( !isset( $args['author_url'] ) ) {
326 $args['author_url'] = $this->plugin_data( 'Author URI:' );
327 }
328
329 // --- Pro Functions ---
330 if ( !isset( $args['proslug'] ) ) {
331 $proslug = $this->plugin_data( '@fs_premium_only' );
332 // 1.3.0: check for pro slug string
333 if ( is_string( $proslug ) ) {
334 // 1.0.1: if more than one file, extract pro slug based on the first filename
335 if ( !strstr( $proslug, ',' ) ) {
336 $profiles = array( $proslug );
337 $proslug = trim( $proslug );
338 } else {
339 $profiles = explode( ',', $proslug );
340 $proslug = trim( $profiles[0] );
341 }
342 $args['proslug'] = substr( $proslug, 0, - 4 ); // strips .php extension
343 $args['profiles'] = $profiles;
344 }
345 }
346
347 // --- Update Loader Args ---
348 $this->args = $args;
349 }
350
351 // ---------------
352 // Get Plugin Data
353 // ---------------
354 public function plugin_data( $key ) {
355
356 $data = $this->data;
357 $value = null;
358 $pos = strpos( $data, $key );
359 if ( false !== $pos ) {
360 $pos = $pos + strlen( $key ) + 1;
361 $tmp = substr( $data, $pos );
362 $pos = strpos( $tmp, "\n" );
363 $value = trim( substr( $tmp, 0, $pos ) );
364 }
365
366 return $value;
367 }
368
369 // ------------------
370 // Get Plugin Version
371 // ------------------
372 // 1.1.2: added get plugin version function
373 public function plugin_version() {
374 $args = $this->args;
375 return $args['version'];
376 }
377
378 // -----------------
379 // Set Pro Namespace
380 // -----------------
381 public function pro_namespace( $pronamespace ) {
382 $this->args['pronamespace'] = $pronamespace;
383 }
384
385
386 // =======================
387 // --- Plugin Settings ---
388 // =======================
389
390 // --------------------
391 // Get Default Settings
392 // --------------------
393 public function default_settings( $dkey = false ) {
394
395 // --- return defaults if already set ---
396 $defaults = $this->defaults;
397 if ( !is_null( $defaults ) ) {
398 if ( $dkey && isset( $defaults[$dkey] ) ) {
399 return $defaults[$dkey];
400 }
401
402 return $defaults;
403 }
404
405 // --- filter and store the plugin default settings ---
406 // 1.1.2: fix to apply options filter
407 $namespace = $this->namespace;
408 $options = $this->options;
409 $options = apply_filters( $namespace . '_plugin_options', $options );
410 $defaults = array();
411 foreach ( $options as $key => $values ) {
412 // 1.0.9: set default to null if default value not set
413 if ( isset( $values['default'] ) ) {
414 $defaults[$key] = $values['default'];
415 } else {
416 $defaults[$key] = null;
417 }
418 }
419 $defaults = apply_filters( $namespace . '_default_settings', $defaults );
420 $this->defaults = $defaults;
421 if ( $dkey && isset( $defaults[$dkey] ) ) {
422 return $defaults[$dkey];
423 }
424
425 return $defaults;
426 }
427
428 // ------------
429 // Add Settings
430 // ------------
431 public function add_settings() {
432
433 // --- add the default plugin settings ---
434 $args = $this->args;
435 $defaults = $this->default_settings();
436 $added = add_option( $args['option'], $defaults );
437
438 // --- if newly added, make the defaults current settings ---
439 if ( $added ) {
440 $namespace = $this->namespace;
441 foreach ( $defaults as $key => $value ) {
442 $GLOBALS[$namespace][$key] = $value;
443 }
444
445 // --- record first installed version ---
446 // 1.1.0: added record for tracking first install version
447 add_option( $args['option'] . '_first_install', $args['version'] );
448 }
449
450 // 1.0.9: trigger add settings action
451 // 1.2.1: fix to namespace typo (nsmespace)
452 do_action( $args['namespace'] . '_add_settings', $args );
453 }
454
455 // -----------------------
456 // Maybe Transfer Settings
457 // -----------------------
458 public function maybe_transfer_settings() {
459 $namespace = $this->namespace;
460 $funcname = $namespace . '_transfer_settings';
461
462 // --- check for either function prefixed or class extended method ---
463 if ( method_exists( $this, 'transfer_settings' ) ) {
464 $this->transfer_settings();
465 } elseif ( function_exists( $funcname ) ) {
466 call_user_func( $funcname );
467 }
468 }
469
470 // -----------------------
471 // Get All Plugin Settings
472 // -----------------------
473 public function get_settings( $filter = true ) {
474 $namespace = $this->namespace;
475 $settings = $GLOBALS[$namespace];
476 if ( $filter ) {
477 // 1.0.8: only apply all settings filter if filter is true
478 $settings = apply_filters( $namespace . '_settings', $settings );
479 // 1.0.8: maybe apply all individual key value filters
480 foreach ( $settings as $key => $value ) {
481 $settings[$key] = apply_filters( $namespace . '_' . $key, $value );
482 }
483 }
484
485 return $settings;
486 }
487
488 // ------------------
489 // Get Plugin Setting
490 // ------------------
491 public function get_setting( $key, $filter = true ) {
492 $args = $this->args;
493 $namespace = $this->namespace;
494 $settings = $GLOBALS[$namespace];
495 $settings = apply_filters( $namespace . '_settings', $settings );
496
497 // --- maybe strip settings prefix ---
498 // 1.0.4: added for backwards compatibility
499 if ( substr( $key, 0, strlen( $args['settings'] ) ) == $args['settings'] ) {
500 $key = substr( $key, strlen( $args['settings'] ) + 1, strlen( $key ) );
501 }
502
503 // --- get plugin setting ---
504 // 1.1.4: fix for weird isset failing glitch
505 // if ( isset( $settings[$key] ) ) {
506 if ( array_key_exists( $key, $settings ) ) {
507 $value = $settings[$key];
508 } else {
509 $defaults = $this->default_settings();
510 // 1.1.4: fix for weird isset failing glitch
511 if ( array_key_exists( $key, $defaults ) ) {
512 $value = $defaults[$key];
513 } else {
514 $value = null;
515 }
516 }
517 if ( $filter ) {
518 $value = apply_filters( $namespace . '_' . $key, $value );
519 }
520
521 return $value;
522 }
523
524 // ---------------------
525 // Reset Plugin Settings
526 // ---------------------
527 public function reset_settings() {
528
529 $args = $this->args;
530 $namespace = $this->namespace;
531
532 // --- check reset triggers ---
533 // 1.0.2: fix to namespace key typo in isset check
534 // 1.0.3: only use namespace not settings key
535 // 1.0.9: check page is set and matches slug
536 if ( !isset( $_REQUEST['page'] ) ) {
537 return;
538 }
539 // 1.0.5: use sanitize_title on request variables
540 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
541 if ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) != $args['slug'] ) {
542 return;
543 }
544 if ( !isset( $_POST[$args['namespace'] . '_update_settings'] ) ) {
545 return;
546 }
547 // phpcs:ignore WordPress.Security.NonceVerification.Missing
548 if ( 'reset' != sanitize_text_field( wp_unslash( $_POST[$args['namespace'] . '_update_settings'] ) ) ) {
549 return;
550 }
551
552 // --- check reset permissions ---
553 $capability = apply_filters( $args['namespace'] . '_manage_options_capability', 'manage_options' );
554 if ( !current_user_can( $capability ) ) {
555 return;
556 }
557
558 // --- verify nonce ---
559 // $noncecheck = wp_verify_nonce( sanitize_text_field( $_POST['_wpnonce'] ), $args['slug'] . '_update_settings' );
560 check_admin_referer( $args['slug'] . '_update_settings' );
561
562 // --- reset plugin settings ---
563 $defaults = $this->default_settings();
564 $defaults['savetime'] = time();
565 update_option( $args['option'], $defaults );
566
567 // --- loop to remerge with settings global ---
568 foreach ( $defaults as $key => $value ) {
569 $GLOBALS[$namespace][$key] = $value;
570 }
571
572 // --- set settings reset message flag ---
573 $_GET['updated'] = 'reset';
574 }
575
576 // ----------------------
577 // Update Plugin Settings
578 // ----------------------
579 public function update_settings() {
580
581 $args = $this->args;
582 $namespace = $this->namespace;
583 $settings = $GLOBALS[$namespace];
584
585 // --- check update triggers ---
586 // 1.0.2: fix to namespace key typo in isset check
587 // 1.0.3: only use namespace not settings key
588 // 1.0.9: check page is set and matches slug
589 if ( !isset( $_REQUEST['page'] ) || ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) != $args['slug'] ) ) ) {
590 return;
591 }
592 $updatekey = $args['namespace'] . '_update_settings';
593 // phpcs:ignore WordPress.Security.NonceVerification.Missing
594 if ( !isset( $_POST[$updatekey] ) || ( 'yes' != sanitize_text_field( wp_unslash( $_POST[$updatekey] ) ) ) ) {
595 return;
596 }
597
598 // --- check update permissions ---
599 $capability = apply_filters( $namespace . '_manage_options_capability', 'manage_options' );
600 if ( !current_user_can( $capability ) ) {
601 return;
602 }
603
604 // --- verify nonce ---
605 // $noncecheck = wp_verify_nonce( sanitize_text_field( $_POST['_wpnonce'] ), $args['slug'] . '_update_settings' );
606 check_admin_referer( $args['slug'] . '_update_settings' );
607
608 // --- debug posted values ---
609 // 1.3.?: move debug output to after check_admin_referer
610 $this->debug_posted( $settings );
611
612 // --- get plugin options and default settings ---
613 // 1.0.9: allow filtering of plugin options (eg. for Pro/Add Ons)
614 $options = $this->options;
615 $options = apply_filters( $namespace . '_options', $options );
616 $defaults = $this->default_settings();
617
618 // --- maybe use custom method or function ---
619 $funcname = $namespace . '_process_settings';
620 if ( method_exists( $this, 'process_settings' ) ) {
621
622 // --- use class extended method if found ---
623 $settings = $this->process_settings();
624
625 } elseif ( function_exists( $funcname ) && is_callable( $funcname ) ) {
626
627 // --- use namespace prefixed function if found ---
628 $settings = call_user_func( $funcname );
629
630 } else {
631
632 // --- loop plugin options to get new settings ---
633 foreach ( $options as $key => $values ) {
634
635 // --- get option type and options ---
636 if ( isset( $values['type'] ) ) {
637 $type = $values['type'];
638 $valid = $validate_args = array();
639 if ( isset( $values['options'] ) ) {
640 $valid = $values['options'];
641 }
642
643 // --- get posted value ---
644 // 1.0.6: set null value for unchecked checkbox fix
645 // 1.2.5: moved get posted value to within each type with sanitization
646 $postkey = $args['settings'] . '_' . $key;
647 $newsettings = null;
648
649 // --- maybe validate special options ---
650 // 1.0.9: check for special options to prepare
651 if ( is_string( $valid ) ) {
652
653 // --- maybe get public post type slugs ---
654 if ( in_array( $valid, array( 'PUBLICTYPE', 'PUBLICTYPES' ) ) ) {
655 $valid = array();
656 if ( !isset( $public ) ) {
657 $cpts = array( 'page', 'post' );
658 $cptargs = array( 'public' => true, '_builtin' => false );
659 $cptlist = get_post_types( $cptargs, 'names', 'and' );
660 $public = array_merge( $cpts, $cptlist );
661 }
662 foreach ( $public as $cpt ) {
663 $valid[$cpt] = '';
664 }
665 }
666
667 // --- maybe get post type slugs ---
668 if ( in_array( $valid, array( 'POSTTYPE', 'POSTTYPES' ) ) ) {
669 $valid = array();
670 if ( !isset( $cpts ) ) {
671 $cpts = array( 'page', 'post' );
672 $cptargs = array( 'public' => true, '_builtin' => false );
673 $cptlist = get_post_types( $cptargs, 'names', 'and' );
674 $cpts = array_merge( $cpts, $cptlist );
675 }
676 foreach ( $cpts as $cpt ) {
677 $valid[$cpt] = '';
678 }
679 }
680
681 // --- maybe get all post type slugs ---
682 if ( in_array( $valid, array( 'ALLTYPE', 'ALLTYPES' ) ) ) {
683 $valid = array();
684 if ( !isset( $allcpts ) ) {
685 $cptargs = array( '_builtin' => false );
686 $allcpts = get_post_types( $cptargs, 'names', 'and' );
687 }
688 foreach ( $allcpts as $cpt ) {
689 $valid[$cpt] = '';
690 }
691 }
692 }
693
694 if ( $this->debug ) {
695 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
696 echo 'Saving Setting Key ' . esc_html( $key ) . ' (' . esc_html( $postkey ) . ')<br>' . "\n";
697 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
698 echo 'Type: ' . esc_html( $type ) . ' - Valid Options ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . '<br>' . "\n";
699 }
700
701 // --- sanitize value according to type ---
702 if ( strstr( $type, '/' ) ) {
703
704 // --- implicit radio / select ---
705 $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null;
706 $valid = explode( '/', $type );
707 if ( in_array( $posted, $valid ) ) {
708 $settings[$key] = $posted;
709 }
710
711 } elseif ( ( 'checkbox' == $type ) || ( 'toggle' == $type ) ) {
712
713 // --- checkbox / toggle ---
714 // 1.0.6: fix to new unchecked checkbox value
715 // 1.0.9: maybe validate to specified checkbox value
716 $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null;
717 if ( isset( $values['value'] ) ) {
718 $valid = array( $values['value'] );
719 } else {
720 $valid = array( 'yes', '1', 'checked', 'on' );
721 }
722 if ( in_array( $posted, $valid ) ) {
723 $settings[$key] = $posted;
724 } elseif ( is_null( $posted ) ) {
725 $settings[$key] = '';
726 }
727
728 } elseif ( 'textarea' == $type ) {
729
730 // --- text area ---
731 // 1.2.5: use sanitize_textarea_field with stripslashes
732 $posted = isset( $_POST[$postkey] ) ? sanitize_textarea_field( wp_unslash( $_POST[$postkey] ) ) : null;
733 // 1.3.0: move use of stripslashes to separate line
734 if ( !is_null( $posted ) ) {
735 $posted = stripslashes( $posted );
736 }
737 $settings[$key] = $posted;
738
739 } elseif ( 'text' == $type ) {
740
741 // --- text field (slug) ---
742 // 1.0.9: move text field sanitization to validation
743 $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null;
744 if ( !is_string( $valid ) ) {
745 $valid = 'TEXT';
746 }
747 $newsettings = $posted;
748
749 } elseif ( 'email' == $type ) {
750
751 // --- email field ---
752 // 1.3.0: added explicitly for email field type
753 $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null;
754 if ( !is_string( $valid ) ) {
755 $valid = 'EMAIL';
756 }
757 $newsettings = $posted;
758
759 } elseif ( ( 'number' == $type ) || ( 'numeric' == $type ) ) {
760
761 // --- number field value ---
762 // 1.0.9: added support for number step, minimum and maximum
763 $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null;
764 $newsettings = $posted;
765 $valid = 'NUMERIC';
766 if ( isset( $values['step'] ) ) {
767 $validate_args['step'] = $values['step'];
768 }
769 if ( isset( $values['min'] ) ) {
770 $validate_args['min'] = $values['min'];
771 }
772 if ( isset( $values['max'] ) ) {
773 $validate_args['max'] = $values['max'];
774 }
775
776 } elseif ( 'multicheck' == $type ) {
777
778 // --- process multicheck boxes ---
779 // 1.0.9: added multicheck input type
780 // note: needs defined options (but works with post types)
781 $posted = array();
782 foreach ( $valid as $option => $label ) {
783 $optionkey = $args['settings'] . '_' . $key . '-' . $option;
784 if ( isset( $_POST[$optionkey] ) ) {
785 // 1.1.2: check for value if specified
786 // 1.2.5: apply sanitize_text_field to posted value
787 if ( ( isset( $values['value'] ) && ( sanitize_text_field( wp_unslash( $_POST[$optionkey] ) ) == $values['value'] ) )
788 || ( !isset( $values['value'] ) && ( 'yes' == sanitize_text_field( wp_unslash( $_POST[$optionkey] ) ) ) ) ) {
789 // 1.1.0: fixed to save only array of key values
790 $posted[] = $option;
791 }
792 }
793 }
794 $settings[$key] = $posted;
795
796 } elseif ( 'csv' == $type ) {
797
798 // -- comma separated values ---
799 // 1.0.4: added comma separated values option
800 $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null;
801 if ( strstr( $posted, ',' ) ) {
802 $posted = explode( ',', $posted );
803 } else {
804 // 1.2.8: fix to convert string to array
805 $posted = array( $posted );
806 }
807 foreach ( $posted as $i => $value ) {
808 $posted[$i] = trim( $value );
809 }
810 if ( is_string( $valid ) ) {
811 $newsettings = $posted;
812 } elseif ( is_array( $valid ) && ( count( $valid ) > 0 ) ) {
813 // 1.2.0: fix to check for empty valid array
814 foreach ( $posted as $i => $value ) {
815 if ( !in_array( $value, $valid ) ) {
816 unset( $posted[$i] );
817 }
818 }
819 $settings[$key] = implode( ',', $posted );
820 } else {
821 $settings[$key] = implode( ',', $posted );
822 }
823
824 } elseif ( ( 'radio' == $type ) || ( 'select' == $type ) ) {
825
826 // --- explicit radio or select value ---
827 $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null;
828 if ( is_string( $valid ) ) {
829 $newsettings = $posted;
830 } elseif ( is_array( $valid ) && array_key_exists( $posted, $valid ) ) {
831 $settings[$key] = $posted;
832 }
833
834 } elseif ( 'multiselect' == $type ) {
835
836 // --- multiselect values ---
837 // 1.0.9: added multiselect value saving
838 $posted = isset( $_POST[$postkey] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST[$postkey] ) ) : array();
839 $newsettings = array_values( $posted );
840
841 } elseif ( 'image' == $type ) {
842
843 // --- check attachment ID value ---
844 // 1.1.7: add image attachment ID saving
845 $posted = isset( $_POST[$postkey] ) ? absint( wp_unslash( $_POST[$postkey] ) ) : null;
846 if ( $posted ) {
847 $attachment = wp_get_attachment_image_src( $posted, 'full' );
848 if ( is_array( $attachment ) ) {
849 $settings[$key] = $posted;
850 }
851 }
852
853 } elseif ( 'color' == $type ) {
854
855 // --- hex color setting ---
856 // 1.1.7: added color picker value saving
857 // 1.2.5: use sanitize_hex_color on color field
858 $posted = isset( $_POST[$postkey] ) ? sanitize_hex_color( wp_unslash( $_POST[$postkey] ) ) : null;
859 $settings[$key] = $posted;
860
861 } elseif ( 'coloralpha' == $type ) {
862
863 // --- color alpha setting ---
864 // 1.2.5: separated color alpha setting condition
865 // 1.2.5: added rgba version of sanitization
866 // ref: https://wordpress.stackexchange.com/a/262578/76440
867 $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null;
868 if ( !is_null( $posted ) ) {
869 $posted = str_replace( ' ', '', $posted );
870 $values = array();
871 // 1.2.7: fix color variable to posted
872 // 1.2.7: make alpha a value key not separate
873 // 1.2.7: check number of commas to see if alpha is set
874 $commas = substr_count( $posted, ',' );
875 if ( 3 == $commas ) {
876 sscanf( $posted, 'rgba(%d,%d,%d,%f)', $values['red'], $values['green'], $values['blue'], $values['alpha'] );
877 } elseif ( 2 == $commas ) {
878 // 1.2.8: remove a from rgba (failing for non-alpha selections)
879 sscanf( $posted, 'rgb(%d,%d,%d)', $values['red'], $values['green'], $values['blue'] );
880 }
881 // echo 'rgba sscanf values: ' . print_r( $values, true ) . "\n";
882 // 1.2.7: fix for use of duplicate key variable
883 foreach ( $values as $k => $v ) {
884 if ( 'alpha' != $k ) {
885 // --- sanitize rgb values ---
886 $v = absint( $v );
887 if ( $v < 0 ) {
888 $values[$k] = 0;
889 } elseif ( $v > 255 ) {
890 $values[$k] = 255;
891 }
892 } else {
893 // --- sanitize alpha value ---
894 if ( $v < 0 ) {
895 $values['alpha'] = 0;
896 } elseif ( $v > 1 ) {
897 $values['alpha'] = 1;
898 }
899 }
900 }
901 if ( 3 == $commas ) {
902 $posted = 'rgba(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ',' . $values['alpha'] . ')';
903 } elseif ( 2 == $commas ) {
904 // 1.2.8: remove a from rgba (for non-alpha selections)
905 $posted = 'rgb(' . $values['red'] . ',' . $values['green'] . ',' . $values['blue'] . ')';
906 }
907 }
908 $settings[$key] = $posted;
909
910 } else {
911
912 // --- fallback to text type ---
913 // 1.3.0: added for unspecified option field type
914 $posted = isset( $_POST[$postkey] ) ? sanitize_text_field( wp_unslash( $_POST[$postkey] ) ) : null;
915 if ( !is_string( $valid ) ) {
916 $valid = 'TEXT';
917 }
918 $newsettings = $posted;
919
920 }
921
922 if ( $this->debug ) {
923 echo 'New Settings for Key ' . esc_html( $key ) . ': ';
924 // 1.2.0: added isset check for newsetting
925 if ( !is_null( $newsettings ) ) {
926 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
927 echo '(To-validate) ' . esc_html( print_r( $newsettings, true ) ) . '<br>' . "\n";
928 } else {
929 // 1.1.7 handle if (new) key not set yet
930 if ( isset( $settings[$key] ) ) {
931 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
932 echo '(Validated) ' . esc_html( print_r( $settings[$key], true ) ) . '<br>' . "\n";
933 } else {
934 echo 'No setting yet for key ' . esc_html( $key ) . '<br>' . "\n";
935 }
936 }
937 }
938
939 // --- maybe validate new settings ---
940 // 1.1.9: fix to allow saving of zero value
941 // 1.2.1: fix to allow saving of empty value
942 if ( !is_null( $newsettings ) ) {
943 if ( is_array( $newsettings ) ) {
944
945 // --- validate array of settings ---
946 // 1.1.9: fix to allow saving of zero value
947 // 1.2.1: fix to allow saving of empty value
948 foreach ( $newsettings as $newkey => $newvalue ) {
949 $newsetting = $this->validate_setting( $newvalue, $valid, $validate_args );
950 if ( $this->debug ) {
951 echo 'Validated Setting array value ' . esc_html( $newvalue ) . ' to ' . esc_html( $newsetting );
952 }
953 if ( $newsetting || ( '' == $newsetting ) ) {
954 $newsettings[$newkey] = $newsetting;
955 } elseif ( ( 0 == $newsetting ) || ( '0' == $newsetting ) ) {
956 $newsettings[$newkey] = $newsetting;
957 } else {
958 unset( $newsettings[$newkey] );
959 }
960 }
961 if ( 'csv' == $type ) {
962 $settings[$key] = implode( ',', $newsettings );
963 } else {
964 $settings[$key] = $newsettings;
965 }
966
967 } elseif ( $newsettings || ( '' == $newsettings ) || ( 0 === $newsettings ) || ( '0' === $newsettings ) ) {
968
969 // --- validate single setting ---
970 if ( 'csv' == $type ) {
971 // 1.1.5: fix to validate each of multiple CSV values
972 $values = explode( ',', $newsettings );
973 $newvalues = array();
974 foreach ( $values as $value ) {
975 $newvalue = $this->validate_setting( $value, $valid, $validate_args );
976 $newvalues[] = $newvalue;
977 if ( $this->debug ) {
978 echo 'Validated Setting value ' . esc_html( $value ) . ' to ' . esc_html( $newvalue ) . '<br>' . "\n";
979 }
980 }
981 $newsettings = implode( ',', $newvalues );
982 $settings[$key] = $newsettings;
983 } else {
984 $newsetting = $this->validate_setting( $newsettings, $valid, $validate_args );
985 // 1.1.9: fix to allow saving of zero value
986 // 1.2.1: fix to allow saving of empty value
987 if ( $this->debug ) {
988 echo 'Validated Setting single value ' . esc_html( $newsettings ) . ' to ' . esc_html( $newsetting ) . '<br>' . "\n";
989 }
990 if ( $newsetting || ( '' == $newsetting ) || ( 0 == $newsetting ) || ( '0' == $newsetting ) ) {
991 $settings[$key] = $newsetting;
992 }
993 }
994 }
995
996 if ( $this->debug ) {
997 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
998 echo 'Valid Options for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $valid, true ) ) . '<br>' . "\n";
999 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
1000 echo 'Validated Settings for Key ' . esc_html( $key ) . ': ' . esc_html( print_r( $settings[$key], true ) ) . '<br>' . "\n";
1001 }
1002 }
1003 }
1004 }
1005 }
1006
1007 // --- process special settings ---
1008 // 1.0.2: added for processing special settings separately
1009 $funcname = $namespace . '_process_special';
1010 if ( method_exists( $this, 'process_special' ) ) {
1011
1012 // --- use class extended method if found ---
1013 $settings = $this->process_special( $settings );
1014
1015 } elseif ( function_exists( $funcname ) && is_callable( $funcname ) ) {
1016
1017 // --- use namespace prefixed function if found ---
1018 $settings = call_user_func( $funcname, $settings );
1019 }
1020
1021 // --- output new settings ---
1022 if ( $this->debug ) {
1023 echo '<br><b>All New Settings:</b><br>';
1024 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
1025 echo esc_html( print_r( $settings, true ) );
1026 echo '<br><br>';
1027 }
1028
1029 if ( $settings && is_array( $settings ) ) {
1030
1031 // --- loop default keys to remove others ---
1032 $settings_keys = array_keys( $defaults );
1033 foreach ( $settings as $key => $value ) {
1034 if ( !in_array( $key, $settings_keys ) ) {
1035 unset( $settings[$key] );
1036 }
1037 }
1038
1039 // --- save settings tab ---
1040 // 1.2.0: validate and save current settings tab
1041 if ( isset( $_POST['settingstab'] ) ) {
1042 $tabs = array();
1043 foreach ( $options as $key => $option ) {
1044 if ( isset( $option['tab'] ) && !in_array( $option['tab'], $tabs ) ) {
1045 $tabs[] = $option['tab'];
1046 }
1047 }
1048 if ( count( $tabs ) > 0 ) {
1049 // 1.2.5: sanitize current tab value before validating
1050 $currenttab = sanitize_text_field( wp_unslash( $_POST['settingstab'] ) );
1051 if ( in_array( $currenttab, $tabs ) ) {
1052 $settings['settingstab'] = $currenttab;
1053 } elseif ( in_array( 'general', $tabs ) ) {
1054 $settings['settingstab'] = 'general';
1055 } else {
1056 $settings['settingstab'] = $tabs[0];
1057 }
1058 }
1059 }
1060
1061 // --- update the plugin settings ---
1062 $settings['savetime'] = time();
1063 update_option( $args['option'], $settings );
1064
1065 // --- merge with existing settings for pageload ---
1066 foreach ( $settings as $key => $value ) {
1067 $GLOBALS[$namespace][$key] = $value;
1068 }
1069
1070 // --- set settings update message flag ---
1071 $_GET['updated'] = 'yes';
1072
1073 } else {
1074 $_GET['updated'] = 'no';
1075 }
1076
1077 // --- maybe trigger update of Pro settings ---
1078 if ( method_exists( $this, 'pro_update_settings' ) ) {
1079 $this->pro_update_settings();
1080 } else {
1081 if ( isset( $args['pronamespace'] ) ) {
1082 $funcname = $args['pronamespace'] . '_update_settings';
1083 } else {
1084 $funcname = $args['namespace'] . '_pro_update_settings';
1085 }
1086 if ( function_exists( $funcname ) ) {
1087 call_user_func( $funcname );
1088 }
1089 }
1090
1091 }
1092
1093 // --------------------------
1094 // Debug Output Posted Values
1095 // --------------------------
1096 function debug_posted( $settings ) {
1097 if ( $this->debug ) {
1098 echo '<br><b>Current Settings:</b><br>';
1099 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
1100 echo esc_html( print_r( $settings, true ) );
1101 echo '<br><br>' . "\n";
1102
1103 echo '<br><b>Plugin Options:</b><br>';
1104 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
1105 echo esc_html( print_r( $this->options, true ) );
1106 echo '<br><br>' . "\n";
1107
1108 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1109 if ( isset( $_POST ) ) {
1110 echo '<br><b>Posted Values:</b><br>';
1111 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1112 $post_keys = array_keys( $_POST );
1113 foreach ( $post_keys as $post_key ) {
1114 $post_key = sanitize_text_field( $post_key );
1115 $value = sanitize_text_field( $_POST[$post_key] );
1116 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
1117 echo esc_html( $post_key ) . ': ' . esc_html( print_r( $value, true ) ) . '<br>' . "\n";
1118 }
1119 }
1120 }
1121 }
1122
1123 // -----------------------
1124 // Validate Plugin Setting
1125 // -----------------------
1126 // 1.1.5: plural options now use extra validation
1127 public function validate_setting( $posted, $valid, $args ) {
1128
1129 // --- allow for clearing of a field ---
1130 // note: array values are cleared if empty, single values are set empty
1131 if ( trim( $posted ) == '' ) {
1132 return '';
1133 }
1134
1135 // --- validate different data types ---
1136 if ( 'TEXT' == $valid ) {
1137
1138 // --- sanitize text field ---
1139 $posted = sanitize_text_field( $posted );
1140
1141 return $posted;
1142
1143 } elseif ( 'ALPHABETIC' == $valid ) {
1144
1145 // --- alphabetic ---
1146 // 1.0.9: added alphabetic-only for completeness
1147 $posted = trim( $posted );
1148 $checkposted = preg_match( '/^[a-zA-Z]+$/', $posted );
1149 if ( $checkposted ) {
1150 return $posted;
1151 }
1152
1153 } elseif ( 'NUMERIC' == $valid ) {
1154
1155 // --- number (numeric text) ---
1156 // note: step key is only used for controls, not for validation
1157 // 1.0.9: cast to integer - not absolute integer
1158 // TODO: validate step value match ?
1159 $posted = floatval( trim( $posted ) );
1160 if ( isset( $args['min'] ) && ( $posted < $args['min'] ) ) {
1161 return $args['min'];
1162 } elseif ( isset( $args['max'] ) && ( $posted > $args['max'] ) ) {
1163 return $args['max'];
1164 } elseif ( 0 === $posted ) {
1165 return 0;
1166 }
1167
1168 return $posted;
1169
1170 } elseif ( 'ALPHANUMERIC' == $valid ) {
1171
1172 // --- alphanumeric text only ---
1173 $posted = trim( $posted );
1174 // TODO: maybe remove underscore from validation regex ?
1175 $checkposted = preg_match( '/^[a-zA-Z0-9_]+$/', $posted );
1176 if ( $checkposted ) {
1177 return $posted;
1178 }
1179
1180 } elseif ( 'PHONE' == $valid ) {
1181
1182 // --- phone number characters only ---
1183 $posted = trim( $posted );
1184 // $checkposted = preg_match( '/^[0-9+\(\)#\.\s\-]+$/', $posted );
1185 // if ( $checkposted ) {
1186 // return $posted;
1187 // }
1188 if ( strlen( $posted ) > 0 ) {
1189 $posted = str_split( $posted, 1 );
1190 $posted = preg_filter( '/^[0-9+\(\)#\.\s\-]+$/', '$0', $posted );
1191 if ( count( $posted ) > 0 ) {
1192 $posted = implode( '', $posted );
1193 return $posted;
1194 } else {
1195 return '';
1196 }
1197 }
1198
1199 } elseif ( in_array( $valid, array( 'URL', 'URLS' ) ) ) {
1200
1201 // --- URL address ---
1202 // 1.0.6: fix to type variable typo (vtype)
1203 // 1.0.4: added validated URL option
1204 // 1.0.6: fix to posted variable type (vposted)
1205 // 1.0.9: remove check for http prefix to allow other protocols
1206 // 1.1.7: use FILTER_SANITIZE_URL not FILTER_SANITIZE_STRING
1207 // 1.2.1: allow for clearing URL by saving empty value
1208 $posted = trim( $posted );
1209 if ( '' != $posted ) {
1210 $posted = filter_var( $posted, FILTER_SANITIZE_URL );
1211 }
1212
1213 // 1.1.7: remove FILTER_VALIDATE_URL check - not working!?
1214 if ( $this->debug ) {
1215 $check = filter_var( $posted, FILTER_VALIDATE_URL );
1216 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
1217 echo 'Validated URL: ' . esc_html( print_r( $check, true ) ) . '<br>';
1218 }
1219 // if ( !filter_var( $url, FILTER_VALIDATE_URL ) ) {
1220 // $posted = '';
1221 // }
1222 return $posted;
1223
1224 } elseif ( in_array( $valid, array( 'EMAIL', 'EMAILS' ) ) ) {
1225
1226 // --- email address ---
1227 // 1.0.3: added email option type checking
1228 $email = sanitize_email( trim( $posted ) );
1229 if ( $email ) {
1230 $posted = $email;
1231 } else {
1232 $posted = '';
1233 }
1234 return $posted;
1235
1236 } elseif ( in_array( $valid, array( 'USEREMAIL', 'USEREMAILS' ) ) ) {
1237
1238 // --- email address with user validation ---
1239 // 1.1.5: added option for validated user emails
1240 $email = sanitize_email( trim( $posted ) );
1241 if ( !$email ) {
1242 return '';
1243 }
1244 $user = get_user_by( 'email', $email );
1245 if ( $user ) {
1246 // 1.2.3: fix to set email value
1247 $posted = $email;
1248 } else {
1249 $posted = '';
1250 }
1251 return $posted;
1252
1253 } elseif ( 'USERNAME' == $valid ) {
1254
1255 // --- username ---
1256 $username = sanitize_user( trim( $posted ) );
1257 if ( !$username ) {
1258 return '';
1259 }
1260 return $username;
1261
1262 } elseif ( 'USERNAMES' == $valid ) {
1263
1264 // --- username with check for existing user ---
1265 // 1.1.5: separated check from singular
1266 $username = sanitize_user( trim( $posted ) );
1267 if ( !$username ) {
1268 return '';
1269 }
1270 $user = get_user_by( 'login', $username );
1271 if ( $user ) {
1272 $posted = $username;
1273 } else {
1274 $posted = '';
1275 }
1276 return $posted;
1277
1278 } elseif ( 'USERID' == $valid ) {
1279
1280 // --- user ID ---
1281 $userid = intval( trim( $posted ) );
1282 if ( 0 === $userid ) {
1283 return '';
1284 }
1285
1286 } elseif ( 'USERIDS' == $valid ) {
1287
1288 // --- user ID with check for existing user ---
1289 // 1.1.5: separated check from singular
1290 $userid = intval( trim( $posted ) );
1291 if ( 0 === $userid ) {
1292 return '';
1293 }
1294 $user = get_user_by( 'ID', $userid );
1295 if ( $user ) {
1296 $posted = $userid;
1297 } else {
1298 $posted = '';
1299 }
1300
1301 return $posted;
1302
1303 } elseif ( in_array( $valid, array( 'SLUG', 'SLUGS' ) ) ) {
1304
1305 // -- post slugs ---
1306 $posted = sanitize_title( trim( $posted ) );
1307
1308 return $posted;
1309
1310 } elseif ( in_array( $valid, array( 'PAGEID', 'POSTID' ) ) ) {
1311
1312 $posted = intval( trim( $posted ) );
1313 if ( 0 === $posted ) {
1314 return '';
1315 }
1316 return $posted;
1317
1318 } elseif ( in_array( $valid, array( 'PAGEIDS', 'POSTIDS' ) ) ) {
1319
1320 $posted = intval( trim( $posted ) );
1321 if ( 0 === $posted ) {
1322 return '';
1323 }
1324 $post = get_post( $posted );
1325 if ( $post ) {
1326 return $posted;
1327 }
1328 }
1329
1330 // TODO: return validation error ?
1331 return false;
1332 }
1333
1334 // ---------------
1335 // Delete Settings
1336 // ---------------
1337 public function delete_settings() {
1338
1339 // TODO: check for plugin settings flag to delete settings ?
1340 // $delete_settings = $this->get_setting( 'delete_settings' );
1341 // if ( $delete_settings ) {
1342 // $args = $this->args;
1343 // delete_option( $args['option'] );
1344 // }
1345
1346 // TODO: check for plugin settings flag to delete data?
1347 // $delete_data = $this->get_setting( 'delete_data' );
1348 // if ( $delete_data ) {
1349 // do_action( $this->namespace . '_delete_data' );
1350 // }
1351 }
1352
1353
1354 // ======================
1355 // --- Plugin Loading ---
1356 // ======================
1357
1358 // --------------------
1359 // Load Plugin Settings
1360 // --------------------
1361 public function load_settings() {
1362 $args = $this->args;
1363 $namespace = $this->namespace;
1364 $GLOBALS[$namespace] = $args;
1365 $settings = get_option( $args['option'], false );
1366 if ( $settings && is_array( $settings ) ) {
1367 foreach ( $settings as $key => $value ) {
1368 $GLOBALS[$namespace][$key] = $value;
1369 }
1370 } else {
1371 $defaults = $this->default_settings();
1372 foreach ( $defaults as $key => $value ) {
1373 $GLOBALS[$namespace][$key] = $value;
1374 }
1375 }
1376 }
1377
1378 // -----------
1379 // Add Actions
1380 // -----------
1381 public function add_actions() {
1382
1383 $args = $this->args;
1384 $namespace = $this->namespace;
1385
1386 // --- add settings on activation ---
1387 register_activation_hook( $args['file'], array( $this, 'add_settings' ) );
1388
1389 // 1.3.7: added for admin options filtering
1390 add_action( 'init', array( $this, 'admin_options' ) );
1391
1392 // --- always check for update and reset of settings ---
1393 add_action( 'admin_init', array( $this, 'update_settings' ) );
1394 add_action( 'admin_init', array( $this, 'reset_settings' ) );
1395
1396 // --- add plugin submenu ---
1397 add_action( 'admin_menu', array( $this, 'settings_menu' ), 1 );
1398
1399 // --- add plugin settings page link ---
1400 add_filter( 'plugin_action_links', array( $this, 'plugin_links' ), 10, 2 );
1401
1402 // --- maybe delete settings on deactivation ---
1403 register_deactivation_hook( $args['file'], array( $this, 'delete_settings' ) );
1404
1405 // --- maybe enqueue scripts / thickbox ---
1406 add_action( 'admin_enqueue_scripts', array( $this, 'maybe_load_thickbox' ) );
1407 // 1.3.4: add earlier enqueue settings page resources check
1408 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_resources' ) );
1409
1410 // --- AJAX readme viewer ---
1411 add_action( 'wp_ajax_' . $namespace . '_readme_viewer', array( $this, 'readme_viewer' ) );
1412
1413 // --- load Freemius (requires PHP 5.4+) ---
1414 // 1.2.1: move Freemius loading to plugins_loaded hook
1415 if ( version_compare( PHP_VERSION, '5.4.0' ) >= 0 ) {
1416 add_action( 'plugins_loaded', array( $this, 'load_freemius' ), 5 );
1417 }
1418 }
1419
1420 // ---------------------
1421 // Load Helper Libraries
1422 // ---------------------
1423 public function load_helpers() {
1424
1425 $args = $this->args;
1426 $file = $args['file'];
1427 $dir = $args['dir'];
1428
1429 // --- Plugin Slug ---
1430 if ( !isset( $args['slug'] ) ) {
1431 $args['slug'] = substr( $file, 0, - 4 );
1432 $this->args = $args;
1433 }
1434
1435 // --- Pro Functions ---
1436 // 1.0.2: added prototype auto-loading of Pro file(s)
1437 // 1.2.1: fix overriding of plan arg to free
1438 // (to work with @fs_premium_only file list)
1439 if ( isset( $args['profiles'] ) && count( $args['profiles'] ) > 0 ) {
1440 $included = get_included_files();
1441 foreach ( $args['profiles'] as $profile ) {
1442 // --- chech for php extension ---
1443 if ( substr( '.php' == $profile, - 4, 4 ) ) {
1444 $filepath = $dir . '/' . $profile;
1445 if ( file_exists( $filepath ) ) {
1446 $plan = 'premium';
1447 // 1.0.9: add check if file already included
1448 if ( !in_array( $filepath, $included ) ) {
1449 include $filepath;
1450 }
1451 }
1452 }
1453 }
1454 }
1455 // 1.2.0: only change plan setting if premium files found
1456 if ( isset( $plan ) ) {
1457 $args['plan'] = $plan;
1458 $this->args = $args;
1459 }
1460
1461 // --- Plugin Update Checker ---
1462 // note: lack of updatechecker.php file indicates WordPress.Org SVN repo version
1463 // presence of updatechecker.php indicates direct site download or GitHub version
1464 $wporg = true;
1465 $updatechecker = $dir . '/updatechecker.php';
1466 if ( file_exists( $updatechecker ) ) {
1467 $wporg = false;
1468 $slug = $args['slug'];
1469 // note: requires $file and $slug to be defined
1470 include $updatechecker;
1471 }
1472 $args['wporg'] = $wporg;
1473 $this->args = $args;
1474
1475 // --- Trigger Loader Helpers Action ---
1476 // 1.0.9: added action for extra loader helpers (eg. WordQuest)
1477 do_action( $args['namespace'] . '_loader_helpers', $args );
1478
1479 // --- load Freemius (requires PHP 5.4+) ---
1480 // 1.2.1: moved to plugins_loaded action hook
1481
1482 }
1483
1484 // -------------------
1485 // Maybe Load Thickbox
1486 // -------------------
1487 public function maybe_load_thickbox() {
1488 $args = $this->args;
1489 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1490 if ( isset( $_REQUEST['page'] ) && ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) == $args['slug'] ) ) {
1491 add_thickbox();
1492 }
1493 }
1494
1495 // ------------------
1496 // Readme Viewer AJAX
1497 // ------------------
1498 public function readme_viewer() {
1499
1500 $args = $this->args;
1501 $dir = $args['dir'];
1502
1503 echo '<html><body style="font-family: Consolas, \'Lucida Console\', Monaco, FreeMono, monospace;">';
1504
1505 // 1.0.7: changed readme.php to reader.php (for Github)
1506 $readme = $dir . '/readme.txt';
1507 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
1508 $contents = file_get_contents( $readme );
1509 $parser = $dir . '/reader.php';
1510
1511 if ( file_exists( $parser ) ) {
1512
1513 // --- include Markdown Readme Parser ---
1514 include $parser;
1515
1516 // --- remove license lines as causing breakage! ---
1517 // 1.3.1: find license lines to handle other possible licenses
1518 // $contents = str_replace( 'License: GPLv2 or later', '', $contents );
1519 // $contents = str_replace( 'License URI: http://www.gnu.org/licenses/gpl-2.0.html', '', $contents );
1520 $strip_lines = array( 'License', 'License URI' );
1521 foreach( $strip_lines as $strip_line ) {
1522 if ( strstr( $contents, $strip_line ) ) {
1523 $pos = strpos( $contents, $strip_line . ':' );
1524 $chunks = str_split( $contents, $pos );
1525 $before = $chunks[0];
1526 unset( $chunks[0] );
1527 $remainder = implode( '', $chunks );
1528 $posb = strpos( $remainder, "\n" );
1529 $chunks = str_split( $remainder, $posb );
1530 unset( $chunks[0] );
1531 $remainder = implode( '', $chunks );
1532 $contents = $before . $remainder;
1533 }
1534 }
1535
1536 // --- instantiate Parser class ---
1537 // 1.3.1: prefix readme parser
1538 $readme = new radio_station_readme_parser();
1539 $parsed = $readme->parse_readme_contents( $contents );
1540
1541 // --- output plugin info ---
1542 echo '<b>' . esc_html( __( 'Plugin Name', 'radio-station' ) ) . '</b>: ' . esc_html( $parsed['name'] ) . '<br>' . "\n";
1543 // echo '<b>' . esc_html( __( 'Tags', 'radio-station' ) ) . '</b>: ' . esc_html( implode( ', ', $parsed['tags'] ) ) . '<br>' . "\n";
1544 echo '<b>' . esc_html( __( 'Requires at least', 'radio-station' ) ) . '</b>: ' . esc_html( __( 'WordPress', 'radio-station' ) ) . ' v' . esc_html( $parsed['requires_at_least'] ) . '<br>' . "\n";
1545 echo '<b>' . esc_html( __( 'Tested up to', 'radio-station' ) ) . '</b>: ' . esc_html( __( 'WordPress', 'radio-station' ) ) . ' v' . esc_html( $parsed['tested_up_to'] ) . '<br>' . "\n";
1546 if ( isset( $parsed['stable_tag'] ) ) {
1547 echo '<b>' . esc_html( __( 'Stable Tag', 'radio-station' ) ) . '</b>: ' . esc_html( $parsed['stable_tag'] ) . '<br>' . "\n";
1548 }
1549 echo '<b>' . esc_html( __( 'Contributors', 'radio-station' ) ) . '</b>: ' . esc_html( implode( ', ', $parsed['contributors'] ) ) . '<br>' . "\n";
1550 // echo '<b>Donate Link</b>: <a href="' . esc_url( $parsed['donate_link'] ) . '" target="_blank">' . esc_html( $parsed['donate_link'] ) . '</a><br>';
1551 // 1.2.5: use wp_kses_post on plugin short description markup
1552 echo '<br>' . wp_kses_post( $parsed['short_description'] ) . '<br><br>' . "\n";
1553
1554 // --- output sections ---
1555 // possible sections: 'description', 'installation', 'frequently_asked_questions',
1556 // 'screenshots', 'changelog', 'change_log', 'upgrade_notice'
1557 $strip = array( 'installation', 'screenshots' );
1558 foreach ( $parsed['sections'] as $key => $section ) {
1559 if ( !empty( $section ) && !in_array( $key, $strip ) ) {
1560 if ( strstr( $key, '_' ) ) {
1561 $parts = explode( '_', $key );
1562 } else {
1563 $parts = array( $key );
1564 }
1565 foreach ( $parts as $i => $part ) {
1566 $parts[$i] = strtoupper( substr( $part, 0, 1 ) ) . substr( $part, 1 );
1567 }
1568 $title = implode( ' ', $parts );
1569 echo '<h3>' . esc_html( $title ) . '</h3>' . "\n";
1570 // 1.2.5: use wp_kses_post on readme section output
1571 echo wp_kses_post( $section );
1572 }
1573 }
1574 if ( isset( $parsed['remaining_content'] ) && !empty( $remaining_content ) ) {
1575 echo '<h3>' . esc_html( __( 'Extra Notes', 'radio-station' ) ) . '</h3>' . "\n";
1576 // 1.2.5: use wp_kses_post on readme extra notes output
1577 echo wp_kses_post( $parsed['remaining_content'] );
1578 }
1579
1580 } else {
1581 // --- fallback text-only display ---
1582 $contents = str_replace( "\n", '<br>', $contents );
1583 echo wp_kses_post( $contents );
1584 }
1585
1586 echo '</body></html>';
1587 exit;
1588 }
1589
1590
1591 // =======================
1592 // --- Freemius Loader ---
1593 // =======================
1594 //
1595 // required settings keys:
1596 // -----------------------
1597 // freemius_id - plugin ID from Freemius plugin dashboard
1598 // freemius_key - public key from Freemius plugin dashboard
1599 //
1600 // optional settings keys:
1601 // -----------------------
1602 // plan - (string) curent plugin plan (value of 'free' or 'premium')
1603 // hasplans - (boolean) switch for whether plugin has premium plans
1604 // hasaddons - (boolean) switch for whether plugin has premium addons
1605 // wporg - (boolean) switch for whether free plugin is WordPress.org compliant
1606 // contact - (boolean) submenu switch for plugin Contact (defaults to on for premium only)
1607 // support - (boolean) submenu switch for plugin Support (default on)
1608 // account - (boolean) submenu switch for plugin Account (default on)
1609 // parentmenu - (string) optional slug for plugin parent menu
1610 //
1611 // okay lets do this...
1612 // ====================
1613 public function load_freemius() {
1614
1615 // 1.2.1: no need to load if not in admin area
1616 // if ( !is_admin() ) {
1617 // return;
1618 // }
1619 // echo '<span style="display:none;">Freemius Loading...</span>';
1620
1621 $args = $this->args;
1622 $namespace = $this->namespace;
1623
1624 // --- check for required Freemius keys ---
1625 if ( !isset( $args['freemius_id'] ) || !isset( $args['freemius_key'] ) ) {
1626 return;
1627 }
1628
1629 // --- check for free / premium plan ---
1630 // convert plan string value of 'free' or 'premium' to boolean premium switch
1631 // TODO: check for active addons also ?
1632 $premium = false;
1633 if ( isset( $args['plan'] ) && ( 'premium' == $args['plan'] ) ) {
1634 $premium = true;
1635 } else {
1636 // 1.2.1: added filter for premium init
1637 $premium = apply_filters( 'freemius_init_premium_' . $args['namespace'], $premium );
1638 }
1639
1640 // --- maybe redirect link to plugin support forum ---
1641 // TODO: change to use new Freemius 2.3.0 support link filter ?
1642 // 1.0.5: use sanitize_text_field on request variable
1643 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1644 if ( isset( $_REQUEST['page'] ) && ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) == $args['slug'] . '-wp-support-forum' ) && is_admin() ) {
1645 // 1.0.7: fix support URL undefined variable warning
1646 if ( isset( $args['support'] ) ) {
1647 // 1.3.6: add action and bug out on redirect
1648 add_action( 'admin_init', array( $this, 'support_redirect' ) );
1649 return;
1650 }
1651 }
1652
1653 // --- do the Freemius Loading boogie ---
1654 if ( !isset( $args['freemius'] ) ) {
1655
1656 // --- start the Freemius SDK ---
1657 if ( !class_exists( 'Freemius' ) ) {
1658 $freemiuspath = dirname( __FILE__ ) . '/freemius/start.php';
1659 $freemiuspath = apply_filters( 'freemius_load_path', $freemiuspath, $namespace, $args );
1660 if ( !file_exists( $freemiuspath ) ) {
1661 return;
1662 }
1663 require_once $freemiuspath;
1664 }
1665
1666 // --- set defaults for optional key values ---
1667 if ( !isset( $args['type'] ) ) {
1668 $args['type'] = 'plugin';
1669 }
1670 if ( !isset( $args['wporg'] ) ) {
1671 $args['wporg'] = false;
1672 }
1673 if ( !isset( $args['hasplans'] ) ) {
1674 $args['hasplans'] = false;
1675 }
1676 if ( !isset( $args['hasaddons'] ) ) {
1677 $args['hasaddons'] = false;
1678 }
1679 // 1.2.1: add filter for addons init
1680 $args['hasaddons'] = apply_filters( 'freemius_init_addons_' . $args['namespace'], $args['hasaddons'] );
1681
1682 // --- set defaults for options submenu key values ---
1683 // 1.0.2: fix to isset check keys
1684 // 1.0.5: fix to set args subkeys for support and account
1685 if ( !isset( $args['support'] ) ) {
1686 $args['support'] = true;
1687 }
1688 if ( !isset( $args['account'] ) ) {
1689 $args['account'] = true;
1690 }
1691 // by default, enable contact submenu item for premium plugins only
1692 if ( !isset( $args['contact'] ) ) {
1693 $args['contact'] = $premium;
1694 }
1695 if ( !isset( $args['affiliation'] ) ) {
1696 // 1.3.1: fix to key typo (affiliaation)
1697 $args['affiliation'] = false;
1698 }
1699
1700 // --- set Freemius settings from plugin settings ---
1701 // ref: https://freemius.com/help/documentation/wordpress-sdk/integrating-freemius-sdk/
1702 // 1.1.1: remove admin_url wrapper on Freemius first-path value
1703 // 1.3.0: added has_affiliation argument key
1704 // TODO: further possible args for Freemius init (eg. bundle_id)
1705 $first_path = add_query_arg( 'page', $args['slug'], 'admin.php' );
1706 $first_path = add_query_arg( 'welcome', 'true', $first_path );
1707 $settings = array(
1708 'type' => $args['type'],
1709 'slug' => $args['slug'],
1710 'id' => $args['freemius_id'],
1711 'public_key' => $args['freemius_key'],
1712 'has_addons' => $args['hasaddons'],
1713 'has_paid_plans' => $args['hasplans'],
1714 'is_org_compliant' => $args['wporg'],
1715 'is_premium' => $premium,
1716 'has_affiliation' => $args['affiliation'],
1717 'menu' => array(
1718 'slug' => $args['slug'],
1719 'first-path' => $first_path,
1720 'contact' => $args['contact'],
1721 'support' => $args['support'],
1722 'account' => $args['account'],
1723 ),
1724 );
1725
1726 // --- maybe add plugin submenu to parent menu ---
1727 if ( isset( $args['parentmenu'] ) ) {
1728 $settings['menu']['parent'] = array( 'slug' => $args['parentmenu'] );
1729 }
1730
1731 // --- filter settings before initializing ---
1732 $settings = apply_filters( 'freemius_init_settings_' . $args['namespace'], $settings );
1733 if ( $this->debug ) {
1734 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
1735 echo '<span style="display:none;">Freemius Settings: ' . esc_html( print_r( $settings, true ) ) . '</span>' . "\n";
1736 }
1737 if ( !$settings || !is_array( $settings ) ) {
1738 return;
1739 }
1740
1741 // --- initialize Freemius now ---
1742 $freemius = $GLOBALS[$namespace . '_freemius'] = fs_dynamic_init( $settings );
1743
1744 // --- set plugin basename ---
1745 // 1.0.1: set free / premium plugin basename
1746 if ( method_exists( $freemius, 'set_basename' ) ) {
1747 $freemius->set_basename( $premium, $args['file'] );
1748 }
1749
1750 // --- add Freemius connect message filter ---
1751 $this->freemius_connect();
1752
1753 // --- Freemius Object Debug ---
1754 if ( $this->debug && current_user_can( 'manage_options' ) ) {
1755 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
1756 echo '<span style="display:none;">Freemius Object: ' . esc_html( print_r( $freemius, true ) ) . '</span>' . "\n";
1757 }
1758
1759 // --- fire Freemius loaded action ---
1760 do_action( $args['namespace'] . '_loaded' );
1761 }
1762 }
1763
1764 // -----------------------
1765 // Filter Freemius Connect
1766 // -----------------------
1767 public function freemius_connect() {
1768 $namespace = $this->args['namespace'];
1769 $freemius = $GLOBALS[$namespace . '_freemius'];
1770 if ( isset( $settings['freemius'] ) && is_object( $freemius ) && method_exists( $freemius, 'add_filter' ) ) {
1771 $freemius->add_filter( 'connect_message', array( $this, 'freemius_connect_message' ), WP_FS__DEFAULT_PRIORITY, 6 );
1772 }
1773 }
1774
1775 // ----------------
1776 // Support Redirect
1777 // ----------------
1778 // 1.3.6: enqueued on admin_init for slightly later execution
1779 function support_redirect() {
1780
1781 $args = $this->args;
1782 $support_url = $args['support'];
1783
1784 // changes the support forum slug for premium based on the pro plugin file slug
1785 // 1.2.1: removed in favour of filtering via Pro
1786 // if ( $premium && isset( $args['proslug'] ) ) {
1787 // $support_url = str_replace( $args['slug'], $args['proslug'], $support_url );
1788 // }
1789 $support_url = apply_filters( 'freemius_plugin_support_url_redirect', $support_url, $args['slug'] );
1790
1791 // 1.3.6: removed conditional include of pluggable (no longer necessary)
1792 // phpcs:ignore WordPress.Security.SafeRedirect
1793 wp_redirect( $support_url );
1794 exit;
1795 }
1796
1797 // ------------------------
1798 // Freemius Connect Message
1799 // ------------------------
1800 public function freemius_connect_message( $message, $user_first_name, $plugin_title, $user_login, $site_link, $freemius_link ) {
1801 // default: 'Never miss an important update - opt-in to our security and feature updates notifications, and non-sensitive diagnostic tracking with %4$s.'
1802 // 1.0.9: fix to remove incorrect first argument in string replacement
1803 $message = __fs( 'hey-x' ) . '<br>';
1804 // 1.2.4: added ordering to replacement arguments
1805 $message .= sprintf(
1806 // Translators: plugin title, user name, site link, freemius link
1807 __( 'If you want to more easily access support and feedback for this plugins features and functionality, %1$s can connect your user, %2$s at %3$s, to %4$s', 'radio-station' ),
1808 '<b>' . $plugin_title . '</b>',
1809 '<b>' . $user_login . '</b>',
1810 $site_link,
1811 $freemius_link
1812 );
1813
1814 return $message;
1815 }
1816
1817 // ----------------------
1818 // Connect Update Message
1819 // ----------------------
1820 // TODO: message for connect updates
1821 public function freemius_update_message( $message, $user_first_name, $plugin_title, $user_login, $site_link, $freemius_link ) {
1822 // default: 'Please help us improve %1$s! If you opt-in, some data about your usage of %1$s will be sent to %4$s. If you skip this, that\'s okay! %1$s will still work just fine.'
1823 // $message = freemius_message( $message, $user_first_name, $plugin_title, $user_login, $site_link, $freemius_link );
1824 // TODO: check if message needs return here ?
1825 return $message;
1826 }
1827
1828
1829 // ====================
1830 // --- Plugin Admin ---
1831 // ====================
1832
1833 // --------------------------------
1834 // Enequeue Settings Page Resources
1835 // --------------------------------
1836 // 1.3.4: added enqueue resources function
1837 public function enqueue_resources() {
1838
1839 $args = $this->args;
1840 $namespace = $this->namespace;
1841
1842 if ( isset( $_REQUEST['page'] ) && ( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) == $args['slug'] ) ) {
1843
1844 // --- get plugin options and default settings ---
1845 // 1.1.2: fix for filtering of plugin options
1846 $options = $this->options;
1847 $options = apply_filters( $namespace . '_plugin_options', $options );
1848
1849 // --- maybe enqueue media scripts ---
1850 // 1.1.7: added media gallery script enqueueing for image field
1851 // 1.1.7: added color picker and color picker alpha script enqueueing
1852 $enqueued_media = $enqueued_color_picker = $enqueue_color_picker = $enqueue_color_picker_alpha = false;
1853 foreach ( $options as $option ) {
1854 if ( isset( $option['type'] ) ) {
1855 if ( ( 'image' == $option['type'] ) && !$enqueued_media ) {
1856 wp_enqueue_media();
1857 $enqueued_media = true;
1858 } elseif ( 'color' == $option['type'] ) {
1859 $enqueue_color_picker = true;
1860 } elseif ( 'coloralpha' == $option['type'] ) {
1861 $enqueue_color_picker_alpha = true;
1862 }
1863 }
1864 }
1865
1866 // 1.2.5: moved out of
1867 if ( $enqueue_color_picker_alpha ) {
1868 wp_enqueue_style( 'wp-color-picker' );
1869 $suffix = '.min';
1870 if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
1871 $suffix = '';
1872 }
1873 $url = plugins_url( '/js/wp-color-picker-alpha' . $suffix . '.js', $args['file'] );
1874 wp_enqueue_script( 'wp-color-picker-a', $url, array( 'wp-color-picker' ), '3.0.0', true );
1875 $enqueued_color_picker = true;
1876 } elseif ( $enqueue_color_picker ) {
1877 wp_enqueue_style( 'wp-color-picker' );
1878 wp_enqueue_script( 'wp-color-picker' );
1879 $enqueued_color_picker = true;
1880 }
1881
1882 // --- enqueue print of settings scripts / styles ---
1883 $this->settings_resources( $enqueued_media, $enqueued_color_picker );
1884 }
1885 }
1886
1887 // -----------------
1888 // Add Settings Menu
1889 // -----------------
1890 public function settings_menu() {
1891
1892 $args = $this->args;
1893 $namespace = $this->namespace;
1894 $settings = $GLOBALS[$namespace];
1895
1896 // --- filter capability ---
1897 $args['capability'] = apply_filters( $args['namespace'] . '_manage_options_capability', 'manage_options' );
1898 if ( !isset( $args['pagetitle'] ) ) {
1899 $args['pagetitle'] = $args['title'];
1900 }
1901 if ( !isset( $args['menutitle'] ) ) {
1902 $args['menutitle'] = $args['title'];
1903 }
1904 // 1.1.9: added filters for page and menu titles
1905 $pagetitle = apply_filters( $namespace . '_settings_page_title', $args['pagetitle'] );
1906 $menutitle = apply_filters( $namespace . '_settings_menu_title', $args['menutitle'] );
1907
1908 // --- trigger filter plugin menu action ---
1909 // (can hook into this to add an admin menu manually using the provided loader args)
1910 // return true from filter function to not add a submenu item in admin Settings menu
1911 // 1.0.8: change from function exists check
1912 // 1.0.9: change to filter usage to check if menu is manually added
1913 $menu_added = apply_filters( $args['namespace'] . '_admin_menu_added', false, $args );
1914
1915 // 1.1.1: record to admin menu added switch
1916 $this->menu_added = $menu_added;
1917
1918 // --- maybe auto-add standalone options page ---
1919 if ( !$menu_added ) {
1920 // 1.0.8: check settingsmenu switch that disables automatic settings menu adding
1921 if ( !isset( $args['settingsmenu'] ) || ( false !== $args['settingsmenu'] ) ) {
1922 // 1.3.0: use filtered pagetitle and menutitle
1923 add_options_page( $pagetitle, $menutitle, $args['capability'], $args['slug'], $args['namespace'] . '_settings_page' );
1924 }
1925 }
1926
1927 // 1.1.0: add admin notices boxer
1928 add_action( 'all_admin_notices', array( $this, 'notice_boxer' ), 999 );
1929 }
1930
1931 // -----------------
1932 // Plugin Page Links
1933 // -----------------
1934 // 1.2.2: merge in plugin links instead of using array_unshift
1935 public function plugin_links( $links, $file ) {
1936
1937 $args = $this->args;
1938 if ( plugin_basename( $args['file'] ) == $file ) {
1939
1940 // --- add settings link ---
1941 // 1.1.1: fix to settings page link URL
1942 // (depending on whether top level menu or Settings submenu item)
1943 $page = $this->menu_added ? 'admin.php' : 'options-general.php';
1944 $settings_url = add_query_arg( 'page', $args['slug'], admin_url( $page ) );
1945 $settings_link = '<a href="' . esc_url( $settings_url ) . '">' . esc_html( __( 'Settings', 'radio-station' ) ) . '</a>';
1946 $link = array( 'settings' => $settings_link );
1947 $links = array_merge( $link, $links );
1948
1949 // --- maybe add Pro upgrade link ---
1950 if ( isset( $args['hasplans'] ) && $args['hasplans'] ) {
1951
1952 // 1.2.1: add check if premium is already installed
1953 if ( !isset( $args['plan'] ) || ( 'premium' != $args['plan'] ) ) {
1954
1955 // -- internal upgrade link ---
1956 if ( isset( $args['upgrade_link'] ) ) {
1957 $upgrade_url = $args['upgrade_link'];
1958 $upgrade_target = '';
1959 } else {
1960 $upgrade_url = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) );
1961 $upgrade_target = !strstr( $upgrade_url, '/wp-admin/' ) ? ' target="_blank"' : '';
1962 }
1963 $upgrade_link = '<b><a href="' . esc_url( $upgrade_url ) . '"' . $upgrade_target . ">" . esc_html( __( 'Upgrade', 'radio-station' ) ) . '</a></b>';
1964 $link = array( 'upgrade' => $upgrade_link );
1965 $links = array_merge( $link, $links );
1966
1967 // --- external pro link ---
1968 // 1.2.0: added separate pro details link
1969 if ( isset( $args['pro_link'] ) ) {
1970 $pro_target = !strstr( $args['pro_link'], '/wp-admin/' ) ? ' target="_blank"' : '';
1971 $pro_link = '<b><a href="' . esc_url( $args['pro_link'] ) . '"' . $pro_target . '>' . esc_html( __( 'Pro Details', 'radio-station' ) ) . '</a></b>';
1972 $link = array( 'pro-details' => $pro_link );
1973 $links = array_merge( $link, $links );
1974 }
1975 }
1976 }
1977
1978 // --- maybe add Addons link ---
1979 // 1.2.0: activated add-ons link
1980 // 1.2.2: remove duplication of addons link
1981 if ( !isset( $args['hasaddons'] ) || !$args['hasaddons'] ) {
1982 if ( isset( $args['addons_link'] ) ) {
1983 $addons_url = $args['addons_link'];
1984 $addons_target = !strstr( $addons_url, '/wp-admin/' ) ? ' target="_blank"' : '';
1985 $addons_link = '<a href="' . esc_url( $addons_url ) . '"' . $addons_target . '>' . esc_html( __( 'Add Ons', 'radio-station' ) ) . '</a>';
1986 $link = array( 'addons' => $addons_link );
1987 $links = array_merge( $link, $links );
1988 }
1989 }
1990
1991 if ( $this->debug ) {
1992 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
1993 echo '<span style="display:none;">Plugin Links for ' . esc_html( $file ) . ': ' . esc_html( print_r( $links, true ) ) . '</span>' . "\n";
1994 }
1995 }
1996
1997 return $links;
1998 }
1999
2000 // -----------
2001 // Message Box
2002 // -----------
2003 // 1.2.5: use output buffering for no-echo
2004 public function message_box( $message, $echo ) {
2005
2006 if ( !$echo ) {
2007 ob_start();
2008 }
2009
2010 echo '<table style="background-color: lightYellow; border-style:solid; border-width:1px; border-color: #E6DB55; text-align:center;">' . "\n";
2011 echo '<tr><td>' . "\n";
2012 echo '<div class="message" style="margin:0.25em; font-weight:bold;">' . "\n";
2013 // 1.2.5: added wp_kses_post to message output
2014 echo wp_kses_post( $message ) . "\n";
2015 echo '</div>' . "\n";
2016 echo '</td></tr>' . "\n";
2017 echo '</table>' . "\n";
2018 if ( !$echo ) {
2019 $box = ob_get_contents();
2020 ob_end_clean();
2021 return $box;
2022 }
2023 }
2024
2025 // ------------
2026 // Notice Boxer
2027 // ------------
2028 // 1.1.0: added admin notices boxer to settings pages
2029 public function notice_boxer() {
2030
2031 $args = $this->args;
2032
2033 // --- bug out if not on radio station pages ---
2034 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2035 if ( !isset( $_REQUEST['page'] ) ) {
2036 return;
2037 }
2038 // 1.0.5: use sanitize_title on request variable
2039 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2040 if ( substr( sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ), 0, strlen( $args['slug'] ) ) != $args['slug'] ) {
2041 return;
2042 }
2043
2044 // 1.2.2: bug out if adminsanity notices are loaded
2045 if ( isset( $GLOBALS['radio_station_data']['load']['notices'] ) && $GLOBALS['radio_station_data']['load']['notices'] ) {
2046 return;
2047 }
2048
2049 // --- output notice box ---
2050 echo '<div style="width: 98%;" id="admin-notices-box" class="postbox">' . "\n";
2051 echo '<h3 class="admin-notices-title" style="cursor:pointer; margin:7px 14px; font-size:16px;" onclick="settings_toggle_notices();">' . "\n";
2052 echo '<span id="admin-notices-arrow" style="font-size:24px;">&#9656;</span> &nbsp; ' . "\n";
2053 echo '<span id="admin-notices-title" style="vertical-align:top;">' . esc_html( __( 'Notices', 'radio-station' ) ) . '</span> &nbsp; ' . "\n";
2054 echo '<span id="admin-notices-count" style="vertical-align:top;"></span></h3>' . "\n";
2055
2056 echo '<div id="admin-notices-wrap" style="display:none";><h2 style="display:none;"></h2></div>' . "\n";
2057 echo '</div>' . "\n";
2058
2059 // 1.3.6: move notice boxer scripts to setting_scripts
2060 // $this->scripts[] = 'notice_boxer';
2061
2062 }
2063
2064 // ------------------
2065 // Plugin Page Header
2066 // ------------------
2067 public function settings_header() {
2068
2069 $args = $this->args;
2070 $namespace = $this->namespace;
2071 $settings = $GLOBALS[$namespace];
2072
2073 // --- check for animated gif icon with fallback to normal icon ---
2074 // 1.0.9: fix to check if PNG file exists
2075 $icon_url = false;
2076 if ( file_exists( $this->args['dir'] . '/images/' . $args['slug'] . '.gif' ) ) {
2077 $icon_url = plugins_url( 'images/' . $args['slug'] . '.gif', $args['file'] );
2078 } elseif ( file_exists( $this->args['dir'] . '/images/' . $args['slug'] . '.png' ) ) {
2079 $icon_url = plugins_url( 'images/' . $args['slug'] . '.png', $args['file'] );
2080 }
2081 $icon_url = apply_filters( $namespace . '_plugin_icon_url', $icon_url );
2082
2083 // --- check for author icon based on provided author name ---
2084 // 1.0.2: check if author icon file exists and fallback
2085 $author_icon_url = false;
2086 $author_slug = strtolower( str_replace( ' ', '', $args['author'] ) );
2087 if ( file_exists( $this->args['dir'] . '/images/' . $author_slug . '.png' ) ) {
2088 $author_icon_url = plugins_url( 'images/' . $author_slug . '.png', $args['file'] );
2089 } elseif ( file_exists( $this->args['dir'] . '/images/wordquest.png' ) ) {
2090 $author_icon_url = plugins_url( 'images/wordquest.png', $args['file'] );
2091 }
2092 $author_icon_url = apply_filters( $namespace . '_author_icon_url', $author_icon_url );
2093
2094 // --- open header table ---
2095 echo '<table class="plugin-settings-page-header"><tr>' . "\n";
2096
2097 // --- plugin icon ---
2098 // 1.1.9: add filter for plugin icon url
2099 $icon_url = apply_filters( $namespace . '_settings_page_icon_url', $icon_url );
2100 echo '<td>' . "\n";
2101 if ( $icon_url ) {
2102 echo '<img class="plugin-settings-page-icon" src="' . esc_url( $icon_url ) . '" width="128" height="128">' . "\n";
2103 }
2104 echo '</td>' . "\n";
2105
2106 echo '<td width="20"></td><td>' . "\n";
2107
2108 echo '<table><tr>' . "\n";
2109
2110 // --- plugin title ---
2111 // 1.1.9: add filter for plugin pagetitle
2112 $title = apply_filters( $namespace . '_settings_page_title', $args['title'] );
2113 echo '<td><h3 style="font-size:20px;">' . "\n";
2114 echo '<a href="' . esc_url( $args['home'] ) . '" target="_blank" style="text-decoration:none;">' . esc_html( $title ) . '</a>' . "\n";
2115 echo '</h3></td>' . "\n";
2116
2117 echo '<td width="20"></td>' . "\n";
2118
2119 // --- plugin version ---
2120 // 1.1.9: add filter for plugin version
2121 $version = apply_filters( $namespace . '_settings_page_version', 'v' . $args['version'] );
2122 echo '<td><h3 class="plugin-setttings-page-version">' . esc_html( $version ) . '</h3></td></tr>' . "\n";
2123
2124 // --- subtitle ---
2125 // 1.1.9: added optional subtitle filter display
2126 $subtitle = apply_filters( $namespace . '_settings_page_subtitle', '' );
2127 if ( '' != $subtitle ) {
2128 echo '<tr><td colspan="3" align="center">' . "\n";
2129 echo '<h4 class="plugins-settings-page-subtitle" style="font-size:14px; margin-top:0;">' . esc_html( $subtitle ) . '</h4>' . "\n";
2130 echo '</td></tr>' . "\n";
2131 }
2132
2133 echo '<tr><td colspan="3" align="center">' . "\n";
2134
2135 echo '<table><tr><td align="center">' . "\n";
2136
2137 // ---- plugin author ---
2138 // 1.0.8: check if author URL is set
2139 if ( isset( $args['author_url'] ) ) {
2140 echo '<font style="font-size:16px;">' . esc_html( __( 'by', 'radio-station' ) ) . '</font> ';
2141 echo '<a href="' . esc_url( $args['author_url'] ) . '" target="_blank" style="text-decoration:none;font-size:16px;" target="_blank"><b>' . esc_html( $args['author'] ) . '</b></a><br><br>' . "\n";
2142 }
2143
2144 // --- readme / docs / support links ---
2145 // 1.0.8: use filtered links array with automatic separator
2146 // 1.1.0: added explicit plugin home link
2147 // 1.1.0: added title attributes to links
2148 $links = array();
2149 if ( isset( $args['home'] ) ) {
2150 $links[] = '<a href="' . esc_url( $args['home'] ) . '" class="pluginlink smalllink" title="' . esc_attr( __( 'Plugin Homepage', 'radio-station' ) ) . '" target="_blank"><b>' . esc_html( __( 'Home', 'radio-station' ) ) . '</b></a>';
2151 }
2152 if ( !isset( $args['readme'] ) || ( false !== $args['readme'] ) ) {
2153 $readme_url = add_query_arg( 'action', $namespace . '_readme_viewer', admin_url( 'admin-ajax.php' ) );
2154 $links[] = '<a href="' . esc_url( $readme_url ) . '" class="pluginlink smalllink thickbox" title="' . esc_attr( __( 'View Plugin', 'radio-station' ) ) . ' readme.txt"><b>' . esc_html( __( 'Readme', 'radio-station' ) ) . '</b></a>';
2155 }
2156 if ( isset( $args['docs'] ) ) {
2157 $links[] = '<a href="' . esc_url( $args['docs'] ) . '" class="pluginlink smalllink" title="' . esc_attr( __( 'Plugin Documentation', 'radio-station' ) ) . '" target="_blank"><b>' . esc_html( __( 'Docs', 'radio-station' ) ) . '</b></a>';
2158 }
2159 if ( isset( $args['support'] ) ) {
2160 $links[] = '<a href="' . esc_url( $args['support'] ) . '" class="pluginlink smalllink" title="' . esc_attr( __( 'Plugin Support', 'radio-station' ) ) . '" target="_blank"><b>' . esc_html( __( 'Support', 'radio-station' ) ) . '</b></a>';
2161 }
2162 if ( isset( $args['development'] ) ) {
2163 $links[] = '<a href="' . esc_url( $args['development'] ) . '" class="pluginlink smalllink" title="' . esc_attr( __( 'Plugin Development', 'radio-station' ) ) . '" target="_blank"><b>' . esc_html( __( 'Dev', 'radio-station' ) ) . '</b></a>';
2164 }
2165
2166 // 1.0.9: change filter from _plugin_links to disambiguate
2167 $links = apply_filters( $args['namespace'] . '_plugin_admin_links', $links );
2168 if ( count( $links ) > 0 ) {
2169 // 1.2.5: use wp_kses_post on output
2170 echo wp_kses_post( implode( ' | ', $links ) );
2171 }
2172
2173 // --- author icon ---
2174 if ( $author_icon_url ) {
2175 echo '</td><td>' . "\n";
2176
2177 // 1.0.8: check if author URL is set for link
2178 if ( isset( $args['author_url'] ) ) {
2179 echo '<a href="' . esc_url( $args['author_url'] ) . '" target="_blank">' . "\n";
2180 }
2181 echo '<img src="' . esc_url( $author_icon_url ) . '" width="64" height="64" border="0">' . "\n";
2182 if ( isset( $args['author_url'] ) ) {
2183 echo '</a>' . "\n";
2184 }
2185 }
2186
2187 echo '</td></tr></table>' . "\n";
2188
2189 echo '</td></tr></table>' . "\n";
2190
2191 echo '</td><td width="50"></td><td style="vertical-align:top;">' . "\n";
2192
2193 // --- plugin supporter links ---
2194 // 1.0.1: set rate/share/donate links and texts
2195 // 1.0.8: added filters for rate/share/donate links
2196 echo '<br><div class="plugin-settings-page-links">' . "\n";
2197
2198 // --- Rate link ---
2199 if ( isset( $args['wporgslug'] ) ) {
2200 if ( isset( $args['rate'] ) ) {
2201 $rate_url = $args['rate'];
2202 } elseif ( isset( $args['type'] ) && ( 'theme' == $args['type'] ) ) {
2203 $rate_url = 'https://wordpress.org/support/theme/' . $args['wporgslug'] . '/reviews/#new-post';
2204 } else {
2205 // 1.2.2: update rating URL to match new repo scheme
2206 $rate_url = 'https://wordpress.org/support/plugins/' . $args['wporgslug'] . '/reviews/#new-post';
2207 }
2208 if ( isset( $args['ratetext'] ) ) {
2209 $rate_text = $args['ratetext'];
2210 } else {
2211 $rate_text = __( 'Rate on WordPress.Org', 'radio-station' );
2212 }
2213 $rate_link = '<a href="' . esc_url( $rate_url ) . '" class="pluginlink" target="_blank">';
2214 $rate_link .= '<span style="font-size:24px; color:#FC5; margin-right:10px;" class="dashicons dashicons-star-filled"></span>' . "\n";
2215 $rate_link .= ' ' . esc_html( $rate_text ) . '</a><br><br>' . "\n";
2216 $rate_link = apply_filters( $args['namespace'] . '_rate_link', $rate_link, $args );
2217 if ( $rate_link ) {
2218 // 1.2.5: use wp_kses_post on rate link output
2219 echo wp_kses_post( $rate_link );
2220 }
2221 }
2222
2223 // --- Share link ---
2224 if ( isset( $args['share'] ) ) {
2225 if ( isset( $args['sharetext'] ) ) {
2226 $share_text = $args['sharetext'];
2227 } else {
2228 $share_text = __( 'Share the Plugin Love', 'radio-station' );
2229 }
2230 $share_link = '<a href="' . esc_url( $args['share'] ) . '" class="pluginlink" target="_blank">';
2231 $share_link .= '<span style="font-size:24px; color:#E0E; margin-right:10px;" class="dashicons dashicons-share"></span> ';
2232 $share_link .= esc_html( $share_text ) . '</a><br><br>';
2233 $share_link = apply_filters( $args['namespace'] . '_share_link', $share_link, $args );
2234 if ( $share_link ) {
2235 // 1.2.5: use wp_kses_post on share link output
2236 echo wp_kses_post( $share_link );
2237 }
2238 }
2239
2240 // --- Donate link ---
2241 if ( isset( $args['donate'] ) ) {
2242 if ( isset( $args['donatetext'] ) ) {
2243 $donate_text = $args['donatetext'];
2244 } else {
2245 $donate_text = __( 'Support this Plugin', 'radio-station' );
2246 }
2247 $donate_link = '<a href="' . esc_url( $args['donate'] ) . '" class="pluginlink" target="_blank">';
2248 $donate_link .= '<span style="font-size:24px; color:#E00; margin-right:10px;" class="dashicons dashicons-heart"></span> ';
2249 $donate_link .= '<b>' . esc_html( $donate_text ) . '</b></a><br><br>';
2250 $donate_link = apply_filters( $args['namespace'] . '_donate_link', $donate_link, $args );
2251 if ( $donate_link ) {
2252 // 1.2.5: use wp_kses_post on donate link output
2253 echo wp_kses_post( $donate_link );
2254 }
2255 }
2256
2257 echo '</div></td></tr>' . "\n";
2258
2259 // --- output updated and reset messages ---
2260 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2261 if ( isset( $_GET['updated'] ) ) {
2262 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2263 $updated = sanitize_text_field( wp_unslash( $_GET['updated'] ) );
2264 if ( 'yes' == $updated ) {
2265 $message = $settings['title'] . ' ' . __( 'Settings Updated.', 'radio-station' );
2266 } elseif ( 'no' == $updated ) {
2267 $message = __( 'Error! Settings NOT Updated.', 'radio-station' );
2268 } elseif ( 'reset' == $updated ) {
2269 $message = $settings['title'] . ' ' . __( 'Settings Reset!', 'radio-station' );
2270 }
2271 if ( isset( $message ) ) {
2272 echo '<tr><td></td><td></td><td align="center">' . "\n";
2273 // 1.2.5: use direct echo option for message box
2274 $this->message_box( $message, true );
2275 echo '</td></tr>' . "\n";
2276 }
2277 } else {
2278 // --- maybe output welcome message ---
2279 // 1.0.5: use sanitize_title on request variable
2280 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
2281 if ( isset( $_REQUEST['welcome'] ) && ( 'true' == sanitize_text_field( wp_unslash( $_REQUEST['welcome'] ) ) ) ) {
2282 // 1.2.3: skip output if welcome message argument is empty
2283 if ( isset( $args['welcome'] ) && ( '' != $args['welcome'] ) ) {
2284 echo '<tr><td colspan="3" align="center">';
2285 // 1.2.5: use direct echo option for message box
2286 $this->message_box( $args['welcome'], true );
2287 echo '</td></tr>' . "\n";
2288 }
2289 }
2290 }
2291
2292 echo '</table><br>' . "\n";
2293 }
2294
2295 // -------------
2296 // Settings Page
2297 // -------------
2298 public function settings_page() {
2299
2300 $namespace = $this->namespace;
2301
2302 // --- open page wrapper ---
2303 echo '<div id="pagewrap" class="wrap" style="width:100%;margin-right:0 !important;">' . "\n";
2304
2305 do_action( $namespace . '_admin_page_top' );
2306
2307 // --- output settings header ---
2308 $this->settings_header();
2309
2310 do_action( $namespace . '_admin_page_middle' );
2311
2312 // --- output settings table ---
2313 $this->settings_table();
2314
2315 do_action( $namespace . '_admin_page_bottom' );
2316
2317 // --- close page wrapper ---
2318 echo '</div>' . "\n";
2319 }
2320
2321 // --------------
2322 // Settings Table
2323 // --------------
2324 // 1.0.9: created automatic Plugin Settings page
2325 // (based on passed plugin options and default settings)
2326 public function settings_table() {
2327
2328 // --- get all options and settings (unfiltered) ---
2329 $args = $this->args;
2330 $namespace = $this->namespace;
2331 $options = $this->options;
2332
2333 // --- get plugin options and default settings ---
2334 // 1.1.2: fix for filtering of plugin options
2335 $options = $this->options;
2336 $options = apply_filters( $namespace . '_options', $options );
2337
2338 $defaults = $this->default_settings();
2339 $settings = $this->get_settings( false );
2340
2341 // --- output saved settings ---
2342 if ( $this->debug ) {
2343 echo '<br><b>Saved Settings:</b><br>' . "\n";
2344 // phpcs:ignore WordPress.PHP.DevelopmentFunctions
2345 echo esc_html( print_r( $settings, true ) );
2346 echo '<br><br>' . "\n";
2347 }
2348
2349 // --- get option tabs and sections ---
2350 $tabs = $this->tabs;
2351 $sections = $this->sections;
2352
2353 $currenttab = '';
2354 // 1.3.4: allow for switching setting tab via querystring
2355 if ( isset( $_REQUEST['tab'] ) ) {
2356 $currenttab = sanitize_text_field( wp_unslash( $_REQUEST['tab'] ) );
2357 } elseif ( isset( $settings['settingstab'] ) ) {
2358 $currenttab = $settings['settingstab'];
2359 }
2360
2361 // --- loop options to maybe get tabbed groupings ---
2362 $taboptions = array();
2363 foreach ( $options as $key => $option ) {
2364 if ( isset( $option['tab'] ) && array_key_exists( $option['tab'], $tabs ) ) {
2365 if ( ( count( $sections ) > 0 ) && isset( $option['section'] ) ) {
2366 $taboptions[$option['tab']][$option['section']][$key] = $option;
2367 } else {
2368 $taboptions[$option['tab']]['general'][$key] = $option;
2369 }
2370 } else {
2371 if ( ( count( $sections ) > 0 ) && isset( $option['section'] ) ) {
2372 $taboptions['general'][$option['section']][$key] = $option;
2373 } else {
2374 $taboptions['general']['general'][$key] = $option;
2375 }
2376 }
2377 }
2378
2379 // --- remove unused tabs automatically ---
2380 // 1.1.7: added for cleaner interface while developing
2381 foreach ( $tabs as $tab => $tablabel ) {
2382 if ( !isset( $taboptions[$tab] ) ) {
2383 unset( $tabs[$tab] );
2384 }
2385 }
2386
2387 // --- maybe push general section to top of tab ---
2388 foreach ( $taboptions as $tab => $tabsections ) {
2389 if ( isset( $tabsections['general'] ) && ( count( $tabsections ) > 1 ) ) {
2390 $general = $tabsections['general'];
2391 unset( $tabsections['general'] );
2392 $taboptions[$tab] = array_merge( $general, $tabsections );
2393 }
2394 }
2395
2396 // --- maybe output tab groupings ---
2397 if ( count( $tabs ) > 0 ) {
2398
2399 // --- output tab switcher script ---
2400 // 1.0.9: add to settings scripts
2401 // 1.2.5: only store script reference
2402 // $this->scripts[] = 'tab_switcher';
2403
2404 $i = 0;
2405 echo '<ul id="settings-tab-buttons">' . "\n";
2406 foreach ( $tabs as $tab => $tablabel ) {
2407 $class = 'inactive';
2408 if ( ( $tab == $currenttab ) || ( ( '' == $currenttab ) && ( 0 == $i ) ) ) {
2409 $class = 'active';
2410 }
2411 // 1.2.5: remove onclick attribute and use jQuery click function
2412 // onclick="plugin_panel_display_tab(\'' . esc_attr( $tab ) . '\');"
2413 echo '<li id="' . esc_attr( $tab ) . '-tab-button" class="settings-tab-button ' . esc_attr( $class ) . '">' . esc_html( $tablabel ) . '</li>' . "\n";
2414 $i++;
2415 }
2416 echo '</ul>' . "\n";
2417 } else {
2418 $tabs = array( 'general' => __( 'General', 'radio-station' ) );
2419 }
2420
2421 // --- reset to default script ---
2422 // 1.0.9: add to settings scripts
2423 // $this->scripts[] = 'settings_reset';
2424
2425 // --- start settings form ---
2426 // 1.2.0: remove unused prefix on settings tab name attribute
2427 echo '<form method="post" id="settings-form">' . "\n";
2428 echo '<input type="hidden" name="' . esc_attr( $namespace ) . '_update_settings" id="settings-action" value="yes">' . "\n";
2429 echo '<input type="hidden" name="settingstab" id="settings-tab" value="' . esc_attr( $currenttab ) . '">' . "\n";
2430 wp_nonce_field( $args['slug'] . '_update_settings' );
2431
2432 // --- maybe set hidden debug input ---
2433 if ( $this->debug ) {
2434 echo '<input type="hidden" name="debug" value="yes">' . "\n";
2435 }
2436
2437 // ---- open wrapbox ---
2438 echo '<div id="wrapbox" class="postbox" style="line-height:2em;">' . "\n";
2439 echo '<div class="inner" style="padding-left:20px;">' . "\n";
2440
2441 // --- output tabbed sections ---
2442 $i = 0;
2443 foreach ( $tabs as $tab => $tablabel ) {
2444
2445 // --- open tab table output ---
2446 $class = 'inactive';
2447 if ( ( $currenttab == $tab ) || ( ( '' == $currenttab ) && ( 0 == $i ) ) ) {
2448 $class = 'active';
2449 }
2450 echo '<div id="' . esc_attr( $tab ) . '-tab" class="settings-tab ' . esc_attr( $class ) . '">' . "\n";
2451
2452 do_action( $namespace . '_admin_page_tab_' . $tab . '_top' );
2453
2454 echo '<table cellpadding="0" cellspacing="0">' . "\n";
2455
2456 if ( count( $sections ) > 0 ) {
2457
2458 $sectionheadings = array();
2459 foreach ( $sections as $section => $sectionlabel ) {
2460 if ( array_key_exists( $section, $taboptions[$tab] ) ) {
2461
2462 // --- section top ---
2463 // 1.2.5: fix to mismatched class setting-section-bottom
2464 echo '<tr class="setting-section-top"><td colspan="5">' . "\n";
2465 // 1.2.5: use do_action directly instead of using stored output
2466 do_action( $namespace . '_admin_page_section_' . $section . '_top' );
2467 echo '</td></tr>' . "\n";
2468
2469 // --- section heading ---
2470 if ( !isset( $sectionheadings[$section] ) ) {
2471 echo '<tr class="setting-section">' . "\n";
2472 echo '<td colspan="5"><h3>' . esc_html( $sectionlabel ) . '</h3></td>' . "\n";
2473 echo '</tr>' . "\n";
2474 $sectionheadings[$section] = true;
2475 }
2476
2477 // --- section setting rows ---
2478 foreach ( $taboptions[$tab][$section] as $key => $option ) {
2479 $option['key'] = $key;
2480 // 1.2.5: use wp_kses on setting row output with custom allowed HTML
2481 // echo $this->setting_row( $option );
2482 echo wp_kses( $this->setting_row( $option ), $this->allowed_html( $option ) );
2483
2484 }
2485 echo '<tr height="25"><td> </td></tr>' . "\n";
2486
2487 // --- section bottom hook ---
2488 echo '<tr class="setting-section-bottom"><td colspan="5">';
2489 // 1.2.5: use do_action directly instead of using stored output
2490 do_action( $namespace . '_admin_page_section_' . $section . '_bottom' );
2491 echo '</td></tr>' . "\n";
2492
2493 }
2494 }
2495 } else {
2496 foreach ( $taboptions[$tab]['general'] as $key => $option ) {
2497 $option['key'] = $key;
2498 echo '<tr height="25"><td> </td></tr>' . "\n";
2499 // 1.2.5: use wp_kses_post on setting output with custom allowed HTML
2500 echo wp_kses( $this->setting_row( $option ), $this->allowed_html( $option ) );
2501 echo '<tr height="25"><td> </td></tr>' . "\n";
2502 }
2503 }
2504
2505 // --- reset/save settings buttons ---
2506 // (filtered so removable from any specific tab)
2507 $buttons = '<tr height="25"><td> </td></tr>' . "\n";
2508 $buttons .= '<tr><td align="center">' . "\n";
2509 // 1.2.5: remove reset onclick attribute
2510 $buttons .= '<input type="button" id="settingsresetbutton" class="button-secondary settings-button reset-button" value="' . esc_attr( __( 'Reset Settings', 'radio-station' ) ) . '">' . "\n";
2511 $buttons .= '</td><td colspan="3"></td><td align="center">' . "\n";
2512 $buttons .= '<input type="submit" class="button-primary settings-button" value="' . esc_attr( __( 'Save Settings', 'radio-station' ) ) . '">' . "\n";
2513 $buttons .= '</td></tr>' . "\n";
2514 $buttons .= '<tr height="25"><td></td></tr>' . "\n";
2515 $buttons = apply_filters( $namespace . '_admin_save_buttons', $buttons, $tab );
2516 if ( $buttons ) {
2517 // 1.2.5: use wp_kses on filtered buttons output
2518 echo wp_kses( $buttons, $this->allowed_html() );
2519 }
2520
2521 // --- close table ---
2522 echo '</table>' . "\n";
2523
2524 // --- do below tab action ---
2525 do_action( $namespace . '_admin_page_tab_' . $tab . '_bottom' );
2526
2527 // --- close tab output ---
2528 echo '</div>' . "\n";
2529
2530 $i++;
2531 }
2532
2533 // --- close wrapbox ---
2534 echo '</div></div>' . "\n";
2535
2536 // --- close settings form ---
2537 echo '</form>' . "\n";
2538
2539 // --- enqueue settings resources ---
2540 // 1.3.4: moved settings resources enqueue to admin_enqueue_scripts
2541 }
2542
2543 // ---------------------
2544 // Allowed Inputs Filter
2545 // ---------------------
2546 // 1.2.5: added allowed inputs filter
2547 public function allowed_html( $option = false ) {
2548
2549 $namespace = $this->namespace;
2550
2551 // --- get default allowed post HTML ---
2552 $allowed = wp_kses_allowed_html( 'post' );
2553
2554 // --- input ---
2555 // 1.2.6: add missing checked attribute
2556 // 1.3.8: added data attributes for previews
2557 // 1.3.8: add minimum and maximum attributes
2558 $allowed['input'] = array(
2559 'id' => array(),
2560 'class' => array(),
2561 'name' => array(),
2562 'value' => array(),
2563 'type' => array(),
2564 'data' => array(),
2565 'placeholder' => array(),
2566 'checked' => array(),
2567 'min' => array(),
2568 'max' => array(),
2569 'data-alpha-enabled' => array(),
2570 'data-default-color' => array(),
2571 'data-key' => array(),
2572 'data-preview' => array(),
2573 'data-linked' => array(),
2574 'data-css' => array(),
2575 'data-settings' => array(),
2576 );
2577
2578 // --- textarea ---
2579 $allowed['textarea'] = array(
2580 'id' => array(),
2581 'class' => array(),
2582 'name' => array(),
2583 'value' => array(),
2584 'type' => array(),
2585 'placeholder' => array(),
2586 // 1.3.6: add rows and cols
2587 'rows' => array(),
2588 'cols' => array(),
2589 'data-key' => array(),
2590 'data-preview' => array(),
2591 'data-linked' => array(),
2592 'data-css' => array(),
2593 'data-settings' => array(),
2594 );
2595
2596 // --- select ---
2597 // 1.2.6: fix multiselect to multiple
2598 $allowed['select'] = array(
2599 'id' => array(),
2600 'class' => array(),
2601 'name' => array(),
2602 'value' => array(),
2603 'type' => array(),
2604 'multiple' => array(),
2605 'data-key' => array(),
2606 'data-preview' => array(),
2607 'data-linked' => array(),
2608 'data-css' => array(),
2609 'data-settings' => array(),
2610 );
2611
2612 // --- select option ---
2613 // 1.2.6: added missing value attribute
2614 $allowed['option'] = array(
2615 'selected' => array(),
2616 'value' => array(),
2617 );
2618
2619 // --- option group ---
2620 $allowed['optgroup'] = array(
2621 'label' => array(),
2622 );
2623
2624 // --- style tags ---
2625 $allowed['style'] = array(
2626 'id' => array(),
2627 );
2628
2629 $allowed = apply_filters( $namespace . '_settings_allowed_html', $allowed, $option );
2630 return $allowed;
2631 }
2632
2633 // ------------------
2634 // Settings Resources
2635 // ------------------
2636 // 1.2.3: added for standalone enqueueing of resources from table
2637 // 1.2.4: added missing public visibility declaration
2638 public function settings_resources( $media = true, $color_picker = true ) {
2639
2640 // 1.3.5: set default scripts to enqueue
2641 // 1.3.8: add preview script
2642 $this->scripts = array( 'notice_boxer', 'tab_switcher', 'settings_reset', 'previews' );
2643
2644 // --- number input step script ---
2645 // 1.0.9: added to script array
2646 // 1.1.8: fix to check for no mix or max value
2647 $this->scripts[] = 'number_step';
2648
2649 // --- image selection script ---
2650 // 1.1.7: added for image field type
2651 if ( $media ) {
2652 $this->scripts[] = 'media_functions';
2653 }
2654
2655 // --- color picker script ---
2656 if ( $color_picker ) {
2657 $this->scripts[] = 'colorpicker_init';
2658 }
2659
2660 // --- enqueue settings scripts ---
2661 // 1.3.4: change from admin_footer hook
2662 // 1.3.5: change back to admin_footer hook (for jQuery!)
2663 // 1.3.6: enqueue and append to dummy admin script
2664 // add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
2665 // add_action( 'admin_footer', array( $this, 'setting_scripts' ) );
2666 $this->enqueue_scripts();
2667
2668 // --- enqueue settings styles ---
2669 // 1.3.4: change from admin_footer hook
2670 // 1.3.6: enqueue and append to dummy admin style
2671 // add_action( 'admin_enqueue_styles', array( $this, 'enqueue_styles' ) );
2672 // add_action( 'admin_print_styles', array( $this, 'setting_styles' ) );
2673 $this->enqueue_styles();
2674
2675 }
2676
2677 // -----------
2678 // Setting Row
2679 // -----------
2680 // 1.0.9: added for automatic Settings table generation
2681 public function setting_row( $option ) {
2682
2683 // --- prepare setting keys ---
2684 $args = $this->args;
2685 $namespace = $this->namespace;
2686 $postkey = $args['settings'];
2687 $name = $postkey . '_' . $option['key'];
2688 $type = $option['type'];
2689 $setting = $this->get_setting( $option['key'], false );
2690
2691 $preview_data = '';
2692 if ( isset( $option['preview'] ) ) {
2693 $preview = $option['preview'];
2694 $preview_data = ' data-preview="1" ';
2695 $props = array( 'css', 'settings', 'linked', /* 'target', 'selector', 'alt-sel', 'property', 'alt-prop' */ );
2696 foreach ( $props as $prop ) {
2697 if ( isset( $preview[$prop] ) ) {
2698 $preview_data .= 'data-' . $prop . '="' . esc_attr( $preview[$prop] ) . '" ';
2699 }
2700 }
2701 }
2702
2703 // --- convert old option type names ---
2704 if ( 'email' == $type ) {
2705 $type = 'text';
2706 $option['options'] = 'EMAIL';
2707 } elseif ( 'emails' == $type ) {
2708 $type = 'text';
2709 $option['options'] = 'EMAIL';
2710 } elseif ( 'url' == $type ) {
2711 $type = 'text';
2712 $option['options'] = 'URL';
2713 } elseif ( 'urls' == $type ) {
2714 $type = 'text';
2715 $option['options'] = 'URL';
2716 } elseif ( 'alpabetic' == $type ) {
2717 $type = 'text';
2718 $option['options'] = 'ALPHABETIC';
2719 } elseif ( 'alphanumeric' == $type ) {
2720 $type = 'text';
2721 $option['options'] = 'ALPHANUMERIC';
2722 } elseif ( 'numeric' == $type ) {
2723 $option['options'] = 'NUMERIC';
2724 } elseif ( 'numeric' == $type ) {
2725 $option['options'] = 'NUMERIC';
2726 } elseif ( 'usernames' == $type ) {
2727 $type = 'text';
2728 $option['options'] = 'USERNAME';
2729 } elseif ( 'csvslugs' == $type ) {
2730 $type = 'text';
2731 $option['options'] = 'SLUG';
2732 } elseif ( 'pageid' == $type ) {
2733 $type = 'select';
2734 $option['options'] = 'PAGEID';
2735 } elseif ( 'postid' == $type ) {
2736 $type = 'select';
2737 $option['options'] = 'POSTID';
2738 } elseif ( 'pageids' == $type ) {
2739 $type = 'multiselect';
2740 $option['options'] = 'PAGEIDS';
2741 } elseif ( 'postids' == $type ) {
2742 $type = 'multiselect';
2743 $option['options'] = 'POSTIDS';
2744 }
2745 // TODO: password and multitoggle ?
2746
2747 // --- prepare row output ---
2748 $row = '<tr class="settings-row">' . "\n";
2749
2750 $row .= '<td class="settings-label">' . $option['label'] . "\n";
2751 if ( 'multiselect' == $type ) {
2752 $row .= '<br><span>' . esc_html( __( 'Use Ctrl and Click to Select', 'radio-station' ) ) . '</span>' . "\n";
2753 }
2754 $row .= '</td><td width="25"></td>' . "\n";
2755
2756 // 1.0.9: added multiple cell spanning note type
2757 if ( ( 'note' == $type ) || ( 'info' == $type ) || ( 'helper' == $type ) ) {
2758
2759 $row .= '<td class="settings-helper" colspan="3">' . "\n";
2760 if ( isset( $option['helper'] ) ) {
2761 $row .= $option['helper'];
2762 }
2763 $row .= '</td>' . "\n";
2764
2765 } else {
2766
2767 // TODO: add check if already Pro version ?
2768 // (currently done by removing pro key/value pair in Pro code)
2769 if ( isset( $option['pro'] ) && $option['pro'] ) {
2770
2771 // --- Pro version setting (teaser) ---
2772 // 1.2.0: improved handling of upgrade links
2773 $row .= '<td class="settings-input setting-pro">';
2774 $upgrade_link = false;
2775 $upgrade_target = '';
2776 if ( ( isset( $args['hasplans'] ) && $args['hasplans'] )
2777 || ( isset( $args['hasaddons'] ) && $args['hasaddons'] ) ) {
2778 $upgrade_link = add_query_arg( 'page', $args['slug'] . '-pricing', admin_url( 'admin.php' ) );
2779 }
2780 if ( isset( $args['upgrade_link'] ) ) {
2781 $upgrade_link = $args['upgrade_link'];
2782 $upgrade_target = !strstr( $upgrade_link, '/wp-admin/' ) ? ' target="_blank"' : '';
2783 }
2784 // 1.2.1: fix to check pro_link not upgrade_link
2785 if ( isset( $args['pro_link'] ) ) {
2786 $pro_link = $args['pro_link'];
2787 $pro_target = !strstr( $pro_link, '/wp-admin/' ) ? ' target="_blank"' : '';
2788 }
2789 if ( $upgrade_link || isset( $pro_link ) ) {
2790 // 1.2.2: change text from Available in Pro
2791 $row .= __( 'Premium Feature.', 'radio-station' ) . '<br>';
2792 if ( $upgrade_link ) {
2793 $row .= '<a href="' . esc_url( $upgrade_link ) . '"' . $upgrade_target . '>' . esc_html( __( 'Upgrade Now', 'radio-station' ) ) . '</a>';
2794 }
2795 if ( $upgrade_link && isset( $pro_link ) ) {
2796 $row .= ' | ';
2797 }
2798 if ( isset( $pro_link ) ) {
2799 // 1.2.2: change text from Pro details
2800 // 1.3.0: add hash link anchor for Pro feature options
2801 $option_anchor = str_replace( '_', '-', $option['key'] );
2802 $row .= '<a href="' . esc_url( $pro_link ) . '#' . esc_attr( $option_anchor ) . '"' . $pro_target . '>' . esc_html( __( 'Details', 'radio-station' ) ) . '</a>' . "\n";
2803 }
2804 } else {
2805 $row .= esc_html( __( 'Coming soon in Pro version!', 'radio-station' ) );
2806 }
2807 $row .= '</td>' . "\n";
2808
2809 } else {
2810
2811 $row .= '<td class="settings-input"';
2812 if ( 'preview' == $type ) {
2813 $row .= ' colspan="3"';
2814 }
2815 $row .= '>' . "\n";
2816
2817 // --- maybe prepare special options ---
2818 if ( isset( $option['options'] ) && is_string( $option['options'] ) ) {
2819
2820 // --- maybe prepare post/page options (once) ---
2821 if ( in_array( $option['options'], array( 'POSTID', 'POSTIDS', 'PAGEID', 'PAGEIDS' ) ) ) {
2822
2823 $posttype = strtolower( substr( $option['options'], 0, 4 ) );
2824 if ( ( ( 'page' == $posttype ) && !isset( $pageoptions ) ) || ( ( 'post' == $posttype ) && !isset( $postoptions ) ) ) {
2825
2826 global $wpdb;
2827 $results = $wpdb->get_results( $wpdb->prepare( "SELECT ID,post_title,post_status FROM " . $wpdb->prefix . "posts WHERE post_type = %s AND post_status != 'auto-draft'", $posttype ), ARRAY_A );
2828
2829 // 1.2.7: fix by moving page/post options variable here
2830 // 1.3.0: check separately to avoid overwriting existing options
2831 if ( !isset( $pageoptions ) ) {
2832 $pageoptions = array( '' => '' );
2833 }
2834 if ( !isset( $postoptions ) ) {
2835 $postoptions = array( '' => '' );
2836 }
2837 if ( $results && ( count( $results ) > 0 ) ) {
2838 foreach ( $results as $result ) {
2839 if ( strlen( $result['post_title'] ) > 35 ) {
2840 $result['post_title'] = substr( $result['post_title'], 0, 35 ) . '...';
2841 }
2842 $label = $result['ID'] . ': ' . $result['post_title'];
2843 if ( 'publish' != $result['post_status'] ) {
2844 $label .= ' (' . $result['post_status'] . ')';
2845 }
2846 if ( 'page' == $posttype ) {
2847 $pageoptions[$result['ID']] = $label;
2848 } elseif ( 'post' == $posttype ) {
2849 $postoptions[$result['ID']] = $label;
2850 }
2851 }
2852 }
2853 }
2854 if ( 'page' == $posttype ) {
2855 $option['options'] = $pageoptions;
2856 } elseif ( 'post' == $posttype ) {
2857 $option['options'] = $postoptions;
2858 }
2859 }
2860
2861 // --- maybe prepare public post type options (once) ---
2862 if ( in_array( $option['options'], array( 'PUBLICTYPE', 'PUBLICTYPES' ) ) ) {
2863 $publicoptions = array();
2864 $cpts = array( 'page', 'post' );
2865 $args = array( 'public' => true, '_builtin' => false );
2866 $cptlist = get_post_types( $args, 'names', 'and' );
2867 $cpts = array_merge( $cpts, $cptlist );
2868 foreach ( $cpts as $cpt ) {
2869 $posttypeobject = get_post_type_object( $cpt );
2870 $label = $posttypeobject->labels->singular_name;
2871 $publicoptions[$cpt] = $label;
2872 }
2873 $option['options'] = $publicoptions;
2874 }
2875
2876 // --- maybe prepare post type options (once) ---
2877 if ( in_array( $option['options'], array( 'POSTTYPE', 'POSTTYPES' ) ) ) {
2878 $cptoptions = array();
2879 $cpts = array( 'page', 'post' );
2880 $args = array( '_builtin' => false );
2881 $cptlist = get_post_types( $args, 'names', 'and' );
2882 $cpts = array_merge( $cpts, $cptlist );
2883 foreach ( $cpts as $cpt ) {
2884 $posttypeobject = get_post_type_object( $cpt );
2885 $label = $posttypeobject->labels->singular_name;
2886 $cptoptions[$cpt] = $label;
2887 }
2888 $option['options'] = $cptoptions;
2889 }
2890
2891 // --- maybe prepare all post type options (once) ---
2892 if ( in_array( $option['options'], array( 'ALLTYPE', 'ALLTYPES' ) ) ) {
2893 $allcptoptions = array();
2894 $args = array( '_builtin' => true );
2895 $cpts = get_post_types( $args, 'names', 'and' );
2896 foreach ( $cpts as $cpt ) {
2897 $posttypeobject = get_post_type_object( $cpt );
2898 $label = $posttypeobject->labels->singular_name;
2899 $allcptoptions[$cpt] = $label;
2900 }
2901 $option['options'] = $allcptoptions;
2902 }
2903
2904 // --- maybe prepare user options (once) ---
2905 if ( in_array( $option['options'], array( 'USERID', 'USERIDS', 'USERNAME', 'USERNAMES' ) ) ) {
2906 if ( in_array( $option['options'], array( 'USERID', 'USERIDS' ) ) ) {
2907 $userkey = 'userid';
2908 } else {
2909 $userkey = 'username';
2910 }
2911 $useroptions = array( '' => '' );
2912
2913 global $wpdb;
2914 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2915 $results = $wpdb->get_results( "SELECT ID,user_login,display_name FROM " . $wpdb->prefix . "users", ARRAY_A );
2916 if ( $results && ( count( $results ) > 0 ) ) {
2917 foreach ( $results as $result ) {
2918 $label = $result['ID'] . ': ' . $result['display_name'];
2919 if ( $result['display_name'] != $result['user_login'] ) {
2920 $label .= ' (' . $result['user_login'] . ')';
2921 }
2922 if ( 'userid' == $userkey ) {
2923 $useroptions[$result['ID']] = $label;
2924 } elseif ( 'username' == $userkey ) {
2925 $useroptions[$result['user_login']] = $label;
2926 }
2927 }
2928 }
2929 $option['options'] = $useroptions;
2930 }
2931
2932 }
2933
2934 // --- output option input types ---
2935 if ( 'toggle' == $type ) {
2936
2937 // --- toggle ---
2938 // 1.0.9: add toggle input (styled checkbox)
2939 // 1.1.7: set default option value if not set
2940 if ( !isset( $option['value'] ) ) {
2941 $option['value'] = '1';
2942 }
2943 $checked = ( $setting == $option['value'] ) ? ' checked="checked"' : '';
2944 $row .= '<label for="' . esc_attr( $name ) . '" class="setting setting-toggle">';
2945 $row .= '<input type="checkbox" name="' . esc_attr( $name ) . '" data-key="' . esc_attr( $option['key'] ) . '" class="setting-toggle" value="' . esc_attr( $option['value'] ) . '"' . $checked . $preview_data . '>' . "\n";
2946 $row .= '<span class="setting-slider round"></span>' . "\n";
2947 $row .= '</label>' . "\n";
2948 if ( isset( $option['suffix'] ) ) {
2949 $row .= ' ' . $option['suffix'];
2950 }
2951
2952 } elseif ( 'checkbox' == $type ) {
2953
2954 // --- checkbox ---
2955 // 1.1.7: set default option value if not set
2956 if ( !isset( $option['value'] ) ) {
2957 $option['value'] = '1';
2958 }
2959 $checked = ( $setting == $option['value'] ) ? ' checked="checked"' : '';
2960 $row .= '<input type="checkbox" name="' . esc_attr( $name ) . '" data-key="' . esc_attr( $option['key'] ) . '" class="setting setting-checkbox" value="' . esc_attr( $option['value'] ) . '"' . $checked . $preview_data . '>' . "\n";
2961 if ( isset( $option['suffix'] ) ) {
2962 $row .= ' ' . $option['suffix'];
2963 }
2964
2965 } elseif ( 'multicheck' == $type ) {
2966
2967 // --- multicheck boxes ---
2968 $checkboxes = array();
2969 foreach ( $option['options'] as $key => $label ) {
2970 $checked = '';
2971 if ( is_array( $setting ) && in_array( $key, $setting ) ) {
2972 $checked = ' checked="checked"';
2973 }
2974 $checkboxes[] = '<input type="checkbox" name="' . esc_attr( $name ) . "-" . esc_attr( $key ) . '" data-key="' . esc_attr( $option['key'] ) . '" class="setting setting-checkbox setting-multicheck" value="yes"' . $checked . $preview_data . '> ' . esc_html( $label ) . "\n";
2975 }
2976 $row .= implode( '<br>', $checkboxes );
2977
2978 } elseif ( 'radio' == $type ) {
2979
2980 // --- radio buttons ---
2981 $radios = array();
2982 foreach ( $option['options'] as $value => $label ) {
2983 $checked = ( $setting == $value ) ? ' checked="checked"' : '';
2984 $radios[] = '<input type="radio" data-key="' . esc_attr( $option['key'] ) . '" class="setting setting-radio" name="' . esc_attr( $name ) . "' value='" . esc_attr( $value ) . '"' . $checked . $preview_data . '> ' . esc_html( $label ) . "\n";
2985 }
2986 $row .= implode( '<br>', $radios );
2987
2988 } elseif ( 'select' == $type ) {
2989
2990 // --- select dropdown ---
2991 $row .= '<select data-key="' . esc_attr( $option['key'] ) . '" class="setting setting-select" name="' . esc_attr( $name ) . '"' . $preview_data . '>' . "\n";
2992 foreach ( $option['options'] as $value => $label ) {
2993 // 1.0.9: support option grouping (set unique key containing OPTGROUP-)
2994 if ( strstr( $value, '*OPTGROUP*' ) ) {
2995 $row .= '<optgroup label="' . esc_attr( $label ) . '">' . esc_html( $label ) . '</optgroup>' . "\n";
2996 } else {
2997 // 1.1.3: remove strict value checking
2998 $row .= '<option value="' . esc_attr( $value ) . '"';
2999 if ( $setting == $value ) {
3000 $row .= ' selected="selected"';
3001 }
3002 $row .= '>' . esc_html( $label ) . '</option>' . "\n";
3003 }
3004 }
3005 $row .= '</select>';
3006 if ( isset( $option['suffix'] ) ) {
3007 $row .= ' ' . $option['suffix'];
3008 }
3009
3010 } elseif ( 'multiselect' == $type ) {
3011
3012 // --- multiselect dropdown ---
3013 $row .= '<select multiple="multiple" data-key="' . esc_attr( $option['key'] ) . '" class="setting setting-select setting-multiselect" name="' . esc_attr( $name ) . '[]"' . $preview_data . '>' . "\n";
3014 foreach ( $option['options'] as $value => $label ) {
3015 if ( '' != $value ) {
3016 // 1.1.3: check for OPTGROUP instead of *OPTGROUP*
3017 if ( strstr( $value, 'OPTGROUP' ) ) {
3018 $row .= '<optgroup label="' . esc_attr( $label ) . '">' . "\n";
3019 } else {
3020 $selected = ( is_array( $setting ) && in_array( $value, $setting ) ) ? ' selected="selected"' : '';
3021 $row .= '<option value="' . esc_attr( $value ) . '"' . $selected . ">" . esc_html( $label ) . '</option>' . "\n";
3022 }
3023 }
3024 }
3025 $row .= '</select>';
3026 if ( isset( $option['suffix'] ) ) {
3027 $row .= ' ' . $option['suffix'];
3028 }
3029
3030 } elseif ( ( 'text' == $type ) || ( 'csv' == $type ) ) {
3031
3032 // 1.2.0: re-added missing csv field type
3033
3034 // --- text inputs ---
3035 $class = 'setting setting-text';
3036 if ( 'csv' == $type ) {
3037 $class .= ' setting-csv';
3038 }
3039 $placeholder = isset( $option['placeholder'] ) ? $option['placeholder'] : '';
3040
3041 // 1.1.7: fix to attribute quoting output
3042 $row .= '<input type="text" name="' . esc_attr( $name ) . '" data-key="' . esc_attr( $option['key'] ) . '" class="' . esc_attr( $class ) . '" value="' . esc_attr( $setting ) . '" placeholder="' . esc_attr( $placeholder ) . '"' . $preview_data . '>' . "\n";
3043 if ( isset( $option['suffix'] ) ) {
3044 $row .= ' ' . $option['suffix'];
3045 }
3046
3047 } elseif ( 'textarea' == $type ) {
3048
3049 // --- textarea input ---
3050 $rows = isset( $option['rows'] ) ? $option['rows'] : '6';
3051 $cols = isset( $option['cols'] ) ? $option['cols'] : '80';
3052 $placeholder = isset( $option['placeholder'] ) ? $option['placeholder'] : '';
3053
3054 // 1.2.4: added missing esc_textarea on value
3055 // 1.3.6: fixed rows attribute, added cols attribute
3056 $row .= '<textarea name="' . esc_attr( $name ) . '" data-key="' . esc_attr( $option['key'] ) . '" class="setting setting-textarea" rows="' . esc_attr( $rows ) . '" cols="' . esc_attr( $cols ) . '" placeholder="' . esc_attr( $placeholder ) . '"' . $preview_data . '>' . esc_textarea( $setting ) . '</textarea>' . "\n";
3057
3058 } elseif ( ( 'numeric' == $type ) || ( 'number' == $type ) ) {
3059
3060 // --- numeric text input ---
3061 // note: step key is only used for controls, not for validation
3062 $placeholder = isset( $option['placeholder'] ) ? $option['placeholder'] : '';
3063 $min = isset( $option['min'] ) ? $option['min'] : 'false';
3064 $max = isset( $option['max'] ) ? $option['max'] : 'false';
3065 $step = isset( $option['step'] ) ? $option['step'] : 1;
3066
3067 // 1.1.7: remove esc_js from onclick attributes
3068 // $onclickdown = "plugin_panel_number_step('down', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");" . "\n";
3069 // $row .= '<input class="setting-button button-secondary" type="button" value="-" onclick="' . $onclickdown . '">' . "\n";
3070 $row .= '<input class="number-button number-down-button setting-button button-secondary" type="button" value="-" data="' . esc_attr( $name ) . '">' . "\n";
3071 if ( isset( $option['prefix'] ) ) {
3072 $row .= ' ' . $option['prefix'];
3073 }
3074 $data = esc_attr( $min ) . "," . esc_attr( $max ) . "," . esc_attr( $step );
3075 $row .= '<input id="number-input-' . esc_attr( $name ) . '" data-key="' . esc_attr( $option['key'] ) . '" class="setting setting-numeric" type="text" name="' . esc_attr( $name ) . '" value="' . esc_attr( $setting ) . '" placeholder="' . esc_attr( $placeholder ) . '" data="' . esc_attr( $data ) . '"' . $preview_data . '>' . "\n";
3076 // $onclickup = "plugin_panel_number_step('up', '" . esc_attr( $name ) . "', " . esc_attr( $min ) . ", " . esc_attr( $max ) . ", " . esc_attr( $step ) . ");" . "\n";
3077 // $row .= '<input class="setting-button button-secondary" type="button" value="+" onclick="' . $onclickup . '">' . "\n";
3078 $row .= '<input class="number-button number-up-button setting-button button-secondary" type="button" value="+" data="' . esc_attr( $name ) . '">' . "\n";
3079 if ( isset( $option['suffix'] ) ) {
3080 $row .= ' ' . $option['suffix'];
3081 }
3082
3083 } elseif ( 'image' == $type ) {
3084
3085 // 1.1.7: added image attachment selection from media library
3086 // note: no preview data as the image IS the preview
3087
3088 // --- get current image ---
3089 $image = wp_get_attachment_image_src( $setting, 'full' );
3090 $has_image = is_array( $image );
3091
3092 // --- image container ---
3093 $row .= '<div class="custom-image-container">';
3094 if ( $has_image ) {
3095 $row .= '<img src="' . esc_url( $image[0] ) . '" alt="" style="max-width:100%;">' . "\n";
3096 }
3097 $row .= '</div>' . "\n";
3098
3099 // --- add and remove links ---
3100 $upload_link = get_upload_iframe_src( 'image' );
3101 $row .= '<p class="hide-if-no-js">' . "\n";
3102 $hidden = '';
3103 if ( $has_image ) {
3104 $hidden = ' hidden';
3105 }
3106 $row .= '<a class="upload-custom-image' . esc_attr( $hidden ) . '" href="' . esc_url( $upload_link ) . '">' . "\n";
3107 $row .= esc_html( __( 'Add Image', 'radio-station' ) );
3108 $row .= '</a>' . "\n";
3109
3110 $hidden = '';
3111 if ( !$has_image ) {
3112 $hidden = ' hidden';
3113 }
3114 $row .= '<a class="delete-custom-image' . esc_attr( $hidden ) . '" href="#">' . "\n";
3115 $row .= esc_html( __( 'Remove Image', 'radio-station' ) );
3116 $row .= '</a>' . "\n";
3117 $row .= '</p>' . "\n";
3118
3119 // --- hidden input for image ID ---
3120 $row .= '<input class="custom-image-id" name="' . esc_attr( $name ) . '" type="hidden" value="' . esc_attr( $setting ) . '">' . "\n";
3121
3122 } elseif ( 'color' == $type ) {
3123
3124 // 1.1.7: added color picker field
3125 $row .= '<input type="text" class="setting color-picker" data-default-color="' . esc_attr( $option['default'] ) . '" name="' . esc_attr( $name ) . '" value="' . esc_attr( $setting ) . '">' . "\n";
3126 $row .= '<input type="hidden" data-key="' . esc_attr( $option['key'] ) . '" class="setting color-picker-input" value="' . esc_attr( $setting ) . '"' . $preview_data . '>' . "\n";
3127
3128 } elseif ( 'coloralpha' == $type ) {
3129
3130 // 1.1.7: added color picker alpha field
3131 $row .= '<input type="text" class="setting color-picker" data-alpha-enabled="true" data-default-color="' . esc_attr( $option['default'] ) . '" name="' . esc_attr( $name ) . '" value="' . esc_attr( $setting ) . '">' . "\n";
3132 $row .= '<input type="hidden" data-key="' . esc_attr( $option['key'] ) . '" class="setting color-picker-input" value="' . esc_attr( $setting ) . '"' . $preview_data . '>' . "\n";
3133
3134 } elseif ( 'preview' == $type ) {
3135
3136 // 1.3.8: added dynamic preview container optional
3137 $settings = $this->get_settings( false );
3138 $row .= '<div id="preview-' . esc_attr( $option['key'] ) . '"';
3139 if ( isset( $option['width'] ) ) {
3140 $row .= ' width="' . esc_attr( $option['width'] ) . '"';
3141 }
3142 if ( isset( $option['height'] ) ) {
3143 $row .= ' height="' . esc_attr( $option['height'] ) . '"';
3144 }
3145 if ( isset( $option['classes'] ) ) {
3146 $all_classes = $classes = explode( ',', $option['classes'] );
3147 foreach ( $classes as $class ) {
3148 $class = trim( $class );
3149 foreach ( $settings as $key => $value ) {
3150 if ( '%%' . $key . '%%' == $class ) {
3151 if ( is_array( $value ) ) {
3152 $value = implode( ' ', $value );
3153 }
3154 $all_classes[] = str_replace( '%%' . $key . '%%', $value, $class );
3155 }
3156 }
3157 }
3158 foreach ( $all_classes as $i => $class ) {
3159 $all_classes[$i] = str_replace( '%%', '', $class );
3160 }
3161 $class_list = implode( ' ', $all_classes );
3162 $row .= ' class="' . esc_attr( $class_list ) .'"';
3163 }
3164 $row .= '>' ."\n";
3165 if ( isset( $option['html'] ) ) {
3166 $html = $option['html'];
3167 foreach ( $settings as $key => $value ) {
3168 $find = '%%' . $key . '%%';
3169 if ( strstr( $html, $find ) ) {
3170 $html = str_replace( $find, $value, $html );
3171 }
3172 }
3173 $row .= $html;
3174 }
3175 $row .= '</div>' . "\n";
3176
3177 if ( isset( $option['css'] ) ) {
3178 $css = $option['css'];
3179 $target = $option['key'];
3180 $target_selector = '#preview-' . esc_attr( $target );
3181 $css = str_replace( '%%target%%', $target_selector, $css );
3182 $settings = $this->get_settings( false );
3183 foreach ( $settings as $key => $value ) {
3184 $find = '%%' . $key . '%%';
3185 if ( strstr( $css, $find ) ) {
3186 $css = str_replace( $find, $value, $css );
3187 }
3188 }
3189 $row .= '<style id="preview-' . esc_attr( $option['key'] ) . '-css">' . $css . '</style>' . "\n";
3190 }
3191
3192 }
3193
3194 $row .= '</td>';
3195 }
3196
3197 // --- setting helper text ---
3198 if ( isset( $option['helper'] ) ) {
3199 if ( 'preview' != $type ) {
3200 $row .= '<td width="25"></td>' . "\n";
3201 $row .= '<td class="settings-helper">' . esc_html( $option['helper'] ) . '</td>' . "\n";
3202 } else {
3203 $row .= '</tr><tr><td></td><td colspan="3" class="settings-helper">' . esc_html( $option['helper'] ) . '</td></tr>' . "\n";
3204 }
3205 }
3206
3207 }
3208
3209 $row .= '</tr>' . "\n";
3210
3211 // --- setting preview ---
3212 // 1.3.8: added image preview option
3213 if ( isset( $option['preview'] ) && isset( $option['preview']['type'] ) ) {
3214 $row .= '<tr>';
3215 $row .= '<td style="text-align:right;">' . esc_html( __( 'Preview', 'radio-station' ) ) . ':</td>' . "\n";
3216 $row .= '<td width="25"></td>' . "\n";
3217 $row .= '<td>';
3218 if ( 'image' == $option['preview']['type'] ) {
3219 $width = $option['preview']['width'];
3220 $height = $option['preview']['height'];
3221 $sources = $this->get_preview_images( $option );
3222 // echo 'Image Sources: ' . print_r( $sources, true );
3223 $row .= '<div class="preview-image" id="preview-' . esc_attr( $option['key'] ) . '"></div>' . "\n";
3224 $row .= '<style>#preview-' . esc_attr( $option['key'] ) . ' {background-image: url("' . esc_url( $sources['source'] ) . '"); background-size: 100% 100%;';
3225 $row .= 'width: ' . esc_attr( $width ) . 'px; height: ' . esc_attr( $height ) . 'px';
3226 $row .= '}' . "\n";
3227 if ( isset( $sources['alt'] ) && ( '' != $sources['alt'] ) ) {
3228 $row .= '#preview-' . esc_attr( $option['key'] ) . ' {cursor: pointer;}' . "\n";
3229 $row .= '#preview-' . esc_attr( $option['key'] ) . ':hover, #preview-' . esc_attr( $option['key'] ) . '.active {background-image: url("' . esc_url( $sources['alt'] ) . '"); background-size: 100% 100%;}' . "\n";
3230 }
3231 $row .= '</style>' . "\n";
3232 } elseif ( 'html' == $option['preview']['type'] ) {
3233 $row .= '<div class="preview-html" id="preview-' . esc_attr( $option['key'] ) . '">' . wp_kses( $option['preview']['html'], $this->allowed_html( $option ) ) . '</div>' . "\n";
3234 }
3235
3236 $row .= '</td>' . "\n";
3237 $row .= '</tr>' . "\n";
3238 }
3239
3240 // --- settings row spacer ---
3241 $row .= '<tr class="settings-spacer"><td>';
3242 if ( isset( $option['preview']['css'] ) ) {
3243 /* $css = $option['preview']['css'];
3244 if ( isset( $option['preview']['target'] ) ) {
3245 $target = $option['preview']['target'];
3246 $target_selector = '#preview-' . esc_attr( $target );
3247 $css = str_replace( '%%target%%', $target_selector, $css );
3248 }
3249 $settings = $this->get_settings( false );
3250 $css = $option['preview']['css'];
3251 foreach ( $settings as $key => $value ) {
3252 $find = '%%' . $key . '%%';
3253 if ( strstr( $css, $find ) ) {
3254 $css = str_replace( $find, $value, $css );
3255 }
3256 } */
3257 $css = '';
3258 $row .= '<style id="preview-' . esc_attr( $option['key'] ) . '-css">' . $css . '</style>';
3259 }
3260 $row .= '</td></tr>' . "\n";
3261
3262 // --- filter and return setting row ---
3263 $row = apply_filters( $namespace . '_setting_row', $row, $option );
3264
3265 return $row;
3266 }
3267
3268 // -----------------
3269 // Get Preview Image
3270 // -----------------
3271 public function get_preview_images( $option ) {
3272 $source = $alt = '';
3273 $settings = $this->get_settings( false );
3274 $setting = $this->get_setting( $option['key'], false );
3275 // echo $option['key'] . ' - ' . $setting;
3276 if ( isset( $option['preview']['sources'][$setting] ) ) {
3277 $source = $option['preview']['sources'][$setting];
3278 if ( isset( $option['preview']['sources-alt'][$setting] ) ) {
3279 $alt = $option['preview']['sources-alt'][$setting];
3280 }
3281 foreach( $settings as $key => $value ) {
3282 $find = '%%' . $key . '%%';
3283 if ( strstr( $source, $find ) ) {
3284 $source = str_replace( $find, $value, $source );
3285 }
3286 if ( strstr( $alt, $find ) ) {
3287 $alt = str_replace( $find, $value, $alt );
3288 }
3289 }
3290 }
3291 $sources = array(
3292 'source' => $source,
3293 'alt' => $alt,
3294 );
3295 return $sources;
3296 }
3297
3298 // ---------------
3299 // Enqueue Scripts
3300 // ---------------
3301 // 1.3.6: enqueue scripts inline via dummy script
3302 public function enqueue_scripts() {
3303
3304 $version = $this->plugin_version();
3305 wp_register_script( 'plugin-admin-settings', null, array( 'jquery' ), $version, true );
3306 wp_enqueue_script( 'plugin-admin-settings' );
3307 $js = $this->setting_scripts();
3308 wp_add_inline_script( 'plugin-admin-settings', $js, 'after' );
3309
3310 }
3311
3312 // ---------------
3313 // Setting Scripts
3314 // ---------------
3315 // 1.0.9: added settings page scripts
3316 public function setting_scripts() {
3317
3318 $args = $this->args;
3319 $scripts = $this->scripts;
3320
3321 if ( count( $scripts ) > 0 ) {
3322
3323 // 1.3.6: buffer script output
3324 ob_start();
3325
3326 foreach ( $scripts as $script ) {
3327
3328 // 1.2.5: output scripts based on stored script keys
3329 if ( 'tab_switcher' == $script ) {
3330
3331 // --- output tab switcher function ---
3332 // 1.2.5: changed function prefix for consistency
3333 /* echo "function plugin_panel_display_tab(tab) {" . "\n";
3334 foreach ( $tabs as $tab => $label ) {
3335 echo " document.getElementById('" . esc_js( $tab ) . "-tab-button').className = 'settings-tab-button inactive';" . "\n";
3336 echo " document.getElementById('" . esc_js( $tab ) . "-tab').className = 'settings-tab inactive'; " . "\n";
3337 }
3338 echo " document.getElementById(tab+'-tab-button').className = 'settings-tab-button active';" . "\n";
3339 echo " document.getElementById(tab+'-tab').className = 'settings-tab active';" . "\n";
3340 echo " document.getElementById('settings-tab').value = tab;" . "\n";
3341 echo "}" . "\n"; */
3342
3343 // 1.2.5: use jQuery click function to remove onclick button attributes
3344 echo "jQuery('.settings-tab-button').on('click', function() {" . "\n";
3345 echo " tab = jQuery(this).attr('id').replace('-tab-button','');" . "\n";
3346 echo " jQuery('.settings-tab,.settings-tab-button').removeClass('active').addClass('inactive');" . "\n";
3347 echo " jQuery('#'+tab+'-tab,#'+tab+'-tab-button').removeClass('inactive').addClass('active');" . "\n";
3348 echo " jQuery('#settings-tab').val(tab);" . "\n";
3349 echo "});" . "\n";
3350
3351 } elseif ( 'notice_boxer' == $script ) {
3352
3353 // --- toggle notice box script ---
3354 echo "function settings_toggle_notices() {
3355 if (document.getElementById('admin-notices-wrap').style.display == '') {
3356 document.getElementById('admin-notices-wrap').style.display = 'none';
3357 document.getElementById('admin-notices-arrow').innerHTML = '&#9656;';
3358 } else {
3359 document.getElementById('admin-notices-wrap').style.display = '';
3360 document.getElementById('admin-notices-arrow').innerHTML= '&#9662;';
3361 }
3362 }" . "\n";
3363
3364 // --- modified from /wp-admin/js/common.js to move notices ---
3365 echo "jQuery(document).ready(function() {
3366 setTimeout(function() {
3367 jQuery('div.update-nag, div.updated, div.error, div.notice').not('.inline, .below-h2').insertAfter(jQuery('#admin-notices-wrap h2'));
3368 count = parseInt(jQuery('#admin-notices-wrap').children().length - 1);
3369 if (count > 0) {jQuery('#admin-notices-count').html('('+count+')');}
3370 else {jQuery('#admin-notices-box').hide();}
3371 }, 500);
3372 });" . "\n";
3373
3374 } elseif ( 'settings_reset' == $script ) {
3375
3376 // --- reset settings function ---
3377 // 1.2.5: changed function prefix for consistency
3378 // 1.2.5: changed to jQuery click function to remove onclick button attribute
3379 $confirmreset = __( 'Are you sure you want to reset to default settings?', 'radio-station' );
3380 // echo "function plugin_panel_reset_defaults() {" . "\n";
3381 // 1.3.7: fix to use class not ID
3382 echo "jQuery('.reset-button').on('click', function() {" . "\n";
3383 echo " agree = confirm('" . esc_js( $confirmreset ) . "');" . "\n";
3384 echo " if (!agree) {return false;}" . "\n";
3385 echo " document.getElementById('settings-action').value = 'reset';" . "\n";
3386 echo " document.getElementById('settings-form').submit();" . "\n";
3387 echo "});" . "\n";
3388 // echo "}" . "\n";
3389
3390 } elseif ( 'number_step' == $script ) {
3391
3392 // --- number step function ---
3393 // 1.2.5: changed function prefix for consistency
3394 /*echo "function plugin_panel_number_step(updown, id, min, max, step) {
3395 if (updown == 'up') {multiplier = 1;} else if (updown == 'down') {multiplier = -1;}
3396 current = parseInt(document.getElementById(id).value);
3397 newvalue = current + (multiplier * parseInt(step));
3398 if ((min !== false) && (newvalue < parseInt(min))) {newvalue = min;}
3399 if ((max !== false) && (newvalue > parseInt(max))) {newvalue = max;}
3400 document.getElementById(id).value = newvalue;
3401 }" . "\n"; */
3402 // 1.2.5: replace with jQuery click function to remove onclick attributes
3403 // 1.2.9: fix for possible empty value converting to NaN
3404 echo "jQuery('.number-button').on('click', function() {
3405 if (jQuery(this).hasClass('number-up-button')) {multiplier = 1;}
3406 else if (jQuery(this).hasClass('number-down-button')) {multiplier = -1;}
3407 idref = 'number-input-'+jQuery(this).attr('data');
3408 data = jQuery('#'+idref).attr('data').split(',');
3409 min = data[0]; max = data[1]; step = data[2];
3410 value = jQuery('#'+idref).val();
3411 if (value == '') {value = 0;} else {value = parseInt(value);}
3412 newvalue = value + (multiplier * parseInt(step));
3413 if ((min !== false) && (newvalue < parseInt(min))) {newvalue = min;}
3414 if ((max !== false) && (newvalue > parseInt(max))) {newvalue = max;}
3415 jQuery('#'+idref).val(newvalue);
3416 });" . "\n";
3417
3418 } elseif ( 'media_functions' == $script ) {
3419
3420 // --- media functions ---
3421 $confirm_remove = __( 'Are you sure you want to remove this image?', 'radio-station' );
3422 echo "jQuery(function(){
3423
3424 var mediaframe, parentdiv;
3425
3426 /* Add Image on Click */
3427 jQuery('.upload-custom-image').on( 'click', function( event ) {
3428
3429 event.preventDefault();
3430 parentdiv = jQuery(this).parent().parent();
3431
3432 if (mediaframe) {mediaframe.open(); return;}
3433 mediaframe = wp.media({
3434 title: 'Select or Upload Image',
3435 button: {text: 'Use this Image'},
3436 multiple: false
3437 });
3438
3439 mediaframe.on( 'select', function() {
3440 var attachment = mediaframe.state().get('selection').first().toJSON();
3441 image = '<img src=\"'+attachment.url+'\" alt=\"\" style=\"max-width:100%;\"/>';
3442 parentdiv.find('.custom-image-container').append(image);
3443 parentdiv.find('.custom-image-id').val(attachment.id);
3444 parentdiv.find('.upload-custom-image').addClass('hidden');
3445 parentdiv.find('.delete-custom-image').removeClass('hidden');
3446 });
3447
3448 mediaframe.open();
3449 jQuery('.media-modal-close').on( 'click', function() {
3450 console.log('close click detected');
3451 mediaframe.close();
3452 });
3453 });
3454
3455 /* Delete Image on Click */
3456 jQuery('.delete-custom-image').on( 'click', function( event ) {
3457 event.preventDefault();
3458 agree = confirm('" . esc_js( $confirm_remove ) . "');
3459 if (!agree) {return;}
3460 parentdiv = jQuery(this).parent().parent();
3461 parentdiv.find('.custom-image-container').html('');
3462 parentdiv.find('.custom-image-id').val('');
3463 parentdiv.find('.upload-custom-image').removeClass('hidden');
3464 parentdiv.find('.delete-custom-image').addClass('hidden');
3465 });
3466
3467 });" . "\n";
3468
3469 } elseif ( 'colorpicker_init' == $script ) {
3470
3471 // --- initialize color pickers ---
3472 echo "jQuery(document).ready(function() {" . "\n";
3473 echo "if (jQuery('.color-picker').length) {" . "\n";
3474 echo "jQuery('.color-picker').wpColorPicker({
3475 change: function(event, ui) {
3476 color = ui && ui.color ? ui.color.toString() : jQuery(this).val();
3477 console.log('Color changed:', color);
3478 console.log(jQuery(this));
3479 input = jQuery(this).closest('.settings-input').find('.color-picker-input');
3480 console.log(input);
3481 input.val(color).trigger('change');
3482 },
3483 clear: function() {console.log('Color cleared');}
3484 });" . "\n";
3485 echo "}" . "\n";
3486 echo "});" . "\n";
3487
3488 } elseif ( 'previews' == $script ) {
3489
3490 // --- preview classes active toggle ---
3491 // 1.3.8: toggle active class on preview clicks
3492 echo "jQuery('.preview-image, .preview-button').on('click', function(e) {
3493 /* if (jQuery(this).data('toggling')) {return;}
3494 jQuery(this).data('toggling', true); */
3495 if (!jQuery(this).hasClass('active')) {jQuery(this).addClass('active');}
3496 else {jQuery(this).removeClass('active');}
3497 /* setTimeout(() => jQuery(this).removeData('toggling'), 50); */
3498 });" . "\n";
3499
3500 // --- add classes and preview css ---
3501 // 1.3.8: added for previews
3502 echo "jQuery(document).ready(function() {
3503 jQuery('.setting').each(function() {
3504 if (jQuery(this).attr('data-preview') == '1') {
3505 jQuery(this).on('change', function() {
3506 console.log(jQuery(this));
3507 linked = jQuery(this).attr('data-linked');
3508 if (typeof linked != 'undefined') {
3509 jQuery('.setting').each(function() {
3510 if (jQuery(this).attr('data-key') == linked) {
3511 console.log('trigger change for linked setting: '+linked);
3512 jQuery(this).trigger('change');
3513 }
3514 });
3515 }
3516 /* target = jQuery(this).attr('data-target'); */
3517 key = jQuery(this).attr('data-key');
3518 css = jQuery(this).attr('data-css');
3519 if (typeof css == 'undefined') {css = '';}
3520 settings = jQuery(this).attr('data-settings');
3521 if (typeof settings == 'undefined') {settings = [key];}
3522 else if (settings.indexOf(',') > -1) {settings = settings.split(',');}
3523 else {settings = [settings];}
3524 console.log(settings);
3525 for (i = 0; i < settings.length; i++) {
3526 value = null; values = []; options = [];
3527 jQuery('.setting').each(function() {
3528 if (jQuery(this).attr('data-key') == settings[i]) {
3529 if (!jQuery(this).hasClass('setting-multicheck') && !jQuery(this).hasClass('setting-radio')) {
3530 value = jQuery(this).val();
3531 values = [value];
3532 console.log(settings[i]+' -> '+value);
3533 } else {
3534 /* TODO: multiselect */
3535 val = jQuery(this).val();
3536 options.push(val);
3537 if (jQuery(this).prop('checked')) {values.push(val);}
3538 }
3539 }
3540 });
3541 if (jQuery('.'+settings[i]).length) {
3542 jQuery('.'+settings[i]).each(function() {
3543 for (j = 0; j < options.length; j++) {jQuery(this).removeClass(options[j]);}
3544 for (j = 0; j < values.length; j++) {jQuery(this).addClass(values);}
3545 });
3546 }
3547 find = '%%'+settings[i]+'%%';
3548 if (value != null) {css = css.replaceAll(find,value);}
3549 }
3550 if (typeof custom_css_preview == 'function' ) {css = custom_css_preview(css);}
3551 jQuery('#preview-'+key+'-css').html(css);
3552
3553 /* selector = jQuery(this).attr('data-selector');
3554 property = jQuery(this).attr('data-property');
3555 jQuery('#'+selector).css({property:value}); */
3556 });
3557 }
3558 });
3559 });" . "\n";
3560 }
3561
3562 }
3563
3564 // 1.2.5: added for possible extra settings scripts
3565 do_action( $args['namespace'] . '_settings_scripts', $args );
3566
3567 $js = ob_get_contents();
3568 ob_end_clean();
3569 return $js;
3570
3571 }
3572 }
3573
3574 // --------------
3575 // Enqueue Styles
3576 // --------------
3577 // 1.3.6: enqueue styles inline via dummy stylesheet
3578 public function enqueue_styles() {
3579
3580 $version = $this->plugin_version();
3581 wp_register_style( 'plugin-admin-settings', null, array(), $version, 'all' );
3582 wp_enqueue_style( 'plugin-admin-settings' );
3583 $css = $this->setting_styles();
3584 wp_add_inline_style( 'plugin-admin-settings', $css );
3585
3586 }
3587
3588 // --------------
3589 // Setting Styles
3590 // --------------
3591 public function setting_styles() {
3592
3593 $styles = array();
3594
3595 // --- page styles ---
3596 // 1.2.9: add padding to bottom of settings form
3597 $styles[] = '#wrapbox {margin-right: 20px; padding-bottom: 20px;}';
3598
3599 // --- plugin header styles ---
3600 // 1.2.0: moved from plugin header section
3601 // 1.2.3: remove underline from plugin icon spans
3602 $styles[] = '.pluginlink, .pluginlink span {text-decoration:none;}';
3603 $styles[] = '.smalllink {font-size:11px;}';
3604 $styles[] = '.readme:hover {text-decoration:underline;}';
3605
3606 // --- settings tab styles ---
3607 // 1.1.0: added max-width:100% to select input
3608 // 1.2.7: add pointer cursor to inactive tabs
3609 $styles[] = '.settings-tab-button {display:inline-block; font-size:15px; padding:7px 14px; margin-right:20px; border-radius:7px;}';
3610 $styles[] = '.settings-tab-button.active {font-weight:bold; background-color:#0073aa; color:#FFF; border:1px solid #FFF;}';
3611 $styles[] = '.settings-tab-button.inactive {font-weight:bold; background-color:#F5F5F5; color:#0073aa; border:1px solid #000; cursor:pointer;}';
3612 $styles[] = '.settings-tab-button.inactive:hover {background-color:#FFFFFF; color:#00a0d2;}';
3613 $styles[] = '.settings-tab.active {display:block;}';
3614 $styles[] = '.settings-tab.inactive {display:none;}';
3615
3616 // --- setting row styles ---
3617 $styles[] = '.settings-label {vertical-align:top; font-weight:bold; min-width:100px; max-width:200px;}';
3618 $styles[] = '.settings-label span {font-weight: normal; font-size:10px;}';
3619 $styles[] = '.settings-helper {vertical-align:top; font-style:italic; min-width:200px; max-width:300px;}';
3620 $styles[] = '.settings-spacer {height: 7px;}';
3621
3622 // --- setting input styles ---
3623 $styles[] = '.settings-input {vertical-align:top; min-width:100px; max-width:300px;}';
3624 // $styles[] = '.settings-input input.setting-radio {}';
3625 // $styles[] = '.settings-input input.setting-checkbox {}';
3626 $styles[] = '.settings-input input.setting-text {width:100%;}';
3627 $styles[] = '.settings-input input.setting-numeric {display:inline-block; width:50%; text-align:center; vertical-align:middle;}';
3628 $styles[] = '.settings-input input.setting-button {display:inline-block; padding:0px 5px;}';
3629 $styles[] = '.settings-input input.setting-button.number-down-button {padding:0px 7px; font-weight:bold;}';
3630 $styles[] = '.settings-input input.setting-textarea {width:100%;}';
3631 $styles[] = '.settings-input select.setting-select {min-width:100px; max-width:100%;}';
3632
3633 // --- toggle input styles ---
3634 // Ref: https://www.w3schools.com/howto/howto_css_switch.asp
3635 $styles[] = '.setting-toggle {position:relative; display:inline-block; width:30px; height:17px;}
3636 .setting-toggle input {opacity:0; width:0; height:0;}
3637 .setting-slider {position:absolute; cursor:pointer;
3638 top:0; left:0; right:0; bottom:0; background-color:#ccc;
3639 -webkit-transition:.4s; transition:.4s;
3640 }
3641 .setting-slider:before {position:absolute; content:""; height:13px; width:13px;
3642 left:2px; bottom:2px; background-color:white; -webkit-transition:.4s; transition:.4s;
3643 }
3644 input:checked + .setting-slider {background-color:#2196F3;}
3645 input:focus + .setting-slider {box-shadow:0 0 1px #2196F3;}
3646 input:checked + .setting-slider:before {
3647 -webkit-transform:translateX(13px); -ms-transform:translateX(13px); transform:translateX(13px);
3648 }
3649 .setting-slider.round {border-radius: 17px;}
3650 .setting-slider.round:before {border-radius: 50%;}';
3651
3652 // --- color picker styles ---
3653 // 1.2.7: added to overlay active color picker
3654 $styles[] = '.wp-picker-active {position:absolute; z-index:999; background-color:#FFF; border:1px solid #999; padding:5px;}';
3655 $styles[] = '.wp-picker-active .wp-picker-input-wrap {display:block;}';
3656 $styles[] = '.wp-color-picker {max-width:200px;}';
3657
3658 // --- filter and output styles ---
3659 $namespace = $this->namespace;
3660 $styles = apply_filters( $namespace . '_admin_page_styles', $styles );
3661 $styles_string = implode( "\n", $styles );
3662
3663 // 1.2.5: added wp_strip_all_tags to styles output
3664 // 1.3.0: use wp_kses_post on styles output
3665 // 1.3.6: return style string instead of echo
3666 // echo wp_strip_all_tags( istyles_string ) );
3667 return $styles_string;
3668 }
3669
3670 }
3671 } // end Plugin Loader Class
3672
3673
3674 // ----------------------------------
3675 // Load Namespaced Prefixed Functions
3676 // ----------------------------------
3677 // [Optional] rename functions prefix to your plugin namespace
3678 // these functions will then be available within your plugin
3679 // to more easily call the matching plugin loader class methods
3680
3681 // 1.0.3: added priority of 0 to prefixed function loading action
3682 add_action( 'plugins_loaded', 'radio_station_load_prefixed_functions', 0 );
3683
3684 if ( !function_exists( 'radio_station_load_prefixed_functions' ) ) {
3685 function radio_station_load_prefixed_functions() {
3686
3687 // ------------------
3688 // Get Namespace Slug
3689 // ------------------
3690 // --- Auto-magic Mamespacing Slug Note ---
3691 // the below functions use the function name to grab and load the corresponding class method
3692 // all function name suffixes here must be two words for the magic namespace grabber to work
3693 // ie. _add_settings, because the namespace is taken from *before the second-last underscore*
3694 if ( !function_exists( 'radio_station_get_namespace_from_function' ) ) {
3695 function radio_station_get_namespace_from_function( $f ) {
3696 return substr( $f, 0, strrpos( $f, '_', ( strrpos( $f, '_' ) - strlen( $f ) - 1 ) ) );
3697 }
3698 }
3699
3700 // -------------------
3701 // Get Loader Instance
3702 // -------------------
3703 // 2.3.0: added function for getting loader class instance
3704 if ( !function_exists( 'radio_station_loader_instance' ) ) {
3705 function radio_station_loader_instance() {
3706 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3707
3708 return $GLOBALS[$namespace . '_instance'];
3709 }
3710 }
3711
3712 // ---------------------
3713 // Get Freemius Instance
3714 // ---------------------
3715 // 2.3.0: added function for getting Freemius class instance
3716 if ( !function_exists( 'radio_station_freemius_instance' ) ) {
3717 function radio_station_freemius_instance() {
3718 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3719
3720 return $GLOBALS[$namespace . '_freemius'];
3721 }
3722 }
3723
3724 // ---------------
3725 // Get Plugin Data
3726 // ---------------
3727 // 1.1.1: added function for getting plugin data
3728 if ( !function_exists( 'radio_station_plugin_data' ) ) {
3729 function radio_station_plugin_data() {
3730 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3731 $instance = $GLOBALS[$namespace . '_instance'];
3732
3733 return $instance->plugin_data();
3734 }
3735 }
3736
3737 // ------------------
3738 // Get Plugin Version
3739 // ------------------
3740 // 1.1.2: added function for getting plugin version
3741 if ( !function_exists( 'radio_station_plugin_version' ) ) {
3742 function radio_station_plugin_version() {
3743 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3744 $instance = $GLOBALS[$namespace . '_instance'];
3745
3746 return $instance->plugin_version();
3747 }
3748 }
3749
3750 // -----------------
3751 // Set Pro Namespace
3752 // -----------------
3753 if ( !function_exists( 'radio_station_pro_namespace' ) ) {
3754 function radio_station_pro_namespace( $pronamespace ) {
3755 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3756 $instance = $GLOBALS[$namespace . '_instance'];
3757 $instance->pro_namespace( $pronamespace );
3758 }
3759 }
3760
3761 // ===============
3762 // Plugin Settings
3763 // ===============
3764
3765 // ------------
3766 // Add Settings
3767 // ------------
3768 if ( !function_exists( 'radio_station_add_settings' ) ) {
3769 function radio_station_add_settings() {
3770 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3771 $instance = $GLOBALS[$namespace . '_instance'];
3772 $instance->add_settings();
3773 }
3774 }
3775
3776 // ------------
3777 // Get Defaults
3778 // ------------
3779 if ( !function_exists( 'radio_station_default_settings' ) ) {
3780 function radio_station_default_settings( $key = false ) {
3781 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3782 $instance = $GLOBALS[$namespace . '_instance'];
3783
3784 return $instance->default_settings( $key );
3785 }
3786 }
3787
3788 // -----------
3789 // Get Options
3790 // -----------
3791 if ( !function_exists( 'radio_station_get_options' ) ) {
3792 function radio_station_get_options() {
3793 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3794 $instance = $GLOBALS[$namespace . '_instance'];
3795
3796 return $instance->options;
3797 }
3798 }
3799
3800 // -----------
3801 // Get Setting
3802 // -----------
3803 if ( !function_exists( 'radio_station_get_setting' ) ) {
3804 function radio_station_get_setting( $key, $filter = true ) {
3805 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3806 $instance = $GLOBALS[$namespace . '_instance'];
3807
3808 return $instance->get_setting( $key, $filter );
3809 }
3810 }
3811
3812 // ----------------
3813 // Get All Settings
3814 // ----------------
3815 // 1.0.9: added missing get_settings prefixed function
3816 if ( !function_exists( 'radio_station_get_settings' ) ) {
3817 function radio_station_get_settings( $filter = true ) {
3818 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3819 $instance = $GLOBALS[$namespace . '_instance'];
3820
3821 return $instance->get_settings( $filter );
3822 }
3823 }
3824
3825 // --------------
3826 // Reset Settings
3827 // --------------
3828 if ( !function_exists( 'radio_station_reset_settings' ) ) {
3829 function radio_station_reset_settings() {
3830 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3831 $instance = $GLOBALS[$namespace . '_instance'];
3832 $instance->reset_settings();
3833 }
3834 }
3835
3836 // ---------------
3837 // Update Settings
3838 // ---------------
3839 if ( !function_exists( 'radio_station_update_settings' ) ) {
3840 function radio_station_update_settings() {
3841 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3842 $instance = $GLOBALS[$namespace . '_instance'];
3843 $instance->update_settings();
3844 }
3845 }
3846
3847 // ---------------
3848 // Delete Settings
3849 // ---------------
3850 if ( !function_exists( 'radio_station_delete_settings' ) ) {
3851 function radio_station_delete_settings() {
3852 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3853 $instance = $GLOBALS[$namespace . '_instance'];
3854 $instance->delete_settings();
3855 }
3856 }
3857
3858
3859
3860 // -----------
3861 // Message Box
3862 // -----------
3863 if ( !function_exists( 'radio_station_message_box' ) ) {
3864 function radio_station_message_box( $message, $echo = false ) {
3865 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3866 $instance = $GLOBALS[$namespace . '_instance'];
3867
3868 return $instance->message_box( $message, $echo );
3869 }
3870 }
3871
3872 // ---------------
3873 // Settings Header
3874 // ---------------
3875 if ( !function_exists( 'radio_station_settings_header' ) ) {
3876 function radio_station_settings_header() {
3877 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3878 $instance = $GLOBALS[$namespace . '_instance'];
3879 $instance->settings_header();
3880 }
3881 }
3882
3883 // -------------
3884 // Settings Page
3885 // -------------
3886 if ( !function_exists( 'radio_station_settings_page' ) ) {
3887 function radio_station_settings_page() {
3888 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3889 $instance = $GLOBALS[$namespace . '_instance'];
3890 $instance->settings_page();
3891 }
3892 }
3893
3894 // --------------
3895 // Settings Table
3896 // --------------
3897 // 1.0.9: added for standalone setting table output
3898 if ( !function_exists( 'radio_station_settings_table' ) ) {
3899 function radio_station_settings_table() {
3900 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3901 $instance = $GLOBALS[$namespace . '_instance'];
3902 $instance->settings_table();
3903 }
3904 }
3905
3906 // ------------
3907 // Settings Row
3908 // ------------
3909 // 1.0.9: added for standalone setting row output
3910 // 1.3.6: fix to incorrect plural function name _settings_row
3911 if ( !function_exists( 'radio_station_setting_row' ) ) {
3912 function radio_station_setting_row( $option, $setting ) {
3913 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3914 $instance = $GLOBALS[$namespace . '_instance'];
3915 // 1.3.6: fix to incorrect plural function name settings_row
3916 $instance->setting_row( $option, $setting );
3917 }
3918 }
3919
3920 // ------------------
3921 // Settings Resources
3922 // ------------------
3923 // 1.2.3: added for separate enqueueing of resources from table
3924 if ( !function_exists( 'radio_station_settings_resources' ) ) {
3925 function radio_station_settings_resources( $media, $color_picker ) {
3926 $namespace = radio_station_get_namespace_from_function( __FUNCTION__ );
3927 $instance = $GLOBALS[$namespace . '_instance'];
3928 $instance->settings_resources( $media, $color_picker );
3929 }
3930 }
3931
3932 }
3933 }
3934
3935 // fully loaded
3936 // ------------
3937
3938
3939 // =========
3940 // CHANGELOG
3941 // =========
3942
3943 // == 1.3.8 ==
3944 // - fix: explicitly set class options variable on construct
3945 // - added setting preview containers and preview images
3946 // - added allowed HTML data and min/max attributes for previews
3947
3948 // == 1.3.7 ==
3949 // - updates for loading delayed string translations
3950
3951 // == 1.3.6 ==
3952 // - delayed support forum redirect to admin_init
3953 // - added rows and cols attributes to textarea input
3954 // - enqueue scripts via dummy admin script
3955 // - change to return style string instead of echo
3956 // - fix to plural function for settings row
3957
3958 // == 1.3.5 ==
3959 // - fix to default script enqueueing
3960
3961 // == 1.3.4 ==
3962 // - switch to settings tab via querystring
3963 // - enqueue settings page resources earlier
3964
3965 // == 1.3.3 ==
3966 // - move post debug output to after check_admin_referer
3967
3968 // == 1.3.2 ==
3969 // - added isset and wp_unslash to $_REQUEST inputs
3970
3971 // == 1.3.1 ==
3972 // - use prefixed markdown reader function
3973 // - when reading strip any license lines causing breakage
3974 // - update to color picker alpha library (3.0.4)
3975 // - added text domain to translation wrappers (for replacing)
3976
3977 // == 1.3.0 ==
3978 // - fix for possible page/post options conflict
3979 // - added explicit email option field type
3980 // - added fallback to text option firld type
3981 // - added check if pro slug data is a string
3982 // - added Freemius has_affiliation key
3983 // - added hash link anchor for Pro feature options
3984
3985 // == 1.2.9 ==
3986 // - fix empty number field converting to NaN value
3987 // - add bottom padding to settings form wrap box
3988
3989 // == 1.2.8 ==
3990 // - fix saving non-alpha colours in coloralpha fields
3991 // - fix saving of single value in CSV field
3992
3993 // == 1.2.7 ==
3994 // - fix color picker alpha sanitization / saving
3995 // - allow color picker alpha to not include alpha
3996 // - added color picker dropdown overlay styling
3997 // - added pointer cursor style to inactive tab
3998
3999 // == 1.2.6 ==
4000 // - expanded wp_kses allowed input attributes
4001
4002 // == 1.2.5 ==
4003 // - improved posted value input sanitization
4004 // - corrected textarea field sanitization
4005 // - added color and color alpha sanitization
4006 // - use wp_kses and allowed HTML on outputs
4007
4008 // == 1.2.4 ==
4009 // - fix to missing declaration on new settings_resources function
4010
4011 // == 1.2.3 ==
4012 // - added separate enqueueing of settings resources
4013 // - remove underline from plugin link icon spans
4014 // - skip welcome message output if empty
4015
4016 // == 1.2.2 ==
4017 // - merge in plugin links instead of using array_unshift
4018 // - update plugin repository rating URL
4019 // - remove duplication of addons link
4020 // - no notice boxer if adminsanity notices loaded
4021 // - change upgrade texts
4022
4023 // == 1.2.1 ==
4024 // - added filters for premium and addons init
4025 // - fix overriding of plan arg to free
4026 // - add check if premium already installed
4027 // - fix namespace typo in add settings action
4028
4029 // == 1.2.0 ==
4030 // - fix missing CSV field type row output condition
4031 // - fix to saving of current settings tab (if any)
4032 // - improved handling of Pro Upgrade and Details links
4033
4034 // == 1.1.9 ==
4035 // - fix to allow saving of zero value
4036 // - added filters for plugin page settings header sections
4037
4038 // == 1.1.8 ==
4039 // - fix to number step if no min or max value
4040
4041 // == 1.1.7 ==
4042 // - added media library upload image field type
4043 // - added color picker and color picker alpha field types
4044 // - automatically remove unused settings tabs
4045 // - fix to text field attribute quoting
4046 // - fix to not escape number step button function
4047 // - remove FILTER_VALIDATE_URL from URL saving (not working)
4048
4049 // == 1.1.6 ==
4050 // - added phone number character validation
4051
4052 // == 1.1.5 ==
4053 // - fix to validate multiple CSV values
4054
4055 // == 1.1.4 ==
4056 // - fix for debug prefix key dash
4057 // - fix for weird get_settings glitch bug (isset failing?!)
4058
4059 // == 1.1.3 ==
4060 // - remove strict value checking on select input
4061 // - change OPTGROUP string check in select options
4062
4063 // == 1.1.2 ==
4064 // - fix for filtering of plugin options
4065 // - fix for plugin page link URL
4066 // - added menu added and debug switches
4067 // - added get plugin version function
4068
4069 // == 1.1.1 ==
4070 // - remove admin_url wrapper on Freemius first-path value
4071
4072 // == 1.1.0 ==
4073 // - fix to saving multicheck as single array of values
4074 // - added admin notices boxer to settings pages
4075 // - added record for tracking first install version
4076 // - added explicit home link to plugin page links
4077
4078 // == 1.0.9 ==
4079 // - added automated Settings table and Admin Page output
4080 // - added tab and section support to options array
4081 // - added tab and section bottom and top action hooks
4082 // - added multicheck, multiselect and toggle input types
4083 // - added post type and post ID input validation
4084 // - added _loader_instance and _get_settings prefixed functions
4085 // - added helper function to get loader and Freemius instances
4086 // - added development link to plugin page header links
4087 // - added missing output sanitization wrappers
4088 // - removed WordQuest specific functionality from loader
4089 // - refactor separate setting update inputs and validation
4090 // - fix to check if plugin icon PNG exists
4091
4092 // == 1.0.8 ==
4093 // - added WordQuest plugin flag (for use by non-WQ plugins)
4094 // - apply settings key filters when getting all settings
4095 // - added filters for rate/share/donate links
4096 // - added page ID to settings input types
4097
4098 // == 1.0.7 ==
4099 // - fix support URL undefined variable warning
4100 // - change readme.php to reader.php (for GitHub)
4101
4102 // == 1.0.6 ==
4103 // - added global options filter
4104 // - added 'emails' option type for multiple email saving
4105 // - fix for new unchecked checkbox value
4106 // - fix for typos in URL option type saving
4107
4108 // == 1.0.5 ==
4109 // - fix for undefined account and support variables
4110
4111 // == 1.0.4 ==
4112 // - added 'url' option type checking and saving
4113 // - added 'csv' option type checking and saving
4114 // - added 'csvslugs' option type checking and saving
4115
4116 // == 1.0.3 ==
4117 // - added priority of 0 to prefixed function loading action
4118 // - only use namespace not settings key for updates
4119 // - added 'email' option type checking and save
4120 // - added 'usernames' option type checking and save
4121
4122 // == 1.0.2 ==
4123 // - fix some Freemius loading argument checks
4124 // - fix namespace typo in update/reset triggers
4125 // - add check for author icon file and fileback
4126 // - add allowance for special settings processing
4127 // - add allowance for auto-load of multiple Pro files
4128
4129 // == 1.0.1 ==
4130 // - set_basename for Freemius free / premium plugins
4131 // - set rate / share / donate links and text anchors
4132 // - check for multiple Pro filenames and use first one
4133
4134 // == 1.0.0 ==
4135 // - Working Release version
4136
4137 // == 0.9.0 ==
4138 // - Development Version
4139
4140