PluginProbe
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets / 4.0.9
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets v4.0.9
4.5.4 4.2.1 4.2.2 4.2.3 4.5.0 4.5.2 4.5.3 4.2.0 4.1.18 4.1.17 4.1.16 4.1.15 4.1.14 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 All 146 releases
ultimate-post-kit / admin / class-rollback-version.php

class-rollback-version.php in Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets 4.0.9, at admin/class-rollback-version.php

840 lines 25.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UltimatePostKit\Admin;
4
5 if (!class_exists('UltimatePostKit_Rollback_Version')):
6
7 class UltimatePostKit_Rollback_Version {
8
9 private $plugin_slug = 'ultimate-post-kit';
10 private $backup_dir;
11 private $htaccess_content = "Order deny,allow\nDeny from all";
12
13 /**
14 * Constructor
15 */
16 public function __construct() {
17 $this->backup_dir = wp_upload_dir()['basedir'] . '/ultimate-post-kit-backups/';
18
19 // Add AJAX handlers for rollback functionality
20 add_action('wp_ajax_upk_rollback_version', array($this, 'handle_rollback_version'));
21
22 // Hook into plugin update process to create backup (BEFORE update)
23 add_action('upgrader_pre_install', array($this, 'create_backup_before_update'), 10, 2);
24
25 // Hook into plugin update process to create backup (AFTER update - fallback)
26 add_action('upgrader_process_complete', array($this, 'create_backup_on_update'), 10, 2);
27
28 // Additional hook to catch any plugin updates
29 add_action('upgrader_process_complete', array($this, 'check_and_backup_on_any_update'), 5, 2);
30
31 // Create initial backup on plugin activation
32 add_action('activated_plugin', array($this, 'create_initial_backup'));
33
34 // Track activation time for better detection
35 add_action('activated_plugin', array($this, 'track_activation_time'));
36
37 // Create backup on fresh installation
38 add_action('plugins_loaded', array($this, 'create_backup_on_installation'));
39
40 // Create backup on activation (more reliable)
41 add_action('admin_init', array($this, 'create_backup_on_activation'));
42
43 // Create backup if none exists (for existing installations)
44 add_action('admin_init', array($this, 'check_and_create_backup'));
45 }
46
47 /**
48 * Check and backup on any plugin update (catch-all method)
49 */
50 public function check_and_backup_on_any_update($upgrader, $hook_extra) {
51
52 // Only proceed if this is a plugin update
53 if ($hook_extra['type'] !== 'plugin') {
54 return;
55 }
56
57 // Check if our plugin directory was modified
58 $plugin_dir = WP_PLUGIN_DIR . '/' . $this->plugin_slug;
59 $plugin_file = $plugin_dir . '/ultimate-post-kit.php';
60
61 // Check if our plugin file exists and was recently modified
62 if (file_exists($plugin_file)) {
63 $file_time = filemtime($plugin_file);
64 $current_time = time();
65
66 // If file was modified in the last 30 seconds, it was likely updated
67 if ($current_time - $file_time < 30) {
68 $this->create_backup();
69 }
70 }
71 }
72
73 /**
74 * Create backup when plugin is updated (AFTER the update - fallback)
75 */
76 public function create_backup_on_update($upgrader, $hook_extra) {
77
78 // Only proceed if this is a plugin update
79 if ($hook_extra['type'] !== 'plugin') {
80 return;
81 }
82
83 // Check if our plugin is being updated - more flexible detection
84 $plugin_path = '';
85 if (isset($hook_extra['plugin'])) {
86 $plugin_path = $hook_extra['plugin'];
87 } elseif (isset($hook_extra['plugins']) && is_array($hook_extra['plugins'])) {
88 // Handle bulk updates
89 foreach ($hook_extra['plugins'] as $plugin) {
90 if (strpos($plugin, $this->plugin_slug) !== false) {
91 $plugin_path = $plugin;
92 break;
93 }
94 }
95 }
96
97 // Check if this is our plugin (multiple ways to detect)
98 $is_our_plugin = false;
99 if ($plugin_path) {
100 $expected_paths = array(
101 $this->plugin_slug . '/' . $this->plugin_slug . '.php',
102 $this->plugin_slug . '.php',
103 'ultimate-post-kit/ultimate-post-kit.php'
104 );
105
106 foreach ($expected_paths as $expected) {
107 if ($plugin_path === $expected) {
108 $is_our_plugin = true;
109 break;
110 }
111 }
112 }
113
114 if ($is_our_plugin) {
115 $this->create_backup();
116 }
117 }
118
119 /**
120 * Hook into pre-update to create backup before update
121 */
122 public function create_backup_before_update($upgrader, $hook_extra) {
123
124 // Check if our plugin is being updated - more flexible detection
125 $plugin_path = '';
126 if (isset($hook_extra['plugin'])) {
127 $plugin_path = $hook_extra['plugin'];
128 } elseif (isset($hook_extra['plugins']) && is_array($hook_extra['plugins'])) {
129 // Handle bulk updates
130 foreach ($hook_extra['plugins'] as $plugin) {
131 if (strpos($plugin, $this->plugin_slug) !== false) {
132 $plugin_path = $plugin;
133 break;
134 }
135 }
136 }
137
138 // Check if this is our plugin (multiple ways to detect)
139 $is_our_plugin = false;
140 if ($plugin_path) {
141 $expected_paths = array(
142 $this->plugin_slug . '/' . $this->plugin_slug . '.php',
143 $this->plugin_slug . '.php',
144 'ultimate-post-kit/ultimate-post-kit.php'
145 );
146
147 foreach ($expected_paths as $expected) {
148 if ($plugin_path === $expected) {
149 $is_our_plugin = true;
150 break;
151 }
152 }
153 }
154
155 if ($is_our_plugin) {
156 $this->create_backup();
157 }
158 }
159
160 /**
161 * Create initial backup on plugin activation
162 */
163 public function create_initial_backup($plugin) {
164 if ($plugin === $this->plugin_slug . '/' . $this->plugin_slug . '.php') {
165 $this->create_backup();
166 }
167 }
168
169 /**
170 * Track activation time for better backup detection
171 */
172 public function track_activation_time($plugin) {
173 if ($plugin === $this->plugin_slug . '/' . $this->plugin_slug . '.php') {
174 update_option('ultimate_post_kit_activation_time', time());
175 }
176 }
177
178 /**
179 * Check and create backup if none exists (for existing installations)
180 */
181 public function check_and_create_backup() {
182 // Only run once per session
183 if (get_transient('ultimate_post_kit_backup_checked')) {
184 return;
185 }
186
187 $backups = get_option('ultimate_post_kit_backups', array());
188 if (empty($backups)) {
189 $this->create_backup();
190 }
191
192 set_transient('ultimate_post_kit_backup_checked', true, HOUR_IN_SECONDS);
193 }
194
195 /**
196 * Create backup on plugin installation (hook into activation)
197 */
198 public function create_backup_on_installation() {
199 // Check if this is a fresh installation by looking for the installation flag
200 $installation_flag = get_option('ultimate_post_kit_installation_complete', false);
201
202 if (!$installation_flag) {
203 $this->create_backup();
204
205 // Mark installation as complete
206 update_option('ultimate_post_kit_installation_complete', true);
207 }
208 }
209
210 /**
211 * Create backup on plugin activation (more reliable method)
212 */
213 public function create_backup_on_activation() {
214 // Check if plugin was just activated
215 $activation_time = get_option('ultimate_post_kit_activation_time', 0);
216 $current_time = time();
217
218 // If activation time is within the last 5 minutes, consider it a fresh activation
219 if ($current_time - $activation_time < 300) {
220 $this->create_backup();
221 }
222 }
223
224 /**
225 * Create backup of current version (ZIP-based)
226 */
227 private function create_backup() {
228 $current_version = BDTUPK_VER;
229 $plugin_dir = WP_PLUGIN_DIR . '/' . $this->plugin_slug;
230
231 // Check if backup already exists for this version
232 $backups = get_option('ultimate_post_kit_backups', array());
233 if (isset($backups[$current_version])) {
234 return true; // Return true since backup exists
235 }
236
237 // Check if plugin directory exists
238 if (!is_dir($plugin_dir)) {
239 return false;
240 }
241
242 // Create backup directory if it doesn't exist
243 if (!wp_mkdir_p($this->backup_dir)) {
244 return false;
245 }
246
247 // Empty the backup directory before creating a new backup
248 $this->empty_directory($this->backup_dir);
249
250 // Test if we can write to the backup directory
251 if (!is_writable($this->backup_dir)) {
252 return false;
253 }
254
255 // Create .htaccess to protect the directory
256 $htaccess_file = $this->backup_dir . '.htaccess';
257 if (!file_exists($htaccess_file)) {
258 $htaccess_result = file_put_contents($htaccess_file, $this->htaccess_content);
259 }
260
261 // Create ZIP backup
262 $zip_filename = 'ultimate-post-kit-v' . $current_version . '-' . date('Y-m-d-H-i-s') . '.zip';
263 $zip_file_path = $this->backup_dir . $zip_filename;
264
265 // Check if ZipArchive is available
266 if (!class_exists('\ZipArchive')) {
267 return false;
268 }
269
270 $zip = new \ZipArchive();
271 $zip_result = $zip->open($zip_file_path, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
272
273 if ($zip_result !== TRUE) {
274 return false;
275 }
276
277 // Add plugin files to ZIP
278 $iterator = new \RecursiveIteratorIterator(
279 new \RecursiveDirectoryIterator($plugin_dir),
280 \RecursiveIteratorIterator::LEAVES_ONLY
281 );
282
283 $file_count = 0;
284 foreach ($iterator as $file) {
285 if (!$file->isDir()) {
286 $filePath = $file->getRealPath();
287 $relativePath = substr($filePath, strlen($plugin_dir) + 1); // +1 for the trailing slash
288
289 // Add file to ZIP with relative path
290 $zip->addFile($filePath, $this->plugin_slug . '/' . $relativePath);
291 $file_count++;
292 }
293 }
294
295 $zip->close();
296
297 // Verify ZIP file was created
298 if (!file_exists($zip_file_path)) {
299 return false;
300 }
301
302 $file_size = filesize($zip_file_path);
303
304 // Save backup info to database
305 $this->save_backup_info($current_version, $zip_file_path, $file_count, $file_size);
306
307 return true;
308 }
309
310 private function empty_directory($dir) {
311 if (!is_dir($dir)) {
312 return;
313 }
314
315 $files = array_diff(scandir($dir), array('.', '..'));
316 foreach ($files as $file) {
317 $file_path = $dir . DIRECTORY_SEPARATOR . $file;
318 if (is_dir($file_path)) {
319 $this->empty_directory($file_path);
320 rmdir($file_path);
321 } else {
322 unlink($file_path);
323 }
324 }
325 }
326
327
328 /**
329 * Save backup information to database
330 */
331 private function save_backup_info($version, $backup_path, $file_count = 0, $file_size = 0) {
332 $backups = get_option('ultimate_post_kit_backups', array());
333
334 $backups[$version] = array(
335 'version' => $version,
336 'backup_path' => $backup_path,
337 'backup_type' => 'zip',
338 'file_count' => $file_count,
339 'file_size' => $file_size,
340 'created_at' => current_time('mysql'),
341 'created_timestamp' => time()
342 );
343
344 update_option('ultimate_post_kit_backups', $backups);
345 }
346
347 /**
348 * Rollback Version Content
349 */
350 public function ultimate_post_kit_rollback_version_content() {
351 // Check if debug mode is requested
352 if (isset($_GET['debug']) && $_GET['debug'] == '1') {
353 $this->show_debug_information();
354 return;
355 }
356
357 $current_version = BDTUPK_VER;
358 $available_versions = $this->get_available_rollback_versions();
359 ?>
360 <div class="upk-dashboard-panel"
361 bdt-scrollspy="target: > div > div > .bdt-card; cls: bdt-animation-slide-bottom-small; delay: 300">
362 <div class="upk-dashboard-rollback-version">
363 <!-- <div class="bdt-card bdt-card-body"> -->
364
365 <div class="upk-rollback-form">
366 <div class="bdt-grid bdt-grid-small" bdt-grid>
367 <?php if (!empty($available_versions)): ?>
368 <div class="bdt-width-1-2@m">
369 <h2 class="bdt-margin-small-bottom bdt-text-bold bdt-text-lead"><strong><?php esc_html_e('Current Version:', 'ultimate-post-kit'); ?></strong> <?php echo esc_html($current_version); ?></h2>
370 <p><?php esc_html_e('Rollback to the previous version if available. This will restore the plugin to the previous version.', 'ultimate-post-kit'); ?></p>
371 <div class="upk-rollback-warning bdt-margin-small-top">
372 <div class="bdt-alert bdt-alert-warning" bdt-alert>
373 <p><strong><?php esc_html_e('Warning:', 'ultimate-post-kit'); ?></strong> <?php esc_html_e('Please backup your database before making the rollback.', 'ultimate-post-kit'); ?></p>
374 </div>
375 </div>
376 </div>
377 <?php endif; ?>
378 <div class="bdt-width-1-2@m">
379 <?php if (!empty($available_versions)): ?>
380 <div class="upk-rollback-form-wrapper">
381 <?php
382 $previous_version = $available_versions[0];
383 ?>
384 <div class="bdt-margin-medium-bottom">
385 <p><strong><?php esc_html_e('Previous Version:', 'ultimate-post-kit'); ?></strong> <?php echo esc_html($previous_version['version']); ?></p>
386
387 <p><strong><?php esc_html_e('Backup Date:', 'ultimate-post-kit'); ?></strong> <?php echo esc_html($previous_version['created_at']); ?></p>
388 <p><strong><?php esc_html_e('Backup Type:', 'ultimate-post-kit'); ?></strong> <?php echo esc_html(ucfirst($previous_version['backup_type'])); ?></p>
389 <?php if (isset($previous_version['file_count']) && $previous_version['file_count'] > 0): ?>
390 <p><strong><?php esc_html_e('Files:', 'ultimate-post-kit'); ?></strong> <?php echo esc_html($previous_version['file_count']); ?></p>
391 <?php endif; ?>
392 <?php if (isset($previous_version['file_size']) && $previous_version['file_size'] > 0): ?>
393 <p><strong><?php esc_html_e('Size:', 'ultimate-post-kit'); ?></strong> <?php echo esc_html(size_format($previous_version['file_size'])); ?></p>
394 <?php endif; ?>
395 </div>
396
397 <form id="upk-rollback-form" method="post">
398 <?php wp_nonce_field('upk-rollback-nonce', 'ep_rollback_nonce'); ?>
399 <input type="hidden" id="upk-rollback-version" name="rollback_version" value="<?php echo esc_attr($previous_version['version']); ?>">
400 <div class="bdt-form-row">
401 <div class="bdt-width-1-1">
402 <button type="submit" id="upk-rollback-submit" class="bdt-button bdt-button-danger bdt-flex bdt-flex-middle">
403 <i class="dashicons dashicons-update"></i>
404 <span class="upk-rollback-button-text bdt-margin-small-left"><?php esc_html_e('Rollback to v', 'ultimate-post-kit'); ?><?php echo esc_html($previous_version['version']); ?></span>
405 </button>
406 <span class="upk-rollback-loading" style="display: none;">
407 <i class="dashicons dashicons-update-alt"></i>
408 <?php esc_html_e('Rolling back...', 'ultimate-post-kit'); ?>
409 </span>
410 </div>
411 </div>
412 </form>
413 </div>
414
415 <?php else: ?>
416 <div class="upk-no-versions ">
417 <div class="bdt-alert bdt-alert-info" bdt-alert>
418 <p><?php esc_html_e('No previous version is available for rollback. This usually means this is a fresh installation or no backup was created during the last update.', 'ultimate-post-kit'); ?></p>
419 </div>
420 </div>
421 <?php endif; ?>
422 </div>
423 </div>
424 </div>
425 <!-- </div> -->
426 </div>
427 </div>
428
429 <style>
430 .upk-rollback-form-wrapper {
431 background: #f8f9fa;
432 padding: 20px;
433 border-radius: 8px;
434 border: 1px solid #e9ecef;
435 }
436 .upk-rollback-loading {
437 color: #dc3545;
438 font-weight: 600;
439 }
440 .upk-rollback-loading .dashicons {
441 animation: spin 1s linear infinite;
442 }
443 @keyframes spin {
444 0% { transform: rotate(0deg); }
445 100% { transform: rotate(360deg); }
446 }
447 </style>
448
449 <script>
450 jQuery(document).ready(function($) {
451
452 // Handle form submission
453 $('#upk-rollback-form').on('submit', function(e) {
454 e.preventDefault();
455
456 var selectedVersion = $('#upk-rollback-version').val();
457 var nonce = $('input[name="upk_rollback_nonce"]').val();
458
459 // Confirm rollback
460 if (!confirm('<?php esc_html_e('Are you sure you want to rollback to version', 'ultimate-post-kit'); ?> ' + selectedVersion + '? <?php esc_html_e('This action cannot be undone.', 'ultimate-post-kit'); ?>')) {
461 return;
462 }
463
464 // Show loading state
465 $('#upk-rollback-submit').hide();
466 $('.upk-rollback-loading').show();
467
468 // Send AJAX request
469 $.ajax({
470 url: ajaxurl,
471 type: 'POST',
472 data: {
473 action: 'upk_rollback_version',
474 version: selectedVersion,
475 nonce: nonce
476 },
477 success: function(response) {
478 if (response.success) {
479 alert(response.data.message);
480 location.reload();
481 } else {
482 alert('Error: ' + response.data.message);
483 }
484 },
485 error: function() {
486 alert('<?php esc_html_e('An error occurred during rollback. Please try again.', 'ultimate-post-kit'); ?>');
487 },
488 complete: function() {
489 $('#upk-rollback-submit').show();
490 $('.upk-rollback-loading').hide();
491 }
492 });
493 });
494
495 // URL validation function
496 function isValidUrl(string) {
497 try {
498 new URL(string);
499 return true;
500 } catch (_) {
501 return false;
502 }
503 }
504 });
505 </script>
506 <?php
507 }
508
509 /**
510 * Get available versions for rollback
511 */
512 private function get_available_rollback_versions() {
513 $backups = get_option('ultimate_post_kit_backups', array());
514 $available_versions = array();
515 $current_version = BDTUPK_VER;
516
517 foreach ($backups as $version => $backup_info) {
518 // Skip current version - don't allow rollback to current version
519 if ($version === $current_version) {
520 continue;
521 }
522
523 // Check if backup file still exists (ZIP or directory)
524 $backup_exists = false;
525 if (isset($backup_info['backup_type']) && $backup_info['backup_type'] === 'zip') {
526 // Check if ZIP file exists
527 $backup_exists = file_exists($backup_info['backup_path']);
528 } else {
529 // Legacy: check if directory exists
530 $backup_exists = is_dir($backup_info['backup_path']);
531 }
532
533 if ($backup_exists) {
534 $available_versions[] = array(
535 'version' => $version,
536 'backup_path' => $backup_info['backup_path'],
537 'backup_type' => isset($backup_info['backup_type']) ? $backup_info['backup_type'] : 'directory',
538 'file_count' => isset($backup_info['file_count']) ? $backup_info['file_count'] : 0,
539 'file_size' => isset($backup_info['file_size']) ? $backup_info['file_size'] : 0,
540 'created_at' => $backup_info['created_at']
541 );
542 }
543 }
544
545 // Sort by version (newest first)
546 usort($available_versions, function($a, $b) {
547 return version_compare($b['version'], $a['version']);
548 });
549
550 // Only return the most recent previous version (limit to 1)
551 return array_slice($available_versions, 0, 1);
552 }
553
554 /**
555 * Handle rollback version AJAX request
556 */
557 public function handle_rollback_version() {
558 // Check nonce for security
559 if (!check_ajax_referer('upk-rollback-nonce', 'nonce', false)) {
560 wp_send_json_error(array('message' => 'Security check failed'));
561 }
562
563 // Check user capabilities
564 if (!current_user_can('manage_options')) {
565 wp_send_json_error(array('message' => 'Insufficient permissions'));
566 }
567
568 $version = sanitize_text_field($_POST['version']);
569
570 if (empty($version)) {
571 wp_send_json_error(array('message' => 'Invalid version data'));
572 }
573
574 // Perform rollback logic here
575 $result = $this->perform_rollback($version);
576
577 if ($result['success']) {
578 wp_send_json_success(array(
579 'message' => sprintf('Successfully rolled back to version %s', $version)
580 ));
581 } else {
582 wp_send_json_error(array('message' => $result['message']));
583 }
584 }
585
586 /**
587 * Perform the actual rollback
588 */
589 private function perform_rollback($version) {
590 $backups = get_option('ultimate_post_kit_backups', array());
591
592 if (!isset($backups[$version])) {
593 return array(
594 'success' => false,
595 'message' => 'Backup for version ' . $version . ' not found'
596 );
597 }
598
599 $backup_info = $backups[$version];
600 $backup_path = $backup_info['backup_path'];
601 $backup_type = isset($backup_info['backup_type']) ? $backup_info['backup_type'] : 'directory';
602
603 $plugin_dir = WP_PLUGIN_DIR . '/' . $this->plugin_slug;
604
605 // Create temporary backup of current version
606 $temp_backup_dir = $this->backup_dir . 'temp-current-' . time() . '/';
607 if (!wp_mkdir_p($temp_backup_dir)) {
608 return array(
609 'success' => false,
610 'message' => 'Could not create temporary backup directory'
611 );
612 }
613
614 // Backup current version
615 $current_backup_result = $this->copy_directory($plugin_dir, $temp_backup_dir);
616 if (is_wp_error($current_backup_result)) {
617 return array(
618 'success' => false,
619 'message' => 'Could not backup current version: ' . $current_backup_result->get_error_message()
620 );
621 }
622
623 // Remove current plugin files
624 $this->remove_directory($plugin_dir);
625
626 // Restore from backup based on type
627 if ($backup_type === 'zip') {
628 // Extract ZIP backup
629 if (!class_exists('\ZipArchive')) {
630 // Restore current version on failure
631 $this->copy_directory($temp_backup_dir, $plugin_dir);
632 return array(
633 'success' => false,
634 'message' => 'ZipArchive class not available'
635 );
636 }
637
638 $zip = new \ZipArchive();
639 $zip_result = $zip->open($backup_path);
640
641 if ($zip_result !== TRUE) {
642 // Restore current version on failure
643 $this->copy_directory($temp_backup_dir, $plugin_dir);
644 return array(
645 'success' => false,
646 'message' => 'Could not open ZIP backup: ' . $zip_result
647 );
648 }
649
650 // Extract to plugin directory
651 $extract_result = $zip->extractTo($plugin_dir);
652 $zip->close();
653
654 if (!$extract_result) {
655 // Restore current version on failure
656 $this->copy_directory($temp_backup_dir, $plugin_dir);
657 return array(
658 'success' => false,
659 'message' => 'Could not extract ZIP backup'
660 );
661 }
662
663 // Move files from subdirectory to plugin directory
664 $extracted_plugin_dir = $plugin_dir . '/' . $this->plugin_slug;
665 if (is_dir($extracted_plugin_dir)) {
666 $move_result = $this->move_directory_contents($extracted_plugin_dir, $plugin_dir);
667 if (is_wp_error($move_result)) {
668 // Restore current version on failure
669 $this->copy_directory($temp_backup_dir, $plugin_dir);
670 return array(
671 'success' => false,
672 'message' => 'Could not move extracted files: ' . $move_result->get_error_message()
673 );
674 }
675 // Remove the empty subdirectory
676 rmdir($extracted_plugin_dir);
677 }
678
679 } else {
680 // Legacy: restore from directory
681 if (!is_dir($backup_path)) {
682 // Restore current version on failure
683 $this->copy_directory($temp_backup_dir, $plugin_dir);
684 return array(
685 'success' => false,
686 'message' => 'Backup directory for version ' . $version . ' not found'
687 );
688 }
689
690 $restore_result = $this->copy_directory($backup_path, $plugin_dir);
691 if (is_wp_error($restore_result)) {
692 // Restore current version on failure
693 $this->copy_directory($temp_backup_dir, $plugin_dir);
694 return array(
695 'success' => false,
696 'message' => 'Could not restore version: ' . $restore_result->get_error_message()
697 );
698 }
699 }
700
701 // Clean up temporary backup
702 $this->remove_directory($temp_backup_dir);
703
704 return array(
705 'success' => true,
706 'message' => 'Rollback completed successfully'
707 );
708 }
709
710 /**
711 * Copy directory recursively
712 */
713 private function copy_directory($source, $destination) {
714 if (!is_dir($source)) {
715 return new WP_Error('source_not_dir', 'Source is not a directory: ' . $source);
716 }
717
718 if (!wp_mkdir_p($destination)) {
719 return new WP_Error('dest_creation_failed', 'Could not create destination directory: ' . $destination);
720 }
721
722 $dir = opendir($source);
723 if (!$dir) {
724 return new WP_Error('source_read_failed', 'Could not read source directory: ' . $source);
725 }
726
727 while (($file = readdir($dir)) !== false) {
728 if ($file === '.' || $file === '..') {
729 continue;
730 }
731
732 $source_path = $source . '/' . $file;
733 $dest_path = $destination . '/' . $file;
734
735 if (is_dir($source_path)) {
736 $result = $this->copy_directory($source_path, $dest_path);
737 if (is_wp_error($result)) {
738 closedir($dir);
739 return $result;
740 }
741 } else {
742 if (!is_readable($source_path)) {
743 closedir($dir);
744 return new WP_Error('file_not_readable', 'Source file not readable: ' . $source_path);
745 }
746
747 if (!copy($source_path, $dest_path)) {
748 closedir($dir);
749 return new WP_Error('file_copy_failed', 'Failed to copy file: ' . $file . ' from ' . $source_path . ' to ' . $dest_path);
750 }
751 }
752 }
753
754 closedir($dir);
755 return true;
756 }
757
758 /**
759 * Remove directory recursively
760 */
761 private function remove_directory($dir) {
762 if (!is_dir($dir)) {
763 return;
764 }
765
766 $files = array_diff(scandir($dir), array('.', '..'));
767 foreach ($files as $file) {
768 $path = $dir . '/' . $file;
769 if (is_dir($path)) {
770 $this->remove_directory($path);
771 } else {
772 unlink($path);
773 }
774 }
775
776 rmdir($dir);
777 }
778
779 /**
780 * Move directory contents from source to destination
781 */
782 private function move_directory_contents($source, $destination) {
783 if (!is_dir($source)) {
784 return new WP_Error('source_not_dir', 'Source is not a directory: ' . $source);
785 }
786
787 if (!is_dir($destination)) {
788 if (!wp_mkdir_p($destination)) {
789 return new WP_Error('dest_creation_failed', 'Could not create destination directory: ' . $destination);
790 }
791 }
792
793 $dir = opendir($source);
794 if (!$dir) {
795 return new WP_Error('source_read_failed', 'Could not read source directory: ' . $source);
796 }
797
798 while (($file = readdir($dir)) !== false) {
799 if ($file === '.' || $file === '..') {
800 continue;
801 }
802
803 $source_path = $source . '/' . $file;
804 $dest_path = $destination . '/' . $file;
805
806 if (is_dir($source_path)) {
807 // Create destination subdirectory
808 if (!wp_mkdir_p($dest_path)) {
809 closedir($dir);
810 return new WP_Error('subdir_creation_failed', 'Could not create subdirectory: ' . $dest_path);
811 }
812
813 // Recursively move contents
814 $result = $this->move_directory_contents($source_path, $dest_path);
815 if (is_wp_error($result)) {
816 closedir($dir);
817 return $result;
818 }
819
820 // Remove empty source directory
821 rmdir($source_path);
822 } else {
823 if (!is_readable($source_path)) {
824 closedir($dir);
825 return new WP_Error('file_not_readable', 'Source file not readable: ' . $source_path);
826 }
827
828 if (!rename($source_path, $dest_path)) {
829 closedir($dir);
830 return new WP_Error('file_move_failed', 'Failed to move file: ' . $file . ' from ' . $source_path . ' to ' . $dest_path);
831 }
832 }
833 }
834
835 closedir($dir);
836 return true;
837 }
838 }
839
840 endif;