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
cartflows / admin-legacy-core / inc / meta-ops.php

meta-ops.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.3.0, at admin-legacy-core/inc/meta-ops.php

322 lines 12.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * CartFlows Step Meta Helper.
4 *
5 * @package CartFlows
6 */
7
8 namespace CartflowsAdmin\AdminLegacyCore\Inc;
9
10 // Exit if accessed directly.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * Class StepMeta.
17 */
18 class MetaOps {
19
20 /**
21 * Save Meta fields - Common Function.
22 *
23 * @param int $post_id post id.
24 * @param array $post_meta options to store.
25 * @param string $action action to check nonce.
26 *
27 * @return array<int, string> Meta keys skipped because the user cannot author scripts.
28 */
29 public static function save_meta_fields( $post_id, $post_meta, $action = '' ) {
30
31 $skipped_fields = array();
32
33 if ( ! check_ajax_referer( $action, 'security', false ) ) {
34 $response_data = array( 'message' => __( 'Nonce validation failed', 'cartflows' ) );
35 wp_send_json_error( $response_data );
36 }
37
38 if ( ! ( $post_id && is_array( $post_meta ) ) ) {
39 return $skipped_fields;
40 }
41
42 $allowed_html = array(
43 'a' => array(
44 'href' => array(),
45 ),
46 'br' => array(),
47 'strong' => array(),
48 'p' => array(),
49 'span' => array(),
50 );
51
52 foreach ( $post_meta as $key => $data ) {
53
54 if ( ! isset( $_POST[ $key ] ) ) {
55 continue;
56 }
57
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;
64
65 // Sanitize values.
66 $sanitize_filter = ( isset( $data['sanitize'] ) ) ? $data['sanitize'] : 'FILTER_DEFAULT';
67
68 switch ( $sanitize_filter ) {
69 case 'FILTER_SANITIZE_STRING':
70 $meta_value = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
71 break;
72
73 case 'FILTER_SANITIZE_URL':
74 $meta_value = filter_input( INPUT_POST, $key, FILTER_SANITIZE_URL );
75 break;
76
77 case 'FILTER_SANITIZE_NUMBER_INT':
78 $meta_value = filter_input( INPUT_POST, $key, FILTER_SANITIZE_NUMBER_INT );
79 break;
80
81 case 'FILTER_CARTFLOWS_ARRAY':
82 if ( isset( $_POST[ $key ] ) && is_array( $_POST[ $key ] ) ) {
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;
87 }
88 break;
89
90 case 'FILTER_SANITIZE_COLOR':
91 // Sanitizes a hex color with #.
92 $meta_value = sanitize_hex_color( $_POST[ $key ] );
93 break;
94
95 case 'FILTER_SANITIZE_FONT_FAMILY':
96 // FILTER_FLAG_NO_ENCODE_QUOTES - Do not encode the single and double quotes.
97 $meta_value = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
98 break;
99
100 case 'FILTER_WP_KSES':
101 // It allow only tags that are defined in $allowed_html variable.
102 $meta_value = wp_kses( $_POST[ $key ], $allowed_html );
103 break;
104
105 case 'FILTER_SCRIPT':
106 // Custom JS/CSS sinks are output raw on the front end. Restrict authoring to
107 // users with `unfiltered_html` so per-plugin caps cannot grant script write
108 // access to lower roles (e.g. Editors via the role manager).
109 if ( ! current_user_can( 'unfiltered_html' ) ) {
110 // Record it — skipping silently let the caller report a clean save.
111 $skipped_fields[] = $key;
112 continue 2;
113 }
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.
115 // We first decode any existing HTML entities to prevent double-encoding on multiple saves, then encode once.
116 $meta_value = htmlentities( html_entity_decode( wp_unslash( $_POST[ $key ] ), ENT_QUOTES, 'UTF-8' ), ENT_QUOTES, 'UTF-8' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
117 break;
118
119 case 'FILTER_WP_KSES_POST':
120 // wp_kses_post() allow only the same tags that are allowed in WP Posts.
121 $meta_value = wp_kses_post( $_POST[ $key ] );
122 break;
123
124 case 'FILTER_CARTFLOWS_CHECKOUT_PRODUCTS':
125 if ( isset( $_POST[ $key ] ) && is_array( $_POST[ $key ] ) ) {
126 $i = 0;
127 $q = 0;
128
129 $post_data = wc_clean( $_POST[ $key ] );
130
131 foreach ( $post_data as $p_index => $p_data ) {
132 if ( ! array_key_exists( 'product', $p_data ) ) {
133 continue;
134 }
135 foreach ( $p_data as $i_key => $i_value ) {
136
137 if ( is_array( $i_value ) ) {
138 foreach ( $i_value as $q_key => $q_value ) {
139 $meta_value[ $i ][ $i_key ][ $q ] = array_map( 'sanitize_text_field', $q_value );
140
141 $q++;
142 }
143 } else {
144 $meta_value[ $i ][ $i_key ] = sanitize_text_field( $i_value );
145 }
146 }
147
148 $i++;
149 }
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 }
158 break;
159
160 case 'FILTER_CARTFLOWS_CHECKOUT_FIELDS':
161 $count = 10;
162 $ordered_fields = array();
163 $billing_shipping_fields = array();
164
165 if ( isset( $_POST[ $key ] ) && is_array( $_POST[ $key ] ) ) {
166 // Ignoring sanitization rule as we are here to sanitize user input.
167 $post_data = $_POST[ $key ]; //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
168
169 if ( 'wcf_field_order_billing' == $key || 'wcf_field_order_shipping' == $key ) {
170
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 );
174 $billing_shipping_fields = \Cartflows_Helper::get_checkout_fields( $type_of_fields, $post_id );
175
176 foreach ( $post_data as $field_key_name => $value ) {
177
178 if ( isset( $billing_shipping_fields[ $field_key_name ] ) ) {
179
180 $is_custom_field = isset( $billing_shipping_fields[ $field_key_name ]['custom'] ) ? $billing_shipping_fields[ $field_key_name ]['custom'] : false;
181
182 $ordered_fields[ $field_key_name ] = $billing_shipping_fields[ $field_key_name ];
183
184 $ordered_fields[ $field_key_name ]['priority'] = $count;
185 $count += 10;
186 $placeholder_text = ! empty( $value['placeholder'] ) ? wc_clean( stripslashes( $value['placeholder'] ) ) : '';
187
188 $ordered_fields[ $field_key_name ]['width'] = intval( $value['width'] );
189 $ordered_fields[ $field_key_name ]['label'] = wp_kses_post( trim( stripslashes( $value['label'] ) ) );
190 $ordered_fields[ $field_key_name ]['placeholder'] = $placeholder_text;
191 $ordered_fields[ $field_key_name ]['default'] = wp_kses_post( trim( stripslashes( $value['default'] ) ) );
192 $ordered_fields[ $field_key_name ]['required'] = 'yes' === $value['required'] ? true : false;
193 $ordered_fields[ $field_key_name ]['optimized'] = 'yes' === $value['optimized'] ? true : false;
194 $ordered_fields[ $field_key_name ]['enabled'] = 'yes' === $value['enabled'] ? true : false;
195 $ordered_fields[ $field_key_name ]['options'] = '';
196
197 if ( isset( $value['options'] ) && $value['options'] ) {
198 $options = explode( '|', $value['options'] );
199 $options = AdminHelper::sanitize_array_values( $options );
200 $ordered_fields[ $field_key_name ]['options'] = array_combine( array_keys( $options ), $options );
201
202 }
203
204 if ( ! empty( $value['custom_attributes'] ) ) {
205 $ordered_fields[ $field_key_name ]['custom_attributes'] = wc_clean( $value['custom_attributes'] );
206 }
207
208 if ( true === $is_custom_field ) {
209
210 $ordered_fields[ $field_key_name ]['show_in_email'] = isset( $value['show_in_email'] ) && 'yes' === $value['show_in_email'] ? true : false;
211 }
212 }
213 }
214
215 $meta_value = $ordered_fields;
216 }
217 }
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 */
227 break;
228
229 case 'FILTER_CARTFLOWS_OPTIN_FIELDS':
230 $count = 10;
231 $ordered_fields = array();
232 $billing_shipping_fields = array();
233
234 if ( isset( $_POST[ $key ] ) && is_array( $_POST[ $key ] ) ) {
235 // Ignoring sanitization rule as we are here to sanitize user input.
236 $post_data = $_POST[ $key ]; //phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
237
238 if ( 'wcf-optin-fields-billing' === $key ) {
239
240 $type_of_fields = 'billing';
241 $billing_shipping_fields = \Cartflows_Helper::get_optin_fields( $type_of_fields, $post_id );
242
243 foreach ( $post_data as $field_key_name => $value ) {
244 if ( isset( $billing_shipping_fields[ $field_key_name ] ) ) {
245
246 $is_custom_field = isset( $billing_shipping_fields[ $field_key_name ]['custom'] ) ? $billing_shipping_fields[ $field_key_name ]['custom'] : false;
247
248 $ordered_fields[ $field_key_name ] = $billing_shipping_fields[ $field_key_name ];
249 $placeholder_text = ! empty( $value['placeholder'] ) ? wc_clean( stripslashes( $value['placeholder'] ) ) : '';
250 $ordered_fields[ $field_key_name ]['priority'] = $count;
251 $count += 10;
252
253 $ordered_fields[ $field_key_name ]['width'] = intval( $value['width'] );
254 $ordered_fields[ $field_key_name ]['label'] = wp_kses_post( trim( stripslashes( $value['label'] ) ) );
255 $ordered_fields[ $field_key_name ]['placeholder'] = $placeholder_text;
256 $ordered_fields[ $field_key_name ]['default'] = wp_kses_post( trim( stripslashes( $value['default'] ) ) );
257 $ordered_fields[ $field_key_name ]['required'] = 'yes' === $value['required'] ? true : false;
258 $ordered_fields[ $field_key_name ]['enabled'] = 'yes' === $value['enabled'] ? true : false;
259 $ordered_fields[ $field_key_name ]['options'] = '';
260
261 if ( isset( $value['options'] ) && $value['options'] ) {
262
263 $options = explode( '|', $value['options'] );
264 $options = AdminHelper::sanitize_array_values( $options );
265 $ordered_fields[ $field_key_name ]['options'] = array_combine( array_keys( $options ), $options );
266
267 }
268
269 if ( ! empty( $value['custom_attributes'] ) ) {
270 $ordered_fields[ $field_key_name ]['custom_attributes'] = wc_clean( $value['custom_attributes'] );
271 }
272
273 if ( true === $is_custom_field ) {
274
275 $ordered_fields[ $field_key_name ]['show_in_email'] = isset( $value['show_in_email'] ) && 'yes' === $value['show_in_email'] ? true : false;
276 }
277 }
278 }
279
280 $meta_value = $ordered_fields;
281 }
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 */
289 break;
290
291 default:
292 if ( 'FILTER_DEFAULT' === $sanitize_filter ) {
293 $meta_value = isset( $_POST[ $key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $key ] ) ) : '';
294 } else {
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 );
302 }
303
304 break;
305 }
306
307 if ( is_null( $meta_value ) ) {
308 continue;
309 }
310
311 if ( false !== $meta_value ) {
312 update_post_meta( $post_id, $key, $meta_value );
313 } else {
314 // To delete the wcf-checkout-products if empty.
315 delete_post_meta( $post_id, $key );
316 }
317 }
318
319 return $skipped_fields;
320 }
321 }
322