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/emails/class-charitable-emails.php +133 -33 1.7.0.12 → 1.8.13 View file →
@@ -4,9 +4,9 @@
4 4 *
5 5 * @version 1.5.0
6 6 * @package Charitable/Classes/Charitable_Emails
7 7 * @author David Bisset
8 - * @copyright Copyright (c) 2022, WP Charitable LLC
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.
@@ -37,8 +37,16 @@
37 37 */
38 38 private $emails;
39 39
40 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 + /**
41 49 * Set up the class.
42 50 *
43 51 * Note that the only way to instantiate an object is with the charitable_start method,
44 52 * which can only be called during the start phase. In other words, don't try
@@ -46,13 +54,73 @@
46 54 *
47 55 * @since 1.0.0
48 56 */
49 57 private function __construct() {
58 + // Initialize state
59 + $this->emails_registered = false;
60 + $this->emails = null;
61 +
50 62 /* 3rd party hook for overriding anything we've done so far. */
51 63 do_action( 'charitable_emails_start', $this );
64 +
65 + add_action( 'init', array( $this, 'register_email_hooks' ) );
52 66 }
53 67
54 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 + /**
55 123 * Returns and/or create the single instance of this class.
56 124 *
57 125 * @since 1.2.0
58 126 *
@@ -73,18 +141,28 @@
73 141 *
74 142 * @return string[]
75 143 */
76 144 public function register_emails() {
77 - $this->emails = apply_filters( 'charitable_emails', array(
78 - 'donation_receipt' => 'Charitable_Email_Donation_Receipt',
79 - 'offline_donation_receipt' => 'Charitable_Email_Offline_Donation_Receipt',
80 - 'new_donation' => 'Charitable_Email_New_Donation',
81 - 'campaign_end' => 'Charitable_Email_Campaign_End',
82 - 'offline_donation_notification' => 'Charitable_Email_Offline_Donation_Notification',
83 - 'password_reset' => 'Charitable_Email_Password_Reset',
84 - 'email_verification' => 'Charitable_Email_Email_Verification',
85 - ) );
145 + // Guard against multiple registrations
146 + if ( $this->emails_registered && is_array( $this->emails ) ) {
147 + return $this->emails;
148 + }
86 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 +
87 165 return $this->emails;
88 166 }
89 167
90 168 /**
@@ -103,27 +181,34 @@
103 181 * @since 1.5.0
104 182 *
105 183 * @param string[] $emails The list of emails and their label.
106 184 */
107 - $emails = apply_filters( 'charitable_resendable_donation_emails', array(
108 - 'donation_receipt',
109 - 'new_donation',
110 - 'offline_donation_receipt',
111 - 'offline_donation_notification',
112 - ) );
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 + );
113 194
114 195 foreach ( $emails as $email ) {
115 196 $class = $this->get_email( $email );
116 - $object = new $class;
197 + $object = new $class();
117 198
118 - $donation_actions->register( 'resend_' . $email, array(
119 - 'label' => $object->get_name(),
120 - 'callback' => array( $this, 'resend_email' ),
121 - 'button_text' => __( 'Resend Email', 'charitable' ),
122 - 'active_callback' => array( $class, 'can_be_resent' ),
123 - 'success_message' => 11,
124 - 'failed_message' => 12,
125 - ), __( '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 + );
126 211 }
127 212 }
128 213
129 214 /**
@@ -141,9 +226,9 @@
141 226 */
142 227 public function resend_email( $success, $object_id, $args, $action ) {
143 228 $email = str_replace( 'resend_', '', $action );
144 229 $class = $this->get_email( $email );
145 - $object = new $class;
230 + $object = new $class();
146 231
147 232 return $object->resend( $object_id, $args );
148 233 }
149 234
@@ -154,22 +239,27 @@
154 239 *
155 240 * @return void
156 241 */
157 242 public function handle_email_settings_request() {
158 - if ( ! wp_verify_nonce( $_REQUEST['_nonce'], 'email' ) ) {
159 - wp_die( __( 'Cheatin\' eh?!', 'charitable' ) );
243 + if ( ! wp_verify_nonce( $_REQUEST['_nonce'], 'email' ) ) { // phpcs:ignore
244 + wp_die( __( 'Cheatin\' eh?!', 'charitable' ) ); // phpcs:ignore
160 245 }
161 246
162 - $email = isset( $_REQUEST['email_id'] ) ? $_REQUEST['email_id'] : false;
247 + $email = isset( $_REQUEST['email_id'] ) ? $_REQUEST['email_id'] : false; // phpcs:ignore
163 248
164 249 /* Gateway must be set */
165 250 if ( false === $email ) {
166 - wp_die( __( 'Missing email.', 'charitable' ) );
251 + wp_die( __( 'Missing email.', 'charitable' ) ); // phpcs:ignore
167 252 }
168 253
254 + /* Ensure emails are registered. */
255 + if ( ! $this->ensure_emails_registered() ) {
256 + wp_die( __( 'Email system not available.', 'charitable' ) );
257 + }
258 +
169 259 /* Validate email. */
170 260 if ( ! isset( $this->emails[ $email ] ) ) {
171 - wp_die( __( 'Invalid email.', 'charitable' ) );
261 + wp_die( __( 'Invalid email.', 'charitable' ) ); // phpcs:ignore
172 262 }
173 263
174 264 /* All good, so disable or enable the email */
175 265 if ( 'charitable_disable_email' == current_filter() ) {
@@ -186,8 +276,11 @@
186 276 *
187 277 * @return string
188 278 */
189 279 public function get_available_emails() {
280 + if ( ! $this->ensure_emails_registered() ) {
281 + return null; // Changed from array() for better error detection
282 + }
190 283 return $this->emails;
191 284 }
192 285
193 286 /**
@@ -221,9 +314,9 @@
221 314 if ( ! class_exists( $class ) ) {
222 315 continue;
223 316 }
224 317
225 - $email = new $class;
318 + $email = new $class();
226 319 $emails[ $email->get_email_id() ] = $email->get_name();
227 320 }
228 321
229 322 return $emails;
@@ -238,8 +331,11 @@
238 331 * @return string|false A string representing the email class name if the
239 332 * email is registered; false if it is not registered.
240 333 */
241 334 public function get_email( $email ) {
335 + if ( ! $this->ensure_emails_registered() ) {
336 + return false;
337 + }
242 338 return isset( $this->emails[ $email ] ) ? $this->emails[ $email ] : false;
243 339 }
244 340
245 341 /**
@@ -257,14 +353,18 @@
257 353 /**
258 354 * Enable an email.
259 355 *
260 356 * @since 1.0.0
357 + * @version 1.8.2 - Added array check.
261 358 *
262 359 * @param string $email The email to be enabled.
263 360 * @return void
264 361 */
265 362 protected function enable_email( $email ) {
266 - $settings = get_option( 'charitable_settings' );
363 + $settings = get_option( 'charitable_settings', array() );
364 + if ( ! is_array( $settings ) ) {
365 + $settings = array();
366 + }
267 367 $enabled_emails = isset( $settings['enabled_emails'] ) ? $settings['enabled_emails'] : array();
268 368 $enabled_emails[ $email ] = $this->emails[ $email ];
269 369 $settings['enabled_emails'] = $enabled_emails;
270 370