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