PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.0
4.11.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 / Framework / Onboarding / FreeOnboarding.php
wp-staging / Framework / Onboarding Last commit date
BackupPluginsDetector.php 1 day ago FirstInstall.php 1 day ago FreeOnboarding.php 1 day ago NextStepRenderer.php 1 day ago OnboardingAjax.php 1 day ago OnboardingJourney.php 1 day ago QueuedBackup.php 1 day ago
FreeOnboarding.php
398 lines
1 <?php
2
3 namespace WPStaging\Framework\Onboarding;
4
5 use WPStaging\Core\WPStaging;
6 use WPStaging\Framework\Analytics\Actions\AnalyticsGenericEvent;
7 use WPStaging\Framework\Analytics\AnalyticsConsent;
8 use WPStaging\Framework\SiteInfo;
9 use WPStaging\Staging\Sites;
10
11
12
13
14
15
16
17
18
19 class FreeOnboarding
20 {
21
22 const OPTION_EXPOSURE = 'wpstg_onboarding_exposure';
23
24
25 const STAGE_PRE_CONSENT = 'pre_consent';
26
27
28 const STAGE_TASK_SELECTOR = 'task_selector';
29
30
31 const STAGE_NONE = '';
32
33 const CONSENT_BEFORE_EXPOSURE = 'before_exposure';
34 const CONSENT_UNRESOLVED_AT_EXPOSURE = 'unresolved_at_exposure';
35
36 const ANALYTICS_GROUP = 'onboarding';
37
38
39 const EVENT_IMPRESSION = 'onboarding_impression';
40
41 const EVENT_ACTION_SELECTED = 'onboarding_action_selected';
42 const EVENT_BACKUP_NEXT_CLICKED = 'backup_next_clicked';
43
44
45 const ACTION_DESKTOP = 'desktop';
46
47 const ACTIONS = ['staging', 'backup', self::ACTION_DESKTOP];
48
49
50 const FIRST_RUN_PAGES = ['wpstg_clone', 'wpstg_backup'];
51
52
53 private $firstInstall;
54
55
56 private $backupPluginsDetector;
57
58
59 private $siteInfo;
60
61
62 private $analyticsConsent;
63
64
65 private $journey;
66
67
68 private $sites;
69
70
71 private $isEligible = null;
72
73 public function __construct(
74 FirstInstall $firstInstall,
75 BackupPluginsDetector $backupPluginsDetector,
76 SiteInfo $siteInfo,
77 AnalyticsConsent $analyticsConsent,
78 OnboardingJourney $journey,
79 Sites $sites
80 ) {
81 $this->firstInstall = $firstInstall;
82 $this->backupPluginsDetector = $backupPluginsDetector;
83 $this->siteInfo = $siteInfo;
84 $this->analyticsConsent = $analyticsConsent;
85 $this->journey = $journey;
86 $this->sites = $sites;
87 }
88
89
90
91
92
93
94
95 public static function resolve()
96 {
97 try {
98 return WPStaging::make(self::class);
99 } catch (\Throwable $e) {
100 return null;
101 }
102 }
103
104
105
106
107
108
109
110
111
112 public function getStage(): string
113 {
114 if (!$this->isEligible()) {
115 return self::STAGE_NONE;
116 }
117
118 return $this->isConsentResolved() ? self::STAGE_TASK_SELECTOR : self::STAGE_PRE_CONSENT;
119 }
120
121
122
123
124
125
126
127
128
129 public function isConsentResolved(): bool
130 {
131 if ($this->analyticsConsent->hasUserConsent() !== null) {
132 return true;
133 }
134
135 return (bool)get_option(AnalyticsConsent::OPTION_NAME_ANALYTICS_MODAL_DISMISSED);
136 }
137
138
139
140
141 public function isEligible(): bool
142 {
143 if ($this->isEligible === null) {
144 $this->isEligible = $this->checkEligibility();
145 }
146
147 return $this->isEligible;
148 }
149
150
151
152
153 public function isTaskSelector(): bool
154 {
155 return $this->getStage() === self::STAGE_TASK_SELECTOR;
156 }
157
158
159
160
161 public function isPreConsent(): bool
162 {
163 return $this->getStage() === self::STAGE_PRE_CONSENT;
164 }
165
166
167
168
169
170
171
172
173
174 public function ownsCurrentScreen(): bool
175 {
176 return $this->isTaskSelector() && $this->isFirstRunPage();
177 }
178
179
180
181
182 public function isPreConsentScreen(): bool
183 {
184 return $this->isPreConsent() && $this->isFirstRunPage();
185 }
186
187 private function isFirstRunPage(): bool
188 {
189 $page = isset($_GET['page']) ? sanitize_key($_GET['page']) : ''; // phpcs:ignore WPStaging.Security.FirstArgNotAString
190
191 return in_array($page, self::FIRST_RUN_PAGES, true);
192 }
193
194 public function getBackupPluginsDetector(): BackupPluginsDetector
195 {
196 return $this->backupPluginsDetector;
197 }
198
199
200
201
202
203 public function isHostedOnWordPressCom(): bool
204 {
205 return $this->siteInfo->isHostedOnWordPressCom();
206 }
207
208
209
210
211
212
213
214
215
216 public function recordExposure()
217 {
218 if (!$this->isTaskSelector() || $this->hasBeenExposed()) {
219 return;
220 }
221
222 $consentTiming = $this->analyticsConsent->hasUserConsent() === '1'
223 ? self::CONSENT_BEFORE_EXPOSURE
224 : self::CONSENT_UNRESOLVED_AT_EXPOSURE;
225
226 update_option(self::OPTION_EXPOSURE, [
227 'at' => time(),
228 'consent' => $consentTiming,
229 ], false);
230
231 AnalyticsGenericEvent::logEvent(self::EVENT_IMPRESSION, self::ANALYTICS_GROUP, [
232 'competitor' => $this->backupPluginsDetector->getCompetitorId(),
233 'consent_timing' => $consentTiming,
234 ]);
235 }
236
237
238
239
240 public function hasBeenExposed(): bool
241 {
242 $exposure = get_option(self::OPTION_EXPOSURE);
243
244 return is_array($exposure) && !empty($exposure['at']);
245 }
246
247
248
249
250
251
252
253
254
255 public function selectAction(string $action): bool
256 {
257 if (!in_array($action, self::ACTIONS, true)) {
258 return false;
259 }
260
261 AnalyticsGenericEvent::logEvent(self::EVENT_ACTION_SELECTED, self::ANALYTICS_GROUP, [
262 'action' => $action,
263 'position' => $this->journey->selectAction($action),
264 'competitor' => $this->backupPluginsDetector->getCompetitorId(),
265 ]);
266
267 return true;
268 }
269
270
271
272
273 public function finish()
274 {
275 $this->journey->completeOnExplicitExit();
276 $this->isEligible = false;
277 }
278
279
280
281
282
283 public function recordBackupNextClicked()
284 {
285 AnalyticsGenericEvent::logEvent(self::EVENT_BACKUP_NEXT_CLICKED, self::ANALYTICS_GROUP);
286 }
287
288 public function getJourney(): OnboardingJourney
289 {
290 return $this->journey;
291 }
292
293
294
295
296
297
298
299
300
301 public function canRestart(): bool
302 {
303 if (!defined('WP_DEBUG') || !WP_DEBUG || WPStaging::isPro()) {
304 return false;
305 }
306
307 return current_user_can('manage_options') && !$this->siteInfo->isStagingSite();
308 }
309
310 public function restart()
311 {
312 $this->journey->restart();
313 $this->isEligible = null;
314 }
315
316
317
318
319
320 public function getJourneyStep(): string
321 {
322 return $this->isTaskSelector() ? $this->journey->getStep() : '';
323 }
324
325
326
327
328
329
330
331
332 public function getLatestStagingSiteUrl(): string
333 {
334 try {
335 $sites = $this->sites->getSortedStagingSites();
336 } catch (\Throwable $e) {
337 return '';
338 }
339
340 $latest = reset($sites);
341
342 if (!is_array($latest) || empty($latest['url'])) {
343 return '';
344 }
345
346
347
348
349 return trailingslashit((string)$latest['url']);
350 }
351
352
353
354
355
356
357
358
359 public function addFocusModeBodyClass($classes): string
360 {
361
362 if (!$this->isFirstRunPage() || (!$this->isPreConsent() && !$this->isTaskSelector())) {
363 return (string)$classes;
364 }
365
366 return trim($classes . ' wpstg-onboarding-focus-mode');
367 }
368
369
370
371
372
373
374 private function checkEligibility(): bool
375 {
376
377 if (WPStaging::isPro() || !is_admin() || !current_user_can('manage_options') || $this->siteInfo->isStagingSite() || is_multisite()) {
378 return false;
379 }
380
381 if ($this->journey->isCompleted()) {
382 return false;
383 }
384
385
386
387
388
389
390
391 if ($this->journey->getStep() !== OnboardingJourney::STEP_SELECT || $this->journey->wasRestarted()) {
392 return true;
393 }
394
395 return $this->firstInstall->isFirstInstall() && empty(get_option(Sites::STAGING_SITES_OPTION, []));
396 }
397 }
398