← All changes
|
includes/abstracts/abstract-class-charitable-form.php
+174
-44
1.6.8
→
1.8.13
View file →
| @@ -1,17 +1,20 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | 3 | * A base class to be extended by specific form classes. |
| 4 | 4 | * |
| 5 | - * @package Charitable/Classes/Charitable_Form | |
| 6 | - * @version 1.0.0 | |
| 7 | - * @author Eric Daams | |
| 8 | - * @copyright Copyright (c) 2019, Studio 164a | |
| 9 | - * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License | |
| 5 | + * @package Charitable/Classes/Charitable_Form | |
| 6 | + * @author David Bisset | |
| 7 | + * @copyright Copyright (c) 2023, WP Charitable LLC | |
| 8 | + * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License | |
| 9 | + * @since 1.0.0 | |
| 10 | + * @version 1.8.12.4 | |
| 10 | 11 | */ |
| 11 | 12 | |
| 12 | 13 | // Exit if accessed directly. |
| 13 | -if ( ! defined( 'ABSPATH' ) ) { exit; } | |
| 14 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 15 | + exit; | |
| 16 | +} | |
| 14 | 17 | |
| 15 | 18 | if ( ! class_exists( 'Charitable_Form' ) ) : |
| 16 | 19 | |
| 17 | 20 | /** |
| @@ -95,9 +98,10 @@ | ||
| 95 | 98 | charitable_get_deprecated()->doing_it_wrong( |
| 96 | 99 | __METHOD__, |
| 97 | 100 | sprintf( |
| 98 | 101 | /* translators: %s: class name */ |
| 99 | - __( 'None of the callbacks are required as of Charitable 1.5; use `Charitable_Public_Form_View` instead. Issue encountered in %s class.' ), get_class( $this ) | |
| 102 | + __( 'None of the callbacks are required as of Charitable 1.5; use `Charitable_Public_Form_View` instead. Issue encountered in %s class.', 'charitable' ), | |
| 103 | + get_class( $this ) | |
| 100 | 104 | ), |
| 101 | 105 | '1.5.0' |
| 102 | 106 | ); |
| 103 | 107 | |
| @@ -153,8 +157,9 @@ | ||
| 153 | 157 | * |
| 154 | 158 | * @return array |
| 155 | 159 | */ |
| 156 | 160 | public function get_fields() { |
| 161 | + return array(); | |
| 157 | 162 | } |
| 158 | 163 | |
| 159 | 164 | /** |
| 160 | 165 | * Return the form action. |
| @@ -193,13 +198,17 @@ | ||
| 193 | 198 | * |
| 194 | 199 | * @param array $fields The hidden fields as a key=>value array. |
| 195 | 200 | * @param Charitable_Form $form This instance of `Charitable_Form`. |
| 196 | 201 | */ |
| 197 | - return apply_filters( 'charitable_form_hidden_fields', array( | |
| 198 | - $this->nonce_name => wp_create_nonce( $this->nonce_action ), | |
| 199 | - '_wp_http_referer' => wp_unslash( $_SERVER['REQUEST_URI'] ), | |
| 200 | - 'charitable_action' => $this->form_action, | |
| 201 | - ), $this ); | |
| 202 | + return apply_filters( | |
| 203 | + 'charitable_form_hidden_fields', | |
| 204 | + array( | |
| 205 | + $this->nonce_name => wp_create_nonce( $this->nonce_action ), | |
| 206 | + '_wp_http_referer' => wp_unslash( $_SERVER['REQUEST_URI'] ), // phpcs:ignore | |
| 207 | + 'charitable_action' => $this->form_action, | |
| 208 | + ), | |
| 209 | + $this | |
| 210 | + ); | |
| 202 | 211 | } |
| 203 | 212 | |
| 204 | 213 | /** |
| 205 | 214 | * Output the nonce. |
| @@ -225,10 +234,10 @@ | ||
| 225 | 234 | |
| 226 | 235 | if ( ! $validated ) { |
| 227 | 236 | charitable_get_notices()->add_error( __( 'Unable to submit form. Please try again.', 'charitable' ) ); |
| 228 | 237 | |
| 229 | - if ( defined( 'CHARITABLE_DEBUG' ) && CHARITABLE_DEBUG ) { | |
| 230 | - error_log( | |
| 238 | + if ( charitable_is_debug() ) { | |
| 239 | + error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 231 | 240 | sprintf( |
| 232 | 241 | /* translators: %1$s: nonce name; %2$s: nonce action; %3$s: submitted nonce */ |
| 233 | 242 | __( 'Nonce verification failed for nonce with name "%1$s" and action "%2$s". Submitted nonce is "%3$s".', 'charitable' ), |
| 234 | 243 | $this->nonce_name, |
| @@ -289,9 +298,10 @@ | ||
| 289 | 298 | |
| 290 | 299 | /** |
| 291 | 300 | * Check the passed fields to ensure that all required fields have been submitted. |
| 292 | 301 | * |
| 293 | - * @since 1.0.0 | |
| 302 | + * @since 1.0.0 | |
| 303 | + * @version 1.8.4.5 Added check for minimum password length. | |
| 294 | 304 | * |
| 295 | 305 | * @param array $fields Array of form fields. |
| 296 | 306 | * @param array $submitted Submitted values. |
| 297 | 307 | * @return boolean |
| @@ -304,11 +314,10 @@ | ||
| 304 | 314 | $ret = true; |
| 305 | 315 | $missing = array(); |
| 306 | 316 | |
| 307 | 317 | foreach ( $this->get_required_fields( $fields ) as $key => $field ) { |
| 308 | - | |
| 309 | 318 | /* We already have a value for this field. */ |
| 310 | - if ( ! empty( $field['value'] ) && 'checkbox' != $field['type'] ) { | |
| 319 | + if ( ! empty( $field['value'] ) && 'checkbox' !== $field['type'] ) { | |
| 311 | 320 | continue; |
| 312 | 321 | } |
| 313 | 322 | |
| 314 | 323 | $exists = isset( $submitted[ $key ] ); |
| @@ -319,11 +328,14 @@ | ||
| 319 | 328 | $exists = ! empty( $value ) || ( is_string( $value ) && strlen( $value ) ); |
| 320 | 329 | } |
| 321 | 330 | |
| 322 | 331 | /* If a value was not provided, check if it's in the $_FILES array. */ |
| 332 | + // phpcs:disable WordPress.Security.NonceVerification.Missing | |
| 323 | 333 | if ( ! $exists ) { |
| 334 | + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- $_FILES array, validated by isset and empty checks | |
| 324 | 335 | $exists = ( 'picture' == $field['type'] && isset( $_FILES[ $key ] ) && ! empty( $_FILES[ $key ]['name'] ) ); |
| 325 | 336 | } |
| 337 | + // phpcs:enable WordPress.Security.NonceVerification.Missing | |
| 326 | 338 | |
| 327 | 339 | $exists = apply_filters( 'charitable_required_field_exists', $exists, $key, $field, $submitted, $this ); |
| 328 | 340 | |
| 329 | 341 | if ( ! $exists ) { |
| @@ -331,10 +343,28 @@ | ||
| 331 | 343 | $label = isset( $field['label'] ) ? $field['label'] : $key; |
| 332 | 344 | |
| 333 | 345 | $missing[] = $label; |
| 334 | 346 | } |
| 335 | - }//end foreach | |
| 347 | + } // end foreach | |
| 336 | 348 | |
| 349 | + // If the user_pass field is required we need to confirm a minimum number of characters for security reasons. | |
| 350 | + if ( isset( $fields['user_pass'] ) && ! empty( $fields['user_pass']['required'] ) ) { | |
| 351 | + $min_length = apply_filters( 'charitable_minimum_password_length', 8 ); | |
| 352 | + | |
| 353 | + if ( isset( $submitted['user_pass'] ) && strlen( $submitted['user_pass'] ) < $min_length ) { | |
| 354 | + charitable_get_notices()->add_error( | |
| 355 | + sprintf( | |
| 356 | + '%s', | |
| 357 | + sprintf( | |
| 358 | + /* translators: %d is the minimum password length required. */ | |
| 359 | + __( 'Your password must be at least %d characters long.', 'charitable' ), | |
| 360 | + $min_length | |
| 361 | + ) | |
| 362 | + ) | |
| 363 | + ); | |
| 364 | + } | |
| 365 | + } | |
| 366 | + | |
| 337 | 367 | $missing = apply_filters( 'charitable_form_missing_fields', $missing, $this, $fields, $submitted ); |
| 338 | 368 | |
| 339 | 369 | if ( count( $missing ) ) { |
| 340 | 370 | |
| @@ -340,9 +370,10 @@ | ||
| 340 | 370 | |
| 341 | 371 | $missing_fields = implode( '</li><li>', $missing ); |
| 342 | 372 | |
| 343 | 373 | charitable_get_notices()->add_error( |
| 344 | - sprintf( '<p>%s</p><ul class="error-list"><li>%s</li></ul>', | |
| 374 | + sprintf( | |
| 375 | + '<p>%s</p><ul class="error-list"><li>%s</li></ul>', | |
| 345 | 376 | __( 'There were problems with your form submission. The following required fields were not filled out:', 'charitable' ), |
| 346 | 377 | $missing_fields |
| 347 | 378 | ) |
| 348 | 379 | ); |
| @@ -368,14 +399,19 @@ | ||
| 368 | 399 | if ( ! isset( $field['type'] ) || 'paragraph' == $field['type'] ) { |
| 369 | 400 | return $ret; |
| 370 | 401 | } |
| 371 | 402 | |
| 372 | - /* Get the data type. Default to meta if no type is set. */ | |
| 373 | - if ( isset( $field['data_type'] ) ) { | |
| 374 | - $ret[ $field['data_type'] ][ $key ] = $field['type']; | |
| 375 | - } else { | |
| 376 | - $ret[ $key ] = $field['type']; | |
| 377 | - } | |
| 403 | + /** | |
| 404 | + * Group the fields by data type. | |
| 405 | + * | |
| 406 | + * Where fields don't have a data_type specified, we group | |
| 407 | + * them as 'internal', since we need them to be grouped into a | |
| 408 | + * data type for the data processor to work as expected. | |
| 409 | + * | |
| 410 | + * @since 1.6.51 Added 'internal' data type as fallback. | |
| 411 | + */ | |
| 412 | + $data_type = isset( $field['data_type'] ) ? $field['data_type'] : 'internal'; | |
| 413 | + $ret[ $data_type ][ $key ] = $field['type']; | |
| 378 | 414 | |
| 379 | 415 | return $ret; |
| 380 | 416 | } |
| 381 | 417 | |
| @@ -386,13 +422,33 @@ | ||
| 386 | 422 | * advantage of the filter on the values. |
| 387 | 423 | * |
| 388 | 424 | * @since 1.0.0 |
| 389 | 425 | * |
| 390 | - * @return array | |
| 426 | + * @param array $fields Merged array of all the fields in the form. | |
| 427 | + * @return array | |
| 391 | 428 | */ |
| 392 | 429 | public function get_submitted_values() { |
| 393 | 430 | if ( ! isset( $this->submitted ) ) { |
| 394 | - $this->submitted = apply_filters( 'charitable_form_submitted_values', $_POST, $this ); | |
| 431 | + $submitted = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing | |
| 432 | + $fields = $this->get_fields_by_data_type(); | |
| 433 | + $options = $this->get_valid_field_options(); | |
| 434 | + | |
| 435 | + /* Process the submitted data. This handles field sanitization. */ | |
| 436 | + $data_processor = new Charitable_Data_Processor( $submitted, $fields, $options ); | |
| 437 | + | |
| 438 | + foreach ( $data_processor->output() as $data_type_data ) { | |
| 439 | + $submitted = array_merge( $submitted, $data_type_data ); | |
| 440 | + } | |
| 441 | + | |
| 442 | + /** | |
| 443 | + * Filter the submitted values. | |
| 444 | + * | |
| 445 | + * @since 1.0.0 | |
| 446 | + * | |
| 447 | + * @param array $data The submitted data. | |
| 448 | + * @param Charitable_Form $form The form class. | |
| 449 | + */ | |
| 450 | + $this->submitted = apply_filters( 'charitable_form_submitted_values', $submitted, $this ); | |
| 395 | 451 | } |
| 396 | 452 | |
| 397 | 453 | return $this->submitted; |
| 398 | 454 | } |
| @@ -402,9 +458,9 @@ | ||
| 402 | 458 | * |
| 403 | 459 | * @since 1.0.0 |
| 404 | 460 | * |
| 405 | 461 | * @param string $key The key to search for. |
| 406 | - * @return mixed Submitted value if set. NULL if value was not set. | |
| 462 | + * @return mixed Submitted value if set. NULL if value was not set. | |
| 407 | 463 | */ |
| 408 | 464 | public function get_submitted_value( $key ) { |
| 409 | 465 | $submitted = $this->get_submitted_values(); |
| 410 | 466 | return isset( $submitted[ $key ] ) ? $submitted[ $key ] : null; |
| @@ -421,13 +477,12 @@ | ||
| 421 | 477 | * @param array $overrides Override the wp_handle_upload() behavior. Optional. |
| 422 | 478 | * @return int|WP_Error ID of the attachment or a WP_Error object on failure. |
| 423 | 479 | */ |
| 424 | 480 | public function upload_post_attachment( $file_key, $post_id, $post_data = array(), $overrides = array() ) { |
| 481 | + require_once ABSPATH . 'wp-admin/includes/image.php'; | |
| 482 | + require_once ABSPATH . 'wp-admin/includes/file.php'; | |
| 483 | + require_once ABSPATH . 'wp-admin/includes/media.php'; | |
| 425 | 484 | |
| 426 | - require_once( ABSPATH . 'wp-admin/includes/image.php' ); | |
| 427 | - require_once( ABSPATH . 'wp-admin/includes/file.php' ); | |
| 428 | - require_once( ABSPATH . 'wp-admin/includes/media.php' ); | |
| 429 | - | |
| 430 | 485 | $overrides = $this->get_file_overrides( $file_key, $overrides ); |
| 431 | 486 | |
| 432 | 487 | return media_handle_upload( $file_key, $post_id, $post_data, $overrides ); |
| 433 | 488 | } |
| @@ -434,23 +489,31 @@ | ||
| 434 | 489 | |
| 435 | 490 | /** |
| 436 | 491 | * Upload a file. |
| 437 | 492 | * |
| 493 | + * @since 1.0.0 | |
| 494 | + * @version 1.8.9.1 | |
| 495 | + * | |
| 438 | 496 | * @param string $file_key Reference to a single element of `$_FILES`. Call the |
| 439 | - * function once for each uploaded file. | |
| 497 | + * function once for each uploaded file. | |
| 440 | 498 | * @param array $overrides Optional. An associative array of names=>values to |
| 441 | - * override default variables. Default false. | |
| 442 | - * @return array|WP_Error On success, returns an associative array of file attributes. | |
| 443 | - * On failure, returns $overrides['upload_error_handler'](&$file, $message ) | |
| 444 | - * or array( 'error'=>$message ). | |
| 445 | - * @since 1.0.0 | |
| 499 | + * override default variables. Default false. | |
| 500 | + * @return array|WP_Error On success, returns an associative array of file attributes. | |
| 501 | + * On failure, returns $overrides['upload_error_handler'](&$file, $message ) | |
| 502 | + * or array( 'error'=>$message ). | |
| 446 | 503 | */ |
| 447 | 504 | public function upload_file( $file_key, $overrides = array() ) { |
| 505 | + require_once ABSPATH . 'wp-admin/includes/file.php'; | |
| 448 | 506 | |
| 449 | - require_once( ABSPATH . 'wp-admin/includes/file.php' ); | |
| 507 | + // phpcs:disable WordPress.Security.NonceVerification.Missing | |
| 508 | + if ( ! isset( $_FILES[ $file_key ] ) ) { | |
| 509 | + return new WP_Error( 'upload_error', __( 'No file was uploaded.', 'charitable' ) ); | |
| 510 | + } | |
| 450 | 511 | |
| 451 | 512 | $overrides = $this->get_file_overrides( $file_key, $overrides ); |
| 513 | + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- $_FILES array, sanitized by wp_handle_upload | |
| 452 | 514 | $file = wp_handle_upload( $_FILES[ $file_key ], $overrides ); |
| 515 | + // phpcs:enable WordPress.Security.NonceVerification.Missing | |
| 453 | 516 | |
| 454 | 517 | if ( isset( $file['error'] ) ) { |
| 455 | 518 | return new WP_Error( 'upload_error', $file['error'] ); |
| 456 | 519 | } |
| @@ -476,8 +539,9 @@ | ||
| 476 | 539 | 'png' => 'image/png', |
| 477 | 540 | 'bmp' => 'image/bmp', |
| 478 | 541 | 'tif|tiff' => 'image/tiff', |
| 479 | 542 | 'ico' => 'image/x-icon', |
| 543 | + 'webp' => 'image/webp', | |
| 480 | 544 | ); |
| 481 | 545 | |
| 482 | 546 | $defaults = array( |
| 483 | 547 | 'test_form' => false, |
| @@ -489,8 +553,70 @@ | ||
| 489 | 553 | return $overrides; |
| 490 | 554 | } |
| 491 | 555 | |
| 492 | 556 | /** |
| 557 | + * Returns all fields as a merged array. | |
| 558 | + * | |
| 559 | + * @since 1.6.51 | |
| 560 | + * | |
| 561 | + * @return array[] | |
| 562 | + */ | |
| 563 | + public function get_merged_fields() { | |
| 564 | + $fields = array(); | |
| 565 | + | |
| 566 | + foreach ( $this->get_fields() as $key => $section ) { | |
| 567 | + if ( isset( $section['fields'] ) ) { | |
| 568 | + $fields = array_merge( $fields, $section['fields'] ); | |
| 569 | + } else { | |
| 570 | + $fields[ $key ] = $section; | |
| 571 | + } | |
| 572 | + } | |
| 573 | + | |
| 574 | + return $fields; | |
| 575 | + } | |
| 576 | + | |
| 577 | + /** | |
| 578 | + * Return the form fields, organized by data type. | |
| 579 | + * | |
| 580 | + * @since 1.6.51 | |
| 581 | + * | |
| 582 | + * @return array | |
| 583 | + */ | |
| 584 | + protected function get_fields_by_data_type() { | |
| 585 | + $fields = array(); | |
| 586 | + | |
| 587 | + foreach ( $this->get_merged_fields() as $key => $field ) { | |
| 588 | + $fields = $this->sort_field_by_data_type( $key, $field, $fields ); | |
| 589 | + } | |
| 590 | + | |
| 591 | + return $fields; | |
| 592 | + } | |
| 593 | + | |
| 594 | + /** | |
| 595 | + * Get an array of valid field options for fields in the form. | |
| 596 | + * | |
| 597 | + * @since 1.6.51 | |
| 598 | + * | |
| 599 | + * @return array | |
| 600 | + */ | |
| 601 | + protected function get_valid_field_options() { | |
| 602 | + $options = array(); | |
| 603 | + | |
| 604 | + foreach ( $this->get_merged_fields() as $key => $field ) { | |
| 605 | + /* The field has a specific set of options. */ | |
| 606 | + if ( isset( $field['options'] ) ) { | |
| 607 | + $options[ $key ] = array_keys( $field['options'] ); | |
| 608 | + /* Checkbox fields without a value set should be either 1 or nothing. */ | |
| 609 | + } elseif ( isset( $field['type'] ) && 'checkbox' === $field['type'] ) { | |
| 610 | + $checked_value = isset( $field['value'] ) ? $field['value'] : 1; | |
| 611 | + $options[ $key ] = array( $checked_value ); | |
| 612 | + } | |
| 613 | + } | |
| 614 | + | |
| 615 | + return $options; | |
| 616 | + } | |
| 617 | + | |
| 618 | + /** | |
| 493 | 619 | * Return the template name used for this field. |
| 494 | 620 | * |
| 495 | 621 | * @deprecated 1.8.0 |
| 496 | 622 | * |
| @@ -506,9 +632,9 @@ | ||
| 506 | 632 | '1.5.0', |
| 507 | 633 | 'Charitable_Public_Form_View::get_template_name()' |
| 508 | 634 | ); |
| 509 | 635 | |
| 510 | - return $form->view()->get_template_name( $field ); | |
| 636 | + return $this->view()->get_template_name( $field ); | |
| 511 | 637 | } |
| 512 | 638 | |
| 513 | 639 | /** |
| 514 | 640 | * Checks whether a template is valid. |
| @@ -685,12 +811,16 @@ | ||
| 685 | 811 | if ( ! $this->view()->rendered_hidden_fields ) { |
| 686 | 812 | $this->view()->render_hidden_fields(); |
| 687 | 813 | } |
| 688 | 814 | |
| 689 | - return $form->view()->render_field( $field, $key, array( | |
| 690 | - 'index' => $index, | |
| 691 | - 'namespace' => $namespace, | |
| 692 | - ) ); | |
| 815 | + return $form->view()->render_field( | |
| 816 | + $field, | |
| 817 | + $key, | |
| 818 | + array( | |
| 819 | + 'index' => $index, | |
| 820 | + 'namespace' => $namespace, | |
| 821 | + ) | |
| 822 | + ); | |
| 693 | 823 | } |
| 694 | 824 | } |
| 695 | 825 | |
| 696 | 826 | endif; |