PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.7.14
Post Views Counter v1.7.14
1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.2 1.3.2.1 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.7.0 1.7.1 1.7.10 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9
post-views-counter / includes / class-settings-api.php
post-views-counter / includes Last commit date
class-admin.php 3 weeks ago class-columns-modal.php 3 weeks ago class-columns.php 3 weeks ago class-counter.php 3 weeks ago class-crawler-detect.php 3 weeks ago class-cron.php 3 weeks ago class-dashboard.php 3 weeks ago class-emails-mailer.php 3 weeks ago class-emails-period.php 3 weeks ago class-emails-query.php 3 weeks ago class-emails-scheduler.php 3 weeks ago class-emails-template.php 3 weeks ago class-emails.php 3 weeks ago class-frontend.php 3 weeks ago class-functions.php 3 weeks ago class-import.php 3 weeks ago class-integration-gutenberg.php 3 weeks ago class-integrations.php 3 weeks ago class-query.php 3 weeks ago class-settings-api.php 3 weeks ago class-settings-display.php 3 weeks ago class-settings-emails.php 3 weeks ago class-settings-general.php 3 weeks ago class-settings-integrations.php 3 weeks ago class-settings-other.php 3 weeks ago class-settings-reports.php 3 weeks ago class-settings.php 3 weeks ago class-toolbar.php 3 weeks ago class-traffic-signals.php 3 weeks ago class-update.php 3 weeks ago class-widgets.php 3 weeks ago functions.php 3 weeks ago
class-settings-api.php
1294 lines
1 <?php
2 // exit if accessed directly
3 if ( ! defined( 'ABSPATH' ) )
4 exit;
5
6 /**
7 * Post_Views_Counter_Settings_API class.
8 *
9 * @class Post_Views_Counter_Settings_API
10 */
11 class Post_Views_Counter_Settings_API {
12
13 private $settings = [];
14 private $input_settings = [];
15 private $validated_settings = [];
16 private $pages = [];
17 private $page_types = [];
18 private $prefix = '';
19 private $slug = '';
20 private $domain = '';
21 private $plugin = '';
22 private $plugin_url = '';
23 private $object;
24 private $nested = false;
25
26 /**
27 * Class constructor.
28 *
29 * @return void
30 */
31 public function __construct( $args ) {
32 // set initial data
33 $this->prefix = $args['prefix'];
34 $this->domain = $args['domain'];
35 $this->nested = isset( $args['nested'] ) ? (bool) $args['nested'] : false;
36
37 // empty slug?
38 if ( empty( $args['slug'] ) )
39 $this->slug = $args['domain'];
40 else
41 $this->slug = $args['slug'];
42
43 $this->object = $args['object'];
44 $this->plugin = $args['plugin'];
45 $this->plugin_url = $args['plugin_url'];
46
47 // actions
48 add_action( 'admin_menu', [ $this, 'admin_menu_options' ], 11 );
49 add_action( 'admin_init', [ $this, 'register_settings' ], 11 );
50 add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
51 }
52
53 /**
54 * Get prefix.
55 *
56 * @return string
57 */
58 public function get_prefix() {
59 return $this->prefix;
60 }
61
62 /**
63 * Get pages.
64 *
65 * @return array
66 */
67 public function get_pages() {
68 return $this->pages;
69 }
70
71 /**
72 * Get settings.
73 *
74 * @return array
75 */
76 public function get_settings() {
77 return $this->settings;
78 }
79
80 /**
81 * Get current input settings during saving.
82 *
83 * @return array
84 */
85 public function get_input_settings() {
86 return $this->input_settings;
87 }
88
89 /**
90 * Get already validated setting fields during saving.
91 *
92 * @return array
93 */
94 public function get_validated_settings() {
95 return $this->validated_settings;
96 }
97
98 /**
99 * Get resolved defaults for a settings group.
100 *
101 * Allows settings objects to provide dynamic defaults for a group,
102 * such as the Emails recipient defaulting to the site admin email.
103 *
104 * @param string $setting_id
105 * @return array
106 */
107 private function get_setting_defaults( $setting_id ) {
108 $defaults = isset( $this->object->defaults[$setting_id] ) && is_array( $this->object->defaults[$setting_id] ) ? $this->object->defaults[$setting_id] : [];
109 $callback = 'get_default_' . str_replace( '-', '_', $setting_id ) . '_settings';
110
111 if ( is_object( $this->object ) && method_exists( $this->object, $callback ) ) {
112 $resolved_defaults = call_user_func( [ $this->object, $callback ] );
113
114 if ( is_array( $resolved_defaults ) )
115 return $resolved_defaults;
116 }
117
118 return $defaults;
119 }
120
121 /**
122 * Load default scripts and styles.
123 *
124 * @return void
125 */
126 public function admin_enqueue_scripts() {
127 // Legacy inline styles for disabled tabs
128 $handler = $this->prefix . '-settings-api-style';
129 wp_register_style( $handler, false );
130 wp_enqueue_style( $handler );
131
132 wp_add_inline_style( $handler, '.nav-tab-wrapper span.nav-span-disabled {
133 cursor: not-allowed;
134 float: left;
135 }
136 body.rtl .nav-tab-wrapper span.nav-span-disabled {
137 float: right;
138 }
139 .nav-tab-wrapper a.nav-tab.nav-tab-disabled {
140 pointer-events: none;
141 }
142 .nav-tab-wrapper a.nav-tab.nav-tab-disabled:hover {
143 cursor: not-allowed;
144 }' );
145
146 if ( $this->current_page_has_field_type( 'editor' ) )
147 wp_enqueue_editor();
148 }
149
150 /**
151 * Determine whether the current settings view contains a given field type.
152 *
153 * @param string $field_type
154 * @return bool
155 */
156 private function current_page_has_field_type( $field_type ) {
157 if ( empty( $this->settings ) || empty( $this->pages ) || empty( $_GET['page'] ) )
158 return false;
159
160 $field_type = sanitize_key( $field_type );
161 $page_slug = sanitize_key( wp_unslash( $_GET['page'] ) );
162
163 if ( $field_type === '' || $page_slug === '' )
164 return false;
165
166 foreach ( $this->pages as $page_key => $page ) {
167 if ( empty( $page['menu_slug'] ) || sanitize_key( $page['menu_slug'] ) !== $page_slug )
168 continue;
169
170 $active_tab = '';
171
172 if ( ! empty( $page['tabs'] ) && is_array( $page['tabs'] ) ) {
173 reset( $page['tabs'] );
174 $active_tab = key( $page['tabs'] );
175
176 if ( ! empty( $_GET['tab'] ) ) {
177 $requested_tab = sanitize_key( wp_unslash( $_GET['tab'] ) );
178
179 if ( array_key_exists( $requested_tab, $page['tabs'] ) )
180 $active_tab = $requested_tab;
181 }
182 }
183
184 if ( empty( $this->settings[$page_key]['fields'] ) || ! is_array( $this->settings[$page_key]['fields'] ) )
185 return false;
186
187 foreach ( $this->settings[$page_key]['fields'] as $field ) {
188 if ( empty( $field['type'] ) || sanitize_key( $field['type'] ) !== $field_type )
189 continue;
190
191 if ( ! empty( $field['tab'] ) && $active_tab !== '' && $field['tab'] !== $active_tab )
192 continue;
193
194 return true;
195 }
196
197 return false;
198 }
199
200 return false;
201 }
202
203 /**
204 * Add menu pages.
205 *
206 * @return void
207 */
208 public function admin_menu_options() {
209 $this->pages = apply_filters( $this->prefix . '_settings_pages', [] );
210 $types = [
211 'page' => [],
212 'subpage' => [],
213 'settings_page' => []
214 ];
215
216 foreach ( $this->pages as $page => $data ) {
217 // skip invalid page types
218 if ( empty( $data['type'] ) || ! array_key_exists( $data['type'], $types ) )
219 continue;
220
221 if ( $data['type'] === 'page' ) {
222 add_menu_page( $data['page_title'], $data['menu_title'], $data['capability'], $data['menu_slug'], ! empty( $data['callback'] ) ? $data['callback'] : [ $this, 'options_page' ], $data['icon'], $data['position'] );
223
224 // add page type
225 $types['page'][$data['menu_slug']] = $page;
226 // menu subpage?
227 } elseif ( $data['type'] === 'subpage' ) {
228 add_submenu_page( $data['parent_slug'], $data['page_title'], $data['menu_title'], $data['capability'], $data['menu_slug'], ! empty( $data['callback'] ) ? $data['callback'] : [ $this, 'options_page' ] );
229
230 // add subpage type
231 $types['subpage'][$data['menu_slug']] = $page;
232 // menu settings page?
233 } elseif ( $data['type'] === 'settings_page' ) {
234 add_options_page( $data['page_title'], $data['menu_title'], $data['capability'], $data['menu_slug'], ! empty( $data['callback'] ) ? $data['callback'] : [ $this, 'options_page' ] );
235
236 // add settings type
237 $types['settings_page'][$data['menu_slug']] = $page;
238 }
239 }
240
241 // set page types
242 $this->page_types = $types;
243 }
244
245 /**
246 * Render settings.
247 *
248 * @global string $pagenow
249 *
250 * @return void
251 */
252 public function options_page() {
253 global $pagenow;
254
255 $valid_page = false;
256
257 // get current screen
258 $screen = get_current_screen();
259
260 // display top level settings page?
261 if ( $pagenow === 'admin.php' && preg_match( '/^toplevel_page_(' . implode( '|', $this->page_types['page'] ) . ')$/', $screen->base, $matches ) === 1 && ! empty( $matches[1] ) ) {
262 $valid_page = true;
263 $page_type = 'page';
264 $url_page = 'admin.php';
265 }
266
267 // display settings page?
268 if ( ! $valid_page && $pagenow === 'options-general.php' && preg_match( '/^(?:settings_page_)(' . implode( '|', array_keys( $this->page_types['settings_page'] ) ) . ')$/', $screen->base, $matches ) === 1 ) {
269 $valid_page = true;
270 $page_type = 'settings_page';
271 $url_page = 'options-general.php';
272 }
273
274 // skip invalid pages
275 if ( ! $valid_page )
276 return;
277
278 $page_key = $this->page_types[$page_type][$matches[1]];
279 $tab_key = '';
280 $tabs = [];
281
282 // any tabs?
283 if ( array_key_exists( 'tabs', $this->pages[$page_key] ) ) {
284 // get tabs
285 $tabs = $this->pages[$page_key]['tabs'];
286
287 // reset tabs
288 reset( $tabs );
289
290 // get first default tab
291 $first_tab = key( $tabs );
292
293 // get current tab
294 $tab_key = ! empty( $_GET['tab'] ) && array_key_exists( $_GET['tab'], $tabs ) ? $_GET['tab'] : $first_tab;
295
296 // check current tab
297 if ( ! empty( $_GET['tab'] ) )
298 $tab_key = sanitize_key( $_GET['tab'] );
299
300 // invalid tab?
301 if ( ! array_key_exists( $tab_key, $tabs ) )
302 $tab_key = $first_tab;
303
304 $tab_label = ! empty( $tabs[$tab_key]['label'] ) ? $tabs[$tab_key]['label'] : '';
305 $tab_heading = ! empty( $tabs[$tab_key]['heading'] ) ? $tabs[$tab_key]['heading'] : '';
306 } else
307 $tab_label = '';
308
309 if ( empty( $tabs ) )
310 $tab_heading = '';
311
312 $heading = $this->plugin;
313
314 echo '
315 <div class="wrap ' . $this->prefix . '-settings-wrapper' . '" data-settings-prefix="' . esc_attr( $this->prefix ) . '">
316 <div class="header-wrapper">
317 <span class="header-title">' . esc_html( $heading ) . '</span>
318 </div>';
319
320 if ( ! empty( $tabs ) ) {
321 echo '
322 <nav class="nav-tab-wrapper">';
323
324 foreach ( $tabs as $key => $tab ) {
325 if ( ! empty( $tab['disabled'] ) )
326 $url = '';
327 else
328 $url = admin_url( $url_page . '?page=' . $matches[1] . '&tab=' . $key );
329
330 if ( ! empty( $tab['disabled'] ) )
331 echo '<span class="nav-span-disabled">';
332
333 echo '
334 <a class="nav-tab' . ( $tab_key === $key ? ' nav-tab-active' : '' ) . ( ! empty( $tab['disabled'] ) ? ' nav-tab-disabled' : '' ) . ( ! empty( $tab['class'] ) ? ' ' . esc_attr( $tab['class'] ) : '' ) . '" href="' . ( $url !== '' ? esc_url( $url ) : '#' ) . '">' . esc_html( $tab['label'] ) . '</a>';
335
336 if ( ! empty( $tab['disabled'] ) )
337 echo '</span>';
338 }
339
340 echo '
341 </nav>';
342 }
343
344 echo '
345 <div class="content-wrapper">
346 <h1 class="screen-reader-text">' . esc_html( $heading ) . '</h1>';
347
348 // Global admin notices are suppressed until this in-interface location.
349 // Render Settings API feedback for both top-level and Settings-submenu pages.
350 settings_errors();
351
352 // get settings page classes
353 $settings_class = apply_filters( $this->prefix . '_settings_page_class', [ $this->slug . '-settings', $tab_key . '-settings', $this->prefix . '-settings' ] );
354
355 // sanitize settings page classes
356 $settings_class = array_unique( array_filter( array_map( 'sanitize_html_class', $settings_class ) ) );
357
358 // resolve setting group for sidebar/form
359 if ( ! empty( $tab_key ) ) {
360 if ( ! empty( $tabs[$tab_key]['option_name'] ) ) {
361 $setting = $tabs[$tab_key]['option_name'];
362 } else {
363 $setting = $this->prefix . '_' . $tab_key . '_settings';
364 }
365 } else {
366 $setting = $this->prefix . '_' . $matches[1] . '_settings';
367 }
368
369 // capture sidebar output
370 ob_start();
371 do_action( $this->prefix . '_settings_sidebar', $setting, $page_type, $url_page, $tab_key );
372 $sidebar_html = trim( ob_get_clean() );
373
374 // add has-sidebar class if sidebar has content
375 if ( ! empty( $sidebar_html ) ) {
376 $settings_class[] = 'has-sidebar';
377 }
378
379 echo '
380 <div class="' . implode( ' ', array_map( 'esc_attr', $settings_class ) ) . '">';
381
382 $display_form = true;
383
384 // check form attribute
385 if ( ! empty( $tab_key ) && ! empty( $tabs[$tab_key]['form'] ) ) {
386 $form = $tabs[$tab_key]['form'];
387
388 if ( isset( $form['buttons'] ) && ! $form['buttons'] )
389 $display_form = false;
390 } elseif ( ! empty( $tab_key ) && isset( $this->settings[$matches[1]]['form'][$tab_key] ) ) {
391 $form = $this->settings[$matches[1]]['form'][$tab_key];
392
393 if ( isset( $form['buttons'] ) && ! $form['buttons'] )
394 $display_form = false;
395 } elseif ( isset( $this->settings[$matches[1]]['form'] ) ) {
396 $form = $this->settings[$matches[1]]['form'];
397
398 if ( isset( $form['buttons'] ) && ! $form['buttons'] )
399 $display_form = false;
400 }
401
402 if ( $display_form ) {
403 echo '
404 <form action="options.php" method="post" novalidate class="' . $this->prefix . '-settings-form">';
405 }
406
407 settings_fields( $setting );
408
409 if ( $display_form )
410 do_action( $this->prefix . '_settings_form', $setting, $page_type, $url_page, $tab_key );
411
412 // filter sections by tab if tabs are present
413 global $wp_settings_sections;
414
415 $original_sections = $wp_settings_sections;
416
417 if ( ! empty( $tab_key ) && isset( $this->settings[$page_key]['sections'] ) ) {
418 $filtered_sections = [];
419
420 foreach ( $this->settings[$page_key]['sections'] as $section_id => $section ) {
421 // include sections without tab or matching current tab
422 if ( empty( $section['tab'] ) || $section['tab'] === $tab_key ) {
423 if ( isset( $wp_settings_sections[$setting][$section_id] ) ) {
424 $filtered_sections[$section_id] = $wp_settings_sections[$setting][$section_id];
425 }
426 }
427 }
428
429 // replace with filtered sections
430 if ( isset( $wp_settings_sections[$setting] ) ) {
431 $wp_settings_sections[$setting] = $filtered_sections;
432 }
433 }
434
435 do_settings_sections( $setting );
436
437 // restore original sections
438 $wp_settings_sections = $original_sections;
439
440 if ( $display_form ) {
441 $setting_hyphenated = str_replace( '_', '-', $setting );
442 $legacy_reset_class = 'reset_' . $setting;
443 echo '
444 <p class="submit">';
445
446 submit_button( '', 'primary save-' . $setting_hyphenated, 'save_' . $setting, false, [ 'id' => 'save-' . $setting_hyphenated ] );
447
448 submit_button( __( 'Reset to defaults', $this->domain ), 'outline reset_pvc_settings ' . $legacy_reset_class . ' reset-' . $setting_hyphenated, 'reset_' . $setting, false, [ 'id' => 'reset-' . $setting_hyphenated ] );
449
450 echo '
451 </p>
452 </form>';
453 }
454
455 // output sidebar if it has content
456 if ( ! empty( $sidebar_html ) ) {
457 echo '
458 <div class="' . $this->prefix . '-sidebar">' . $sidebar_html . '</div>';
459 }
460
461 echo '
462 </div>
463 </div>';
464
465 echo '
466 <div class="clear"></div>
467 </div>';
468 }
469
470 /**
471 * Register settings.
472 *
473 * @return void
474 */
475 public function register_settings() {
476 $this->settings = apply_filters( $this->prefix . '_settings_data', [] );
477
478 // check settings
479 foreach ( $this->settings as $setting_id => $setting ) {
480 // tabs?
481 if ( is_array( $setting['option_name'] ) ) {
482 foreach ( $setting['option_name'] as $tab => $option_name ) {
483 $this->register_setting_fields( $tab, $setting, $option_name );
484 }
485 } else
486 $this->register_setting_fields( $setting_id, $setting );
487 }
488 }
489
490 /**
491 * Register setting with sections and fields.
492 *
493 * @return void
494 */
495 public function register_setting_fields( $setting_id, $setting, $option_name = '' ) {
496 if ( empty( $option_name ) )
497 $option_name = $setting['option_name'];
498
499 // register setting
500 register_setting( $option_name, $option_name, ! empty( $setting['validate'] ) ? $setting['validate'] : [ $this, 'validate_settings' ] );
501
502 // register setting sections
503 if ( ! empty( $setting['sections'] ) ) {
504 foreach ( $setting['sections'] as $section_id => $section ) {
505 // skip unwanted sections
506 if ( ! empty( $section['tab'] ) && $section['tab'] !== $setting_id )
507 continue;
508
509 // Auto-generate section classes and wrapper
510 $base_slug = sanitize_html_class( str_replace( '_', '-', $section_id ) );
511 $section_prefix = apply_filters( $this->prefix . '_settings_section_prefix', $this->prefix );
512 $section_prefix = sanitize_html_class( $section_prefix );
513 $section_classes = $section_prefix . '-section ' . $section_prefix . '-section-' . $base_slug;
514 $section_args = [
515 'section_class' => $section_classes,
516 'before_section' => '<section id="' . $section_prefix . '-section-' . $base_slug . '" class="%s">',
517 'after_section' => '</section>',
518 ];
519
520 add_settings_section(
521 $section_id,
522 ! empty( $section['title'] ) ? esc_html( $section['title'] ) : '',
523 ! empty( $section['callback'] ) ? $section['callback'] : null,
524 ! empty( $section['page'] ) ? $section['page'] : $option_name,
525 $section_args
526 );
527 }
528 }
529
530 // register setting fields
531 if ( ! empty( $setting['fields'] ) ) {
532 foreach ( $setting['fields'] as $field_key => $field ) {
533 // skip unwanted fields
534 if ( ! empty( $field['tab'] ) && $field['tab'] !== $setting_id )
535 continue;
536
537 // set field ID
538 $field_id = implode( '_', [ $this->prefix, $setting_id, $field_key ] );
539
540 // skip rendering this field?
541 if ( ! empty( $field['skip_rendering'] ) )
542 continue;
543
544 // prepare field args
545 $args = array_merge( $this->prepare_field_args( $field, $field_id, $field_key, $setting_id, $option_name ), $field );
546 // Raw field definitions override prepared arguments, so normalize the
547 // attributes that are emitted directly into the input afterwards.
548 $args['autocomplete'] = ! empty( $args['autocomplete'] ) && is_string( $args['autocomplete'] ) && in_array( $args['autocomplete'], [ 'off', 'new-password' ], true ) ? $args['autocomplete'] : '';
549 $args['readonly'] = ! empty( $args['readonly'] );
550 $args['setting_id'] = $setting_id;
551 $class = sanitize_html_class( str_replace( '_', '-', $field_id ) );
552 $classes = [ $class ];
553
554 if ( ! empty( $args['class'] ) ) {
555 $extra_classes = preg_split( '/\s+/', trim( $args['class'] ) );
556 $extra_classes = array_filter( $extra_classes );
557 $extra_classes = array_map( 'sanitize_html_class', $extra_classes );
558 $classes = array_merge( $classes, $extra_classes );
559 }
560
561 $classes = array_values( array_unique( array_filter( $classes ) ) );
562
563 $field_class = implode( ' ', $classes );
564 $wrapper_class = $class !== '' ? $class . '-row' : '';
565
566 // preserve user classes in wrapper
567 if ( ! empty( $field['class'] ) ) {
568 $wrapper_class .= ' ' . $field['class'];
569 }
570
571 $args['class'] = $wrapper_class;
572 $args['field_class'] = $field_class;
573 $args['css_id'] = $class;
574
575 add_settings_field(
576 $field_id,
577 ! empty( $field['title'] ) ? esc_html( $field['title'] ) : '',
578 [ $this, 'render_field' ],
579 $option_name,
580 ! empty( $field['section'] ) ? esc_attr( $field['section'] ) : '',
581 $args
582 );
583 }
584 }
585 }
586
587 /**
588 * Prepare field arguments.
589 *
590 * @param array $args
591 * @return array
592 */
593 public function prepare_field_args( $field, $field_id, $field_key, $setting_id, $setting_name ) {
594 // get field type
595 $field_type = ! empty( $field['type'] ) ? $field['type'] : '';
596
597 // default lookup path
598 $value = null;
599 $default = null;
600 $setting_defaults = $this->get_setting_defaults( $setting_id );
601 $name = $setting_name . '[' . $field_key . ']';
602
603 // check for parent
604 if ( ! empty( $field['parent'] ) ) {
605 $name = $setting_name . '[' . $field['parent'] . '][' . $field_key . ']';
606
607 // try with setting_id first
608 if ( isset( $this->object->options[$setting_id][$field['parent']][$field_key] ) ) {
609 $value = $this->object->options[$setting_id][$field['parent']][$field_key];
610 } elseif ( isset( $this->object->options[$field['parent']][$field_key] ) ) {
611 // try without setting_id
612 $value = $this->object->options[$field['parent']][$field_key];
613 }
614
615 // defaults
616 if ( isset( $setting_defaults[$field['parent']][$field_key] ) ) {
617 $default = $setting_defaults[$field['parent']][$field_key];
618 } elseif ( isset( $this->object->defaults[$field['parent']][$field_key] ) ) {
619 $default = $this->object->defaults[$field['parent']][$field_key];
620 }
621 } else {
622 // nested?
623 if ( $this->nested ) {
624 $name = $setting_name . '[' . $setting_id . '][' . $field_key . ']';
625
626 if ( isset( $this->object->options[$setting_id][$field_key] ) )
627 $value = $this->object->options[$setting_id][$field_key];
628
629 if ( isset( $setting_defaults[$field_key] ) )
630 $default = $setting_defaults[$field_key];
631 } else {
632 // flat
633 if ( isset( $this->object->options[$setting_id][$field_key] ) ) {
634 $value = $this->object->options[$setting_id][$field_key];
635 } elseif ( isset( $this->object->options[$field_key] ) ) {
636 $value = $this->object->options[$field_key];
637 }
638
639 // defaults
640 if ( isset( $setting_defaults[$field_key] ) ) {
641 $default = $setting_defaults[$field_key];
642 } elseif ( isset( $this->object->defaults[$field_key] ) ) {
643 $default = $this->object->defaults[$field_key];
644 }
645 }
646 }
647
648 if ( $field_type === 'custom' ) {
649 $value = null;
650 $default = null;
651 }
652
653 return [
654 'id' => $field_id,
655 'html_id' => sanitize_html_class( str_replace( '_', '-', $field_id ) ),
656 'name' => $name,
657 'class' => ! empty( $field['class'] ) ? $field['class'] : '',
658 'type' => $field_type,
659 'label' => ! empty( $field['label'] ) ? $field['label'] : '',
660 'description' => ! empty( $field['description'] ) ? $field['description'] : '',
661 'text' => ! empty( $field['text'] ) ? $field['text'] : '',
662 'min' => ! empty( $field['min'] ) ? (int) $field['min'] : 0,
663 'max' => ! empty( $field['max'] ) ? (int) $field['max'] : 0,
664 'options' => ! empty( $field['options'] ) ? $field['options'] : [],
665 'callback' => ! empty( $field['callback'] ) ? $field['callback'] : null,
666 'validate' => ! empty( $field['validate'] ) ? $field['validate'] : null,
667 'callback_args' => ! empty( $field['callback_args'] ) ? $field['callback_args'] : [],
668 'default' => $default,
669 'value' => $value,
670 'setting_id' => $setting_id,
671 'animation' => ! empty( $field['animation'] ) ? $field['animation'] : '',
672 'logic' => ! empty( $field['logic'] ) ? $field['logic'] : null,
673 'fallback_option' => ! empty( $field['fallback_option'] ) ? sanitize_key( $field['fallback_option'] ) : '',
674 'autocomplete' => ! empty( $field['autocomplete'] ) && in_array( $field['autocomplete'], [ 'off', 'new-password' ], true ) ? $field['autocomplete'] : '',
675 'readonly' => ! empty( $field['readonly'] ),
676 'rows' => ! empty( $field['rows'] ) ? (int) $field['rows'] : 6,
677 'cols' => ! empty( $field['cols'] ) ? (int) $field['cols'] : 50
678 /*
679 after_field
680 before_field
681 */
682 ];
683 }
684
685 /**
686 * Render settings field.
687 *
688 * @param array $args
689 * @return void|string
690 */
691 public function render_field( $args ) {
692 if ( empty( $args ) || ! is_array( $args ) )
693 return;
694
695 // build wrapper classes
696 $wrapper_classes = [ $this->prefix . '-field', $this->prefix . '-field-type-' . $args['type'] ];
697 if ( ! empty( $args['class'] ) )
698 $wrapper_classes[] = $args['class'];
699
700 // add disabled class if field is disabled (but not if feature is available)
701 if ( ! empty( $args['disabled'] ) && $args['disabled'] === true && empty( $args['available'] ) )
702 $wrapper_classes[] = $this->prefix . '-disabled';
703
704 // build wrapper attributes
705 $wrapper_attrs = ' id="' . esc_attr( str_replace( '_', '-', $args['id'] ) ) . '-setting" class="' . esc_attr( implode( ' ', $wrapper_classes ) ) . '"';
706
707 // add conditional data attributes
708 $data_attrs = '';
709 $conditions = [];
710 $fallback_option = ! empty( $args['fallback_option'] ) ? $args['fallback_option'] : '';
711
712 if ( ! empty( $args['logic'] ) && is_array( $args['logic'] ) ) {
713 if ( isset( $args['logic']['field'] ) ) {
714 $conditions = [ $args['logic'] ];
715 } else {
716 $conditions = $args['logic'];
717 }
718 }
719
720 if ( ! empty( $conditions ) ) {
721 $data_attr_prefix = sanitize_html_class( $this->prefix );
722 $normalized_conditions = [];
723
724 foreach ( $conditions as $condition ) {
725 if ( empty( $condition['field'] ) || empty( $condition['operator'] ) ) {
726 continue;
727 }
728
729 $field = $condition['field'];
730
731 if ( strpos( $field, '-' ) === false && ! empty( $args['setting_id'] ) ) {
732 $field_id = implode( '_', [ $this->prefix, $args['setting_id'], $field ] );
733 $field = sanitize_html_class( str_replace( '_', '-', $field_id ) );
734 }
735
736 $normalized_conditions[] = [
737 'field' => $field,
738 'operator' => $condition['operator'],
739 'value' => isset( $condition['value'] ) ? $condition['value'] : '',
740 'scope' => ! empty( $condition['scope'] ) ? sanitize_key( $condition['scope'] ) : '',
741 'action' => ! empty( $condition['action'] ) ? sanitize_key( $condition['action'] ) : '',
742 'target' => ! empty( $condition['target'] ) ? sanitize_text_field( $condition['target'] ) : '',
743 'container' => ! empty( $condition['container'] ) ? sanitize_text_field( $condition['container'] ) : '',
744 ];
745 }
746
747 if ( ! empty( $normalized_conditions ) && $data_attr_prefix !== '' ) {
748 if ( ! empty( $args['animation'] ) && in_array( $args['animation'], [ 'fade', 'slide' ], true ) ) {
749 $data_attrs .= ' data-' . $data_attr_prefix . '-animation="' . esc_attr( $args['animation'] ) . '"';
750 }
751 $data_attrs .= ' data-' . $data_attr_prefix . '-logic="' . esc_attr( wp_json_encode( $normalized_conditions ) ) . '"';
752 }
753 }
754
755 if ( $fallback_option !== '' ) {
756 $data_attrs .= ' data-' . $this->prefix . '-fallback-option="' . esc_attr( $fallback_option ) . '"';
757 }
758
759 $wrapper_attrs .= $data_attrs;
760
761 $html = '<div' . $wrapper_attrs . '>';
762
763 if ( ! empty ( $args['before_field'] ) )
764 $html .= $args['before_field'];
765
766 switch ( $args['type'] ) {
767 case 'boolean':
768 if ( empty( $args['disabled'] ) )
769 $html .= '<input type="hidden" name="' . esc_attr( $args['name'] ) . '" value="false" />';
770
771 $html .= '<label><input id="' . esc_attr( $args['html_id'] ) . '" type="checkbox" role="switch" name="' . esc_attr( $args['name'] ) . '" value="true" ' . checked( (bool) $args['value'], true, false ) . ' ' . disabled( empty( $args['disabled'] ), false, false ) . ' />' . wp_kses_post( $args['label'] ) . '</label>';
772 break;
773
774 case 'radio':
775 if ( empty( $args['options'] ) || ! is_array( $args['options'] ) )
776 break;
777
778 $display_type = ! empty( $args['display_type'] ) && in_array( $args['display_type'], [ 'horizontal', 'vertical' ], true ) ? $args['display_type'] : 'vertical';
779
780 if ( count( $args['options'] ) > 1 )
781 $html .= '<div class="' . esc_attr( $this->prefix ) . '-field-group ' . esc_attr( $this->prefix ) . '-radio-group ' . $display_type . '">';
782 foreach ( $args['options'] as $key => $name ) {
783 $is_disabled = ! empty( $args['disabled'] ) && ( is_array( $args['disabled'] ) && in_array( $key, $args['disabled'], true ) || $args['disabled'] === true );
784 $label_classes = [];
785
786 if ( $is_disabled && is_array( $args['disabled'] ) )
787 $label_classes[] = $this->prefix . '-disabled';
788
789 // add disabled-state class for disabled options in extended fields
790 if ( $is_disabled && ! empty( $args['class'] ) && strpos( $args['class'], 'pvc-pro-extended' ) !== false ) {
791 $label_classes[] = $this->prefix . '-pro';
792 }
793
794 $label_class = ! empty( $label_classes ) ? ' class="' . implode( ' ', $label_classes ) . '"' : '';
795
796 $display_name = esc_html( $name );
797
798 $html .= '<label for="' . esc_attr( $args['html_id'] . '-' . $key ) . '"' . $label_class . '><input id="' . esc_attr( $args['html_id'] . '-' . $key ) . '" type="radio" name="' . esc_attr( $args['name'] ) . '" value="' . esc_attr( $key ) . '" ' . checked( $key, $args['value'], false ) . ' ' . disabled( $is_disabled, true, false ) . ' />' . $display_name . '</label> ';
799 }
800 if ( count( $args['options'] ) > 1 )
801 $html .= '</div>';
802 break;
803
804 case 'checkbox':
805 // possible "empty" value
806 if ( $args['value'] === 'empty' )
807 $args['value'] = [];
808
809 // ensure value is an array
810 if ( ! is_array( $args['value'] ) )
811 $args['value'] = [];
812
813 $display_type = ! empty( $args['display_type'] ) && in_array( $args['display_type'], [ 'horizontal', 'vertical' ], true ) ? $args['display_type'] : 'vertical';
814
815 $html .= '<input type="hidden" name="' . esc_attr( $args['name'] ) . '" value="empty" />';
816
817 if ( empty( $args['options'] ) || ! is_array( $args['options'] ) )
818 break;
819 if ( count( $args['options'] ) > 1 )
820 $html .= '<div class="' . esc_attr( $this->prefix ) . '-field-group ' . esc_attr( $this->prefix ) . '-checkbox-group ' . $display_type . '">';
821 foreach ( $args['options'] as $key => $name ) {
822 $is_disabled = ! empty( $args['disabled'] ) && ( is_array( $args['disabled'] ) && in_array( $key, $args['disabled'], true ) || $args['disabled'] === true );
823 $label_classes = [];
824
825 if ( $is_disabled && is_array( $args['disabled'] ) )
826 $label_classes[] = $this->prefix . '-disabled';
827
828 // add disabled-state styling for disabled options in extended fields
829 if ( $is_disabled && ! empty( $args['class'] ) && strpos( $args['class'], 'pvc-pro-extended' ) !== false )
830 $label_classes[] = $this->prefix . '-pro';
831
832 $label_class = ! empty( $label_classes ) ? ' class="' . implode( ' ', $label_classes ) . '"' : '';
833
834 $display_name = esc_html( $name );
835
836 $html .= '<label' . $label_class . '><input id="' . esc_attr( $args['html_id'] . '-' . $key ) . '" type="checkbox" name="' . esc_attr( $args['name'] ) . '[]" value="' . esc_attr( $key ) . '" ' . checked( in_array( $key, $args['value'] ), true, false ) . ' ' . disabled( $is_disabled, true, false ) . ' />' . $display_name . '</label>';
837 }
838 if ( count( $args['options'] ) > 1 )
839 $html .= '</div>';
840 break;
841
842 case 'select':
843 $html .= '<select id="' . esc_attr( $args['html_id'] ) . '" name="' . esc_attr( $args['name'] ) . '" ' . disabled( empty( $args['disabled'] ), false, false ) . '/>';
844
845 foreach ( $args['options'] as $key => $name ) {
846 $html .= '<option value="' . esc_attr( $key ) . '" ' . selected( $args['value'], $key, false ) . '>' . esc_html( $name ) . '</option>';
847 }
848
849 $html .= '</select>';
850 break;
851
852 case 'range':
853 $html .= '<input id="' . esc_attr( $args['html_id'] ) . '-slider" type="range" name="' . esc_attr( $args['name'] ) . '" value="' . esc_attr( $args['value'] ) . '" min="' . esc_attr( $args['min'] ) . '" max="' . esc_attr( $args['max'] ) . '" data-range-output="' . esc_attr( $args['html_id'] ) . '-output" /><output id="' . esc_attr( $args['html_id'] ) . '-output" class="' . esc_attr( $this->slug ) . '-range">' . ( (int) $args['value'] ) . '</output>';
854 break;
855
856 case 'number':
857 $html .= ( ! empty( $args['prepend'] ) ? wp_kses_post( $args['prepend'] ) : '' );
858 $html .= '<input id="' . esc_attr( $args['html_id'] ) . '" type="text" value="' . esc_attr( $args['value'] ) . '" name="' . esc_attr( $args['name'] ) . '" />';
859 $html .= ( ! empty( $args['append'] ) ? wp_kses_post( $args['append'] ) : '' );
860 break;
861
862 case 'color':
863 $color_value = esc_attr( $args['value'] );
864 $color_name = esc_attr( $args['name'] );
865 $input_id = esc_attr( $args['html_id'] );
866 $input_class = $this->prefix . '-color-input';
867
868 if ( ! empty( $args['subclass'] ) ) {
869 $input_class .= ' ' . esc_attr( $args['subclass'] );
870 }
871
872 $swatch_style = ' style="background-color: ' . $color_value . ';"';
873
874 $html .= '<div class="' . $this->prefix . '-color-control">';
875 // Text input for the hex color value.
876 $html .= '<input id="' . $input_id . '" type="text" name="' . $color_name . '" value="' . $color_value . '" class="' . $input_class . '" />';
877 // Swatch button to toggle the picker.
878 $html .= '<button type="button" class="' . $this->prefix . '-color-swatch"' . $swatch_style . ' aria-label="' . esc_attr__( 'Open color picker', $this->domain ) . '" aria-expanded="false"></button>';
879 // Vanilla-colorful picker (hidden by default).
880 $html .= '<div class="' . $this->prefix . '-color-popover" aria-hidden="true"><hex-color-picker class="' . $this->prefix . '-hex-color-picker" color="' . $color_value . '"></hex-color-picker></div>';
881 $html .= '</div>';
882 break;
883
884 case 'custom':
885 $html .= call_user_func( $args['callback'], $args );
886 break;
887
888 case 'textarea':
889 $empty_disabled = empty( $args['disabled'] );
890 $rows = max( 2, (int) $args['rows'] );
891 $cols = max( 20, (int) $args['cols'] );
892
893 $html .= '<textarea id="' . esc_attr( $args['html_id'] ) . '"' . ( ! empty( $args['subclass'] ) ? ' class="' . esc_attr( $args['subclass'] ) . '"' : '' ) . ' name="' . esc_attr( $args['name'] ) . '" rows="' . esc_attr( $rows ) . '" cols="' . esc_attr( $cols ) . '" ' . disabled( $empty_disabled, false, false ) . '>' . esc_textarea( $args['value'] ) . '</textarea>';
894 break;
895
896 case 'editor':
897 $rows = max( 2, (int) $args['rows'] );
898 $editor_settings = ! empty( $args['editor_settings'] ) && is_array( $args['editor_settings'] ) ? $args['editor_settings'] : [];
899 $editor_settings = array_merge(
900 [
901 'textarea_name' => $args['name'],
902 'textarea_rows' => $rows,
903 'teeny' => true,
904 'media_buttons' => false,
905 'tinymce' => true,
906 'quicktags' => true
907 ],
908 $editor_settings
909 );
910
911 $html .= ! empty( $args['prepend'] ) ? wp_kses_post( $args['prepend'] ) : '';
912
913 ob_start();
914 wp_editor( (string) $args['value'], $args['html_id'], $editor_settings );
915 $html .= ob_get_clean();
916
917 $html .= ! empty( $args['append'] ) ? wp_kses_post( $args['append'] ) : '';
918 break;
919
920 case 'info':
921 $html .= '<span' . ( ! empty( $args['subclass'] ) ? ' class="' . esc_attr( $args['subclass'] ) . '"' : '' ) . '>' . esc_html( $args['text'] ) . '</span>';
922 break;
923
924 case 'class':
925 case 'input':
926 default:
927 $empty_disabled = empty( $args['disabled'] );
928
929 $html .= ( ! empty( $args['prepend'] ) ? wp_kses_post( $args['prepend'] ) : '' );
930 $html .= '<input id="' . esc_attr( $args['html_id'] ) . '"' . ( ! empty( $args['subclass'] ) ? ' class="' . esc_attr( $args['subclass'] ) . '"' : '' ) . ' type="text" value="' . esc_attr( $args['value'] ) . '" name="' . esc_attr( $args['name'] ) . '"' . ( ! empty( $args['autocomplete'] ) ? ' autocomplete="' . esc_attr( $args['autocomplete'] ) . '"' : '' ) . ( ! empty( $args['readonly'] ) ? ' readonly="readonly"' : '' ) . ' ' . disabled( $empty_disabled, false, false ) . '/>';
931 $html .= ( ! empty( $args['append'] ) ? wp_kses_post( $args['append'] ) : '' );
932
933 if ( ! $empty_disabled )
934 $html .= '<input' . ( $empty_disabled ? '' : ' class="hidden"' ) . ' type="text" value="' . esc_attr( $args['value'] ) . '" name="' . esc_attr( $args['name'] ) . '">';
935 }
936
937 if ( ! empty ( $args['after_field'] ) )
938 $html .= $args['after_field'];
939
940 if ( ! empty ( $args['description'] ) )
941 $html .= '<p class="description">' . $args['description'] . '</p>';
942
943 $html .= '</div>';
944
945 if ( ! empty( $args['return'] ) )
946 return $html;
947 else
948 echo $html;
949 }
950
951 /**
952 * Validate settings field.
953 *
954 * @param mixed $value
955 * @param string $type
956 * @param array $args
957 * @return mixed
958 */
959 public function validate_field( $value = null, $type = '', $args = [] ) {
960 if ( is_null( $value ) )
961 return null;
962
963 switch ( $type ) {
964 case 'boolean':
965 // possible value: 'true' or 'false'
966 $value = ( $value === 'true' || $value === true );
967 break;
968
969 case 'radio':
970 $value = is_array( $value ) ? $args['default'] : sanitize_key( $value );
971
972 // disallow disabled radios
973 if ( ! empty( $args['disabled'] ) && in_array( $value, $args['disabled'], true ) )
974 $value = $args['default'];
975 break;
976
977 case 'checkbox':
978 // possible value: 'empty' or array
979 if ( $value === 'empty' )
980 $value = [];
981 else {
982 if ( is_array( $value ) && ! empty( $value ) ) {
983 $value = array_map( 'sanitize_key', $value );
984 $values = [];
985
986 foreach ( $value as $single_value ) {
987 if ( array_key_exists( $single_value, $args['options'] ) )
988 $values[] = $single_value;
989 }
990
991 $value = $values;
992 } else
993 $value = [];
994 }
995 break;
996
997 case 'number':
998 $value = (int) $value;
999
1000 // is value lower than?
1001 if ( isset( $args['min'] ) && $value < $args['min'] )
1002 $value = $args['min'];
1003
1004 // is value greater than?
1005 if ( isset( $args['max'] ) && $value > $args['max'] )
1006 $value = $args['max'];
1007 break;
1008
1009 case 'color':
1010 $value = sanitize_text_field( $value );
1011 if ( ! preg_match( '/^#[a-f0-9]{3,6}$/i', $value ) ) {
1012 $value = $args['default'] ?? '#000000';
1013 }
1014 break;
1015
1016 case 'info':
1017 $value = '';
1018 break;
1019
1020 case 'custom':
1021 // do nothing
1022 break;
1023
1024 case 'textarea':
1025 $value = is_array( $value ) ? implode( "\n", array_map( 'sanitize_textarea_field', $value ) ) : sanitize_textarea_field( $value );
1026 break;
1027
1028 case 'editor':
1029 $value = is_array( $value ) ? '' : wp_kses_post( $value );
1030 break;
1031
1032 case 'class':
1033 $value = trim( $value );
1034
1035 // more than 1 class?
1036 if ( strpos( $value, ' ' ) !== false ) {
1037 // get unique valid HTML classes
1038 $value = array_unique( array_filter( array_map( 'sanitize_html_class', explode( ' ', $value ) ) ) );
1039
1040 if ( ! empty( $value ) )
1041 $value = implode( ' ', $value );
1042 else
1043 $value = '';
1044 // single class
1045 } else
1046 $value = sanitize_html_class( $value, $args['default'] );
1047 break;
1048
1049 case 'input':
1050 case 'select':
1051 default:
1052 $value = is_array( $value ) ? array_map( 'sanitize_text_field', $value ) : sanitize_text_field( $value );
1053 break;
1054 }
1055
1056 return stripslashes_deep( $value );
1057 }
1058
1059 /**
1060 * Validate settings.
1061 *
1062 * @param array $input
1063 * @return array
1064 */
1065 public function validate_settings( $input ) {
1066 // check capability
1067 $capability = apply_filters( 'pvc_settings_capability', 'manage_options' );
1068
1069 if ( ! current_user_can( $capability ) )
1070 return $input;
1071
1072 // check option page
1073 if ( empty( $_POST['option_page'] ) )
1074 return $input;
1075
1076 // try to get setting name and ID
1077 foreach ( $this->settings as $id => $setting ) {
1078 // tabs?
1079 if ( is_array( $setting['option_name'] ) ) {
1080 foreach ( $setting['option_name'] as $tab => $option_name ) {
1081 // found valid setting?
1082 if ( $option_name === $_POST['option_page'] ) {
1083 // assign setting ID
1084 $setting_id = $tab;
1085
1086 // assign setting name
1087 $setting_name = $option_name;
1088
1089 // assign setting key
1090 $setting_key = $id;
1091
1092 // already found setting, no need to check the rest
1093 break 2;
1094 }
1095 }
1096 } else {
1097 // found valid setting?
1098 if ( $setting['option_name'] === $_POST['option_page'] ) {
1099 // assign setting ID and key
1100 $setting_key = $setting_id = $id;
1101
1102 // assign setting name
1103 $setting_name = $setting['option_name'];
1104
1105 // already found setting, no need to check the rest
1106 break;
1107 }
1108 }
1109 }
1110
1111 // check setting id, no need to check $setting_name since it was at the same stage
1112 if ( empty( $setting_id ) )
1113 return $input;
1114
1115 $is_update_request = isset( $_POST['action'] ) && sanitize_key( wp_unslash( $_POST['action'] ) ) === 'update';
1116 $has_custom_submit_action = isset( $_POST['post_views_counter_import_views'] )
1117 || isset( $_POST['post_views_counter_analyse_views'] )
1118 || isset( $_POST['post_views_counter_reset_views'] );
1119
1120 // save settings
1121 if ( isset( $_POST['save_' . $setting_name] ) || ( $is_update_request && ! $has_custom_submit_action && ! isset( $_POST['reset_' . $setting_name] ) ) ) {
1122 $input = $this->validate_input_settings( $setting_id, $setting_key, $input );
1123
1124 // Older releases use these stable settings-error keys. Preserve
1125 // their notices instead of adding duplicate generic save feedback.
1126 $license_notices = function_exists( 'get_settings_errors' ) ? get_settings_errors( 'validate_license' ) : [];
1127
1128 if ( empty( $license_notices ) )
1129 add_settings_error( $setting_name, 'settings_saved', __( 'Settings saved.', $this->domain ), 'updated' );
1130 // reset settings
1131 } elseif ( isset( $_POST['reset_' . $setting_name] ) ) {
1132 // get default values
1133 $input = $this->get_setting_defaults( $setting_id );
1134
1135 // check custom reset functions
1136 if ( ! empty( $this->settings[$setting_key]['fields'] ) ) {
1137 foreach ( $this->settings[$setting_key]['fields'] as $field_id => $field ) {
1138 // skip invalid tab field if any
1139 if ( ! empty( $field['tab'] ) && $field['tab'] !== $setting_id )
1140 continue;
1141
1142 // custom reset function?
1143 if ( ! empty( $field['reset'] ) ) {
1144 // valid function? no need to check "else" since all default values are already set
1145 if ( $this->callback_function_exists( $field['reset'] ) ) {
1146 if ( $field['type'] === 'custom' )
1147 $input = call_user_func( $field['reset'], $input, $field );
1148 else
1149 $input[$field_id] = call_user_func( $field['reset'], $input[$field_id], $field );
1150 }
1151 }
1152 }
1153 }
1154
1155 // Retain the legacy reset key for new Free with older releases.
1156 $license_notices = function_exists( 'get_settings_errors' ) ? get_settings_errors( 'reset_license' ) : [];
1157
1158 if ( empty( $license_notices ) )
1159 add_settings_error( $setting_name, 'settings_restored', __( 'Settings restored to defaults.', $this->domain ), 'updated' );
1160 }
1161
1162 do_action( $this->prefix . '_configuration_updated', 'settings', $input );
1163
1164 return $input;
1165 }
1166
1167 /**
1168 * Validate input settings.
1169 *
1170 * @param string $setting_id
1171 * @param array $input
1172 * @return array
1173 */
1174 public function validate_input_settings( $setting_id, $setting_key, $input ) {
1175 $setting_defaults = $this->get_setting_defaults( $setting_id );
1176 $current_settings = $this->get_current_setting_values( $setting_id, $setting_key );
1177
1178 if ( ! empty( $this->settings[$setting_key]['fields'] ) ) {
1179 foreach ( $this->settings[$setting_key]['fields'] as $field_id => $field ) {
1180 // skip saving this field?
1181 if ( ! empty( $field['skip_saving'] ) ) {
1182 if ( array_key_exists( $field_id, $current_settings ) )
1183 $input[$field_id] = $current_settings[$field_id];
1184 else
1185 unset( $input[$field_id] );
1186
1187 continue;
1188 }
1189
1190 // skip invalid tab field if any
1191 if ( ! empty( $field['tab'] ) && $field['tab'] !== $setting_id )
1192 continue;
1193
1194 // custom validate function?
1195 if ( ! empty( $field['validate'] ) ) {
1196 // valid function?
1197 if ( $this->callback_function_exists( $field['validate'] ) ) {
1198 if ( $field['type'] === 'custom' )
1199 $input = call_user_func( $field['validate'], $input, $field );
1200 else
1201 $input[$field_id] = isset( $input[$field_id] ) ? call_user_func( $field['validate'], $input[$field_id], $field ) : $setting_defaults[$field_id];
1202 } else
1203 $input[$field_id] = $setting_defaults[$field_id];
1204 } else {
1205 // field data?
1206 if ( isset( $input[$field_id] ) ) {
1207 // make sure default value is available
1208 if ( ! isset( $field['default'] ) )
1209 $field['default'] = $setting_defaults[$field_id];
1210
1211 $input[$field_id] = $this->validate_field( $input[$field_id], $field['type'], $field );
1212 } else
1213 $input[$field_id] = $setting_defaults[$field_id];
1214 }
1215
1216 // update input data
1217 $this->input_settings = $input;
1218
1219 // add this field as validated
1220 $this->validated_settings[] = $field_id;
1221 }
1222 }
1223
1224 return $input;
1225 }
1226
1227 /**
1228 * Get the currently stored values for a settings group.
1229 *
1230 * @param string $setting_id
1231 * @param string $setting_key
1232 * @return array
1233 */
1234 private function get_current_setting_values( $setting_id, $setting_key ) {
1235 if ( empty( $this->settings[$setting_key]['option_name'] ) )
1236 return [];
1237
1238 $option_name = $this->settings[$setting_key]['option_name'];
1239
1240 if ( is_array( $option_name ) )
1241 $option_name = isset( $option_name[$setting_id] ) ? $option_name[$setting_id] : '';
1242
1243 if ( ! is_string( $option_name ) || $option_name === '' )
1244 return [];
1245
1246 $current_values = get_option( $option_name, [] );
1247
1248 return is_array( $current_values ) ? $current_values : [];
1249 }
1250
1251 /**
1252 * Check whether callback is a valid function.
1253 *
1254 * @param string|array $callback
1255 * @return bool
1256 */
1257 public function callback_function_exists( $callback ) {
1258 // function as array?
1259 if ( is_array( $callback ) ) {
1260 list( $object, $function ) = $callback;
1261
1262 // check function existence
1263 $function_exists = method_exists( $object, $function );
1264 // function as string?
1265 } elseif ( is_string( $callback ) ) {
1266 // check function existence
1267 $function_exists = function_exists( $callback );
1268 } else
1269 $function_exists = false;
1270
1271 return $function_exists;
1272 }
1273
1274 /**
1275 * Get value based on minimum and maximum.
1276 *
1277 * @param array $data
1278 * @param string $setting_name
1279 * @param int $default
1280 * @param int $min
1281 * @param int $max
1282 * @return void
1283 */
1284 public function get_int_value( $data, $setting_name, $default, $min, $max ) {
1285 // check existence of value
1286 $value = array_key_exists( $setting_name, $data ) ? (int) $data[$setting_name] : $default;
1287
1288 if ( $value > $max || $value < $min )
1289 $value = $default;
1290
1291 return $value;
1292 }
1293 }
1294