PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.12.2
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.12.2
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 / shortcodes / class-charitable-mini-widget.php

class-charitable-mini-widget.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.12.2, at includes/shortcodes/class-charitable-mini-widget.php

346 lines 11.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Mini Donation Widget renderer.
4 *
5 * Ported from Charitable Pro 1.8.16 (class-charitable-mini-widget-shortcode.php)
6 * for use by the campaign-hero block. Not registered as a public shortcode in Lite;
7 * exposed via Charitable_Mini_Widget::render( $args ) so it can be consumed by any
8 * future shortcode handler or Gutenberg block.
9 *
10 * @package Charitable/Shortcodes/Mini_Widget
11 * @author David Bisset
12 * @copyright Copyright (c) 2026, WP Charitable LLC
13 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
14 * @since 1.8.11.1
15 */
16
17 // Exit if accessed directly.
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 if ( ! class_exists( 'Charitable_Mini_Widget' ) ) :
23
24 /**
25 * Charitable_Mini_Widget class.
26 *
27 * @since 1.8.11.1
28 */
29 class Charitable_Mini_Widget {
30
31 /**
32 * Whether the widget's CSS/JS assets have already been enqueued for this request.
33 *
34 * @since 1.8.11.1
35 * @var bool
36 */
37 private static $assets_enqueued = false;
38
39 /**
40 * Enqueue the widget's CSS and JS. Idempotent — safe to call multiple times.
41 *
42 * @since 1.8.11.1
43 *
44 * @return void
45 */
46 public static function enqueue_assets() {
47 if ( self::$assets_enqueued ) {
48 return;
49 }
50
51 $assets_dir = charitable()->get_path( 'assets', false );
52 $min = charitable_get_min_suffix();
53 $version = charitable()->get_version();
54
55 wp_enqueue_style(
56 'charitable-mini-widget',
57 $assets_dir . 'css/charitable-mini-widget' . $min . '.css',
58 array(),
59 $version
60 );
61
62 wp_enqueue_script(
63 'charitable-mini-widget',
64 $assets_dir . 'js/frontend/charitable-mini-widget' . $min . '.js',
65 array( 'jquery' ),
66 $version,
67 true
68 );
69
70 wp_localize_script(
71 'charitable-mini-widget',
72 'wpCharitableMiniWidget',
73 array(
74 'i18n' => array(
75 'other' => __( 'Other', 'charitable' ),
76 'enterAmount' => __( 'Enter amount', 'charitable' ),
77 'enterAmountMonthly' => __( 'Enter monthly amount', 'charitable' ),
78 'minError' => __( 'Minimum amount is %s', 'charitable' ),
79 'maxError' => __( 'Maximum amount is %s', 'charitable' ),
80 ),
81 )
82 );
83
84 self::$assets_enqueued = true;
85 }
86
87 /**
88 * Render the mini donation widget.
89 *
90 * Accepts the same argument keys as the Pro [charitable_mini_widget] shortcode
91 * (campaign_id, cta_mode, cta_label, recurring, accent_color, etc.) plus dynamic
92 * monthly_impact_X / onetime_impact_X impact keys.
93 *
94 * @since 1.8.11.1
95 *
96 * @param array $args Render arguments. See $defaults below for accepted keys.
97 * @return string The rendered HTML, or an empty string on failure.
98 */
99 public static function render( $args = array() ) {
100 // Extract dynamic impact keys before the defaults merge strips them.
101 $impact_map = array(
102 'monthly' => array(),
103 'onetime' => array(),
104 );
105
106 if ( is_array( $args ) ) {
107 foreach ( $args as $key => $value ) {
108 if ( preg_match( '/^(monthly|onetime)_impact_(\d+(?:\.\d+)?)$/', $key, $matches ) ) {
109 $tab = $matches[1];
110 $amount = $matches[2];
111 $impact_map[ $tab ][ $amount ] = wp_kses( $value, array() );
112 }
113 }
114 }
115
116 // Parse static attributes.
117 $defaults = array(
118 'campaign_id' => '',
119 'cta_mode' => 'redirect',
120 'cta_label' => __( 'Donate Now', 'charitable' ),
121 'recurring' => 'false',
122 'show_currency' => 'false',
123 'min_amount' => '',
124 'max_amount' => '',
125 'accent_color' => '',
126 'class' => '',
127 'monthly_amounts' => '',
128 'monthly_default' => '',
129 'monthly_show_other' => 'false',
130 'onetime_amounts' => '',
131 'onetime_default' => '',
132 'onetime_show_other' => 'false',
133 'widget_align' => 'center',
134 'widget_width' => '',
135 'impact_align' => '',
136 'monthly_label' => '',
137 'give_once_label' => '',
138 'widget_title' => '',
139 );
140
141 $args = wp_parse_args( $args, $defaults );
142
143 // Validate campaign_id.
144 $campaign_id = absint( $args['campaign_id'] );
145 if ( ! $campaign_id ) {
146 if ( current_user_can( 'edit_posts' ) ) {
147 return '<p><em>' . esc_html__( 'Mini Donation Widget: campaign_id is required.', 'charitable' ) . '</em></p>';
148 }
149 return '';
150 }
151
152 $campaign = get_post( $campaign_id );
153 if ( ! $campaign || 'campaign' !== $campaign->post_type ) {
154 if ( current_user_can( 'edit_posts' ) ) {
155 return '<p><em>' . esc_html__( 'Mini Donation Widget: campaign not found.', 'charitable' ) . '</em></p>';
156 }
157 return '';
158 }
159
160 // Page-builder preview detection (WPBakery / Divi).
161 $is_wpbakery = defined( 'VCV_VERSION' ) ||
162 ( isset( $_GET['vc_action'] ) && 'vc_inline' === $_GET['vc_action'] ) || // phpcs:ignore WordPress.Security.NonceVerification.Recommended
163 ( function_exists( 'vc_is_inline' ) && vc_is_inline() );
164 $is_divi = defined( 'DOING_AJAX' ) && DOING_AJAX && defined( 'ET_BUILDER_VERSION' );
165 $is_preview = $is_wpbakery || $is_divi;
166
167 if ( $is_preview ) {
168 return '<div class="charitable-mini-widget charitable-mini-widget--preview" style="pointer-events:none;opacity:.7;border:2px dashed #ccc;padding:20px;text-align:center;">' .
169 '<p style="margin:0;">' . esc_html__( 'Mini Donation Widget — preview only', 'charitable' ) . '</p>' .
170 '</div>';
171 }
172
173 // Auto-enqueue assets on first render.
174 self::enqueue_assets();
175
176 // Normalize booleans.
177 $recurring = self::parse_bool( $args['recurring'] );
178 $show_currency = self::parse_bool( $args['show_currency'] );
179 $show_other_m = self::parse_bool( $args['monthly_show_other'] );
180 $show_other_o = self::parse_bool( $args['onetime_show_other'] );
181
182 // Recurring donations addon detection.
183 $recurring_active = $recurring && class_exists( 'Charitable_Recurring' );
184
185 // Verify the selected campaign has recurring enabled at the campaign level.
186 if ( $recurring_active ) {
187 $campaign_recurring_mode = get_post_meta( $campaign_id, '_campaign_recurring_donation_mode', true );
188 if ( empty( $campaign_recurring_mode ) || 'disabled' === $campaign_recurring_mode ) {
189 $recurring_active = false;
190 }
191 }
192
193 // Parse amounts.
194 $monthly_amounts = self::parse_amounts( $args['monthly_amounts'] );
195 $onetime_amounts = self::parse_amounts( $args['onetime_amounts'] );
196
197 // Fallback: if no onetime amounts but monthly amounts exist, share them.
198 if ( empty( $onetime_amounts ) && ! empty( $monthly_amounts ) ) {
199 $onetime_amounts = $monthly_amounts;
200 }
201
202 $monthly_default = self::resolve_default( $args['monthly_default'], $monthly_amounts );
203 $onetime_default = self::resolve_default( $args['onetime_default'], $onetime_amounts );
204
205 $min_amount = '' !== $args['min_amount'] ? floatval( $args['min_amount'] ) : null;
206 $max_amount = '' !== $args['max_amount'] ? floatval( $args['max_amount'] ) : null;
207
208 $accent_color = '' !== $args['accent_color'] ? sanitize_hex_color( $args['accent_color'] ) : '';
209
210 $cta_mode = in_array( $args['cta_mode'], array( 'redirect', 'modal' ), true ) ? $args['cta_mode'] : 'redirect';
211
212 // In modal mode, ensure Stripe assets get enqueued if they're registered elsewhere.
213 if ( 'modal' === $cta_mode ) {
214 if ( wp_script_is( 'charitable-stripe', 'registered' ) ) {
215 wp_enqueue_script( 'charitable-stripe' );
216 }
217 if ( wp_script_is( 'stripe-v3', 'registered' ) ) {
218 wp_enqueue_script( 'stripe-v3' );
219 }
220 }
221
222 $campaign_url = ( 'redirect' === $cta_mode )
223 ? charitable_get_permalink( 'campaign_donation', array( 'campaign_id' => $campaign_id ) )
224 : '';
225
226 $currency_code = charitable_get_currency();
227 $currency_symbol = charitable_get_currency_helper()->get_currency_symbol();
228 $currency_pos = charitable_get_option( 'currency_format', 'left' );
229
230 $widget_align = in_array( $args['widget_align'], array( 'left', 'center', 'right' ), true )
231 ? $args['widget_align'] : 'left';
232 $widget_width = sanitize_text_field( $args['widget_width'] );
233
234 $view_args = array(
235 'campaign_id' => $campaign_id,
236 'cta_mode' => $cta_mode,
237 'cta_label' => sanitize_text_field( $args['cta_label'] ),
238 'recurring_active' => $recurring_active,
239 'show_currency' => $show_currency,
240 'min_amount' => $min_amount,
241 'max_amount' => $max_amount,
242 'accent_color' => $accent_color,
243 'extra_class' => implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['class'] ) ) ),
244 'monthly_amounts' => $monthly_amounts,
245 'monthly_default' => $monthly_default,
246 'monthly_show_other' => $show_other_m,
247 'monthly_impact' => $impact_map['monthly'],
248 'onetime_amounts' => $onetime_amounts,
249 'onetime_default' => $onetime_default,
250 'onetime_show_other' => $show_other_o,
251 'onetime_impact' => $impact_map['onetime'],
252 'campaign_url' => $campaign_url,
253 'currency_code' => $currency_code,
254 'currency_symbol' => $currency_symbol,
255 'currency_pos' => $currency_pos,
256 'widget_align' => $widget_align,
257 'widget_width' => $widget_width,
258 'impact_align' => in_array( $args['impact_align'], array( 'left', 'center', 'right' ), true ) ? $args['impact_align'] : '',
259 'monthly_label' => sanitize_text_field( $args['monthly_label'] ),
260 'give_once_label' => sanitize_text_field( $args['give_once_label'] ),
261 'widget_title' => sanitize_text_field( $args['widget_title'] ),
262 );
263
264 ob_start();
265
266 charitable_template( 'shortcodes/mini-widget.php', $view_args );
267
268 /**
269 * Filter the output of the mini donation widget.
270 *
271 * @since 1.8.11.1
272 *
273 * @param string $output The rendered HTML.
274 * @param array $args The render arguments.
275 */
276 return apply_filters( 'charitable_mini_widget_output', ob_get_clean(), $args );
277 }
278
279 /**
280 * Parse a comma-delimited amounts string into an array of floats.
281 *
282 * @since 1.8.11.1
283 *
284 * @param string $amounts_string Comma-delimited amounts, e.g. "10,20,40,100".
285 * @return float[]
286 */
287 public static function parse_amounts( $amounts_string ) {
288 if ( empty( $amounts_string ) ) {
289 return array();
290 }
291
292 $parts = explode( ',', $amounts_string );
293 $out = array();
294
295 foreach ( $parts as $part ) {
296 $val = floatval( trim( $part ) );
297 if ( $val > 0 ) {
298 $out[] = $val;
299 }
300 }
301
302 return $out;
303 }
304
305 /**
306 * Resolve a default amount against an amounts list. Falls back to the first amount.
307 *
308 * @since 1.8.11.1
309 *
310 * @param mixed $default Default amount (0 = no explicit default).
311 * @param float[] $amounts The amounts list.
312 * @return float 0 if list is empty, otherwise a valid amount from the list.
313 */
314 public static function resolve_default( $default, $amounts ) {
315 if ( empty( $amounts ) ) {
316 return 0;
317 }
318
319 $default = floatval( $default );
320
321 if ( $default > 0 && in_array( $default, $amounts, true ) ) {
322 return $default;
323 }
324
325 return $amounts[0];
326 }
327
328 /**
329 * Parse a boolean-ish string or value.
330 *
331 * @since 1.8.11.1
332 *
333 * @param mixed $value The value to parse.
334 * @return bool
335 */
336 public static function parse_bool( $value ) {
337 if ( is_bool( $value ) ) {
338 return $value;
339 }
340
341 return in_array( strtolower( (string) $value ), array( 'true', '1', 'yes' ), true );
342 }
343 }
344
345 endif;
346