PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.2.1
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.2.1
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 66 releases
better-payment / includes / Campaign / MetaBox.php

MetaBox.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.2.1, at includes/Campaign/MetaBox.php

194 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\Campaign;
4
5 use Better_Payment\Lite\Controller;
6
7 if ( ! defined( 'ABSPATH' ) ) {
8 exit;
9 }
10
11 /**
12 * Handles saving and reading _bpc_* post meta for bp_campaign posts.
13 * The builder save path goes through the REST API (CampaignAPI).
14 * This class handles the rare case of direct post saves (e.g., autosave, bulk actions).
15 */
16 class MetaBox extends Controller {
17
18 /**
19 * Meta fields and their sanitize callbacks.
20 */
21 private static function meta_fields(): array {
22 return apply_filters( 'better_payment/campaign/meta_fields', [
23 '_bpc_goal_amount' => 'floatval',
24 '_bpc_end_date' => 'sanitize_text_field',
25 '_bpc_suggested_amounts' => [ self::class, 'sanitize_amounts_array' ],
26 '_bpc_allow_custom_amount' => 'absint',
27 '_bpc_minimum_amount' => 'floatval',
28 '_bpc_form_page_id' => 'absint',
29 '_bpc_fields_layout' => [ self::class, 'sanitize_json' ],
30 '_bpc_status' => 'sanitize_text_field',
31 '_bpc_color_primary' => 'sanitize_hex_color',
32 '_bpc_color_button' => 'sanitize_hex_color',
33 '_bpc_css_class' => 'sanitize_html_class',
34 '_bpc_template_key' => 'sanitize_text_field',
35 ] );
36 }
37
38 /**
39 * Save post meta on save_post_bp_campaign.
40 */
41 public function save( int $post_id ) {
42 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
43 return;
44 }
45
46 if ( ! current_user_can( 'edit_post', $post_id ) ) {
47 return;
48 }
49
50 // Only handle REST saves that explicitly post _bpc nonce; direct post saves are rare.
51 if ( ! isset( $_POST['bpc_meta_nonce'] ) ) {
52 return;
53 }
54
55 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['bpc_meta_nonce'] ) ), 'bpc_save_meta_' . $post_id ) ) {
56 return;
57 }
58
59 foreach ( self::meta_fields() as $key => $sanitize ) {
60 if ( ! isset( $_POST[ $key ] ) ) {
61 continue;
62 }
63
64 $value = wp_unslash( $_POST[ $key ] );
65 $value = is_callable( $sanitize ) ? call_user_func( $sanitize, $value ) : $value;
66 update_post_meta( $post_id, $key, $value );
67 }
68 }
69
70 /**
71 * Save meta directly from an array (used by CampaignAPI).
72 */
73 public static function save_from_array( int $post_id, array $data ) {
74 foreach ( self::meta_fields() as $key => $sanitize ) {
75 $short_key = ltrim( $key, '_' );
76
77 if ( array_key_exists( $short_key, $data ) ) {
78 $raw = $data[ $short_key ];
79 $value = is_callable( $sanitize ) ? call_user_func( $sanitize, $raw ) : sanitize_text_field( $raw );
80 // wp_unslash() is applied inside update_metadata, which strips backslashes from strings.
81 // wp_slash() pre-escapes so the round-trip leaves the value intact (standard WP REST API pattern).
82 if ( is_string( $value ) ) {
83 $value = wp_slash( $value );
84 }
85 update_post_meta( $post_id, $key, $value );
86 }
87 }
88 }
89
90 /**
91 * Read all meta for a campaign post as a plain array.
92 */
93 public static function get_all( int $post_id ): array {
94 $result = [];
95 foreach ( array_keys( self::meta_fields() ) as $key ) {
96 $short = ltrim( $key, '_' );
97 $result[ $short ] = get_post_meta( $post_id, $key, true );
98 }
99
100 // Cast integer/float meta so JS receives 0/1 (not "0"/"1" strings) —
101 // get_post_meta always returns strings; "0" is truthy in JS which breaks boolean checks.
102 $result['bpc_allow_custom_amount'] = (int) ( $result['bpc_allow_custom_amount'] ?? 1 );
103 $result['bpc_goal_amount'] = (float) ( $result['bpc_goal_amount'] ?? 0 );
104 $result['bpc_minimum_amount'] = '' !== ( $result['bpc_minimum_amount'] ?? '' )
105 ? (float) $result['bpc_minimum_amount']
106 : '';
107 $result['bpc_form_page_id'] = (int) ( $result['bpc_form_page_id'] ?? 0 );
108 $result['bpc_css_class'] = (string) ( $result['bpc_css_class'] ?? '' );
109
110 // Parse JSON fields.
111 if ( ! empty( $result['bpc_fields_layout'] ) && is_string( $result['bpc_fields_layout'] ) ) {
112 $decoded = json_decode( $result['bpc_fields_layout'], true );
113 $result['bpc_fields_layout'] = is_array( $decoded ) ? $decoded : [];
114 }
115
116 // Parse suggested amounts — stored as JSON array of {id,amount,description,is_default}.
117 $sa = $result['bpc_suggested_amounts'] ?? '';
118 if ( is_string( $sa ) && '' !== $sa ) {
119 $decoded = json_decode( $sa, true );
120 if ( is_array( $decoded ) ) {
121 $result['bpc_suggested_amounts'] = $decoded;
122 } else {
123 // Legacy comma-separated — migrate on read.
124 $parts = array_filter( array_map( 'trim', explode( ',', $sa ) ) );
125 $result['bpc_suggested_amounts'] = array_values( array_map(
126 function ( $amount, $i ) {
127 return [
128 'id' => 'sa_' . ( $i + 1 ),
129 'amount' => (string) floatval( $amount ),
130 'description' => '',
131 'is_default' => false,
132 ];
133 },
134 $parts,
135 array_keys( $parts )
136 ) );
137 }
138 } else {
139 $result['bpc_suggested_amounts'] = [];
140 }
141
142 return $result;
143 }
144
145 // ------------------------------------------------------------------ helpers
146
147 public static function sanitize_amounts_array( $value ): string {
148 if ( is_array( $value ) ) {
149 // PHP array from REST API JSON body — sanitize each item and re-encode.
150 $clean = array_values( array_map( function ( $item ) {
151 return [
152 'id' => sanitize_text_field( $item['id'] ?? '' ),
153 'amount' => (string) floatval( $item['amount'] ?? 0 ),
154 'description' => sanitize_text_field( $item['description'] ?? '' ),
155 'is_default' => ! empty( $item['is_default'] ),
156 ];
157 }, $value ) );
158 return wp_json_encode( $clean );
159 }
160 if ( is_string( $value ) ) {
161 $decoded = json_decode( $value, true );
162 if ( is_array( $decoded ) ) {
163 return self::sanitize_amounts_array( $decoded );
164 }
165 // Legacy comma-separated — migrate.
166 $parts = array_filter( array_map( 'trim', explode( ',', $value ) ) );
167 if ( ! empty( $parts ) ) {
168 $items = array_values( array_map( function ( $a, $i ) {
169 return [
170 'id' => 'sa_' . ( $i + 1 ),
171 'amount' => (string) floatval( $a ),
172 'description' => '',
173 'is_default' => false,
174 ];
175 }, $parts, array_keys( $parts ) ) );
176 return wp_json_encode( $items );
177 }
178 return '[]';
179 }
180 return '[]';
181 }
182
183 public static function sanitize_json( $value ): string {
184 if ( is_array( $value ) ) {
185 return wp_json_encode( $value );
186 }
187 if ( is_string( $value ) ) {
188 $decoded = json_decode( $value, true );
189 return is_array( $decoded ) ? wp_json_encode( $decoded ) : '[]';
190 }
191 return '[]';
192 }
193 }
194