PluginProbe ʕ •ᴥ•ʔ
Advanced Database Cleaner – Optimize & Clean Database to Speed Up Site Performance / trunk
Advanced Database Cleaner – Optimize & Clean Database to Speed Up Site Performance vtrunk
trunk 1.0.0 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.5 1.3.6 1.3.7 2.0.0 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.1.0 4.1.1
advanced-database-cleaner / includes / utils / validator / class-adbc-common-validator.php
advanced-database-cleaner / includes / utils / validator Last commit date
class-adbc-automation-validator.php 7 months ago class-adbc-common-validator.php 2 months ago class-adbc-selected-items-validator.php 2 months ago class-adbc-settings-validator.php 2 months ago class-adbc-tables-validator.php 7 months ago
class-adbc-common-validator.php
577 lines
1 <?php
2
3 // Exit if accessed directly
4 if ( ! defined( 'ABSPATH' ) )
5 exit;
6
7 /**
8 * ADBC Common validator class.
9 *
10 * This class provides functions to validate and sanitize general data used in the plugin.
11 */
12 class ADBC_Common_Validator {
13
14 /**
15 * Sanitize the DataTable filters sent by the user.
16 *
17 * @param WP_REST_Request $filters_request The filters request.
18 *
19 * @return array The sanitized filters.
20 */
21 public static function sanitize_filters( WP_REST_Request $filters_request ) {
22
23 $sanitized_filters = [
24 'size' => sanitize_key( $filters_request->get_param( 'size' ) ),
25 'size_unit' => sanitize_text_field( $filters_request->get_param( 'sizeUnit' ) ), // KB, MB, GB
26 'table_status' => sanitize_key( $filters_request->get_param( 'tableStatus' ) ),
27 'prefix_status' => sanitize_key( $filters_request->get_param( 'prefixStatus' ) ),
28 'belongs_to' => sanitize_key( $filters_request->get_param( 'belongsTo' ) ),
29 'current_page' => sanitize_key( $filters_request->get_param( 'currentPage' ) ),
30 'items_per_page' => sanitize_key( $filters_request->get_param( 'itemsPerPage' ) ),
31 'sort_by' => sanitize_text_field( $filters_request->get_param( 'sortBy' ) ),
32 'sort_order' => sanitize_text_field( $filters_request->get_param( 'sortOrder' ) ),
33 'items_type' => $filters_request->get_param( 'itemsType' ),
34 'expired' => sanitize_key( $filters_request->get_param( 'expired' ) ),
35 'duplicated' => sanitize_key( $filters_request->get_param( 'duplicated' ) ),
36 'unused' => sanitize_key( $filters_request->get_param( 'unused' ) ),
37 'has_action' => sanitize_key( $filters_request->get_param( 'hasAction' ) ),
38 'search_for' => $filters_request->get_param( 'search' ), // Don't sanitize this to not break the search query. We assure security later.
39 'search_in' => sanitize_key( $filters_request->get_param( 'searchIn' ) ),
40 'site_id' => sanitize_key( $filters_request->get_param( 'site' ) ),
41 'belongs_to_plugin_slug' => sanitize_text_field( $filters_request->get_param( 'belongsToPluginSlug' ) ),
42 'belongs_to_theme_slug' => sanitize_text_field( $filters_request->get_param( 'belongsToThemeSlug' ) ),
43 'show_manual_corrections_only' => $filters_request->get_param( 'showManualCorrectionsOnly' ),
44 'autoload' => sanitize_key( $filters_request->get_param( 'autoload' ) ),
45 'start_date' => $filters_request->get_param( 'startDate' ),
46 'end_date' => $filters_request->get_param( 'endDate' ),
47 'frequency' => sanitize_key( $filters_request->get_param( 'frequency' ) ),
48 'interval' => $filters_request->get_param( 'interval' ),
49 'post_types_posts_count' => sanitize_key( $filters_request->get_param( 'postTypesPostsCount' ) ),
50 'post_types_visibility' => sanitize_key( $filters_request->get_param( 'postTypesVisibility' ) ),
51 ];
52
53 // Sanitize and validate common filters
54 $sanitized_filters['size'] = absint( $sanitized_filters['size'] );
55 $sanitized_filters['size_unit'] = in_array( $sanitized_filters['size_unit'], [ 'B', 'KB', 'MB', 'GB' ] ) ? $sanitized_filters['size_unit'] : 'KB';
56 $sanitized_filters['table_status'] = in_array( $sanitized_filters['table_status'], [ 'all', 'to_optimize', 'to_repair' ] ) ? $sanitized_filters['table_status'] : 'all';
57 $sanitized_filters['prefix_status'] = in_array( $sanitized_filters['prefix_status'], [ 'all', 'valid_prefix', 'invalid_prefix' ] ) ? $sanitized_filters['prefix_status'] : 'all';
58 $sanitized_filters['belongs_to'] =
59 in_array( $sanitized_filters['belongs_to'], [ 'all', 'not_scanned', 'plugins', 'themes', 'wordpress', 'orphans', 'unknown' ] ) ? $sanitized_filters['belongs_to'] : 'all';
60 $sanitized_filters['current_page'] = self::sanitize_validate_current_page( $sanitized_filters['current_page'] );
61 $sanitized_filters['items_per_page'] = self::sanitize_validate_limit( $sanitized_filters['items_per_page'] );
62 // $sanitized_filters['sort_by'] SQL queries will check if the column exists.
63 $sanitized_filters['sort_order'] = in_array( $sanitized_filters['sort_order'], [ 'ASC', 'DESC' ] ) ? $sanitized_filters['sort_order'] : 'ASC';
64 $sanitized_filters['items_type'] = self::sanitize_items_type( $sanitized_filters['items_type'] );
65 $sanitized_filters['expired'] = in_array( $sanitized_filters['expired'], [ 'all', 'yes', 'no' ], true ) ? $sanitized_filters['expired'] : 'all';
66 $sanitized_filters['duplicated'] = in_array( $sanitized_filters['duplicated'], [ 'all', 'yes', 'no' ], true ) ? $sanitized_filters['duplicated'] : 'all';
67 $sanitized_filters['unused'] = in_array( $sanitized_filters['unused'], [ 'all', 'yes', 'no' ], true ) ? $sanitized_filters['unused'] : 'all';
68 $sanitized_filters['has_action'] = in_array( $sanitized_filters['has_action'], [ 'all', 'yes', 'no' ], true ) ? $sanitized_filters['has_action'] : 'all';
69 $sanitized_filters['autoload'] = in_array( $sanitized_filters['autoload'], [ 'all', 'yes', 'no' ] ) ? $sanitized_filters['autoload'] : 'all';
70
71 // In free set premium filters to default values since they are not supported in free version.
72 if ( ADBC_VERSION_TYPE === "FREE" ) {
73 $sanitized_filters['search_for'] = '';
74 $sanitized_filters['search_in'] = 'name';
75 $sanitized_filters['site_id'] = 'all';
76 $sanitized_filters['belongs_to_plugin_slug'] = '';
77 $sanitized_filters['belongs_to_theme_slug'] = '';
78 $sanitized_filters['show_manual_corrections_only'] = false;
79 $sanitized_filters['start_date'] = null;
80 $sanitized_filters['end_date'] = null;
81 $sanitized_filters['frequency'] = 'all';
82 $sanitized_filters['interval'] = 'all';
83 $sanitized_filters['post_types_posts_count'] = 0;
84 $sanitized_filters['post_types_visibility'] = 'all';
85 } else {
86 // In premium, sanitize the premium filters.
87 ADBC_Premium_Common_Validator::sanitize_filters( $sanitized_filters );
88 }
89
90 return $sanitized_filters;
91
92 }
93
94 /**
95 * Checks if the given value equals "0" or "1".
96 *
97 * @param string $key The key of the value to check (not used in this method).
98 * @param string $value The value to check.
99 * @return bool True if the value equals "0" or "1", false otherwise.
100 */
101 public static function is_string_equals_0_or_1( $key, $value ) {
102 return ( $value === "0" || $value === "1" );
103 }
104
105 /**
106 * Checks if the given value is a valid number that is between the specified min and max values.
107 *
108 * @param string $value The value to check.
109 * @param int $min The minimum value.
110 * @param int $max The maximum value.
111 * @return bool True if the value is a number between min and max, false otherwise.
112 */
113 public static function is_number_between_min_and_max( $value, $min, $max ) {
114 return ( is_numeric( $value ) && $value >= $min && $value <= $max );
115 }
116
117 /**
118 * Validates the action data for the given action and items type sent by the user.
119 *
120 * @param string $action The action to validate (e.g., 'optimize_tables', 'delete_options'...).
121 * @param string $items_type The type of items (e.g., 'tables', 'options'...).
122 * @param WP_REST_Request $request_data The request data containing the action and selected items, etc. sent by the user to the endpoint.
123 * @param bool $keep_prefix Whether to keep the prefix in the returned table names (used only for tables).
124 *
125 * @return array|string An array of valid items or an error message if validation fails.
126 */
127 public static function validate_endpoint_action_data( $action, $items_type, $request_data, $keep_prefix = true ) {
128
129 // Get params
130 $action_type = $request_data->get_param( 'actionType' );
131 $selected_items = $request_data->get_param( 'selectedItems' );
132
133 // Check action is valid
134 if ( $action_type !== $action )
135 return "Invalid action.";
136
137 // Delete invalid items from the selected tables and return valid ones.
138 $selected_items = ADBC_Selected_Items_Validator::remove_invalid_selected_items( $items_type, $selected_items, $keep_prefix );
139
140 // Check empty.
141 if ( empty( $selected_items ) )
142 return "No items to process";
143
144 return $selected_items;
145 }
146
147 /**
148 * Validates the data sent by the user to get_column_value_from_table endpoint.
149 *
150 * @param string $items_type The type of items (e.g., 'options', 'transients').
151 * @param int $site_id The site ID.
152 * @param int $row_id The row ID to get the value from.
153 * @param string $transient_found_in The transient found in, if applicable (e.g., 'options', 'sitemeta').
154 *
155 * @return array|string An array with success status, message, and data or an error message if validation fails.
156 */
157 public static function validate_get_column_value_endpoint_data( $items_type, $site_id, $row_id, $transient_found_in ) {
158
159 $answer = [ "success" => false, "message" => "", "data" => []];
160
161 // Check items type is valid
162 if ( ! in_array( $items_type, [ 'options', 'transients', 'posts_meta', 'users_meta', 'revisions', 'auto_drafts', 'trashed_posts', 'unapproved_comments', 'spam_comments', 'trashed_comments', 'pingbacks', 'trackbacks', 'unused_postmeta', 'duplicated_postmeta', 'unused_commentmeta', 'duplicated_commentmeta', 'unused_usermeta', 'duplicated_usermeta', 'unused_termmeta', 'duplicated_termmeta', 'unused_relationships', 'expired_transients', 'oembed_caches', 'actionscheduler_completed_actions', 'actionscheduler_failed_actions', 'actionscheduler_canceled_actions', 'actionscheduler_completed_logs', 'actionscheduler_failed_logs', 'actionscheduler_canceled_logs', 'actionscheduler_orphan_logs' ], true ) )
163 $answer['message'] = "Invalid items type.";
164
165 // Check site ID is valid
166 if ( ! is_numeric( $site_id ) || $site_id < 0 )
167 $answer['message'] = "Invalid site ID.";
168
169 // Check row ID is valid
170 if ( ! is_numeric( $row_id ) || $row_id < 0 )
171 $answer['message'] = "Invalid row ID.";
172
173 // Check if transient_found_in is valid
174 if ( $items_type === 'expired_transients' && ! in_array( $transient_found_in, [ 'options', 'sitemeta' ], true ) )
175 $answer['message'] = "Invalid transient_found_in parameter.";
176
177 // Get the site prefix
178 $site_prefix = ADBC_Sites::instance()->get_prefix_from_site_id( $site_id );
179 if ( $site_prefix === null )
180 $answer['message'] = "Cannot find the site prefix.";
181
182 // Check if there is an error
183 if ( ! empty( $answer['message'] ) )
184 return $answer;
185
186 // Prepare the table name and column names based on the items type
187 switch ( $items_type ) {
188 case 'options':
189 $answer['data'] = [
190 'table_name' => $site_prefix . 'options',
191 'column_id' => "option_id",
192 'column_name' => "option_value"
193 ];
194 break;
195
196 case 'posts_meta':
197 $answer['data'] = [
198 'table_name' => $site_prefix . 'postmeta',
199 'column_id' => "meta_id",
200 'column_name' => "meta_value"
201 ];
202 break;
203
204 case 'users_meta':
205 $answer['data'] = [
206 'table_name' => $site_prefix . 'usermeta',
207 'column_id' => "umeta_id",
208 'column_name' => "meta_value"
209 ];
210 break;
211
212 case 'revisions':
213 case 'auto_drafts':
214 case 'trashed_posts':
215 $answer['data'] = [
216 'table_name' => $site_prefix . 'posts',
217 'column_id' => "ID",
218 'column_name' => "post_content"
219 ];
220 break;
221
222 case 'unapproved_comments':
223 case 'spam_comments':
224 case 'trashed_comments':
225 case 'pingbacks':
226 case 'trackbacks':
227 $answer['data'] = [
228 'table_name' => $site_prefix . 'comments',
229 'column_id' => "comment_ID",
230 'column_name' => "comment_content"
231 ];
232 break;
233
234 case 'unused_postmeta':
235 case 'duplicated_postmeta':
236 case 'oembed_caches':
237 $answer['data'] = [
238 'table_name' => $site_prefix . 'postmeta',
239 'column_id' => "meta_id",
240 'column_name' => "meta_value"
241 ];
242 break;
243
244 case 'unused_commentmeta':
245 case 'duplicated_commentmeta':
246 $answer['data'] = [
247 'table_name' => $site_prefix . 'commentmeta',
248 'column_id' => "meta_id",
249 'column_name' => "meta_value"
250 ];
251 break;
252
253 case 'unused_termmeta':
254 case 'duplicated_termmeta':
255 $answer['data'] = [
256 'table_name' => $site_prefix . 'termmeta',
257 'column_id' => "meta_id",
258 'column_name' => "meta_value"
259 ];
260 break;
261
262 case 'unused_usermeta':
263 case 'duplicated_usermeta':
264 $answer['data'] = [
265 'table_name' => $site_prefix . 'usermeta',
266 'column_id' => "umeta_id",
267 'column_name' => "meta_value"
268 ];
269 break;
270
271 case 'unused_relationships':
272 $answer['data'] = [
273 'table_name' => $site_prefix . 'term_relationships',
274 'column_id' => "object_id",
275 'column_name' => "term_order"
276 ];
277 break;
278
279 case 'expired_transients':
280 case 'transients':
281 switch ( $transient_found_in ) {
282 case 'sitemeta':
283 $answer['data'] = [
284 'table_name' => $site_prefix . 'sitemeta',
285 'column_id' => "meta_id",
286 'column_name' => "meta_value"
287 ];
288 break;
289 case 'options':
290 $answer['data'] = [
291 'table_name' => $site_prefix . 'options',
292 'column_id' => "option_id",
293 'column_name' => "option_value"
294 ];
295 break;
296 }
297 break;
298
299 case 'actionscheduler_completed_actions':
300 case 'actionscheduler_failed_actions':
301 case 'actionscheduler_canceled_actions':
302 $answer['data'] = [
303 'table_name' => $site_prefix . 'actionscheduler_actions',
304 'column_id' => "action_id",
305 'column_name' => "args"
306 ];
307 break;
308 case 'actionscheduler_completed_logs':
309 case 'actionscheduler_failed_logs':
310 case 'actionscheduler_canceled_logs':
311 case 'actionscheduler_orphan_logs':
312 $answer['data'] = [
313 'table_name' => $site_prefix . 'actionscheduler_logs',
314 'column_id' => "log_id",
315 'column_name' => "message"
316 ];
317 break;
318 }
319
320 $answer['success'] = true;
321 return $answer;
322 }
323
324 /**
325 * Validates two optional dates for use as REST filter parameters.
326 *
327 * If only one date is valid, it is returned while the other slot is `null`.
328 * If both dates are present they must be chronological and, when `$max_days`
329 * is > 0, their span must not exceed that limit.
330 *
331 * @param string|null $start_date The start date in the specified format.
332 * @param string|null $end_date The end date in the specified format.
333 * @param string $date_format The date format to use for parsing the dates.
334 * @param int $max_days The maximum number of days allowed between the start and end dates.
335 *
336 * @return array [ start|null, end|null ]
337 */
338 public static function validate_filter_date_range( $start_date = null, $end_date = null, $date_format = 'Y-m-d', $max_days = 0 ) {
339
340 $result = [ null, null ];
341 $start_obj = null;
342 $end_obj = null;
343
344 /*— individually validate ------------------------------------------------*/
345 if ( ! empty( $start_date ) ) {
346 $start_obj = ADBC_Common_Utils::parse_date( $start_date, $date_format );
347 if ( $start_obj ) {
348 $result[0] = $start_obj->format( $date_format );
349 }
350 }
351 if ( ! empty( $end_date ) ) {
352 $end_obj = ADBC_Common_Utils::parse_date( $end_date, $date_format );
353 if ( $end_obj ) {
354 $result[1] = $end_obj->format( $date_format );
355 }
356 }
357
358 /*— both dates supplied? apply cross checks ------------------------------*/
359 if ( $start_obj && $end_obj ) {
360 // chronology
361 if ( $start_obj > $end_obj ) {
362 return [ null, null ];
363 }
364 // length restriction
365 if ( $max_days > 0 && $start_obj->diff( $end_obj )->days > $max_days ) {
366 return [ null, null ];
367 }
368 }
369
370 return $result;
371
372 }
373
374 /**
375 * Validates a *complete* date range. Anything off → `[null, null]`.
376 *
377 * Use when the caller must provide both dates and (optionally) stay within
378 * `$max_days`.
379 *
380 * @param string $start_date The start date in the specified format.
381 * @param string $end_date The end date in the specified format.
382 * @param string $date_format The date format to use for parsing the dates.
383 * @param int $max_days The maximum number of days allowed between the start and end dates.
384 *
385 * @return array [ start|null, end|null ]
386 */
387 public static function validate_strict_date_range( $start_date, $end_date, $date_format = 'Y-m-d', $max_days = 0 ) {
388
389 $invalid = [ null, null ];
390
391 /*— both dates must be present ------------------------------------------*/
392 if ( empty( $start_date ) || empty( $end_date ) ) {
393 return $invalid;
394 }
395
396 $start_obj = ADBC_Common_Utils::parse_date( $start_date, $date_format );
397 $end_obj = ADBC_Common_Utils::parse_date( $end_date, $date_format );
398
399 if ( ! $start_obj || ! $end_obj ) {
400 return $invalid;
401 }
402
403 /*— chronology and (optional) length ------------------------------------*/
404 if ( $start_obj > $end_obj ) {
405 return $invalid;
406 }
407 if ( $max_days > 0 && $start_obj->diff( $end_obj )->days > $max_days ) {
408 return $invalid;
409 }
410
411 return [
412 $start_obj->format( $date_format ),
413 $end_obj->format( $date_format ),
414 ];
415
416 }
417
418 /**
419 * Sanitize the items type sent by the user.
420 *
421 * @param string $items_type The items type.
422 *
423 * @return string The sanitized items type, empty string if invalid.
424 */
425 public static function sanitize_items_type( $items_type ) {
426
427 $items_type = sanitize_key( $items_type );
428
429 $valid_items = array_merge(
430 [
431 'options',
432 'tables',
433 'cron_jobs',
434 'transients',
435 'posts_meta',
436 'users_meta',
437 'post_types',
438 ],
439 ADBC_Cleanup_Type_Registry::get_all_items_type()
440 );
441
442 $valid_items_type = in_array( $items_type, $valid_items, true );
443
444 return $valid_items_type ? $items_type : '';
445
446 }
447
448 /**
449 * Sanitize an array of items types sent by the user.
450 *
451 * @param array $items_types The array of items types.
452 *
453 * @return array The sanitized array of items types, empty array if all invalid.
454 */
455 public static function sanitize_items_types( $items_types ) {
456
457 if ( ! is_array( $items_types ) ) {
458 return [];
459 }
460
461 $validated_items_types = [];
462
463 foreach ( $items_types as $item_type ) {
464 $sanitized = self::sanitize_items_type( $item_type );
465 if ( $sanitized !== '' ) {
466 $validated_items_types[] = $sanitized;
467 }
468 }
469
470 return $validated_items_types;
471
472 }
473
474 /**
475 * Validate the manual categorization sent by the user.
476 *
477 * @param string $manual_categorization The manual categorization.
478 * @return string|bool The error message if the manual categorization is invalid, true otherwise.
479 */
480 public static function is_manual_categorization_valid( $manual_categorization ) {
481
482 $generic_error_msg = "Invalid manual correction.";
483
484 // Check if three keys exist in the manual categorization associative array: type, slug and send_to_server
485 if ( ! is_array( $manual_categorization ) ||
486 ! key_exists( 'type', $manual_categorization ) ||
487 ! key_exists( 'slug', $manual_categorization ) ||
488 ! key_exists( 'send_to_server', $manual_categorization ) )
489 return $generic_error_msg . ' #1';
490
491 $correction_category = $manual_categorization['type'];
492 if ( ! in_array( $correction_category, [ 'p', 't', 'w', 'o', 'u' ] ) )
493 return $generic_error_msg . ' #2';
494
495 $slug = $manual_categorization['slug'];
496
497 if ( in_array( $correction_category, [ 'w', 'o', 'u' ] ) && ! in_array( $slug, [ 'w', 'o', 'u' ] ) )
498 return $generic_error_msg . ' #3';
499
500 if ( $correction_category === 'p' && ! ADBC_Plugins::instance()->is_plugin_slug_currently_installed( $slug ) )
501 return $generic_error_msg . ' #4';
502
503 if ( $correction_category === 't' && ! ADBC_Themes::instance()->is_theme_slug_currently_installed( $slug ) )
504 return $generic_error_msg . ' #5';
505
506 $send_correction_to_server = $manual_categorization['send_to_server'];
507
508 if ( ! in_array( $send_correction_to_server, [ '0', '1' ] ) )
509 return $generic_error_msg . ' #6';
510
511 return true;
512 }
513
514 /**
515 * Validate the offset sent by the user.
516 *
517 * @param int $offset The offset.
518 *
519 * @return int The sanitized offset, 0 if invalid.
520 */
521 public static function sanitize_validate_offset( $offset ) {
522
523 // Sanitize the offset
524 $offset = absint( $offset );
525
526 if ( $offset < 0 )
527 return 0;
528
529 return $offset;
530
531 }
532
533 /**
534 * Validate the limit sent by the user.
535 *
536 * @param int $limit The limit.
537 *
538 * @return int The sanitized limit, 50 if invalid and 1000 max.
539 */
540 public static function sanitize_validate_limit( $limit ) {
541
542 // Sanitize the limit
543 $limit = absint( $limit );
544
545 // Limit the limit to 50 if less than 1
546 if ( $limit < 1 )
547 return 50;
548
549 // Limit the limit to 1000
550 if ( $limit > 1000 )
551 return 1000;
552
553 return $limit;
554
555 }
556
557 /**
558 * Sanitize and validate the current page sent by the user.
559 *
560 * @param int $current_page The current page.
561 *
562 * @return int The sanitized current page, 1 if invalid.
563 */
564 public static function sanitize_validate_current_page( $current_page ) {
565
566 // Sanitize the current page
567 $current_page = absint( $current_page );
568
569 // If current page is less than 1, set it to 1
570 if ( $current_page < 1 )
571 return 1;
572
573 return $current_page;
574
575 }
576
577 }