PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.6.6
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.6.6
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 1.7.0.9 All 211 releases
charitable / includes / db / class-charitable-donors-db.php

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

444 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Charitable Donors DB class.
4 *
5 * @package Charitable/Classes/Charitable_Donors_DB
6 * @author Eric Daams
7 * @copyright Copyright (c) 2018, Studio 164a
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.0.0
10 * @version 1.6.5
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Donors_DB' ) ) :
19
20 /**
21 * Charitable_Donors_DB
22 *
23 * @since 1.0.0
24 */
25 class Charitable_Donors_DB extends Charitable_DB {
26
27 /**
28 * The version of our database table
29 *
30 * @since 1.0.0
31 *
32 * @var string
33 */
34 public $version = '1.6.5';
35
36 /**
37 * The name of the primary column
38 *
39 * @since 1.0.0
40 *
41 * @var string
42 */
43 public $primary_key = 'donor_id';
44
45 /**
46 * Columns found in the database.
47 *
48 * @since 1.6.5
49 *
50 * @var array
51 */
52 protected $db_columns;
53
54 /**
55 * Set up the database table name.
56 *
57 * @since 1.0.0
58 *
59 * @global WPDB $wpdb
60 */
61 public function __construct() {
62 global $wpdb;
63
64 $this->table_name = $wpdb->prefix . 'charitable_donors';
65 }
66
67 /**
68 * Create the table.
69 *
70 * @since 1.0.0
71 *
72 * @global WPDB $wpdb
73 */
74 public function create_table() {
75 global $wpdb;
76
77 $charset_collate = $wpdb->get_charset_collate();
78
79 $sql = "CREATE TABLE {$this->table_name} (
80 donor_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
81 user_id bigint(20) unsigned NOT NULL,
82 email varchar(100) NOT NULL,
83 first_name varchar(255) default '',
84 last_name varchar(255) default '',
85 date_joined datetime NOT NULL default '0000-00-00 00:00:00',
86 data_erased datetime default '0000-00-00 00:00:00',
87 contact_consent tinyint(1) unsigned default NULL,
88 PRIMARY KEY (donor_id),
89 KEY user_id (user_id),
90 KEY email (email),
91 KEY data_erased (data_erased),
92 KEY contact_consent (contact_consent)
93 ) $charset_collate;";
94
95 $this->_create_table( $sql );
96 }
97
98 /**
99 * Return the list of tables that currently exist in the database.
100 *
101 * This allows us to handle backwards compatibility in cases where
102 * columns have been added to the table structure, but have not yet
103 * been added in the database.
104 *
105 * @since 1.6.5
106 *
107 * @global WPDB $wpdb
108 * @return array
109 */
110 public function get_db_columns() {
111 if ( ! isset( $this->db_columns ) ) {
112 global $wpdb;
113
114 $columns = $wpdb->get_results( "SHOW COLUMNS FROM $this->table_name" );
115 $this->db_columns = $columns ? wp_list_pluck( $columns, 'Field' ) : array();
116 }
117
118 return $this->db_columns;
119 }
120
121 /**
122 * Whitelist of columns.
123 *
124 * @since 1.0.0
125 *
126 * @return array
127 */
128 public function get_columns() {
129 $columns = array(
130 'donor_id' => '%d',
131 'user_id' => '%d',
132 'email' => '%s',
133 'first_name' => '%s',
134 'last_name' => '%s',
135 'date_joined' => '%s',
136 'data_erased' => '%s',
137 'contact_consent' => '%d',
138 );
139
140 return array_intersect_key( $columns, array_flip( $this->get_db_columns() ) );
141 }
142
143 /**
144 * Default column values.
145 *
146 * @since 1.0.0
147 *
148 * @return array
149 */
150 public function get_column_defaults() {
151 $defaults = array(
152 'donor_id' => '',
153 'user_id' => 0,
154 'email' => '',
155 'first_name' => '',
156 'last_name' => '',
157 'date_joined' => date( 'Y-m-d H:i:s' ),
158 'data_erased' => '',
159 'contact_consent' => '',
160 );
161
162 return array_intersect_key( $defaults, array_flip( $this->get_db_columns() ) );
163 }
164
165 /**
166 * Add a new donor.
167 *
168 * @since 1.0.0
169 *
170 * @param array $data Donor data to insert.
171 * @param string $type Should always be 'donors'.
172 * @return int The ID of the inserted donor.
173 */
174 public function insert( $data, $type = 'donors' ) {
175 if ( ! $this->validate_donor_email( $data ) ) {
176 charitable_get_notices()->add_error(
177 __( 'Unable to insert donor. This email address is already in use.', 'charitable' )
178 );
179 return 0;
180 }
181
182 $donor_id = parent::insert( $data, $type );
183
184 $this->maybe_log_consent( $data, $donor_id );
185
186 return $donor_id;
187 }
188
189 /**
190 * Update a donor record.
191 *
192 * @since 1.0.0
193 *
194 * @param int $row_id The record to update.
195 * @param array $data Donor data to update.
196 * @param string $where Column used in where argument.
197 * @return boolean Whether the donor record was updated.
198 */
199 public function update( $row_id, $data = array(), $where = 'donor_id' ) {
200 if ( ! $this->validate_donor_email( $data, $row_id, $where ) ) {
201 charitable_get_notices()->add_error(
202 __( 'Unable to update donor. This email address is already in use.', 'charitable' )
203 );
204 return false;
205 }
206
207 $updated = parent::update( $row_id, $data, $where );
208
209 if ( $updated ) {
210 $this->maybe_log_consent( $data, $row_id, $where );
211 }
212
213 return $updated;
214 }
215
216 /**
217 * Validate a donor's email.
218 *
219 * @since 1.6.2
220 *
221 * @param array $data The donor data.
222 * @param mixed $row_id The id of the donor record to update.
223 * @param string $where Column used in where argument.
224 * @return boolean
225 */
226 public function validate_donor_email( $data, $row_id = 0, $where = 'donor_id' ) {
227 if ( ! array_key_exists( 'email', $data ) ) {
228 return true;
229 }
230
231 /* We're anonymizing their email address. */
232 if ( $this->is_anonymized_email( $data['email'] ) ) {
233 return true;
234 }
235
236 $email_donor_id = $this->get_donor_id_by_email( $data['email'] );
237
238 /* No one else has this email address. */
239 if ( 0 == $email_donor_id ) {
240 return true;
241 }
242
243 /* This is a new donor, but the email address already belongs to another. */
244 if ( 0 == $row_id ) {
245 return false;
246 }
247
248 /* For an existing donor, the email address isn't being changed. */
249 return $this->get_column_by( 'email', $where, $row_id ) == $data['email'];
250 }
251
252 /**
253 * Log consent after a donor record has been added or updated, if contact_consent
254 * was explicitly set.
255 *
256 * @since 1.6.2
257 *
258 * @param array $data The donor data.
259 * @param mixed $row_id The id of the donor record to update.
260 * @param string $where Column used in where argument.
261 * @return boolean Whether consent was logged.
262 */
263 public function maybe_log_consent( $data, $row_id, $where = 'donor_id' ) {
264 if ( ! array_key_exists( 'contact_consent', $data ) || is_null( $data['contact_consent'] ) ) {
265 return false;
266 }
267
268 $donor_id = 'donor_id' == $where ? $row_id : $this->get_column_by( 'donor_id', $where, $row_id );
269 $log = new Charitable_Donor_Consent_Log( $donor_id );
270
271 return $log->add(
272 $data['contact_consent'],
273 charitable_get_option( 'contact_consent_label', __( 'Yes, I am happy for you to contact me via email or phone.', 'charitable' ) )
274 );
275 }
276
277 /**
278 * Return a user's ID, based on their donor ID.
279 *
280 * @since 1.0.0
281 *
282 * @param int $donor_id The Donor ID.
283 * @return int
284 */
285 public function get_user_id( $donor_id ) {
286 $user_id = $this->get_column_by( 'user_id', 'donor_id', $donor_id );
287
288 return is_null( $user_id ) ? 0 : (int) $user_id;
289 }
290
291 /**
292 * Return a donor ID, based on their user ID.
293 *
294 * @since 1.0.0
295 *
296 * @param int $user_id Donor User ID.
297 * @return int
298 */
299 public function get_donor_id( $user_id ) {
300 $donor_id = $this->get_column_by( 'donor_id', 'user_id', $user_id );
301
302 return is_null( $donor_id ) ? 0 : (int) $donor_id;
303 }
304
305 /**
306 * Return a donor ID, based on their email address.
307 *
308 * @since 1.0.0
309 *
310 * @param string $email Donor email address.
311 * @return int
312 */
313 public function get_donor_id_by_email( $email ) {
314 $donor_id = $this->get_column_by( 'donor_id', 'email', $email );
315
316 return is_null( $donor_id ) ? 0 : (int) $donor_id;
317 }
318
319 /**
320 * Return a donor's personal data, given their email address.
321 *
322 * @since 1.6.0
323 *
324 * @global $wpdb WPDB
325 * @param string $email Donor email address.
326 * @return array|object|null Database query results
327 */
328 public function get_personal_data( $email ) {
329 global $wpdb;
330
331 return $wpdb->get_results(
332 $wpdb->prepare(
333 "SELECT donor_id, email, first_name, last_name FROM {$this->table_name} WHERE email = %s;",
334 $email
335 )
336 );
337 }
338
339 /**
340 * Count the number of donors with donations.
341 *
342 * @since 1.3.4
343 *
344 * @global WPDB $wpdb
345 * @param string|array $statuses One or more donation statuses.
346 * @return int
347 */
348 public function count_donors_with_donations( $statuses = array( 'charitable-completed' ) ) {
349 global $wpdb;
350
351 if ( ! is_array( $statuses ) ) {
352 $statuses = array( $statuses );
353 }
354
355 if ( empty( $statuses ) ) {
356 $status_clause = '';
357 } else {
358 $statuses = array_filter( $statuses, 'charitable_is_valid_donation_status' );
359 $placeholders = array_fill( 0, count( $statuses ), '%s' );
360 $in = implode( ', ', $placeholders );
361 $status_clause = "AND p.post_status IN ( $in )";
362 }
363
364 $sql = "SELECT COUNT( DISTINCT(d.donor_id) )
365 FROM {$wpdb->prefix}charitable_donors d
366 INNER JOIN {$wpdb->prefix}charitable_campaign_donations cd ON cd.donor_id = d.donor_id
367 INNER JOIN $wpdb->posts p ON cd.donation_id = p.ID
368 WHERE 1 = 1
369 $status_clause;";
370
371 return $wpdb->get_var( $wpdb->prepare( $sql, $statuses ) );
372 }
373
374 /**
375 * Erase personal data for given donor IDs.
376 *
377 * @since 1.6.0
378 *
379 * @param int|int[] $donor_id The donor IDs.
380 * @return int|false Number of rows affected/selected or false on error.
381 */
382 public function erase_donor_data( $donor_id ) {
383 global $wpdb;
384
385 if ( ! is_array( $donor_id ) ) {
386 $donor_id = array( $donor_id );
387 }
388
389 /* Filter out any non absolute integers. */
390 $donor_id = array_filter( $donor_id, 'absint' );
391
392 if ( empty( $donor_id ) ) {
393 return false;
394 }
395
396 $placeholders = charitable_get_query_placeholders( count( $donor_id ), '%d' );
397
398 if ( in_array( 'data_erased', $this->get_db_columns() ) ) {
399
400 $parameters = array_merge(
401 array(
402 wp_privacy_anonymize_data( 'email' ),
403 current_time( 'mysql', 0 ),
404 ),
405 $donor_id
406 );
407
408 $sql = "UPDATE {$this->table_name}
409 SET email = %s, first_name = '', last_name = '', data_erased = %s
410 WHERE donor_id IN ( $placeholders )";
411
412 } else {
413
414 $parameters = array_merge(
415 array(
416 wp_privacy_anonymize_data( 'email' ),
417 ),
418 $donor_id
419 );
420
421 $sql = "UPDATE {$this->table_name}
422 SET email = %s, first_name = '', last_name = ''
423 WHERE donor_id IN ( $placeholders )";
424
425 }
426
427 return $wpdb->query( $wpdb->prepare( $sql, $parameters ) );
428 }
429
430 /**
431 * Checks whether the passed email is an anonymized email address.
432 *
433 * @since 1.6.4
434 *
435 * @param string $email The email address.
436 * @return boolean
437 */
438 private function is_anonymized_email( $email ) {
439 return function_exists( 'wp_privacy_anonymize_data' ) && wp_privacy_anonymize_data( 'email' ) == $email;
440 }
441 }
442
443 endif;
444