PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.4.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.4.0
1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
suredonation / inc / admin / notices.php

notices.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management 1.4.0, at inc/admin/notices.php

724 lines 22.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin Notices.
4 *
5 * Registers SureDonation's engagement admin notices:
6 *
7 * - Review notice (donation) : shown once the site has at least one live
8 * donation, after the 3-day install grace.
9 * - Test mode notice : shown when a payment gateway is connected but
10 * the site is still in test mode, nudging the
11 * admin to switch to live mode.
12 * - Review notice (gateway) : shown when a payment gateway is connected in
13 * live mode but no live donation exists yet,
14 * after the 3-day install grace.
15 * - Setup gateway notice : shown instantly (no grace) when no payment
16 * gateway is connected at all.
17 *
18 * The four notices are mutually exclusive by construction. Priority is:
19 * live donation > gateway configured (test mode) > gateway configured (live
20 * mode) > no gateway.
21 *
22 * @package SureDonation
23 */
24
25 namespace SureDonation\Inc\Admin;
26
27 use SureDonation\Inc\Database\Tables\Donations;
28 use SureDonation\Inc\Helper;
29 use SureDonation\Inc\Payments\Payment_Helper;
30 use SureDonation\Inc\Payments\Stripe\Stripe_Helper;
31
32 // Exit if accessed directly.
33 if ( ! defined( 'ABSPATH' ) ) {
34 exit;
35 }
36
37 /**
38 * Admin Notices class.
39 *
40 * @since 1.2.0
41 */
42 class Notices {
43
44 /**
45 * Install-grace period, in seconds, before the review notices may appear.
46 *
47 * @var int
48 * @since 1.2.0
49 */
50 public const REVIEW_NOTICE_DELAY = 3 * DAY_IN_SECONDS;
51
52 /**
53 * WordPress.org review URL for the CTA.
54 *
55 * @var string
56 * @since 1.2.0
57 */
58 public const REVIEW_URL = 'https://wordpress.org/support/plugin/suredonation/reviews/#new-post';
59
60 /**
61 * Instance of this class.
62 *
63 * @var Notices|null
64 * @since 1.2.0
65 */
66 private static $instance = null;
67
68 /**
69 * Memoized "has at least one live donation" result.
70 *
71 * @var bool|null
72 * @since 1.2.0
73 */
74 private $has_live_donation = null;
75
76 /**
77 * Memoized "a payment gateway is configured" result.
78 *
79 * @var bool|null
80 * @since 1.2.0
81 */
82 private $gateway_configured = null;
83
84 /**
85 * Constructor.
86 *
87 * @since 1.2.0
88 */
89 private function __construct() {
90 // Ensure the notices library (and its priority-30 renderer) is loaded
91 // early, before the admin_notices hook fires.
92 if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
93 require_once SUREDONATION_DIR . 'inc/lib/astra-notices/class-bsf-admin-notices.php';
94 }
95
96 add_action( 'admin_notices', [ $this, 'display_review_notice_donation' ] );
97 add_action( 'admin_notices', [ $this, 'display_test_mode_notice' ] );
98 add_action( 'admin_notices', [ $this, 'display_review_notice_gateway' ] );
99 add_action( 'admin_notices', [ $this, 'display_setup_gateway_notice' ] );
100 add_action( 'admin_notices', [ $this, 'display_webhook_notice' ] );
101
102 // Load the banner-notice styles from the admin <head> (not the late
103 // after-markup hook) so the banner never renders unstyled first.
104 add_action( 'admin_enqueue_scripts', [ $this, 'maybe_enqueue_banner_notice_style' ] );
105
106 add_action( 'wp_ajax_suredonation_notice_response', [ $this, 'handle_notice_response' ] );
107 }
108
109 /**
110 * Get instance of this class.
111 *
112 * @return Notices
113 * @since 1.2.0
114 */
115 public static function get_instance() {
116 if ( null === self::$instance ) {
117 self::$instance = new self();
118 }
119 return self::$instance;
120 }
121
122 /**
123 * Review notice shown after the first live donation (notice A).
124 *
125 * @return void
126 * @since 1.2.0
127 */
128 public function display_review_notice_donation() {
129 if ( ! Helper::current_user_can() ) {
130 return;
131 }
132
133 if ( ! apply_filters( 'suredonation_show_review_notice_donation', true ) ) {
134 return;
135 }
136
137 if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
138 return;
139 }
140
141 \BSF_Admin_Notices::add_notice(
142 [
143 'id' => 'sd-review-donation',
144 'type' => '',
145 'message' => $this->build_notice_markup(
146 esc_html__( 'You received your first donation with SureDonation!', 'suredonation' ),
147 esc_html__( 'That is a big milestone. If SureDonation is helping power your cause, would you take a moment to leave a 5-star review on WordPress.org? It really helps.', 'suredonation' ),
148 esc_url( self::REVIEW_URL ),
149 esc_html__( 'Rate SureDonation', 'suredonation' ),
150 esc_html__( 'Maybe later', 'suredonation' ),
151 esc_html__( 'I already did', 'suredonation' ),
152 WEEK_IN_SECONDS,
153 true
154 ),
155 'repeat-notice-after' => WEEK_IN_SECONDS,
156 // Test-mode takes priority: while the site is in test mode the
157 // "switch to live" warning is more useful than a review ask.
158 'show_if' => $this->is_three_days_elapsed() && $this->has_live_donation() && ! $this->should_show_test_mode_notice(),
159 'display-with-other-notices' => true,
160 ]
161 );
162
163 add_action( 'astra_notice_after_markup_sd-review-donation', [ $this, 'enqueue_notice_response_script' ] );
164 }
165
166 /**
167 * Test-mode notice shown when a gateway is connected but the site is still
168 * in test mode (notice A2).
169 *
170 * Nudges the admin to switch to live mode so real donations can be
171 * accepted. Takes priority over the gateway review notice: there is no
172 * point asking for a review while the site cannot yet take real money.
173 *
174 * @return void
175 * @since 1.3.0
176 */
177 public function display_test_mode_notice() {
178 if ( ! Helper::current_user_can() ) {
179 return;
180 }
181
182 if ( ! apply_filters( 'suredonation_show_test_mode_notice', true ) ) {
183 return;
184 }
185
186 if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
187 return;
188 }
189
190 \BSF_Admin_Notices::add_notice(
191 [
192 'id' => 'sd-test-mode',
193 'type' => '',
194 'message' => $this->build_test_mode_notice_markup(),
195 'repeat-notice-after' => WEEK_IN_SECONDS,
196 'show_if' => $this->should_show_test_mode_notice() && ! $this->should_show_webhook_notice(),
197 'display-with-other-notices' => true,
198 ]
199 );
200
201 add_action( 'astra_notice_after_markup_sd-test-mode', [ $this, 'enqueue_notice_response_script' ] );
202 }
203
204 /**
205 * Stripe webhook-not-configured notice.
206 *
207 * Shown on wp-admin pages when Stripe is connected but its webhook is not
208 * configured for the current mode, so donation/subscription statuses may not
209 * sync. Mirrors the SureForms webhook notice: a standard dismissible core
210 * notice (dismissal is per-page-load and reappears until the webhook is set).
211 * Takes priority over the test-mode banner, which is suppressed while this
212 * shows (matching the React dashboard notice chain).
213 *
214 * Hooked - admin_notices
215 *
216 * @return void
217 * @since 1.3.0
218 */
219 public function display_webhook_notice() {
220 if ( ! Helper::current_user_can() ) {
221 return;
222 }
223
224 if ( ! $this->should_show_webhook_notice() ) {
225 return;
226 }
227
228 // Load the analytics tracker so the configure-click and dismiss are
229 // recorded, matching the other notices (see handle_notice_response()).
230 $this->enqueue_notice_response_script();
231 ?>
232 <div id="sd-webhook-not-configured" class="notice notice-error is-dismissible">
233 <p>
234 <?php
235 printf(
236 /* translators: %1$s: link to configure the Stripe webhook */
237 esc_html__( 'Webhooks keep SureDonation in sync with Stripe by automatically updating donation and subscription data. Please %1$s the webhook.', 'suredonation' ),
238 sprintf(
239 '<a class="sd-notice-cta" href="%1$s">%2$s</a>',
240 esc_url( Payment_Helper::get_settings_url( 'stripe' ) ),
241 esc_html__( 'configure', 'suredonation' )
242 )
243 );
244 ?>
245 </p>
246 </div>
247 <?php
248 }
249
250 /**
251 * Review notice shown when a gateway is configured but there are no live
252 * donations yet (notice B).
253 *
254 * @return void
255 * @since 1.2.0
256 */
257 public function display_review_notice_gateway() {
258 if ( ! Helper::current_user_can() ) {
259 return;
260 }
261
262 if ( ! apply_filters( 'suredonation_show_review_notice_gateway', true ) ) {
263 return;
264 }
265
266 if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
267 return;
268 }
269
270 \BSF_Admin_Notices::add_notice(
271 [
272 'id' => 'sd-review-gateway',
273 'type' => '',
274 'message' => $this->build_notice_markup(
275 esc_html__( 'Your payment gateway is all set up!', 'suredonation' ),
276 esc_html__( 'You have connected a payment gateway and SureDonation is ready to start raising funds. If you are enjoying it so far, a quick 5-star review on WordPress.org would mean a lot.', 'suredonation' ),
277 esc_url( self::REVIEW_URL ),
278 esc_html__( 'Rate SureDonation', 'suredonation' ),
279 esc_html__( 'Maybe later', 'suredonation' ),
280 esc_html__( 'I already did', 'suredonation' ),
281 WEEK_IN_SECONDS,
282 true
283 ),
284 'repeat-notice-after' => WEEK_IN_SECONDS,
285 // In test mode the test-mode notice takes this slot instead,
286 // keeping the notice chain mutually exclusive.
287 'show_if' => $this->is_three_days_elapsed() && ! $this->has_live_donation() && $this->is_gateway_configured() && ! $this->should_show_test_mode_notice(),
288 'display-with-other-notices' => true,
289 ]
290 );
291
292 add_action( 'astra_notice_after_markup_sd-review-gateway', [ $this, 'enqueue_notice_response_script' ] );
293 }
294
295 /**
296 * Setup notice shown instantly when no payment gateway is connected
297 * (notice C). No install-grace applies to this notice.
298 *
299 * @return void
300 * @since 1.2.0
301 */
302 public function display_setup_gateway_notice() {
303 if ( ! Helper::current_user_can() ) {
304 return;
305 }
306
307 if ( ! apply_filters( 'suredonation_show_setup_gateway_notice', true ) ) {
308 return;
309 }
310
311 if ( ! class_exists( 'BSF_Admin_Notices' ) ) {
312 return;
313 }
314
315 \BSF_Admin_Notices::add_notice(
316 [
317 'id' => 'sd-setup-gateway',
318 'type' => '',
319 'message' => $this->build_setup_notice_markup(),
320 'repeat-notice-after' => WEEK_IN_SECONDS,
321 'show_if' => $this->should_show_setup_gateway_notice(),
322 'display-with-other-notices' => true,
323 ]
324 );
325
326 add_action( 'astra_notice_after_markup_sd-setup-gateway', [ $this, 'enqueue_notice_response_script' ] );
327 }
328
329 /**
330 * Enqueue the notice-response analytics script.
331 *
332 * Called via the astra_notice_after_markup_{id} hook so the script only
333 * loads when a SureDonation notice is actually rendered.
334 *
335 * @return void
336 * @since 1.2.0
337 */
338 public function enqueue_notice_response_script() {
339 if ( wp_script_is( 'suredonation-notice-response', 'enqueued' ) ) {
340 return;
341 }
342
343 wp_enqueue_script(
344 'suredonation-notice-response',
345 SUREDONATION_URL . 'assets/js/notice-response.js',
346 [],
347 SUREDONATION_VER,
348 true
349 );
350
351 wp_localize_script(
352 'suredonation-notice-response',
353 'suredonationNoticeResponse',
354 [
355 'ajaxurl' => admin_url( 'admin-ajax.php' ),
356 'nonce' => wp_create_nonce( 'suredonation_notice_response' ),
357 ]
358 );
359 }
360
361 /**
362 * Handle the notice-response AJAX request.
363 *
364 * Validates the request and records the analytics event for the notice
365 * button that was clicked.
366 *
367 * @return void
368 * @since 1.2.0
369 */
370 public function handle_notice_response() {
371 if ( ! check_ajax_referer( 'suredonation_notice_response', 'nonce', false ) ) {
372 wp_send_json_error( [ 'message' => __( 'Invalid nonce.', 'suredonation' ) ], 403 );
373 }
374
375 if ( ! Helper::current_user_can() ) {
376 wp_send_json_error( [ 'message' => __( 'Unauthorized user.', 'suredonation' ) ], 403 );
377 }
378
379 $notice_id = isset( $_POST['notice_id'] ) ? sanitize_text_field( wp_unslash( $_POST['notice_id'] ) ) : '';
380 $button = isset( $_POST['button'] ) ? sanitize_text_field( wp_unslash( $_POST['button'] ) ) : '';
381
382 $valid = [
383 'sd-review-donation' => [
384 'rate_suredonation' => 'review_notice_donation_cta',
385 'maybe_later' => 'review_notice_donation_snooze',
386 'dismissed' => 'review_notice_donation_dismiss',
387 ],
388 'sd-test-mode' => [
389 'switch_to_live' => 'test_mode_notice_cta',
390 'maybe_later' => 'test_mode_notice_snooze',
391 'dismissed' => 'test_mode_notice_dismiss',
392 ],
393 'sd-review-gateway' => [
394 'rate_suredonation' => 'review_notice_gateway_cta',
395 'maybe_later' => 'review_notice_gateway_snooze',
396 'dismissed' => 'review_notice_gateway_dismiss',
397 ],
398 'sd-setup-gateway' => [
399 'configure_gateway' => 'setup_gateway_notice_cta',
400 'maybe_later' => 'setup_gateway_notice_snooze',
401 'dismissed' => 'setup_gateway_notice_dismiss',
402 ],
403 'sd-webhook-not-configured' => [
404 'configure_webhook' => 'webhook_notice_cta',
405 'dismissed' => 'webhook_notice_dismiss',
406 ],
407 ];
408
409 if ( ! isset( $valid[ $notice_id ][ $button ] ) ) {
410 wp_send_json_error( [ 'message' => __( 'Invalid parameters.', 'suredonation' ) ], 400 );
411 }
412
413 $event_name = $valid[ $notice_id ][ $button ];
414
415 $events = Analytics::events();
416 if ( null !== $events ) {
417 $events->track( $event_name, $button );
418 }
419
420 wp_send_json_success();
421 }
422
423 /**
424 * Build the shared HTML markup for admin notices.
425 *
426 * All text parameters must be pre-escaped by the caller (e.g. via
427 * esc_html__()). URL parameters must be pre-escaped via esc_url().
428 *
429 * @param string $heading The notice heading text (pre-escaped).
430 * @param string $message The notice body text (pre-escaped).
431 * @param string $cta_url The primary CTA URL (pre-escaped).
432 * @param string $cta_text The primary CTA button text (pre-escaped).
433 * @param string $snooze_text The snooze button text (pre-escaped).
434 * @param string $dismiss_text The dismiss button text (pre-escaped).
435 * @param int $snooze_duration Snooze duration in seconds for the data-repeat-notice-after attribute.
436 * @param bool $external_cta Whether the CTA opens in a new tab and also dismisses the notice
437 * via the astra-notice-close class. Default false.
438 * @return string The notice HTML markup.
439 * @since 1.2.0
440 */
441 private function build_notice_markup( $heading, $message, $cta_url, $cta_text, $snooze_text, $dismiss_text, $snooze_duration, $external_cta = false ) {
442 $image_path = esc_url( SUREDONATION_URL . 'images/suredonation-icon.svg' );
443 $cta_class = $external_cta ? 'astra-notice-close button-primary' : 'button-primary';
444 $cta_attrs = $external_cta ? ' target="_blank" rel="noopener noreferrer"' : '';
445
446 return sprintf(
447 '<div class="notice-image">
448 <img src="%1$s" class="custom-logo" alt="SureDonation" width="64" height="64" itemprop="logo">
449 </div>
450 <div class="notice-content">
451 <div class="notice-heading">
452 %2$s
453 </div>
454 %3$s<br />
455 <div class="astra-review-notice-container">
456 <a href="%4$s" class="%5$s"%6$s>
457 %7$s
458 </a>
459 <span class="dashicons dashicons-clock" aria-hidden="true"></span>
460 <a href="#" data-repeat-notice-after="%8$s" class="astra-notice-close">
461 %9$s
462 </a>
463 <span class="dashicons dashicons-smiley" aria-hidden="true"></span>
464 <a href="#" class="astra-notice-close">
465 %10$s
466 </a>
467 </div>
468 </div>',
469 $image_path,
470 $heading,
471 $message,
472 $cta_url,
473 esc_attr( $cta_class ),
474 $cta_attrs,
475 $cta_text,
476 $snooze_duration,
477 $snooze_text,
478 $dismiss_text
479 );
480 }
481
482 /**
483 * Build the markup for the "configure a payment gateway" setup notice
484 * (notice C).
485 *
486 * @return string The notice HTML markup.
487 * @since 1.2.0
488 */
489 private function build_setup_notice_markup() {
490 return $this->build_banner_notice_markup(
491 esc_html__( 'Your donation site is almost ready!', 'suredonation' ),
492 esc_html__( 'Connect a payment gateway to start accepting donations. Set up Stripe or PayPal in just a few clicks to go live.', 'suredonation' ),
493 Payment_Helper::get_settings_url( 'stripe' ),
494 esc_html__( 'Configure Payment Gateway', 'suredonation' ),
495 SUREDONATION_URL . 'images/payment-gateway-notice.png'
496 );
497 }
498
499 /**
500 * Build the markup for the "switch to live mode" test-mode notice
501 * (notice A2).
502 *
503 * @return string The notice HTML markup.
504 * @since 1.3.0
505 */
506 private function build_test_mode_notice_markup() {
507 return $this->build_banner_notice_markup(
508 esc_html__( 'SureDonation is in test mode', 'suredonation' ),
509 esc_html__( 'No real payments are being accepted right now. Switch to live mode to start collecting real donations.', 'suredonation' ),
510 Payment_Helper::get_settings_url(),
511 esc_html__( 'Switch to Live Mode', 'suredonation' )
512 );
513 }
514
515 /**
516 * Build the shared banner-notice markup (accent bar, icon, heading, body and
517 * a primary CTA, with an optional right-side illustration).
518 *
519 * Shared by the setup-gateway and test-mode notices; each is scoped by its
520 * wrapper id (#sd-setup-gateway / #sd-test-mode) in setup-gateway-notice.css
521 * so they can carry different accent colors from the same template.
522 *
523 * The text parameters must be pre-escaped by the caller (e.g. via
524 * esc_html__()); the URL and art path are escaped here.
525 *
526 * @param string $title The notice heading (pre-escaped).
527 * @param string $text The notice body text (pre-escaped).
528 * @param string $cta_url The primary CTA URL (raw; escaped here).
529 * @param string $cta_text The primary CTA button text (pre-escaped).
530 * @param string $art Optional right-side illustration URL (raw; escaped
531 * here). When empty, the banner drops the reserved
532 * art space via the --no-art modifier.
533 * @return string The notice HTML markup.
534 * @since 1.3.0
535 */
536 private function build_banner_notice_markup( $title, $text, $cta_url, $cta_text, $art = '' ) {
537 $has_art = '' !== $art;
538 $notice_class = $has_art ? 'sd-setup-notice' : 'sd-setup-notice sd-setup-notice--no-art';
539 $art_markup = $has_art
540 ? sprintf( '<img class="sd-setup-notice__art" src="%s" alt="" width="187" height="128" />', esc_url( $art ) )
541 : '';
542
543 return sprintf(
544 '<div class="%1$s">
545 <div class="sd-setup-notice__main">
546 <img class="sd-setup-notice__icon" src="%2$s" alt="" width="28" height="28" />
547 <div class="sd-setup-notice__body">
548 <h2 class="sd-setup-notice__title">%3$s</h2>
549 <p class="sd-setup-notice__text">%4$s</p>
550 <a href="%5$s" class="button button-primary sd-setup-notice__button">%6$s</a>
551 </div>
552 </div>
553 %7$s
554 </div>',
555 esc_attr( $notice_class ),
556 esc_url( SUREDONATION_URL . 'images/suredonation-icon.svg' ),
557 $title,
558 $text,
559 esc_url( $cta_url ),
560 $cta_text,
561 $art_markup
562 );
563 }
564
565 /**
566 * Enqueue the banner-notice stylesheet.
567 *
568 * @return void
569 * @since 1.2.0
570 */
571 public function enqueue_setup_notice_style() {
572 if ( wp_style_is( 'suredonation-setup-notice', 'enqueued' ) ) {
573 return;
574 }
575
576 wp_enqueue_style(
577 'suredonation-setup-notice',
578 SUREDONATION_URL . 'assets/css/setup-gateway-notice.css',
579 [],
580 SUREDONATION_VER
581 );
582 }
583
584 /**
585 * Enqueue the banner-notice stylesheet from the admin <head> when either
586 * banner notice (setup-gateway or test-mode) is eligible to show.
587 *
588 * Hooked on admin_enqueue_scripts (which runs before admin_head) and gated
589 * by the same conditions as the notices themselves, so the stylesheet is in
590 * the page head before the banner paints. This avoids the flash of unstyled
591 * content that occurred when the CSS was enqueued on the notice's
592 * after-markup hook (which fires at admin_notices priority 30, after styles
593 * have already been printed).
594 *
595 * @return void
596 * @since 1.2.0
597 */
598 public function maybe_enqueue_banner_notice_style() {
599 if ( ! Helper::current_user_can() ) {
600 return;
601 }
602
603 $setup_eligible = apply_filters( 'suredonation_show_setup_gateway_notice', true ) && $this->should_show_setup_gateway_notice();
604 $test_eligible = apply_filters( 'suredonation_show_test_mode_notice', true ) && $this->should_show_test_mode_notice();
605
606 if ( ! $setup_eligible && ! $test_eligible ) {
607 return;
608 }
609
610 $this->enqueue_setup_notice_style();
611 }
612
613 /**
614 * Whether the test-mode notice is eligible to show: a gateway is connected
615 * (in any mode) but the site is currently running in test mode. This fires
616 * regardless of past donations — a site switched back to test mode still
617 * needs the "switch to live" nudge — and takes priority over the review
618 * notices, which are suppressed while it is showing.
619 *
620 * @return bool
621 * @since 1.3.0
622 */
623 private function should_show_test_mode_notice() {
624 return $this->is_gateway_configured()
625 && 'test' === Payment_Helper::get_payment_mode();
626 }
627
628 /**
629 * Whether the webhook-not-configured notice is eligible to show: Stripe is
630 * connected but its webhook is not configured for the current mode.
631 *
632 * @return bool
633 * @since 1.3.0
634 */
635 private function should_show_webhook_notice() {
636 return Stripe_Helper::is_stripe_connected()
637 && ! Stripe_Helper::is_webhook_configured();
638 }
639
640 /**
641 * Whether the setup-gateway notice is eligible to show: no gateway is
642 * connected and no live donation has been recorded.
643 *
644 * @return bool
645 * @since 1.3.0
646 */
647 private function should_show_setup_gateway_notice() {
648 return ! $this->has_live_donation() && ! $this->is_gateway_configured();
649 }
650
651 /**
652 * Whether the 3-day install grace has elapsed.
653 *
654 * @return bool
655 * @since 1.2.0
656 */
657 private function is_three_days_elapsed() {
658 return ( time() - $this->get_install_time() ) >= self::REVIEW_NOTICE_DELAY;
659 }
660
661 /**
662 * Get (creating if missing) the plugin install timestamp.
663 *
664 * The activation hook seeds this on fresh installs; this getter back-fills
665 * it for sites that were already active before the option existed, so their
666 * grace period starts from the first admin pageload after the update.
667 *
668 * @return int Unix timestamp.
669 * @since 1.2.0
670 */
671 private function get_install_time() {
672 $install_time = Helper::get_integer_value( get_option( 'suredonation_install_time', 0 ) );
673
674 if ( ! $install_time ) {
675 $install_time = time();
676 update_option( 'suredonation_install_time', $install_time );
677 }
678
679 return $install_time;
680 }
681
682 /**
683 * Whether the site has at least one completed, live-mode donation.
684 *
685 * @return bool
686 * @since 1.2.0
687 */
688 private function has_live_donation() {
689 if ( null === $this->has_live_donation ) {
690 // Persist a monotonic flag: once the site has recorded a completed
691 // live donation it stays "true" for this notice's purpose, so we
692 // stop running COUNT(*) on every admin pageload once it is set.
693 if ( get_option( 'suredonation_has_live_donation' ) ) {
694 $this->has_live_donation = true;
695 } else {
696 $this->has_live_donation = Donations::count_live_completed() >= 1;
697
698 if ( $this->has_live_donation ) {
699 update_option( 'suredonation_has_live_donation', 1, false );
700 }
701 }
702 }
703
704 return $this->has_live_donation;
705 }
706
707 /**
708 * Whether a payment gateway (Stripe or PayPal) is connected, in any mode.
709 *
710 * @return bool
711 * @since 1.2.0
712 */
713 private function is_gateway_configured() {
714 if ( null === $this->gateway_configured ) {
715 // "Configured" means connected in any mode; delegated to the shared
716 // Payment_Helper check (memoized here so repeated notice-chain reads
717 // only resolve it once per request).
718 $this->gateway_configured = Payment_Helper::is_any_gateway_connected();
719 }
720
721 return $this->gateway_configured;
722 }
723 }
724