PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.7.0
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.7.0
1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 1.8.0 All 210 releases
charitable / includes / admin / settings / class-charitable-settings.php

class-charitable-settings.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.7.0, at includes/admin/settings/class-charitable-settings.php

687 lines 21.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Charitable Settings UI.
4 *
5 * @package Charitable/Classes/Charitable_Settings
6 * @author David Bisset
7 * @copyright Copyright (c) 2022, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.0.0
10 * @version 1.0.0
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Settings' ) ) :
19
20 /**
21 * Charitable_Settings
22 *
23 * @final
24 * @since 1.0.0
25 */
26 final class Charitable_Settings {
27
28 /**
29 * The single instance of this class.
30 *
31 * @var Charitable_Settings|null
32 */
33 private static $instance = null;
34
35 /**
36 * Dynamic groups.
37 *
38 * @since 1.5.7
39 *
40 * @var array
41 */
42 private $dynamic_groups;
43
44 /**
45 * List of static pages, used in some settings.
46 *
47 * @since 1.5.9
48 *
49 * @var array
50 */
51 private $pages;
52
53 /**
54 * Create object instance.
55 *
56 * @since 1.0.0
57 */
58 private function __construct() {
59 do_action( 'charitable_admin_settings_start', $this );
60 }
61
62 /**
63 * Returns and/or create the single instance of this class.
64 *
65 * @since 1.2.0
66 *
67 * @return Charitable_Settings
68 */
69 public static function get_instance() {
70 if ( is_null( self::$instance ) ) {
71 self::$instance = new self();
72 }
73
74 return self::$instance;
75 }
76
77 /**
78 * Return the array of tabs used on the settings page.
79 *
80 * @since 1.0.0
81 *
82 * @return string[]
83 */
84 public function get_sections() {
85 /**
86 * Filter the settings tabs.
87 *
88 * @since 1.0.0
89 *
90 * @param string[] $tabs List of tabs in key=>label format.
91 */
92 return apply_filters(
93 'charitable_settings_tabs',
94 array(
95 'general' => __( 'General', 'charitable' ),
96 'gateways' => __( 'Payment Gateways', 'charitable' ),
97 'emails' => __( 'Emails', 'charitable' ),
98 'privacy' => __( 'Privacy', 'charitable' ),
99 'advanced' => __( 'Advanced', 'charitable' ),
100 )
101 );
102
103 }
104
105 /**
106 * Optionally add the extensions tab.
107 *
108 * @since 1.3.0
109 *
110 * @param string[] $tabs The existing set of tabs.
111 * @return string[]
112 */
113 public function maybe_add_extensions_tab( $tabs ) {
114 $actual_tab = isset( $_GET['tab'] ) ? $_GET['tab'] : 'general';
115
116 /* Set the tab to 'extensions' */
117 $_GET['tab'] = 'extensions';
118
119 /**
120 * Filter the settings in the extensions tab.
121 *
122 * @since 1.3.0
123 *
124 * @param array $fields Array of fields. Empty by default.
125 */
126 $settings = apply_filters( 'charitable_settings_tab_fields_extensions', array() );
127
128 /* Set the tab back to whatever it actually is */
129 $_GET['tab'] = $actual_tab;
130
131 if ( ! empty( $settings ) ) {
132 $tabs = charitable_add_settings_tab(
133 $tabs,
134 'extensions',
135 __( 'Extensions', 'charitable' ),
136 array(
137 'index' => 4,
138 )
139 );
140 }
141
142 return $tabs;
143 }
144
145 /**
146 * Add the hidden "extensions" section field.
147 *
148 * @since 1.6.7
149 *
150 * @param array $fields All the settings fields.
151 * @return array
152 */
153 public function add_hidden_extensions_setting_field( $fields ) {
154 if ( ! array_key_exists( 'extensions', $fields ) ) {
155 return $fields;
156 }
157
158 $fields['extensions']['section'] = array(
159 'title' => '',
160 'type' => 'hidden',
161 'priority' => 10000,
162 'value' => 'extensions',
163 'save' => false,
164 );
165
166 return $fields;
167 }
168
169 /**
170 * Register setting.
171 *
172 * @since 1.0.0
173 *
174 * @return void
175 */
176 public function register_settings() {
177 if ( ! charitable_is_settings_view() ) {
178 return;
179 }
180
181 register_setting( 'charitable_settings', 'charitable_settings', array( $this, 'sanitize_settings' ) );
182
183 $fields = $this->get_fields();
184
185 if ( empty( $fields ) ) {
186 return;
187 }
188
189 $sections = array_merge( $this->get_sections(), $this->get_dynamic_groups() );
190
191 /* Register each section */
192 foreach ( $sections as $section_key => $section ) {
193 $section_id = 'charitable_settings_' . $section_key;
194
195 add_settings_section(
196 $section_id,
197 __return_null(),
198 '__return_false',
199 $section_id
200 );
201
202 if ( ! isset( $fields[ $section_key ] ) || empty( $fields[ $section_key ] ) ) {
203 continue;
204 }
205
206 /* Sort by priority */
207 $section_fields = $fields[ $section_key ];
208 uasort( $section_fields, 'charitable_priority_sort' );
209
210 /* Add the individual fields within the section */
211 foreach ( $section_fields as $key => $field ) {
212 $this->register_field( $field, array( $section_key, $key ) );
213 }
214 }
215 }
216
217 /**
218 * Sanitize submitted settings before saving to the database including "workaround".
219 *
220 * @since 1.0.0
221 *
222 * @param array $values The submitted values.
223 * @return string
224 */
225 public function sanitize_settings( $values ) {
226 $old_values = get_option( 'charitable_settings', array() );
227 $new_values = array();
228
229 if ( ! is_array( $old_values ) ) {
230 $old_values = array();
231 }
232
233 if ( ! is_array( $values ) ) {
234 $values = array();
235 }
236
237 /* Loop through all fields, merging the submitted values into the master array */
238 foreach ( $values as $section => $submitted ) {
239 $new_values = array_merge( $new_values, $this->get_section_submitted_values( $section, $submitted ) );
240 }
241
242 $settings_keys = array_keys( $new_values );
243
244 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
245 error_log( 'santiize_settings' );
246 error_log( 'values:' );
247 error_log( print_r( $values, true ) );
248 error_log( 'old_values:' );
249 error_log( print_r( $old_values, true ) );
250 error_log( 'new_values:' );
251 error_log( print_r( $new_values, true ) );
252 }
253
254 // determine if Charitable gateway "test mode" is being changed, and if so add a notice to the user.
255 if ( array_key_exists('test_mode', $new_values ) && array_key_exists('test_mode', $old_values ) && $old_values['test_mode'] !== $new_values['test_mode'] ) {
256 $old_settings_keys = array_keys( $old_values );
257 $dismissible = true;
258 if ( in_array( 'gateways_stripe', $old_settings_keys ) ) {
259 charitable_get_admin_notices()->add_notice( 'Some active payment gateways <strong>(including Stripe)</strong> might have reset their connections due to an update in the test mode. Please check your active payment gateways in ensure they are still connected.', 'warning', false, $dismissible );
260 } else {
261 charitable_get_admin_notices()->add_notice( 'Some active payment gateways might have reset their connections due to an update in the test mode. Please check your active payment gateways in ensure they are still connected.', 'warning', false, $dismissible );
262 }
263 }
264
265 if ( in_array( 'gateways_stripe', $settings_keys ) && charitable()->is_stripe_connect_addon() && false === charitable_using_stripe_connect() ) {
266 // non-existant array keys in the array for API keys might be a result of the "manual update key" feature which is based on ajax and might not load keys into form if the user doesn't click on the link in the settings to view/edit them.
267 if ( ! array_key_exists( 'live_secret_key', $values['gateways_stripe'] ) && isset( $old_values['gateways_stripe']['live_secret_key'] ) && ( false !== $old_values['gateways_stripe']['live_secret_key'] ) ) {
268 $values['gateways_stripe']['live_secret_key'] = $old_values['gateways_stripe']['live_secret_key'];
269 }
270 if ( ! array_key_exists( 'live_public_key', $values['gateways_stripe'] ) && isset( $old_values['gateways_stripe']['live_public_key'] ) && ( false !== $old_values['gateways_stripe']['live_public_key'] ) ) {
271 $values['gateways_stripe']['live_public_key'] = $old_values['gateways_stripe']['live_secret_key'];
272 }
273 if ( ! array_key_exists( 'test_secret_key', $values['gateways_stripe'] ) && isset( $old_values['gateways_stripe']['test_secret_key'] ) && ( false !== $old_values['gateways_stripe']['test_secret_key'] ) ) {
274 $values['gateways_stripe']['test_secret_key'] = $old_values['gateways_stripe']['test_secret_key'];
275 }
276 if ( ! array_key_exists( 'test_public_key', $values['gateways_stripe'] ) && isset( $old_values['gateways_stripe']['test_public_key'] ) && ( false !== $old_values['gateways_stripe']['test_public_key'] ) ) {
277 $values['gateways_stripe']['test_public_key'] = $old_values['gateways_stripe']['test_public_key'];
278 }
279 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
280 error_log( 'update:' );
281 error_log( print_r( $values, true ) );
282 }
283 }
284
285 // determine if stripe is being returned as new values - if so, then perform the API key save workaround
286 if ( in_array( 'gateways_stripe', $settings_keys ) && charitable()->is_stripe_connect_addon() && false === charitable_using_stripe_connect() ) {
287 error_log( 'there is a stripe connect addon but we are not using stripe connect' );
288 // the stripe connect addon is installed AND the AM Stripe Connect isn't being used... so it's possible the keys could be manual and the user could be updating them... therefore let's just make sure the new values match the values incoming.
289 $new_values['gateways_stripe']['live_secret_key'] = ( isset( $values['gateways_stripe']['live_secret_key'] ) && ( false !== $values['gateways_stripe']['live_secret_key'] ) ) ? $values['gateways_stripe']['live_secret_key'] : null;
290 $new_values['gateways_stripe']['live_public_key'] = ( isset( $values['gateways_stripe']['live_public_key'] ) && ( false !== $values['gateways_stripe']['live_public_key'] ) ) ? $values['gateways_stripe']['live_public_key'] : null;
291 $new_values['gateways_stripe']['test_secret_key'] = ( isset( $values['gateways_stripe']['test_secret_key'] ) && ( false !== $values['gateways_stripe']['test_secret_key'] ) ) ? $values['gateways_stripe']['test_secret_key'] : null;
292 $new_values['gateways_stripe']['test_public_key'] = ( isset( $values['gateways_stripe']['test_public_key'] ) && ( false !== $values['gateways_stripe']['test_public_key'] ) ) ? $values['gateways_stripe']['test_public_key'] : null;
293 } else if ( in_array( 'gateways_stripe', $settings_keys ) ) {
294 // otherwise we preserve the keys
295 error_log( 'last resort:' );
296 $new_values['gateways_stripe']['live_secret_key'] = ( isset( $old_values['gateways_stripe']['live_secret_key'] ) && ( false !== $old_values['gateways_stripe']['live_secret_key'] ) ) ? $old_values['gateways_stripe']['live_secret_key'] : null;
297 $new_values['gateways_stripe']['live_public_key'] = ( isset( $old_values['gateways_stripe']['live_public_key'] ) && ( false !== $old_values['gateways_stripe']['live_public_key'] ) ) ? $old_values['gateways_stripe']['live_public_key'] : null;
298 $new_values['gateways_stripe']['test_secret_key'] = ( isset( $old_values['gateways_stripe']['test_secret_key'] ) && ( false !== $old_values['gateways_stripe']['test_secret_key'] ) ) ? $old_values['gateways_stripe']['test_secret_key'] : null;
299 $new_values['gateways_stripe']['test_public_key'] = ( isset( $old_values['gateways_stripe']['test_public_key'] ) && ( false !== $old_values['gateways_stripe']['test_public_key'] ) ) ? $old_values['gateways_stripe']['test_public_key'] : null;
300 }
301
302 // preserve any webhook keys, regardless if stripe connect addon is active or not.
303 if ( in_array( 'gateways_stripe', $settings_keys ) ) {
304 if ( array_key_exists( 'test_webhook_id', $old_values['gateways_stripe'] ) && isset( $old_values['gateways_stripe']['test_webhook_id'] ) ) {
305 $new_values['gateways_stripe']['test_webhook_id'] = $old_values['gateways_stripe']['test_webhook_id'];
306 }
307 if ( array_key_exists( 'live_webhook_id', $old_values['gateways_stripe'] ) && isset( $old_values['gateways_stripe']['live_webhook_id'] ) ) {
308 $new_values['gateways_stripe']['live_webhook_id'] = $old_values['gateways_stripe']['live_webhook_id'];
309 }
310 }
311
312 $values = wp_parse_args( $new_values, $old_values );
313
314 if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) {
315 error_log( 'santiize_settings values updated' );
316 error_log( print_r( $values, true ) );
317 }
318
319 /**
320 * Filter sanitized settings.
321 *
322 * @since 1.0.0
323 *
324 * @param array $values All values, merged.
325 * @param array $new_values Newly submitted values.
326 * @param array $old_values Old settings.
327 */
328 $values = apply_filters( 'charitable_save_settings', $values, $new_values, $old_values );
329
330 $this->add_update_message( __( 'Settings saved', 'charitable' ), 'success' );
331
332 return $values;
333 }
334
335 /**
336 * Checkbox settings should always be either 1 or 0.
337 *
338 * @since 1.0.0
339 *
340 * @param mixed $value Submitted value for field.
341 * @param array $field Field definition.
342 * @return int
343 */
344 public function sanitize_checkbox_value( $value, $field ) {
345 if ( isset( $field['type'] ) && 'checkbox' == $field['type'] ) {
346 $value = intval( $value && 'on' == $value );
347 }
348
349 return $value;
350 }
351
352 /**
353 * Render field. This is the default callback used for all fields, unless an alternative callback has been specified.
354 *
355 * @since 1.0.0
356 *
357 * @param array $args Field definition.
358 * @return void
359 */
360 public function render_field( $args ) {
361 $field_type = isset( $args['type'] ) ? $args['type'] : 'text';
362
363 charitable_admin_view( 'settings/' . $field_type, $args );
364 }
365
366 /**
367 * Returns an array of all pages in the id=>title format.
368 *
369 * @since 1.0.0
370 *
371 * @return string[]
372 */
373 public function get_pages() {
374 if ( ! isset( $this->pages ) ) {
375 $this->pages = charitable_get_pages_options();
376 }
377
378 return $this->pages;
379 }
380
381 /**
382 * Add an update message.
383 *
384 * @since 1.4.6
385 *
386 * @param string $message The message text.
387 * @param string $type The type of message. Options: 'error', 'success', 'warning', 'info'.
388 * @param boolean $dismissible Whether the message can be dismissed.
389 * @return void
390 */
391 public function add_update_message( $message, $type = 'error', $dismissible = true ) {
392 if ( ! in_array( $type, array( 'error', 'success', 'warning', 'info' ) ) ) {
393 $type = 'error';
394 }
395
396 charitable_get_admin_notices()->add_notice( $message, $type, false, $dismissible );
397 }
398
399 /**
400 * Recursively add settings fields, given an array.
401 *
402 * @since 1.0.0
403 *
404 * @param array $field The setting field.
405 * @param array $keys Array containing the section key and field key.
406 * @return void
407 */
408 private function register_field( $field, $keys ) {
409 $section_id = 'charitable_settings_' . $keys[0];
410
411 if ( isset( $field['render'] ) && ! $field['render'] ) {
412 return;
413 }
414
415 /* Drop the first key, which is the section identifier */
416 $field['name'] = implode( '][', $keys );
417
418 if ( ! $this->is_dynamic_group( $keys[0] ) ) {
419 array_shift( $keys );
420 }
421
422 $field['key'] = $keys;
423 $field['classes'] = $this->get_field_classes( $field );
424 $callback = isset( $field['callback'] ) ? $field['callback'] : array( $this, 'render_field' );
425 $label = $this->get_field_label( $field, end( $keys ) );
426
427 add_settings_field(
428 sprintf( 'charitable_settings_%s', implode( '_', $keys ) ),
429 $label,
430 $callback,
431 $section_id,
432 $section_id,
433 $field
434 );
435 }
436
437 /**
438 * Return the label for the given field.
439 *
440 * @since 1.0.0
441 *
442 * @param array $field The field definition.
443 * @param string $key The field key.
444 * @return string
445 */
446 private function get_field_label( $field, $key ) {
447 $label = '';
448
449 if ( isset( $field['label_for'] ) ) {
450 $label = $field['label_for'];
451 }
452
453 if ( isset( $field['title'] ) ) {
454 $label = $field['title'];
455 }
456
457 return $label;
458 }
459
460 /**
461 * Return a space separated string of classes for the given field.
462 *
463 * @since 1.0.0
464 *
465 * @param array $field Field definition.
466 * @return string
467 */
468 private function get_field_classes( $field ) {
469 $classes = array( 'charitable-settings-field' );
470
471 if ( isset( $field['class'] ) ) {
472 $classes[] = $field['class'];
473 }
474
475 /**
476 * Filter the list of classes to apply to settings fields.
477 *
478 * @since 1.0.0
479 *
480 * @param array $classes The list of classes.
481 * @param array $field The field definition.
482 */
483 $classes = apply_filters( 'charitable_settings_field_classes', $classes, $field );
484
485 return implode( ' ', $classes );
486 }
487
488 /**
489 * Return an array with all the fields & sections to be displayed.
490 *
491 * @uses charitable_settings_fields
492 * @see Charitable_Settings::register_setting()
493 * @since 1.0.0
494 *
495 * @return array
496 */
497 private function get_fields() {
498 /**
499 * Use the charitable_settings_tab_fields to include the fields for new tabs.
500 * DO NOT use it to add individual fields. That should be done with the
501 * filters within each of the methods.
502 */
503 $fields = array();
504
505 foreach ( $this->get_sections() as $section_key => $section ) {
506 /**
507 * Filter the array of fields to display in a particular tab.
508 *
509 * @since 1.0.0
510 *
511 * @param array $fields Array of fields.
512 */
513 $fields[ $section_key ] = apply_filters( 'charitable_settings_tab_fields_' . $section_key, array() );
514 }
515
516 /**
517 * Filter the array of settings fields.
518 *
519 * @since 1.0.0
520 *
521 * @param array $fields Array of fields.
522 */
523 return apply_filters( 'charitable_settings_tab_fields', $fields );
524 }
525
526 /**
527 * Get the submitted value for a particular setting.
528 *
529 * @since 1.0.0
530 *
531 * @param string $key The key of the setting being saved.
532 * @param array $field The setting field.
533 * @param array $submitted The submitted values.
534 * @param string $section The section being saved.
535 * @return mixed|null Returns null if the value was not submitted or is not applicable.
536 */
537 private function get_setting_submitted_value( $key, $field, $submitted, $section ) {
538 $value = null;
539
540 if ( isset( $field['save'] ) && ! $field['save'] ) {
541 return $value;
542 }
543
544 $field_type = isset( $field['type'] ) ? $field['type'] : '';
545
546 switch ( $field_type ) {
547
548 case 'checkbox':
549 $value = intval( array_key_exists( $key, $submitted ) && 'on' == $submitted[ $key ] );
550 break;
551
552 case 'multi-checkbox':
553 $value = isset( $submitted[ $key ] ) ? $submitted[ $key ] : array();
554 break;
555
556 case '':
557 case 'heading':
558 return $value;
559
560 default:
561 if ( ! array_key_exists( $key, $submitted ) ) {
562 return $value;
563 }
564
565 $value = $submitted[ $key ];
566
567 }//end switch
568
569 /**
570 * General way to sanitize values. If you only need to sanitize a
571 * specific setting, used the filter below instead.
572 *
573 * @since 1.0.0
574 *
575 * @param mixed $value The current setting value.
576 * @param array $field The field configuration.
577 * @param array $submitted All submitted data.
578 * @param string $key The setting key.
579 * @param string $section The section being saved.
580 */
581 $value = apply_filters( 'charitable_sanitize_value', $value, $field, $submitted, $key, $section );
582
583 /**
584 * Sanitize the setting value.
585 *
586 * The filter hook is formatted like this: charitable_sanitize_value_{$section}_{$key}.
587 *
588 * @since 1.5.0
589 *
590 * @param mixed $value The current setting value.
591 * @param array $field The field configuration.
592 * @param array $submitted All submitted data.
593 */
594 return apply_filters( 'charitable_sanitize_value_' . $section . '_' . $key, $value, $field, $submitted );
595 }
596
597 /**
598 * Return the submitted values for the given section.
599 *
600 * @since 1.0.0
601 *
602 * @param string $section The section being edited.
603 * @param array $submitted The submitted values.
604 * @return array
605 */
606 private function get_section_submitted_values( $section, $submitted ) {
607 $values = array();
608 $form_fields = $this->get_fields();
609
610 if ( ! isset( $form_fields[ $section ] ) ) {
611 return $values;
612 }
613
614 foreach ( $form_fields[ $section ] as $key => $field ) {
615 $value = $this->get_setting_submitted_value( $key, $field, $submitted, $section );
616
617 if ( is_null( $value ) ) {
618 continue;
619 }
620
621 if ( $this->is_dynamic_group( $section ) ) {
622 $values[ $section ][ $key ] = $value;
623 continue;
624 }
625
626 $values[ $key ] = $value;
627 }
628
629 return $values;
630 }
631
632 /**
633 * Return list of dynamic groups.
634 *
635 * @since 1.0.0
636 *
637 * @return string[]
638 */
639 private function get_dynamic_groups() {
640 if ( ! isset( $this->dynamic_groups ) ) {
641 /**
642 * Filter the list of dynamic groups.
643 *
644 * @since 1.0.0
645 *
646 * @param array $groups The dynamic groups.
647 */
648 $this->dynamic_groups = apply_filters( 'charitable_dynamic_groups', array() );
649 }
650
651 return $this->dynamic_groups;
652 }
653
654 /**
655 * Returns whether the given key indicates the start of a new section of the settings.
656 *
657 * @since 1.0.0
658 *
659 * @param string $composite_key The unique key for this group.
660 * @return boolean
661 */
662 private function is_dynamic_group( $composite_key ) {
663 return array_key_exists( $composite_key, $this->get_dynamic_groups() );
664 }
665
666 /* DEPRECATED FUNCTIONS */
667
668 /**
669 * Get the update messages.
670 *
671 * @deprecated 1.7.0
672 *
673 * @since 1.4.13 Deprecated.
674 */
675 public function get_update_messages() {
676 charitable_get_deprecated()->deprecated_function(
677 __METHOD__,
678 '1.4.13',
679 'Charitable_Admin_Notices::get_notices()'
680 );
681
682 return charitable_get_admin_notices()->get_notices();
683 }
684 }
685
686 endif;
687