PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.11.3
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.11.3
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
charitable / includes / square / admin / class-charitable-square-webhookshealthcheck.php

class-charitable-square-webhookshealthcheck.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.11.3, at includes/square/admin/class-charitable-square-webhookshealthcheck.php

253 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The class responsible for monitoring Square webhooks health.
4 *
5 * @package Charitable Square/Classes/Charitable_Square_WebhooksHealthCheck
6 * @author David Bisset
7 * @copyright Copyright (c) 2021-2022, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.1.0
10 * @version 1.3.0
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Square_WebhooksHealthCheck' ) ) :
19
20 /**
21 * Webhooks Health Check class.
22 *
23 * @since 1.8.7
24 */
25 class Charitable_Square_WebhooksHealthCheck {
26
27 /**
28 * Endpoint status option name.
29 *
30 * @since 1.8.7
31 */
32 public const ENDPOINT_OPTION = 'charitable_square_webhooks_endpoint_status';
33
34 /**
35 * Webhook health check cron hook.
36 *
37 * @since 1.8.7
38 */
39 private const CRON_HOOK = 'charitable_square_webhooks_health_check';
40
41 /**
42 * Status OK key.
43 *
44 * @since 1.8.7
45 */
46 public const STATUS_OK = 'ok';
47
48 /**
49 * Status error key.
50 *
51 * @since 1.8.7
52 */
53 private const STATUS_ERROR = 'error';
54
55 /**
56 * Admin notice ID.
57 *
58 * @since 1.8.7
59 */
60 private const NOTICE_ID = 'charitable_square_webhooks_site_health';
61
62 /**
63 * Initialization.
64 *
65 * @since 1.8.7
66 */
67 public function init() {
68 $this->hooks();
69 return $this;
70 }
71
72 /**
73 * Register hooks.
74 *
75 * @since 1.8.7
76 */
77 private function hooks() {
78 add_action( 'admin_notices', array( $this, 'admin_notice' ) );
79 add_action( self::CRON_HOOK, array( $this, 'process_webhooks_status_check' ) );
80 add_action( 'init', array( $this, 'maybe_schedule_check' ) );
81
82 // Hook into the charitable_uninstall action for cleanup.
83 add_action( 'charitable_uninstall', array( $this, 'uninstall' ) );
84 }
85
86 /**
87 * Schedule webhook health check using WP Cron.
88 *
89 * @since 1.8.7
90 */
91 public function maybe_schedule_check() {
92 // Skip if webhook health check should be disabled.
93 if ( apply_filters( 'charitable_square_webhooks_health_check_disable', false ) ) {
94 return;
95 }
96
97 // Skip if not configured or already scheduled.
98 if ( ! Charitable_Square_Helpers::is_webhook_enabled() || wp_next_scheduled( self::CRON_HOOK ) ) {
99 return;
100 }
101
102 // Schedule health check to run hourly.
103 wp_schedule_event( time(), 'hourly', self::CRON_HOOK );
104 }
105
106 /**
107 * Clean up scheduled events and options on uninstall.
108 *
109 * @since 1.8.7
110 */
111 public function uninstall() {
112 // Clear cron event.
113 $timestamp = wp_next_scheduled( self::CRON_HOOK );
114 if ( $timestamp ) {
115 wp_unschedule_event( $timestamp, self::CRON_HOOK );
116 }
117
118 // Clean up options.
119 delete_option( self::ENDPOINT_OPTION );
120 }
121
122 /**
123 * Process webhook health check status.
124 *
125 * @since 1.8.7
126 */
127 public function process_webhooks_status_check() {
128 // Skip if webhooks are not enabled.
129 if ( ! Charitable_Square_Helpers::is_webhook_enabled() ) {
130 return;
131 }
132
133 $last_payment = $this->get_last_square_payment();
134
135 // If no Square payments found, remove status option to avoid false positives.
136 if ( empty( $last_payment ) ) {
137 delete_option( self::ENDPOINT_OPTION );
138 return;
139 }
140
141 // If payment is processed but webhook didn't update status within 15 minutes,
142 // we likely have a webhook configuration issue.
143 if (
144 isset( $last_payment['status'] ) &&
145 $last_payment['status'] === 'processed' &&
146 time() > ( strtotime( $last_payment['date_created_gmt'] ) + ( 15 * MINUTE_IN_SECONDS ) )
147 ) {
148 Charitable_Square_Helpers::reset_webhook_configuration();
149 self::save_status( self::STATUS_ERROR );
150 return;
151 }
152
153 // Everything looks good.
154 self::save_status( self::STATUS_OK );
155 }
156
157 /**
158 * Get the most recent Square payment.
159 *
160 * @since 1.8.7
161 *
162 * @return array Payment data or empty array if none found.
163 */
164 private function get_last_square_payment() {
165 global $wpdb;
166
167 // Find the last 'donation' custom post type with a meta_key of 'gateway' set to 'square'.
168 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQLPlaceholders.UnnecessaryPrepare
169 $payments = $wpdb->get_results(
170 "SELECT * FROM {$wpdb->posts} p
171 INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
172 WHERE p.post_type = 'donation'
173 AND p.post_status = 'charitable-completed'
174 AND pm.meta_key = 'donation_gateway'
175 AND pm.meta_value = 'square'
176 ORDER BY ID DESC
177 LIMIT 1",
178 ARRAY_A
179 );
180
181 return $payments ? $payments[0] : array();
182 }
183
184 /**
185 * Display admin notice about webhook issues.
186 *
187 * @since 1.8.7
188 */
189 public function admin_notice() {
190 // Only show notice if Square is configured.
191 if ( ! Charitable_Square_Helpers::is_square_configured() ) {
192 return;
193 }
194
195 // Only show if webhooks are enabled but not properly configured.
196 if (
197 ! Charitable_Square_Helpers::is_webhook_enabled() ||
198 Charitable_Square_Helpers::is_webhook_configured()
199 ) {
200 return;
201 }
202
203 // Only show if we've detected a webhook error.
204 if ( get_option( self::ENDPOINT_OPTION, self::STATUS_OK ) === self::STATUS_OK ) {
205 return;
206 }
207
208 // Only show if there are Square payments.
209 if ( empty( $this->get_last_square_payment() ) ) {
210 return;
211 }
212
213 // Display the admin notice.
214 $notice = sprintf(
215 wp_kses(
216 /* translators: %1$s: URL to the webhooks documentation */
217 __( 'Looks like you have a problem with your webhooks configuration. Please check and confirm that you\'ve configured the WPCharitable webhooks in your Square account. This notice will disappear automatically when a new Square request comes in. See our <a href="%1$s" rel="nofollow noopener" target="_blank">documentation</a> for more information.', 'charitable' ),
218 array(
219 'a' => array(
220 'href' => array(),
221 'target' => array(),
222 'rel' => array(),
223 ),
224 )
225 ),
226 esc_url( charitable_utm_link( 'https://wpcharitable.com/docs/setting-up-square-webhooks/', 'Admin', 'Square Webhooks not active' ) )
227 );
228
229 // Use WordPress admin notices.
230 ?>
231 <div class="notice notice-error is-dismissible" data-notice-id="<?php echo esc_attr( self::NOTICE_ID ); ?>">
232 <p><?php echo wp_kses_post( $notice ); ?></p>
233 </div>
234 <?php
235 }
236
237 /**
238 * Save webhooks status.
239 *
240 * @since 1.8.7
241 *
242 * @param string $value Status value.
243 */
244 public static function save_status( string $value ) {
245 if ( ! in_array( $value, array( self::STATUS_OK, self::STATUS_ERROR ), true ) ) {
246 return;
247 }
248
249 update_option( self::ENDPOINT_OPTION, $value );
250 }
251 }
252
253 endif;