PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.11.2
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.11.2
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 1.8.0 All 210 releases
charitable / includes / emails / class-charitable-emails.php

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

404 lines 10.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class that sets up the emails.
4 *
5 * @version 1.5.0
6 * @package Charitable/Classes/Charitable_Emails
7 * @author David Bisset
8 * @copyright Copyright (c) 2023, WP Charitable LLC
9 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
10 */
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 if ( ! class_exists( 'Charitable_Emails' ) ) :
18
19 /**
20 * Charitable_Emails
21 *
22 * @since 1.0.0
23 */
24 class Charitable_Emails {
25
26 /**
27 * The single instance of this class.
28 *
29 * @var Charitable_Emails|null
30 */
31 private static $instance = null;
32
33 /**
34 * All available emails.
35 *
36 * @var string[]
37 */
38 private $emails;
39
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 /**
49 * Set up the class.
50 *
51 * Note that the only way to instantiate an object is with the charitable_start method,
52 * which can only be called during the start phase. In other words, don't try
53 * to instantiate this object.
54 *
55 * @since 1.0.0
56 */
57 private function __construct() {
58 // Initialize state
59 $this->emails_registered = false;
60 $this->emails = null;
61
62 /* 3rd party hook for overriding anything we've done so far. */
63 do_action( 'charitable_emails_start', $this );
64
65 add_action( 'init', array( $this, 'register_email_hooks' ) );
66 }
67
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 /**
123 * Returns and/or create the single instance of this class.
124 *
125 * @since 1.2.0
126 *
127 * @return Charitable_Emails
128 */
129 public static function get_instance() {
130 if ( is_null( self::$instance ) ) {
131 self::$instance = new self();
132 }
133
134 return self::$instance;
135 }
136
137 /**
138 * Register Charitable emails.
139 *
140 * @since 1.0.0
141 *
142 * @return string[]
143 */
144 public function register_emails() {
145 // Guard against multiple registrations
146 if ( $this->emails_registered && is_array( $this->emails ) ) {
147 return $this->emails;
148 }
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
165 return $this->emails;
166 }
167
168 /**
169 * Register admin actions for default emails.
170 *
171 * @since 1.5.0
172 *
173 * @return void
174 */
175 public function register_admin_actions() {
176 $donation_actions = charitable_get_donation_actions();
177
178 /**
179 * Filter the list of resendable donation emails.
180 *
181 * @since 1.5.0
182 *
183 * @param string[] $emails The list of emails and their label.
184 */
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 );
194
195 foreach ( $emails as $email ) {
196 $class = $this->get_email( $email );
197 $object = new $class();
198
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 );
211 }
212 }
213
214 /**
215 * Resend an email.
216 *
217 * This is the callback for all of the resend email actions.
218 *
219 * @since 1.5.0
220 *
221 * @param boolean $success Whether the action has been successfully completed.
222 * @param int $object_id An object ID.
223 * @param array $args Mixed set of arguments.
224 * @param string $action The action we are executing.
225 * @return boolean
226 */
227 public function resend_email( $success, $object_id, $args, $action ) {
228 $email = str_replace( 'resend_', '', $action );
229 $class = $this->get_email( $email );
230 $object = new $class();
231
232 return $object->resend( $object_id, $args );
233 }
234
235 /**
236 * Receives a request to enable or disable an email and validates it before passing it off.
237 *
238 * @since 1.0.0
239 *
240 * @return void
241 */
242 public function handle_email_settings_request() {
243 if ( ! wp_verify_nonce( $_REQUEST['_nonce'], 'email' ) ) { // phpcs:ignore
244 wp_die( __( 'Cheatin\' eh?!', 'charitable' ) ); // phpcs:ignore
245 }
246
247 $email = isset( $_REQUEST['email_id'] ) ? $_REQUEST['email_id'] : false; // phpcs:ignore
248
249 /* Gateway must be set */
250 if ( false === $email ) {
251 wp_die( __( 'Missing email.', 'charitable' ) ); // phpcs:ignore
252 }
253
254 /* Ensure emails are registered. */
255 if ( ! $this->ensure_emails_registered() ) {
256 wp_die( __( 'Email system not available.', 'charitable' ) );
257 }
258
259 /* Validate email. */
260 if ( ! isset( $this->emails[ $email ] ) ) {
261 wp_die( __( 'Invalid email.', 'charitable' ) ); // phpcs:ignore
262 }
263
264 /* All good, so disable or enable the email */
265 if ( 'charitable_disable_email' == current_filter() ) {
266 $this->disable_email( $email );
267 } else {
268 $this->enable_email( $email );
269 }
270 }
271
272 /**
273 * Returns all available emails.
274 *
275 * @since 1.0.0
276 *
277 * @return string
278 */
279 public function get_available_emails() {
280 if ( ! $this->ensure_emails_registered() ) {
281 return null; // Changed from array() for better error detection
282 }
283 return $this->emails;
284 }
285
286 /**
287 * Returns the currently enabled emails.
288 *
289 * @since 1.0.0
290 *
291 * @return string[]
292 */
293 public function get_enabled_emails() {
294 $enabled = charitable_get_option( 'enabled_emails', array() );
295
296 /* The Password Reset & Email Verification emails are always enabled. */
297 $enabled['password_reset'] = 'Charitable_Email_Password_Reset';
298 $enabled['email_verification'] = 'Charitable_Email_Email_Verification';
299
300 return $enabled;
301 }
302
303 /**
304 * Returns a list of the names of currently enabled emails.
305 *
306 * @since 1.3.0
307 *
308 * @return string[]
309 */
310 public function get_enabled_emails_names() {
311 $emails = array();
312
313 foreach ( $this->get_enabled_emails() as $class ) {
314 if ( ! class_exists( $class ) ) {
315 continue;
316 }
317
318 $email = new $class();
319 $emails[ $email->get_email_id() ] = $email->get_name();
320 }
321
322 return $emails;
323 }
324
325 /**
326 * Return the email class name for a given email.
327 *
328 * @since 1.0.0
329 *
330 * @param string $email The email to return.
331 * @return string|false A string representing the email class name if the
332 * email is registered; false if it is not registered.
333 */
334 public function get_email( $email ) {
335 if ( ! $this->ensure_emails_registered() ) {
336 return false;
337 }
338 return isset( $this->emails[ $email ] ) ? $this->emails[ $email ] : false;
339 }
340
341 /**
342 * Returns whether the passed email is enabled.
343 *
344 * @since 1.0.0
345 *
346 * @param string $email_id The email to check for.
347 * @return boolean
348 */
349 public function is_enabled_email( $email_id ) {
350 return array_key_exists( $email_id, $this->get_enabled_emails() );
351 }
352
353 /**
354 * Enable an email.
355 *
356 * @since 1.0.0
357 * @version 1.8.2 - Added array check.
358 *
359 * @param string $email The email to be enabled.
360 * @return void
361 */
362 protected function enable_email( $email ) {
363 $settings = get_option( 'charitable_settings', array() );
364 if ( ! is_array( $settings ) ) {
365 $settings = array();
366 }
367 $enabled_emails = isset( $settings['enabled_emails'] ) ? $settings['enabled_emails'] : array();
368 $enabled_emails[ $email ] = $this->emails[ $email ];
369 $settings['enabled_emails'] = $enabled_emails;
370
371 update_option( 'charitable_settings', $settings );
372
373 Charitable_Settings::get_instance()->add_update_message( __( 'Email enabled', 'charitable' ), 'success' );
374
375 do_action( 'charitable_email_enable', $email );
376 }
377
378 /**
379 * Disable an email.
380 *
381 * @since 1.0.0
382 *
383 * @param string $email The email to be disabled.
384 * @return void
385 */
386 protected function disable_email( $email ) {
387 $settings = get_option( 'charitable_settings' );
388
389 if ( ! isset( $settings['enabled_emails'][ $email ] ) ) {
390 return;
391 }
392
393 unset( $settings['enabled_emails'][ $email ] );
394
395 update_option( 'charitable_settings', $settings );
396
397 Charitable_Settings::get_instance()->add_update_message( __( 'Email disabled', 'charitable' ), 'success' );
398
399 do_action( 'charitable_email_disable', $email );
400 }
401 }
402
403 endif;
404