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-legacy-core/inc/meta-ops.php +48 -5 3.1.4 → 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.
@@ -135,8 +147,15 @@
135 147
136 148 $i++;
137 149 }
138 150 }
151 +
152 + if ( null === $meta_value ) {
153 + // Delete-on-empty is deliberate here: clearing every product removes the
154 + // meta. The repeater posts an empty scalar, not an array, once the last
155 + // product is removed, so this has to sit outside the is_array() guard.
156 + $meta_value = false;
157 + }
139 158 break;
140 159
141 160 case 'FILTER_CARTFLOWS_CHECKOUT_FIELDS':
142 161 $count = 10;
@@ -148,9 +167,11 @@
148 167 $post_data = $_POST[ $key ]; //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
149 168
150 169 if ( 'wcf_field_order_billing' == $key || 'wcf_field_order_shipping' == $key ) {
151 170
152 - $type_of_fields = ltrim( $key, 'wcf_field_order_' );
171 + // ltrim() takes a character set, not a prefix — it only produced the
172 + // right answer here because 'b' and 's' happen to be absent from it.
173 + $type_of_fields = str_replace( 'wcf_field_order_', '', $key );
153 174 $billing_shipping_fields = \Cartflows_Helper::get_checkout_fields( $type_of_fields, $post_id );
154 175
155 176 foreach ( $post_data as $field_key_name => $value ) {
156 177
@@ -194,8 +215,16 @@
194 215 $meta_value = $ordered_fields;
195 216 }
196 217 }
197 218
219 + /*
220 + * Deliberately no `null === $meta_value` normalisation here. Unlike the
221 + * checkout products and FILTER_CARTFLOWS_ARRAY cases, no shipped field posts
222 + * a scalar or an unhandled key to this filter — the only keys that use it are
223 + * handled above, and the field-order editor always posts per-field arrays. If
224 + * that ever changes, leaving the saved ordering alone is the safe outcome;
225 + * deleting it would be the silent data loss this sentinel exists to prevent.
226 + */
198 227 break;
199 228
200 229 case 'FILTER_CARTFLOWS_OPTIN_FIELDS':
201 230 $count = 10;
@@ -250,8 +279,14 @@
250 279
251 280 $meta_value = $ordered_fields;
252 281 }
253 282 }
283 +
284 + /*
285 + * Deliberately no `null === $meta_value` normalisation here either — same
286 + * reasoning as FILTER_CARTFLOWS_CHECKOUT_FIELDS above. `wcf-optin-fields-billing`
287 + * is the only key that uses this filter and it is handled.
288 + */
254 289 break;
255 290
256 291 default:
257 292 if ( 'FILTER_DEFAULT' === $sanitize_filter ) {
@@ -256,9 +291,15 @@
256 291 default:
257 292 if ( 'FILTER_DEFAULT' === $sanitize_filter ) {
258 293 $meta_value = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
259 294 } else {
260 - $meta_value = apply_filters( 'cartflows_admin_save_meta_field_values', $meta_value, $post_id, $key, $sanitize_filter, $action );
295 + /*
296 + * CartFlows Pro hooks this filter and several of its cases leave the
297 + * incoming value untouched when the field was not posted, relying on
298 + * it being false so the meta is deleted. Pass false rather than the
299 + * new null sentinel so that contract is unchanged.
300 + */
301 + $meta_value = apply_filters( 'cartflows_admin_save_meta_field_values', false, $post_id, $key, $sanitize_filter, $action );
261 302 }
262 303
263 304 break;
264 305 }
@@ -273,6 +314,8 @@
273 314 // To delete the wcf-checkout-products if empty.
274 315 delete_post_meta( $post_id, $key );
275 316 }
276 317 }
318 +
319 + return $skipped_fields;
277 320 }
278 321 }