PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
← All changes | admin-core/inc/meta-ops.php +57 -9 3.1.0 → 3.3.0 View file →
@@ -23,12 +23,14 @@
23 23 * @param int $post_id post id.
24 24 * @param array $post_meta options to store.
25 25 * @param string $action action to check nonce.
26 26 *
27 - * @return void
27 + * @return array<int, string> Meta keys skipped because the user cannot author scripts.
28 28 */
29 29 public static function save_meta_fields( $post_id, $post_meta, $action = '' ) {
30 30
31 + $skipped_fields = array();
32 +
31 33 if ( ! check_ajax_referer( $action, 'security', false ) ) {
32 34 $response_data = array( 'message' => __( 'Nonce validation failed', 'cartflows' ) );
33 35 wp_send_json_error( $response_data );
34 36 }
@@ -33,9 +35,9 @@
33 35 wp_send_json_error( $response_data );
34 36 }
35 37
36 38 if ( ! ( $post_id && is_array( $post_meta ) ) ) {
37 - return;
39 + return $skipped_fields;
38 40 }
39 41
40 42 $allowed_html = array(
41 43 'a' => array(
@@ -52,9 +54,14 @@
52 54 if ( ! isset( $_POST[ $key ] ) ) {
53 55 continue;
54 56 }
55 57
56 - $meta_value = false;
58 + /*
59 + * null leaves the stored meta alone; false deletes it. Starting at null means a
60 + * sanitize case that forgets to assign can no longer silently destroy saved data
61 + * — every branch that genuinely wants a delete now says so explicitly.
62 + */
63 + $meta_value = null;
57 64
58 65 // Sanitize values.
59 66 $sanitize_filter = ( isset( $data['sanitize'] ) ) ? $data['sanitize'] : 'FILTER_DEFAULT';
60 67
@@ -73,8 +80,11 @@
73 80
74 81 case 'FILTER_CARTFLOWS_ARRAY':
75 82 if ( isset( $_POST[ $key ] ) && is_array( $_POST[ $key ] ) ) {
76 83 $meta_value = array_map( 'sanitize_text_field', wp_unslash( $_POST[ $key ] ) );
84 + } else {
85 + // A posted-but-not-array value has always cleared this meta; keep it.
86 + $meta_value = false;
77 87 }
78 88 break;
79 89
80 90 case 'FILTER_SANITIZE_COLOR':
@@ -96,8 +106,10 @@
96 106 // Custom JS/CSS sinks are output raw on the front end. Restrict authoring to
97 107 // users with `unfiltered_html` so per-plugin caps cannot grant script write
98 108 // access to lower roles (e.g. Editors via the role manager).
99 109 if ( ! current_user_can( 'unfiltered_html' ) ) {
110 + // Record it — skipping silently let the caller report a clean save.
111 + $skipped_fields[] = $key;
100 112 continue 2;
101 113 }
102 114 // Reason for ignoring phpcs rule: Here we are saving the custom JS/CSS script. Encoding it before saving to db. No escaping function working here.
103 115 // We first decode any existing HTML entities to prevent double-encoding on multiple saves, then encode once.
@@ -110,10 +122,11 @@
110 122 break;
111 123
112 124 case 'FILTER_CARTFLOWS_CHECKOUT_PRODUCTS':
113 125 if ( isset( $_POST[ $key ] ) && is_array( $_POST[ $key ] ) ) {
114 - $i = 0;
115 - $q = 0;
126 + $i = 0;
127 + $q = 0;
128 + $checkout_products = array();
116 129
117 130 $post_data = wc_clean( $_POST[ $key ] );
118 131
119 132 foreach ( $post_data as $p_index => $p_data ) {
@@ -123,20 +136,31 @@
123 136 foreach ( $p_data as $i_key => $i_value ) {
124 137
125 138 if ( is_array( $i_value ) ) {
126 139 foreach ( $i_value as $q_key => $q_value ) {
127 - $meta_value[ $i ][ $i_key ][ $q ] = array_map( 'sanitize_text_field', $q_value );
140 + $checkout_products[ $i ][ $i_key ][ $q ] = array_map( 'sanitize_text_field', $q_value );
128 141
129 142 $q++;
130 143 }
131 144 } else {
132 - $meta_value[ $i ][ $i_key ] = sanitize_text_field( $i_value );
145 + $checkout_products[ $i ][ $i_key ] = sanitize_text_field( $i_value );
133 146 }
134 147 }
135 148
136 149 $i++;
137 150 }
151 +
152 + if ( ! empty( $checkout_products ) ) {
153 + $meta_value = $checkout_products;
154 + }
138 155 }
156 +
157 + if ( null === $meta_value ) {
158 + // Delete-on-empty is deliberate here: clearing every product removes the
159 + // meta. The repeater posts an empty scalar, not an array, once the last
160 + // product is removed, so this has to sit outside the is_array() guard.
161 + $meta_value = false;
162 + }
139 163 break;
140 164
141 165 case 'FILTER_CARTFLOWS_CHECKOUT_FIELDS':
142 166 $count = 10;
@@ -148,9 +172,11 @@
148 172 $post_data = $_POST[ $key ]; //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
149 173
150 174 if ( 'wcf_field_order_billing' == $key || 'wcf_field_order_shipping' == $key ) {
151 175
152 - $type_of_fields = ltrim( $key, 'wcf_field_order_' );
176 + // ltrim() takes a character set, not a prefix — it only produced the
177 + // right answer here because 'b' and 's' happen to be absent from it.
178 + $type_of_fields = str_replace( 'wcf_field_order_', '', $key );
153 179 $billing_shipping_fields = \Cartflows_Helper::get_checkout_fields( $type_of_fields, $post_id );
154 180
155 181 foreach ( $post_data as $field_key_name => $value ) {
156 182
@@ -194,8 +220,16 @@
194 220 $meta_value = $ordered_fields;
195 221 }
196 222 }
197 223
224 + /*
225 + * Deliberately no `null === $meta_value` normalisation here. Unlike the
226 + * checkout products and FILTER_CARTFLOWS_ARRAY cases, no shipped field posts
227 + * a scalar or an unhandled key to this filter — the only keys that use it are
228 + * handled above, and the field-order editor always posts per-field arrays. If
229 + * that ever changes, leaving the saved ordering alone is the safe outcome;
230 + * deleting it would be the silent data loss this sentinel exists to prevent.
231 + */
198 232 break;
199 233
200 234 case 'FILTER_CARTFLOWS_OPTIN_FIELDS':
201 235 $count = 10;
@@ -250,8 +284,14 @@
250 284
251 285 $meta_value = $ordered_fields;
252 286 }
253 287 }
288 +
289 + /*
290 + * Deliberately no `null === $meta_value` normalisation here either — same
291 + * reasoning as FILTER_CARTFLOWS_CHECKOUT_FIELDS above. `wcf-optin-fields-billing`
292 + * is the only key that uses this filter and it is handled.
293 + */
254 294 break;
255 295
256 296 default:
257 297 if ( 'FILTER_DEFAULT' === $sanitize_filter ) {
@@ -256,9 +296,15 @@
256 296 default:
257 297 if ( 'FILTER_DEFAULT' === $sanitize_filter ) {
258 298 $meta_value = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
259 299 } else {
260 - $meta_value = apply_filters( 'cartflows_admin_save_meta_field_values', $meta_value, $post_id, $key, $sanitize_filter, $action );
300 + /*
301 + * CartFlows Pro hooks this filter and several of its cases leave the
302 + * incoming value untouched when the field was not posted, relying on
303 + * it being false so the meta is deleted. Pass false rather than the
304 + * new null sentinel so that contract is unchanged.
305 + */
306 + $meta_value = apply_filters( 'cartflows_admin_save_meta_field_values', false, $post_id, $key, $sanitize_filter, $action );
261 307 }
262 308
263 309 break;
264 310 }
@@ -277,6 +323,8 @@
277 323 // To delete the wcf-checkout-products if empty.
278 324 delete_post_meta( $post_id, $key );
279 325 }
280 326 }
327 +
328 + return $skipped_fields;
281 329 }
282 330 }