1
|
|
|
<?php |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* Class for allowing donors access to their donation w/o logging in; |
4
|
|
|
* |
5
|
|
|
* Based on the work from Matt Gibbs - https://github.com/FacetWP/edd-no-logins |
6
|
|
|
* |
7
|
|
|
* @package Give |
8
|
|
|
* @copyright Copyright (c) 2016, WordImpress |
9
|
|
|
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License |
10
|
|
|
* @since 1.4 |
11
|
|
|
*/ |
12
|
|
|
|
13
|
|
|
defined( 'ABSPATH' ) or exit; |
|
|
|
|
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Class Give_Email_Access |
17
|
|
|
*/ |
18
|
|
|
class Give_Email_Access { |
19
|
|
|
|
20
|
|
|
public $token_exists = false; |
21
|
|
|
public $token_email = false; |
22
|
|
|
public $token = false; |
23
|
|
|
public $error = ''; |
24
|
|
|
|
25
|
|
|
private $verify_throttle; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Give_Email_Access constructor. |
29
|
|
|
*/ |
30
|
|
|
function __construct() { |
|
|
|
|
31
|
|
|
|
32
|
|
|
// get it started |
33
|
|
|
add_action( 'init', array( $this, 'init' ) ); |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Register defaults and filters |
39
|
|
|
*/ |
40
|
|
|
function init() { |
|
|
|
|
41
|
|
|
|
42
|
|
|
$is_enabled = give_get_option( 'email_access' ); |
43
|
|
|
|
44
|
|
|
//Non-logged in users only |
45
|
|
|
if ( is_user_logged_in() || $is_enabled !== 'on' || is_admin() ) { |
46
|
|
|
return; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
//Are db columns setup? |
50
|
|
|
$is_setup = give_get_option( 'email_access_installed' ); |
51
|
|
|
if ( empty( $is_setup ) ) { |
52
|
|
|
$this->create_columns(); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
// Timeouts |
56
|
|
|
$this->verify_throttle = apply_filters( 'give_nl_verify_throttle', 300 ); |
57
|
|
|
$this->token_expiration = apply_filters( 'give_nl_token_expiration', 7200 ); |
|
|
|
|
58
|
|
|
|
59
|
|
|
// Setup login |
60
|
|
|
$this->check_for_token(); |
61
|
|
|
|
62
|
|
|
if ( $this->token_exists ) { |
63
|
|
|
add_filter( 'give_can_view_receipt', '__return_true' ); |
64
|
|
|
add_filter( 'give_user_pending_verification', '__return_false' ); |
65
|
|
|
add_filter( 'give_get_users_purchases_args', array( $this, 'users_purchases_args' ) ); |
66
|
|
|
} |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* Prevent email spamming |
71
|
|
|
* |
72
|
|
|
* @param $customer_id |
73
|
|
|
* |
74
|
|
|
* @return bool |
75
|
|
|
*/ |
76
|
|
|
function can_send_email( $customer_id ) { |
|
|
|
|
77
|
|
|
global $wpdb; |
|
|
|
|
78
|
|
|
|
79
|
|
|
// Prevent multiple emails within X minutes |
80
|
|
|
$throttle = date( 'Y-m-d H:i:s', time() - $this->verify_throttle ); |
81
|
|
|
|
82
|
|
|
// Does a user row exist? |
83
|
|
|
$exists = (int) $wpdb->get_var( |
84
|
|
|
$wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}give_customers WHERE id = %d", $customer_id ) |
85
|
|
|
); |
86
|
|
|
|
87
|
|
|
if ( 0 < $exists ) { |
88
|
|
|
$row_id = (int) $wpdb->get_var( |
89
|
|
|
$wpdb->prepare( "SELECT id FROM {$wpdb->prefix}give_customers WHERE id = %d AND (verify_throttle < %s OR verify_key = '') LIMIT 1", $customer_id, $throttle ) |
90
|
|
|
); |
91
|
|
|
|
92
|
|
|
if ( $row_id < 1 ) { |
93
|
|
|
give_set_error( 'give_email_access_attempts_exhausted', __( 'Please wait a few minutes before requesting a new email access link.', 'give' ) ); |
94
|
|
|
|
95
|
|
|
return false; |
96
|
|
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return true; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Send the user's token |
105
|
|
|
* |
106
|
|
|
* @param $customer_id |
107
|
|
|
* @param $email |
108
|
|
|
*/ |
109
|
|
|
function send_email( $customer_id, $email ) { |
|
|
|
|
110
|
|
|
|
111
|
|
|
$verify_key = wp_generate_password( 20, false ); |
112
|
|
|
|
113
|
|
|
// Generate a new verify key |
114
|
|
|
$this->set_verify_key( $customer_id, $email, $verify_key ); |
115
|
|
|
|
116
|
|
|
// Get the purchase history URL |
117
|
|
|
$page_id = give_get_option( 'history_page' ); |
118
|
|
|
|
119
|
|
|
$access_url = add_query_arg( array( |
120
|
|
|
'give_nl' => $verify_key, |
121
|
|
|
), get_permalink( $page_id ) ); |
122
|
|
|
|
123
|
|
|
//Nice subject and message |
124
|
|
|
$subject = apply_filters( 'give_email_access_token_subject', sprintf( __( 'Your Access Link to %1$s', 'give' ), get_bloginfo( 'name' ) ) ); |
125
|
|
|
|
126
|
|
|
$message = __( 'You or someone in your organization requested an access link be sent to this email address. This is a temporary access link for you to view your donation information. Click on the link below to view:', 'give' ) . "\n\n"; |
127
|
|
|
|
128
|
|
|
$message .= '<a href="' . esc_url( $access_url ) . '" target="_blank">' . __( 'Access My Donation Details', 'give' ) . ' »</a>'; |
129
|
|
|
|
130
|
|
|
$message .= "\n\n"; |
131
|
|
|
$message .= "\n\n"; |
132
|
|
|
$message .= __( 'Sincerely,', 'give' ); |
133
|
|
|
$message .= "\n" . get_bloginfo( 'name' ) . "\n"; |
134
|
|
|
|
135
|
|
|
$message = apply_filters( 'give_email_access_token_message', $message ); |
136
|
|
|
|
137
|
|
|
|
138
|
|
|
// Send the email |
139
|
|
|
Give()->emails->__set( 'heading', apply_filters( 'give_email_access_token_heading', __( 'Your Access Link', 'give' ) ) ); |
140
|
|
|
Give()->emails->send( $email, $subject, $message ); |
141
|
|
|
|
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Has the user authenticated? |
147
|
|
|
*/ |
148
|
|
|
function check_for_token() { |
|
|
|
|
149
|
|
|
|
150
|
|
|
$token = isset( $_GET['give_nl'] ) ? $_GET['give_nl'] : ''; |
151
|
|
|
|
152
|
|
|
// Check for cookie |
153
|
|
|
if ( empty( $token ) ) { |
154
|
|
|
$token = isset( $_COOKIE['give_nl'] ) ? $_COOKIE['give_nl'] : ''; |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
if ( ! empty( $token ) ) { |
158
|
|
|
if ( ! $this->is_valid_token( $token ) ) { |
159
|
|
|
if ( ! $this->is_valid_verify_key( $token ) ) { |
160
|
|
|
return; |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
$this->token_exists = true; |
165
|
|
|
// Set cookie |
166
|
|
|
$lifetime = current_time( 'timestamp' ) + Give()->session->set_expiration_time(); |
167
|
|
|
@setcookie( 'give_nl', $token, $lifetime, COOKIEPATH, COOKIE_DOMAIN, false ); |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Is this a valid token? |
173
|
|
|
* |
174
|
|
|
* @param $token |
175
|
|
|
* |
176
|
|
|
* @return bool |
177
|
|
|
*/ |
178
|
|
|
function is_valid_token( $token ) { |
|
|
|
|
179
|
|
|
|
180
|
|
|
global $wpdb; |
|
|
|
|
181
|
|
|
|
182
|
|
|
// Make sure token isn't expired |
183
|
|
|
$expires = date( 'Y-m-d H:i:s', time() - $this->token_expiration ); |
184
|
|
|
|
185
|
|
|
$email = $wpdb->get_var( |
186
|
|
|
$wpdb->prepare( "SELECT email FROM {$wpdb->prefix}give_customers WHERE token = %s AND verify_throttle >= %s LIMIT 1", $token, $expires ) |
187
|
|
|
); |
188
|
|
|
|
189
|
|
|
if ( ! empty( $email ) ) { |
190
|
|
|
$this->token_email = $email; |
191
|
|
|
$this->token = $token; |
192
|
|
|
|
193
|
|
|
return true; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
//Set error only if email access form isn't being submitted |
197
|
|
|
if ( ! isset( $_POST['give_email'] ) && ! isset( $_POST['_wpnonce'] ) ) { |
198
|
|
|
give_set_error( 'give_email_token_expired', apply_filters( 'give_email_token_expired_message', 'Sorry, your access token has expired. Please request a new one below:', 'give' ) ); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
|
202
|
|
|
return false; |
203
|
|
|
|
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Add the verify key to DB |
208
|
|
|
* |
209
|
|
|
* @param $customer_id |
210
|
|
|
* @param $email |
211
|
|
|
* @param $verify_key |
212
|
|
|
*/ |
213
|
|
|
function set_verify_key( $customer_id, $email, $verify_key ) { |
|
|
|
|
214
|
|
|
global $wpdb; |
|
|
|
|
215
|
|
|
|
216
|
|
|
$now = date( 'Y-m-d H:i:s' ); |
217
|
|
|
|
218
|
|
|
// Insert or update? |
219
|
|
|
$row_id = (int) $wpdb->get_var( |
220
|
|
|
$wpdb->prepare( "SELECT id FROM {$wpdb->prefix}give_customers WHERE id = %d LIMIT 1", $customer_id ) |
221
|
|
|
); |
222
|
|
|
|
223
|
|
|
// Update |
224
|
|
|
if ( ! empty( $row_id ) ) { |
225
|
|
|
$wpdb->query( |
226
|
|
|
$wpdb->prepare( "UPDATE {$wpdb->prefix}give_customers SET verify_key = %s, verify_throttle = %s WHERE id = %d LIMIT 1", $verify_key, $now, $row_id ) |
227
|
|
|
); |
228
|
|
|
} // Insert |
229
|
|
|
else { |
230
|
|
|
$wpdb->query( |
231
|
|
|
$wpdb->prepare( "INSERT INTO {$wpdb->prefix}give_customers ( verify_key, verify_throttle) VALUES (%s, %s)", $verify_key, $now ) |
232
|
|
|
); |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Is this a valid verify key? |
238
|
|
|
* |
239
|
|
|
* @param $token |
240
|
|
|
* |
241
|
|
|
* @return bool |
242
|
|
|
*/ |
243
|
|
|
function is_valid_verify_key( $token ) { |
|
|
|
|
244
|
|
|
global $wpdb; |
|
|
|
|
245
|
|
|
|
246
|
|
|
// See if the verify_key exists |
247
|
|
|
$row = $wpdb->get_row( |
248
|
|
|
$wpdb->prepare( "SELECT id, email FROM {$wpdb->prefix}give_customers WHERE verify_key = %s LIMIT 1", $token ) |
249
|
|
|
); |
250
|
|
|
|
251
|
|
|
$now = date( 'Y-m-d H:i:s' ); |
252
|
|
|
|
253
|
|
|
// Set token |
254
|
|
|
if ( ! empty( $row ) ) { |
255
|
|
|
$wpdb->query( |
256
|
|
|
$wpdb->prepare( "UPDATE {$wpdb->prefix}give_customers SET verify_key = '', token = %s, verify_throttle = %s WHERE id = %d LIMIT 1", $token, $now, $row->id ) |
257
|
|
|
); |
258
|
|
|
|
259
|
|
|
$this->token_email = $row->email; |
260
|
|
|
$this->token = $token; |
261
|
|
|
|
262
|
|
|
return true; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
return false; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Force Give to find transactions by donation email, not user ID |
270
|
|
|
* |
271
|
|
|
* @param $args |
272
|
|
|
* |
273
|
|
|
* @return mixed |
274
|
|
|
*/ |
275
|
|
|
function users_purchases_args( $args ) { |
|
|
|
|
276
|
|
|
$args['user'] = $this->token_email; |
277
|
|
|
|
278
|
|
|
return $args; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Create Columns |
284
|
|
|
* |
285
|
|
|
* @description Create the necessary columns for email access |
286
|
|
|
*/ |
287
|
|
|
function create_columns() { |
|
|
|
|
288
|
|
|
|
289
|
|
|
global $wpdb; |
|
|
|
|
290
|
|
|
|
291
|
|
|
//Create columns in customers table |
292
|
|
|
$query = $wpdb->query( "ALTER TABLE {$wpdb->prefix}give_customers ADD `token` VARCHAR(255) CHARACTER SET utf8 NOT NULL, ADD `verify_key` VARCHAR(255) CHARACTER SET utf8 NOT NULL AFTER `token`, ADD `verify_throttle` DATETIME NOT NULL AFTER `verify_key`" ); |
293
|
|
|
|
294
|
|
|
//Columns added properly |
295
|
|
|
if ( $query ) { |
296
|
|
|
give_update_option( 'email_access_installed', 1 ); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
|
302
|
|
|
} |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.