PluginProbe ʕ •ᴥ•ʔ
Advanced Database Cleaner – Optimize & Clean Database to Speed Up Site Performance / 4.2.0
Advanced Database Cleaner – Optimize & Clean Database to Speed Up Site Performance v4.2.0
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 / models / class-adbc-post-types.php
advanced-database-cleaner / includes / models Last commit date
class-adbc-common-model.php 7 months ago class-adbc-cron-jobs.php 2 months ago class-adbc-database.php 5 months ago class-adbc-options.php 4 months ago class-adbc-post-types.php 3 months ago class-adbc-posts-meta.php 4 months ago class-adbc-sites.php 7 months ago class-adbc-tables.php 3 months ago class-adbc-transients.php 4 months ago class-adbc-users-meta.php 4 months ago
class-adbc-post-types.php
560 lines
1 <?php
2
3 // Exit if accessed directly
4 if ( ! defined( 'ABSPATH' ) )
5 exit;
6
7 /**
8 * ADBC post types model class.
9 *
10 * This class provides the post types model.
11 */
12 class ADBC_Post_Types {
13
14 private const NON_PUBLIC_POST_TYPES_POST_COUNT_THRESHOLD = 100; // Post types with more than this number of posts are considered noteworthy.
15
16 /**
17 * Get the post types list for the endpoint.
18 *
19 * @param array $filters Output of sanitize_filters().
20 *
21 * @return WP_REST_Response The list of post types.
22 */
23 public static function get_post_types_list( $filters ) {
24
25 global $wpdb;
26
27 $sites_list = ADBC_Sites::instance()->get_sites_list( $filters['site_id'] );
28
29 /* ──────────────────────────────────────────────────────────────
30 * 1. Collect post types per site from the database
31 * ─────────────────────────────────────────────────────────────*/
32 $post_types_list = [];
33
34 foreach ( $sites_list as $site ) {
35
36 $table_name = $site['prefix'] . 'posts';
37
38 $public_post_types_names = self::get_public_post_types_names_for_site( (int) $site['id'] );
39
40 $results = $wpdb->get_results(
41 "SELECT post_type, COUNT(*) AS posts_count
42 FROM {$table_name}
43 WHERE post_type != ''
44 GROUP BY post_type",
45 OBJECT
46 );
47
48 if ( ! empty( $results ) ) {
49
50 foreach ( $results as $row ) {
51 $post_types_list[] = (object) [
52 'name' => (string) $row->post_type,
53 'posts_count' => (int) $row->posts_count,
54 'site_id' => $site['id'],
55 'is_public' => isset( $public_post_types_names[ $row->post_type ] ),
56 ];
57 }
58
59 }
60
61 }
62
63 /* ──────────────────────────────────────────────────────────────
64 * 2. Load scan / hardcoded categorization onto items
65 * ─────────────────────────────────────────────────────────────*/
66 if ( ADBC_VERSION_TYPE === 'PREMIUM' )
67 ADBC_Scan_Results::instance()->load_scan_results_to_items_rows( $post_types_list, 'post_types' );
68 else
69 ADBC_Common_Model::load_scan_results_to_items_for_free_version( $post_types_list );
70
71 ADBC_Hardcoded_Items::instance()->load_hardcoded_scan_results_to_items_rows( $post_types_list, 'post_types' );
72
73 /* ──────────────────────────────────────────────────────────────
74 * 3. Apply filters (belongs_to, posts_count, search) and count categories
75 * ─────────────────────────────────────────────────────────────*/
76 $min_posts_count = $filters['post_types_posts_count'];
77 $search_for = strtolower( $filters['search_for'] );
78 $post_types_public = $filters['post_types_visibility'];
79
80 $scan_counter = new ADBC_Scan_Counter();
81
82 $filtered_post_types_list = [];
83
84 foreach ( $post_types_list as $post_type ) {
85
86 // Filter by posts count
87 if ( $min_posts_count > 0 && $post_type->posts_count <= $min_posts_count )
88 continue;
89
90 // Public / non-public filter
91 if ( $post_types_public === 'public' && ! $post_type->is_public )
92 continue;
93 if ( $post_types_public === 'non_public' && $post_type->is_public )
94 continue;
95
96 // Search filter
97 if ( $search_for !== '' && strpos( strtolower( $post_type->name ), $search_for ) === false )
98 continue;
99
100 $scan_counter->refresh_categorization_count( $post_type->belongs_to );
101
102 if ( ! ADBC_Common_Model::is_item_satisfies_belongs_to( $filters, $post_type->belongs_to ) )
103 continue;
104
105 $filtered_post_types_list[] = $post_type;
106
107 }
108
109 /* ──────────────────────────────────────────────────────────────
110 * 4. Sorting
111 * ─────────────────────────────────────────────────────────────*/
112 $sort_by = $filters['sort_by'];
113 $sort_order = $filters['sort_order'];
114 $allowed_columns = [
115 'name',
116 'posts_count',
117 'site_id',
118 'is_public',
119 ];
120
121 if ( in_array( $sort_by, $allowed_columns, true ) ) {
122 usort(
123 $filtered_post_types_list,
124 function ($a, $b) use ($sort_by, $sort_order) {
125 if ( $sort_by === 'posts_count' ) {
126 $cmp = $a->posts_count <=> $b->posts_count;
127 } elseif ( $sort_by === 'site_id' ) {
128 $cmp = $a->site_id <=> $b->site_id;
129 } elseif ( $sort_by === 'is_public' ) {
130 $cmp = (int) $a->is_public <=> (int) $b->is_public;
131 } else {
132 $cmp = strnatcasecmp( (string) $a->name, (string) $b->name );
133 }
134 return ( $sort_order === 'DESC' ) ? -$cmp : $cmp;
135 }
136 );
137 }
138
139 /* ──────────────────────────────────────────────────────────────
140 * 5. Pagination
141 * ─────────────────────────────────────────────────────────────*/
142 $total_post_types = count( $filtered_post_types_list );
143 $startRecord = ( $filters['current_page'] - 1 ) * $filters['items_per_page'];
144 $paginated_post_types_list = array_slice( $filtered_post_types_list, $startRecord, $filters['items_per_page'] );
145 $post_types_list = [];
146
147 foreach ( $paginated_post_types_list as $post_type ) {
148
149 $post_types_list[] = [
150 'composite_id' => [
151 'items_type' => 'post_types',
152 'site_id' => (int) $post_type->site_id,
153 'name' => $post_type->name,
154 ],
155 'name' => $post_type->name,
156 'posts_count' => $post_type->posts_count,
157 'site_id' => $post_type->site_id,
158 'is_public' => (bool) $post_type->is_public,
159 'belongs_to' => $post_type->belongs_to,
160 'known_plugins' => $post_type->known_plugins,
161 'known_themes' => $post_type->known_themes,
162 ];
163 }
164
165 // Dictionary names
166 if ( ADBC_VERSION_TYPE === 'PREMIUM' )
167 ADBC_Dictionary::add_missing_addons_names_from_dictionary( $post_types_list, $scan_counter, 'post_types' );
168
169 $total_real_pages = max( 1, ceil( $total_post_types / $filters['items_per_page'] ) );
170
171 return ADBC_Rest::success( "", [
172 'items' => $post_types_list,
173 'total_items' => $total_post_types,
174 'real_current_page' => min( $filters['current_page'], $total_real_pages ),
175 'categorization_count' => $scan_counter->get_categorization_count(),
176 'plugins_count' => $scan_counter->get_plugins_count(),
177 'themes_count' => $scan_counter->get_themes_count(),
178 ] );
179 }
180
181 /**
182 * Get all distinct post type names for a site.
183 * *
184 * @param string $site_prefix Site table prefix (e.g. wp_ or wp_2_).
185 *
186 * @return array Flat list of post type names.
187 */
188 public static function get_all_post_type_names_for_site( $site_prefix ) {
189
190 global $wpdb;
191
192 $table = $site_prefix . 'posts';
193
194 $names = $wpdb->get_col(
195 "SELECT DISTINCT post_type FROM `{$table}` WHERE post_type != ''"
196 );
197
198 return is_array( $names ) ? $names : [];
199
200 }
201
202 /**
203 * Get unique post type names across all sites.
204 *
205 * @return array Associative array of post type slugs.
206 */
207 public static function get_post_types_names() {
208
209 global $wpdb;
210
211 $all_post_types = [];
212
213 $sites = ADBC_Sites::instance()->get_sites_list();
214
215 foreach ( $sites as $site ) {
216
217 $table_name = $site['prefix'] . 'posts';
218
219 $post_types = $wpdb->get_col(
220 "SELECT DISTINCT post_type FROM {$table_name} WHERE post_type != ''"
221 );
222
223 $all_post_types = array_merge( $all_post_types, $post_types );
224
225 }
226
227 return array_fill_keys( $all_post_types, true );
228
229 }
230
231 /**
232 * Count the total number of post types that are not scanned.
233 *
234 * @return int Total not-scanned post types.
235 */
236 public static function count_total_not_scanned_post_types() {
237
238 $total_not_scanned = 0;
239
240 if ( ADBC_VERSION_TYPE === 'PREMIUM' )
241 $registered_post_types_dictionary = array_keys( ADBC_Registered_Post_Types_Dict_Tracker::load_dictionary_from_file() );
242
243 $sites_list = ADBC_Sites::instance()->get_sites_list();
244
245 foreach ( $sites_list as $site ) {
246
247 $post_types = self::get_all_post_type_names_for_site( $site['prefix'] );
248
249 if ( ADBC_VERSION_TYPE === 'PREMIUM' ) {
250 $post_types = array_diff( $post_types, $registered_post_types_dictionary ); // don't count the registered post types in dictionary as not scanned
251 $not_scanned_count = ADBC_Scan_Utils::count_not_scanned_items_in_list( 'post_types', $post_types );
252 } else {
253 $not_scanned_count = ADBC_Common_Model::count_not_scanned_items_in_list_for_free( 'post_types', $post_types );
254 }
255
256 $total_not_scanned += $not_scanned_count;
257
258 }
259
260 return $total_not_scanned;
261
262 }
263
264 /**
265 * Count the total number of non-public post types with a large number of posts.
266 *
267 * @return int Total count of non-public post types above the threshold.
268 */
269 public static function count_total_large_non_public_post_types() {
270
271 global $wpdb;
272
273 $total = 0;
274 $threshold = self::NON_PUBLIC_POST_TYPES_POST_COUNT_THRESHOLD;
275
276 $sites_list = ADBC_Sites::instance()->get_sites_list();
277
278 foreach ( $sites_list as $site ) {
279
280 $table_name = $site['prefix'] . 'posts';
281 $public_post_types_names = self::get_public_post_types_names_for_site( (int) $site['id'] );
282
283 $results = $wpdb->get_results(
284 $wpdb->prepare(
285 "SELECT post_type, COUNT(*) AS posts_count
286 FROM {$table_name}
287 WHERE post_type != ''
288 GROUP BY post_type
289 HAVING COUNT(*) >= %d",
290 $threshold
291 ),
292 OBJECT
293 );
294
295 if ( ! empty( $results ) ) {
296 foreach ( $results as $row ) {
297 if ( ! isset( $public_post_types_names[ $row->post_type ] ) ) {
298 $total++;
299 }
300 }
301 }
302 }
303
304 return $total;
305
306 }
307
308 /**
309 * Get total post types count.
310 *
311 * @return int Sum of unique post types per site across the network.
312 */
313 public static function get_total_post_types_count() {
314
315 global $wpdb;
316
317 $total_post_types = 0;
318
319 $sites_list = ADBC_Sites::instance()->get_sites_list();
320
321 foreach ( $sites_list as $site ) {
322
323 $table_name = $site['prefix'] . 'posts';
324
325 $site_unique_count = (int) $wpdb->get_var( "SELECT COUNT(DISTINCT post_type) FROM {$table_name}" );
326
327 $total_post_types += $site_unique_count;
328
329 }
330
331 return $total_post_types;
332
333 }
334
335 /**
336 * Get a paginated list of posts for a given post type in a given site.
337 *
338 * @param int $site_id The site ID.
339 * @param string $post_type The post type name.
340 * @param array $filters Sanitized filters (current_page, items_per_page, sort_by, sort_order).
341 *
342 * @return array Paginated result with items, total_items, and real_current_page.
343 */
344 public static function get_posts_rows( $site_id, $post_type, $filters ) {
345
346 global $wpdb;
347
348 // All filters are already validated, casted and defaulted at the endpoint level.
349 $current_page = $filters['current_page'];
350 $items_per_page = $filters['items_per_page'];
351 $sort_by = $filters['sort_by'];
352 $sort_order = $filters['sort_order'];
353
354 $prefix = ADBC_Sites::instance()->get_prefix_from_site_id( $site_id );
355 $table_name = $prefix . 'posts';
356
357 // Validate sort_by against real columns of the table. If invalid, do not sort.
358 $allowed_columns = $wpdb->get_col( "SHOW COLUMNS FROM {$table_name}", 0 );
359 $order_by_sql = '';
360
361 if ( in_array( $sort_by, $allowed_columns, true ) ) {
362 $order_by_sql = "ORDER BY `{$sort_by}` {$sort_order}";
363 }
364
365 // Total posts for this post type in this site.
366 $total_items = (int) $wpdb->get_var(
367 $wpdb->prepare(
368 "SELECT COUNT(*) FROM {$table_name} WHERE post_type = %s",
369 $post_type
370 )
371 );
372
373 $offset = ( $current_page - 1 ) * $items_per_page;
374
375 // Fetch the requested page of rows.
376 $rows = $wpdb->get_results(
377 $wpdb->prepare(
378 "SELECT * FROM {$table_name} WHERE post_type = %s {$order_by_sql} LIMIT %d OFFSET %d",
379 $post_type,
380 $items_per_page,
381 $offset
382 ),
383 ARRAY_A
384 );
385
386 $total_real_pages = $items_per_page > 0
387 ? max( 1, (int) ceil( $total_items / $items_per_page ) )
388 : 1;
389
390 return [
391 'items' => is_array( $rows ) ? $rows : [],
392 'total_items' => $total_items,
393 'real_current_page' => min( $current_page, $total_real_pages ),
394 ];
395
396 }
397
398 /**
399 * Delete all posts belonging to the selected post types, grouped by site ID.
400 *
401 * Expected input shape:
402 * [
403 * 1 => [ [ 'name' => 'my_cpt' ], ... ],
404 * 2 => [ [ 'name' => 'another_cpt' ], ... ],
405 * ]
406 *
407 * @param array $grouped_selected Post types grouped by site ID.
408 * @return array Post type names that could not be fully purged.
409 */
410 public static function delete_posts( $grouped_selected ) {
411
412 $cleanup_method = ADBC_Settings::instance()->get_setting( 'sql_or_native_cleanup_method' );
413
414 if ( $cleanup_method === 'native' )
415 return self::delete_posts_native( $grouped_selected );
416
417 return self::delete_posts_sql( $grouped_selected );
418
419 }
420
421 /**
422 * Delete posts using WordPress native wp_delete_post().
423 *
424 * @param array $grouped_selected Post types grouped by site ID.
425 * @return array Post type names that could not be fully purged.
426 */
427 protected static function delete_posts_native( $grouped_selected ) {
428
429 global $wpdb;
430
431 $not_processed = [];
432
433 foreach ( $grouped_selected as $site_id => $group ) {
434
435 ADBC_Sites::instance()->switch_to_blog_id( $site_id );
436
437 foreach ( $group as $selected ) {
438
439 $post_type = $selected['name'];
440
441 $post_ids = $wpdb->get_col(
442 $wpdb->prepare(
443 "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s",
444 $post_type
445 )
446 );
447
448 if ( empty( $post_ids ) )
449 continue;
450
451 $has_failure = false;
452
453 foreach ( $post_ids as $post_id ) {
454 $result = wp_delete_post( (int) $post_id, true );
455 if ( ! $result )
456 $has_failure = true;
457 }
458
459 if ( $has_failure ) {
460 $remaining = (int) $wpdb->get_var(
461 $wpdb->prepare(
462 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s",
463 $post_type
464 )
465 );
466 if ( $remaining > 0 )
467 $not_processed[] = $post_type;
468 }
469 }
470
471 ADBC_Sites::instance()->restore_blog();
472 }
473
474 return $not_processed;
475
476 }
477
478 /**
479 * Delete posts using direct SQL for each post type per site.
480 *
481 * @param array $grouped_selected Post types grouped by site ID.
482 * @return array Post type names that could not be fully purged.
483 */
484 protected static function delete_posts_sql( $grouped_selected ) {
485
486 global $wpdb;
487
488 $not_processed = [];
489
490 foreach ( $grouped_selected as $site_id => $group ) {
491
492 $prefix = ADBC_Sites::instance()->get_prefix_from_site_id( $site_id );
493 $table_name = $prefix . 'posts';
494
495 foreach ( $group as $selected ) {
496
497 $post_type = $selected['name'];
498
499 $wpdb->query(
500 $wpdb->prepare(
501 "DELETE FROM {$table_name} WHERE post_type = %s",
502 $post_type
503 )
504 );
505
506 $remaining = (int) $wpdb->get_var(
507 $wpdb->prepare(
508 "SELECT COUNT(*) FROM {$table_name} WHERE post_type = %s",
509 $post_type
510 )
511 );
512
513 if ( $remaining > 0 )
514 $not_processed[] = $post_type;
515 }
516
517 }
518
519 return $not_processed;
520
521 }
522
523 /**
524 * Get public post types names for a given site.
525 *
526 * @param int $site_id The site ID.
527 * @return array Associative array of public post types names as keys and true as values.
528 */
529 protected static function get_public_post_types_names_for_site( $site_id ) {
530
531 // Cache the public post types names for each site to avoid multiple calls to the database if the site public post types names were already fetched.
532 static $cache = [];
533
534 if ( isset( $cache[ $site_id ] ) ) {
535 return $cache[ $site_id ];
536 }
537
538 $sites = ADBC_Sites::instance();
539
540 $sites->switch_to_blog_id( $site_id );
541
542 $public_post_types = get_post_types(
543 [
544 'public' => true,
545 ],
546 'names'
547 );
548
549 $sites->restore_blog();
550
551 $cache[ $site_id ] = is_array( $public_post_types )
552 ? array_fill_keys( $public_post_types, true )
553 : [];
554
555 return $cache[ $site_id ];
556
557 }
558
559 }
560