PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
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
← All changes | includes/user-management/class-charitable-user-management.php +112 -79 1.6.8 → 1.8.13 View file →
@@ -1,16 +1,19 @@
1 1 <?php
2 2 /**
3 3 * Class that manages the hook functions for the forgot password form.
4 4 *
5 - * @package Charitable/User Management/User Management
6 - * @version 1.4.0
7 - * @author Rafe Colton
8 - * @copyright Copyright (c) 2019, Studio 164a
9 - * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
5 + * @package Charitable/User Management/User Management
6 + * @author Rafe Colton, David Bisset
7 + * @copyright Copyright (c) 2023, WP Charitable LLC
8 + * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 + * @since 1.4.0
10 + * @version 1.8.12.1
10 11 */
11 12
12 -if ( ! defined( 'ABSPATH' ) ) { exit; }
13 +if ( ! defined( 'ABSPATH' ) ) {
14 + exit;
15 +}
13 16
14 17 if ( ! class_exists( 'Charitable_User_Management' ) ) :
15 18
16 19 /**
@@ -15,9 +18,9 @@
15 18
16 19 /**
17 20 * Charitable_User_Management class
18 21 *
19 - * @since 1.4.0
22 + * @since 1.4.0
20 23 */
21 24 class Charitable_User_Management {
22 25
23 26 /**
@@ -22,10 +25,11 @@
22 25
23 26 /**
24 27 * The class instance.
25 28 *
26 - * @var Charitable_User_Management
27 - * @since 1.4.0
29 + * @var Charitable_User_Management
30 + *
31 + * @since 1.4.0
28 32 */
29 33 private static $instance;
30 34
31 35 /**
@@ -30,24 +34,20 @@
30 34
31 35 /**
32 36 * Returns and/or create the single instance of this class.
33 37 *
34 - * @since 1.4.0
38 + * @since 1.4.0
35 39 *
36 - * @return Charitable_User_Management
40 + * @return Charitable_User_Management
37 41 */
38 42 public static function get_instance() {
39 - if ( is_null( self::$instance ) ) {
40 - self::$instance = new self();
41 - }
42 -
43 - return self::$instance;
43 + return self::$instance ??= new self();
44 44 }
45 45
46 46 /**
47 47 * Set up the class.
48 48 *
49 - * @since 1.4.0
49 + * @since 1.4.0
50 50 */
51 51 private function __construct() {
52 52 }
53 53
@@ -55,23 +55,22 @@
55 55 * Check whether we have clicked on a password reset link.
56 56 *
57 57 * If so, redirect to the password reset page without the query string.
58 58 *
59 - * @since 1.4.0
59 + * @since 1.4.0
60 60 *
61 - * @return false|void False if no redirect takes place.
61 + * @return false|void False if no redirect takes place.
62 62 */
63 63 public function maybe_redirect_to_password_reset() {
64 -
65 64 if ( ! charitable_is_page( 'reset_password_page' ) ) {
66 65 return false;
67 66 }
68 67
69 - if ( ! isset( $_GET['key'] ) || ! isset( $_GET['login'] ) ) {
68 + if ( ! isset( $_GET['key'] ) || ! isset( $_GET['login'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
70 69 return false;
71 70 }
72 71
73 - $value = sprintf( '%s:%s', wp_unslash( $_GET['login'] ), wp_unslash( $_GET['key'] ) );
72 + $value = sprintf( '%s:%s', wp_unslash( $_GET['login'] ), wp_unslash( $_GET['key'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
74 73
75 74 $this->set_reset_cookie( $value );
76 75
77 76 wp_safe_redirect( esc_url_raw( charitable_get_permalink( 'reset_password_page' ) ) );
@@ -76,9 +75,8 @@
76 75
77 76 wp_safe_redirect( esc_url_raw( charitable_get_permalink( 'reset_password_page' ) ) );
78 77
79 78 exit();
80 -
81 79 }
82 80
83 81 /**
84 82 * Check if user is attempting to access the default reset password page
@@ -84,14 +82,13 @@
84 82 * Check if user is attempting to access the default reset password page
85 83 *
86 84 * If so, and charitable_disable_wp_login is set, redirect them to the custom reset password page
87 85 *
88 - * @since 1.4.0
86 + * @since 1.4.0
89 87 *
90 - * @return void
88 + * @return void
91 89 */
92 90 public function maybe_redirect_to_custom_password_reset_page() {
93 -
94 91 if ( ! apply_filters( 'charitable_disable_wp_login', false ) ) {
95 92 return;
96 93 }
97 94
@@ -99,10 +96,10 @@
99 96 return;
100 97 }
101 98
102 99 $redirect_url = charitable_get_permalink( 'reset_password_page' );
103 - $redirect_url = add_query_arg( 'login', esc_attr( $_REQUEST['login'] ), $redirect_url );
104 - $redirect_url = add_query_arg( 'key', esc_attr( $_REQUEST['key'] ), $redirect_url );
100 + $redirect_url = add_query_arg( 'login', esc_attr( $_REQUEST['login'] ), $redirect_url ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
101 + $redirect_url = add_query_arg( 'key', esc_attr( $_REQUEST['key'] ), $redirect_url ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
105 102
106 103 wp_safe_redirect( esc_url_raw( $redirect_url ) );
107 104
108 105 exit();
@@ -112,16 +109,16 @@
112 109 * Check if a failed user login attempt originated from Charitable login form.
113 110 *
114 111 * If so redirect user to Charitable login page.
115 112 *
116 - * @since 1.4.0
113 + * @since 1.4.0
117 114 *
118 - * @param WP_User|WP_Error $user_or_error
119 - * @param string $username
120 - * @return WP_User|void
115 + * @param WP_User|WP_Error $user_or_error
116 + * @param string $username
117 + * @return WP_User|void
121 118 */
122 119 public function maybe_redirect_at_authenticate( $user_or_error, $username ) {
123 - if ( 'POST' != $_SERVER['REQUEST_METHOD'] ) {
120 + if ( 'POST' != $_SERVER['REQUEST_METHOD'] ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
124 121 return $user_or_error;
125 122 }
126 123
127 124 if ( ! is_wp_error( $user_or_error ) ) {
@@ -127,36 +124,62 @@
127 124 if ( ! is_wp_error( $user_or_error ) ) {
128 125 return $user_or_error;
129 126 }
130 127
131 - if ( ! isset( $_POST['charitable'] ) || ! $_POST['charitable'] ) {
128 + if ( ! isset( $_POST['charitable'] ) || ! $_POST['charitable'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
132 129 return $user_or_error;
133 130 }
134 131
132 + /*
133 + * Resolve our redirect target before touching the notice session.
134 + *
135 + * If the site uses the default WordPress login (the 'login_page' setting is
136 + * 'wp', which is the default), or the configured login page has since been
137 + * deleted, then there is no page of ours to send the user to. Bail out and let
138 + * wp-login.php render its own error, which it does natively.
139 + *
140 + * Redirecting to wp_login_url() in that case would be strictly worse than
141 + * doing nothing: our messages are queued in the Charitable notice session and
142 + * nothing renders them on wp-login.php, so the user would land on a blank
143 + * login form with no indication of what went wrong.
144 + */
145 + $redirect_url = charitable_get_permalink( 'login_page' );
146 +
147 + if ( ! $redirect_url || wp_login_url() === $redirect_url ) {
148 + return $user_or_error;
149 + }
150 +
135 151 foreach ( $user_or_error->errors as $code => $error ) {
136 152
137 153 /* Make sure the error messages link to our forgot password page, not WordPress' */
138 154 switch ( $code ) {
139 155
140 - case 'invalid_email' :
156 + case 'invalid_email':
141 157 $error = __( '<strong>ERROR</strong>: Invalid email address.', 'charitable' ) .
142 158 ' <a href="' . esc_url( charitable_get_permalink( 'forgot_password_page' ) ) . '">' .
143 - __( 'Lost your password?' ) .
159 + __( 'Lost your password?', 'charitable' ) .
144 160 '</a>';
145 161 break;
146 162
147 - case 'incorrect_password' :
163 + case 'incorrect_password':
148 164 $error = sprintf(
149 165 /* translators: %s: email address */
150 - __( '<strong>ERROR</strong>: The password you entered for %s is incorrect.' ),
151 - '<strong>' . $username . '</strong>'
166 + __( '<strong>ERROR</strong>: The password you entered for %s is incorrect.', 'charitable' ),
167 + /*
168 + * Escape the submitted username. This string is queued as a notice and
169 + * rendered as HTML, so it must not carry markup through. In practice
170 + * wp_authenticate() has already run sanitize_user() on it before the
171 + * authenticate filter fires, which strips tags -- this is belt and
172 + * braces so the escaping does not depend on that upstream behaviour.
173 + */
174 + '<strong>' . esc_html( $username ) . '</strong>'
152 175 ) .
153 176 ' <a href="' . esc_url( charitable_get_permalink( 'forgot_password_page' ) ) . '">' .
154 - __( 'Lost your password?' ) .
177 + __( 'Lost your password?', 'charitable' ) .
155 178 '</a>';
156 179 break;
157 180
158 - default :
181 + default:
159 182 $error = $error[0];
160 183 }
161 184
162 185 charitable_get_notices()->add_error( $error );
@@ -163,10 +186,8 @@
163 186 }
164 187
165 188 charitable_get_session()->add_notices();
166 189
167 - $redirect_url = charitable_get_permalink( 'login_page' );
168 -
169 190 if ( strlen( $username ) ) {
170 191 $redirect_url = add_query_arg( 'username', $username, $redirect_url );
171 192 }
172 193
@@ -179,14 +200,13 @@
179 200 * Check if user is attempting to access the forgot password page
180 201 *
181 202 * If so, and charitable_disable_wp_login is set, redirect them to the custom forgot password page
182 203 *
183 - * @since 1.4.0
204 + * @since 1.4.0
184 205 *
185 - * @return void
206 + * @return void
186 207 */
187 208 public function maybe_redirect_to_custom_lostpassword() {
188 -
189 209 /**
190 210 * Set whether the WP login should be disabled.
191 211 *
192 212 * @since 1.4.0
@@ -200,9 +220,9 @@
200 220 if ( 'wp' == charitable_get_option( 'login_page', 'wp' ) ) {
201 221 return;
202 222 }
203 223
204 - if ( $_SERVER[ 'REQUEST_METHOD' ] != 'GET' ) {
224 + if ( $_SERVER['REQUEST_METHOD'] != 'GET' ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
205 225 return;
206 226 }
207 227
208 228 wp_safe_redirect( esc_url_raw( charitable_get_permalink( 'forgot_password_page' ) ) );
@@ -216,17 +236,16 @@
216 236 * This is based on the WC_Shortcode_My_Account::set_reset_password_cookie()
217 237 * method in WooCommerce, which in turn is based on the core implementation
218 238 * in wp-login.php.
219 239 *
220 - * @since 1.4.0
240 + * @since 1.4.0
221 241 *
222 - * @param string $value
223 - * @return void
242 + * @param string $value
243 + * @return void
224 244 */
225 245 public function set_reset_cookie( $value = '' ) {
226 -
227 246 $rp_cookie = 'wp-resetpass-' . COOKIEHASH;
228 - $rp_path = current( explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) ) );
247 + $rp_path = current( explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
229 248
230 249 if ( $value ) {
231 250 setcookie( $rp_cookie, $value, 0, $rp_path, COOKIE_DOMAIN, is_ssl(), true );
232 251 } else {
@@ -231,9 +250,8 @@
231 250 setcookie( $rp_cookie, $value, 0, $rp_path, COOKIE_DOMAIN, is_ssl(), true );
232 251 } else {
233 252 setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true );
234 253 }
235 -
236 254 }
237 255
238 256 /**
239 257 * Hides WP Admin bar if the user is not allowed to see it.
@@ -239,14 +257,13 @@
239 257 * Hides WP Admin bar if the user is not allowed to see it.
240 258 *
241 259 * Uses the builtin show_admin_bar function.
242 260 *
243 - * @see show_admin_bar()
261 + * @see show_admin_bar()
244 262 *
245 - * @since 1.4.0
263 + * @since 1.4.0
246 264 */
247 265 public function maybe_remove_admin_bar() {
248 -
249 266 /**
250 267 * To enable the admin bar for users without admin bar access,
251 268 * you can use this one-liner:
252 269 *
@@ -258,18 +275,16 @@
258 275
259 276 if ( ! $this->user_has_admin_access() ) {
260 277 show_admin_bar( false );
261 278 }
262 -
263 279 }
264 280
265 281 /**
266 282 * Redirects the user away from /wp-admin if they are not authorized to access it.
267 283 *
268 - * @since 1.4.0
284 + * @since 1.4.0
269 285 */
270 286 public function maybe_redirect_away_from_admin() {
271 -
272 287 /* Leave AJAX requests alone. */
273 288 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
274 289 return;
275 290 }
@@ -298,12 +313,11 @@
298 313 */
299 314 $redirect_url = apply_filters( 'charitable_admin_redirect_url', false );
300 315
301 316 if ( ! $redirect_url ) {
302 -
303 317 $redirect_url = charitable_get_permalink( 'profile_page' );
304 318
305 - if ( false === $redirect_url ) {
319 + if ( ! $redirect_url ) {
306 320 $redirect_url = home_url();
307 321 }
308 322 }
309 323
@@ -309,17 +323,16 @@
309 323
310 324 wp_safe_redirect( esc_url_raw( $redirect_url ) );
311 325
312 326 exit();
313 -
314 327 }
315 328
316 329 /**
317 330 * Redirect the user to the Charitable login page.
318 331 *
319 - * @since 1.4.0
332 + * @since 1.4.0
320 333 *
321 - * @return void
334 + * @return void
322 335 */
323 336 public function maybe_redirect_to_charitable_login() {
324 337 if ( ! apply_filters( 'charitable_disable_wp_login', false ) ) {
325 338 return;
@@ -329,9 +342,9 @@
329 342 return;
330 343 }
331 344
332 345 /* Don't prevent logging out. */
333 - if ( 'GET' != $_SERVER['REQUEST_METHOD'] ) {
346 + if ( 'GET' != $_SERVER['REQUEST_METHOD'] ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
334 347 return;
335 348 }
336 349
337 350 wp_safe_redirect( esc_url_raw( charitable_get_permalink( 'login_page' ) ) );
@@ -342,16 +355,19 @@
342 355 /**
343 356 * Send the user verification email.
344 357 *
345 358 * @since 1.5.0
359 + * @since 1.6.32
346 360 *
347 - * @param WP_User $user An instance of `WP_User`.
348 - * @param string $redirect_url Where the user should be redirected to after verifying their email.
361 + * @param WP_User $user An instance of `WP_User`.
362 + * @param string $redirect_url Where the user should be redirected to after verifying their email.
363 + * @param boolean|null $force_send Whether the link should include an argument to force
364 + * resending the email, even if it was sent recently.
349 365 * @return boolean True if the email is sent. False otherwise.
350 366 */
351 - public function send_verification_email( $user = '', $redirect_url = '' ) {
352 - if ( empty( $user ) && array_key_exists( 'user', $_GET ) ) {
353 - $user = get_user_by( 'id', $_GET['user'] );
367 + public function send_verification_email( $user = '', $redirect_url = '', $force_send = null ) {
368 + if ( empty( $user ) && array_key_exists( 'user', $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
369 + $user = wp_get_current_user();
354 370 }
355 371
356 372 if ( ! is_a( $user, 'WP_User' ) ) {
357 373 return false;
@@ -356,16 +372,27 @@
356 372 if ( ! is_a( $user, 'WP_User' ) ) {
357 373 return false;
358 374 }
359 375
360 - if ( empty( $redirect_url ) && array_key_exists( 'redirect_url', $_GET ) ) {
361 - $redirect_url = $_GET['redirect_url'];
376 + if ( is_null( $force_send ) ) {
377 + $force_send = array_key_exists( 'force_send', $_GET ) && $_GET['force_send']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
362 378 }
363 379
380 + if ( empty( $redirect_url ) && array_key_exists( 'redirect_url', $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
381 + $redirect_url = $_GET['redirect_url']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
382 + }
383 +
364 384 /* Prepare the email. */
365 385 $email = new Charitable_Email_Email_Verification( array( 'user' => $user ) );
366 386
387 + if ( ! $force_send && $email->sent_recently() ) {
388 + return false;
389 + }
390 +
367 391 if ( ! empty( $redirect_url ) ) {
392 + /* Ensure that the redirect URL is encoded, but not double-encoded. */
393 + $redirect_url = urlencode( urldecode( $redirect_url ) );
394 +
368 395 $email->set_redirect_url( $redirect_url );
369 396 }
370 397
371 398 /* If the confirmation link is generated correctly and the email is sent, set a notice. */
@@ -378,24 +405,30 @@
378 405
379 406 /**
380 407 * Check whether the user has admin access.
381 408 *
382 - * @since 1.4.0
409 + * @since 1.4.0
383 410 *
384 - * @return boolean
411 + * @return boolean
385 412 */
386 413 private function user_has_admin_access() {
387 -
388 414 if ( ! is_user_logged_in() ) {
389 415 return false;
390 416 }
391 417
392 - $ret = current_user_can( 'edit_posts' )
418 + $has_access = current_user_can( 'edit_posts' )
393 419 || current_user_can( 'manage_charitable_settings' )
394 - || current_user_can( 'edit_products' );
420 + || current_user_can( 'edit_products' )
421 + || current_user_can( 'translate' );
395 422
396 - return apply_filters( 'charitable_user_has_admin_access', $ret );
397 -
423 + /**
424 + * Filter whether the user should have access to the WordPress dashboard.
425 + *
426 + * @since 1.4.0
427 + *
428 + * @param boolean $has_access Whether the user has access.
429 + */
430 + return apply_filters( 'charitable_user_has_admin_access', $has_access );
398 431 }
399 432 }
400 433
401 434 endif;