PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.12
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.12
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 / utilities / class-charitable-data-processor.php

class-charitable-data-processor.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.12, at includes/utilities/class-charitable-data-processor.php

534 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Responsible for accepting a set of raw data, such as form submission data,
4 * and sanitizing and normalizing that data, depending on its data type and
5 * the type of data (text, checkbox, etc.).
6 *
7 * @package Charitable/Classes/Charitable_Data_Processor
8 * @author David Bisset
9 * @copyright Copyright (c) 2023, WP Charitable LLC
10 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
11 * @since 1.5.9
12 * @version 1.6.55
13 */
14
15 // Exit if accessed directly.
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20
21 if ( ! class_exists( 'Charitable_Data_Processor' ) ) :
22
23 /**
24 * Data processor class.
25 *
26 * @since 1.5.9
27 */
28 class Charitable_Data_Processor {
29 /**
30 * The raw input data.
31 *
32 * @since 1.5.9
33 *
34 * @var array
35 */
36 protected $data;
37
38 /**
39 * The map of fields.
40 *
41 * @since 1.5.9
42 *
43 * @var array
44 */
45 protected $fields;
46
47 /**
48 * An array of valid options for specific fields.
49 *
50 * @since 1.6.51
51 *
52 * @var array
53 */
54 protected $options;
55
56 /**
57 * This is the output data. It has the same structure
58 * as the map of fields, but has the sanitized &
59 * normalized values from the input data.
60 *
61 * @since 1.5.9
62 *
63 * @var array
64 */
65 protected $output;
66
67 /**
68 * Whether the data is invalid.
69 *
70 * @since 1.5.9
71 *
72 * @var boolean
73 */
74 protected $invalid;
75
76 /**
77 * Instantiate the formatter with a dataset and array of fields.
78 *
79 * @since 1.5.9
80 *
81 * @param array $data The raw input data.
82 * @param array $fields The map of fields.
83 */
84 public function __construct( $data, $fields, $options = array() ) {
85 $this->data = $data;
86 $this->fields = $fields;
87 $this->options = $options;
88 $this->output = array();
89 $this->invalid = false;
90
91 $this->process_data( $this->fields );
92 }
93
94 /**
95 * Returns whether the data is valid.
96 *
97 * @since 1.5.9
98 *
99 * @return boolean
100 */
101 public function is_valid() {
102 return false == $this->invalid;
103 }
104
105 /**
106 * Return a single field's output data.
107 *
108 * @since 1.5.9
109 *
110 * @param string $key The field key.
111 * @param string|false $data_type Optional. The data type.
112 * @return mixed|null
113 */
114 public function get( $key, $data_type = false ) {
115 if ( $data_type ) {
116 return $this->get_from_data_type( $key, $data_type );
117 }
118
119 return isset( $this->output[ $key ] ) ? $this->output[ $key ] : null;
120 }
121
122 /**
123 * Return a single field's output data, from a data type.
124 *
125 * @since 1.5.9
126 *
127 * @param string $key The field key.
128 * @param string $data_type The data type.
129 * @return mixed|null
130 */
131 public function get_from_data_type( $key, $data_type ) {
132 return isset( $this->output[ $data_type ][ $key ] ) ? $this->output[ $data_type ][ $key ] : null;
133 }
134
135 /**
136 * Returns the full output array.
137 *
138 * @since 1.5.9
139 *
140 * @return array
141 */
142 public function output() {
143 return $this->output;
144 }
145
146 /**
147 * Process an array of fields.
148 *
149 * @since 1.5.9
150 *
151 * @param array $fields Map of fields.
152 * @param false|string $data_type Optional. Data type.
153 * @return void
154 */
155 protected function process_data( $fields, $data_type = false ) {
156 $data = array();
157
158 foreach ( $fields as $key => $type ) {
159 if ( is_array( $type ) ) {
160 $this->process_data( $type, $key );
161 continue;
162 }
163
164 $data[ $key ] = $this->process_field( $key, $type, $data_type );
165 }
166
167 if ( ! empty( $data ) ) {
168 $this->set_output( $data, $data_type );
169 }
170 }
171
172 /**
173 * Add a set of data to the output array.
174 *
175 * @since 1.5.9
176 *
177 * @param array $data The processed data.
178 * @param false|string $data_type Optional. Data type.
179 * @return void
180 */
181 protected function set_output( $data, $data_type = false ) {
182 if ( $data_type ) {
183 $this->output[ $data_type ] = $data;
184 } else {
185 $this->output = $data;
186 }
187 }
188
189 /**
190 * Apply the correct function to a field, based on the type of function
191 * and the key, type and data type of the field.
192 *
193 * @since 1.5.9
194 *
195 * @param array $functions Stack of functions, in order of priority, with $args
196 * passed as the value of the function.
197 * @param mixed $default The default value to return if none of the functions exist.
198 * @return mixed|null
199 */
200 protected function apply_function_to_field( $functions, $default = '' ) {
201 foreach ( $functions as $function => $args ) {
202 if ( method_exists( $this, $function ) ) {
203 return call_user_func_array( array( $this, $function ), $args );
204 }
205 }
206
207 return $default;
208 }
209
210 /**
211 * Process a single field, returning the set value or null.
212 *
213 * @since 1.5.9
214 *
215 * @param string $key The field key.
216 * @param string $type The type of field.
217 * @param string|false $data_type Optional. The data type.
218 * @return mixed|null The set value of the field, or NULL if the
219 * field was not contained in the data.
220 */
221 protected function process_field( $key, $type, $data_type = false ) {
222 $sanitized_key = str_replace( '-', '_', $key );
223 $sanitized_type = str_replace( '-', '_', $type );
224 $sanitized_data_type = str_replace( '-', '_', $data_type );
225 $field_options = isset( $this->options[ $key ] ) ? $this->options[ $key ] : null;
226
227 /* Retrieve the value. */
228 $value = $this->apply_function_to_field(
229 array(
230 'process_' . $sanitized_key => array( $type, $data_type ),
231 'process_' . $sanitized_data_type => array( $key, $type ),
232 'process_' . $sanitized_type => array( $key, $data_type ),
233 'process_generic_field' => array( $key ),
234 )
235 );
236
237 /* Return the value after it is sanitized. */
238 return $this->apply_function_to_field(
239 array(
240 'sanitize_' . $sanitized_key => array( $value, $type, $data_type, $field_options ),
241 'sanitize_' . $sanitized_data_type => array( $value, $key, $type, $field_options ),
242 'sanitize_' . $sanitized_type => array( $value, $key, $data_type, $field_options ),
243 'sanitize_generic_field' => array( $value, $key, $field_options ),
244 ),
245 $value
246 );
247 }
248
249 /**
250 * Process a checkbox field.
251 *
252 * @since 1.5.9
253 *
254 * @param string $key The field key.
255 * @return int|string Returns 0 if the checkbox was not checked, or the
256 * value of the checkbox if checked.
257 */
258 protected function process_checkbox( $key ) {
259 return array_key_exists( $key, $this->data ) ? $this->data[ $key ] : 0;
260 }
261
262 /**
263 * Process a picture field.
264 *
265 * @since 1.5.9
266 *
267 * @param string $key The key of the picture field.
268 * @return int|false
269 */
270 protected function process_picture( $key ) {
271 $value = array_key_exists( $key, $this->data ) ? $this->data[ $key ] : '';
272
273 /**
274 * If Javascript is enabled, we do not expect to have a $_FILES array with the
275 * picture, as the upload was already handled client-side.
276 */
277 if ( ! $this->picture_file_exists( $key ) ) {
278 return $value;
279 }
280
281 $value = $this->upload_attachment( $key );
282
283 if ( is_wp_error( $value ) ) {
284 charitable_get_notices()->add_errors_from_wp_error( $value );
285 $value = '';
286 $this->invalid = true;
287 }
288
289 return $value;
290 }
291
292 /**
293 * Process a generic field. i.e. One that hasn't been processed
294 * by any of the other processors.
295 *
296 * @since 1.5.9
297 *
298 * @param string $key The field key.
299 * @return mixed|null The set value of the field, or NULL if the
300 * field was not contained in the data.
301 */
302 public function process_generic_field( $key ) {
303 return isset( $this->data[ $key ] ) ? $this->data[ $key ] : null;
304 }
305
306 /**
307 * Sanitize a number.
308 *
309 * @since 1.5.9
310 *
311 * @param string|int $value The number to be sanitized.
312 * @return int
313 */
314 public static function sanitize_number( $value ) {
315 return intval( $value );
316 }
317
318 /**
319 * Sanitize a value received from a datepicker.
320 *
321 * When decline_months is on, the JS datepicker sends yy-mm-dd (e.g. 2026-2-1).
322 * We normalize to Y-m-d (e.g. 2026-02-01) so MySQL and PHP never interpret
323 * the string as d-m-y (e.g. January 2 instead of February 1).
324 *
325 * @since 1.5.9
326 * @version 1.8.9.2
327 *
328 * @param string $value The datepicker value.
329 * @return string|int If a date was chosen, returns the date in YYYY-MM-DD format.
330 * Otherwise, returns 0.
331 */
332 public static function sanitize_datepicker( $value ) {
333 if ( empty( $value ) ) {
334 return 0;
335 }
336
337 if ( ! charitable()->registry()->get( 'i18n' )->decline_months() ) {
338 $value = charitable_sanitize_date( $value, 'Y-m-d' );
339 if ( false === $value ) {
340 $value = 0;
341 }
342 } else {
343 // Normalize to canonical Y-m-d so MySQL/PHP never interpret as d-m-y.
344 $value = trim( $value );
345 $dt = DateTime::createFromFormat( 'Y-m-d', $value );
346 if ( ! $dt ) {
347 $dt = DateTime::createFromFormat( 'Y-n-j', $value );
348 }
349 if ( $dt ) {
350 $value = $dt->format( 'Y-m-d' );
351 } else {
352 $value = 0;
353 }
354 }
355
356 return $value;
357 }
358
359 /**
360 * Sanitize editor fields.
361 *
362 * @since 1.6.51
363 *
364 * @param mixed $value The submitted value.
365 * @return string
366 */
367 public static function sanitize_editor( $value ) {
368 return wp_kses_post( $value );
369 }
370
371 /**
372 * Sanitize textarea.
373 *
374 * @since 1.6.51
375 *
376 * @param mixed $value The submitted value.
377 * @return string
378 */
379 public static function sanitize_textarea( $value ) {
380 return sanitize_textarea_field( $value );
381 }
382
383 /**
384 * Sanitize submitted email.
385 *
386 * @since 1.6.51
387 *
388 * @param mixed $value The submitted value.
389 * @return string
390 */
391 public static function sanitize_email( $value ) {
392 return sanitize_email( $value );
393 }
394
395 /**
396 * Escape a submitted url.
397 *
398 * @since 1.6.51
399 *
400 * @param mixed $value The submitted value.
401 * @return string
402 */
403 public static function sanitize_url( $value ) {
404 return esc_url_raw( $value );
405 }
406
407 /**
408 * Fallback sanitization, using sanitize_text_field.
409 *
410 * @since 1.6.51
411 *
412 * @param mixed $value The submitted value.
413 * @return string
414 */
415 public static function sanitize_text( $value ) {
416 return sanitize_text_field( $value );
417 }
418
419 /**
420 * Sanitize multi-checkbox fields.
421 *
422 * @since 1.6.51
423 *
424 * @param mixed $value The submitted value.
425 * @param string $key The field key.
426 * @param string $data_type The type of data.
427 * @param null|array $options The options for the field.
428 * @return string|array
429 */
430 public static function sanitize_multi_checkbox( $value, $key, $data_type, $options = null ) {
431 /* Multi-checkbox should return an empty string or an array. */
432 if ( ! is_array( $value ) ) {
433 return '';
434 }
435
436 if ( is_null( $options ) ) {
437 return $value;
438 }
439
440 /* Check against a specific set of options that are valid for this field. */
441 foreach ( $value as $i => $option ) {
442 /* If the selected option is not valid, remove it. */
443 if ( ! in_array( $option, $options ) ) {
444 unset( $value[ $i ] );
445 }
446 }
447
448 return $value;
449 }
450
451 /**
452 * Fallback sanitization.
453 *
454 * @since 1.6.51
455 *
456 * @param mixed $value The submitted value.
457 * @param string $key The field key.
458 * @param null|array $options The options for the field.
459 * @return string
460 */
461 public static function sanitize_generic_field( $value, $key, $options = null ) {
462 /* Check against a specific set of options that are valid for this field. */
463 if ( is_array( $options ) ) {
464 $value = in_array( $value, $options ) ? $value : '';
465 }
466
467 return $value;
468 }
469
470 /**
471 * Returns true if a file was found for the picture in the $_FILES array.
472 *
473 * @since 1.5.9
474 *
475 * @param string $key The picture file key.
476 * @return boolean
477 */
478 protected function picture_file_exists( $key ) {
479 return isset( $_FILES ) && isset( $_FILES[ $key ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
480 }
481
482 /**
483 * Uploads a file and attaches it to the given post.
484 *
485 * @since 1.5.9
486 *
487 * @param string $file_key Key of the file input.
488 * @param int $post_id Post ID.
489 * @return int|WP_Error ID of the attachment or a WP_Error object on failure.
490 */
491 public function upload_attachment( $file_key, $post_id = 0 ) {
492 require_once( ABSPATH . 'wp-admin/includes/image.php' );
493 require_once( ABSPATH . 'wp-admin/includes/file.php' );
494 require_once( ABSPATH . 'wp-admin/includes/media.php' );
495
496 $overrides = $this->get_file_overrides( $file_key, $overrides );
497
498 return media_handle_upload( $file_key, $post_id, array(), $overrides );
499 }
500
501 /**
502 * Return overrides array for use with upload_attachment() methods.
503 *
504 * @since 1.0.0
505 *
506 * @param string $file_key Reference to a single element of `$_FILES`. Call the
507 * function once for each uploaded file.
508 * @param array $overrides Optional. An associative array of names=>values to
509 * override default variables. Default false.
510 * @return array
511 */
512 protected function get_file_overrides( $file_key, $overrides = array() ) {
513 $allowed_mimes = array(
514 'jpg|jpeg|jpe' => 'image/jpeg',
515 'gif' => 'image/gif',
516 'png' => 'image/png',
517 'bmp' => 'image/bmp',
518 'tif|tiff' => 'image/tiff',
519 'ico' => 'image/x-icon',
520 );
521
522 $defaults = array(
523 'test_form' => false,
524 'mimes' => apply_filters( 'charitable_file_' . $file_key . '_allowed_mimes', $allowed_mimes ),
525 );
526
527 $overrides = wp_parse_args( $overrides, $defaults );
528
529 return $overrides;
530 }
531 }
532
533 endif;
534