PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 0.0.1
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v0.0.1
1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
suredonation / inc / helper.php

helper.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management 0.0.1, at inc/helper.php

610 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Helper Class - Utility functions for SureDonation
4 *
5 * @package SureDonation
6 */
7
8 namespace SureDonation\Inc;
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * Helper class.
17 * Provides utility functions for the plugin.
18 *
19 * @since 0.0.1
20 */
21 class Helper {
22 /**
23 * Option name for all SureDonation settings.
24 *
25 * @since 0.0.1
26 */
27 public const OPTION_NAME = 'suredonation_options';
28
29 /**
30 * Campaign meta key name.
31 *
32 * @since 0.0.1
33 */
34 public const SUREDONATION_CAMPAIGN_META_KEY = '_suredonation_campaign_meta';
35
36 /**
37 * Default campaign meta values.
38 *
39 * @since 0.0.1
40 * @var array<string, mixed>
41 */
42 private static $campaign_meta_defaults = [
43 'goal_type' => 'raised_amount',
44 'goal_amount' => 0,
45 'campaign_status' => 'active',
46 'email_settings' => [],
47 'allow_fees_coverage' => false,
48 'require_terms' => false,
49 'terms_text' => '',
50 'thank_you_message' => '',
51 ];
52
53 /**
54 * Get a value from the suredonation_options array.
55 *
56 * @param string $key The key to retrieve.
57 * @param mixed $default_value Default value if key doesn't exist.
58 * @return mixed
59 * @since 0.0.1
60 */
61 public static function get_suredonation_option( $key, $default_value = null ) {
62 $options = get_option( self::OPTION_NAME, [] );
63
64 if ( ! is_array( $options ) ) {
65 $options = [];
66 }
67
68 return array_key_exists( $key, $options ) ? $options[ $key ] : $default_value;
69 }
70
71 /**
72 * Update a value in the suredonation_options array.
73 *
74 * @param string $key The key to update.
75 * @param mixed $value The value to set.
76 * @return bool True on success, false on failure.
77 * @since 0.0.1
78 */
79 public static function update_suredonation_option( $key, $value ) {
80 $options = get_option( self::OPTION_NAME, [] );
81
82 if ( ! is_array( $options ) ) {
83 $options = [];
84 }
85
86 $options[ $key ] = $value;
87
88 return update_option( self::OPTION_NAME, $options );
89 }
90
91 /**
92 * Get all campaign meta as an array.
93 *
94 * @param int $campaign_id Campaign post ID.
95 * @return array<string, mixed> Campaign meta values.
96 * @since 0.0.1
97 */
98 public static function get_campaign_meta( $campaign_id ) {
99 $raw = get_post_meta( $campaign_id, self::SUREDONATION_CAMPAIGN_META_KEY, true );
100
101 $meta = ! empty( $raw ) && is_string( $raw ) ? json_decode( $raw, true ) : [];
102
103 if ( ! is_array( $meta ) ) {
104 $meta = [];
105 }
106
107 return array_merge( self::$campaign_meta_defaults, $meta );
108 }
109
110 /**
111 * Get a single campaign meta value.
112 *
113 * @param int $campaign_id Campaign post ID.
114 * @param string $key Meta key within the campaign meta array.
115 * @param mixed $default_value Default value if not set.
116 * @return mixed
117 * @since 0.0.1
118 */
119 public static function get_campaign_meta_value( $campaign_id, $key, $default_value = null ) {
120 $meta = self::get_campaign_meta( $campaign_id );
121
122 return $meta[ $key ] ?? $default_value;
123 }
124
125 /**
126 * Update campaign meta. Merges provided values with existing meta.
127 *
128 * @param int $campaign_id Campaign post ID.
129 * @param array<string, mixed> $values Key-value pairs to update.
130 * @return bool|int Meta ID on success, false on failure.
131 * @since 0.0.1
132 */
133 public static function update_campaign_meta( $campaign_id, $values ) {
134 $meta = self::get_campaign_meta( $campaign_id );
135 $meta = array_merge( $meta, $values );
136
137 return update_post_meta( $campaign_id, self::SUREDONATION_CAMPAIGN_META_KEY, wp_json_encode( $meta ) );
138 }
139
140 /**
141 * Checks if current value is string or else returns default value
142 *
143 * @param mixed $data data which need to be checked if is string.
144 * @return string
145 * @since 0.0.1
146 */
147 public static function get_string_value( $data ) {
148 if ( is_scalar( $data ) ) {
149 return (string) $data;
150 }
151 if ( is_object( $data ) && method_exists( $data, '__toString' ) ) {
152 return $data->__toString();
153 }
154 if ( is_null( $data ) ) {
155 return '';
156 }
157 return '';
158 }
159
160 /**
161 * Checks if current value is number or else returns default value
162 *
163 * @param mixed $value data which need to be checked if is string.
164 * @param int $base value can be set is $data is not a string, defaults to empty string.
165 * @return int
166 * @since 0.0.1
167 */
168 public static function get_integer_value( $value, $base = 10 ) {
169 if ( is_numeric( $value ) ) {
170 return (int) $value;
171 }
172 if ( is_string( $value ) ) {
173 $trimmed_value = trim( $value );
174 return intval( $trimmed_value, $base );
175 }
176 return 0;
177 }
178
179 /**
180 * Safely converts a mixed value to float
181 *
182 * @param mixed $value The value to convert.
183 * @param float $default_value Default value if conversion fails.
184 * @return float
185 * @since 0.0.1
186 */
187 public static function get_float_value( $value, $default_value = 0.0 ) {
188 if ( is_numeric( $value ) ) {
189 return (float) $value;
190 }
191 return $default_value;
192 }
193
194 /**
195 * Safely get array value with type checking
196 *
197 * @param mixed $value The value to check.
198 * @param array<string, mixed> $default_value Default value if not an array.
199 * @return array<string, mixed>
200 * @since 0.0.1
201 */
202 public static function get_array_value( $value, $default_value = [] ) {
203 return is_array( $value ) ? $value : $default_value;
204 }
205
206 /**
207 * Check if current user has required capability.
208 *
209 * @param string $capability Capability to check (default: 'manage_options').
210 * @param array<mixed> $args Additional arguments for capability check.
211 * @return bool True if user has capability.
212 * @since 0.0.1
213 */
214 public static function current_user_can( $capability = '', $args = [] ) {
215 if ( ! function_exists( 'current_user_can' ) ) {
216 return false;
217 }
218
219 if ( ! is_string( $capability ) || empty( $capability ) ) {
220 $capability = 'manage_options';
221 }
222
223 return ! empty( $args )
224 ? current_user_can( $capability, ...$args )
225 : current_user_can( $capability );
226 }
227
228 /**
229 * Join an array of strings into a single string, filtering out empty values.
230 *
231 * @param array<string> $strings Array of strings to join.
232 * @param string $glue Separator to use (default: ' ').
233 * @return string Joined string.
234 * @since 0.0.1
235 */
236 public static function join_strings( $strings, $glue = ' ' ) {
237 if ( ! is_array( $strings ) ) {
238 return '';
239 }
240
241 $filtered = array_filter(
242 $strings,
243 static function ( $item ) {
244 return is_string( $item ) && '' !== trim( $item );
245 }
246 );
247
248 return implode( $glue, array_map( 'trim', $filtered ) );
249 }
250
251 /**
252 * Process blocks to generate unique slugs for SureDonation blocks.
253 *
254 * Recursively processes all blocks and generates slugs for those that
255 * don't have one set. Ensures all slugs are unique within the form.
256 *
257 * @param array<mixed> $blocks The blocks to process.
258 * @param array<string> $slugs Array of existing slugs (keyed by block_id).
259 * @param bool $updated Whether any blocks were updated.
260 * @param string $prefix Optional prefix for nested blocks.
261 * @return array{0: array<mixed>, 1: array<string>, 2: bool} Processed blocks, slugs, and updated flag.
262 * @since 0.0.1
263 */
264 public static function process_blocks( $blocks, $slugs = [], $updated = false, $prefix = '' ) {
265 if ( ! is_array( $blocks ) ) {
266 return [ [], $slugs, $updated ];
267 }
268 foreach ( $blocks as $index => $block ) {
269 if ( ! is_array( $block ) ) {
270 continue;
271 }
272 // Skip non-SureDonation blocks.
273 if ( ! isset( $block['blockName'] ) || ! is_string( $block['blockName'] ) || strpos( $block['blockName'], 'sd/' ) !== 0 ) {
274 // Process inner blocks if any.
275 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
276 [ $blocks[ $index ]['innerBlocks'], $slugs, $updated ] = self::process_blocks( $block['innerBlocks'], $slugs, $updated, $prefix );
277 }
278 continue;
279 }
280
281 // Skip if no attrs or slug is already set and block_id is in slugs array.
282 if (
283 ! isset( $block['attrs'] ) ||
284 ! is_array( $block['attrs'] ) ||
285 (
286 ! empty( $block['attrs']['slug'] ) &&
287 isset( $block['attrs']['block_id'] ) &&
288 isset( $slugs[ $block['attrs']['block_id'] ] )
289 )
290 ) {
291 // Process inner blocks if any.
292 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
293 [ $blocks[ $index ]['innerBlocks'], $slugs, $updated ] = self::process_blocks( $block['innerBlocks'], $slugs, $updated, $prefix );
294 }
295 continue;
296 }
297
298 // Generate slug if empty.
299 if ( empty( $block['attrs']['slug'] ) ) {
300 $blocks[ $index ]['attrs']['slug'] = self::generate_unique_block_slug( $block, $slugs, $prefix );
301 $updated = true;
302 }
303
304 // Track the slug if block_id is set.
305 if ( isset( $block['attrs']['block_id'] ) ) {
306 $slugs[ $block['attrs']['block_id'] ] = $blocks[ $index ]['attrs']['slug'];
307 }
308
309 // Process inner blocks recursively.
310 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
311 [ $blocks[ $index ]['innerBlocks'], $slugs, $updated ] = self::process_blocks(
312 $block['innerBlocks'],
313 $slugs,
314 $updated,
315 $blocks[ $index ]['attrs']['slug']
316 );
317 }
318 }
319
320 return [ $blocks, $slugs, $updated ];
321 }
322
323 /**
324 * Generates a unique slug based on the provided block and existing slugs.
325 *
326 * @param array<mixed> $block The block data.
327 * @param array<string> $slugs The array of existing slugs.
328 * @param string $prefix Optional prefix for nested blocks.
329 * @return string The generated unique block slug.
330 * @since 0.0.1
331 */
332 public static function generate_unique_block_slug( $block, $slugs, $prefix = '' ) {
333 $slug = is_string( $block['blockName'] ?? '' ) ? str_replace( 'sd/', '', $block['blockName'] ) : '';
334
335 // Use label if available.
336 if ( ! empty( $block['attrs']['label'] ) && is_string( $block['attrs']['label'] ) ) {
337 $slug = sanitize_title( $block['attrs']['label'] );
338 }
339
340 // Add prefix for nested blocks.
341 if ( ! empty( $prefix ) ) {
342 $slug = $prefix . '-' . $slug;
343 }
344
345 return self::generate_unique_slug( $slug, $slugs );
346 }
347
348 /**
349 * Ensures that the slug is unique.
350 *
351 * If the slug is already taken, it appends a number to make it unique.
352 *
353 * @param string $slug The slug to make unique.
354 * @param array<string> $slugs Array of existing slugs.
355 * @return string The unique slug.
356 * @since 0.0.1
357 */
358 public static function generate_unique_slug( $slug, $slugs ) {
359 $slug = sanitize_title( $slug );
360
361 // Check if slug exists in the array values.
362 if ( ! in_array( $slug, $slugs, true ) ) {
363 return $slug;
364 }
365
366 // Append a number to make it unique.
367 $index = 1;
368 while ( in_array( $slug . '-' . $index, $slugs, true ) ) {
369 ++$index;
370 }
371
372 return $slug . '-' . $index;
373 }
374
375 /**
376 * Get client IP address for logging purposes.
377 *
378 * Checks forwarded headers first (for proxied/load-balanced environments)
379 * then falls back to REMOTE_ADDR. This is suitable for informational
380 * logging only — do NOT use for security-critical IP validation.
381 *
382 * @return string Client IP address.
383 * @since 0.0.1
384 */
385 public static function get_client_ip() {
386 $ip_headers = [
387 'HTTP_CLIENT_IP',
388 'HTTP_X_FORWARDED_FOR',
389 'REMOTE_ADDR',
390 ];
391
392 foreach ( $ip_headers as $header ) {
393 if ( ! empty( $_SERVER[ $header ] ) ) {
394 $ips = explode( ',', sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) );
395 $ip = trim( $ips[0] );
396
397 if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) {
398 return $ip;
399 }
400 }
401 }
402
403 return '';
404 }
405
406 /**
407 * Get allowed HTML tags for form markup.
408 *
409 * The wp_kses_post() doesn't allow form elements, so we need a custom allowed tags array.
410 * This is safe because the markup is generated internally by trusted code that already
411 * escapes user input with esc_attr(), esc_html(), etc.
412 *
413 * @return array<string, array<string, bool>> Allowed HTML tags and attributes.
414 * @since 0.0.1
415 */
416 public static function get_allowed_form_html() {
417 // Note: data-* wildcard doesn't work in wp_kses, so we list each data attribute explicitly.
418 $common_data_attrs = [
419 'data-block-id' => true,
420 'data-form-id' => true,
421 'data-gateway' => true,
422 'data-stripe-key' => true,
423 'data-currency' => true,
424 'data-payment-mode' => true,
425 'data-amount-type' => true,
426 'data-fixed-amount' => true,
427 'data-payment-type' => true,
428 'data-customer-name-field' => true,
429 'data-customer-email-field' => true,
430 'data-nonce' => true,
431 'data-variable-amount-field' => true,
432 'data-minimum-amount' => true,
433 'data-subscription-plan-name' => true,
434 'data-subscription-interval' => true,
435 'data-subscription-billing-cycles' => true,
436 'data-currency-symbol' => true,
437 'data-message-format' => true,
438 'data-slug' => true,
439 'data-required' => true,
440 'data-fee-percentage' => true,
441 'data-fee-fixed' => true,
442 'data-invalid-email-msg' => true,
443 ];
444
445 return [
446 'div' => array_merge(
447 [
448 'id' => true,
449 'class' => true,
450 'style' => true,
451 'role' => true,
452 'aria-live' => true,
453 'aria-atomic' => true,
454 'aria-labelledby' => true,
455 ],
456 $common_data_attrs
457 ),
458 'form' => array_merge(
459 [
460 'id' => true,
461 'class' => true,
462 'method' => true,
463 'action' => true,
464 ],
465 $common_data_attrs
466 ),
467 'fieldset' => [
468 'id' => true,
469 'class' => true,
470 ],
471 'legend' => [
472 'id' => true,
473 'class' => true,
474 ],
475 'label' => [
476 'id' => true,
477 'class' => true,
478 'for' => true,
479 ],
480 'input' => array_merge(
481 [
482 'id' => true,
483 'class' => true,
484 'type' => true,
485 'name' => true,
486 'value' => true,
487 'placeholder' => true,
488 'min' => true,
489 'max' => true,
490 'step' => true,
491 'maxlength' => true,
492 'checked' => true,
493 'disabled' => true,
494 'readonly' => true,
495 'required' => true,
496 'aria-describedby' => true,
497 'aria-required' => true,
498 'aria-hidden' => true,
499 ],
500 $common_data_attrs
501 ),
502 'button' => array_merge(
503 [
504 'id' => true,
505 'class' => true,
506 'type' => true,
507 'disabled' => true,
508 ],
509 $common_data_attrs
510 ),
511 'select' => array_merge(
512 [
513 'id' => true,
514 'class' => true,
515 'name' => true,
516 'disabled' => true,
517 'required' => true,
518 'aria-describedby' => true,
519 'aria-required' => true,
520 ],
521 $common_data_attrs
522 ),
523 'option' => [
524 'value' => true,
525 'selected' => true,
526 'disabled' => true,
527 ],
528 'textarea' => array_merge(
529 [
530 'id' => true,
531 'class' => true,
532 'name' => true,
533 'rows' => true,
534 'cols' => true,
535 'placeholder' => true,
536 'maxlength' => true,
537 'disabled' => true,
538 'readonly' => true,
539 'required' => true,
540 'aria-describedby' => true,
541 'aria-required' => true,
542 ],
543 $common_data_attrs
544 ),
545 'span' => [
546 'id' => true,
547 'class' => true,
548 'style' => true,
549 'aria-hidden' => true,
550 ],
551 'p' => [
552 'id' => true,
553 'class' => true,
554 'style' => true,
555 'role' => true,
556 ],
557 'a' => [
558 'id' => true,
559 'class' => true,
560 'href' => true,
561 'target' => true,
562 'rel' => true,
563 'style' => true,
564 ],
565 'strong' => [
566 'class' => true,
567 ],
568 'em' => [
569 'class' => true,
570 ],
571 'br' => [],
572 'svg' => [
573 'class' => true,
574 'width' => true,
575 'height' => true,
576 'viewbox' => true,
577 'fill' => true,
578 'xmlns' => true,
579 'aria-hidden' => true,
580 ],
581 'circle' => [
582 'cx' => true,
583 'cy' => true,
584 'r' => true,
585 'stroke' => true,
586 'stroke-width' => true,
587 'fill' => true,
588 ],
589 'rect' => [
590 'x' => true,
591 'y' => true,
592 'width' => true,
593 'height' => true,
594 'rx' => true,
595 'stroke' => true,
596 'stroke-width' => true,
597 ],
598 'path' => [
599 'class' => true,
600 'd' => true,
601 'stroke' => true,
602 'stroke-width' => true,
603 'stroke-linecap' => true,
604 'stroke-linejoin' => true,
605 'fill' => true,
606 ],
607 ];
608 }
609 }
610