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 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
← All changes | includes/emails/class-charitable-emails.php +138 -36 1.6.7 → 1.8.13 View file →
@@ -3,15 +3,17 @@
3 3 * Class that sets up the emails.
4 4 *
5 5 * @version 1.5.0
6 6 * @package Charitable/Classes/Charitable_Emails
7 - * @author Eric Daams
8 - * @copyright Copyright (c) 2018, Studio 164a
7 + * @author David Bisset
8 + * @copyright Copyright (c) 2023, WP Charitable LLC
9 9 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
10 10 */
11 11
12 12 // Exit if accessed directly.
13 -if ( ! defined( 'ABSPATH' ) ) { exit; }
13 +if ( ! defined( 'ABSPATH' ) ) {
14 + exit;
15 +}
14 16
15 17 if ( ! class_exists( 'Charitable_Emails' ) ) :
16 18
17 19 /**
@@ -35,8 +37,16 @@
35 37 */
36 38 private $emails;
37 39
38 40 /**
41 + * Registration flag to prevent multiple calls.
42 + *
43 + * @since 1.8.9.2
44 + * @var bool
45 + */
46 + private $emails_registered = false;
47 +
48 + /**
39 49 * Set up the class.
40 50 *
41 51 * Note that the only way to instantiate an object is with the charitable_start method,
42 52 * which can only be called during the start phase. In other words, don't try
@@ -44,13 +54,73 @@
44 54 *
45 55 * @since 1.0.0
46 56 */
47 57 private function __construct() {
58 + // Initialize state
59 + $this->emails_registered = false;
60 + $this->emails = null;
61 +
48 62 /* 3rd party hook for overriding anything we've done so far. */
49 63 do_action( 'charitable_emails_start', $this );
64 +
65 + add_action( 'init', array( $this, 'register_email_hooks' ) );
50 66 }
51 67
52 68 /**
69 + * Register email hooks for donation status changes.
70 + *
71 + * @since 1.0.0
72 + *
73 + * @return void
74 + */
75 + public function register_email_hooks() {
76 + foreach ( charitable_get_approval_statuses() as $approval_status ) {
77 + add_action( $approval_status . '_' . Charitable::DONATION_POST_TYPE, array( 'Charitable_Email_Donation_Receipt', 'send_with_donation_id' ) );
78 + add_action( $approval_status . '_' . Charitable::DONATION_POST_TYPE, array( 'Charitable_Email_New_Donation', 'send_with_donation_id' ) );
79 + }
80 + }
81 +
82 + /**
83 + * Ensure emails are registered before accessing them.
84 + * Implements lazy loading to handle timing race conditions.
85 + *
86 + * @since 1.8.9.2
87 + * @return bool True if emails are available, false on failure.
88 + */
89 + private function ensure_emails_registered() {
90 + // Quick check - if emails exist, we're done
91 + if ( is_array( $this->emails ) && ! empty( $this->emails ) ) {
92 + return true;
93 + }
94 +
95 + // Multiple detection methods for maximum compatibility
96 + if ( did_action( 'init' ) || doing_action( 'init' ) || wp_doing_ajax() || is_admin() ) {
97 + return $this->safe_register_emails();
98 + }
99 +
100 + // Fallback registration with error handling
101 + return $this->safe_register_emails();
102 + }
103 +
104 + /**
105 + * Safely register emails with error handling and guard.
106 + *
107 + * @since 1.8.9.2
108 + * @return bool True if registration successful, false otherwise.
109 + */
110 + private function safe_register_emails() {
111 + try {
112 + $this->register_emails();
113 + return is_array( $this->emails ) && ! empty( $this->emails );
114 + } catch ( Exception $e ) {
115 + if ( charitable_is_debug() ) {
116 + error_log( 'Charitable: Email registration failed - ' . $e->getMessage() );
117 + }
118 + return false;
119 + }
120 + }
121 +
122 + /**
53 123 * Returns and/or create the single instance of this class.
54 124 *
55 125 * @since 1.2.0
56 126 *
@@ -71,18 +141,28 @@
71 141 *
72 142 * @return string[]
73 143 */
74 144 public function register_emails() {
75 - $this->emails = apply_filters( 'charitable_emails', array(
76 - 'donation_receipt' => 'Charitable_Email_Donation_Receipt',
77 - 'offline_donation_receipt' => 'Charitable_Email_Offline_Donation_Receipt',
78 - 'new_donation' => 'Charitable_Email_New_Donation',
79 - 'campaign_end' => 'Charitable_Email_Campaign_End',
80 - 'offline_donation_notification' => 'Charitable_Email_Offline_Donation_Notification',
81 - 'password_reset' => 'Charitable_Email_Password_Reset',
82 - 'email_verification' => 'Charitable_Email_Email_Verification',
83 - ) );
145 + // Guard against multiple registrations
146 + if ( $this->emails_registered && is_array( $this->emails ) ) {
147 + return $this->emails;
148 + }
84 149
150 + $this->emails_registered = true;
151 +
152 + $this->emails = apply_filters(
153 + 'charitable_emails',
154 + array(
155 + 'donation_receipt' => 'Charitable_Email_Donation_Receipt',
156 + 'offline_donation_receipt' => 'Charitable_Email_Offline_Donation_Receipt',
157 + 'new_donation' => 'Charitable_Email_New_Donation',
158 + 'campaign_end' => 'Charitable_Email_Campaign_End',
159 + 'offline_donation_notification' => 'Charitable_Email_Offline_Donation_Notification',
160 + 'password_reset' => 'Charitable_Email_Password_Reset',
161 + 'email_verification' => 'Charitable_Email_Email_Verification',
162 + )
163 + );
164 +
85 165 return $this->emails;
86 166 }
87 167
88 168 /**
@@ -101,27 +181,34 @@
101 181 * @since 1.5.0
102 182 *
103 183 * @param string[] $emails The list of emails and their label.
104 184 */
105 - $emails = apply_filters( 'charitable_resendable_donation_emails', array(
106 - 'donation_receipt',
107 - 'new_donation',
108 - 'offline_donation_receipt',
109 - 'offline_donation_notification',
110 - ) );
185 + $emails = apply_filters(
186 + 'charitable_resendable_donation_emails',
187 + array(
188 + 'donation_receipt',
189 + 'new_donation',
190 + 'offline_donation_receipt',
191 + 'offline_donation_notification',
192 + )
193 + );
111 194
112 195 foreach ( $emails as $email ) {
113 196 $class = $this->get_email( $email );
114 - $object = new $class;
197 + $object = new $class();
115 198
116 - $donation_actions->register( 'resend_' . $email, array(
117 - 'label' => $object->get_name(),
118 - 'callback' => array( $this, 'resend_email' ),
119 - 'button_text' => __( 'Resend Email', 'charitable' ),
120 - 'active_callback' => array( $class, 'can_be_resent' ),
121 - 'success_message' => 11,
122 - 'failed_message' => 12,
123 - ), __( 'Resend Donation Emails', 'charitable' ) );
199 + $donation_actions->register(
200 + 'resend_' . $email,
201 + array(
202 + 'label' => $object->get_name(),
203 + 'callback' => array( $this, 'resend_email' ),
204 + 'button_text' => __( 'Resend Email', 'charitable' ),
205 + 'active_callback' => array( $class, 'can_be_resent' ),
206 + 'success_message' => 11,
207 + 'failed_message' => 12,
208 + ),
209 + __( 'Resend Donation Emails', 'charitable' )
210 + );
124 211 }
125 212 }
126 213
127 214 /**
@@ -139,9 +226,9 @@
139 226 */
140 227 public function resend_email( $success, $object_id, $args, $action ) {
141 228 $email = str_replace( 'resend_', '', $action );
142 229 $class = $this->get_email( $email );
143 - $object = new $class;
230 + $object = new $class();
144 231
145 232 return $object->resend( $object_id, $args );
146 233 }
147 234
@@ -152,22 +239,27 @@
152 239 *
153 240 * @return void
154 241 */
155 242 public function handle_email_settings_request() {
156 - if ( ! wp_verify_nonce( $_REQUEST['_nonce'], 'email' ) ) {
157 - wp_die( __( 'Cheatin\' eh?!', 'charitable' ) );
243 + if ( ! wp_verify_nonce( $_REQUEST['_nonce'], 'email' ) ) { // phpcs:ignore
244 + wp_die( __( 'Cheatin\' eh?!', 'charitable' ) ); // phpcs:ignore
158 245 }
159 246
160 - $email = isset( $_REQUEST['email_id'] ) ? $_REQUEST['email_id'] : false;
247 + $email = isset( $_REQUEST['email_id'] ) ? $_REQUEST['email_id'] : false; // phpcs:ignore
161 248
162 249 /* Gateway must be set */
163 250 if ( false === $email ) {
164 - wp_die( __( 'Missing email.', 'charitable' ) );
251 + wp_die( __( 'Missing email.', 'charitable' ) ); // phpcs:ignore
165 252 }
166 253
254 + /* Ensure emails are registered. */
255 + if ( ! $this->ensure_emails_registered() ) {
256 + wp_die( __( 'Email system not available.', 'charitable' ) );
257 + }
258 +
167 259 /* Validate email. */
168 260 if ( ! isset( $this->emails[ $email ] ) ) {
169 - wp_die( __( 'Invalid email.', 'charitable' ) );
261 + wp_die( __( 'Invalid email.', 'charitable' ) ); // phpcs:ignore
170 262 }
171 263
172 264 /* All good, so disable or enable the email */
173 265 if ( 'charitable_disable_email' == current_filter() ) {
@@ -184,8 +276,11 @@
184 276 *
185 277 * @return string
186 278 */
187 279 public function get_available_emails() {
280 + if ( ! $this->ensure_emails_registered() ) {
281 + return null; // Changed from array() for better error detection
282 + }
188 283 return $this->emails;
189 284 }
190 285
191 286 /**
@@ -214,14 +309,14 @@
214 309 */
215 310 public function get_enabled_emails_names() {
216 311 $emails = array();
217 312
218 - foreach ( $this->get_enabled_emails() as $class ) {
313 + foreach ( $this->get_enabled_emails() as $class ) {
219 314 if ( ! class_exists( $class ) ) {
220 315 continue;
221 316 }
222 317
223 - $email = new $class;
318 + $email = new $class();
224 319 $emails[ $email->get_email_id() ] = $email->get_name();
225 320 }
226 321
227 322 return $emails;
@@ -236,8 +331,11 @@
236 331 * @return string|false A string representing the email class name if the
237 332 * email is registered; false if it is not registered.
238 333 */
239 334 public function get_email( $email ) {
335 + if ( ! $this->ensure_emails_registered() ) {
336 + return false;
337 + }
240 338 return isset( $this->emails[ $email ] ) ? $this->emails[ $email ] : false;
241 339 }
242 340
243 341 /**
@@ -255,14 +353,18 @@
255 353 /**
256 354 * Enable an email.
257 355 *
258 356 * @since 1.0.0
357 + * @version 1.8.2 - Added array check.
259 358 *
260 359 * @param string $email The email to be enabled.
261 360 * @return void
262 361 */
263 362 protected function enable_email( $email ) {
264 - $settings = get_option( 'charitable_settings' );
363 + $settings = get_option( 'charitable_settings', array() );
364 + if ( ! is_array( $settings ) ) {
365 + $settings = array();
366 + }
265 367 $enabled_emails = isset( $settings['enabled_emails'] ) ? $settings['enabled_emails'] : array();
266 368 $enabled_emails[ $email ] = $this->emails[ $email ];
267 369 $settings['enabled_emails'] = $enabled_emails;
268 370