PluginProbe
WebberZone Top 10 — Popular Posts / 4.3.1
WebberZone Top 10 — Popular Posts v4.3.1
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.3.1, at includes/admin/settings/class-settings-sanitize.php

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