PluginProbe
WebberZone Top 10 — Popular Posts / 4.4.0
WebberZone Top 10 — Popular Posts v4.4.0
4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 All 117 releases
top-10 / includes / admin / settings / class-settings-sanitize.php

class-settings-sanitize.php in WebberZone Top 10 — Popular Posts 4.4.0, at includes/admin/settings/class-settings-sanitize.php

503 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Functions to sanitize settings.
4 *
5 * @link https://webberzone.com
6 *
7 * @package WebberZone\Top_Ten
8 */
9
10 namespace WebberZone\Top_Ten\Admin\Settings;
11
12 // If this file is called directly, abort.
13 if ( ! defined( 'WPINC' ) ) {
14 die;
15 }
16
17 /**
18 * Settings Sanitize Class.
19 *
20 * @since 4.0.0
21 */
22 class Settings_Sanitize {
23
24 /**
25 * Settings Key.
26 *
27 * @var string Settings Key.
28 */
29 public $settings_key;
30
31 /**
32 * Prefix which is used for creating the unique filters and actions.
33 *
34 * @var string Prefix.
35 */
36 public $prefix;
37
38 /**
39 * Main constructor class.
40 *
41 * @param mixed $args {
42 * Array or string of arguments. Default is blank array.
43 * @type string $settings_key Settings key.
44 * @type string $prefix Prefix.
45 * }
46 */
47 public function __construct( $args ) {
48 $defaults = array(
49 'settings_key' => '',
50 'prefix' => '',
51 );
52 $args = wp_parse_args( $args, $defaults );
53
54 foreach ( $args as $name => $value ) {
55 $this->$name = $value;
56 }
57 }
58
59 /**
60 * Get the value of a settings field.
61 *
62 * @param string $option Settings field name.
63 * @param mixed $default_value Default value if option is not found.
64 * @return mixed
65 */
66 public function get_option( $option, $default_value = '' ) {
67 $options = \get_option( $this->settings_key );
68
69 if ( isset( $options[ $option ] ) ) {
70 return $options[ $option ];
71 }
72
73 return $default_value;
74 }
75
76 /**
77 * Miscellaneous sanitize function
78 *
79 * @param mixed $value Setting Value.
80 * @return string Sanitized value.
81 */
82 public function sanitize_missing( $value ) {
83 return $value;
84 }
85
86 /**
87 * Sanitize text fields
88 *
89 * @param string $value The field value.
90 * @return string Sanitizied value
91 */
92 public function sanitize_text_field( $value ) {
93 return $this->sanitize_textarea_field( $value );
94 }
95
96 /**
97 * Sanitize number fields
98 *
99 * @param string $value The field value.
100 * @return string Sanitized value
101 */
102 public function sanitize_number_field( $value ) {
103 return filter_var( $value, FILTER_SANITIZE_NUMBER_INT );
104 }
105
106 /**
107 * Sanitize CSV fields
108 *
109 * @param string $value The field value.
110 * @return string Sanitizied value
111 */
112 public function sanitize_csv_field( $value ) {
113 return implode( ',', array_map( 'trim', explode( ',', sanitize_text_field( wp_unslash( $value ) ) ) ) );
114 }
115
116 /**
117 * Sanitize CSV fields which hold numbers
118 *
119 * @param string $value The field value.
120 * @return string Sanitized value
121 */
122 public function sanitize_numbercsv_field( $value ) {
123 return implode( ',', array_filter( array_map( 'absint', explode( ',', sanitize_text_field( wp_unslash( $value ) ) ) ) ) );
124 }
125
126 /**
127 * Sanitize CSV fields which hold post IDs
128 *
129 * @param string $value The field value.
130 * @return string Sanitized value
131 */
132 public function sanitize_postids_field( $value ) {
133 $ids = array_filter( array_map( 'absint', explode( ',', sanitize_text_field( wp_unslash( $value ) ) ) ) );
134
135 foreach ( $ids as $key => $value ) {
136 if ( false === get_post_status( $value ) ) {
137 unset( $ids[ $key ] );
138 }
139 }
140
141 return implode( ',', $ids );
142 }
143
144 /**
145 * Sanitize textarea fields
146 *
147 * @param string $value The field value.
148 * @return string Sanitized value
149 */
150 public function sanitize_textarea_field( $value ) {
151
152 global $allowedposttags;
153
154 // We need more tags to allow for script and style.
155 $moretags = array(
156 'script' => array(
157 'type' => true,
158 'src' => true,
159 'async' => true,
160 'defer' => true,
161 'charset' => true,
162 ),
163 'style' => array(
164 'type' => true,
165 'media' => true,
166 'scoped' => true,
167 ),
168 'link' => array(
169 'rel' => true,
170 'type' => true,
171 'href' => true,
172 'media' => true,
173 'sizes' => true,
174 'hreflang' => true,
175 ),
176 );
177
178 $allowedtags = array_merge( $allowedposttags, $moretags );
179
180 /**
181 * Filter allowed tags allowed when sanitizing text and textarea fields.
182 *
183 * @param array $allowedtags Allowed tags array.
184 */
185 $allowedtags = apply_filters( $this->prefix . '_sanitize_allowed_tags', $allowedtags ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound
186
187 return wp_kses( wp_unslash( $value ), $allowedtags );
188 }
189
190 /**
191 * Sanitize checkbox fields
192 *
193 * @param mixed $value The field value.
194 * @return int Sanitized value
195 */
196 public function sanitize_checkbox_field( $value ) {
197 $value = in_array( (int) $value, array( 0, -1 ), true ) ? 0 : 1;
198
199 return $value;
200 }
201
202 /**
203 * Sanitize toggle fields
204 *
205 * @param mixed $value The field value.
206 * @return int Sanitized value
207 */
208 public function sanitize_toggle_field( $value ) {
209 return $this->sanitize_checkbox_field( $value );
210 }
211
212 /**
213 * Sanitize multicheck fields
214 *
215 * @param array|int $value The field value.
216 * @return string $value Sanitized value
217 */
218 public function sanitize_multicheck_field( $value ) {
219 $values = ( -1 === (int) $value ) ? array() : array_map( 'sanitize_text_field', (array) wp_unslash( $value ) );
220
221 return implode( ',', $values );
222 }
223
224 /**
225 * Sanitize post_types fields
226 *
227 * @param array|int $value The field value.
228 * @return string $value Sanitized value
229 */
230 public function sanitize_posttypes_field( $value ) {
231 return $this->sanitize_multicheck_field( $value );
232 }
233
234 /**
235 * Sanitize post_types fields
236 *
237 * @param array|int $value The field value.
238 * @return string $value Sanitized value
239 */
240 public function sanitize_taxonomies_field( $value ) {
241 return $this->sanitize_multicheck_field( $value );
242 }
243
244 /**
245 * Sanitize color fields.
246 *
247 * @param string $value The field value.
248 * @return string Sanitized value
249 */
250 public function sanitize_color_field( $value ) {
251 return sanitize_hex_color( $value );
252 }
253
254 /**
255 * Sanitize email fields.
256 *
257 * @param string $value The field value.
258 * @return string Sanitized value
259 */
260 public function sanitize_email_field( $value ) {
261 return sanitize_email( $value );
262 }
263
264 /**
265 * Sanitize URL fields.
266 *
267 * @param string $value The field value.
268 * @return string Sanitized value
269 */
270 public function sanitize_url_field( $value ) {
271 return esc_url_raw( $value );
272 }
273
274 /**
275 * Sanitize sensitive fields.
276 *
277 * @param string $value The field value.
278 * @param string|array $key The field key.
279 * @return string Sanitized value
280 */
281 public function sanitize_sensitive_field( $value, $key ) {
282 if ( is_array( $key ) ) {
283 if ( isset( $key['id'] ) ) {
284 $key = $key['id'];
285 } else {
286 return $value;
287 }
288 }
289
290 $stored_encrypted_key = $this->get_option( $key );
291
292 // Empty input clears the stored value.
293 if ( '' === (string) $value ) {
294 return '';
295 }
296
297 // If input is masked, return existing encrypted key.
298 if ( strpos( (string) $value, '**' ) !== false ) {
299 return $stored_encrypted_key;
300 }
301
302 return Settings_API::encrypt_api_key( $value );
303 }
304
305 /**
306 * Sanitize repeater field.
307 *
308 * @param mixed $value Array of repeater values (may be non-array from form data).
309 * @param array $field Field configuration array.
310 * @return array Sanitized array
311 */
312 public function sanitize_repeater_field( $value, $field = array() ) {
313 if ( ! is_array( $value ) ) {
314 return array();
315 }
316
317 $sanitized_value = array();
318 $existing_rows = array();
319
320 // Get the subfields configuration.
321 $subfields = ! empty( $field['fields'] ) ? $field['fields'] : array();
322 if ( ! empty( $field['id'] ) ) {
323 $stored_value = $this->get_option( $field['id'], array() );
324 $existing_rows = is_array( $stored_value ) ? $stored_value : array();
325 }
326
327 // Create a lookup table for existing rows by row_id.
328 $existing_by_id = array();
329 foreach ( $existing_rows as $existing_row ) {
330 if ( isset( $existing_row['row_id'] ) ) {
331 $existing_by_id[ $existing_row['row_id'] ] = $existing_row;
332 }
333 }
334
335 foreach ( $value as $index => $row ) {
336 // Ensure we have a valid row structure.
337 if ( ! isset( $row['fields'] ) || ! is_array( $row['fields'] ) ) {
338 continue;
339 }
340
341 $sanitized_row = array(
342 'fields' => array(),
343 );
344
345 // Preserve row_id if it exists.
346 if ( isset( $row['row_id'] ) ) {
347 $sanitized_row['row_id'] = sanitize_text_field( $row['row_id'] );
348 }
349
350 // Get the corresponding existing row for sensitive field preservation.
351 $existing_row = null;
352 if ( isset( $row['row_id'] ) && isset( $existing_by_id[ $row['row_id'] ] ) ) {
353 $existing_row = $existing_by_id[ $row['row_id'] ];
354 }
355
356 foreach ( $row['fields'] as $field_key => $field_value ) {
357 $field_key = sanitize_key( $field_key );
358
359 // Skip if field_key is not in our subfields configuration.
360 $field_config = null;
361 foreach ( $subfields as $subfield ) {
362 if ( isset( $subfield['id'] ) && $subfield['id'] === $field_key ) {
363 $field_config = $subfield;
364 break;
365 }
366 }
367
368 if ( null === $field_config ) {
369 continue;
370 }
371
372 // Get the field type from the subfield configuration.
373 $field_type = isset( $field_config['type'] ) ? $field_config['type'] : 'text';
374
375 // For sensitive fields, distinguish empty (clear) from masked (preserve).
376 if ( 'sensitive' === $field_type ) {
377 if ( '' === (string) $field_value ) {
378 $sanitized_row['fields'][ $field_key ] = '';
379 continue;
380 }
381 if ( is_string( $field_value ) && false !== strpos( $field_value, '**' ) ) {
382 if ( $existing_row && isset( $existing_row['fields'][ $field_key ] ) ) {
383 $sanitized_row['fields'][ $field_key ] = $existing_row['fields'][ $field_key ];
384 }
385 continue;
386 }
387 }
388
389 // Call the appropriate sanitization method.
390 $sanitize_method = 'sanitize_' . $field_type . '_field';
391 if ( method_exists( $this, $sanitize_method ) ) {
392 if ( 'sensitive' === $field_type ) {
393 $sanitized_row['fields'][ $field_key ] = $this->$sanitize_method( $field_value, $field_key );
394 } else {
395 $sanitized_row['fields'][ $field_key ] = $this->$sanitize_method( $field_value, $field_config );
396 }
397 } else {
398 $sanitized_row['fields'][ $field_key ] = $this->sanitize_text_field( $field_value );
399 }
400 }
401
402 if ( ! empty( $sanitized_row['fields'] ) ) {
403 $sanitized_value[ $index ] = $sanitized_row;
404 }
405 }
406
407 return $sanitized_value;
408 }
409
410 /**
411 * Convert a string to CSV.
412 *
413 * @param array $input_array Input string.
414 * @param string $delimiter Delimiter.
415 * @param string $enclosure Enclosure.
416 * @param string $terminator Terminating string.
417 * @return string CSV string.
418 */
419 public static function str_putcsv( $input_array, $delimiter = ',', $enclosure = '"', $terminator = "\n" ) {
420 // First convert associative array to numeric indexed array.
421 $work_array = array();
422 foreach ( $input_array as $key => $value ) {
423 $work_array[] = $value;
424 }
425
426 $output = '';
427 $array_size = count( $work_array );
428
429 for ( $i = 0; $i < $array_size; $i++ ) {
430 // Nested array, process nest item.
431 if ( is_array( $work_array[ $i ] ) ) {
432 $output .= self::str_putcsv( $work_array[ $i ], $delimiter, $enclosure, $terminator );
433 } else {
434 switch ( gettype( $work_array[ $i ] ) ) {
435 // Manually set some strings.
436 case 'NULL':
437 $sp_format = '';
438 break;
439 case 'boolean':
440 $sp_format = ( true === $work_array[ $i ] ) ? 'true' : 'false';
441 break;
442 // Make sure sprintf has a good datatype to work with.
443 case 'integer':
444 $sp_format = '%d';
445 break;
446 case 'double':
447 $sp_format = '%0.2f';
448 break;
449 case 'string':
450 $sp_format = '%s';
451 $work_array[ $i ] = str_replace( "$enclosure", "$enclosure$enclosure", $work_array[ $i ] );
452 break;
453 // Unknown or invalid items for a csv - note: the datatype of array is already handled above, assuming the data is nested.
454 case 'object':
455 case 'resource':
456 default:
457 $sp_format = '';
458 break;
459 }
460 $output .= sprintf( '%2$s' . $sp_format . '%2$s', $work_array[ $i ], $enclosure );
461 $output .= ( $i < ( $array_size - 1 ) ) ? $delimiter : $terminator;
462 }
463 }
464
465 return $output;
466 }
467
468 /**
469 * Processes category/taxonomy slugs and adds a new element to the settings array containing the term taxonomy IDs.
470 *
471 * @param array $settings The settings array containing the taxonomy slugs to sanitize.
472 * @param string $source_key The key in the settings array containing the slugs. Pattern is Name (taxonomy:term_taxonomy_id).
473 * @param string $target_key The key in the settings array to store the sanitized term taxonomy IDs.
474 * @return void
475 */
476 public static function sanitize_tax_slugs( &$settings, $source_key, $target_key ) {
477 if ( isset( $settings[ $source_key ] ) ) {
478 $slugs = array_unique( str_getcsv( $settings[ $source_key ], ',', '"', '' ) );
479
480 $tax_ids = array();
481 $tax_slugs = array();
482
483 foreach ( $slugs as $slug ) {
484 // Pattern is Name (taxonomy:term_taxonomy_id).
485 preg_match( '/(.*)\((.*):(\d+)\)/i', (string) $slug, $matches );
486 if ( isset( $matches[3] ) ) {
487 $term = get_term_by( 'term_taxonomy_id', $matches[3] );
488 } else {
489 // Fallback to fetching the category as this was the original format.
490 $term = get_term_by( 'name', $slug, 'category' );
491 }
492 if ( isset( $term->term_taxonomy_id ) ) {
493 $tax_ids[] = $term->term_taxonomy_id;
494 $tax_slugs[] = "{$term->name} ({$term->taxonomy}:{$term->term_taxonomy_id})";
495 }
496 }
497
498 $settings[ $target_key ] = join( ',', $tax_ids );
499 $settings[ $source_key ] = self::str_putcsv( $tax_slugs );
500 }
501 }
502 }
503