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