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 / classes / class-adbc-migration.php
advanced-database-cleaner / includes / classes Last commit date
addons 6 days ago general-cleanup 6 days ago class-adbc-admin-init.php 6 days ago class-adbc-automation.php 6 days ago class-adbc-hardcoded-items.php 6 days ago class-adbc-migration.php 6 days ago class-adbc-routes.php 6 days ago class-adbc-scan-counter.php 7 months ago class-adbc-settings.php 6 days ago class-adbc-singleton.php 7 months ago
class-adbc-migration.php
1065 lines
1 <?php
2
3 // Exit if accessed directly
4 if ( ! defined( 'ABSPATH' ) )
5 exit;
6
7 /**
8 * ADBC Migration.
9 *
10 * This class provides methods for the migration process.
11 */
12 class ADBC_Migration {
13
14 /**
15 * The automation task frequency mapping.
16 *
17 * @var array Old is key, new is value.
18 */
19 private const FREQUENCY_MAPPING = [
20 'once' => 'adbc_once',
21 'hourly' => 'adbc_hourly',
22 'twicedaily' => 'adbc_twicedaily',
23 'daily' => 'adbc_daily',
24 'weekly' => 'adbc_weekly',
25 'monthly' => 'adbc_monthly',
26 'yearly' => 'adbc_yearly',
27 ];
28
29 /**
30 * The automation task operations mapping.
31 *
32 * @var array Old is key, new is value.
33 */
34 private const OPERATIONS_MAPPING = [
35 "revision" => "revisions",
36 "auto-draft" => "auto_drafts",
37 "trash-posts" => "trashed_posts",
38 "moderated-comments" => "unapproved_comments",
39 "spam-comments" => "spam_comments",
40 "trash-comments" => "trashed_comments",
41 "pingbacks" => "pingbacks",
42 "trackbacks" => "trackbacks",
43 "orphan-postmeta" => "unused_postmeta",
44 "orphan-commentmeta" => "unused_commentmeta",
45 "orphan-usermeta" => "unused_usermeta",
46 "orphan-termmeta" => "unused_termmeta",
47 "orphan-relationships" => "unused_relationships",
48 "expired-transients" => "expired_transients",
49 "optimize" => "tables_to_optimize",
50 "repair" => "tables_to_repair",
51 ];
52
53 /**
54 * Get the available migration data for a given data segment.
55 *
56 * @param string $data_type One of 'all', 'free', or 'pro'. Controls which
57 * group of data to check for availability.
58 *
59 * @return array The available migration data keys. Possible values:
60 * 'keep_last', 'automation_tasks', 'manual_corrections',
61 * 'old_free_exists', 'pro_exists'.
62 */
63 public static function get_available_migration_data( $data_type = 'all' ) {
64
65 $available_data = [];
66
67 // Get the pro data, always get the data that is not stored in the database
68 if ( $data_type === 'all' || $data_type === 'pro' ) {
69
70 // Check if there are manual corrections if we are in premium
71 if ( ADBC_VERSION_TYPE === 'PREMIUM' && ADBC_Common_Utils::is_old_pro_exists() ) {
72 if ( self::is_old_manual_corrections_exists() ) {
73 $available_data[] = 'manual_corrections';
74 }
75 }
76
77 // Check if there are installed old free version
78 if ( ADBC_VERSION_TYPE === 'PREMIUM' && ADBC_Common_Utils::is_old_free_exists() ) {
79 $available_data[] = 'old_free_exists';
80 }
81
82 // Check if there are installed pro version
83 if ( ADBC_VERSION_TYPE === 'PREMIUM' && ADBC_Common_Utils::is_old_pro_exists() ) {
84 $available_data[] = 'pro_exists';
85 }
86
87 }
88
89 $old_settings = get_option( 'aDBc_settings', [] );
90
91 // If the old settings are not found, return the available non database data
92 if ( empty( $old_settings ) || ! is_array( $old_settings ) || ! isset( $old_settings['plugin_version'] ) ) {
93 return $available_data;
94 }
95
96 // Get the free data
97 if ( $data_type === 'all' || $data_type === 'free' ) {
98
99 // Check if there are keep last settings
100 if ( isset( $old_settings['keep_last'] ) && is_array( $old_settings['keep_last'] ) && count( $old_settings['keep_last'] ) > 0 ) {
101 $available_data[] = 'keep_last';
102 }
103
104 // Check if there are cleaning tasks
105 $has_automation_tasks = false;
106 $cleaning_tasks = get_option( 'aDBc_clean_schedule', [] );
107 if ( ! empty( $cleaning_tasks ) && is_array( $cleaning_tasks ) && count( $cleaning_tasks ) > 0 ) {
108 $has_automation_tasks = true;
109 }
110
111 // Check if there are optimization tasks
112 $optimization_tasks = get_option( 'aDBc_optimize_schedule', [] );
113 if ( ! empty( $optimization_tasks ) && is_array( $optimization_tasks ) && count( $optimization_tasks ) > 0 ) {
114 $has_automation_tasks = true;
115 }
116
117 if ( $has_automation_tasks ) {
118 $available_data[] = 'automation_tasks';
119 }
120
121 }
122
123 return $available_data;
124
125 }
126
127 /**
128 * Run the migration.
129 *
130 * @param array $items_to_migrate Array of strings indicating which items to migrate.
131 * Supported values: 'automation_tasks', 'keep_last', 'manual_corrections'.
132 * @param bool $uninstall_old_versions Whether to uninstall the old plugin versions after migration (premium only).
133 * @param string $data_type One of 'all', 'free', or 'pro'. Controls which data groups are processed.
134 *
135 * @return array Result map; keys present depend on requested items. Possible keys:
136 * 'automation_tasks_success' (0|1|2), 'keep_last_success' (0|1|2),
137 * 'manual_corrections_success' (0|1|2), 'uninstall_old_versions_success' (0|1|2).
138 */
139 public static function run( $items_to_migrate, $uninstall_old_versions = false, $data_type = 'all' ) {
140
141 $results = [];
142
143 $old_settings = get_option( 'aDBc_settings', false );
144
145 // Migrate free data
146 if ( $old_settings !== false ) { // Try to migrate free data for requested data type only if the old settings exist
147
148 if ( in_array( $data_type, [ 'all', 'free' ], true ) ) {
149
150 // Migrate automation tasks if any
151 if ( array_search( 'automation_tasks', $items_to_migrate ) !== false ) {
152
153 $automation_cleaning_tasks_success = 0;
154 $automation_optimization_tasks_success = 0;
155
156 // Migrate cleaning tasks if any
157 $cleaning_tasks = get_option( 'aDBc_clean_schedule' );
158 if ( is_array( $cleaning_tasks ) && count( $cleaning_tasks ) > 0 )
159 $automation_cleaning_tasks_success = self::migrate_cleaning_tasks( $cleaning_tasks );
160 elseif ( $cleaning_tasks === false )
161 $automation_cleaning_tasks_success = null;
162
163 // Migrate optimization tasks if any
164 $optimization_tasks = get_option( 'aDBc_optimize_schedule' );
165 if ( is_array( $optimization_tasks ) && count( $optimization_tasks ) > 0 )
166 $automation_optimization_tasks_success = self::migrate_optimization_tasks( $optimization_tasks );
167 elseif ( $optimization_tasks === false )
168 $automation_optimization_tasks_success = null;
169
170 // Return the success result of the automation tasks combined
171 if ( $automation_cleaning_tasks_success === 0 && $automation_optimization_tasks_success === 0 )
172 $results['automation_tasks_success'] = 0; // Both Tasks types exists but no tasks were migrated
173 elseif ( $automation_cleaning_tasks_success === 1 && $automation_optimization_tasks_success === 1 )
174 $results['automation_tasks_success'] = 1; // Both Tasks types exists, and all tasks were migrated
175 elseif ( $automation_cleaning_tasks_success === null && $automation_optimization_tasks_success === null )
176 $results['automation_tasks_success'] = 0; // No tasks exist
177 elseif ( $automation_cleaning_tasks_success === null && $automation_optimization_tasks_success !== null )
178 $results['automation_tasks_success'] = $automation_optimization_tasks_success; // Only optimization tasks exists, return the success result of the optimization tasks
179 elseif ( $automation_cleaning_tasks_success !== null && $automation_optimization_tasks_success === null )
180 $results['automation_tasks_success'] = $automation_cleaning_tasks_success; // Only cleaning tasks exists, return the success result of the cleaning tasks
181 else
182 $results['automation_tasks_success'] = 2; // Both Tasks types exists, and some tasks were not migrated
183
184 // Add the imported_tasks_deactivated_notice if some tasks were migrated
185 if ( in_array( $automation_cleaning_tasks_success, [ 1, 2 ], true ) || in_array( $automation_optimization_tasks_success, [ 1, 2 ], true ) )
186 ADBC_Notifications::instance()->add_notification( 'imported_tasks_deactivated_notice' );
187
188 }
189
190 // Migrate keep last settings if any
191 if ( array_search( 'keep_last', $items_to_migrate ) !== false ) {
192
193 if ( is_array( $old_settings ) && isset( $old_settings['keep_last'] ) && is_array( $old_settings['keep_last'] ) && count( $old_settings['keep_last'] ) > 0 )
194 $results['keep_last_success'] = self::migrate_keep_last_settings( $old_settings['keep_last'] );
195 else
196 $results['keep_last_success'] = 0;
197
198 }
199
200 }
201
202 } else { // If the old settings do not exist, return 0 for both automation tasks and keep last if requested
203
204 if ( array_search( 'automation_tasks', $items_to_migrate ) !== false )
205 $results['automation_tasks_success'] = 0;
206
207 if ( array_search( 'keep_last', $items_to_migrate ) !== false )
208 $results['keep_last_success'] = 0;
209
210 }
211
212 // Migrate pro data
213 if ( in_array( $data_type, [ 'all', 'pro' ], true ) ) {
214
215 // Migrate manual corrections if any and if we are in premium
216 if ( ADBC_VERSION_TYPE === 'PREMIUM' && array_search( 'manual_corrections', $items_to_migrate ) !== false ) {
217 $results['manual_corrections_success'] = ADBC_Common_Utils::is_old_pro_data_exists() ? self::migrate_manual_corrections() : 0;
218 }
219
220 // Uninstall old versions if requested
221 if ( ADBC_VERSION_TYPE === 'PREMIUM' && $uninstall_old_versions ) {
222 $results['uninstall_old_versions_success'] = self::uninstall_old_versions();
223 }
224
225 }
226
227 // In premium, dismiss the migration available notification; and delete the old versions data depending on their existence
228 if ( ADBC_VERSION_TYPE === 'PREMIUM' && ADBC_IS_PRO_VERSION === false ) {
229
230 ADBC_Notifications::instance()->dismiss_notification( 'migration_available' );
231
232 $old_free_exists = ADBC_Common_Utils::is_old_free_exists();
233 $old_pro_exists = ADBC_Common_Utils::is_old_pro_exists();
234
235 if ( $old_free_exists && ! $old_pro_exists )
236 self::delete_old_pro_data();
237 elseif ( ! $old_free_exists && ! $old_pro_exists )
238 self::delete_all_old_data();
239
240 }
241
242 return $results;
243
244 }
245
246 /**
247 * Run the free migration.
248 *
249 * Invoked on 'init' in the free version to migrate automation tasks and
250 * "keep last" settings from older versions if needed.
251 *
252 * @return void
253 */
254 public static function run_free_migration() {
255
256 try {
257
258 // Migrate free data if the free migration is not done
259 if ( ADBC_Settings::instance()->get_setting( 'free_migration_done' ) === '0' ) {
260
261 // Don't migrate if the premium already migrated
262 if ( ADBC_Common_Utils::is_premium_exists() && adbc_notifications::instance()->is_notification_dismissed( 'migration_available' ) ) {
263 if ( ! ADBC_Common_Utils::is_old_pro_exists() )
264 self::delete_all_old_data();
265 return;
266 }
267
268 self::run( [ 'automation_tasks', 'keep_last' ], false, 'free' );
269
270 ADBC_Settings::instance()->update_settings( [ 'free_migration_done' => '1' ] );
271
272 if ( ! ADBC_Common_Utils::is_old_pro_exists() )
273 self::delete_all_old_data();
274
275 }
276
277 } catch (Exception $e) {
278 return ADBC_Logging::log_error( 'ADBC Migration: Failed to migrate free data - ' . $e->getMessage() );
279 }
280
281 }
282
283 /**
284 * Run the pro migration.
285 *
286 * @return void
287 */
288 public static function run_pro_migration() {
289
290 try {
291
292 // Migrate and activate the license key if the old license key exists and it's not already activated in the current new pro version
293 if ( ! isset( ADBC_License_Manager::get_license_data( false )['key'] ) && self::is_old_license_exists() ) {
294
295 $old_license_key = get_option( 'aDBc_edd_license_key' );
296
297 // If the old license key is empty, don't migrate
298 if ( empty( $old_license_key ) ) {
299 return;
300 }
301
302 // Send activation request to EDD to activate this old license in the current version
303 ADBC_License_Manager::activate_license( $old_license_key );
304
305 // delete the old license key
306 delete_option( 'aDBc_edd_license_key' );
307
308 }
309
310 // Premium exists or migration available notification is dismissed, don't migrate
311 if ( ADBC_Notifications::instance()->is_notification_dismissed( 'migration_available' ) || ADBC_Common_Utils::is_premium_exists() )
312 return;
313
314 // old pro data doesn't exist, add and dismiss the migration available notification explicitly to avoid further checks
315 if ( ! ADBC_Common_Utils::is_old_pro_data_exists() ) {
316 ADBC_Notifications::instance()->add_notification( 'migration_available' );
317 ADBC_Notifications::instance()->dismiss_notification( 'migration_available' );
318 return;
319 }
320
321 // run the migration for all data
322 self::run( [ 'manual_corrections', 'automation_tasks', 'keep_last' ] );
323
324 // Mark the free migration as done
325 ADBC_Settings::instance()->update_settings( [ 'free_migration_done' => '1' ] );
326
327 // Dismiss the migration available notification
328 ADBC_Notifications::instance()->dismiss_notification( 'migration_available' );
329
330 // delete old pro data if old free exists or delete all old data if old free doesn't exist
331 if ( ADBC_Common_Utils::is_old_free_exists() )
332 self::delete_old_pro_data();
333 else
334 self::delete_all_old_data();
335
336 } catch (Exception $e) {
337 return ADBC_Logging::log_error( 'ADBC Migration: Failed to migrate pro data - ' . $e->getMessage() );
338 }
339
340 }
341
342 /**
343 * Migrate the cleaning tasks.
344 *
345 * @param array $cleaning_tasks The cleaning tasks.
346 *
347 * @return int 0 if failed, 1 if success, 2 if some of the cleaning tasks were not migrated
348 */
349 private static function migrate_cleaning_tasks( $cleaning_tasks ) {
350
351 $existing_tasks = ADBC_Automation::instance()->tasks();
352
353 $total_migrated_cleaning_tasks = 0;
354
355 foreach ( $cleaning_tasks as $old_task_name => $old_task_details ) {
356
357 if ( self::validate_old_automation_task_structure( $old_task_name, $old_task_details ) === false ) {
358 continue;
359 }
360
361 $new_task = [
362 'type' => 'general_cleanup',
363 'name' => $old_task_name,
364 'frequency' => self::FREQUENCY_MAPPING[ $old_task_details['repeat'] ],
365 'start_datetime' => self::get_timestamp_from_old_date_and_time( $old_task_details['start_date'], $old_task_details['start_time'] ),
366 'operations' => self::migrate_old_operations( $old_task_details['elements_to_clean'] ),
367 'active' => false, // Always deactivate the task
368 ];
369
370 if ( self::is_task_already_exists( $new_task, $existing_tasks ) ) {
371 continue;
372 }
373
374 if ( ADBC_Automation::instance()->create( $new_task ) !== null )
375 $total_migrated_cleaning_tasks++;
376
377 }
378
379 // If no cleaning tasks were migrated, return 0
380 if ( $total_migrated_cleaning_tasks === 0 )
381 return 0;
382
383 // If all cleaning tasks were migrated, return 1, otherwise if it's mixed, return 2
384 return $total_migrated_cleaning_tasks === count( $cleaning_tasks ) ? 1 : 2;
385
386 }
387
388 /**
389 * Migrate the optimization tasks.
390 *
391 * @param array $optimization_tasks The optimization tasks.
392 *
393 * @return int 0 if failed, 1 if success, 2 if some of the optimization tasks were not migrated
394 */
395 private static function migrate_optimization_tasks( $optimization_tasks ) {
396
397 $existing_tasks = ADBC_Automation::instance()->tasks();
398
399 $total_migrated_optimization_tasks = 0;
400
401 foreach ( $optimization_tasks as $old_task_name => $old_task_details ) {
402
403 if ( self::validate_old_automation_task_structure( $old_task_name, $old_task_details ) === false ) {
404 continue;
405 }
406
407 $new_task = [
408 'type' => 'general_cleanup',
409 'name' => $old_task_name,
410 'frequency' => self::FREQUENCY_MAPPING[ $old_task_details['repeat'] ],
411 'start_datetime' => self::get_timestamp_from_old_date_and_time( $old_task_details['start_date'], $old_task_details['start_time'] ),
412 'operations' => self::migrate_old_operations( $old_task_details['operations'] ),
413 'active' => false, // Always deactivate the task
414 ];
415
416 if ( self::is_task_already_exists( $new_task, $existing_tasks ) ) {
417 continue;
418 }
419
420 if ( ADBC_Automation::instance()->create( $new_task ) !== null )
421 $total_migrated_optimization_tasks++;
422
423 }
424
425 // If no optimization tasks were migrated, return 0
426 if ( $total_migrated_optimization_tasks === 0 )
427 return 0;
428
429 // If all optimization tasks were migrated, return 1, otherwise if it's mixed, return 2
430 return $total_migrated_optimization_tasks === count( $optimization_tasks ) ? 1 : 2;
431
432 }
433
434 /**
435 * Check if a task with the same name, type, frequency, start datetime and operations already exists in the existing tasks.
436 *
437 * @param array $new_task The new task to check.
438 * @param array $existing_tasks The existing tasks to check against.
439 *
440 * @return bool True if a task with the same name, type, frequency, start datetime and operations already exists, false if it doesn't
441 */
442 private static function is_task_already_exists( $new_task, $existing_tasks ) {
443
444 foreach ( $existing_tasks as $existing_task ) {
445 if ( $existing_task['name'] === $new_task['name'] && $existing_task['type'] === $new_task['type'] && $existing_task['frequency'] === $new_task['frequency'] && $existing_task['start_datetime'] === $new_task['start_datetime'] && $existing_task['operations'] == $new_task['operations'] ) {
446 return true;
447 }
448 }
449
450 return false;
451
452 }
453
454 /**
455 * Get the old keep last value for item type.
456 *
457 * @param string $item_type The item type.
458 *
459 * @return int The old keep last value for item type.
460 */
461 private static function get_old_keep_last_for_item_type( $item_type ) {
462
463 $old_settings = get_option( 'aDBc_settings', [] );
464
465 if ( isset( $old_settings['keep_last'] ) && is_array( $old_settings['keep_last'] ) ) {
466 return isset( $old_settings['keep_last'][ $item_type ] ) ? intval( $old_settings['keep_last'][ $item_type ] ) : 0;
467 }
468
469 return 0;
470
471 }
472
473 /**
474 * Migrate the keep last settings.
475 *
476 * @param array $old_keep_last_settings The old keep last settings.
477 *
478 * @return int 0 if failed, 1 if success, 2 if some of the keep last settings were not migrated
479 */
480 private static function migrate_keep_last_settings( $old_keep_last_settings ) {
481
482 $new_keep_last_settings = [];
483 $total_old_keep_last_settings_with_non_zero_value = 0;
484 $total_old_keep_last_settings_with_value_of_0 = 0;
485
486 foreach ( $old_keep_last_settings as $old_item_type => $old_value ) {
487
488 // Skip 0 value, in the new structure we just don't store keep last settings for items types with a value of 0
489 if ( intval( $old_value ) <= 0 ) {
490 $total_old_keep_last_settings_with_value_of_0++;
491 continue;
492 }
493
494 $total_old_keep_last_settings_with_non_zero_value++;
495
496 if ( isset( self::OPERATIONS_MAPPING[ $old_item_type ] ) ) {
497
498 $new_item_type = self::OPERATIONS_MAPPING[ $old_item_type ];
499 $new_keep_last_settings[ $new_item_type ] = [
500 'type' => 'days',
501 'value' => intval( $old_value ),
502 ];
503
504 }
505
506 }
507
508 // If all old keep last were skipped because they had a value of 0, return 1
509 if ( count( $old_keep_last_settings ) === $total_old_keep_last_settings_with_value_of_0 )
510 return 1;
511
512 // if no new keep last is validated, return 0
513 if ( count( $new_keep_last_settings ) === 0 )
514 return 0;
515
516 ADBC_General_Cleanup::set_keep_last( $new_keep_last_settings );
517
518 // If some non zero value keep last were not migrated, return 2
519 if ( $total_old_keep_last_settings_with_non_zero_value !== count( $new_keep_last_settings ) )
520 return 2;
521
522 // If all non zero value keep last were migrated, return 1
523 return 1;
524
525 }
526
527 /**
528 * Migrate the manual corrections.
529 *
530 * @return int 0 if failed, 1 if success, 2 if some of the manual corrections were not valid
531 */
532 private static function migrate_manual_corrections() {
533
534 $security_code = get_option( 'aDBc_security_folder_code' );
535 $uploads = wp_upload_dir();
536 $old_adbc_upload_dir = $uploads['basedir'] . '/adbc_uploads_' . $security_code;
537
538 if ( ! ADBC_Files::instance()->exists( $old_adbc_upload_dir ) ) {
539 return 0;
540 }
541
542 $old_cron_jobs_file_path = $old_adbc_upload_dir . "/tasks_corrected_manually.txt";
543 $old_options_file_path = $old_adbc_upload_dir . "/options_corrected_manually.txt";
544 $old_tables_file_path = $old_adbc_upload_dir . "/tables_corrected_manually.txt";
545
546 $results = [];
547
548 if ( ADBC_Files::instance()->exists( $old_cron_jobs_file_path ) ) {
549 $manual_corrections = ADBC_Files::instance()->get_contents( $old_cron_jobs_file_path );
550 $manual_corrections = json_decode( $manual_corrections, true );
551 if ( is_array( $manual_corrections ) && ! empty( $manual_corrections ) ) {
552 $results[] = self::write_old_version_manual_corrections_to_file( 'cron_jobs', $manual_corrections );
553 }
554 }
555
556 if ( ADBC_Files::instance()->exists( $old_options_file_path ) ) {
557 $manual_corrections = ADBC_Files::instance()->get_contents( $old_options_file_path );
558 $manual_corrections = json_decode( $manual_corrections, true );
559 if ( is_array( $manual_corrections ) && ! empty( $manual_corrections ) ) {
560 $results[] = self::write_old_version_manual_corrections_to_file( 'options', $manual_corrections );
561 }
562 }
563
564 if ( ADBC_Files::instance()->exists( $old_tables_file_path ) ) {
565 $manual_corrections = ADBC_Files::instance()->get_contents( $old_tables_file_path );
566 $manual_corrections = json_decode( $manual_corrections, true );
567 if ( is_array( $manual_corrections ) && ! empty( $manual_corrections ) ) {
568 $results[] = self::write_old_version_manual_corrections_to_file( 'tables', $manual_corrections );
569 }
570 }
571
572 $number_of_successes = count( array_filter( $results, function ($result) {
573 return $result === 1;
574 } ) );
575 $number_of_failures = count( array_filter( $results, function ($result) {
576 return $result === 0;
577 } ) );
578
579 // If there are no results or all results are failures, return 0
580 if ( empty( $results ) || ( $number_of_failures === count( $results ) ) )
581 return 0;
582
583 // If all results are successes, return 1
584 if ( $number_of_successes === count( $results ) )
585 return 1;
586
587 // If the results are mixed, return 2
588 return 2;
589
590 }
591
592 /**
593 * Uninstall the old versions.
594 *
595 * @return int 0 if failed, 1 if success, 2 if some of the old versions were not uninstalled
596 */
597 private static function uninstall_old_versions() {
598
599 // Ensure current user has permission to remove plugins
600 if ( ! current_user_can( 'delete_plugins' ) ) {
601 return 0;
602 }
603
604 // Respect environments where file modifications are disabled
605 if ( defined( 'DISALLOW_FILE_MODS' ) && constant( 'DISALLOW_FILE_MODS' ) ) {
606 return 0;
607 }
608
609 // Load WordPress plugin management functions once
610 if (
611 ! function_exists( 'delete_plugins' ) ||
612 ! function_exists( 'deactivate_plugins' ) ||
613 ! function_exists( 'is_plugin_active' ) ||
614 ! function_exists( 'is_plugin_active_for_network' )
615 ) {
616 require_once ABSPATH . 'wp-admin/includes/plugin.php';
617 }
618
619 if ( ADBC_Common_Utils::is_old_free_exists() )
620 $plugins_files[] = 'advanced-database-cleaner/advanced-db-cleaner.php';
621 if ( ADBC_Common_Utils::is_old_pro_exists() )
622 $plugins_files[] = 'advanced-database-cleaner-pro/advanced-db-cleaner.php';
623
624 $final_success = [];
625
626 foreach ( $plugins_files as $plugin_file ) {
627
628 // Deactivate if active (site or network) before deletion; silence hooks
629 $network_wide = function_exists( 'is_plugin_active_for_network' ) && is_plugin_active_for_network( $plugin_file );
630 if ( function_exists( 'deactivate_plugins' ) ) {
631 deactivate_plugins( [ $plugin_file ], false, $network_wide );
632 }
633
634 // Attempt deletion and log any failures
635 if ( function_exists( 'delete_plugins' ) ) {
636 $result = delete_plugins( [ $plugin_file ] );
637 if ( is_wp_error( $result ) || $result === false || $result === null ) {
638 $final_success[] = 0;
639 } else {
640 $final_success[] = 1;
641 }
642 }
643
644 }
645
646 // Remove the old version data from the database.
647 self::delete_all_old_data();
648
649 $number_of_successes = count( array_filter( $final_success, function ($result) {
650 return $result === 1;
651 } ) );
652 $number_of_failures = count( array_filter( $final_success, function ($result) {
653 return $result === 0;
654 } ) );
655
656 // If there are no results or all results are failures, return 0
657 if ( empty( $final_success ) || ( $number_of_failures === count( $plugins_files ) ) )
658 return 0;
659
660 // If all results are successes, return 1, otherwise if it's mixed, return 2
661 return $number_of_successes === count( $plugins_files ) ? 1 : 2;
662
663 }
664
665 /**
666 * Validate the old automation task structure.
667 *
668 * @param string $old_task_name The old task name.
669 * @param array $old_task_details The old task details.
670 *
671 * @return bool True if the old automation task structure is valid, false if it's not
672 */
673 private static function validate_old_automation_task_structure( $old_task_name, $old_task_details ) {
674
675 // Validate name against regex
676 if ( ! is_string( $old_task_name ) || ! preg_match( '/^[a-zA-Z0-9_]+$/', $old_task_name ) ) {
677 return false;
678 }
679
680 // Basic shape
681 if ( ! is_array( $old_task_details ) || empty( $old_task_details ) ) {
682 return false;
683 }
684
685 // Validate repeat/frequency
686 $valid_repeats = array_keys( self::FREQUENCY_MAPPING );
687 $repeat = $old_task_details['repeat'] ?? null;
688 if ( ! is_string( $repeat ) || ! in_array( $repeat, $valid_repeats, true ) ) {
689 return false;
690 }
691
692 // Validate date and time
693 $start_date = $old_task_details['start_date'] ?? null;
694 $start_time = $old_task_details['start_time'] ?? null;
695 if ( ! is_string( $start_date ) || ! is_string( $start_time ) ) {
696 return false;
697 }
698 $dt = DateTime::createFromFormat( 'Y-m-d H:i', trim( $start_date . ' ' . $start_time ), new DateTimeZone( 'UTC' ) );
699 if ( ! ( $dt instanceof DateTime ) ) {
700 return false;
701 }
702
703 // Validate active flag (0|1)
704 if ( ! array_key_exists( 'active', $old_task_details ) ) {
705 return false;
706 }
707 $active = (int) $old_task_details['active'];
708 if ( $active !== 0 && $active !== 1 ) {
709 return false;
710 }
711
712 // Validate operations/elements depending on task type
713 $has_elements_to_clean = isset( $old_task_details['elements_to_clean'] ) && is_array( $old_task_details['elements_to_clean'] );
714 $has_operations = isset( $old_task_details['operations'] ) && is_array( $old_task_details['operations'] );
715
716 // Must have exactly one of them
717 if ( ( $has_elements_to_clean ? 1 : 0 ) + ( $has_operations ? 1 : 0 ) !== 1 ) {
718 return false;
719 }
720
721 $all_old_keys = array_keys( self::OPERATIONS_MAPPING );
722 $cleaning_allowed = array_diff( $all_old_keys, [ 'optimize', 'repair' ] );
723
724 if ( $has_elements_to_clean ) {
725 if ( empty( $old_task_details['elements_to_clean'] ) ) {
726 return false;
727 }
728 foreach ( $old_task_details['elements_to_clean'] as $item_key ) {
729 if ( ! is_string( $item_key ) || ! in_array( $item_key, $cleaning_allowed, true ) ) {
730 return false;
731 }
732 }
733 }
734
735 if ( $has_operations ) {
736 if ( empty( $old_task_details['operations'] ) ) {
737 return false;
738 }
739 foreach ( $old_task_details['operations'] as $op ) {
740 if ( ! is_string( $op ) || ! in_array( $op, [ 'optimize', 'repair' ], true ) ) {
741 return false;
742 }
743 }
744 }
745
746 return true;
747
748 }
749
750 /**
751 * Get the timestamp from old date and time.
752 *
753 * @param string $date_str The old date in format 'Y-m-d'.
754 * @param string $time_str The old time in format 'H:i'.
755 *
756 * @return int The UNIX timestamp (UTC). Falls back to current time on parse failure.
757 */
758 private static function get_timestamp_from_old_date_and_time( $date_str, $time_str ) {
759
760 $datetime_str = trim( $date_str . ' ' . $time_str );
761
762 // Create a DateTime object in UTC
763 $dt = DateTime::createFromFormat( 'Y-m-d H:i', $datetime_str, new DateTimeZone( 'UTC' ) );
764
765 if ( $dt instanceof DateTime ) {
766 return $dt->getTimestamp();
767 }
768
769 return time();
770
771 }
772
773 /**
774 * Migrate the old operations.
775 *
776 * Maps legacy operation keys to the new keys and attaches "keep last" when available.
777 *
778 * @param array $old_operations The old operations.
779 *
780 * @return array The new operations array keyed by new operation name; values are
781 * either 'no_keep_last' or an array like ['type' => 'days', 'value' => int].
782 */
783 private static function migrate_old_operations( $old_operations ) {
784
785 $new_operations = [];
786
787 foreach ( $old_operations as $old_operation ) {
788
789 if ( isset( self::OPERATIONS_MAPPING[ $old_operation ] ) ) {
790 $keep_last_value = self::get_old_keep_last_for_item_type( $old_operation );
791 $keep_last = $keep_last_value !== 0 ? [ 'type' => 'days', 'value' => $keep_last_value ] : 'no_keep_last';
792 $new_operations[ self::OPERATIONS_MAPPING[ $old_operation ] ] = $keep_last;
793 }
794
795 }
796
797 return $new_operations;
798
799 }
800
801 /**
802 * Check if the old manual corrections exist.
803 *
804 * @return bool True if the old manual corrections exist, false if they don't
805 */
806 private static function is_old_manual_corrections_exists() {
807
808 $security_code = get_option( 'aDBc_security_folder_code' );
809 $uploads = wp_upload_dir();
810 $old_adbc_upload_dir = $uploads['basedir'] . '/adbc_uploads_' . $security_code;
811
812 if ( file_exists( $old_adbc_upload_dir ) ) {
813 $files = [
814 $old_adbc_upload_dir . '/tasks_corrected_manually.txt',
815 $old_adbc_upload_dir . '/options_corrected_manually.txt',
816 $old_adbc_upload_dir . '/tables_corrected_manually.txt',
817 ];
818
819 foreach ( $files as $file_path ) {
820 if ( file_exists( $file_path ) ) {
821 $handle = fopen( $file_path, 'r' );
822 if ( $handle ) {
823 $line = fgets( $handle );
824 fclose( $handle );
825 if ( $line !== false ) {
826 return true;
827 }
828 }
829 }
830 }
831 }
832
833 return false;
834
835 }
836
837 /**
838 * Write the old version manual corrections to the file.
839 *
840 * @param string $items_type The type of items to migrate. "tables", "options", "cron_jobs", etc.
841 * @param array $manual_corrections The manual corrections.
842 *
843 * @return int 0 if failed, 1 if success, 2 if some of the manual corrections were not valid
844 */
845 public static function write_old_version_manual_corrections_to_file( $items_type, $manual_corrections ) {
846
847 // Verify if there is a scan in progress. If there is, return an error.
848 if ( ADBC_Scan_Utils::is_scan_exists( $items_type ) ) {
849 return 0;
850 }
851
852 $scan_results_file_path = ADBC_Scan_Paths::get_scan_results_path( $items_type );
853 $temp_results_file_path = ADBC_Scan_Paths::get_manual_categorization_results_temp_file_path( $items_type );
854
855 // Create the scan results file if it doesn't exist.
856 ADBC_Files::instance()->create_file( $scan_results_file_path );
857 $scan_results_file_handle = ADBC_Files::instance()->get_file_handle( $scan_results_file_path, 'r' );
858 if ( $scan_results_file_handle === false ) {
859 return 0;
860 }
861
862 // Create the temp file to store the new manual corrections.
863 $temp_result_file_handle = ADBC_Files::instance()->get_file_handle( $temp_results_file_path, 'w' );
864 if ( $temp_result_file_handle === false ) {
865 return 0;
866 }
867
868 // Remove the hardcoded items from the manual corrections.
869 ADBC_Hardcoded_Items::instance()->remove_hardcoded_items_from_list( $manual_corrections, $items_type );
870
871 $count_old_manual_corrections = count( $manual_corrections );
872
873 // Convert the old manual corrections to the new format.
874 $new_manual_corrections = [];
875 foreach ( $manual_corrections as $item_name => $slug ) {
876
877 if ( ! self::is_old_manual_correction_valid( $item_name, $slug ) )
878 continue;
879
880 [ $slug_name, $slug_type ] = explode( ':', $slug, 2 );
881 $relation = [ 'm' => [ $slug_type . ':' . $slug_name ] ];
882 $new_manual_correction_line = $item_name . '|' . json_encode( $relation );
883 $new_manual_corrections[ $item_name ] = $new_manual_correction_line;
884
885 }
886
887 $items_count = 0;
888
889 // Read the scan results file, edit or add the new manual corrections to the temp file.
890 while ( ( $line = fgets( $scan_results_file_handle ) ) !== false ) {
891
892 list( $item_name, $belong_to_json ) = ADBC_Scan_Utils::split_result_file_line( $line );
893 if ( $item_name === false || $belong_to_json === false )
894 continue;
895
896 // Update the line with the new manual correction if it exists.
897 if ( isset( $new_manual_corrections[ $item_name ] ) ) {
898 fwrite( $temp_result_file_handle, $new_manual_corrections[ $item_name ] . "\n" );
899 unset( $new_manual_corrections[ $item_name ] );
900 $items_count++;
901 } else {
902 fwrite( $temp_result_file_handle, $line );
903 }
904
905 }
906
907 // Add the remaining manual corrections to the scan results file.
908 foreach ( $new_manual_corrections as $item_name => $new_manual_correction_line ) {
909 fwrite( $temp_result_file_handle, $new_manual_correction_line . "\n" );
910 $items_count++;
911 }
912
913 fclose( $scan_results_file_handle );
914 fclose( $temp_result_file_handle );
915
916 // Rename the temp file to the scan results file.
917 if ( ADBC_Files::instance()->exists( $temp_results_file_path ) && ! rename( $temp_results_file_path, $scan_results_file_path ) ) {
918 return 0;
919 }
920
921 // If the number of old manual corrections is greater than the number of new manual corrections, return 2.
922 if ( $count_old_manual_corrections > $items_count ) {
923 return 2;
924 }
925
926 return 1;
927
928 }
929
930 /**
931 * Validate the old manual correction format (e.g., {"item":"plugin-slug:p"}).
932 *
933 * Rules:
934 * - Value must be a string containing exactly one colon separating name and type
935 * - Type must be one of "p", "t", or "w"
936 * - For type "w", value must be exactly "w:w"
937 *
938 * @param string $item_name The item name (any non-empty string)
939 * @param mixed $slug The old-format slug (e.g., "plugin-slug:p")
940 *
941 * @return bool True when valid; false otherwise
942 */
943 private static function is_old_manual_correction_valid( $item_name, $slug ) {
944
945 if ( ! is_string( $item_name ) || $item_name === '' )
946 return false;
947
948 if ( ! is_string( $slug ) )
949 return false;
950
951 $slug = trim( $slug );
952 $parts = explode( ':', $slug, 2 );
953 if ( count( $parts ) !== 2 )
954 return false;
955
956 list( $slug_name, $slug_type ) = $parts;
957 $slug_name = trim( $slug_name );
958 $slug_type = trim( $slug_type );
959
960 if ( $slug_type !== 'p' && $slug_type !== 't' && $slug_type !== 'w' )
961 return false;
962
963 // For WordPress core, expect exactly "w:w"
964 if ( $slug_type === 'w' )
965 return $slug_name === 'w';
966
967 // The slug name should not contain any spaces
968 return strpos( $slug_name, ' ' ) === false;
969
970 }
971
972 /**
973 * Delete the old free version data.
974 *
975 * @return void
976 */
977 private static function delete_old_free_version_data() {
978
979 // Delete the old free version options
980 delete_option( 'aDBc_settings' );
981 delete_option( 'aDBc_clean_schedule' );
982 delete_option( 'aDBc_optimize_schedule' );
983
984 // Unschedule the old free version cron jobs
985 wp_unschedule_hook( 'aDBc_clean_scheduler' );
986 wp_unschedule_hook( 'aDBc_optimize_scheduler' );
987
988 }
989
990 /**
991 * Delete the old pro version data.
992 *
993 * @return void
994 */
995 private static function delete_old_pro_data() {
996
997 // Delete folder containing scan results
998 $aDBc_security_code = get_option( 'aDBc_security_folder_code' );
999 $aDBc_upload_dir = wp_upload_dir();
1000 $aDBc_upload_dir = str_replace( '\\', '/', $aDBc_upload_dir['basedir'] ) . '/adbc_uploads_' . $aDBc_security_code;
1001
1002 if ( file_exists( $aDBc_upload_dir ) ) {
1003 $dir = opendir( $aDBc_upload_dir );
1004 while ( ( $file = readdir( $dir ) ) !== false ) {
1005 if ( $file != '.' && $file != '..' ) {
1006 $entry_path = $aDBc_upload_dir . '/' . $file;
1007 if ( is_file( $entry_path ) ) {
1008 @unlink( $entry_path );
1009 }
1010 }
1011 }
1012 closedir( $dir );
1013 rmdir( $aDBc_upload_dir );
1014 }
1015
1016 // Delete the old pro version options
1017 $array_items = array( 'options', 'tables', 'tasks' );
1018
1019 foreach ( $array_items as $item ) {
1020
1021 delete_option( 'aDBc_temp_last_iteration_' . $item );
1022 delete_option( 'aDBc_temp_still_searching_' . $item );
1023 delete_option( 'aDBc_temp_last_item_line_' . $item );
1024 delete_option( 'aDBc_temp_last_file_line_' . $item );
1025 delete_option( 'aDBc_last_search_ok_' . $item );
1026 delete_option( 'aDBc_temp_total_files_' . $item );
1027 delete_option( 'aDBc_temp_maybe_scores_' . $item );
1028 delete_option( 'aDBc_temp_currently_scanning_' . $item );
1029 delete_option( 'aDBc_temp_progress_scan_' . $item );
1030 delete_option( 'aDBc_temp_progress_files_preparation_' . $item );
1031 delete_option( 'aDBc_temp_last_collected_file_path_' . $item );
1032 delete_option( 'aDBc_temp_items_to_scan_' . $item );
1033 delete_option( 'aDBc_temp_scan_type_' . $item );
1034 delete_option( 'aDBc_temp_current_scan_step_' . $item );
1035
1036 }
1037
1038 delete_option( 'aDBc_security_folder_code' );
1039 delete_option( 'aDBc_edd_license_key' );
1040 delete_option( 'aDBc_edd_license_status' );
1041
1042 }
1043
1044 /**
1045 * Delete all old versions data.
1046 *
1047 * @return void
1048 */
1049 private static function delete_all_old_data() {
1050 self::delete_old_pro_data();
1051 self::delete_old_free_version_data();
1052 }
1053
1054 /**
1055 * Check if the old license exists.
1056 *
1057 * @return bool True if the old license exists, false if it doesn't
1058 */
1059 private static function is_old_license_exists() {
1060 $old_edd_license_key_exists = get_option( 'aDBc_edd_license_key' );
1061 return $old_edd_license_key_exists !== false && $old_edd_license_key_exists !== '' && get_option( 'aDBc_settings' ) !== false;
1062 }
1063
1064 }
1065