PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.7.0.1
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.7.0.1
1.8.13.1 1.8.13 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 All 212 releases
charitable / includes / privacy / class-charitable-privacy.php

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

674 lines 18.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * A class providing privacy tools for Charitable.
4 *
5 * @package Charitable/Classes/Charitable_Privacy
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.6.0
10 * @version 1.6.0
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Privacy' ) ) :
19
20 /**
21 * Charitable_Privacy
22 *
23 * @since 1.6.0
24 */
25 class Charitable_Privacy {
26
27 /**
28 * User donation fields.
29 *
30 * @since 1.6.0
31 *
32 * @var Charitable_Donation_Field[]
33 */
34 protected $user_donation_fields;
35
36 /**
37 * Fields that should be retained during the Data Retention Period.
38 *
39 * @since 1.6.0
40 *
41 * @var Charitable_Donation_Field[]
42 */
43 protected $data_retention_fields;
44
45 /**
46 * Set up class instance.
47 *
48 * @since 1.6.0
49 */
50 public function __construct() {
51 if ( ! function_exists( 'wp_privacy_anonymize_data' ) ) {
52 return;
53 }
54
55 /* Register our data exporter. */
56 add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_exporter' ) );
57
58 /* Register our data eraser. */
59 add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_eraser' ) );
60
61 /* Add our privacy policy content. */
62 add_action( 'admin_init', array( $this, 'add_privacy_policy_content' ) );
63 }
64
65 /**
66 * Return the data to be retained for donations within the Data Retention Period.
67 *
68 * @since 1.6.0
69 *
70 * @return array
71 */
72 public function get_data_retention_fields() {
73 if ( ! isset( $this->data_retention_fields ) ) {
74 $this->data_retention_fields = charitable_get_option( 'data_retention_fields', array_keys( $this->get_user_donation_fields() ) );
75 }
76
77 return $this->data_retention_fields;
78 }
79
80 /**
81 * Return the Data Retention Period.
82 *
83 * @since 1.6.0
84 *
85 * @return int|string
86 */
87 public function get_data_retention_period() {
88 return charitable_get_option( 'minimum_data_retention_period', 2 );
89 }
90
91 /**
92 * Get the user donation fields.
93 *
94 * @since 1.6.0
95 *
96 * @return Charitable_Donation_Field[]
97 */
98 public function get_user_donation_fields() {
99 if ( ! isset( $this->user_donation_fields ) ) {
100 $this->user_donation_fields = charitable()->donation_fields()->get_data_type_fields( 'user' );
101 }
102
103 return $this->user_donation_fields;
104 }
105
106 /**
107 * Return all erasable fields for a particular donation.
108 *
109 * @since 1.6.0
110 *
111 * @param int $donation_id The donation ID.
112 * @return Charitable_Donation_Field[]
113 */
114 public function get_erasable_fields_for_donation( $donation_id ) {
115 if ( ! $this->is_donation_in_data_retention_period( $donation_id ) ) {
116 return $this->get_user_donation_fields();
117 }
118
119 return $this->get_erasable_fields_in_data_retention_period();
120 }
121
122 /**
123 * Checks whether a donation is in the Data Retention Period.
124 *
125 * @since 1.6.0
126 *
127 * @param int $donation_id The donation ID.
128 * @return boolean
129 */
130 public function is_donation_in_data_retention_period( $donation_id ) {
131 $period = $this->get_data_retention_period();
132
133 if ( '0' == (string) $period ) {
134 return false;
135 }
136
137 if ( 'endless' == $period ) {
138 return true;
139 }
140
141 $diff = (int) abs( time() - strtotime( get_post_field( 'post_date_gmt', $donation_id, 'raw' ) ) );
142
143 return floor( $diff / YEAR_IN_SECONDS ) < $period;
144 }
145
146 /**
147 * Register the data exporter.
148 *
149 * @since 1.6.0
150 *
151 * @param array $exporters The list of exporters.
152 * @return array
153 */
154 public function register_exporter( $exporters ) {
155 return array_merge( $exporters, array(
156 array(
157 'exporter_friendly_name' => __( 'Charitable Donor Data', 'charitable' ),
158 'callback' => array( $this, 'export_user_data' ),
159 ),
160 ) );
161 }
162
163 /**
164 * Register the data eraser.
165 *
166 * @since 1.6.0
167 *
168 * @param array $erasers The list of registered data erasers.
169 * @return array
170 */
171 public function register_eraser( $erasers ) {
172 return array_merge( $erasers, array(
173 array(
174 'eraser_friendly_name' => __( 'Charitable Donor Data Eraser', 'charitable' ),
175 'callback' => array( $this, 'erase_user_data' ),
176 ),
177 ) );
178 }
179
180 /**
181 * Add privacy policy content for Charitable.
182 *
183 * @since 1.6.0
184 *
185 * @return void
186 */
187 public function add_privacy_policy_content() {
188 if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
189 return;
190 }
191
192 /**
193 * Filter the Charitable privacy policy content.
194 *
195 * @since 1.6.0
196 *
197 * @param string $content The privacy policy content.
198 */
199 $content = wp_kses_post( apply_filters( 'charitable_privacy_policy_content', wpautop( __( '
200 We collect information about you during the donation process on our website. This information may include, but is not limited to, your name, address, phone number, credit card/payment details and any other details that might be requested from you for the purpose of processing your donations.
201
202 Handling this data allows us to:
203
204 - Send you important account/donation/service information.
205 - Respond to your queries, refund requests or complaints.
206 - Process payments and prevent fraudulent transactions. We do this on the basis of our legitimate organizational interests.
207 - Set up and administer your account, provide technical and donor support, and to verify your identity.
208
209 Additionally, we may also collect the following information:
210
211 - Your donor comments if you choose to leave them on our website.
212 - Cookies which are essential to keep track of your donation history whilst your session is active. This allows you to access your donation receipt without being a registered, logged in user.
213 - Account email/password to allow access to your account, if you have one.
214 - If you choose to create an account with us, your name, address, email and phone number, which will be used to populate the donation form for future donations.
215 ', 'charitable' ) ) ) );
216
217 wp_add_privacy_policy_content( 'Charitable', $content );
218 }
219
220 /**
221 * Return a user's data.
222 *
223 * We export the following pieces of data:
224 *
225 * 1. Registered donor meta. (*_usermeta)
226 * 2. Donor profile records. (*_charitable_donors table)
227 * 3. Donation donor meta. (*_postmeta)
228 *
229 * @since 1.6.0
230 *
231 * @param string $email The user's email address.
232 * @param int $page The page of data to retrieve.
233 * @return array
234 */
235 public function export_user_data( $email, $page = 1 ) {
236 $export_items = array();
237
238 if ( 1 === $page ) {
239 /* 1. Get registered donor meta. */
240 $user = get_user_by( 'email', $email );
241
242 if ( $user instanceof WP_User ) {
243 $export_items[] = $this->get_registered_donor_data( $user );
244 }
245 }
246
247 /* 2. Get donor profile. */
248 $profiles = charitable_get_table( 'donors' )->get_personal_data( $email );
249
250 if ( is_array( $profiles ) && 1 === $page ) {
251 $export_items = array_merge(
252 $export_items,
253 array_map( array( $this, 'get_donor_profile_data' ), $profiles )
254 );
255 }
256
257 /* If there are no donor profiles, there are no donations; return whatever we have. */
258 if ( empty( $profiles ) ) {
259 return array(
260 'data' => $export_items,
261 'done' => true,
262 );
263 }
264
265 /* 3. Donation donor meta */
266 $user_donation_fields = $this->get_user_donation_fields();
267
268 if ( ! empty( $user_donation_fields ) ) {
269 $donor_id = wp_list_pluck( $profiles, 'donor_id' );
270 $donations = charitable_get_table( 'campaign_donations' )->get_distinct_ids( 'donation_id', $donor_id, 'donor_id' );
271 $export_items = array_merge(
272 $export_items,
273 array_map( array( $this, 'get_personal_donation_meta_data' ), $donations )
274 );
275 }
276
277 return array(
278 'data' => $export_items,
279 'done' => true,
280 );
281 }
282
283 /**
284 * Remove a user's personal data.
285 *
286 * @since 1.6.0
287 *
288 * @param string $email The user's email address.
289 * @param int $page The page of data to retrieve.
290 * @return array
291 */
292 public function erase_user_data( $email, $page = 1 ) {
293 $this->retained = false;
294 $this->removed = false;
295
296 /* 1. Remove registered donor meta. */
297 $user = get_user_by( 'email', $email );
298
299 if ( $user instanceof WP_User ) {
300 $this->erase_registered_donor_personal_data( $user );
301 }
302
303 /* 2. Remove donor profile data. */
304 $profiles = charitable_get_table( 'donors' )->get_personal_data( $email );
305
306 if ( is_array( $profiles ) ) {
307 charitable_get_table( 'donors' )->erase_donor_data( wp_list_pluck( $profiles, 'donor_id' ) );
308
309 $this->removed = true;
310 }
311
312 /* If there are no donor profiles, there are no donations. */
313 if ( empty( $profiles ) ) {
314 return array(
315 'items_removed' => $this->removed,
316 'items_retained' => $this->retained,
317 'messages' => array(),
318 'done' => true,
319 );
320 }
321
322 /* 3. Donation donor meta */
323 $user_donation_fields = $this->get_user_donation_fields();
324
325 if ( ! empty( $user_donation_fields ) ) {
326 $donations = $this->get_donations_from_profiles( $profiles );
327
328 array_walk( $donations, array( $this, 'erase_donation_personal_data' ) );
329 }
330
331 return array(
332 'items_removed' => $this->removed,
333 'items_retained' => $this->retained,
334 'messages' => array(),
335 'done' => true,
336 );
337 }
338
339 /**
340 * Return a registered donor's data.
341 *
342 * @since 1.6.0
343 *
344 * @param WP_User $user An instance of `WP_User`.
345 * @return array
346 */
347 public function get_registered_donor_data( $user ) {
348 $data = array();
349 $form = new Charitable_Profile_Form;
350 $methods = array(
351 'get_user_fields',
352 'get_address_fields',
353 'get_social_fields',
354 );
355 $key_map = charitable_get_user_mapped_keys();
356
357 foreach ( $methods as $method ) {
358 $fields = call_user_func( array( $form, $method ) );
359
360 if ( ! is_array( $fields ) ) {
361 continue;
362 }
363
364 foreach ( $fields as $key => $field ) {
365 $key = array_key_exists( $key, $key_map ) ? $key_map[ $key ] : $key;
366
367 if ( ! $user->has_prop( $key ) ) {
368 continue;
369 }
370
371 $data[] = array(
372 'name' => array_key_exists( 'label', $field ) ? $field['label'] : ucfirst( str_replace( '_', ' ', $key ) ),
373 'value' => $user->{$key},
374 );
375 }
376 }
377
378 /**
379 * Filter the personal donor meta data for a registered donor.
380 *
381 * @since 1.6.0
382 *
383 * @param array $data Set of personal donor data.
384 * @param WP_User $user An instance of `WP_User`.
385 */
386 $data = apply_filters( 'charitable_privacy_export_personal_registered_donor_data', $data, $user );
387
388 return array(
389 'item_id' => 'user',
390 'group_id' => 'charitable_users',
391 'group_label' => __( 'Donor Meta', 'charitable' ),
392 'data' => $data,
393 );
394 }
395
396 /**
397 * Returns donor profile data for export.
398 *
399 * @since 1.6.0
400 *
401 * @param object $profile The profile record.
402 * @return array
403 */
404 protected function get_donor_profile_data( $profile ) {
405 /**
406 * Filter the personal donor record data.
407 *
408 * @since 1.6.0
409 *
410 * @param array $data Set of personal donor data.
411 * @param object $profile The profile record.
412 */
413 $data = apply_filters( 'charitable_privacy_export_personal_donor_profile_data', array(
414 array(
415 'name' => __( 'First Name', 'charitable' ),
416 'value' => $profile->first_name,
417 ),
418 array(
419 'name' => __( 'Last Name', 'charitable' ),
420 'value' => $profile->last_name,
421 ),
422 array(
423 'name' => __( 'Email', 'charitable' ),
424 'value' => $profile->email,
425 ),
426 ) );
427
428 return array(
429 'item_id' => 'donor-' . $profile->donor_id,
430 'group_id' => 'charitable_donors',
431 'group_label' => __( 'Donor Profiles', 'charitable' ),
432 'data' => $data,
433 );
434 }
435
436 /**
437 * Return the donor meta stored with a donor's donation records.
438 *
439 * @since 1.6.0
440 *
441 * @param int $donation_id A donation ID.
442 * @return array
443 */
444 protected function get_personal_donation_meta_data( $donation_id ) {
445 $meta = get_post_meta( $donation_id, 'donor', true );
446
447 if ( ! is_array( $meta ) ) {
448 return array();
449 }
450
451 $data = array();
452
453 foreach ( $this->get_user_donation_fields() as $field_id => $field ) {
454 if ( ! array_key_exists( $field_id, $meta ) ) {
455 continue;
456 }
457
458 $data[] = array(
459 'name' => $field->label,
460 'value' => $meta[ $field_id ],
461 );
462 }
463
464 /**
465 * Filter the personal donation meta data for a particular donation.
466 *
467 * @since 1.6.0
468 *
469 * @param array $data Set of personal donation meta data.
470 * @param array $data The donor meta array.
471 * @param int $donation_id The donation ID.
472 */
473 $data = apply_filters( 'charitable_privacy_export_personal_donation_meta_data', $data, $meta, $donation_id );
474
475 return array(
476 'item_id' => 'donation-' . $donation_id,
477 'group_id' => 'charitable_donations',
478 'group_label' => __( 'Donation Meta', 'charitable' ),
479 'data' => $data,
480 );
481 }
482
483 /**
484 * Remove personal data for a registered donor.
485 *
486 * @since 1.6.0
487 *
488 * @param WP_User $user The instance of `WP_User`.
489 * @return void
490 */
491 protected function erase_registered_donor_personal_data( WP_User $user ) {
492 $form = new Charitable_Profile_Form;
493 $methods = array(
494 'get_user_fields',
495 'get_address_fields',
496 'get_social_fields',
497 );
498 $key_map = charitable_get_user_mapped_keys();
499
500 foreach ( $methods as $method ) {
501 $fields = call_user_func( array( $form, $method ) );
502
503 if ( ! is_array( $fields ) ) {
504 continue;
505 }
506
507 foreach ( $fields as $key => $field ) {
508 $key = array_key_exists( $key, $key_map ) ? $key_map[ $key ] : $key;
509
510 if ( ! $user->has_prop( $key ) ) {
511 continue;
512 }
513
514 /* Get the data type we will use for erasing this particular data field. */
515 $data_type = $this->get_field_data_type( (object) $field );
516
517 /**
518 * Filter the placeholder data that will replace the personal data.
519 *
520 * @since 1.6.0
521 *
522 * @param string $data The anonymous data.
523 * @param mixed $value The current value of the field.
524 * @param string $key The key of the field.
525 * @param array $field The field definition.
526 */
527 $data = apply_filters( 'charitable_privacy_erasure_registered_user_data_prop_value', wp_privacy_anonymize_data( $data_type, $user->{$key} ), $user->{$key}, $key, $field );
528
529 update_user_meta( $user->ID, $key, $data );
530
531 $this->removed = true;
532 }
533 }
534 }
535
536 /**
537 * Remove personal data from a single donation.
538 *
539 * @since 1.6.0
540 *
541 * @param int $donation_id The donation ID.
542 * @return void
543 */
544 protected function erase_donation_personal_data( $donation_id ) {
545 $meta = get_post_meta( $donation_id, 'donor', true );
546
547 foreach ( $this->get_erasable_fields_for_donation( $donation_id ) as $field_id => $field ) {
548 if ( ! array_key_exists( $field_id, $meta ) ) {
549 continue;
550 }
551
552 /* Get the data type we will use for erasing this particular data field. */
553 $data_type = $this->get_field_data_type( $field );
554 $replace = empty( $meta[ $field_id ] ) ? '' : wp_privacy_anonymize_data( $data_type, $meta[ $field_id ] );
555
556 /**
557 * Filter the placeholder data that will replace the personal data.
558 *
559 * @since 1.6.0
560 *
561 * @param string $data The anonymous data.
562 * @param mixed $value The current value of the field.
563 * @param string $key The key of the field.
564 * @param Charitable_Donation_Field $field The field definition.
565 */
566 $meta[ $field_id ] = apply_filters( 'charitable_privacy_erasure_donation_donor_data_prop_value', $replace, $meta[ $field_id ], $field_id, $field );
567 }
568
569 update_post_meta( $donation_id, 'donor', $meta );
570 update_post_meta( $donation_id, 'data_erased', current_time( 'mysql', 0 ) );
571
572 /* Change the post_title since that otherwise has the donor's name. */
573 wp_update_post( array(
574 'ID' => $donation_id,
575 'post_title' => sprintf(
576 /* translators: %s: campaign names */
577 __( 'Anonymous &ndash; %s', 'charitable' ),
578 charitable_get_donation( $donation_id )->get_campaigns_donated_to()
579 ),
580 ) );
581
582 $log = new Charitable_Donation_Log( $donation_id );
583 $log->add( __( 'Personal data erased.', 'charitable' ) );
584 }
585
586 /**
587 * Checks whether there are any user donation fields that can be erased
588 * for donations within the data retention period.
589 *
590 * @since 1.6.0
591 *
592 * @return boolean
593 */
594 protected function has_erasable_fields_within_data_retention_period() {
595 return 0 < count( array_unique(
596 array_keys( $this->get_user_donation_fields() ),
597 $this->get_data_retention_fields()
598 ) );
599 }
600
601 /**
602 * Returns the erasable fields within the Data Retention Period.
603 *
604 * @since 1.6.0
605 *
606 * @return Charitable_Donation_Field[]
607 */
608 protected function get_erasable_fields_in_data_retention_period() {
609 return array_diff_key(
610 $this->get_user_donation_fields(),
611 array_flip( $this->get_data_retention_fields() )
612 );
613 }
614
615 /**
616 * Return the donation IDs for one or more profiles.
617 *
618 * @since 1.6.0
619 *
620 * @param object[] $profiles The donor profiles.
621 * @return int[]
622 */
623 protected function get_donations_from_profiles( $profiles ) {
624 $donor_id = wp_list_pluck( $profiles, 'donor_id' );
625
626 return charitable_get_table( 'campaign_donations' )->get_distinct_ids( 'donation_id', $donor_id, 'donor_id' );
627 }
628
629 /**
630 * Return the data type for a field.
631 *
632 * @since 1.6.0
633 *
634 * @param object $field The field attributes. This may be a Charitable_Donation_Field
635 * instance, or a simple object cast from an array.
636 * @return string
637 */
638 protected function get_field_data_type( $field ) {
639 if ( isset( $field->type ) ) {
640 $type = $field->type;
641 } else {
642 $type = is_array( $field->donation_form ) ? $field->donation_form['type'] : $field->admin_form['type'];
643 }
644
645 switch ( $type ) {
646 case 'select':
647 case 'multi-checkbox':
648 $data_type = '';
649 break;
650
651 case 'textarea':
652 case 'editor':
653 $data_type = 'longtext';
654 break;
655
656 default:
657 $data_type = $type;
658 }
659
660 /**
661 * Filter the data type to use for a field to be erased.
662 *
663 * @since 1.6.0
664 *
665 * @param string $data_type The data type.
666 * @param object $field The field attributes. This may be a Charitable_Donation_Field
667 * instance, or a simple object cast from an array.
668 */
669 return apply_filters( 'charitable_privacy_erasure_field_data_type', $data_type, $field );
670 }
671 }
672
673 endif;
674