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
4.2.0 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-selected-items-validator.php
advanced-database-cleaner / includes / utils / validator Last commit date
class-adbc-automation-validator.php 7 months ago class-adbc-common-validator.php 12 hours ago class-adbc-selected-items-validator.php 3 months ago class-adbc-settings-validator.php 12 hours ago class-adbc-tables-validator.php 7 months ago
class-adbc-selected-items-validator.php
386 lines
1 <?php
2
3 // Exit if accessed directly
4 if ( ! defined( 'ABSPATH' ) )
5 exit;
6
7 /**
8 * ADBC Selected Items validator class.
9 *
10 * This class provides functions to validate and sanitize the selected items sent by the user to the endpoints.
11 */
12 class ADBC_Selected_Items_Validator {
13
14 private const MAX_ITEMS_TO_PROCESS = 1000;
15
16 /**
17 * Removes invalid selected items by checking their structure and checking if they exist in the database for tables and cron jobs.
18 *
19 * @param string $items_type The type of items to validate.
20 * @param array $selected_items The selected items in this format [["items_type":"value", "site_id":"value", "name":"value", "cron_args":"value"],...].
21 * @param bool $keep_prefix Whether to keep the table prefix or not for tables.
22 *
23 * @return array The validated selected items in the same format.
24 */
25 public static function remove_invalid_selected_items( $items_type, $selected_items, $keep_prefix = true ) {
26
27 // validate items type
28 $items_type = ADBC_Common_Validator::sanitize_items_type( $items_type );
29 if ( empty( $items_type ) || ! is_array( $selected_items ) )
30 return [];
31
32 // Do not process if too many items are selected.
33 if ( count( $selected_items ) > self::MAX_ITEMS_TO_PROCESS )
34 return [];
35
36 $final_valid_selected_items = [];
37
38 // remove invalid selected items by checking their structure
39 $valid_structure_selected_items = self::remove_invalid_structure_selected_items( $items_type, $selected_items );
40
41 // remove invalid selected items by checking their names against the database for tables and cron jobs
42 if ( $items_type === 'tables' ) {
43
44 $final_valid_selected_items = self::remove_inexistent_tables_names( $valid_structure_selected_items, $keep_prefix );
45
46 } else if ( $items_type === 'cron_jobs' ) {
47
48 $final_valid_selected_items = self::remove_inexistent_cron_jobs( $valid_structure_selected_items );
49
50 } else if ( in_array( $items_type, [ 'transients', 'expired_transients' ], true ) ) {
51
52 $final_valid_selected_items = self::remove_inexistant_transients( $valid_structure_selected_items );
53
54 } else if ( in_array( $items_type, [ 'options', 'posts_meta', 'users_meta' ], true ) ) {
55
56 $final_valid_selected_items = self::remove_inexistent_items_names( $valid_structure_selected_items, $items_type );
57
58 } else if ( $items_type === 'post_types' ) {
59
60 $final_valid_selected_items = self::remove_inexistent_post_types( $valid_structure_selected_items );
61
62 } else {
63
64 // For all other types (in General Cleanup), just return the valid structure selected items because we are working with IDs not names.
65 $final_valid_selected_items = $valid_structure_selected_items;
66 }
67
68 return $final_valid_selected_items;
69 }
70
71 /**
72 * Remove invalid selected items by checking their structure.
73 *
74 * @param string $items_type The type of items to validate.
75 * @param array $selected_items The selected items in this format [["items_type":"value", "site_id":"value", "name":"value", "cron_args":"value"..],...].
76 *
77 * @return array The validated selected items in the same format.
78 */
79 private static function remove_invalid_structure_selected_items( $items_type, $selected_items ) {
80
81 $is_not_empty = function ($v) {
82 return ! empty( $v );
83 };
84 $is_in_array = function ($v) {
85 return in_array( $v, [ 'options', 'sitemeta' ], true );
86 };
87 $is_numeric = function ($v) {
88 return is_numeric( $v ) && $v > 0;
89 };
90 $is_array = function ($v) {
91 return is_array( $v );
92 };
93
94 // Key => validation-callback map for every item type
95 $schema = [
96
97 'tables' => [ 'name' => $is_not_empty ],
98
99 'cron_jobs' => [ 'site_id' => $is_numeric, 'name' => $is_not_empty, 'args' => $is_array, 'timestamp' => $is_numeric ],
100
101 'options' => [ 'site_id' => $is_numeric, 'id' => $is_numeric, 'name' => $is_not_empty ],
102
103 'post_types' => [ 'site_id' => $is_numeric, 'name' => $is_not_empty ],
104
105 'posts_meta' => [ 'site_id' => $is_numeric, 'id' => $is_numeric, 'name' => $is_not_empty ],
106 'users_meta' => [ 'site_id' => $is_numeric, 'id' => $is_numeric, 'name' => $is_not_empty ],
107
108 'transients' => [
109 'site_id' => $is_numeric,
110 'id' => $is_numeric,
111 'name' => $is_not_empty,
112 'found_in' => $is_in_array,
113 ],
114
115 'expired_transients' => [
116 'site_id' => $is_numeric,
117 'id' => $is_numeric,
118 'name' => $is_not_empty,
119 'found_in' => $is_in_array,
120 ],
121
122 'unused_relationships' => [
123 'site_id' => $is_numeric,
124 'id' => $is_numeric,
125 'term_taxonomy_id' => $is_numeric,
126 ],
127
128 // fallback: all “general-cleanup” types
129 'default' => [ 'site_id' => $is_numeric, 'id' => $is_numeric,],
130 ];
131
132 $rules = $schema[ $items_type ] ?? $schema['default'];
133
134 /*********************************************************************
135 * Build a new array that keeps only the rows whose structure is valid
136 * ******************************************************************/
137 $validRows = [];
138
139 foreach ( $selected_items as $row ) {
140
141 // 1. items_type must be present and equal to the context
142 if ( ! isset( $row['items_type'] ) || $row['items_type'] !== $items_type )
143 continue;
144
145 // 2. every required key must exist and pass its validator
146 $isValid = true;
147 foreach ( $rules as $key => $validator ) {
148 if ( ! isset( $row[ $key ] ) || ! $validator( $row[ $key ] ) ) {
149 $isValid = false;
150 break;
151 }
152 }
153
154 // 3. keep the row only if it passed every test
155 if ( $isValid )
156 $validRows[] = $row;
157 }
158
159 return $validRows;
160 }
161
162 /**
163 * Remove tables names that do not exist in the database.
164 *
165 * @param array $selected_tables The tables names in this format [["items_type":"tables", "name": "value"],...].
166 * @param bool $keep_prefix Whether to keep the table prefix or not.
167 *
168 * @return array The validated tables names selected by the user in the same format.
169 */
170 private static function remove_inexistent_tables_names( $selected_tables, $keep_prefix = true ) {
171
172 $batch_size = ADBC_Settings::instance()->get_setting( 'database_rows_batch' );
173 $offset = 0;
174 $valid_selected_tables = [];
175
176 // loop through the tables in batches and check the selected tables against the existing tables
177 while ( $tables_names_batch = ADBC_Tables::get_tables_names( $batch_size, $offset, $keep_prefix ) ) {
178
179 foreach ( $selected_tables as $selected_table ) {
180
181 if ( $keep_prefix === false )
182 $selected_table['name'] = ADBC_Tables::remove_prefix_from_table_name( $selected_table['name'] );
183
184 // if the table exists, add it to the valid tables
185 if ( key_exists( $selected_table['name'], $tables_names_batch ) )
186 $valid_selected_tables[] = $selected_table;
187
188 }
189
190 $offset += $batch_size;
191
192 }
193
194 return $valid_selected_tables;
195 }
196
197 /**
198 * Filter invalid cron jobs by checking them against the database.
199 *
200 * @param array $selected_cron_jobs The cron jobs names in this format [["items_type":"cron_jobs", "site_id":"value", "name":"value","cron_args":"value"],...].
201 *
202 * @return array The validated cron jobs selected by the user in the same format.
203 */
204 private static function remove_inexistent_cron_jobs( $selected_cron_jobs ) {
205
206 $valid_cron_jobs = [];
207
208 // create an associative array with the site id as the key and their selected cron jobs as the value
209 $siteid_keyed_cron_jobs = self::group_selected_items_by_site_id( $selected_cron_jobs );
210
211 // loop through the sites id selected cron jobs and check if they exist in the database
212 foreach ( $siteid_keyed_cron_jobs as $site_id => $site_selected_cron_jobs ) {
213
214 $site_cron_jobs = ADBC_Cron_Jobs::get_site_cron_jobs( $site_id );
215
216 // loop through the selected cron jobs and check if they exist in the database
217 foreach ( $site_selected_cron_jobs as $selected_cron_job ) {
218
219 foreach ( $site_cron_jobs as $site_cron_job ) {
220 if ( $site_cron_job->name === $selected_cron_job['name'] && $site_cron_job->args === $selected_cron_job['args'] && $site_cron_job->timestamp === $selected_cron_job['timestamp'] ) {
221 $valid_cron_jobs[] = $selected_cron_job;
222 break;
223 }
224 }
225
226 }
227
228 }
229
230 return $valid_cron_jobs;
231
232 }
233
234 /**
235 * Remove inexistent transients names from the selected items.
236 *
237 * @param array $selected_transients The selected transients in this format [["items_type":"transients", "site_id":"value", "name":"value", "found_in":"value"],...].
238 *
239 * @return array The validated selected transients in the same format.
240 */
241 private static function remove_inexistant_transients( $selected_transients ) {
242
243 $valid_transients = [];
244 $siteid_grouped_transients = self::group_selected_items_by_site_id( $selected_transients );
245
246 foreach ( $siteid_grouped_transients as $site_id => $site_group_transients ) {
247
248 // group the site grouped selected transients by their found_in value
249 $found_in_grouped_transients = [];
250 foreach ( $site_group_transients as $selected_transient ) {
251 $found_in_grouped_transients[ $selected_transient['found_in'] ][] = $selected_transient;
252 }
253
254 // loop through the found_in grouped transients to execute only one query per found_in value
255 foreach ( $found_in_grouped_transients as $found_in => $selected_transients ) {
256
257 // Prepare ids list to check against the database (Ids have already been validated in the structure validation step)
258 $ids = array_column( $selected_transients, 'id' );
259 $table_name = $found_in === 'sitemeta' ? 'sitemeta' : 'options';
260 $column_name = $found_in === 'sitemeta' ? 'meta_key' : 'option_name';
261 $id_column_name = $found_in === 'sitemeta' ? 'meta_id' : 'option_id';
262 $transients_names_in_db = ADBC_Common_Model::get_column_rows_from_table( $site_id, $table_name, $column_name, $id_column_name, $ids );
263
264 if ( empty( $transients_names_in_db ) )
265 continue;
266
267 // loop through the found_in grouped transients for the current site_id and check if they exist
268 foreach ( $selected_transients as $selected ) {
269 // If the transient name exists in the database, add it to the valid transients
270 if ( in_array( $selected['name'], $transients_names_in_db, true ) ) {
271 $valid_transients[] = $selected;
272 }
273 }
274
275 }
276
277 }
278
279 return $valid_transients;
280
281 }
282
283 /**
284 * Remove inexistent items names from the selected items. Works for options, posts_meta, users_meta, transients and expired_transients.
285 *
286 * @param array $selected_items The selected items in this format [["items_type":"value", "site_id":"value", "name":"value",...].
287 * @param string $items_type The type of items to validate. Can be 'options', 'posts_meta', 'users_meta', 'transients' or 'expired_transients'.
288 *
289 * @return array The validated selected items in the same format.
290 */
291 private static function remove_inexistent_items_names( $selected_items, $items_type ) {
292
293 switch ( $items_type ) {
294 case 'options':
295 $table_name = 'options';
296 $column_name = 'option_name';
297 $id_column_name = 'option_id';
298 break;
299 case 'posts_meta':
300 $table_name = 'postmeta';
301 $column_name = 'meta_key';
302 $id_column_name = 'meta_id';
303 break;
304 case 'users_meta':
305 $table_name = 'usermeta';
306 $column_name = 'meta_key';
307 $id_column_name = 'umeta_id';
308 break;
309 }
310
311 $valid_items = [];
312 $siteid_grouped_items = self::group_selected_items_by_site_id( $selected_items );
313
314 // loop through the sites id selected items and check if they exist in the database
315 foreach ( $siteid_grouped_items as $site_id => $group_selected ) {
316
317 // Prepare ids list to check against the database (Ids have already been validated in the structure validation step)
318 $ids = array_column( $group_selected, 'id' );
319
320 $items_names_in_db = ADBC_Common_Model::get_column_rows_from_table( $site_id, $table_name, $column_name, $id_column_name, $ids );
321
322 if ( empty( $items_names_in_db ) )
323 continue;
324
325 // loop through the selected items for the current site_id and check if they exist in the retrieved items names
326 foreach ( $group_selected as $selected ) {
327
328 // If the item name exists in the database, add it to the valid items
329 if ( in_array( $selected['name'], $items_names_in_db, true ) )
330 $valid_items[] = $selected;
331
332 }
333 }
334
335 return $valid_items;
336 }
337
338 /**
339 * Remove post types that do not exist in the posts table of the given site.
340 *
341 * @param array $selected_post_types [["items_type":"post_types", "site_id":"value", "name":"value"], ...].
342 *
343 * @return array The validated selected post types.
344 */
345 private static function remove_inexistent_post_types( $selected_post_types ) {
346
347 $valid = [];
348 $grouped = self::group_selected_items_by_site_id( $selected_post_types );
349
350 foreach ( $grouped as $site_id => $group ) {
351
352 $site_prefix = ADBC_Sites::instance()->get_prefix_from_site_id( $site_id );
353
354 if ( $site_prefix === null )
355 continue;
356
357 $existing = ADBC_Post_Types::get_all_post_type_names_for_site( $site_prefix );
358 $existing_map = array_flip( $existing );
359
360 foreach ( $group as $selected ) {
361 if ( isset( $existing_map[ $selected['name'] ] ) )
362 $valid[] = $selected;
363 }
364 }
365
366 return $valid;
367 }
368
369 /**
370 * Group selected items by site id as key.
371 *
372 * @param array $selected_items The selected items in this format [["items_type":"value", "site_id":"value", "name":"value", "cron_args":"value"],...].
373 *
374 * @return array The grouped selected items by site id in the same format.
375 */
376 public static function group_selected_items_by_site_id( $selected_items ) {
377
378 $grouped = [];
379 foreach ( $selected_items as $selected_item ) {
380 $grouped[ $selected_item['site_id'] ][] = $selected_item;
381 }
382
383 return $grouped;
384 }
385
386 }