PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.6.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.6.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 / form-editor / assets.php

assets.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management 1.6.0, at inc/form-editor/assets.php

595 lines 22.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Form Editor Assets.
4 *
5 * Handles asset registration and enqueuing for the donation form editor.
6 *
7 * @package SureDonation
8 * @since 0.0.1
9 */
10
11 namespace SureDonation\Inc\FormEditor;
12
13 use SureDonation\Inc\Helper;
14 use SureDonation\Inc\Payments\Payment_Helper;
15 use SureDonation\Inc\Traits\Get_Instance;
16 use SureDonation\Inc\Post_Types\Donation_Form;
17
18 // Exit if accessed directly.
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 /**
24 * Assets Class.
25 *
26 * @since 0.0.1
27 */
28 class Assets {
29 use Get_Instance;
30
31 /**
32 * Constructor.
33 *
34 * @since 0.0.1
35 */
36 public function __construct() {
37 add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_editor_assets' ] );
38 // The stylesheet is registered separately, on the hook WordPress replays
39 // inside the editor canvas iframe. See enqueue_editor_styles().
40 add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_styles' ] );
41 add_action( 'init', [ $this, 'register_form_settings_meta' ] );
42 add_action( 'init', [ $this, 'register_email_notifications_meta' ] );
43 }
44
45 /**
46 * Enqueue editor assets for the donation form editor.
47 *
48 * @return void
49 * @since 0.0.1
50 */
51 public function enqueue_editor_assets() {
52 // Only load on donation form editor.
53 if ( ! $this->is_form_editor_screen() ) {
54 return;
55 }
56
57 // Core's bundled CodeMirror (CSS mode) backs the Custom CSS tab in the form
58 // settings dialog. Returns false when the user turned syntax highlighting
59 // off in their profile; the tab falls back to a plain textarea then.
60 wp_enqueue_code_editor( [ 'type' => 'text/css' ] );
61 wp_enqueue_script( 'wp-theme-plugin-editor' );
62 wp_enqueue_style( 'wp-codemirror' );
63
64 $editor_asset = $this->get_editor_asset();
65
66 // Editor plugin JS.
67 wp_enqueue_script(
68 'suredonation-form-editor',
69 SUREDONATION_URL . 'assets/build/editor/editor.js',
70 $editor_asset['dependencies'],
71 $editor_asset['version'],
72 true
73 );
74
75 // Editor styles are enqueued from enqueue_editor_styles(), not here.
76
77 // The OttoKit embed script (defines window.SureTriggers) is NOT enqueued
78 // here — it is remote executable JS and would load on every form-editor
79 // session even when OttoKit is absent. It is lazy-injected from the
80 // OttoKit tab only when the builder opens (see OttoKitSettings.js), using
81 // the URL passed below.
82
83 // Localized data.
84 $global_currency = Payment_Helper::get_global_setting( 'currency', 'USD' );
85 $global_currency = is_string( $global_currency ) ? $global_currency : 'USD';
86
87 $editor_data = [
88 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
89 'nonce' => wp_create_nonce( 'suredonation_editor_nonce' ),
90 'postType' => Donation_Form::POST_TYPE,
91 'smartTags' => Helper::get_smart_tags()['confirmation'],
92 'emailSmartTags' => Helper::get_smart_tags()['email_grouped'],
93 'defaultEmailNotifications' => $this->get_default_email_notifications(),
94 // The recurring payment type is a Pro control, but the block attribute
95 // it sets is registered in free and persists in post content. Without
96 // this, deactivating Pro leaves the recurring notifications on screen
97 // and editable while nothing can send them.
98 'isProActive' => defined( 'SUREDONATION_PRO_VER' ),
99 'currency' => $global_currency,
100 'currencySymbol' => Payment_Helper::get_currency_symbol( $global_currency ),
101 // OttoKit integration: embed config + lazy-loaded builder script.
102 'suretriggersNonce' => wp_create_nonce( 'suredonation_suretriggers_nonce' ),
103 'embedScriptUrl' => SUREDONATION_SURETRIGGERS_INTEGRATION_BASE_URL . 'js/v2/embed.js',
104 'integrations' => [
105 'sure_triggers' => Helper::get_ottokit_integration(),
106 ],
107 ];
108
109 // Plugin install/activate nonces are a plugin-management capability;
110 // only expose them to users who can actually install plugins. The AJAX
111 // handlers enforce this server-side too — this keeps the nonces out of
112 // the page source for sub-admins who reach the editor via post.php.
113 if ( current_user_can( 'install_plugins' ) ) {
114 $editor_data['pluginInstallerNonce'] = wp_create_nonce( 'updates' );
115 $editor_data['pluginManagerNonce'] = wp_create_nonce( 'suredonation_plugin_manager' );
116 }
117
118 wp_localize_script(
119 'suredonation-form-editor',
120 'suredonationFormEditor',
121 $editor_data
122 );
123
124 // Set script translations.
125 wp_set_script_translations( 'suredonation-form-editor', 'suredonation' );
126 }
127
128 /**
129 * Enqueue the form editor stylesheet.
130 *
131 * Split out from enqueue_editor_assets() because the two need different
132 * hooks. `enqueue_block_editor_assets` only reaches the admin document, but
133 * most of this stylesheet targets `.editor-styles-wrapper` — the block
134 * canvas, which is an iframe. WordPress used to paper over that with a
135 * compatibility pass that clones any outer stylesheet mentioning
136 * `.editor-styles-wrapper` into the canvas, logging "<handle> was added to
137 * the iframe incorrectly" for each one (see the block editor's `Iframe`
138 * component). `enqueue_block_assets` is the supported hook instead:
139 * core replays it inside _wp_get_iframed_editor_assets() to build the
140 * canvas document, so the styles land there directly and the compatibility
141 * pass skips the handle instead of cloning it.
142 *
143 * The hook fires for the admin document as well, so a single enqueue here
144 * covers the editor chrome too and the handle stays `suredonation-form-editor`
145 * — the id core matches on when deciding whether a clone is still needed.
146 *
147 * One consequence of the move: `enqueue_block_assets` reaches the admin
148 * document only through wp_common_block_scripts_and_styles(), which bails when
149 * `should_load_block_editor_scripts_and_styles` is filtered false in wp-admin.
150 * Anything doing that also strips `wp-block-library` and visibly breaks core's
151 * own editor, so it is not a case worth defending against here.
152 *
153 * @return void
154 * @since 1.5.1
155 */
156 public function enqueue_editor_styles() {
157 // `enqueue_block_assets` also fires on the front end, where there is no
158 // editor to style.
159 if ( ! is_admin() || ! $this->is_form_editor_screen() ) {
160 return;
161 }
162
163 /*
164 * No `wp-components` dependency. It is already in both documents ahead of
165 * this sheet without being asked for: the admin page loads it as editor
166 * chrome, and _wp_get_iframed_editor_assets() enqueues `wp-edit-blocks`
167 * — whose dependencies include `wp-components` — before it fires
168 * `enqueue_block_assets`. Declaring it would neither change the cascade nor
169 * keep anything out of the canvas.
170 */
171 wp_enqueue_style(
172 'suredonation-form-editor',
173 SUREDONATION_URL . 'assets/build/editor/editor.css',
174 [],
175 $this->get_editor_asset()['version']
176 );
177 }
178
179 /**
180 * Whether the current admin screen is the donation form editor.
181 *
182 * @return bool
183 * @since 1.5.1
184 */
185 private function is_form_editor_screen() {
186 // get_current_screen() lives in an admin include, so it is missing on the
187 // front end — where enqueue_block_assets also fires. Checked here rather
188 // than at each call site so the helper is safe for any caller.
189 if ( ! function_exists( 'get_current_screen' ) ) {
190 return false;
191 }
192
193 $screen = get_current_screen();
194
195 return $screen instanceof \WP_Screen && Donation_Form::POST_TYPE === $screen->post_type;
196 }
197
198 /**
199 * Build metadata (dependencies and version) for the editor bundle.
200 *
201 * @return array{dependencies: array<int, string>, version: string}
202 * @since 1.5.1
203 */
204 private function get_editor_asset() {
205 $editor_asset_file = SUREDONATION_DIR . 'assets/build/editor/editor.asset.php';
206
207 return file_exists( $editor_asset_file )
208 ? include $editor_asset_file
209 : [
210 'dependencies' => [
211 'wp-plugins',
212 'wp-editor',
213 'wp-components',
214 'wp-data',
215 'wp-element',
216 'wp-i18n',
217 ],
218 'version' => SUREDONATION_VER,
219 ];
220 }
221
222 /**
223 * Register form settings meta fields.
224 *
225 * Stores form confirmation settings as a single JSON string.
226 *
227 * @return void
228 * @since 1.0.0
229 */
230 public function register_form_settings_meta() {
231 $post_type = Donation_Form::POST_TYPE;
232
233 // Default confirmation message HTML (receipt layout with smart tags).
234 $default_message = Helper::get_default_confirmation_message();
235
236 $default_confirmation = wp_json_encode(
237 [
238 'confirmation_type' => 'same page',
239 'message' => $default_message,
240 'submission_action' => 'hide form',
241 'custom_url' => '',
242 'page_url' => '',
243 ]
244 );
245
246 // Form confirmation settings (JSON string).
247 register_post_meta(
248 $post_type,
249 '_suredonation_form_confirmation',
250 [
251 'type' => 'string',
252 'description' => __( 'Form confirmation settings.', 'suredonation' ),
253 'single' => true,
254 'default' => $default_confirmation,
255 'show_in_rest' => [
256 'schema' => [
257 'type' => 'string',
258 'context' => [ 'edit' ],
259 ],
260 ],
261 'sanitize_callback' => [ $this, 'sanitize_confirmation_settings' ],
262 'auth_callback' => static function () {
263 return current_user_can( 'manage_options' );
264 },
265 ]
266 );
267 }
268
269 /**
270 * Email notification meta key.
271 *
272 * No migration from global `suredonation_options['email_notifications']` is needed:
273 * the plugin is pre-release (v0.0.1) with no production installs carrying customized
274 * global settings. New forms are seeded with defaults via the editor JS useEffect.
275 *
276 * @since 1.0.0
277 */
278 public const EMAIL_NOTIFICATIONS_META_KEY = '_suredonation_form_email_notifications';
279
280 /**
281 * Get default email notifications for new forms.
282 *
283 * Provides the initial set of notifications (donation receipt + admin) that
284 * are seeded when a form has no email notifications configured.
285 *
286 * @return array<int, array<string, mixed>> Default notifications array.
287 * @since 1.0.0
288 */
289 public function get_default_email_notifications() {
290 $sig = '<p>' . esc_html__( 'We truly appreciate your support', 'suredonation' ) . '</p>'
291 . '<p>— {site_title}</p>';
292
293 // phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found -- Readability.
294 $defaults = [
295 // --- Donor Emails ---
296 [
297 'key' => 'donation_receipt',
298 'id' => 1,
299 'status' => true,
300 'name' => __( 'Donation Receipt', 'suredonation' ),
301 'email_to' => '{donor_email}',
302 'subject' => __( 'Thank you for your donation to {campaign_name}', 'suredonation' ),
303 'email_body' => '<p>' . esc_html__( 'Hi {donor_name},', 'suredonation' ) . '</p>'
304 . '<p>' . esc_html__( 'Thank you for supporting {campaign_name}. Your contribution means a lot.', 'suredonation' ) . '</p>'
305 . '<p><strong>' . esc_html__( 'Donation Details:', 'suredonation' ) . '</strong></p>'
306 . '<ul>'
307 . '<li>' . esc_html__( 'Amount:', 'suredonation' ) . ' {amount}</li>'
308 . '<li>' . esc_html__( 'Date:', 'suredonation' ) . ' {donation_date}</li>'
309 . '<li>' . esc_html__( 'Transaction ID:', 'suredonation' ) . ' {transaction_id}</li>'
310 . '</ul>'
311 . $sig,
312 'from_name' => '{site_title}',
313 'from_email' => '{admin_email}',
314 'reply_to' => '',
315 'trigger' => 'donation_completed',
316 ],
317 [
318 'key' => 'donation_processing',
319 'id' => 2,
320 'status' => true,
321 'name' => __( 'Donation Processing', 'suredonation' ),
322 'email_to' => '{donor_email}',
323 'subject' => __( 'Your donation is being processed', 'suredonation' ),
324 'email_body' => '<p>' . esc_html__( 'Hi {donor_name},', 'suredonation' ) . '</p>'
325 . '<p>' . esc_html__( 'Your donation to {campaign_name} is currently being processed.', 'suredonation' ) . '</p>'
326 . '<ul>'
327 . '<li>' . esc_html__( 'Amount:', 'suredonation' ) . ' {amount}</li>'
328 . '<li>' . esc_html__( 'Date:', 'suredonation' ) . ' {donation_date}</li>'
329 . '</ul>'
330 . '<p>' . esc_html__( "We'll notify you once it's confirmed.", 'suredonation' ) . '</p>'
331 . '<p>— {site_title}</p>',
332 'from_name' => '{site_title}',
333 'from_email' => '{admin_email}',
334 'reply_to' => '',
335 'trigger' => 'donation_processing',
336 ],
337 [
338 'key' => 'donation_failed',
339 'id' => 3,
340 'status' => true,
341 'name' => __( 'Donation Failed', 'suredonation' ),
342 'email_to' => '{donor_email}',
343 'subject' => __( "We couldn't process your donation", 'suredonation' ),
344 'email_body' => '<p>' . esc_html__( 'Hi {donor_name},', 'suredonation' ) . '</p>'
345 . '<p>' . esc_html__( 'Unfortunately, your donation to {campaign_name} could not be completed.', 'suredonation' ) . '</p>'
346 . '<p>' . esc_html__( 'If you need help, feel free to contact us.', 'suredonation' ) . '</p>'
347 . '<p>— {site_title}</p>',
348 'from_name' => '{site_title}',
349 'from_email' => '{admin_email}',
350 'reply_to' => '',
351 'trigger' => 'donation_failed',
352 ],
353 [
354 'key' => 'refund_processed',
355 'id' => 4,
356 'status' => true,
357 'name' => __( 'Refund Processed', 'suredonation' ),
358 'email_to' => '{donor_email}',
359 'subject' => __( 'Your donation has been refunded', 'suredonation' ),
360 'email_body' => '<p>' . esc_html__( 'Hi {donor_name},', 'suredonation' ) . '</p>'
361 . '<p>' . esc_html__( 'Your donation has been refunded.', 'suredonation' ) . '</p>'
362 . '<ul>'
363 . '<li>' . esc_html__( 'Campaign:', 'suredonation' ) . ' {campaign_name}</li>'
364 . '<li>' . esc_html__( 'Amount:', 'suredonation' ) . ' {refund_amount}</li>'
365 . '</ul>'
366 . '<p>' . esc_html__( 'If you have any questions, feel free to reach out.', 'suredonation' ) . '</p>'
367 . '<p>— {site_title}</p>',
368 'from_name' => '{site_title}',
369 'from_email' => '{admin_email}',
370 'reply_to' => '',
371 'trigger' => 'refund_processed',
372 ],
373
374 // --- Admin Emails ---
375 [
376 'key' => 'donation_receipt_admin',
377 'id' => 9,
378 'status' => true,
379 'name' => __( 'New Donation (Admin)', 'suredonation' ),
380 'email_to' => '{admin_email}',
381 'subject' => __( 'New donation received', 'suredonation' ),
382 'email_body' => '<p>' . esc_html__( 'A new donation has been received.', 'suredonation' ) . '</p>'
383 . '<ul>'
384 . '<li>' . esc_html__( 'Donor:', 'suredonation' ) . ' {donor_name}</li>'
385 . '<li>' . esc_html__( 'Email:', 'suredonation' ) . ' {donor_email}</li>'
386 . '<li>' . esc_html__( 'Campaign:', 'suredonation' ) . ' {campaign_name}</li>'
387 . '<li>' . esc_html__( 'Amount:', 'suredonation' ) . ' {amount}</li>'
388 . '<li>' . esc_html__( 'Date:', 'suredonation' ) . ' {donation_date}</li>'
389 . '</ul>'
390 . '<p>' . esc_html__( 'View details:', 'suredonation' ) . '<br />{admin_url}</p>',
391 'from_name' => '{site_title}',
392 'from_email' => '{admin_email}',
393 'reply_to' => '',
394 'trigger' => 'donation_completed',
395 ],
396 [
397 'key' => 'donation_failed_admin',
398 'id' => 10,
399 'status' => true,
400 'name' => __( 'Donation Failed (Admin)', 'suredonation' ),
401 'email_to' => '{admin_email}',
402 'subject' => __( 'Donation failed', 'suredonation' ),
403 'email_body' => '<p>' . esc_html__( 'A donation attempt has failed.', 'suredonation' ) . '</p>'
404 . '<ul>'
405 . '<li>' . esc_html__( 'Donor:', 'suredonation' ) . ' {donor_name}</li>'
406 . '<li>' . esc_html__( 'Campaign:', 'suredonation' ) . ' {campaign_name}</li>'
407 . '<li>' . esc_html__( 'Amount:', 'suredonation' ) . ' {amount}</li>'
408 . '<li>' . esc_html__( 'Date:', 'suredonation' ) . ' {donation_date}</li>'
409 . '</ul>'
410 . '<p>' . esc_html__( 'Check details:', 'suredonation' ) . '<br />{admin_url}</p>',
411 'from_name' => '{site_title}',
412 'from_email' => '{admin_email}',
413 'reply_to' => '',
414 'trigger' => 'donation_failed',
415 ],
416 [
417 'key' => 'refund_processed_admin',
418 'id' => 11,
419 'status' => true,
420 'name' => __( 'Refund Processed (Admin)', 'suredonation' ),
421 'email_to' => '{admin_email}',
422 'subject' => __( 'Refund issued', 'suredonation' ),
423 'email_body' => '<p>' . esc_html__( 'A refund has been processed.', 'suredonation' ) . '</p>'
424 . '<ul>'
425 . '<li>' . esc_html__( 'Donor:', 'suredonation' ) . ' {donor_name}</li>'
426 . '<li>' . esc_html__( 'Campaign:', 'suredonation' ) . ' {campaign_name}</li>'
427 . '<li>' . esc_html__( 'Amount:', 'suredonation' ) . ' {refund_amount}</li>'
428 . '</ul>'
429 . '<p>' . esc_html__( 'View details:', 'suredonation' ) . '<br />{admin_url}</p>',
430 'from_name' => '{site_title}',
431 'from_email' => '{admin_email}',
432 'reply_to' => '',
433 'trigger' => 'refund_processed',
434 ],
435 ];
436 // phpcs:enable Generic.Strings.UnnecessaryStringConcat.Found
437
438 /**
439 * Filter default email notifications.
440 * Pro uses this to add subscription email notification templates.
441 *
442 * @param array<int, array<string, mixed>> $defaults Default notification configs.
443 * @since 1.0.0
444 */
445 return apply_filters( 'suredonation_default_email_notifications', $defaults );
446 }
447
448 /**
449 * Register email notification meta for donation forms.
450 *
451 * Stores per-form email notification settings as a JSON string.
452 *
453 * @return void
454 * @since 1.0.0
455 */
456 public function register_email_notifications_meta() {
457 register_post_meta(
458 Donation_Form::POST_TYPE,
459 self::EMAIL_NOTIFICATIONS_META_KEY,
460 [
461 'type' => 'string',
462 'description' => __( 'Form email notification settings.', 'suredonation' ),
463 'single' => true,
464 'default' => '',
465 'show_in_rest' => [
466 'schema' => [
467 'type' => 'string',
468 'context' => [ 'edit' ],
469 ],
470 ],
471 'sanitize_callback' => [ $this, 'sanitize_email_notifications' ],
472 'auth_callback' => static function () {
473 return current_user_can( 'manage_options' );
474 },
475 ]
476 );
477 }
478
479 /**
480 * Sanitize email notification settings.
481 *
482 * @param string $value JSON string of email notification settings.
483 * @return string Sanitized JSON string.
484 * @since 1.0.0
485 */
486 public function sanitize_email_notifications( $value ) {
487 if ( empty( $value ) || ! is_string( $value ) ) {
488 return '';
489 }
490
491 $data = json_decode( $value, true );
492
493 if ( ! is_array( $data ) ) {
494 return '';
495 }
496
497 $sanitized = [];
498 foreach ( $data as $notification ) {
499 if ( ! is_array( $notification ) ) {
500 continue;
501 }
502
503 $trigger = isset( $notification['trigger'] ) && is_string( $notification['trigger'] ) ? $notification['trigger'] : '';
504
505 // Preserve the trigger verbatim rather than validating against the
506 // registered list. 'all' means "send on every event", so coercing an
507 // unrecognised trigger to it silently rewires that notification to
508 // fire on every donation event. Saving a form while Pro is inactive
509 // did exactly that to the recurring templates: the trigger was not
510 // registered, so a customised "Subscription Created" became a message
511 // sent on completed, failed, processing and refunded donations, and
512 // the editor stopped recognising it as recurring and appended a
513 // duplicate set.
514 //
515 // An unregistered trigger is already inert: dispatch is an equality
516 // match against an event name, and the code that fires the recurring
517 // events does not load while Pro is inactive. Preserving the value
518 // costs nothing and lets the notification resume working, with its
519 // customisations, as soon as Pro is active again.
520 $sanitized[] = [
521 // Stable machine identity. `id` is reassigned when a set is
522 // re-seeded and `name` is user-editable and translated, so neither
523 // survives as a way to recognise a notification later.
524 'key' => isset( $notification['key'] ) && is_string( $notification['key'] ) ? sanitize_key( $notification['key'] ) : '',
525 'id' => isset( $notification['id'] ) ? absint( $notification['id'] ) : 0,
526 'status' => isset( $notification['status'] ) ? (bool) $notification['status'] : true,
527 'name' => isset( $notification['name'] ) ? sanitize_text_field( $notification['name'] ) : '',
528 'email_to' => isset( $notification['email_to'] ) ? sanitize_text_field( $notification['email_to'] ) : '',
529 'subject' => isset( $notification['subject'] ) ? sanitize_text_field( $notification['subject'] ) : '',
530 'email_body' => isset( $notification['email_body'] ) ? wp_kses_post( $notification['email_body'] ) : '',
531 'from_name' => isset( $notification['from_name'] ) ? sanitize_text_field( $notification['from_name'] ) : '',
532 'from_email' => isset( $notification['from_email'] ) ? sanitize_text_field( $notification['from_email'] ) : '',
533 'reply_to' => isset( $notification['reply_to'] ) ? sanitize_text_field( $notification['reply_to'] ) : '',
534 // A missing trigger must stay inert. Dispatch skips an empty
535 // trigger but treats 'all' as "fire on every event", so falling
536 // back to 'all' routes the unknown case to the most permissive
537 // outcome — the opposite of what a missing value should mean.
538 'trigger' => '' !== $trigger ? sanitize_key( $trigger ) : '',
539 ];
540 }
541
542 $encoded = wp_json_encode( $sanitized );
543 return is_string( $encoded ) ? $encoded : '';
544 }
545
546
547 /**
548 * Sanitize form confirmation settings.
549 *
550 * @param string $value JSON string of confirmation settings.
551 * @return string Sanitized JSON string.
552 * @since 1.0.0
553 */
554 public function sanitize_confirmation_settings( $value ) {
555 $fallback_data = [
556 'confirmation_type' => 'same page',
557 'message' => '',
558 'submission_action' => 'hide form',
559 'custom_url' => '',
560 'page_url' => '',
561 ];
562 $fallback = (string) wp_json_encode( $fallback_data );
563
564 if ( empty( $value ) || ! is_string( $value ) ) {
565 return $fallback;
566 }
567
568 $data = json_decode( $value, true );
569
570 if ( ! is_array( $data ) ) {
571 return $fallback;
572 }
573
574 $allowed_types = [ 'same page', 'different page', 'custom url' ];
575 $allowed_actions = [ 'hide form', 'reset form' ];
576
577 $message = isset( $data['message'] ) ? wp_kses_post( $data['message'] ) : '';
578
579 $sanitized = [
580 'confirmation_type' => isset( $data['confirmation_type'] ) && in_array( $data['confirmation_type'], $allowed_types, true )
581 ? $data['confirmation_type']
582 : 'same page',
583 'message' => $message,
584 'submission_action' => isset( $data['submission_action'] ) && in_array( $data['submission_action'], $allowed_actions, true )
585 ? $data['submission_action']
586 : 'hide form',
587 'custom_url' => isset( $data['custom_url'] ) ? esc_url_raw( $data['custom_url'] ) : '',
588 'page_url' => isset( $data['page_url'] ) ? esc_url_raw( $data['page_url'] ) : '',
589 ];
590
591 $encoded = wp_json_encode( $sanitized );
592 return is_string( $encoded ) ? $encoded : $fallback;
593 }
594 }
595