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