PluginProbe ʕ •ᴥ•ʔ
Transferito: WP Migration / 14.0.7
Transferito: WP Migration v14.0.7
trunk 11.4.0 12.0.0 13.1.0 14.0.0 14.0.11 14.0.7 14.1.0 14.1.1 14.1.2 14.1.3 14.1.4
transferito / src / Controllers / Transfer.php
transferito / src / Controllers Last commit date
Transfer.php 8 months ago
Transfer.php
3663 lines
1 <?php
2
3 namespace Transferito\Controllers;
4
5 use Transferito\Models\Core\Config;
6 use Transferito\Models\Transfer\CodeBase;
7 use Transferito\Models\Transfer\Database;
8 use Transferito\Models\Transfer\Upload;
9 use Transferito\Models\Core\Api as TransferitoAPI;
10 use Transferito\Models\Settings\Telemetry;
11
12 if (!defined('ABSPATH')) exit;
13
14 class Transfer {
15
16 private $codeBase;
17 private $dataBase;
18 private $upload;
19 private $api;
20 private $options;
21 private $telemetry;
22
23 public function __construct()
24 {
25 if (current_user_can('activate_plugins')) {
26 $this->api = new TransferitoAPI();
27 $this->codeBase = new CodeBase();
28 $this->dataBase = new Database();
29 $this->upload = new Upload();
30 $this->telemetry = new Telemetry();
31
32 $this->options = get_option( 'transferito_settings_option' );
33
34 add_action("wp_ajax_preparing_transfer", [ $this, "prepareDownload"]);
35 add_action("wp_ajax_start_migration", [ $this, "startMigration"]);
36 add_action("wp_ajax_clean_up_files", [ $this, "cleanUp"]);
37 add_action("wp_ajax_status_check", [ $this, "statusCheck"]);
38
39 /**
40 * @deprecated
41 */
42 add_action("wp_ajax_get_directories", [ $this, "getFTPDirectories"]);
43 add_action("wp_ajax_correct_directory_validation", [ $this, "directoryValidation"]);
44
45
46 add_action("wp_ajax_initiate_local_upload", [ $this, "initiateUpload"]);
47 add_action("wp_ajax_upload_chunk", [ $this, "uploadChunk"]);
48 add_action("wp_ajax_complete_upload", [ $this, "completeUpload"]);
49 add_action("wp_ajax_preparing_codebase", [ $this, "prepareCodebase"]);
50 add_action("wp_ajax_add_files_to_codebase_archive", [ $this, "addFilesToCodebase"]);
51 add_action("wp_ajax_codebase_completion", [ $this, "codebaseArchiveComplete"]);
52 add_action("wp_ajax_preparing_database", [ $this, "prepareDatabase"]);
53 add_action("wp_ajax_create_db_exports", [ $this, "chunkedDBExport"]);
54 add_action("wp_ajax_database_completion", [ $this, "databaseExportComplete"]);
55 add_action("wp_ajax_archive_db_exports", [ $this, "archiveDBExport"]);
56 add_action("wp_ajax_database_relocation", [ $this, "databaseRelocation"]);
57 add_action("wp_ajax_database_relocation_check", [ $this, "databaseRelocationCheck"]);
58 add_action("wp_ajax_check_archive_completion", [ $this, "checkArchiveCompletion"]);
59 add_action("wp_ajax_archive_creation", [ $this, "archiveCreation"]);
60 add_action("wp_ajax_archive_progress_check", [ $this, "archiveProgressCheck"]);
61 add_action("wp_ajax_cpanel_authentication", [ $this, "cpanelAuthentication"]);
62 add_action("wp_ajax_server_detail_validation", [ $this, "serverDetailValidation"]);
63 add_action("wp_ajax_transferito_hide_welcome_screen", [ $this, "hideWelcomeScreen"]);
64
65
66 add_action("wp_ajax_database_detail_validation", [ $this, "databaseValidation"]);
67 add_action("wp_ajax_hide_quickstart_popup", [ $this, "hideQuickStart"]);
68 add_action("wp_ajax_send_request_form", [ $this, "sendRequestForm"]);
69 add_action("wp_ajax_log_transferito_event", [ $this, "logEvent"]);
70 add_action("wp_ajax_start_directory_search", [ $this, "startDirectoryCheck"]);
71 add_action("wp_ajax_get_directory_check_update", [ $this, "getDirectoryCheckUpdate"]);
72 add_action('wp_ajax_check_premium_api_keys', [ $this, "checkPremiumApiKeys"]);
73 add_action('wp_ajax_download_transferito_verification_file', [ $this, "downloadVerificationFile"]);
74 add_action('wp_ajax_transferito_validate_destination_server_connection', [ $this, "destinationServerValidation"]);
75
76
77 /**
78 * Move to routing class
79 */
80 add_action("wp_ajax_check_current_site", [$this, "wpSiteCheck"]);
81 add_action("wp_ajax_check_cpanel_availability", [$this, "cPanelCheck"]);
82 add_action("wp_ajax_choose_migration_method", [$this, "chooseMigrationMethod"]);
83 add_action("wp_ajax_switch_mode", [$this, "switchMode"]);
84 add_action("wp_ajax_screen_route_redirection", [$this, "screenRouting"]);
85 add_action("wp_ajax_load_directory_template", [$this, "loadDirectoryTemplate"]);
86 }
87 }
88
89 public function logEvent()
90 {
91 /**
92 * Verify nonce with every event logged
93 */
94 check_ajax_referer('log_event', 'securityKey');
95
96 /**
97 * Get the ByPass exec usage Flag from the settings
98 */
99 $settingsOption = get_option('transferito_settings_option');
100
101 /**
102 * Check to see if the tracking has been opted in
103 */
104 $trackingEnabled = isset($settingsOption['transferito_enable_debug_tracking'])
105 ? $settingsOption['transferito_enable_debug_tracking']
106 : false;
107
108 /**
109 * Logs the event if the user has enabled debug tracking
110 */
111 if ($trackingEnabled) {
112 $event = isset($_POST['event'])
113 ? sanitize_text_field(wp_unslash($_POST['event']))
114 : '';
115 $eventProperties = isset($_POST['eventProperties'])
116 ? array_map('sanitize_text_field', wp_unslash($_POST['eventProperties']))
117 : '';
118
119 $this->telemetry->pushEvent($event, $eventProperties);
120 }
121
122 wp_send_json_success([ 'logged' => true ]);
123 }
124
125 /**
126 * @deprecated
127 * @return void
128 */
129 public function getFTPDirectories()
130 {
131 /**
132 * verify nonce with every FTP Directory request
133 */
134 check_ajax_referer('get_directory_list', 'securityKey');
135
136 /**
137 * Check that the ftpInfo element exists
138 */
139 $getDirectoriesPayload = get_transient('transferito_manual_server_detail');
140
141 /**
142 * Assign the correct path to the ftp details
143 */
144 if (count($getDirectoriesPayload) > 0) {
145 $path = isset($_POST['path']) ? sanitize_text_field(wp_unslash($_POST['path'])) : '';
146 $getDirectoriesPayload['path'] = $path;
147 }
148
149
150 try {
151 /**
152 * Hit the endpoint to return the results
153 */
154 $getDirectoriesRequest = $this->api->getDirectories($getDirectoriesPayload);
155
156 /**
157 * Listen to a successful response
158 * & then filter the array to just return the directories
159 */
160 if ($getDirectoriesRequest['code'] === 200) {
161 $directoryList = $getDirectoriesRequest['message']->filelist;
162 $filteredDirectoryList = array_filter($directoryList, function($value, $key) {
163 return $value->type === 'dir';
164 }, ARRAY_FILTER_USE_BOTH);
165 wp_send_json_success([
166 'folders' => $filteredDirectoryList,
167 'port' => $getDirectoriesPayload['ftpPort']
168 ]);
169 } else if ($getDirectoriesRequest['code'] !== 200) {
170 wp_send_json_error([
171 'err' => $getDirectoriesRequest['message']->result,
172 'payload' => $getDirectoriesPayload,
173 'message' => $getDirectoriesRequest
174 ], 400);
175 }
176 } catch(\Exception $exception) {
177 wp_send_json_error('There has been an issue - If this problem persists. Contact support', 500);
178 }
179 }
180
181 /**
182 * @deprecated
183 * @return void
184 */
185 public function startDirectoryCheck()
186 {
187 /**
188 * verify nonce with every FTP Directory request
189 */
190 check_ajax_referer('get_directory_list', 'securityKey');
191
192 /**
193 * Check that the ftpInfo element exists
194 */
195 $directoryCheckPayload = get_transient('transferito_manual_server_detail');
196
197 /**
198 * Get the destination server URL
199 */
200 $domain = get_transient('transferito_migration_domain');
201
202 /**
203 * Make initial Directory Check request
204 */
205 $directoryCheck = $this->api->startDirectoryCheck(array(
206 'ftpHost' => $directoryCheckPayload['ftpHost'],
207 'ftpUser' => $directoryCheckPayload['ftpUser'],
208 'ftpPass' => $directoryCheckPayload['ftpPass'],
209 'ftpPort' => $directoryCheckPayload['ftpPort'],
210 'useSFTP' => $directoryCheckPayload['useSFTP'],
211 'URL' => $domain,
212 ));
213
214 /**
215 * Check the status code
216 */
217 $httpStatusCode = $directoryCheck['code'];
218
219 /**
220 * Check we have a successful response
221 */
222 if ($httpStatusCode === 200) {
223 wp_send_json_success(array_merge((array)$directoryCheck['message'], $directoryCheckPayload));
224 }
225
226 /**
227 * If we have anything other than a 200
228 * Fail the endpoint
229 */
230 if ($httpStatusCode !== 200) {
231 wp_send_json_error($directoryCheck['message'], 400);
232 }
233
234 }
235
236 /**
237 * @deprecated
238 * @return void
239 */
240 public function getDirectoryCheckUpdate()
241 {
242 /**
243 * verify nonce with every FTP Directory request
244 */
245 check_ajax_referer('get_directory_list', 'securityKey');
246
247 $url = isset($_POST['url']) ? sanitize_text_field(wp_unslash($_POST['url'])) : '';
248 $directoryCheckId = isset($_POST['directoryCheckId']) ? sanitize_text_field(wp_unslash($_POST['directoryCheckId'])) : '';
249 $directoryCheckUpdate = $this->api->getDirectoryUpdate(array(
250 'URL' => $url,
251 'directoryCheckId' => $directoryCheckId
252 ));
253
254 /**
255 * Check the status code
256 */
257 $httpStatusCode = $directoryCheckUpdate['code'];
258
259 /**
260 * If we have anything other than a 200
261 * Fail the endpoint
262 */
263 if ($httpStatusCode !== 200) {
264 wp_send_json_error($directoryCheckUpdate['message'], 400);
265 }
266
267 /**
268 * If the path has been found
269 * Update the Server Detail
270 */
271 if ($directoryCheckUpdate['message']->found) {
272 $serverDetails = get_transient('transferito_manual_server_detail');
273
274 /**
275 * Fix to add the path if the document root is the ftp path
276 */
277 if ($directoryCheckUpdate['message']->path === '' && $serverDetails['useSFTP'] === '0') {
278 $directoryCheckUpdate['message']->path = './';
279 }
280
281 /**
282 * Add the path to the server details array
283 */
284 $serverDetails['path'] = $directoryCheckUpdate['message']->path;
285 $serverDetails['ftpPath'] = $directoryCheckUpdate['message']->path;
286 $serverDetails['URL'] = $url;
287
288 /**
289 * Update the server array
290 */
291 set_transient('transferito_manual_server_detail', $serverDetails);
292 }
293
294 wp_send_json_success($directoryCheckUpdate['message']);
295 }
296
297 public function hideWelcomeScreen()
298 {
299 /**
300 * verify nonce with every template change call
301 */
302 check_ajax_referer('transferito_welcome_screen', 'securityKey');
303
304 set_transient('transferito_hide_welcome_screen', true);
305
306 wp_send_json_success();
307 }
308
309 public function wpSiteCheck()
310 {
311 /**
312 * verify nonce with every template change call
313 */
314 check_ajax_referer('template_change', 'actionKey');
315
316 /**
317 * Clean up previous migration that may have failed
318 */
319 $this->freshStart();
320
321 $siteDetails = getDirectorySize(TRANSFERITO_ABSPATH);
322 $zipEnabled = class_exists('ZipArchive');
323 $getWPInstallationSizes = get_transient('transferito_installation_size');
324
325 /**
326 * Check if the user has exceeded the max size
327 */
328 $this->checkSiteWithinFreeTier($siteDetails);
329
330 /**
331 * Log any failures created during the getDirectorySize check
332 */
333 if (isset($getWPInstallationSizes['errors']) && count($getWPInstallationSizes['errors']) > 0) {
334 $errorMessage = implode('|', $getWPInstallationSizes['errors']);
335 $this->api->failedMigration($errorMessage);
336 }
337
338 /**
339 * Default the exec enabled flag to false
340 */
341 $execEnabled = false;
342
343 /**
344 * Get the ByPass exec usage Flag from the settings
345 */
346 $settingsOption = get_option('transferito_settings_option');
347 $bypassExecUsage = isset($settingsOption['transferito_bypass_exec_archive_creation'])
348 ? $settingsOption['transferito_bypass_exec_archive_creation']
349 : false;
350
351 /**
352 * If the flag is not set
353 * Check to see if the OS is windows
354 * If it is not - Check to see if exec can be used
355 */
356 if (!$bypassExecUsage) {
357 /**
358 * Windows HOT Fix
359 * @todo return to the original check when FULL FIX has been implemented
360 */
361 $isWindows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
362 $execEnabled = $isWindows
363 ? false
364 : function_exists('exec') && @exec('echo EXEC') == 'EXEC';
365 }
366
367
368 $metRequirements = !$zipEnabled && !$execEnabled ? false : true;
369 $useZip = !$execEnabled;
370
371 /**
372 * Save the requirements on load
373 */
374 set_transient('transferito_requirements', [
375 'metRequirements' => $metRequirements,
376 'useZip' => $useZip
377 ]);
378
379 /**
380 * Initially default the fallback to false
381 */
382 set_transient('transferito_request_fallback', false);
383
384 /**
385 * Get Welcome Screen option
386 */
387 $hideWelcomeScreen = get_transient('transferito_hide_welcome_screen');
388
389 /**
390 * Return the template for all users
391 */
392 $response = [
393 'hideWelcomeScreen' => $hideWelcomeScreen,
394 'htmlTemplate' => loadTemplate('parts/migration/cpanel-check', [
395 'secondaryMessage' => 'To start your migration, please choose a migration method',
396 'metRequirements' => $metRequirements,
397 'hideQuickStart' => true
398 ])
399 ];
400
401 /**
402 * Push loadScreen event to telemetry
403 */
404 $this->telemetry->pushEvent('loadScreen', [
405 'screenName' => 'DestinationURL'
406 ]);
407
408 wp_send_json_success($response);
409 }
410
411 public function switchMode()
412 {
413 /**
414 * Verify nonce with every screen switch
415 */
416 check_ajax_referer('template_change', 'securityKey');
417
418 $transferMethod = isset($_POST['method']) ? sanitize_text_field(wp_unslash($_POST['method'])) : '';
419 $domain = get_transient('transferito_migration_domain');
420 $cPanelAllowed = boolval(get_transient('transferito_cpanel_allowed'));
421
422 /**
423 * Has the cpanel domain
424 */
425 $hasCpanelDomain = get_transient('transferito_cpanel_domain');
426
427 /**
428 * Set / Remove the transient based on the transfer method
429 */
430 if ($transferMethod === 'cpanel' && !$hasCpanelDomain) {
431 /**
432 * Domain
433 */
434 $updatedDomain = rtrim(trim($domain), '/');
435
436 /**
437 * cPanel admin URL
438 */
439 $cpanelAdminURL = $updatedDomain . ':2083';
440
441 /**
442 * Result from the cPanel check
443 */
444 $response = $this->api->cPanelAvailabilityCheck($cpanelAdminURL);
445
446 /**
447 * Check that the string is in the response
448 */
449 $cPanelInHTMLSource = stripos($response['message'], 'cpanel') !== false;
450
451 /**
452 * Check if we should default to cPanel
453 */
454 $cPanelAllowed = $response['code'] === 200 && $cPanelInHTMLSource;
455
456 /**
457 * Correct cPanel URL
458 */
459 $cpanelFinalAdminURLSplit = explode(':2083', $response['url']);
460
461 if ($cPanelAllowed) {
462 set_transient('transferito_cpanel_domain', $cpanelFinalAdminURLSplit[0]);
463 }
464 }
465
466 /**
467 * Update transient - For the transfer method
468 */
469 $updatedTransferMethod = $transferMethod === 'cpanel' ? 'cpanel' : 'manual';
470 set_transient('transferito_transfer_method', $updatedTransferMethod);
471
472 /**
473 * Build the part dynamically
474 */
475 $templatePath = 'parts/migration/' . $transferMethod . '/main';
476
477 /**
478 * Load the correct template based on the site size
479 */
480 $htmlTemplate = loadTemplate($templatePath, [
481 'cpanelAllowed' => $cPanelAllowed,
482 'directories' => Config::getWPContentPaths(),
483 'cpanelDetail' => get_transient('transferito_cpanel_auth_details'),
484 'cpanelCompleted' => get_transient('transferito_cpanel_auth_details_completed'),
485 'detail' => get_transient('transferito_manual_server_detail')
486 ]);
487
488 /**
489 * Push loadScreen event to telemetry
490 */
491 $this->telemetry->pushEvent('loadScreen', [
492 'screenName' => $transferMethod === 'cpanel' ? 'cPanelAuthentication' : 'FTPAuthentication'
493 ]);
494
495 /**
496 * Return the object
497 */
498 wp_send_json_success([
499 'cPanelAllowed' => $cPanelAllowed,
500 'URL' => $domain,
501 'transferMethod' => $transferMethod,
502 'htmlTemplate' => $htmlTemplate,
503 ]);
504 }
505
506 public function screenRouting()
507 {
508 /**
509 * Verify nonce with every screen route change
510 */
511 check_ajax_referer('template_change', 'securityKey');
512
513 try {
514 $screenRoute = isset($_POST['route']) ? sanitize_text_field(wp_unslash($_POST['route'])) : '';
515 $serverDetail = get_transient('transferito_manual_server_detail');
516 $url = explode('://', get_transient('transferito_migration_domain'));
517 $domain = count($url) === 2 ? $url[1] : $url[0];
518 $cPanelDomainList = get_transient('transferito_cpanel_domain_selection');
519
520 $mappedScreenRoutes = [
521 'destinationURL' => loadTemplate('parts/migration/cpanel-check', [
522 'url' => $domain,
523 'mainMessage' => '',
524 'secondaryMessage' => 'To start your migration, please choose a migration method',
525 'metRequirements' => get_transient('transferito_requirements')['metRequirements'],
526 'hideQuickStart' => true
527 ]),
528 'databaseAuthentication' => loadTemplate('parts/migration/manual/database-detail', [
529 'detail' => $serverDetail,
530 'completed' => get_transient('transferito_database_detail_completed')
531 ])
532 ];
533
534 /**
535 * If the route does not exist in the mapped routes
536 *
537 * @todo throw error
538 */
539 if (!isset($mappedScreenRoutes[$screenRoute])) {
540 wp_send_json_error([
541 'message' => 'routeDoesNotExist'
542 ], 400);
543 }
544
545 /**
546 * Return the object
547 */
548 wp_send_json_success([
549 'htmlTemplate' => $mappedScreenRoutes[$screenRoute],
550 'detail' => $serverDetail
551 ]);
552 } catch (\Exception $exception) {
553 wp_send_json_error([
554 'message' => $exception->getMessage()
555 ], 400);
556 }
557 }
558
559 public function sendRequestForm()
560 {
561 check_ajax_referer('hosting_guide_request_detail', 'securityKey');
562
563 $this->api->hostingGuideRequest([
564 'email' => isset($_POST['data']['emailAddress']) ? sanitize_text_field(wp_unslash($_POST['data']['emailAddress'])) : '',
565 'hostingProvider' => isset($_POST['data']['hostingProvider']) ? sanitize_text_field(wp_unslash($_POST['data']['hostingProvider'])) : '',
566 'guideName' => isset($_POST['data']['guideName']) ? sanitize_text_field(wp_unslash($_POST['data']['guideName'])) : ''
567 ]);
568
569 /**
570 * Return the object
571 */
572 wp_send_json_success([ 'completed' => true ]);
573 }
574
575 public function checkPremiumApiKeys()
576 {
577 check_ajax_referer('in_plugin_premium_upgrade', 'securityKey');
578
579 try {
580
581 $publicKey = isset($_POST['data']['publicKey']) ? sanitize_text_field(wp_unslash($_POST['data']['publicKey'])) : '';
582 $secretKey = isset($_POST['data']['secretKey']) ? sanitize_text_field(wp_unslash($_POST['data']['secretKey'])) : '';
583
584 /**
585 * Check the user's API keys
586 */
587 $hasValidAPIKeys = $this->areAPIKeysValid($publicKey, $secretKey);
588
589 /**
590 * If the API Keys aren't valid
591 * Throw an error
592 */
593 if (!$hasValidAPIKeys) {
594 set_transient('transferito_user_status', 'FREE');
595 throw new \Exception('NOT_VALID');
596 }
597
598 /**
599 * Get Current Options
600 */
601 $options = get_option('transferito_settings_option');
602
603 /**
604 * Update the API Keys
605 */
606 $options['public_transferito_key'] = sanitize_text_field($publicKey);
607 $options['secret_transferito_key'] = sanitize_text_field($secretKey);
608
609 /**
610 * Update the options
611 */
612 update_option('transferito_settings_option', $options);
613
614 /**
615 * Set the USER status
616 */
617 set_transient('transferito_user_status', 'PREMIUM');
618
619 wp_send_json_success([ 'validated' => true ]);
620 } catch(\Exception $exception) {
621 wp_send_json_error([ 'validated' => true ], 403);
622 }
623 }
624
625 /**
626 * @remove all functionality for quickstart
627 */
628 public function hideQuickStart()
629 {
630 set_transient('transferito_hide_quick_start_guide', true);
631
632 /**
633 * Return the object
634 */
635 wp_send_json_success(['completed' => true ]);
636 }
637
638 public function loadDirectoryTemplate()
639 {
640 $serverDetail = get_transient('transferito_manual_server_detail');
641
642 /**
643 * Push loadScreen event to telemetry
644 */
645 $this->telemetry->pushEvent('loadScreen', [
646 'screenName' => 'directorySelector'
647 ]);
648
649 /**
650 * Return success object
651 * To update the nav and change the child template
652 */
653 wp_send_json_success([
654 'template' => loadTemplate('parts/migration/manual/directory-selection', []),
655 'path' => $serverDetail['path'],
656 'navigation' => [
657 'completed' => 'transferitoNav__manualFTPDetails',
658 'active' => 'transferitoNav__manualFTPDirectorySelect'
659 ],
660 ]);
661
662 }
663
664 public function downloadVerificationFile()
665 {
666 check_ajax_referer('connect_to_server', 'securityKey');
667
668 try {
669
670 $verification = $this->api->createVerificationRequest();
671 $created = $verification['code'] === 200;
672
673 /**
674 * Throw an error if the response isn't 200
675 */
676 if (!$created) {
677 throw new \Exception($verification['message']);
678 }
679
680 /**
681 * Assign verification details
682 */
683 $verificationInfo = $verification['message'];
684
685 /**
686 * Build the download URL
687 */
688 $url = Config::getBaseApiUrl() . '/verification/' . $verificationInfo->uuid . '/' . $verificationInfo->token;
689
690 /**
691 * Get the domain
692 */
693 $domain = get_transient('transferito_migration_unchanged_domain');
694
695 /**
696 * Create Verification Array
697 */
698 $verificationDetails = [
699 'domain' => $domain,
700 'token' => $verificationInfo->token,
701 'filename' => $verificationInfo->filename,
702 'connected' => false
703 ];
704
705 /**
706 * Get the list of connected sites
707 */
708 $connectedSites = get_transient('transferito_connected_sites');
709
710 /**
711 * Check to see if the sites are empty if so - Create an array
712 */
713 if (!$connectedSites) {
714 $connectedSites = array();
715 }
716
717 /**
718 * Add verification Details to the array
719 */
720 $connectedSites[] = $verificationDetails;
721
722 /**
723 * Save the verification details
724 */
725 set_transient('transferito_connected_sites', $connectedSites);
726
727 /**
728 * Return the response
729 */
730 wp_send_json_success([ 'url' => $url ]);
731
732 } catch (\Exception $exception) {
733 wp_send_json_error([
734 'message' => 'Unable to create Download Request',
735 'debug' => $exception->getMessage()
736 ], 400);
737 }
738 }
739
740 public function destinationServerValidation()
741 {
742 check_ajax_referer('connect_to_server', 'securityKey');
743
744 try {
745
746 $this->destinationServerValidationCheck(false);
747
748 /**
749 * Return the response
750 */
751 wp_send_json([ 'connected' => true ], 200);
752
753 } catch (\Exception $exception) {
754 wp_send_json([
755 'e' => get_transient('transferito_connected_sites'),
756 'connected' => false ,
757 'reason' => $exception->getMessage()
758 ], 400);
759 }
760 }
761
762 public function addCheckServerValidation($url, $filename, $token)
763 {
764 /**
765 * Check to see if connection can be verified
766 */
767 $siteAdded = false;
768
769 /**
770 * Set the migration domain
771 */
772 set_transient('transferito_migration_unchanged_domain', $url);
773
774 try {
775 /**
776 * Check to see of a site already exists and is connected
777 */
778 $this->destinationServerValidationCheck(true);
779
780 /**
781 * Assign Value to Site Added
782 */
783 $siteAdded = true;
784
785 } catch (\Exception $exception) {
786
787 /**
788 * Create Verification Array
789 */
790 $verificationDetails = [
791 'domain' => $url,
792 'token' => $token,
793 'filename' => $filename,
794 'connected' => false
795 ];
796
797 /**
798 * Get the list of connected sites
799 */
800 $connectedSites = get_transient('transferito_connected_sites');
801
802 /**
803 * Check to see if the sites are empty if so - Create an array
804 */
805 if (!$connectedSites) {
806 $connectedSites = array();
807 }
808
809 /**
810 * Add verification Details to the array
811 */
812 $connectedSites[] = $verificationDetails;
813
814 /**
815 * Save the verification details
816 */
817 set_transient('transferito_connected_sites', $connectedSites);
818
819 try {
820 /**
821 * Check to see of a site already exists and is connected
822 */
823 $this->destinationServerValidationCheck(true);
824
825 /**
826 * Assign Value to Site Added
827 */
828 $siteAdded = true;
829 } catch (\Exception $secondException) { }
830 }
831
832 return $siteAdded;
833 }
834
835 private function destinationServerValidationCheck($removeNonConnectedSite)
836 {
837 try {
838 $connectionExists = $this->destinationServerConnectionExists('array');
839
840 /**
841 * Check to see if the site exists
842 */
843 if (!$connectionExists) {
844 throw new \Exception('NO_CONNECTION_DETAILS_EXIST');
845 }
846
847 /**
848 * Build the Validation URL
849 */
850 $validationURL = $this->buildVerificationURL(
851 $connectionExists['domain'],
852 $connectionExists['filename']
853 );
854
855 /**
856 * Check to see if connection can be verified
857 */
858 $validationCheck = $this->api->validateServerConnection($validationURL, array(
859 'method' => 'connect',
860 'token' => $connectionExists['token']
861 ));
862
863 /**
864 * Get the Key
865 */
866 $connectionSiteKey = $this->destinationServerConnectionExists('key');
867
868 /**
869 * Check to see if we are connected
870 */
871 $connected = $validationCheck['code'] === 202;
872
873 /**
874 * Check to see if fails
875 */
876 if (!$connected) {
877 /**
878 * If the site no longer exists - Remove it
879 */
880 if ($removeNonConnectedSite) {
881 $this->updateTheDestinationServerConnection($connectionSiteKey, true, 'remove');
882 }
883
884 throw new \Exception('FILE_DOES_NOT_EXIST_YET');
885 }
886
887 /**
888 * Update the connected site array
889 */
890 $this->updateTheDestinationServerConnection($connectionSiteKey, true, 'update');
891 } catch (\Exception $exception) {
892 throw new \Exception(esc_html($exception->getMessage()));
893 }
894 }
895
896 private function buildVerificationURL($domain, $filename)
897 {
898 return $domain . '/' . $filename;
899 }
900
901 private function destinationServerConnectionExists($mode)
902 {
903 /**
904 * Get the domain
905 */
906 $domain = get_transient('transferito_migration_unchanged_domain');
907
908 /**
909 * Get the list of connected sites
910 */
911 $connectedSites = get_transient('transferito_connected_sites');
912
913 /**
914 * If no sites exist - Return false
915 */
916 if (!$connectedSites) {
917 return false;
918 }
919
920 /**
921 * Get the array
922 */
923 $selectedSite = array_filter($connectedSites, function($value) use ($domain) {
924 $domainParts = explode('://', $domain);
925 $siteDomainParts = explode('://', $value['domain']);
926 return $domainParts[1] === $siteDomainParts[1];
927 });
928
929 /**
930 * If the site can't be found - return falsey
931 */
932 if (empty($selectedSite)) {
933 return false;
934 }
935
936 $selectedArrayKey = array_keys($selectedSite)[0];
937
938 /**
939 * If the mode is key just return the key
940 */
941 if ($mode === 'key') {
942 return $selectedArrayKey;
943 }
944
945 return $selectedSite[$selectedArrayKey];
946 }
947
948 private function updateTheDestinationServerConnection($key, $connected, $mode)
949 {
950 /**
951 * Get the list of connected sites
952 */
953 $connectedSites = get_transient('transferito_connected_sites');
954
955 /**
956 * If there aren't any sites return false
957 */
958 if (!$connectedSites) {
959 return false;
960 }
961
962 if ($mode === 'update') {
963 /**
964 * Update the connection Status for the element
965 */
966 $connectedSites[$key]['connected'] = $connected;
967 }
968
969 if ($mode === 'remove') {
970 unset($connectedSites[$key]);
971 }
972
973 /**
974 * Save the connection sites
975 */
976 set_transient('transferito_connected_sites', $connectedSites);
977 }
978
979 public function cPanelCheck()
980 {
981 /**
982 * verify nonce with every template change call
983 */
984 check_ajax_referer('cpanel_check', 'securityKey');
985
986 $localMigrationSanitized = isset($_POST['localMigration'])
987 ? sanitize_text_field(wp_unslash($_POST['localMigration']))
988 : '';
989
990 $domainSanitized = isset($_POST['domain'])
991 ? sanitize_text_field(wp_unslash($_POST['domain']))
992 : '';
993
994 $localMigration = filter_var($localMigrationSanitized, FILTER_VALIDATE_BOOL);
995
996 /**
997 * If migrating to use the DesktopApp
998 */
999 if ($localMigration) {
1000
1001 /**
1002 * Domain
1003 */
1004 $domain = rtrim(trim($domainSanitized), '/');
1005
1006 /**
1007 * Default transfer method
1008 */
1009 $transferMethod = 'localSiteMigration';
1010
1011 set_transient('transferito_transfer_method', $transferMethod);
1012 set_transient('transferito_migration_domain', $domain);
1013
1014 /**
1015 *
1016 */
1017 wp_send_json_success([
1018 'transferMethod' => $transferMethod,
1019 'cpanelAllowed' => false,
1020 'securityToken' => wp_create_nonce("prepare_migration_files")
1021 ]);
1022
1023
1024 // $this->prepareLocalDownload();
1025
1026 die();
1027 }
1028
1029 /**
1030 *
1031 */
1032 if (!$localMigration) {
1033 $splitURL = explode('://', $domainSanitized);
1034
1035 /**
1036 * Check user hasn't used double protocol
1037 */
1038 if (count($splitURL) !== 2) {
1039 wp_send_json_error([
1040 'message' => 'Failed URL Check'
1041 ], 400);
1042 }
1043
1044 /**
1045 * Domain
1046 */
1047 $domain = rtrim(trim($domainSanitized), '/');
1048
1049 /**
1050 * cPanel admin URL
1051 */
1052 $cpanelAdminURL = $domain . ':2083';
1053
1054 /**
1055 * Result from the cPanel check
1056 */
1057 $response = $this->api->cPanelAvailabilityCheck($cpanelAdminURL);
1058
1059 /**
1060 * Check that the string is in the response
1061 */
1062 $cPanelInHTMLSource = stripos($response['message'], 'cpanel') !== false;
1063
1064 /**
1065 * Check if we should default to cPanel
1066 */
1067 $cPanelAllowed = $response['code'] === 200 && $cPanelInHTMLSource;
1068
1069 /**
1070 * Default transfer method
1071 */
1072 $transferMethod = $cPanelAllowed ? 'cpanel' : 'manual';
1073
1074 /**
1075 * Correct cPanel URL
1076 */
1077 $cpanelFinalAdminURLSplit = explode(':2083', $response['url']);
1078
1079 /**
1080 * Save the domain
1081 */
1082 set_transient('transferito_migration_domain', $domain);
1083 set_transient('transferito_migration_unchanged_domain', $domain);
1084 set_transient('transferito_cpanel_allowed', $cPanelAllowed);
1085 set_transient('transferito_transfer_method', $transferMethod);
1086
1087 /**
1088 * Only save if cPanel is allowed
1089 */
1090 if ($cPanelAllowed) {
1091 set_transient('transferito_cpanel_domain', $cpanelFinalAdminURLSplit[0]);
1092 } else {
1093 set_transient('transferito_cpanel_domain', $domain);
1094 }
1095
1096 $htmlTemplate = '';
1097 $screenName = '';
1098
1099 /**
1100 * Check to see which template to render based on the result of the site connection test
1101 */
1102 try {
1103
1104 $this->destinationServerValidationCheck(true);
1105
1106 /**
1107 * Set the Screen Name
1108 */
1109 $screenName = 'databaseAuthentication';
1110
1111 /**
1112 * Load the correct for the DB Details
1113 * As the user is connected
1114 */
1115 $htmlTemplate = loadTemplate('parts/migration/manual/database-detail', [
1116 'detail' => get_transient('transferito_manual_server_detail'),
1117 'completed' => get_transient('transferito_database_detail_completed'),
1118 ]);
1119
1120 } catch (\Exception $exception) {
1121
1122 /**
1123 * Set the Screen Name
1124 */
1125 $screenName = 'connectToServer';
1126
1127 /**
1128 * Load the correct for the user to connect
1129 */
1130 $htmlTemplate = loadTemplate('parts/migration/connect-to-server', [
1131 'cpanelAllowed' => $cPanelAllowed,
1132 'transferMethod' => $transferMethod,
1133 'url' => $domain
1134 ]);
1135 }
1136
1137 /**
1138 * Push loadScreen event to telemetry
1139 */
1140 $this->telemetry->pushEvent('loadScreen', [
1141 'screenName' => $screenName // @todo change screen name to ConnectToServer
1142 ]);
1143
1144 wp_send_json_success([
1145 't' => $cpanelFinalAdminURLSplit[0],
1146 'cPanelAllowed' => $cPanelAllowed,
1147 'URL' => $domain,
1148 'transferMethod' => $transferMethod,
1149 'htmlTemplate' => $htmlTemplate,
1150 ]);
1151 }
1152 }
1153
1154 /**
1155 * @deprecated
1156 * @return void
1157 */
1158 public function cpanelAuthentication()
1159 {
1160 try {
1161 check_ajax_referer('cpanel_migration', 'securityKey');
1162
1163 /**
1164 * Get the domain
1165 */
1166 $domain = get_transient('transferito_cpanel_domain');
1167
1168 /**
1169 * Get the destination URL
1170 */
1171 $destinationURL = get_transient('transferito_migration_domain');
1172
1173 /**
1174 * Split the destination URL into a domain via the protocol
1175 */
1176 $splitDestinationURL = explode('//', $destinationURL);
1177
1178 /**
1179 * Get the destination Domain
1180 */
1181 $destinationDomain = count($splitDestinationURL) === 2 ? $splitDestinationURL[1] : $destinationURL;
1182
1183 /**
1184 * Pull the auth details
1185 */
1186 $cPanelDetails = isset($_POST['auth']) ? array_map('sanitize_text_field', wp_unslash($_POST['auth'])) : [];
1187 $cPanelDetails['cpanelPass'] = sanitize_text_field(wp_unslash($cPanelDetails['cpanelPass']));
1188 $cPanelDetails['cPanelUseApiToken'] = isset($cPanelDetails['cPanelUseApiToken']) ? false : true;
1189
1190 /**
1191 * Updated the cPanel array
1192 */
1193 $updatedAuthArray = array_merge($cPanelDetails, [ 'cpanelHost' => $domain ]);
1194
1195 /**
1196 * Make a request to the endpoint to get a list of the available domains
1197 */
1198 $authResult = $this->api->cPanelAuth($updatedAuthArray);
1199
1200 /**
1201 * If there is an error authenticating
1202 */
1203 if ($authResult['code'] !== 200) {
1204 throw new \Exception(stripslashes($authResult['message']));
1205 }
1206
1207 /**
1208 * Get the list of selectable domains
1209 */
1210 $availableDomains = $authResult['message'];
1211
1212 /**
1213 * Save the cPanel details
1214 */
1215 set_transient('transferito_cpanel_auth_details', $cPanelDetails);
1216
1217 /**
1218 * Update the completed flag
1219 */
1220 set_transient('transferito_cpanel_auth_details_completed', true);
1221
1222 /**
1223 * Push loadScreen event to telemetry
1224 */
1225 $this->telemetry->pushEvent('loadScreen', [
1226 'screenName' => 'cPanelDomainSelection'
1227 ]);
1228
1229 /**
1230 * Assign the data needed for the template to a variable to save
1231 */
1232 $domainSelectionOptions = [
1233 'domain' => $destinationDomain,
1234 'domains' => $availableDomains,
1235 'username' => $cPanelDetails['cpanelUser'],
1236 'password' => $cPanelDetails['cpanelPass'],
1237 'apiToken' => $cPanelDetails['cPanelApiToken'],
1238 'useApiToken' => $cPanelDetails['cPanelUseApiToken'],
1239 'URL' => $domain,
1240 ];
1241
1242 /**
1243 * Save the domain selection data needed for the template
1244 */
1245 set_transient('transferito_cpanel_domain_selection', $domainSelectionOptions);
1246
1247 /**
1248 * Return success object
1249 * To update the nav and change the child template
1250 */
1251 wp_send_json_success([
1252 'template' => loadTemplate('parts/migration/cpanel/domain-selection', $domainSelectionOptions)
1253 ]);
1254
1255 } catch(\Exception $exception) {
1256 wp_send_json_error([
1257 'template' => loadTemplate('parts/migration/cpanel/main', [
1258 'showErrorPopup' => true
1259 ]),
1260 ], 400);
1261 }
1262 }
1263
1264 /**
1265 * @deprecated
1266 * @return void
1267 */
1268 public function serverDetailValidation()
1269 {
1270 try {
1271 check_ajax_referer('manual_migration_server_detail', 'securityKey');
1272
1273 /**
1274 * Pull the server details
1275 */
1276 $serverDetails = isset($_POST['serverDetails'])
1277 ? array_map('sanitize_text_field', wp_unslash($_POST['serverDetails']))
1278 : [];
1279
1280 /**
1281 * Updated the server details array
1282 */
1283 $updatedServerDetailArray = array_merge($serverDetails, [ 'ftpPath' => '.' ]);
1284
1285 /**
1286 * Set the server detail as a transient
1287 */
1288 set_transient('transferito_manual_server_detail', $updatedServerDetailArray);
1289
1290 /**
1291 * Make a request to the endpoint to get a list of the available domains
1292 */
1293 $serverDetailResult = $this->api->ftpValidation($updatedServerDetailArray);
1294
1295 /**
1296 * Fail when the FTP connection fails
1297 */
1298 if ($serverDetailResult['code'] !== 200) {
1299 throw new \Exception('API_REQUEST_FAILURE');
1300 }
1301
1302 /**
1303 * Fail when the FTP connection fails
1304 */
1305 if (!$serverDetailResult['message']) {
1306 throw new \Exception('FALSEY_RESPONSE');
1307 }
1308
1309 /**
1310 * Fail when the has connected property isn't present
1311 */
1312 if (!property_exists($serverDetailResult['message'], 'hasConnected')) {
1313 throw new \Exception('PROPERTY_MISSING');
1314 }
1315
1316 /**
1317 * Fail when the FTP connection fails
1318 */
1319 if (!$serverDetailResult['message']->hasConnected) {
1320 throw new \Exception('FTP_CONNECTION_FAILURE');
1321 }
1322
1323 /**
1324 * Push loadScreen event to telemetry
1325 */
1326 $this->telemetry->pushEvent('loadScreen', [
1327 'screenName' => 'directorySelector'
1328 ]);
1329
1330 /**
1331 * Set the server detail as a transient
1332 */
1333 $updatedServerDetailArray['ftpPort'] = (string) $serverDetailResult['message']->ftpPort;
1334 $updatedServerDetailArray['useSFTP'] = (string) $serverDetailResult['message']->useSFTP;
1335 set_transient('transferito_manual_server_detail', $updatedServerDetailArray);
1336
1337 /**
1338 * Return success object
1339 * To update the nav and change the child template
1340 */
1341 wp_send_json_success([
1342 'template' => loadTemplate('parts/migration/manual/directory-selection', []),
1343 'useSFTP' => $updatedServerDetailArray['useSFTP']
1344 ]);
1345
1346 } catch (\Exception $exception) {
1347 wp_send_json_error([
1348 'connected' => false,
1349 ], 400);
1350 }
1351 }
1352
1353 /**
1354 * @deprecated
1355 * @return void
1356 */
1357 public function directoryValidation()
1358 {
1359 try {
1360 check_ajax_referer('manual_migration_directory_selection', 'securityKey');
1361
1362 /**
1363 * Check that the ftpInfo element exists
1364 */
1365 $serverDetail = get_transient('transferito_manual_server_detail');
1366
1367 /**
1368 * Get the saved domain to use as the domain to check
1369 */
1370 $domain = get_transient('transferito_migration_domain');
1371
1372 /**
1373 * Sanitize the directory
1374 */
1375 $directory = isset($_POST['directory']) ? sanitize_text_field(wp_unslash($_POST['directory'])) : '';
1376
1377 /**
1378 * Create the updated server detail
1379 */
1380 $directoryCheckPayload = array_merge($serverDetail, [
1381 'path' => $directory,
1382 'URL' => $domain
1383 ]);
1384
1385 /**
1386 * update the ftpPath from the payload
1387 */
1388 $directoryCheckPayload['ftpPath'] = $directory;
1389
1390 /**
1391 * Save the updated server detail
1392 */
1393 set_transient('transferito_manual_server_detail', $directoryCheckPayload);
1394
1395 /**
1396 * Response from the directory check
1397 */
1398 $response = $this->api->directoryCheck($directoryCheckPayload);
1399
1400 /**
1401 *
1402 */
1403 // if ($response['code'] !== 200) {
1404 // throw new \Exception('FAILED_CORRECT_DIRECTORY_CHECK');
1405 // }
1406
1407 /**
1408 * Push loadScreen event to telemetry
1409 */
1410 // $this->telemetry->pushEvent('loadScreen', [
1411 // 'screenName' => 'databaseAuthentication'
1412 // ]);
1413
1414 /**
1415 * Return success object
1416 *
1417 * @todo Update the message -> check to see if the return message has the property correctDirectory
1418 */
1419 wp_send_json_success($response['message']);
1420
1421 } catch (\Exception $exception) {
1422 wp_send_json_error([
1423 'error' => $exception->getMessage(),
1424 'template' => loadTemplate('parts/migration/manual/directory-selection', []),
1425 ], 400);
1426 }
1427 }
1428
1429 public function databaseValidation()
1430 {
1431 try {
1432 check_ajax_referer('manual_migration_database_detail', 'securityKey');
1433
1434 /**
1435 * Merge the array
1436 */
1437 $databaseTestPayload = isset($_POST['databaseDetail'])
1438 ? array_map('sanitize_text_field', wp_unslash($_POST['databaseDetail']))
1439 : [];
1440
1441 /**
1442 * Get the saved domain to use as the domain to check
1443 */
1444 $domain = get_transient('transferito_migration_domain');
1445
1446 /**
1447 * Exclude database
1448 */
1449 $excludeDatabase = isset($databaseTestPayload['exclude_database_transfer']) && $databaseTestPayload['exclude_database_transfer'] === 'true';
1450
1451 /**
1452 * Use Existing Details
1453 */
1454 $useExistingDetails = isset($databaseTestPayload['use_existing_database']) && $databaseTestPayload['use_existing_database'] === 'true';
1455
1456 /**
1457 * Assign the domain flag to the new payload
1458 */
1459 $databaseTestPayload['domain'] = $domain;
1460 $databaseTestPayload['URL'] = $domain;
1461
1462 /**
1463 * Update the server detail transient
1464 */
1465 set_transient('transferito_manual_server_detail', $databaseTestPayload);
1466
1467 /**
1468 * Add check that db detail has been completed
1469 */
1470 set_transient('transferito_database_detail_completed', true);
1471
1472 /**
1473 * Only do the DB check if the database isn't excluded
1474 */
1475 if (!$excludeDatabase && !$useExistingDetails) {
1476 /**
1477 * Get connected Site
1478 */
1479 $connectedSite = $this->destinationServerConnectionExists('array');
1480
1481 /**
1482 * Merged Payload
1483 */
1484 $mergedPayload = array_merge($databaseTestPayload, [
1485 'validationToken' => $connectedSite['token'],
1486 'validationFile' => $connectedSite['filename'],
1487 'domain' => $connectedSite['domain']
1488 ]);
1489
1490 /**
1491 * Make a request to the database validation endpoint
1492 */
1493 $response = $this->api->databaseValidation($mergedPayload);
1494
1495 /**
1496 * Check to see whether the DB details have failed
1497 */
1498 if ($response['code'] !== 200) {
1499 throw new \Exception('FAILED_DATABASE_CHECK');
1500 }
1501 }
1502
1503 /**
1504 * Final additions to the payload
1505 */
1506 $migrationDetail = array_merge($databaseTestPayload, [
1507 'transferMethod' => 'ftp',
1508 'transferType' => 'manual'
1509 ]);
1510
1511 /**
1512 * Pass the full details and nonce to validate the call
1513 */
1514 wp_send_json_success([
1515 'migrationDetail' => array_map('stripslashes', $migrationDetail),
1516 'securityKey' => wp_create_nonce("prepare_migration_files")
1517 ]);
1518
1519 } catch (\Exception $exception) {
1520 /**
1521 * Get the server detail
1522 */
1523 $serverDetail = get_transient('transferito_manual_server_detail');
1524
1525 /**
1526 *
1527 */
1528 wp_send_json_error([
1529 'template' => loadTemplate('parts/migration/manual/database-detail', [
1530 'detail' => $serverDetail,
1531 'completed' => get_transient('transferito_database_detail_completed')
1532 ]),
1533 ], 400);
1534 }
1535 }
1536
1537 public function chooseMigrationMethod()
1538 {
1539 /**
1540 * verify nonce with every template change call
1541 */
1542 check_ajax_referer('migration_choice', 'actionKey');
1543
1544 $freeTierUser = (!$this->options['public_transferito_key'] || !$this->options['secret_transferito_key']);
1545 $mainMessage = stripslashes('We are creating a backup of your current WordPress installation');
1546 $secondaryMessage = stripslashes('Please wait.. This may take a few minutes, do not close this window or refresh the page');
1547
1548 $migrationType = isset($_POST['data']['migrationType'])
1549 ? sanitize_text_field(wp_unslash($_POST['data']['migrationType']))
1550 : '';
1551
1552 $htmlTemplate = ($migrationType === 'useCpanel')
1553 ? loadTemplate('parts/migration/cpanel-validation', [
1554 'mainMessage' => $mainMessage,
1555 'secondaryMessage' => $secondaryMessage
1556 ])
1557 : loadTemplate('parts/migration/transfer-detail-entry', [
1558 'mainMessage' => $mainMessage,
1559 'secondaryMessage' => $secondaryMessage,
1560 'freeMigration' => $freeTierUser,
1561 'directories' => Config::getWPContentPaths()
1562 ]);
1563
1564 $siteCheckResponse = [ 'htmlTemplate' => $htmlTemplate ];
1565
1566 wp_send_json_success($siteCheckResponse);
1567 }
1568
1569 public function statusCheck()
1570 {
1571
1572 /**
1573 * Verify nonce with every status check
1574 */
1575 check_ajax_referer('migration_status_check', 'securityKey');
1576
1577 $token = isset($_POST['token']) ? sanitize_text_field(wp_unslash($_POST['token'])) : '';
1578 $response = $this->api->getStatus($token);
1579 $responseMessage = $response['message'];
1580
1581 /**
1582 * Check the migration
1583 */
1584 if ($response["code"] === 200) {
1585 $startCleanUp = get_transient('transferito_cleanup_after_completion');
1586 $isCompleted = (isset($responseMessage->status))
1587 ? $responseMessage->status === 'completed' || $responseMessage->status === 'completed.with.errors'
1588 : false;
1589
1590 if ($responseMessage->status === 'completed' && $startCleanUp) {
1591 delete_transient('transferito_cleanup_after_completion');
1592 }
1593
1594 wp_send_json_success([
1595 'metadata' => $responseMessage->metaData,
1596 'statuses' => $responseMessage->all,
1597 'status' => $responseMessage->status,
1598 'completed' => $isCompleted
1599 ]);
1600
1601 } else {
1602 wp_send_json_error($responseMessage->error, $response['code']);
1603 }
1604 }
1605
1606 public function prepareDownload()
1607 {
1608 try {
1609 check_ajax_referer('prepare_migration_files', 'security');
1610
1611 $settings = get_option('transferito_settings_option');
1612
1613 /**
1614 * Site size info
1615 */
1616 $siteDetails = getDirectorySize(TRANSFERITO_ABSPATH);
1617 $siteSizeInfo = get_transient('transferito_installation_size');
1618 $siteSize = $siteSizeInfo ? $siteSizeInfo : [];
1619
1620 /**
1621 * Check the user's API keys
1622 */
1623 $hasValidAPIKeys = $this->areAPIKeysValid(
1624 $settings['public_transferito_key'],
1625 $settings['secret_transferito_key']
1626 );
1627
1628 /**
1629 * Get the Transfer Method
1630 */
1631 $transferMethod = isset($_POST['migrationDetails']['transferMethod'])
1632 ? sanitize_text_field(wp_unslash($_POST['migrationDetails']['transferMethod']))
1633 : '';
1634
1635 /**
1636 * Fire the required upgrade flow
1637 */
1638 if (!$hasValidAPIKeys && $siteDetails['maxSizeExceeded']) {
1639 wp_send_json_success(array_merge([
1640 'message' => '<strong>PLEASE DO NOT</strong> navigate away or reload this page while your migration is in process. Doing so will stop your migration.',
1641 'force' => true,
1642 'useZipFallback' => false,
1643 'created' => true,
1644 'excludeDatabase' => false,
1645 'upgradeRequired' => true,
1646 'htmlTemplate' => loadTemplate('parts/migration/progress/main',
1647 array_merge(
1648 [
1649 'backupPrepare' => true,
1650 'backupInstallation' => true,
1651 'uploadBackup' => false,
1652 'downloadBackup' => true,
1653 'extractBackup' => true,
1654 'installDatabase' => true,
1655 'finalizeInstallation' => true,
1656 'completed' => true
1657 ],
1658 [
1659 'method' => ''
1660 ]
1661 )
1662 )
1663 ], $siteSize));
1664
1665 die();
1666 }
1667
1668 /**
1669 * Set as a FREE User
1670 */
1671 if (!$hasValidAPIKeys) {
1672 $this->api->setFreeUser();
1673 }
1674
1675 /**
1676 * If a local migration forward to the migration method
1677 */
1678 if ($transferMethod === 'localSiteMigration') {
1679 $this->prepareLocalDownload($hasValidAPIKeys);
1680 die();
1681 }
1682
1683 /**
1684 * @DoNotRemove
1685 * Important as it creates the access file to check whether the migration will be an upload
1686 */
1687 Config::getCorrectPath();
1688
1689 $forceUpload = isset($settings['transferito_force_upload']) ? $settings['transferito_force_upload'] : false;
1690 $migrationDetails = isset($_POST['migrationDetails'])
1691 ? array_map('sanitize_text_field', wp_unslash($_POST['migrationDetails']))
1692 : [];
1693 $excludeDatabase = isset($migrationDetails['exclude_database_transfer'])
1694 ? $migrationDetails['exclude_database_transfer']
1695 : null;
1696 $folderPaths = isset($migrationDetails['folder_path'])
1697 ? array_map('sanitize_text_field', wp_unslash($migrationDetails['folder_path']))
1698 : null;
1699 $destinationURL = isset($migrationDetails['domain'])
1700 ? sanitize_text_field($migrationDetails['domain'])
1701 : null;
1702 $selectedFolderEnabled = ($folderPaths !== null);
1703 $backupDirectory = bin2hex(openssl_random_pseudo_bytes(8));
1704
1705 /**
1706 * Get the current domain & add it to the migrationDetails
1707 */
1708 $migrationDetails['currentDomain'] = site_url();
1709
1710 /**
1711 * Add the check to see if the options are selected
1712 */
1713 $disableWPObjectCache = isset($settings['transferito_disable_wordpress_cache'])
1714 ? $settings['transferito_disable_wordpress_cache']
1715 : false;
1716 $ignoreMalcareWAF = isset($settings['transferito_malcare_waf_plugin_fix'])
1717 ? $settings['transferito_malcare_waf_plugin_fix']
1718 : false;
1719
1720 /**
1721 * Get connected Site
1722 */
1723 $connectedSite = $this->destinationServerConnectionExists('array');
1724
1725 /**
1726 * Find the functions file - To Disable the WP Object Cache
1727 */
1728 if ($disableWPObjectCache) {
1729 $this->disableWPObjectCache();
1730 }
1731
1732 /**
1733 * Find the user.ini file - To Disable MalCare WAF
1734 */
1735 if ($ignoreMalcareWAF) {
1736 $this->disableAutoPrependOption();
1737 }
1738
1739
1740 /**
1741 * Set the destination URL transient
1742 */
1743 set_transient('transferito_migration_domain', $destinationURL);
1744
1745 /**
1746 * Check if we can download directly from the server, unless the force upload flag is checked
1747 */
1748 if ($forceUpload) {
1749 $siteAccessed = false;
1750 } else {
1751 $sampleArchiveFilename = bin2hex(random_bytes(16)) . '.zip';
1752
1753 /**
1754 * Create zip file
1755 * @todo create zip file
1756 */
1757
1758 try {
1759 $zip = new \ZipArchive;
1760
1761 if ($zip->open(TRANSFERITO_ABSPATH . $sampleArchiveFilename, \ZipArchive::CREATE) === TRUE) {
1762 $zip->addFromString('test_file.txt', 'Test zip file - to test migration');
1763 $zip->close();
1764 }
1765
1766 /**
1767 * Request to check the direct download
1768 */
1769 $directDownload = $this->api->directDownloadCheck([
1770 'validationToken' => $connectedSite['token'],
1771 'validationFile' => $connectedSite['filename'],
1772 "domain" => $destinationURL,
1773 'currentURL' => site_url(),
1774 'filename' => $sampleArchiveFilename,
1775 ]);
1776
1777 /**
1778 * Assign the result to
1779 */
1780 $siteAccessed = $directDownload['message']->canDownload;
1781
1782 /**
1783 * Remove the sample file
1784 */
1785 wp_delete_file(TRANSFERITO_ABSPATH . $sampleArchiveFilename);
1786
1787 } catch(\Exception $exception) {
1788 $siteAccessed = false;
1789 }
1790 }
1791
1792 /**
1793 * Add the local flag to the ftp details
1794 */
1795 $migrationDetails['isLocal'] = $siteAccessed;
1796
1797 /**
1798 * If the folder path is set and the transfer type is manual
1799 * Then validate the
1800 */
1801 $additionalData = [];
1802 if (isset($migrationDetails['folder_path']) && $migrationDetails['transferType'] === 'manual') {
1803 $additionalData['folder_path'] = array_map('sanitize_text_field', wp_unslash($migrationDetails['folder_path']));
1804 }
1805
1806 /**
1807 * Clean text fields before create migration request
1808 */
1809 $migrationPayload = array_merge($migrationDetails, $additionalData);
1810 $migrationPayload['isLocal'] = $siteAccessed;
1811 $migrationPayload['currentPath'] = ABSPATH;
1812
1813 /**
1814 * Create a migration and return a token
1815 */
1816 $createdMigration = $this->api->createMigration($migrationPayload);
1817
1818 /**
1819 * Fail gracefully if there is an issue creating the migration
1820 */
1821 if ($createdMigration['code'] !== 200) {
1822 $message = (property_exists($createdMigration['message'], 'result'))
1823 ? $createdMigration['message']->result
1824 : 'We are unable to create your migration. If this issue persists, please contact support.';
1825 throw new \Exception(stripslashes($message));
1826 }
1827
1828 /**
1829 * Set transients to use with the failure endpoint
1830 */
1831 set_transient('transferito_migration_token', $createdMigration['message']->token);
1832 set_transient('transferito_migration_timestamp', $createdMigration['message']->timestamp);
1833
1834 /**
1835 * Get the migration token
1836 */
1837 $migrationToken = $createdMigration['message']->token;
1838
1839 /**
1840 * Final Domain
1841 */
1842 $finalDomain = $createdMigration['message']->domain;
1843
1844 $serverCheck = null;
1845
1846 /**
1847 * If we can contact the site
1848 * Then check the server requirements
1849 */
1850 if ($siteAccessed) {
1851 /**
1852 * Create the test file & token
1853 */
1854 $testFile = Config::createTestFile();
1855
1856 /**
1857 * Check the destination server info
1858 */
1859 $serverRequirements = $this->api->checkDestinationServerRequirements([
1860 'validationToken' => $connectedSite['token'],
1861 'validationFile' => $connectedSite['filename'],
1862 'domain' => $destinationURL,
1863 'token' => $migrationToken,
1864 'timestamp' => $createdMigration['message']->timestamp,
1865 'fileURL' => $testFile['url'],
1866 'fileHash' => $testFile['hash']
1867 ]);
1868
1869 /**
1870 * Fallback
1871 * If the server req check fails
1872 */
1873 if ($serverRequirements['code'] !== 200) {
1874 $uploadToS3 = true;
1875 } else {
1876 $serverCheck = $serverRequirements;
1877 /**
1878 * Check the site access again
1879 * If it is a local environment - or if the module allowed is wget - Then default to S3
1880 */
1881 $uploadToS3 = !$serverRequirements['message']->pullDirect;
1882 }
1883 }
1884
1885 /**
1886 * If we can not reach the site
1887 * Upload directly to S3 bucket
1888 */
1889 if (!$siteAccessed) {
1890 $uploadToS3 = true;
1891 }
1892
1893 /**
1894 * Set transient for backup status
1895 */
1896 set_transient('transferito_backup_status', [
1897 'databaseBackupComplete' => $excludeDatabase ? true : false,
1898 'databaseExportComplete' => $excludeDatabase ? true : false,
1899 'codebaseBackupComplete' => false,
1900 'excludedDatabase' => $excludeDatabase
1901 ]);
1902
1903 /**
1904 * Set transient with transfer detail
1905 */
1906 set_transient('transferito_transfer_detail', [
1907 'isLocalEnv' => $uploadToS3,
1908 'selectedFolders' => $selectedFolderEnabled,
1909 'folders' => $folderPaths,
1910 'directory' => $backupDirectory,
1911 'token' => $migrationToken,
1912 'timestamp' => $createdMigration['message']->timestamp,
1913 'fromUrl' => site_url(),
1914 'newUrl' => $finalDomain
1915 ]);
1916
1917 /**
1918 * Set the transient for the destination site URL
1919 */
1920 set_transient('transferito_final_destination_url', $finalDomain);
1921
1922 /**
1923 * Available steps to pass to the template
1924 */
1925 $progressSteps = [
1926 'backupPrepare' => true,
1927 'backupInstallation' => true,
1928 'uploadBackup' => $uploadToS3,
1929 'downloadBackup' => true,
1930 'extractBackup' => true,
1931 'installDatabase' => !$excludeDatabase,
1932 'finalizeInstallation' => true,
1933 'completed' => true
1934 ];
1935
1936 /**
1937 * Get the requirement transient
1938 */
1939 $transferitoRequirements = get_transient('transferito_requirements');
1940
1941 /**
1942 * Push Migration detail event to telemetry
1943 */
1944 $this->telemetry->pushEvent('migrationDetails', [
1945 'uploadBackup' => $uploadToS3,
1946 'localMigration' => $uploadToS3,
1947 'databaseExcluded' => $excludeDatabase,
1948 'selectedFolders' => $selectedFolderEnabled,
1949 'migrationMethod' => $migrationDetails['transferMethod'],
1950 'siteSize' => isset($siteSize['totalSize']) ? $siteSize['totalSize'] : 0,
1951 'cPanelAPIToken' => isset($migrationDetails['cpanelApiToken']) ? $migrationDetails['cpanelApiToken'] : false
1952 ]);
1953
1954 /**
1955 * Push loadScreen event to telemetry
1956 */
1957 $this->telemetry->pushEvent('loadScreen', [
1958 'screenName' => 'migrationInProgress'
1959 ]);
1960
1961 wp_send_json_success(array_merge([
1962 'debug' => [
1963 'siteAccess' => $siteAccessed,
1964 'uploadS3' => $uploadToS3
1965 ],
1966 'message' => '<strong>PLEASE DO NOT</strong> navigate away or reload this page while your migration is in process. Doing so will stop your migration.',
1967 'force' => $forceUpload,
1968 'useZipFallback' => $transferitoRequirements['useZip'],
1969 'created' => true,
1970 'excludeDatabase' => $excludeDatabase,
1971 'upgradeRequired' => false,
1972 'htmlTemplate' => loadTemplate('parts/migration/progress/main',
1973 array_merge($progressSteps, [
1974 'method' => $migrationDetails['transferMethod'],
1975 'migrationToken' => $migrationToken
1976 ] )
1977 )
1978 ], $siteSize));
1979
1980 } catch (\Exception $exception) {
1981 /**
1982 * Push loadScreen event to telemetry
1983 */
1984 $this->telemetry->pushEvent('failedMigration', [
1985 'migrationStatus' => 'prepareDownload',
1986 'errorMessage' => $exception->getMessage()
1987 ]);
1988 $errorMessage = 'prepareDownload: ' . $exception->getMessage();
1989 $this->api->failedMigration($errorMessage);
1990 delete_transient('transferito_transfer_detail');
1991 wp_send_json_error([
1992 'message' => $exception->getMessage(),
1993 'error' => $exception->getTraceAsString(),
1994 'line' => $exception->getLine()
1995 ], 400);
1996 }
1997 }
1998
1999 private function prepareLocalDownload($premiumUser)
2000 {
2001 try {
2002
2003 /**
2004 * Fire the required upgrade flow
2005 */
2006 if (!$premiumUser) {
2007 wp_send_json_success([
2008 'message' => '<strong>PLEASE DO NOT</strong> navigate away or reload this page while your migration is in process. Doing so will stop your migration.',
2009 'force' => true,
2010 'useZipFallback' => false,
2011 'created' => true,
2012 'excludeDatabase' => false,
2013 'upgradeRequired' => true,
2014 'htmlTemplate' => loadTemplate('parts/migration/progress/main',
2015 array_merge(
2016 [
2017 'backupPrepare' => true,
2018 'backupInstallation' => true,
2019 'uploadBackup' => false,
2020 'downloadBackup' => true,
2021 'extractBackup' => true,
2022 'installDatabase' => true,
2023 'finalizeInstallation' => true,
2024 'completed' => true
2025 ],
2026 [
2027 'method' => ''
2028 ]
2029 )
2030 )
2031 ]);
2032
2033 die();
2034 }
2035
2036
2037 /**
2038 * @DoNotRemove
2039 * Important as it creates the access file to check whether the migration will be an upload
2040 */
2041 Config::getCorrectPath();
2042
2043 $transferMethod = 'localSiteMigration';
2044 $settings = get_option('transferito_settings_option');
2045 $destinationURL = get_transient('transferito_migration_domain');
2046 $backupDirectory = bin2hex(openssl_random_pseudo_bytes(8));
2047
2048 /**
2049 * Add the check to see if the options are selected
2050 */
2051 $disableWPObjectCache = isset($settings['transferito_disable_wordpress_cache'])
2052 ? $settings['transferito_disable_wordpress_cache']
2053 : false;
2054 $ignoreMalcareWAF = isset($settings['transferito_malcare_waf_plugin_fix'])
2055 ? $settings['transferito_malcare_waf_plugin_fix']
2056 : false;
2057
2058 /**
2059 * Find the functions file - To Disable the WP Object Cache
2060 */
2061 if ($disableWPObjectCache) {
2062 $this->disableWPObjectCache();
2063 }
2064
2065 /**
2066 * Find the user.ini file - To Disable MalCare WAF
2067 */
2068 if ($ignoreMalcareWAF) {
2069 $this->disableAutoPrependOption();
2070 }
2071
2072 /**
2073 * Set the destination URL transient
2074 */
2075 set_transient('transferito_migration_domain', $destinationURL);
2076
2077 /**
2078 * Create a migration and return a token
2079 */
2080 $createdMigration = $this->api->createMigration([
2081 'currentDomain' => site_url(),
2082 'transferMethod' => $transferMethod,
2083 'domain' => $destinationURL,
2084 'isLocal' => true,
2085 'currentPath' => ABSPATH
2086 ]);
2087
2088 /**
2089 * Fail gracefully if there is an issue creating the migration
2090 */
2091 if ($createdMigration['code'] !== 200) {
2092 $message = (property_exists($createdMigration['message'], 'result'))
2093 ? $createdMigration['message']->result
2094 : 'We are unable to create your migration. If this issue persists, please contact support.';
2095 throw new \Exception(stripslashes($message));
2096 }
2097
2098 /**
2099 * Set transients to use with the failure endpoint
2100 */
2101 set_transient('transferito_migration_token', $createdMigration['message']->token);
2102 set_transient('transferito_migration_timestamp', $createdMigration['message']->timestamp);
2103
2104 /**
2105 * Get the migration token
2106 */
2107 $migrationToken = $createdMigration['message']->token;
2108
2109 /**
2110 * Final Domain
2111 */
2112 $finalDomain = $createdMigration['message']->domain;
2113
2114 /**
2115 * Set transient for backup status
2116 */
2117 set_transient('transferito_backup_status', [
2118 'databaseBackupComplete' => false,
2119 'databaseExportComplete' => false,
2120 'codebaseBackupComplete' => false,
2121 'excludedDatabase' => false
2122 ]);
2123
2124 /**
2125 * Set transient with transfer detail
2126 */
2127 set_transient('transferito_transfer_detail', [
2128 'isLocalEnv' => true, // Defaulted to true as will always upload to S3 in this instance
2129 'selectedFolders' => false,
2130 'folders' => null, // No folders have been selected set directly to null
2131 'directory' => $backupDirectory,
2132 'token' => $migrationToken,
2133 'timestamp' => $createdMigration['message']->timestamp,
2134 'fromUrl' => site_url(),
2135 'newUrl' => $finalDomain
2136 ]);
2137
2138 /**
2139 * Set the transient for the destination site URL
2140 */
2141 set_transient('transferito_final_destination_url', $finalDomain);
2142
2143 /**
2144 * Available steps to pass to the template
2145 */
2146 $progressSteps = [
2147 'backupPrepare' => true,
2148 'backupInstallation' => true,
2149 'uploadBackup' => true,
2150 'downloadBackup' => true,
2151 'extractBackup' => true,
2152 'installDatabase' => true,
2153 'finalizeInstallation' => true,
2154 'completed' => true
2155 ];
2156
2157 /**
2158 * Get the requirement transient
2159 */
2160 $transferitoRequirements = get_transient('transferito_requirements');
2161
2162 /**
2163 * Site size info
2164 */
2165 $siteSizeInfo = get_transient('transferito_installation_size');
2166 $siteSize = $siteSizeInfo ? $siteSizeInfo : [];
2167
2168 /**
2169 * Push Migration detail event to telemetry
2170 */
2171 $this->telemetry->pushEvent('migrationDetails', [
2172 'uploadBackup' => true,
2173 'localMigration' => true,
2174 'databaseExcluded' => false,
2175 'selectedFolders' => false,
2176 'migrationMethod' => $transferMethod,
2177 'siteSize' => isset($siteSize['totalSize']) ? $siteSize['totalSize'] : 0,
2178 'cPanelAPIToken' => false
2179 ]);
2180
2181 /**
2182 * Push loadScreen event to telemetry
2183 */
2184 $this->telemetry->pushEvent('loadScreen', [
2185 'screenName' => 'migrationInProgress'
2186 ]);
2187
2188 wp_send_json_success(array_merge([
2189 'message' => '<strong>PLEASE DO NOT</strong> navigate away or reload this page while your migration is in process. Doing so will stop your migration.',
2190 'force' => true,
2191 'useZipFallback' => $transferitoRequirements['useZip'],
2192 'created' => true,
2193 'excludeDatabase' => false,
2194 'htmlTemplate' => loadTemplate('parts/migration/progress/main',
2195 array_merge(
2196 $progressSteps,
2197 [
2198 'method' => 'localSiteMigration'
2199 ]
2200 )
2201 )
2202 ], $siteSize));
2203
2204
2205 } catch (\Exception $exception) {
2206
2207 /**
2208 * @todo Implement a failure screen - If the migration creation fails
2209 */
2210
2211
2212 /**
2213 * Push loadScreen event to telemetry
2214 */
2215 $this->telemetry->pushEvent('failedMigration', [
2216 'migrationStatus' => 'prepareDownload',
2217 'errorMessage' => $exception->getMessage()
2218 ]);
2219 $errorMessage = 'prepareLocalDownload: ' . $exception->getMessage();
2220 $this->api->failedMigration($errorMessage);
2221 delete_transient('transferito_transfer_detail');
2222 wp_send_json_error($exception->getMessage(), 400);
2223 }
2224
2225 }
2226
2227 public function prepareCodebase()
2228 {
2229 try {
2230 check_ajax_referer('prepare_migration_files', 'security');
2231
2232 $transferDetail = get_transient('transferito_transfer_detail');
2233 $fileListCreated = $this->codeBase->createFileList($transferDetail['selectedFolders'], $transferDetail['folders']);
2234 $zipPaths = $this->codeBase->createArchivePath();
2235
2236 /**
2237 * Throw the error if the zip paths are not an array
2238 */
2239 if (is_string($zipPaths)) {
2240 throw new \Exception($zipPaths);
2241 }
2242
2243 /**
2244 * Set transient for zip creation paths
2245 */
2246 set_transient('transferito_codebase_archive', $zipPaths);
2247
2248 /**
2249 * Get the site size
2250 */
2251 $siteSizeInfo = get_transient('transferito_installation_size');
2252
2253 wp_send_json_success(array_merge($fileListCreated, $siteSizeInfo));
2254
2255 } catch(\Exception $exception) {
2256 /**
2257 * Push loadScreen event to telemetry
2258 */
2259 $this->telemetry->pushEvent('failedMigration', [
2260 'migrationStatus' => 'prepareCodebase',
2261 'errorMessage' => $exception->getMessage()
2262 ]);
2263 $errorMessage = 'prepareCodebase: ' . $exception->getMessage();
2264 $this->api->failedMigration($errorMessage);
2265 wp_send_json_error($exception->getMessage(), 400);
2266 }
2267 }
2268
2269 public function addFilesToCodebase()
2270 {
2271 try {
2272 check_ajax_referer('prepare_migration_files', 'security');
2273
2274 $currentIndex = isset($_POST['currentFileIndex']) ? sanitize_text_field(wp_unslash($_POST['currentFileIndex'])) : 0;
2275
2276 /**
2277 * Check to see if the client passed the add db flag flag is passed in to
2278 */
2279 $dbExportFileListPath = isset($_POST['addDatabaseExports']) ? DIRECTORY_SEPARATOR . 'db_import' : '';
2280 $zipDetail = get_transient('transferito_codebase_archive');
2281 $jsonFileName = TRANSFERITO_UPLOAD_PATH . $dbExportFileListPath . DIRECTORY_SEPARATOR . 'json' . DIRECTORY_SEPARATOR . 'file_list_' . $currentIndex . '.json';
2282 $jsonFile = file_get_contents($jsonFileName);
2283
2284 if (!file_exists($jsonFileName)) {
2285 throw new \Exception('Something has gone wrong. We can not find the requested back up file list.');
2286 }
2287
2288 if (!$jsonFile) {
2289 throw new \Exception('Something has gone wrong. We can not read requested back up file list.');
2290 }
2291
2292 $files = json_decode($jsonFile);
2293 $addedToArchive = $this->codeBase->addFileToArchive($zipDetail['path'], $files);
2294
2295 /**
2296 * If the response isn't truthy throw
2297 */
2298 if ($addedToArchive !== true) {
2299 throw new \Exception($addedToArchive);
2300 }
2301
2302 wp_send_json_success($zipDetail);
2303 } catch(\Exception $exception) {
2304 wp_send_json_error($exception->getMessage(), 400);
2305 }
2306 }
2307
2308 public function codebaseArchiveComplete()
2309 {
2310 try {
2311 check_ajax_referer('prepare_migration_files', 'security');
2312
2313 /**
2314 * Get the related transients
2315 */
2316 $backupStatus = get_transient('transferito_backup_status');
2317 $transferDetail = get_transient('transferito_transfer_detail');
2318 $zipDetail = get_transient('transferito_codebase_archive');
2319
2320 /**
2321 * Update the backup array
2322 * Notification of the codebase archive completed
2323 */
2324 $backupStatus['codebaseBackupComplete'] = true;
2325
2326 /**
2327 * Update the code path with the archive path or url
2328 */
2329 $uploadS3 = $transferDetail['isLocalEnv'];
2330 $transferDetail['archive'] = $uploadS3 ? $zipDetail['path'] : $zipDetail['url'];
2331
2332 /**
2333 * Update the transients with the updated values
2334 */
2335 set_transient('transferito_backup_status', $backupStatus);
2336 set_transient('transferito_transfer_detail', $transferDetail);
2337
2338 wp_send_json_success([
2339 'codebaseArchived' => true,
2340 'excludeDatabase' => $backupStatus['excludedDatabase']
2341 ]);
2342
2343 } catch (\Exception $exception) {
2344 wp_send_json_error($exception->getMessage(), 400);
2345 }
2346 }
2347
2348 public function prepareDatabase()
2349 {
2350 try {
2351 check_ajax_referer('prepare_migration_files', 'security');
2352
2353 /**
2354 * Prepare a table map that allows the chunked db database export
2355 */
2356 $tableMap = $this->dataBase->prepareTableMap();
2357
2358 /**
2359 * If the table map can not be created
2360 * Throw the error
2361 */
2362 if (!$tableMap) {
2363 throw new \Exception('We were in the process of preparing to back up your database but this has failed.');
2364 }
2365
2366 /**
2367 * Set the table map as a transient
2368 */
2369 set_transient('transferito_database_table_map', $tableMap);
2370
2371 /**
2372 * Return an array
2373 * Notifying the client of the table creation
2374 */
2375 wp_send_json_success([ 'tableMapCreated' => true ]);
2376
2377 } catch(\Exception $exception) {
2378 /**
2379 * Push loadScreen event to telemetry
2380 */
2381 $this->telemetry->pushEvent('failedMigration', [
2382 'migrationStatus' => 'prepareDatabase',
2383 'errorMessage' => $exception->getMessage()
2384 ]);
2385 $errorMessage = 'prepareDatabase: ' . $exception->getMessage();
2386 $this->api->failedMigration($errorMessage);
2387 wp_send_json_error($exception->getMessage(), 400);
2388 }
2389 }
2390
2391 public function chunkedDBExport()
2392 {
2393 try {
2394 check_ajax_referer('prepare_migration_files', 'security');
2395
2396 $firstRunSanitized = isset($_POST['firstRun'])
2397 ? sanitize_text_field(wp_unslash($_POST['firstRun']))
2398 : false;
2399
2400 /**
2401 * Validate the first run flag and convert it to a boolean
2402 */
2403 $firstRun = filter_var($firstRunSanitized, FILTER_VALIDATE_BOOLEAN);
2404
2405 /**
2406 * Pull the export progress data
2407 */
2408 $getDBProgress = get_transient('transferito_db_export_progress');
2409 $exportProgress = $getDBProgress ? $getDBProgress : [];
2410
2411 /**
2412 * On the first run - No arguments needed
2413 */
2414 if ($firstRun) {
2415 $exportResult = $this->dataBase->chunkedDBExport();
2416 }
2417
2418 /**
2419 * If it isn't the first run
2420 * Pass in the export progress
2421 */
2422 if (!$firstRun) {
2423 $exportResult = $this->dataBase->chunkedDBExport(
2424 $exportProgress['fileIndex'],
2425 $exportProgress['currentRowIndex'],
2426 $exportProgress['tableIndex']
2427 );
2428 }
2429
2430 /**
2431 * If the export fails - throw
2432 */
2433 if (!$exportResult) {
2434 throw new \Exception('We are unable to backup a part of your database.');
2435 }
2436
2437 /**
2438 * Return an array
2439 * Notifying the client of the status of the export
2440 */
2441 $mergedArray = array_merge($exportResult, $exportProgress, [ 'firstRun' => $firstRun ]);
2442 wp_send_json_success($mergedArray);
2443
2444 } catch(\Exception $exception) {
2445 wp_send_json_error($exception->getMessage(), 400);
2446 }
2447 }
2448
2449 public function databaseExportComplete()
2450 {
2451 try {
2452 check_ajax_referer('prepare_migration_files', 'security');
2453
2454 /**
2455 * Get the related transients
2456 */
2457 $backupStatus = get_transient('transferito_backup_status');
2458
2459 /**
2460 * Update the backup array
2461 * Notification of the database export completion
2462 */
2463 $backupStatus['databaseExportComplete'] = true;
2464
2465 /**
2466 * Get the requirement transient
2467 */
2468 $transferitoRequirements = get_transient('transferito_requirements');
2469
2470 /**
2471 * Update the transients with the updated values
2472 */
2473 set_transient('transferito_backup_status', $backupStatus);
2474
2475 wp_send_json_success([
2476 'databaseExported' => true ,
2477 'useZipFallback' => $transferitoRequirements['useZip'],
2478 ]);
2479
2480 } catch (\Exception $exception) {
2481 wp_send_json_error($exception->getMessage(), 400);
2482 }
2483 }
2484
2485 public function databaseRelocation()
2486 {
2487 try {
2488 check_ajax_referer('prepare_migration_files', 'security');
2489
2490 $this->dataBase->moveDatabaseFiles();
2491
2492 wp_send_json_success([ 'moved' => true ]);
2493
2494 } catch (\Exception $exception) {
2495 wp_send_json_error(stripslashes('We could not move your database files'), 400);
2496 }
2497 }
2498
2499 public function databaseRelocationCheck()
2500 {
2501 try {
2502 check_ajax_referer('prepare_migration_files', 'security');
2503
2504 /**
2505 * Pull the pid
2506 */
2507 $databaseMovePID = get_transient('transferito_database_relocation_pid');
2508
2509 /**
2510 * Get the site size
2511 */
2512 $siteSizeInfo = get_transient('transferito_installation_size');
2513
2514 wp_send_json_success([
2515 'completed' => checkJobHasCompleted($databaseMovePID),
2516 'siteInfo' => $siteSizeInfo
2517 ]);
2518
2519 } catch (\Exception $exception) {
2520 wp_send_json_error(stripslashes('We can not move your database files'), 400);
2521 }
2522 }
2523
2524 public function archiveCreation()
2525 {
2526 try {
2527 check_ajax_referer('prepare_migration_files', 'security');
2528
2529 /**
2530 * Pull the transfer detail to get the selected folder information
2531 */
2532 $transferDetail = get_transient('transferito_transfer_detail');
2533
2534 /**
2535 * Start the archive
2536 */
2537 $zipPaths = $this->codeBase->createExecArchive($transferDetail['selectedFolders'], $transferDetail['folders']);
2538
2539 /**
2540 * Set transient for zip creation paths
2541 */
2542 set_transient('transferito_codebase_archive', $zipPaths);
2543
2544 wp_send_json_success([ 'archiveCreationStarted' => true ]);
2545
2546 } catch (\Exception $exception) {
2547 /**
2548 * Push loadScreen event to telemetry
2549 */
2550 $this->telemetry->pushEvent('failedMigration', [
2551 'migrationStatus' => 'archiveCreation',
2552 'errorMessage' => $exception->getMessage()
2553 ]);
2554 $errorMessage = 'archiveCreation: ' . $exception->getMessage();
2555 $this->api->failedMigration($errorMessage);
2556 wp_send_json_error(stripslashes('We can not create a backup of your site'), 400);
2557 }
2558 }
2559
2560 public function archiveProgressCheck()
2561 {
2562 try {
2563
2564 check_ajax_referer('prepare_migration_files', 'security');
2565
2566 /**
2567 * Pull the PID for the progress file
2568 */
2569 $archiveCreationPID = get_transient('transferito_codebase_archive_pid');
2570
2571 /**
2572 * Get the file name
2573 */
2574 $progressFile = TRANSFERITO_UPLOAD_PATH . '/.archive-process';
2575
2576 /**
2577 * Get the archives
2578 */
2579 $archives = get_transient('transferito_codebase_archive');
2580
2581 /**
2582 * Get info for the archive path
2583 */
2584 $archiveInfo = pathinfo($archives['path']);
2585
2586 /**
2587 * Progress value
2588 */
2589 $progressValue = null;
2590
2591 /**
2592 * Set the archive result
2593 */
2594 $archiveResult = [];
2595
2596 /**
2597 * If the file is TAR
2598 * Read the amount of files
2599 */
2600 if ($archiveInfo['extension'] === 'tar') {
2601 /**
2602 * Pull the information about the installation
2603 */
2604 $installationInfo = get_transient('transferito_installation_size');
2605
2606 /**
2607 * Get the amount of lines in the progress file
2608 */
2609 $file = new \SplFileObject($progressFile, 'r');
2610 $file->seek(PHP_INT_MAX);
2611 $progressAmount = $file->key() + 1;
2612
2613 /**
2614 * Calculate the percentage of the archive creation
2615 */
2616 $progressValue = ($progressAmount / $installationInfo['amountOfFiles']) * 100;
2617 }
2618
2619 /**
2620 * If the archive is ZIP
2621 * Calculate the progress of the archive creation based on the zip verbose file structure
2622 */
2623 if ($archiveInfo['extension'] === 'zip') {
2624 /**
2625 * Get the last line of the progress file
2626 */
2627 $lastLine = transferitoGetLastLine($progressFile);
2628
2629 /**
2630 * Get everything within the brackets
2631 */
2632 preg_match("/\[(.*?)\]/", $lastLine, $match);
2633
2634 /**
2635 * If there is a match
2636 * Process the logic to calculate the progress
2637 */
2638 if (count($match) === 2) {
2639 /**
2640 * Remove all spaces from string
2641 */
2642 $cleanValue = str_replace(' ', '', $match[1]);
2643
2644 /**
2645 * Split the progress values
2646 */
2647 $values = explode('/', $cleanValue);
2648
2649 /**
2650 * Check that the 2 elements exist in the array
2651 */
2652 if (count($values) === 2) {
2653 /**
2654 * The amount completed via the zip
2655 */
2656 $completedAmount = preg_split('/(?<=[0-9])(?=[a-z]+)/i', $values[0]);
2657
2658 /**
2659 * The amount remaining
2660 */
2661 $remainingAmount = preg_split('/(?<=[0-9])(?=[a-z]+)/i', $values[1]);
2662
2663 /**
2664 * If the amount id 0 - dont count it yet
2665 */
2666 if ($completedAmount !== '0') {
2667
2668 $completedInBytes = transferitoConvertToBytes($completedAmount);
2669 $remainingInBytes = transferitoConvertToBytes($remainingAmount);
2670 $total = $completedInBytes + $remainingInBytes;
2671 $progressValue = ($total === 0) ? 0 : round(($completedInBytes / $total) * 100);
2672 }
2673 }
2674 }
2675 }
2676
2677 /**
2678 * Check if the process has completed
2679 */
2680 $completed = checkJobHasCompleted($archiveCreationPID);
2681
2682 /**
2683 * Remove the DB import directory
2684 */
2685 if ($completed) {
2686
2687 $transferDetail = get_transient('transferito_transfer_detail');
2688
2689 /**
2690 * Update the code path with the archive path or url
2691 */
2692 $uploadS3 = $transferDetail['isLocalEnv'];
2693 $transferDetail['archive'] = $uploadS3 ? $archives['path'] : $archives['url'];
2694
2695 /**
2696 * Save the archive
2697 */
2698 set_transient('transferito_transfer_detail', $transferDetail);
2699
2700 /**
2701 * Remove the import directory
2702 */
2703 $this->removeDBImportDirectory();
2704
2705 /**
2706 * Get the backup status
2707 */
2708 $backupStatus = get_transient('transferito_backup_status');
2709
2710 /**
2711 * Assign the flags to variables
2712 */
2713 $databaseExcluded = $backupStatus['excludedDatabase'];
2714
2715 /**
2716 * Result of the response when the DB is excluded
2717 */
2718 if ($databaseExcluded) {
2719 $archiveResult = $this->archiveCompletionResponse(true, false);
2720 }
2721
2722 /**
2723 * Result of the response when the DB is not excluded
2724 */
2725 if (!$databaseExcluded) {
2726 $archiveResult = $this->archiveCompletionResponse(true, true);
2727 }
2728 }
2729
2730 wp_send_json_success(array_merge([
2731 'completed' => $completed,
2732 'progress' => $progressValue
2733 ], $archiveResult));
2734
2735 } catch (\Exception $exception) {
2736 wp_send_json_error(stripslashes('We can not get the progress of your backup'), 400);
2737 }
2738 }
2739
2740 public function archiveDBExport()
2741 {
2742 try {
2743 check_ajax_referer('prepare_migration_files', 'security');
2744
2745 /**
2746 * Create the DB file list
2747 */
2748 $exportFileList = $this->dataBase->createFileList();
2749
2750 /**
2751 * Throw if any issues
2752 */
2753 if (!$exportFileList) {
2754 throw new \Exception('We are unable to create the list of database exports that wll be added to your backup.');
2755 }
2756
2757 wp_send_json_success($exportFileList);
2758
2759 } catch (\Exception $exception) {
2760 wp_send_json_error($exception->getMessage(), 400);
2761 }
2762 }
2763
2764 public function checkArchiveCompletion()
2765 {
2766 try {
2767 check_ajax_referer('prepare_migration_files', 'security');
2768
2769 /**
2770 * Get the backup status
2771 */
2772 $backupStatus = get_transient('transferito_backup_status');
2773
2774 /**
2775 * Check that there is a backup status available
2776 */
2777 if (!$backupStatus) {
2778 throw new \Exception('There was an error retrieving your backup information');
2779 }
2780
2781 /**
2782 * Assign the flags to variables
2783 */
2784 $databaseExcluded = $backupStatus['excludedDatabase'];
2785
2786 /**
2787 * Default the archive result
2788 */
2789 $archiveResult = $this->archiveCompletionResponse(false, false);
2790
2791 /**
2792 * Result of the response when the DB is excluded
2793 */
2794 if ($databaseExcluded && $backupStatus['codebaseBackupComplete']) {
2795 $archiveResult = $this->archiveCompletionResponse(true, false);
2796 }
2797
2798 /**
2799 * Result of the response when the DB is not excluded
2800 */
2801 if (!$databaseExcluded && $backupStatus['codebaseBackupComplete'] && $backupStatus['databaseExportComplete']) {
2802 $archiveResult = $this->archiveCompletionResponse(true, true);
2803 }
2804
2805 /**
2806 * Notify the client whether or not the migration should be started or the db exports need to be zipped
2807 */
2808 wp_send_json_success($archiveResult);
2809
2810 } catch (\Exception $exception) {
2811 wp_send_json_error($exception->getMessage(), 400);
2812 }
2813 }
2814
2815 public function initiateUpload()
2816 {
2817 check_ajax_referer('start_upload', 'security');
2818
2819 $transferDetail = get_transient('transferito_transfer_detail');
2820
2821 try {
2822 scandir(TRANSFERITO_UPLOAD_PATH);
2823
2824 /**
2825 * Create an array to hold the backups
2826 */
2827 $backup = [];
2828
2829 /**
2830 * Start the upload for the codebase archive
2831 */
2832 try {
2833 $codebaseUploadId = $this->upload->startUpload();
2834 } catch(\Exception $ex) {
2835 throw new \Exception($ex->getMessage());
2836 }
2837
2838 /**
2839 * Add all the relevant detail to the $backup array
2840 */
2841 $backup['archive'] = [
2842 'type' => 'codebase',
2843 'parts' => $this->getFileParts($transferDetail['archive']),
2844 'uploadId' => $codebaseUploadId
2845 ];
2846
2847 /**
2848 * Send response
2849 */
2850 wp_send_json_success([
2851 'backup' => $backup
2852 ]);
2853
2854 } catch (\Exception $exception) {
2855 /**
2856 * Push failedMigration event to telemetry
2857 */
2858 $this->telemetry->pushEvent('failedMigration', [
2859 'migrationStatus' => 'AWSInitiateUpload',
2860 'errorMessage' => $exception->getMessage()
2861 ]);
2862 $errorMessage = 'AWSInitiateUpload: ' . $exception->getMessage();
2863 $this->api->failedMigration($errorMessage);
2864 wp_send_json_error($errorMessage, 400);
2865 }
2866
2867 }
2868
2869 public function uploadChunk()
2870 {
2871 /**
2872 * Verify nonce with every chunk uploaded
2873 */
2874 check_ajax_referer('upload_chunk', 'securityKey');
2875
2876 try {
2877 $transferDetail = get_transient('transferito_transfer_detail');
2878
2879 /**
2880 * Get the correct file
2881 */
2882 $filePath = $transferDetail['archive'];
2883
2884 /**
2885 *
2886 */
2887 $partNumberSanitized = isset($_POST['partNumber'])
2888 ? sanitize_text_field(wp_unslash($_POST['partNumber']))
2889 : 0;
2890
2891 /**
2892 * Get the part number
2893 */
2894 $partNumber = filter_var($partNumberSanitized, FILTER_VALIDATE_INT);
2895
2896 /**
2897 * Get the chunk size
2898 */
2899 $chunkSize = Config::getChunkSize();
2900
2901 /**
2902 * Set FilePointer
2903 */
2904 $filePointer = ($partNumber - 1) * $chunkSize;
2905
2906 /**
2907 * Get the file
2908 */
2909 $file = fopen($filePath , 'rb');
2910
2911 /**
2912 * Set the file pointer
2913 */
2914 fseek($file, $filePointer);
2915
2916 /**
2917 * Get the chunk
2918 */
2919 $chunk = fread($file, $chunkSize);
2920
2921 /**
2922 * Close the file
2923 */
2924 fclose($file);
2925
2926 unset($file);
2927
2928 /**
2929 * Send the chunk to the API
2930 */
2931 try {
2932 $chunkUploaded = $this->upload->uploadChunk($partNumber, $chunk);
2933 } catch(\Exception $ex) {
2934 throw new \Exception($ex->getMessage());
2935 }
2936
2937 unset($chunk);
2938
2939
2940 /**
2941 * Send response
2942 */
2943 wp_send_json_success([
2944 'uploaded' => true,
2945 'partNumber' => $partNumber
2946 ]);
2947
2948 } catch (\Exception $exception) {
2949 /**
2950 * Push failedMigration event to telemetry
2951 */
2952 $this->telemetry->pushEvent('failedMigration', [
2953 'migrationStatus' => 'AWSUploadChunk',
2954 'errorMessage' => $exception->getMessage()
2955 ]);
2956 $errorMessage = 'AWSUploadChunk: ' . $exception->getMessage();
2957 $this->api->failedMigration($errorMessage);
2958 wp_send_json_error('There was an error uploading your chunk', 400);
2959 }
2960 }
2961
2962 public function completeUpload()
2963 {
2964 try {
2965 /**
2966 * Pull the transfer detail
2967 */
2968 $transferDetail = get_transient('transferito_transfer_detail');
2969
2970 /**
2971 * Call the upload complete end point
2972 */
2973 try {
2974 $uploadInfo = $this->upload->completeUpload();
2975 } catch(\Exception $ex) {
2976 throw new \Exception($ex->getMessage());
2977 }
2978
2979 /**
2980 * Assign the upload to the migration
2981 */
2982 $completeUpload = $this->api->completeUpload([
2983 'filename' => $uploadInfo['path'],
2984 'token' => $transferDetail['token'],
2985 'timestamp' => $transferDetail['timestamp']
2986 ]);
2987
2988 /**
2989 * If the upload completion failed
2990 */
2991 if ($completeUpload['code'] !== 200) {
2992 throw new \Exception(stripslashes('Your upload can not be assigned to your migration.'));
2993 }
2994
2995 /**
2996 * Set the publicly accessible path for the backup
2997 */
2998 $transferDetail['archive'] = $uploadInfo['URL'];
2999
3000 /**
3001 * Set transient with transfer detail
3002 */
3003 set_transient('transferito_transfer_detail', $transferDetail);
3004
3005 /**
3006 * Send response
3007 */
3008 wp_send_json_success([
3009 'htmlTemplate' => loadTemplate('parts/loading', [
3010 'showMigrationImage' => true,
3011 'mainMessage' => stripslashes('We have successfully backed up your WordPress installation'),
3012 'secondaryMessage' => 'We are currently migrating your site to your new destination',
3013 ]),
3014 'securityKey' => wp_create_nonce('start_migration')
3015 ]);
3016
3017 } catch (\Exception $exception) {
3018 /**
3019 * Push failedMigration event to telemetry
3020 */
3021 $this->telemetry->pushEvent('failedMigration', [
3022 'migrationStatus' => 'AWSCompleteUpload',
3023 'errorMessage' => $exception->getMessage()
3024 ]);
3025 $errorMessage = 'AWSCompleteUpload: ' . $exception->getMessage();
3026 $this->api->failedMigration($errorMessage);
3027
3028 wp_send_json_error('There was an error completing your upload', 400);
3029 }
3030 }
3031
3032 public function startMigration()
3033 {
3034 check_ajax_referer('start_migration', 'security');
3035
3036 $settings = get_option('transferito_settings_option');
3037 $chunkSize = isset($settings['transferito_chunk_size']) ? $settings['transferito_chunk_size'] : '10';
3038 $deleteVerificationFile = isset($settings['transferito_delete_verification_file']) ? $settings['transferito_delete_verification_file'] : false;
3039 $transferDetail = get_transient('transferito_transfer_detail');
3040 $installationInfo = get_transient('transferito_installation_size');
3041 $dbCharsetInfo = get_transient('transferito_database_charset_info');
3042
3043 /**
3044 * Check if the migration is a local migration or not
3045 */
3046 $localMigration = get_transient('transferito_transfer_method') === 'localSiteMigration';
3047
3048 delete_transient('transferito_transfer_detail');
3049 delete_transient('transferito_database_charset_info');
3050
3051 /**
3052 * Check the user's API keys
3053 */
3054 $hasValidAPIKeys = $this->areAPIKeysValid(
3055 $settings['public_transferito_key'],
3056 $settings['secret_transferito_key']
3057 );
3058
3059 /**
3060 * Set as a FREE User
3061 */
3062 if (!$hasValidAPIKeys) {
3063 $this->api->setFreeUser();
3064 }
3065
3066 /**
3067 * Get connected Site
3068 */
3069 $connectedSite = $this->destinationServerConnectionExists('array');
3070
3071 /**
3072 * Create the payload to send to the API
3073 */
3074 $startMigrationPayload = [
3075 'validationToken' => $connectedSite['token'],
3076 'validationFile' => $connectedSite['filename'],
3077 "domain" => $connectedSite['domain'],
3078 "deleteValidation" => $deleteVerificationFile,
3079 'chunkSize' => $chunkSize,
3080 'charset' => $dbCharsetInfo['actualCharset'],
3081 'configCharset' => $dbCharsetInfo['configCharset'],
3082 'archiveFileAmount' => $installationInfo['amountOfFiles'],
3083 'isLocal' => $transferDetail['isLocalEnv'],
3084 'archive' => $transferDetail['archive'],
3085 'backupDirectory' => $transferDetail['directory'],
3086 'token' => $transferDetail['token'],
3087 'timestamp' => $transferDetail['timestamp'],
3088 'fromUrl' => $transferDetail['fromUrl'],
3089 'currentPath' => ABSPATH
3090 ];
3091
3092 /**
3093 * Send files to API
3094 */
3095 $startMigrationRequest = $this->api->startMigration($startMigrationPayload);
3096 $statusCode = $startMigrationRequest['code'];
3097 $response = $startMigrationRequest['message'];
3098
3099 /**
3100 * If upload notification has been successful
3101 * Add flags stop backup removal
3102 */
3103 if ($statusCode === 200) {
3104 set_transient('transferito_ignore_cleanup', true, 12 * HOUR_IN_SECONDS);
3105 set_transient('transferito_cleanup_after_completion', true, 12 * HOUR_IN_SECONDS);
3106 }
3107
3108 /**
3109 * Return response based on status
3110 */
3111 if ($statusCode === 200) {
3112 $emptyKeys = get_transient('transferito_empty_keys');
3113 $availableTransfers = get_transient('transferito_has_available_transfers');
3114 $message = ($emptyKeys || !$availableTransfers)
3115 ? 'If you navigate away from the site, your migration will continue in the background. As you\'re not a premium member, we advise that you stay on this page until your migration has completed.'
3116 : 'If you navigate away from the site, your migration will continue in the background. You will be sent an email once your migration has completed.';
3117 wp_send_json_success([
3118 'localMigration' => $localMigration,
3119 'token' => $response->token,
3120 'message' => $message
3121 ]);
3122 } else {
3123 $errorMessage = is_array($response) ? 'There has been an error starting your migration' : stripslashes($response);
3124 wp_send_json_error($errorMessage, $statusCode);
3125 }
3126 }
3127
3128 public function cleanUp($url = null)
3129 {
3130 /**
3131 * Verify nonce on clean up
3132 */
3133 check_ajax_referer('run_cleanup', 'securityKey');
3134
3135 $url = get_transient('transferito_final_destination_url');
3136 $hasError = isset($_POST['hasError']) ? sanitize_text_field(wp_unslash($_POST['hasError'])) : false;
3137 $metadata = isset($_POST['metadata']) ? sanitize_text_field(wp_unslash($_POST['metadata'])) : '';
3138
3139 $localMigrationSanitized = isset($_POST['localMigration'])
3140 ? sanitize_text_field(wp_unslash($_POST['localMigration']))
3141 : false;
3142
3143 $localMigration = filter_var($localMigrationSanitized, FILTER_VALIDATE_BOOL);
3144
3145 $errors = isset($_POST['errors']) ? sanitize_text_field(wp_unslash($_POST['errors'])) : null;
3146 $failureList = [
3147 'CPANEL_CHECK_FAILED' => 'There has been an issue checking your URL',
3148 'SWITCH_METHOD_FAILED' => 'There has been an issue switching to the new migration mode',
3149 'FAILED_BACKUP_CHECK' => 'There has been an error processing a backup of your WordPress site',
3150 'FAILED_CREATE_BACKUP' => 'There has been an error creating a backup of your WordPress site',
3151 'FAILED_REMOVE_BACKUP' => 'There has been an error removing a backup of your WordPress site',
3152 'FAILED_STARTING_MIGRATION' => 'There has been an issue starting your migration',
3153 'ERROR_GETTING_STATUS' => 'There has been an issue getting the status of your migration.',
3154 'UPLOAD_START_FAILURE' => 'We have been unable to start uploading your backup to our servers',
3155 'UPLOAD_CHUNK_FAILURE' => 'An error has occurred whilst uploading your backup to our servers',
3156 'UPLOAD_COMPLETION_FAILURE' => 'We are unable to complete the upload of your backup to our servers',
3157 'USE_CUSTOM_ERROR_MESSAGE' => $errors && isset($errors['data']) ? $errors['data'] : 'There has been an issue processing your migration',
3158 ];
3159 $validError = in_array($hasError, array_keys($failureList));
3160
3161 $completionHTMLTemplate = ($localMigration)
3162 ? 'parts/migration/local-migration-completed'
3163 : 'parts/migration/completed';
3164
3165 try {
3166 clearstatcache();
3167
3168 if (file_exists(TRANSFERITO_UPLOAD_PATH)) {
3169 /**
3170 * Check if there has been an error archiving
3171 */
3172 $this->checkArchiveError();
3173
3174 /**
3175 * Remove everything in the transferito directory
3176 */
3177 $this->purgeDirectory(TRANSFERITO_UPLOAD_PATH);
3178 }
3179
3180 /**
3181 * Remove all instances of the db import directory
3182 */
3183 $this->removeDBImportDirectory();
3184
3185 /**
3186 * If the error exists
3187 */
3188 if ($validError) {
3189 $this->api->failedMigration($failureList[$hasError]);
3190 $htmlTemplate = [
3191 'error' => $failureList[$hasError],
3192 'failed' => true,
3193 ];
3194 } else {
3195 $htmlTemplate = loadTemplate($completionHTMLTemplate, [
3196 'url' => $url,
3197 'metadata' => $metadata
3198 ]);
3199 }
3200 } catch (\Exception $exception) {
3201 if ($validError) {
3202 $this->api->failedMigration($errors);
3203 $htmlTemplate = [
3204 'error' => $failureList[$hasError],
3205 'failed' => true,
3206 'secondary' => stripslashes('We have not been able to remove your backup. Please remove it manually.')
3207 ];
3208 } else {
3209 $this->api->failedMigration($failureList['FAILED_REMOVE_BACKUP']);
3210 $htmlTemplate = loadTemplate($completionHTMLTemplate, [ 'url' => $url, 'error' => 'FAILED_BACKUP_REMOVAL' ]);
3211 }
3212 }
3213
3214 $this->enableAutoPrependOption();
3215 $this->enableWPObjectCache();
3216 $this->removeTransferitoTransients();
3217
3218 wp_send_json_success([ 'htmlTemplate' => $htmlTemplate ]);
3219 }
3220
3221 private function areAPIKeysValid($publicKey, $secretKey) {
3222 $valid = null;
3223
3224 try {
3225 $planInfo = $this->api->planInformation([
3226 'publicTransferito_APIKey' => $publicKey,
3227 'secretTransferito_APIKey' => $secretKey
3228 ]);
3229 $code = $planInfo['code'];
3230
3231 /**
3232 * Success is a 200
3233 * If anything else throw an error
3234 */
3235 if ($code !== 200) {
3236 throw new \Exception('INVALID_API_KEYS');
3237 }
3238
3239 $availableTransfers = isset($planInfo['message']->availableTransfers)
3240 ? $planInfo['message']->availableTransfers
3241 : 0;
3242
3243 set_transient('transferito_has_available_transfers', ($availableTransfers !== 0));
3244
3245 $valid = ($code === 200 && $availableTransfers !== 0);
3246
3247 } catch(\Exception $exception) {
3248 $valid = false;
3249 }
3250
3251 return $valid;
3252 }
3253
3254 private function checkSiteWithinFreeTier($siteDetails)
3255 {
3256 try {
3257
3258 /**
3259 * Default the user status to FREE
3260 */
3261 $userStatus = 'FREE';
3262
3263 /**
3264 * Default if the user has valid API keys to false
3265 */
3266 $hasValidAPIKeys = false;
3267
3268 /**
3269 * Plan Check
3270 */
3271 if ($this->options && $this->options['public_transferito_key'] && $this->options['secret_transferito_key']) {
3272
3273 /**
3274 * Check the user's API keys
3275 */
3276 $hasValidAPIKeys = $this->areAPIKeysValid(
3277 $this->options['public_transferito_key'],
3278 $this->options['secret_transferito_key']
3279 );
3280
3281 /**
3282 * Only set the status if the API keys are valid
3283 */
3284 if ($hasValidAPIKeys) {
3285 $userStatus = 'PREMIUM';
3286 }
3287 }
3288
3289 /**
3290 * Save the user's status
3291 */
3292 set_transient('transferito_user_status', $userStatus);
3293
3294 /**
3295 * Only update the FREE User Options
3296 * If the user doesn't have Valid API Keys
3297 */
3298 if (!$hasValidAPIKeys) {
3299 /**
3300 * Inform the API Wrapper of the type of user
3301 */
3302 $this->api->setFreeUser();
3303 $this->api->setMaxSizeExceeded($siteDetails['maxSizeExceeded']);
3304
3305 /**
3306 * Update legend message
3307 */
3308 if ($siteDetails['maxSizeExceeded']) {
3309 /**
3310 * Push upgradeRequired event to telemetry
3311 */
3312 $this->telemetry->pushEvent('premiumUpgradeRequired', [
3313 'exceededFreeTierSize' => 'yes',
3314 ]);
3315 }
3316 }
3317
3318 } catch(\Exception $exception) {
3319 /**
3320 * Push upgradeRequired event to telemetry
3321 */
3322 $this->telemetry->pushEvent('premiumUpgradeRequired', [
3323 'exceededFreeTierSize' => 'yes',
3324 ]);
3325 }
3326 }
3327
3328 private function getFileParts($filePath)
3329 {
3330 sleep(5);
3331 getDirectorySize(TRANSFERITO_ABSPATH);
3332 clearstatcache();
3333 $fileInfo = stat($filePath);
3334 $archiveSize = $fileInfo['size'];
3335 $chunkSize = Config::getChunkSize();
3336 return ceil($archiveSize / $chunkSize);
3337 }
3338
3339 private function freshStart() {
3340 try {
3341 clearstatcache();
3342
3343 $this->enableAutoPrependOption();
3344 $this->enableWPObjectCache();
3345 $this->removeTransferitoTransients();
3346 $this->removeDBImportDirectory();
3347
3348 /**
3349 * Check if an unfinished migration still exists
3350 */
3351 if (file_exists(TRANSFERITO_UPLOAD_PATH)) {
3352 /**
3353 * Check if there has been an error archiving
3354 */
3355 $this->checkArchiveError();
3356
3357 /**
3358 * Remove everything in the transferito directory
3359 */
3360 $this->purgeDirectory(TRANSFERITO_UPLOAD_PATH);
3361 }
3362 } catch(\Exception $exception) {
3363 // do nada - for now
3364 }
3365 }
3366
3367 private function removeTransferitoTransients()
3368 {
3369 delete_transient('transferito_size_size_in_bytes');
3370 delete_transient('transferito_readable_size_size_in_bytes');
3371 delete_transient('transferito_request_fallback');
3372 delete_transient('transferito_destination_url');
3373 delete_transient('transferito_backup_status');
3374 delete_transient('transferito_transfer_detail');
3375 delete_transient('transferito_final_destination_url');
3376 delete_transient('transferito_codebase_archive');
3377 delete_transient('transferito_database_table_map');
3378 delete_transient('transferito_ignore_cleanup');
3379 delete_transient('transferito_cleanup_after_completion');
3380 delete_transient('transferito_db_export_progress');
3381 delete_transient('transferito_migration_token');
3382 delete_transient('transferito_migration_timestamp');
3383 delete_transient('transferito_migration_domain');
3384 delete_transient('transferito_migration_unchanged_domain');
3385 delete_transient('transferito_transfer_method');
3386 delete_transient('transferito_cpanel_allowed');
3387 delete_transient('transferito_cpanel_domain');
3388 delete_transient('transferito_manual_server_detail');
3389 delete_transient('transferito_use_backup_fallback');
3390 delete_transient('transferito_installation_size');
3391 delete_transient('transferito_requirements');
3392 delete_transient('transferito_database_relocation_pid');
3393 delete_transient('transferito_codebase_archive_pid');
3394 delete_transient('transferito_upload_information');
3395 delete_transient('transferito_database_charset_info');
3396 delete_transient('transferito_cpanel_auth_details');
3397 delete_transient('transferito_empty_keys');
3398 delete_transient('transferito_has_available_transfers');
3399 delete_transient('transferito_database_detail_completed');
3400 delete_transient('transferito_cpanel_auth_details_completed');
3401 delete_transient('transferito_archive_extension');
3402 delete_transient('transferito_cpanel_domain_selection');
3403 delete_transient('transferito_object_cache_filter_present');
3404 delete_transient('transferito_auto_prepend_option_disabled');
3405
3406 }
3407
3408 private function archiveCompletionResponse($backupsCompleted, $zipDBExport)
3409 {
3410 /**
3411 * Get the Transfer Detail
3412 */
3413 $transferDetail = get_transient('transferito_transfer_detail');
3414
3415 /**
3416 * Get the local Env flag
3417 */
3418 $localEnv = $transferDetail['isLocalEnv'];
3419
3420 /**
3421 * Information for the client
3422 */
3423 $information = null;
3424
3425 /**
3426 * If the zip DB Export is not needed
3427 */
3428 if ($backupsCompleted) {
3429 /**
3430 * If the site can not be reached - Then upload the archive
3431 */
3432 if ($localEnv) {
3433 $information = [
3434 'htmlTemplate' => loadTemplate('parts/migration/upload-progress', [
3435 'title' => 'Upload in progress. Please wait...',
3436 'mainMessage' => stripslashes('We are uploading a backup of your site to our secure servers.'),
3437 'secondaryMessage' => stripslashes('Please wait.. This may take a few minutes, do not close this window or refresh the page'),
3438 ]),
3439 'securityKey' => wp_create_nonce('start_upload'),
3440 'uploadFiles' => true,
3441 ];
3442 }
3443
3444 /**
3445 * If the site can be reached - Then pull direct
3446 */
3447 if (!$localEnv) {
3448 $information = [
3449 'htmlTemplate' => loadTemplate('parts/loading', [
3450 'showMigrationImage' => true,
3451 'mainMessage' => stripslashes('We have successfully backed up your WordPress installation'),
3452 'secondaryMessage' => 'We are currently migrating your site to your new destination',
3453 ]),
3454 'securityKey' => wp_create_nonce('start_migration'),
3455 'uploadFiles' => false,
3456 ];
3457 }
3458 }
3459
3460 return [
3461 'backupComplete' => $backupsCompleted,
3462 'zipDatabaseExport' => $zipDBExport,
3463 'information' => $information
3464 ];
3465 }
3466
3467 private function removeDBImportDirectory()
3468 {
3469 $importDirectory = TRANSFERITO_ABSPATH . 'transferito_import';
3470
3471 /**
3472 * If the Directory exists
3473 */
3474 if (file_exists($importDirectory)) {
3475 array_map('wp_delete_file', array_filter(glob($importDirectory . '/*')));
3476 rmdir($importDirectory);
3477 }
3478 }
3479
3480 private function purgeDirectory($directory)
3481 {
3482 if (!$directory) {
3483 return true;
3484 }
3485
3486 clearstatcache();
3487
3488 $it = new \RecursiveDirectoryIterator($directory, \RecursiveDirectoryIterator::SKIP_DOTS);
3489 $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST);
3490 foreach($files as $file) {
3491 if ($file->isDir()){
3492 rmdir($file->getRealPath());
3493 } else {
3494 wp_delete_file($file->getRealPath());
3495 }
3496 }
3497
3498 clearstatcache();
3499
3500 rmdir($directory);
3501
3502 return true;
3503 }
3504
3505 private function checkArchiveError()
3506 {
3507 $archiveErrorPath = TRANSFERITO_UPLOAD_PATH . DIRECTORY_SEPARATOR . '.archive-error';
3508 $archiveErrorRootPath = TRANSFERITO_ABSPATH . '.archive-error';
3509 $errorFileExists = file_exists($archiveErrorPath);
3510
3511 if ($errorFileExists) {
3512 $hasErrors = strlen(file_get_contents($archiveErrorPath)) > 0;
3513
3514 /**
3515 * If the file has errors
3516 * Then move the archive error file to the root directory
3517 */
3518 if ($hasErrors) {
3519 rename($archiveErrorPath, $archiveErrorRootPath);
3520 }
3521 }
3522 }
3523
3524 private function disableAutoPrependOption()
3525 {
3526 $autoPrependOptionData = getPrependOptionNameData();
3527 $userIniFilePath = $autoPrependOptionData['path'];
3528 $userIniFileExists = file_exists($userIniFilePath);
3529
3530 /**
3531 * Only proceed with the user.ini modification if the file exists
3532 */
3533 if ($userIniFileExists) {
3534 try {
3535
3536 $prependOptionName = $autoPrependOptionData['text'];
3537 $commentedPrependOptionName = $autoPrependOptionData['commented'];
3538 $userIniFile = file_get_contents($userIniFilePath);
3539 $optionExists = str_contains($userIniFile, $prependOptionName);
3540
3541 /**
3542 * If there isn't an auto_prepend option throw a graceful error
3543 */
3544 if (!$optionExists) {
3545 throw new \Exception('AUTO_PREPEND_MODIFICATION_NOT_REQUIRED');
3546 }
3547
3548 /**
3549 * Check to see if the option has been commented previously
3550 */
3551 $optionPreviouslyCommentedExists = str_contains($userIniFile, $commentedPrependOptionName);
3552
3553 /**
3554 * If a comment doesn't exist - modify the file
3555 */
3556 if (!$optionPreviouslyCommentedExists) {
3557 $modifiedUserIni = str_replace($prependOptionName, $commentedPrependOptionName, $userIniFile);
3558 file_put_contents($userIniFilePath, $modifiedUserIni);
3559 }
3560
3561 set_transient('transferito_auto_prepend_option_disabled', true);
3562
3563 } catch(\Exception $exception) {
3564 set_transient('transferito_auto_prepend_option_disabled', false);
3565 }
3566 }
3567 }
3568
3569 private function disableWPObjectCache()
3570 {
3571 $objectCacheFilterData = getObjectCacheFilterData();
3572
3573 /**
3574 * Only make this change if the function file exists
3575 */
3576 if (file_exists($objectCacheFilterData['path'])) {
3577 try {
3578 $cacheFilterExists = str_contains(file_get_contents($objectCacheFilterData['path']), $objectCacheFilterData['text']);
3579
3580 /**
3581 * If the filter isn't already in the PHP file
3582 */
3583 if (!$cacheFilterExists) {
3584 $filterModified = PHP_EOL . $objectCacheFilterData['text'] . PHP_EOL;
3585 file_put_contents($objectCacheFilterData['path'], $filterModified, FILE_APPEND | LOCK_EX);
3586 }
3587
3588 /**
3589 * Set the transient, so we can decide whether we need to revert the file
3590 */
3591 set_transient('transferito_object_cache_filter_present', true);
3592 } catch (\Exception $exception) {
3593 set_transient('transferito_object_cache_filter_present', false);
3594 }
3595 }
3596 }
3597
3598 private function enableAutoPrependOption()
3599 {
3600 $autoPrependOptionStatus = get_transient('transferito_auto_prepend_option_disabled');
3601
3602 /**
3603 * Only enable the option if the file transient status if truthy
3604 */
3605 if ($autoPrependOptionStatus) {
3606 $autoPrependOptionData = getPrependOptionNameData();
3607
3608 /**
3609 * Check the file exists
3610 */
3611 if (file_exists($autoPrependOptionData['path'])) {
3612 try {
3613 $userIniFile = file_get_contents($autoPrependOptionData['path']);
3614 $commentedOptionExists = str_contains($userIniFile, $autoPrependOptionData['commented']);
3615
3616 /**
3617 * Check to see that the commented option is present
3618 */
3619 if ($commentedOptionExists) {
3620 $modifiedUserIniFile = str_replace($autoPrependOptionData['commented'], $autoPrependOptionData['text'], $userIniFile);
3621 file_put_contents($autoPrependOptionData['path'], $modifiedUserIniFile);
3622 }
3623 } catch(\Exception $exception) {
3624 // handle gracefullu
3625 }
3626 }
3627 }
3628 }
3629
3630 private function enableWPObjectCache()
3631 {
3632 $wpObjectCacheStatus = get_transient('transferito_object_cache_filter_present');
3633
3634 /**
3635 * Only enable if the status is truthy
3636 */
3637 if ($wpObjectCacheStatus) {
3638 $objectCacheFilterData = getObjectCacheFilterData();
3639
3640 /**
3641 * Check the file exists
3642 */
3643 if (file_exists($objectCacheFilterData['path'])) {
3644 try {
3645 $functionsFile = file_get_contents($objectCacheFilterData['path']);
3646 $cacheFilterExists = str_contains($functionsFile, $objectCacheFilterData['text']);
3647
3648 /**
3649 * If the filter lives in the functions file, remove it
3650 */
3651 if ($cacheFilterExists) {
3652 $modifiedFunctionsFile = str_replace($objectCacheFilterData['text'], '', $functionsFile);
3653 file_put_contents($objectCacheFilterData['path'], $modifiedFunctionsFile);
3654 }
3655 } catch(\Exception $exception) {
3656 // Fail gracefully
3657 }
3658 }
3659 }
3660 }
3661
3662 }
3663