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 / admin / security / class-charitable-security-settings.php

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

313 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Charitable Security Settings.
4 *
5 * @package Charitable/Classes/Charitable_Security_Settings
6 * @author David Bisset
7 * @copyright Copyright (c) 2023, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.8.9
10 * @version 1.8.9
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Security_Settings' ) ) :
19
20 /**
21 * Charitable_Security_Settings
22 *
23 * @since 1.8.0
24 */
25 class Charitable_Security_Settings {
26
27 /**
28 * The single instance of this class.
29 *
30 * @var Charitable_Security_Settings|null
31 */
32 private static $instance = null;
33
34 /**
35 * Loaded modules.
36 *
37 * @var array
38 */
39 private $loaded_modules = array();
40
41 /**
42 * Create object instance.
43 *
44 * @since 1.8.9
45 */
46 private function __construct() {
47 // If spam blocker is active, don't load core security features.
48 if ( defined( 'CHARITABLE_SPAMBLOCKER_FEATURE_PLUGIN' ) ) {
49 return;
50 }
51
52 $this->load_modules();
53 $this->setup_notices();
54 }
55
56 /**
57 * Returns and/or create the single instance of this class.
58 *
59 * @since 1.8.0
60 *
61 * @return Charitable_Security_Settings
62 */
63 public static function get_instance() {
64 if ( is_null( self::$instance ) ) {
65 self::$instance = new self();
66 }
67
68 return self::$instance;
69 }
70
71 /**
72 * Load all security modules.
73 *
74 * @since 1.8.0
75 *
76 * @return void
77 */
78 private function load_modules() {
79 $modules_dir = charitable()->get_path( 'includes', true ) . 'admin/security/modules/';
80
81 if ( ! is_dir( $modules_dir ) ) {
82 return;
83 }
84
85 // Load CAPTCHA modules.
86 $captcha_modules = array(
87 'captcha/class-charitable-captcha.php',
88 'captcha/class-charitable-captcha-google-recaptcha-v2.php',
89 'captcha/class-charitable-captcha-google-recaptcha-v3.php',
90 'captcha/class-charitable-captcha-hcaptcha.php',
91 'captcha/class-charitable-captcha-cloudflare-turnstile.php',
92 );
93
94 foreach ( $captcha_modules as $module_file ) {
95 $file_path = $modules_dir . $module_file;
96 if ( file_exists( $file_path ) ) {
97 require_once $file_path;
98 }
99 }
100
101 // Instantiate modules that have get_settings method.
102 $module_classes = array(
103 'Charitable_Captcha',
104 'Charitable_Captcha_Google_ReCAPTCHA_V2',
105 'Charitable_Captcha_Google_ReCAPTCHA_V3',
106 'Charitable_Captcha_HCaptcha',
107 'Charitable_Captcha_Cloudflare_Turnstile',
108 );
109
110 foreach ( $module_classes as $class_name ) {
111 if ( class_exists( $class_name ) && method_exists( $class_name, 'get_settings' ) ) {
112 $instance = new $class_name();
113 $this->loaded_modules[] = $instance;
114 if ( charitable_is_debug() && charitable_is_debug( 'security' ) && method_exists( $instance, 'is_active' ) ) {
115 error_log( '[Charitable Security] Loaded module: ' . $class_name . ' | Active: ' . ( $instance->is_active() ? 'YES' : 'NO' ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
116 }
117 }
118 }
119 }
120
121 /**
122 * Add security settings group.
123 *
124 * @since 1.8.0
125 *
126 * @param array $groups The setting groups.
127 * @return array
128 */
129 public function add_security_settings_group( $groups ) {
130 // If spam blocker is active, don't add core security group.
131 if ( defined( 'CHARITABLE_SPAMBLOCKER_FEATURE_PLUGIN' ) ) {
132 return $groups;
133 }
134
135 if ( ! is_array( $groups ) ) {
136 $groups = array();
137 }
138 if ( ! in_array( 'security', $groups, true ) ) {
139 $groups[] = 'security';
140 }
141 return $groups;
142 }
143
144 /**
145 * Add settings from all modules.
146 *
147 * @since 1.8.0
148 *
149 * @param array $settings Existing settings.
150 * @return array Modified settings
151 */
152 public function add_settings( $settings ) {
153 // If spam blocker is active, don't add core security settings.
154 if ( defined( 'CHARITABLE_SPAMBLOCKER_FEATURE_PLUGIN' ) ) {
155 return $settings;
156 }
157
158 if ( empty( $this->loaded_modules ) ) {
159 return $settings;
160 }
161
162 foreach ( $this->loaded_modules as $module ) {
163 if ( method_exists( $module, 'get_settings' ) && is_callable( array( $module, 'get_settings' ) ) ) {
164 $module_settings = $module->get_settings();
165 if ( is_array( $module_settings ) ) {
166 $settings = array_merge(
167 $settings,
168 $module_settings
169 );
170 }
171 }
172 }
173
174 return $settings;
175 }
176
177 /**
178 * Set up admin notices for security recommendations.
179 *
180 * @since 1.8.9
181 *
182 * @return void
183 */
184 private function setup_notices() {
185 if ( ! is_admin() ) {
186 return;
187 }
188
189 add_action( 'admin_notices', array( $this, 'render_security_recommendation_notice' ) );
190 add_action( 'charitable_dismiss_notice', array( $this, 'dismiss_security_recommendation_notice' ), 10, 1 );
191 add_filter( 'charitable_save_settings', array( $this, 'maybe_reset_security_notice_dismissal' ), 10, 2 );
192 }
193
194 /**
195 * Render security recommendation notice.
196 *
197 * Shows a notice when CAPTCHA is disabled and there's at least one campaign.
198 *
199 * @since 1.8.9
200 *
201 * @return void
202 */
203 public function render_security_recommendation_notice() {
204 if ( ! function_exists( 'get_current_screen' ) ) {
205 return;
206 }
207
208 // If spam blocker is active, don't show notice.
209 if ( defined( 'CHARITABLE_SPAMBLOCKER_FEATURE_PLUGIN' ) ) {
210 return;
211 }
212
213 $screen = get_current_screen();
214 if ( is_null( $screen ) ) {
215 return;
216 }
217
218 // Only show on specific Charitable pages.
219 $allowed_screens = array(
220 'campaign',
221 'edit-campaign',
222 'donation',
223 'edit-donation',
224 'charitable_page_charitable-settings',
225 'charitable_page_charitable-tools',
226 );
227
228 if ( ! in_array( $screen->id, $allowed_screens, true ) ) {
229 return;
230 }
231
232 // Check if at least one campaign exists.
233 $count_campaigns = wp_count_posts( 'campaign' );
234 $total_campaigns = isset( $count_campaigns->publish ) ? intval( $count_campaigns->publish ) : 0;
235 if ( $total_campaigns < 1 ) {
236 return;
237 }
238
239 // Check if CAPTCHA is disabled.
240 $captcha_provider = charitable_get_option( 'captcha_provider', 'disabled' );
241
242 $captcha_disabled = 'disabled' === $captcha_provider;
243
244 if ( ! $captcha_disabled ) {
245 return;
246 }
247
248 // Check if notice was dismissed.
249 $slug = 'security-recommendation';
250 $dismissed = get_transient( 'charitable_' . $slug . '_banner' );
251 if ( $dismissed ) {
252 return;
253 }
254
255 // Build the notice message.
256 $security_url = admin_url( 'admin.php?page=charitable-settings&tab=security' );
257 $message = sprintf(
258 /* translators: %s: link to security settings */
259 __( 'Charitable recommends enabling CAPTCHA to protect your donation forms against bots and spam. <a href="%s">Configure security settings</a>.', 'charitable' ),
260 esc_url( $security_url )
261 );
262
263 $key = 'security-recommendation';
264 charitable_get_admin_notices()->render_notice( $message, 'warning', true, $key, false );
265 }
266
267 /**
268 * Dismiss security recommendation notice.
269 *
270 * @since 1.8.9
271 *
272 * @param array $postdata POST data from dismissal request.
273 * @return void
274 */
275 public function dismiss_security_recommendation_notice( $postdata ) {
276 if ( empty( $postdata['notice'] ) || 'security-recommendation' !== $postdata['notice'] ) {
277 return;
278 }
279
280 $slug = 'security-recommendation';
281 set_transient( 'charitable_' . $slug . '_banner', 1, 0 );
282 }
283
284 /**
285 * Reset security notice dismissal if settings are saved with CAPTCHA disabled.
286 *
287 * @since 1.8.9
288 *
289 * @param array $values Current settings values.
290 * @param array $new_values New settings values being saved.
291 * @return array
292 */
293 public function maybe_reset_security_notice_dismissal( $values, $new_values ) {
294 // Only process if security settings are being saved.
295 if ( ! isset( $new_values['captcha_provider'] ) ) {
296 return $values;
297 }
298
299 // Get the final values (after merge with old values).
300 $captcha_provider = isset( $new_values['captcha_provider'] ) ? $new_values['captcha_provider'] : charitable_get_option( 'captcha_provider', 'disabled' );
301
302 // If CAPTCHA is disabled, clear the dismissal so notice appears again.
303 if ( 'disabled' === $captcha_provider ) {
304 delete_transient( 'charitable_security-recommendation_banner' );
305 }
306
307 return $values;
308 }
309 }
310
311 endif;
312
313