PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.8.0
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.8.0
4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Backend / Administrator.php
wp-staging / Backend Last commit date
Activation 3 months ago Modules 2 months ago Optimizer 1 month ago Pluginmeta 8 months ago Upgrade 1 year ago Administrator.php 2 months ago
Administrator.php
1271 lines
1 <?php
2
3 namespace WPStaging\Backend;
4
5 use WPStaging\Backend\Modules\Jobs\Job;
6 use WPStaging\Core\WPStaging;
7 use WPStaging\Core\DTO\Settings;
8 use WPStaging\Framework\Analytics\Actions\AnalyticsStagingReset;
9 use WPStaging\Framework\Analytics\Actions\AnalyticsStagingUpdate;
10 use WPStaging\Framework\Assets\Assets;
11 use WPStaging\Framework\Facades\Hooks;
12 use WPStaging\Framework\SiteInfo;
13 use WPStaging\Framework\Security\Auth;
14 use WPStaging\Framework\Mails\Report\Report;
15 use WPStaging\Framework\Filesystem\Filters\ExcludeFilter;
16 use WPStaging\Framework\Filesystem\PathIdentifier;
17 use WPStaging\Framework\TemplateEngine\TemplateEngine;
18 use WPStaging\Framework\Utils\Math;
19 use WPStaging\Framework\Utils\WpDefaultDirectories;
20 use WPStaging\Framework\Notices\DismissNotice;
21 use WPStaging\Staging\Sites;
22 use WPStaging\Backend\Modules\Jobs\Cancel;
23 use WPStaging\Backend\Modules\Jobs\CancelUpdate;
24 use WPStaging\Backend\Modules\Jobs\Cloning;
25 use WPStaging\Backend\Modules\Jobs\Updating;
26 use WPStaging\Backend\Modules\Jobs\Scan;
27 use WPStaging\Backend\Modules\Jobs\Logs;
28 use WPStaging\Backend\Modules\Jobs\ProcessLock;
29 use WPStaging\Backend\Modules\SystemInfo;
30 use WPStaging\Backend\Modules\Views\Tabs\Tabs;
31 use WPStaging\Backend\Modules\Views\Forms\Settings as FormSettings;
32 use WPStaging\Backend\Activation;
33 use WPStaging\Backend\Pro\Modules\Jobs\Processing;
34 use WPStaging\Backend\Pro\Modules\Jobs\Backups\BackupUploadsDir;
35 use WPStaging\Backend\Pluginmeta\Pluginmeta;
36 use WPStaging\Framework\Database\SelectedTables;
37 use WPStaging\Framework\Utils\Sanitize;
38 use WPStaging\Backend\Pro\Modules\Jobs\Scan as ScanProModule;
39 use WPStaging\Basic\Feedback\Feedback;
40 use WPStaging\Core\CloningJobProvider;
41 use WPStaging\Framework\Utils\PluginInfo;
42 use WPStaging\Framework\Security\Nonce;
43 use WPStaging\Framework\Newsfeed\NewsfeedProvider;
44
45 /**
46 * Class Administrator
47 * @package WPStaging\Backend
48 */
49 class Administrator
50 {
51 /**
52 * @var int Place WP Staging Menu below Plugins
53 */
54 const MENU_POSITION_ORDER = 65;
55
56 /**
57 * @var int Place WP Staging Menu below Plugins for multisite
58 */
59 const MENU_POSITION_ORDER_MULTISITE = 20;
60
61 /**
62 * @var string
63 */
64 const FILTER_MAIN_SETTING_TABS = 'wpstg.main_settings_tabs';
65
66 /**
67 * All registered WP Staging admin page slugs.
68 * @var string[]
69 */
70 const ADMIN_PAGE_SLUGS = [
71 'wpstg_clone',
72 'wpstg_backup',
73 'wpstg-settings',
74 'wpstg-tools',
75 'wpstg-welcome',
76 'wpstg-restorer',
77 'wpstg-license',
78 'wpstg-install',
79 ];
80
81 /** @var string */
82 private $viewsPath;
83
84 /**
85 * @var Assets
86 */
87 private $assets;
88
89 /**
90 * @var Auth
91 */
92 private $auth;
93
94 /**
95 * @var SiteInfo
96 */
97 private $siteInfo;
98
99 /** @var Sanitize */
100 private $sanitize;
101
102 /** @var Report */
103 private $report;
104
105 /** @var PluginInfo */
106 private $pluginInfo;
107
108 public function __construct()
109 {
110 $this->auth = WPStaging::make(Auth::class);
111 $this->assets = WPStaging::make(Assets::class);
112 $this->siteInfo = WPStaging::make(SiteInfo::class);
113 $this->report = WPStaging::make(Report::class);
114 $this->pluginInfo = WPStaging::make(PluginInfo::class);
115 $this->viewsPath = WPSTG_VIEWS_DIR;
116
117 $this->defineHooks();
118
119 $this->sanitize = WPStaging::make(Sanitize::class);
120
121 // Load plugins meta data
122 $this->loadMeta();
123 }
124
125 /**
126 * Load plugin meta data
127 */
128 public function loadMeta()
129 {
130 new Pluginmeta();
131 }
132
133 /**
134 * Define Hooks
135 */
136 private function defineHooks()
137 {
138 if (!defined('WPSTGPRO_VERSION')) {
139 new Activation\Welcome();
140 }
141
142 if ($this->pluginInfo->canShowAdminMenu()) {
143 add_action("admin_menu", [$this, "addMenu"], 10);
144 add_action('network_admin_menu', [$this, "addMenu"]);
145 }
146
147 add_action("admin_init", [$this, "upgrade"]);
148 add_action("admin_post_wpstg_download_sysinfo", [$this, "downloadSystemInfoAndLogFiles"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
149
150 if (defined('WPSTGPRO_VERSION') && class_exists('WPStaging\Backend\Pro\WpstgRestoreDownloader')) {
151 add_action("admin_post_wpstg_download_restorer", [$this, "downloadWpstgRestoreFile"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
152 }
153
154 if (!defined('WPSTGPRO_VERSION') && $this->isPluginsPage()) {
155 add_filter('admin_footer', [$this, 'loadFeedbackForm']);
156 }
157
158 // Ajax Requests
159 add_action("wp_ajax_wpstg_scanning", [$this, "ajaxCloneScan"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
160 add_action("wp_ajax_wpstg_check_clone", [$this, "ajaxCheckCloneDirectoryName"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
161 add_action("wp_ajax_wpstg_restart", [$this, "ajaxRestart"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
162 add_action("wp_ajax_wpstg_update", [$this, "ajaxUpdateProcess"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
163 add_action("wp_ajax_wpstg_reset", [$this, "ajaxResetProcess"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
164 add_action("wp_ajax_wpstg_cloning", [$this, "ajaxStartClone"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
165 add_action("wp_ajax_wpstg_processing", [$this, "ajaxCloneDatabase"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
166 add_action("wp_ajax_wpstg_clone_prepare_directories", [$this, "ajaxPrepareDirectories"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
167 add_action("wp_ajax_wpstg_clone_files", [$this, "ajaxCopyFiles"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
168 add_action("wp_ajax_wpstg_clone_replace_data", [$this, "ajaxReplaceData"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
169 add_action("wp_ajax_wpstg_clone_finish", [$this, "ajaxFinish"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
170 add_action("wp_ajax_wpstg_cancel_clone", [$this, "ajaxCancelClone"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
171 add_action("wp_ajax_wpstg_cancel_update", [$this, "ajaxCancelUpdate"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
172 add_action("wp_ajax_wpstg_hide_rating", [$this, "ajaxHideRating"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
173 add_action("wp_ajax_wpstg_hide_later", [$this, "ajaxHideLaterRating"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
174 add_action("wp_ajax_wpstg_hide_beta", [$this, "ajaxHideBeta"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
175 add_action("wp_ajax_wpstg_logs", [$this, "ajaxLogs"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
176 add_action("wp_ajax_wpstg_check_disk_space", [$this, "ajaxCheckFreeSpace"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
177 add_action("wp_ajax_wpstg_send_report", [$this, "ajaxSendReport"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
178 add_action("wp_ajax_wpstg_send_feedback", [$this, "sendFeedback"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
179 add_action("wp_ajax_wpstg_enable_staging_cloning", [$this, "ajaxEnableStagingCloning"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
180 add_action("wp_ajax_wpstg_clone_excludes_settings", [$this, "ajaxCloneExcludesSettings"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
181 add_action("wp_ajax_wpstg_fetch_dir_children", [$this, "ajaxFetchDirChildren"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
182 add_action("wp_ajax_wpstg_modal_error", [$this, "ajaxModalError"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
183 add_action("wp_ajax_wpstg_dismiss_notice", [$this, "ajaxDismissNotice"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
184 add_action("wp_ajax_wpstg_restore_settings", [$this, "ajaxRestoreSettings"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
185 add_action("wp_ajax_wpstg_send_debug_log_report", [$this->report, "ajaxSendDebugLog"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
186
187 // Ajax hooks pro Version
188 // TODO: move all below actions to pro service provider?
189 add_action("wp_ajax_wpstg_scan", [$this, "ajaxPushScan"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
190 add_action("wp_ajax_wpstg_push_tables", [$this, "ajaxPushTables"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
191 add_action("wp_ajax_wpstg_push_processing", [$this, "ajaxPushProcessing"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
192 add_action("wp_ajax_nopriv_wpstg_push_processing", [$this, "ajaxPushProcessing"]); // phpcs:ignore WPStaging.Security.AuthorizationChecked
193
194 // TODO: replace uploads backup during push once we have backups PR ready,
195 // Then there will be no need to have any cron to delete those backups
196 if (class_exists('WPStaging\Backend\Pro\Modules\Jobs\Backups\BackupUploadsDir')) {
197 add_action(BackupUploadsDir::BACKUP_DELETE_CRON_HOOK_NAME, [$this, "removeOldUploadsBackup"]); // phpcs:ignore WPStaging.Security.FirstArgNotAString -- Cron callback
198 }
199 }
200
201 /**
202 * Load Feedback Form on plugins.php
203 */
204 public function loadFeedbackForm()
205 {
206 $form = WPStaging::make(Feedback::class);
207 $form->loadForm();
208 }
209
210 /**
211 * Send Feedback data via mail
212 */
213 public function sendFeedback()
214 {
215 if (!$this->isAuthenticated()) {
216 return;
217 }
218
219 $form = WPStaging::make(Feedback::class);
220 $form->sendDeactivateFeedback();
221 }
222
223 /**
224 * Upgrade routine
225 * @action admin_init 10 0
226 * @see \WPStaging\Backend\Administrator::defineHooks
227 */
228 public function upgrade()
229 {
230 if (defined('WPSTGPRO_VERSION') && class_exists('WPStaging\Backend\Pro\Upgrade\Upgrade')) {
231 $upgrade = WPStaging::make('WPStaging\Backend\Pro\Upgrade\Upgrade');
232 } else {
233 $upgrade = WPStaging::make('WPStaging\Backend\Upgrade\Upgrade');
234 }
235 $upgrade->doUpgrade();
236 }
237
238 /**
239 * Add Admin Menu(s)
240 */
241 public function addMenu()
242 {
243 global $wp_version;
244 $logo = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwMCAxMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAxMDAwIDEwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiIGZpbGw9Im5vbmUiPgo8Zz48Zz48cGF0aCBzdHlsZT0iZmlsbDojZmZmIiAgZD0iTTEzNy42LDU2MS4zSDEzLjhIMTB2MzA2LjNsOTAuNy04My40QzE4OS42LDkwOC43LDMzNS4zLDk5MCw1MDAsOTkwYzI0OS45LDAsNDU2LjEtMTg3LjEsNDg2LjItNDI4LjhIODYyLjRDODMzLjMsNzM1LjEsNjgyLjEsODY3LjUsNTAwLDg2Ny41Yy0xMjksMC0yNDIuNS02Ni41LTMwOC4xLTE2Ny4ybDE1MS4zLTEzOS4xSDEzNy42eiIvPjxwYXRoIHN0eWxlPSJmaWxsOiNmZmYiICBkPSJNNTAwLDEwQzI1MC4xLDEwLDQzLjksMTk3LjEsMTMuOCw0MzguOGgxMjMuOEMxNjYuNywyNjQuOSwzMTcuOSwxMzIuNSw1MDAsMTMyLjVjMTMyLjksMCwyNDkuMyw3MC41LDMxMy44LDE3Ni4yTDY4My44LDQzOC44aDEyMi41aDU2LjJoMTIzLjhoMy44VjEzMi41bC04Ny43LDg3LjdDODEzLjgsOTMuMSw2NjYuNiwxMCw1MDAsMTB6Ii8+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjwvZz4KPC9zdmc+';
245
246 $pos = self::MENU_POSITION_ORDER;
247 if (is_multisite()) {
248 $pos = self::MENU_POSITION_ORDER_MULTISITE;
249 }
250
251 // Menu Position order needs to be unique for WordPress < 4.4
252 // We are not using a unique position by default to keep WP Staging directly below plugin menu
253 if (version_compare($wp_version, '4.4', '<')) {
254 $pos++;
255 }
256
257 $proSlug = defined('WPSTGPRO_VERSION') ? 'Pro' : '';
258
259 $defaultPageSlug = "wpstg_clone";
260 $defaultPageCallback = "getClonePage";
261 $defaultPageTitle = esc_html__("Staging Sites", "wp-staging");
262 $secondaryPageSlug = "wpstg_backup";
263 $secondaryPageCallback = "getBackupPage";
264 $secondaryPageTitle = esc_html__("Backup & Migration", "wp-staging");
265 /** @var SiteInfo */
266 $siteInfo = WPStaging::make(SiteInfo::class);
267 if ($siteInfo->isHostedOnWordPressCom()) {
268 $defaultPageSlug = "wpstg_backup";
269 $defaultPageCallback = "getBackupPage";
270 $defaultPageTitle = esc_html__("Backup & Migration", "wp-staging");
271 $secondaryPageSlug = "wpstg_clone";
272 $secondaryPageCallback = "getClonePage";
273 $secondaryPageTitle = esc_html__("Staging Sites", "wp-staging");
274 }
275
276 // Main WP Staging Menu
277 add_menu_page(
278 "WP STAGING",
279 esc_html("WP Staging " . $proSlug),
280 "manage_options",
281 $defaultPageSlug,
282 [$this, $defaultPageCallback],
283 $logo,
284 $pos
285 );
286
287 // Clone page normally but backup page on WordPress.com
288 add_submenu_page(
289 $defaultPageSlug,
290 esc_html__("WP Staging - Staging", "wp-staging"),
291 $defaultPageTitle,
292 "manage_options",
293 $defaultPageSlug,
294 [$this, $defaultPageCallback]
295 );
296
297 // Backup page normally but clone page on WordPress.com
298 add_submenu_page(
299 $defaultPageSlug,
300 esc_html__("WP Staging - Backup & Migration", "wp-staging"),
301 $secondaryPageTitle,
302 "manage_options",
303 $secondaryPageSlug,
304 [$this, $secondaryPageCallback]
305 );
306
307 // Page: Temporary Logins
308 add_submenu_page(
309 $defaultPageSlug,
310 esc_html__("WP Staging - Temporary Logins", "wp-staging"),
311 esc_html__("Temporary Logins", "wp-staging"),
312 "manage_options",
313 "wpstg-settings&tab=temporary-login",
314 [$this, "getTempLoginsPage"]
315 );
316
317 // Page: Settings
318 add_submenu_page(
319 $defaultPageSlug,
320 esc_html__("WP Staging - Settings", "wp-staging"),
321 esc_html__("Settings", "wp-staging"),
322 "manage_options",
323 "wpstg-settings",
324 [$this, "getSettingsPage"]
325 );
326
327 // Page: Tools
328 add_submenu_page(
329 $defaultPageSlug,
330 esc_html__("WP Staging - System Info", "wp-staging"),
331 esc_html__("System Info", "wp-staging"),
332 "manage_options",
333 "wpstg-tools",
334 [$this, "getToolsPage"]
335 );
336
337 if (!defined('WPSTGPRO_VERSION')) {
338 // Page: Tools
339 add_submenu_page(
340 $defaultPageSlug,
341 esc_html__("WP Staging - Welcome", "wp-staging"),
342 esc_html__("Get WP Staging Pro", "wp-staging"),
343 "manage_options",
344 "wpstg-welcome",
345 [$this, "getWelcomePage"]
346 );
347 }
348
349 if (defined('WPSTGPRO_VERSION')) {
350 // Page: wpstg-restorer
351 add_submenu_page(
352 $defaultPageSlug,
353 esc_html__("WP Staging - Restore", "wp-staging"),
354 '',
355 "manage_options",
356 "wpstg-restorer",
357 [$this, "getRestorerPage"]
358 );
359
360 // Remove wpstg-restorer side menu
361 add_filter('submenu_file', function ($submenu_file) use ($defaultPageSlug) {
362 remove_submenu_page($defaultPageSlug, 'wpstg-restorer');
363 });
364
365 // Page: License
366 add_submenu_page(
367 $defaultPageSlug,
368 esc_html__("WP Staging - Licence", "wp-staging"),
369 esc_html__("License", "wp-staging"),
370 "manage_options",
371 "wpstg-license",
372 [$this, "getLicensePage"]
373 );
374 }
375 }
376
377 /**
378 * Settings Page
379 */
380 public function getSettingsPage()
381 {
382
383 $license = get_option('wpstg_license_status');
384
385 // Tabs
386 $tabs = new Tabs(Hooks::applyFilters(self::FILTER_MAIN_SETTING_TABS, [
387 "general" => __("General", "wp-staging"),
388 ]));
389
390 WPStaging::getInstance()
391 // Set tabs
392 ->set("tabs", $tabs)
393 // Forms
394 ->set("forms", new FormSettings($tabs));
395
396 require_once "{$this->viewsPath}settings/main-settings.php";
397 }
398
399 /**
400 * Clone Page
401 */
402 public function getClonePage()
403 {
404
405 $license = get_option('wpstg_license_status');
406
407 $availableClones = get_option(Sites::STAGING_SITES_OPTION, []);
408
409 $isStagingPage = true;
410 $isBackupPage = false;
411 require_once "{$this->viewsPath}clone/index.php";
412 }
413
414 /**
415 * Backup & Migration Page
416 */
417 public function getBackupPage()
418 {
419 $license = get_option('wpstg_license_status');
420
421 // Existing clones
422 $availableClones = get_option(Sites::STAGING_SITES_OPTION, []);
423
424 $isBackupPage = true;
425 $isStagingPage = false;
426 require_once "{$this->viewsPath}clone/index.php";
427 }
428
429 /**
430 * Welcome Page
431 */
432 public function getWelcomePage()
433 {
434 if (defined('WPSTGPRO_VERSION')) {
435 return;
436 }
437
438 require_once "{$this->viewsPath}welcome/welcome.php";
439 }
440
441 /**
442 * Tools Page
443 */
444 public function getToolsPage()
445 {
446 // Tabs
447 $tabs = new Tabs([
448 "system-info" => __("System Info", "wp-staging"),
449 ]);
450
451 WPStaging::getInstance()->set("tabs", $tabs);
452
453 WPStaging::getInstance()->set("systemInfo", new SystemInfo());
454
455 // Get license data
456 $license = get_option('wpstg_license_status');
457
458 require_once "{$this->viewsPath}tools/index.php";
459 }
460
461 /**
462 * WP Staging Restore Page
463 * @todo Move this to Pro namespace
464 */
465 public function getRestorerPage()
466 {
467 // Get license data
468 $license = get_option('wpstg_license_status');
469
470 require_once "{$this->viewsPath}pro/wpstg-restorer-ui.php";
471 }
472
473 /**
474 * Download wpstg-restore.php file.
475 * @see dev/docs/wpstg-restore/README.md
476 * @return void
477 * @todo Move this to Pro namespace
478 */
479 public function downloadWpstgRestoreFile()
480 {
481 if (!defined('WPSTGPRO_VERSION') || !class_exists('WPStaging\Backend\Pro\WpstgRestoreDownloader', false)) {
482 wp_die('Invalid access', 'WP Staging Restore', ['response' => 403, 'back_link' => true]);
483 }
484
485 $WpstgRestore = WPStaging::make('WPStaging\Backend\Pro\WpstgRestoreDownloader');
486 $WpstgRestore->downloadFile();
487 }
488
489 /**
490 * Download System Information and latest log files.
491 * @return void
492 */
493 public function downloadSystemInfoAndLogFiles()
494 {
495 if (!current_user_can("update_plugins")) {
496 return;
497 }
498
499 $reportHandle = WPStaging::make(Report::class);
500 $downloadFile = $reportHandle->getBundledLogs();
501 if (empty($downloadFile)) {
502 wp_die('Failed to get All Log Files', 'WP Staging', ['response' => 200, 'back_link' => true]);
503 }
504
505 $isZipFile = count($downloadFile) === 1 && substr($downloadFile[0], -4) === '.zip';
506
507 nocache_headers();
508
509 if ($isZipFile) {
510 header('Content-Type: application/zip');
511 header('Content-Disposition: attachment; filename="wpstg-bundled-logs.zip"');
512 readfile($downloadFile[0]); // phpcs:ignore
513 $reportHandle->deleteBundledLogs();
514 exit();
515 }
516
517 header('Content-Type: text/plain');
518 header('Content-Disposition: attachment; filename="wpstg-bundled-logs.txt"');
519
520 $separator = "\n\n" . str_repeat('-', 100) . "\n\n";
521
522 foreach ($downloadFile as $logFile) {
523 $header = $separator . 'Log File: ' . basename($logFile) . $separator;
524 echo esc_html($header);
525 readfile($logFile); // phpcs:ignore
526 }
527
528 $reportHandle->deleteBundledLogs();
529 exit();
530 }
531
532 /**
533 * Render a view file
534 * @param string $file
535 * @param array $vars
536 * @return string
537 */
538 public function render($file, $vars = [])
539 {
540 $fullPath = $this->viewsPath . $file . ".php";
541 $fullPath = wp_normalize_path($fullPath);
542
543 if (!file_exists($fullPath) || !is_readable($fullPath)) {
544 return "Can't render : {$fullPath} either file doesn't exist or can't read it";
545 }
546
547 $contents = @file_get_contents($fullPath);
548
549 // Variables are set
550 if (count($vars) > 0) {
551 $vars = array_combine(
552 array_map(function ($key) {
553 return "{{" . $key . "}}";
554 }, array_keys($vars)),
555 $vars
556 );
557
558 $contents = str_replace(array_keys($vars), array_values($vars), $contents);
559 }
560
561 return $contents;
562 }
563
564 /**
565 * @return bool Whether the current request is considered to be authenticated.
566 */
567 private function isAuthenticated($nonce = Nonce::WPSTG_NONCE)
568 {
569 return $this->auth->isAuthenticatedRequest($nonce);
570 }
571
572 /**
573 * Restart cloning process
574 */
575 public function ajaxRestart()
576 {
577 if (!$this->isAuthenticated()) {
578 return;
579 }
580
581 $process = WPStaging::make(ProcessLock::class);
582 $process->restart();
583 }
584
585 /**
586 * Ajax Scan
587 * @action wp_ajax_wpstg_scanning 10 0
588 * @see Administrator::defineHooks()
589 */
590 public function ajaxCloneScan()
591 {
592 if (!$this->isAuthenticated()) {
593 return;
594 }
595
596 // Check first if there is already a process running
597 $processLock = WPStaging::make(ProcessLock::class);
598 $response = $processLock->ajaxIsRunning();
599 if ($response !== false) {
600 echo json_encode($response);
601
602 exit();
603 }
604
605 $siteInfo = WPStaging::make(SiteInfo::class);
606
607 $db = WPStaging::make('wpdb');
608
609 // Scan
610 $scan = WPStaging::make(Scan::class);
611 $scan->setGifLoaderPath($this->assets->getAssetsUrl('img/spinner.gif'));
612 $scan->setInfoIcon($this->assets->getAssetsUrl('svg/info-outline.svg'));
613 $scan->start();
614
615 // Get Options
616 $options = $scan->getOptions();
617 $excludeUtils = WPStaging::make(ExcludeFilter::class);
618 $wpDefaultDirectories = WPStaging::make(WpDefaultDirectories::class);
619 $isPro = WPStaging::isPro();
620
621 if ($isPro) {
622 require_once "{$this->viewsPath}pro/clone/ajax/scan.php";
623 } else {
624 require_once "{$this->viewsPath}clone/ajax/scan.php";
625 }
626
627 wp_die();
628 }
629
630 /**
631 * Fetch children of the given directory
632 */
633 public function ajaxFetchDirChildren()
634 {
635 if (!$this->isAuthenticated()) {
636 wp_send_json(['success' => false]);
637 return;
638 }
639
640 $isChecked = isset($_POST['isChecked']) ? $this->sanitize->sanitizeBool($_POST['isChecked']) : false;
641 $forceDefault = isset($_POST['forceDefault']) ? $this->sanitize->sanitizeBool($_POST['forceDefault']) : false;
642 $path = isset($_POST['dirPath']) ? $this->sanitize->sanitizePath($_POST['dirPath']) : "";
643 $prefix = isset($_POST['prefix']) ? $this->sanitize->sanitizePath($_POST['prefix']) : "";
644 $basePath = ABSPATH;
645 if ($prefix === PathIdentifier::IDENTIFIER_WP_CONTENT) {
646 $basePath = WP_CONTENT_DIR;
647 }
648
649 $path = trailingslashit($basePath) . $path;
650
651 // Prevent path traversal outside the base path
652 $resolvedPath = realpath($path);
653 $resolvedBase = realpath($basePath);
654 if ($resolvedPath === false || $resolvedBase === false) {
655 wp_send_json(['success' => false]);
656 return;
657 }
658
659 // Use trailing slash to enforce directory boundary (prevents matching sibling dirs like wp-content-old)
660 if ($resolvedPath !== $resolvedBase && strpos(trailingslashit($resolvedPath), trailingslashit($resolvedBase)) !== 0) {
661 wp_send_json(['success' => false]);
662 return;
663 }
664
665 $path = $resolvedPath;
666 $scan = new Scan($path);
667 $scan->setBasePath($basePath);
668 $scan->setPathIdentifier($prefix);
669 $scan->setGifLoaderPath($this->assets->getAssetsUrl('img/spinner.gif'));
670 $scan->getDirectories($path);
671 wp_send_json([
672 "success" => true,
673 "directoryListing" => json_encode($scan->directoryListing($isChecked, $forceDefault)),
674 ]);
675 }
676
677 /**
678 * Ajax Check Clone Name
679 */
680 public function ajaxCheckCloneDirectoryName()
681 {
682 if (!$this->isAuthenticated()) {
683 return;
684 }
685
686 /** @var Sites $sitesHelper */
687 $sitesHelper = WPStaging::make(Sites::class);
688 $cloneDirectoryName = isset($_POST["directoryName"]) ? $sitesHelper->sanitizeDirectoryName($_POST["directoryName"]) : '';
689
690 if (strlen($cloneDirectoryName) < 1) {
691 return;
692 }
693
694 $result = $sitesHelper->isCloneExists($cloneDirectoryName);
695 if ($result === false) {
696 wp_send_json(["status" => "success"]);
697 return;
698 }
699
700 wp_send_json([
701 "status" => "failed",
702 "message" => $result,
703 ]);
704 }
705
706 /**
707 * Ajax Start Updating Clone (Basically just layout and saving data)
708 */
709 public function ajaxUpdateProcess()
710 {
711 if (!$this->isAuthenticated()) {
712 return;
713 }
714
715 $cloning = WPStaging::make(Updating::class);
716
717 if (!$cloning->save()) {
718 wp_die('Can not save clone data');
719 }
720
721 $options = $cloning->getOptions();
722 WPStaging::make(AnalyticsStagingUpdate::class)->enqueueStartEvent($options->jobIdentifier, $options);
723
724 require_once "{$this->viewsPath}clone/ajax/update.php";
725
726 wp_die();
727 }
728
729 /**
730 * Ajax Start Resetting Clone
731 */
732 public function ajaxResetProcess()
733 {
734 if (!$this->isAuthenticated()) {
735 return;
736 }
737
738 $cloning = WPStaging::make(Updating::class);
739 $cloning->setMainJob(Job::RESET);
740 if (!$cloning->save()) {
741 wp_die('can not save clone data');
742 }
743
744 $options = $cloning->getOptions();
745 WPStaging::make(AnalyticsStagingReset::class)->enqueueStartEvent($options->jobIdentifier, $options);
746
747 require_once "{$this->viewsPath}clone/ajax/update.php";
748 wp_die();
749 }
750
751 /**
752 * Ajax Start Clone (Basically just layout and saving data)
753 */
754 public function ajaxStartClone()
755 {
756 if (!$this->isAuthenticated()) {
757 return;
758 }
759
760 // Check first if there is already a process running
761 $processLock = WPStaging::make(ProcessLock::class);
762 $processLock->isRunning();
763
764 $cloning = $this->getCloningJob();
765
766 if (!$cloning->save()) {
767 $message = $cloning->getErrorMessage();
768 wp_send_json([
769 'success' => false,
770 'message' => $message !== '' ? $message : 'Can not save clone data',
771 ]);
772
773 wp_die();
774 }
775
776 require_once "{$this->viewsPath}clone/ajax/start.php";
777
778 wp_die();
779 }
780
781 /**
782 * Ajax Clone Database
783 */
784 public function ajaxCloneDatabase()
785 {
786 if (!$this->isAuthenticated()) {
787 return;
788 }
789
790 $cloning = $this->getCloningJob();
791 wp_send_json($cloning->start());
792 }
793
794 /**
795 * Ajax Prepare Directories (get listing of files)
796 */
797 public function ajaxPrepareDirectories()
798 {
799 if (!$this->isAuthenticated()) {
800 return;
801 }
802
803 $cloning = $this->getCloningJob();
804 wp_send_json($cloning->start());
805 }
806
807 /**
808 * Ajax Clone Files
809 */
810 public function ajaxCopyFiles()
811 {
812 if (!$this->isAuthenticated()) {
813 return;
814 }
815
816 $cloning = $this->getCloningJob();
817 wp_send_json($cloning->start());
818 }
819
820 /**
821 * Ajax Replace Data
822 */
823 public function ajaxReplaceData()
824 {
825 if (!$this->isAuthenticated()) {
826 return;
827 }
828
829 $cloning = $this->getCloningJob();
830 wp_send_json($cloning->start());
831 }
832
833 /**
834 * Ajax Finish
835 */
836 public function ajaxFinish()
837 {
838 if (!$this->isAuthenticated()) {
839 return;
840 }
841
842 $cloning = $this->getCloningJob();
843 wp_send_json($cloning->start());
844 }
845
846 /**
847 * Cancel clone
848 */
849 public function ajaxCancelClone()
850 {
851 if (!$this->isAuthenticated()) {
852 return;
853 }
854
855 $cancel = WPStaging::make(Cancel::class);
856 wp_send_json($cancel->start());
857 }
858
859 /**
860 * Cancel updating process / Do not delete clone!
861 */
862 public function ajaxCancelUpdate()
863 {
864 if (!$this->isAuthenticated()) {
865 return;
866 }
867
868 $cancelUpdate = WPStaging::make(CancelUpdate::class);
869 wp_send_json($cancelUpdate->start());
870 }
871
872 /**
873 * Ajax Hide Rating
874 *
875 * Runs when the user dismisses the notice to rate the plugin.
876 */
877 public function ajaxHideRating()
878 {
879 if (!$this->isAuthenticated()) {
880 return;
881 }
882
883 if (update_option("wpstg_rating", "no") !== false) {
884 wp_send_json(true);
885 }
886
887 wp_send_json(null);
888 }
889
890 /**
891 * Ajax Hide Rating and show it again after one week
892 *
893 * Runs when the user chooses to rate the plugin later.
894 */
895 public function ajaxHideLaterRating()
896 {
897 if (!$this->isAuthenticated()) {
898 return;
899 }
900
901 $date = date('Y-m-d', strtotime(date('Y-m-d') . ' + 7 days'));
902 if (update_option('wpstg_rating', $date) !== false) {
903 wp_send_json(true);
904 }
905
906 wp_send_json(false);
907 }
908
909 /**
910 * Ajax Hide Beta
911 */
912 public function ajaxHideBeta()
913 {
914 if (!$this->isAuthenticated()) {
915 return;
916 }
917
918 wp_send_json(update_option("wpstg_beta", "no"));
919 }
920
921 /**
922 * @return void
923 */
924 public function ajaxDismissNotice()
925 {
926 if (!$this->isAuthenticated()) {
927 return;
928 }
929
930 // Early bail if no notice option available
931 if (!isset($_POST['wpstg_notice'])) {
932 wp_send_json(null);
933 return;
934 }
935
936 /** @var DismissNotice */
937 $dismissNotice = WPStaging::make(DismissNotice::class);
938 $dismissNotice->dismiss($this->sanitize->sanitizeString($_POST['wpstg_notice']));
939 }
940
941 /**
942 * Clone logs
943 */
944 public function ajaxLogs()
945 {
946 if (!$this->isAuthenticated()) {
947 return;
948 }
949
950 $logs = WPStaging::make(Logs::class);
951 wp_send_json($logs->start());
952 }
953
954 /**
955 * Ajax Checks Free Disk Space
956 */
957 public function ajaxCheckFreeSpace()
958 {
959 if (!$this->isAuthenticated()) {
960 return false;
961 }
962
963 $excludedDirectories = isset($_POST["excludedDirectories"]) ? $this->sanitize->sanitizeString($_POST["excludedDirectories"]) : '';
964 $extraDirectories = isset($_POST["extraDirectories"]) ? $this->sanitize->sanitizeString($_POST["extraDirectories"]) : '';
965 $isUploadsSymlinked = isset($_POST["isUploadsSymlinked"]) && $this->sanitize->sanitizeBool($_POST["isUploadsSymlinked"]);
966
967 $scan = WPStaging::make(Scan::class);
968 $scan->setIsUploadsSymlinked($isUploadsSymlinked);
969
970 return $scan->hasFreeDiskSpace($excludedDirectories, $extraDirectories);
971 }
972
973 /**
974 * Ajax Start Push Changes Process
975 * Start with the module Scan
976 * @action wp_ajax_wpstg_scans 10 0
977 * @see Administrator::defineHooks()
978 */
979 public function ajaxPushScan()
980 {
981 if (!$this->isAuthenticated()) {
982 return false;
983 }
984
985 if (!class_exists('WPStaging\Backend\Pro\Modules\Jobs\Scan')) {
986 return false;
987 }
988
989 // Scan
990 $scan = WPStaging::make(ScanProModule::class);
991
992 $scan->start();
993
994 // Get Options
995 $options = $scan->getOptions();
996
997 // Get Framework\Utils\Math
998 $utilsMath = WPStaging::make(Math::class);
999
1000 require_once "{$this->viewsPath}pro/scan.php";
1001
1002 wp_die();
1003 }
1004
1005 /**
1006 * Fetch all tables for push process
1007 */
1008 public function ajaxPushTables()
1009 {
1010 if (!$this->isAuthenticated()) {
1011 return false;
1012 }
1013
1014 if (!class_exists('WPStaging\Backend\Pro\Modules\Jobs\Scan')) {
1015 return false;
1016 }
1017
1018 // Scan
1019 $scan = WPStaging::make(ScanProModule::class);
1020 $scan->loadStagingDBTables($onlyLoadStagingPrefixTables = false);
1021 $scan->start();
1022 $options = $scan->getOptions();
1023
1024 $includedTables = isset($_POST['includedTables']) ? $this->sanitize->sanitizeString($_POST['includedTables']) : '';
1025 $excludedTables = isset($_POST['excludedTables']) ? $this->sanitize->sanitizeString($_POST['excludedTables']) : '';
1026 $selectedTablesWithoutPrefix = isset($_POST['selectedTablesWithoutPrefix']) ? $this->sanitize->sanitizeString($_POST['selectedTablesWithoutPrefix']) : '';
1027 $selectedTables = new SelectedTables($includedTables, $excludedTables, $selectedTablesWithoutPrefix);
1028 $selectedTables->setDatabaseInfo($options->databaseServer, $options->databaseUser, $options->databasePassword, $options->databaseDatabase, empty($options->databasePrefix) ? $options->prefix : $options->databasePrefix, $options->databaseSsl);
1029 $tables = $selectedTables->getSelectedTables($options->networkClone);
1030
1031 $templateEngine = WPStaging::make(TemplateEngine::class);
1032
1033 echo json_encode([
1034 'success' => true,
1035 "content" => $templateEngine->render("pro/selections/tables.php", [
1036 'isNetworkClone' => $scan->isNetworkClone(),
1037 'options' => $options,
1038 'showAll' => true,
1039 'selected' => $tables,
1040 ]),
1041 ]);
1042
1043 exit();
1044 }
1045
1046 /**
1047 * Ajax Start Pushing. Needs WP Staging Pro
1048 */
1049 public function ajaxPushProcessing()
1050 {
1051 if (!$this->isAuthenticated()) {
1052 return false;
1053 }
1054
1055 if (!class_exists('WPStaging\Backend\Pro\Modules\Jobs\Processing')) {
1056 return false;
1057 }
1058
1059 // Start the process
1060 wp_send_json(WPStaging::make(Processing::class)->start());
1061
1062 return false;
1063 }
1064
1065 /**
1066 * License Page
1067 */
1068 public function getLicensePage()
1069 {
1070 // Get license data
1071 $license = get_option('wpstg_license_status');
1072
1073 require_once "{$this->viewsPath}pro/licensing.php";
1074 }
1075
1076 /**
1077 * @return void
1078 */
1079 public function getTempLoginsPage()
1080 {
1081 Hooks::applyFilters(Administrator::FILTER_MAIN_SETTING_TABS, [
1082 "temporary-logins" => __("Temporary Logins", "wp-staging"),
1083 ]);
1084 }
1085
1086 /**
1087 * Send mail via ajax
1088 * @param array $args
1089 */
1090 public function ajaxSendReport($args = [])
1091 {
1092 if (!$this->isAuthenticated()) {
1093 return;
1094 }
1095
1096 // Set params
1097 if (empty($args)) {
1098 $args = stripslashes_deep($_POST);
1099 }
1100
1101 // Set e-mail
1102 $emailRecipient = '';
1103 if (isset($args['wpstg_email'])) {
1104 $emailRecipient = $this->sanitize->sanitizeEmail($args['wpstg_email']);
1105 }
1106
1107 // Set hosting provider
1108 $providerName = '';
1109 if (!empty($args['wpstg_provider'])) {
1110 $providerName = $this->sanitize->sanitizeString($args['wpstg_provider']);
1111 }
1112
1113 // Set message
1114 $messageBody = '';
1115 if (!empty($args['wpstg_message'])) {
1116 $messageBody = $this->sanitize->sanitizeString($args['wpstg_message']);
1117 }
1118
1119 // Set syslog
1120 $sendLogFiles = false;
1121 if (isset($args['wpstg_syslog'])) {
1122 $sendLogFiles = $this->sanitize->sanitizeBool($args['wpstg_syslog']);
1123 }
1124
1125 // Set terms
1126 $termsAccepted = false;
1127 if (isset($args['wpstg_terms'])) {
1128 $termsAccepted = $this->sanitize->sanitizeBool($args['wpstg_terms']);
1129 }
1130
1131 // Set forceSend
1132 $forceSend = isset($_POST['wpstg_force_send']) && $this->sanitize->sanitizeBool($_POST['wpstg_force_send']);
1133
1134 $report = WPStaging::make(Report::class);
1135 $errors = $report->send($emailRecipient, $messageBody, $termsAccepted, $sendLogFiles, $providerName, $forceSend);
1136
1137 echo json_encode(['errors' => $errors]);
1138 exit;
1139 }
1140
1141 /**
1142 * Action to perform when error modal confirm button is clicked
1143 *
1144 * @todo use constants instead of hardcoded strings for error types
1145 */
1146 public function ajaxModalError()
1147 {
1148 if (!$this->isAuthenticated()) {
1149 return;
1150 }
1151
1152 $type = isset($_POST['type']) ? $this->sanitize->sanitizeString($_POST['type']) : null;
1153 if ($type === 'processLock') {
1154 $process = WPStaging::make(ProcessLock::class);
1155 $process->restart();
1156
1157 exit();
1158 }
1159 }
1160
1161 /**
1162 * Render tables and files selection for RESET function
1163 */
1164 public function ajaxCloneExcludesSettings()
1165 {
1166 if (!$this->isAuthenticated()) {
1167 return;
1168 }
1169
1170 $processLock = WPStaging::make(ProcessLock::class);
1171 $response = $processLock->ajaxIsRunning();
1172 if ($response !== false) {
1173 echo json_encode($response);
1174
1175 exit();
1176 }
1177
1178 $templateEngine = WPStaging::make(TemplateEngine::class);
1179
1180 // Scan
1181 $scan = WPStaging::make(Scan::class);
1182 $scan->setGifLoaderPath($this->assets->getAssetsUrl('img/spinner.gif'));
1183 $scan->start();
1184
1185 echo json_encode([
1186 'success' => true,
1187 "html" => $templateEngine->render("clone/ajax/exclude-settings.php", [
1188 'scan' => $scan,
1189 'options' => $scan->getOptions(),
1190 'excludeUtils' => WPStaging::make(ExcludeFilter::class),
1191 ]),
1192 ]);
1193
1194 exit();
1195 }
1196
1197 /**
1198 * Enable cloning on staging site if it is not enabled already
1199 */
1200 public function ajaxEnableStagingCloning()
1201 {
1202 if (!$this->isAuthenticated()) {
1203 return;
1204 }
1205
1206 if ($this->siteInfo->enableStagingSiteCloning()) {
1207 echo json_encode(['success' => 'true']);
1208 exit();
1209 }
1210
1211 echo json_encode(['success' => 'false', 'message' => __('Unable to enable cloning in the staging site', 'wp-staging')]);
1212 exit();
1213 }
1214
1215 /**
1216 * Restore Settings, can be used when settings are corrupted
1217 */
1218 public function ajaxRestoreSettings()
1219 {
1220 if (!$this->isAuthenticated()) {
1221 return;
1222 }
1223
1224 // Delete old settings
1225 delete_option('wpstg_settings');
1226 $settings = WPStaging::make(Settings::class);
1227 $settings->setDefault();
1228 }
1229
1230 /**
1231 * Remove uploads backup
1232 */
1233 public function removeOldUploadsBackup()
1234 {
1235 $backup = new BackupUploadsDir(null);
1236 $backup->removeUploadsBackup();
1237 }
1238
1239 /**
1240 * Check if Plugin is Pro version
1241 * @return bool
1242 */
1243 protected function isPro()
1244 {
1245 if (!defined("WPSTGPRO_VERSION")) {
1246 return false;
1247 }
1248
1249 return true;
1250 }
1251
1252 /**
1253 * @return Cloning
1254 */
1255 private function getCloningJob(): Cloning
1256 {
1257 return WPStaging::make(CloningJobProvider::class)->getCloningJob();
1258 }
1259
1260 /**
1261 * Check if current page is plugins.php
1262 * @global array $pagenow
1263 * @return bool
1264 */
1265 private function isPluginsPage()
1266 {
1267 global $pagenow;
1268 return ($pagenow === 'plugins.php');
1269 }
1270 }
1271