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 / Activation / Welcome.php
wp-staging / Backend / Activation Last commit date
Welcome.php 4 days ago
Welcome.php
232 lines
1 <?php
2
3 namespace WPStaging\Backend\Activation;
4
5 // No Direct Access
6 if (!defined("WPINC")) {
7 die;
8 }
9
10 use WPStaging\Framework\Analytics\Actions\AnalyticsGenericEvent;
11 use WPStaging\Framework\Experiments\ExperimentsRegistry;
12 use WPStaging\Framework\Onboarding\FreeOnboarding;
13 use WPStaging\Framework\Traits\NoticesTrait;
14 use WPStaging\Framework\Facades\Escape;
15 use WPStaging\Framework\Notices\Notices;
16
17 class Welcome
18 {
19 use NoticesTrait;
20
21 /**
22 * Marks the welcome page as the one the activation redirect sent the user to.
23 *
24 * The sidebar's "Get WP Staging Pro" opens this same page, so without it the
25 * page cannot tell an upgrade the user went looking for from one the plugin
26 * put in front of them, and both would be attributed as the same click.
27 */
28 const QUERY_FROM_ACTIVATION = 'wpstg-activation';
29
30 /** utm_content for the buy button on a welcome page reached by activation. */
31 const UTM_CONTENT_ACTIVATION = 'welcome_page_activation';
32
33 /** utm_content for the buy button on a welcome page the user asked for. */
34 const UTM_CONTENT_SIDEBAR = 'sidebar_upgrade';
35
36 /** @see dev/docs/analytics/generic-events.md */
37 const EVENT_ACTIVATION_REDIRECT = 'activation_redirect';
38
39 public function __construct()
40 {
41 add_action('admin_init', [$this, 'welcome']);
42 add_action('wp_ajax_wpstg_activate_pro', [$this, 'ajaxActivatePro']); // phpcs:ignore WPStaging.Security.AuthorizationChecked -- Authorization checked in ajaxActivatePro()
43
44 if (wpstgGetProVersionNumberIfInstalled() && $this->isWPStagingAdminPage()) {
45 add_action(Notices::ACTION_ADMIN_NOTICES, [$this, 'wpstgproActivationNotice']);
46 }
47 }
48
49 /**
50 * @return void
51 * @todo make this message permanently hideable when dismissed.
52 */
53 public function wpstgproActivationNotice()
54 {
55 if ($this->isFirstRunAwaitingConsent()) {
56 return;
57 }
58
59 $nonce = wp_create_nonce('wpstg_activate_pro');
60 ?>
61 <div class="notice notice-warning is-dismissible">
62 <p>
63 <?php
64 printf(
65 Escape::escapeHtml(__('WP Staging Pro is installed but not active. %1$sActivate now%2$s to unlock all Pro features.', 'wp-staging')),
66 '<a href="#" id="wpstg-activate-pro" data-nonce="' . esc_attr($nonce) . '">',
67 '</a>'
68 );
69 ?>
70 </p>
71 </div>
72 <script>
73 (function() {
74 var link = document.getElementById('wpstg-activate-pro');
75 if (!link) return;
76 link.addEventListener('click', function(e) {
77 e.preventDefault();
78 if (link.dataset.busy) return;
79 link.dataset.busy = '1';
80 var originalText = link.textContent;
81 link.textContent = '<?php echo esc_js(__('Activating...', 'wp-staging')); ?>';
82 link.style.pointerEvents = 'none';
83 var xhr = new XMLHttpRequest();
84 xhr.open('POST', ajaxurl);
85 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
86 xhr.onload = function() {
87 try { var res = JSON.parse(xhr.responseText); } catch(err) { res = {}; }
88 if (res.success) {
89 window.location.reload();
90 } else {
91 link.textContent = res.data || 'Activation failed.';
92 link.style.pointerEvents = '';
93 delete link.dataset.busy;
94 }
95 };
96 xhr.onerror = function() {
97 link.textContent = originalText;
98 link.style.pointerEvents = '';
99 delete link.dataset.busy;
100 };
101 xhr.send('action=wpstg_activate_pro&nonce=' + link.dataset.nonce);
102 });
103 })();
104 </script>
105 <?php
106 }
107
108 /**
109 * The neutral first-run shell asks for exactly one decision, and its notice
110 * dispatcher cannot be silenced wholesale because the consent modal renders
111 * through it. So this notice stands down on its own instead.
112 *
113 * @return bool
114 */
115 private function isFirstRunAwaitingConsent(): bool
116 {
117 $onboarding = FreeOnboarding::resolve();
118
119 return $onboarding !== null && $onboarding->isPreConsentScreen();
120 }
121
122 /**
123 * @return void
124 */
125 public function ajaxActivatePro()
126 {
127 if (!check_ajax_referer('wpstg_activate_pro', 'nonce', false)) {
128 wp_send_json_error('Invalid security token.');
129 }
130
131 if (!current_user_can('activate_plugins')) {
132 wp_send_json_error('Insufficient permissions.');
133 }
134
135 $slug = wpstgGetPluginSlug(WPSTG_PRO_VERSION_PLUGIN_FILE);
136 if (!$slug) {
137 wp_send_json_error('Pro plugin not found.');
138 }
139
140 $result = activate_plugin($slug);
141 if (is_wp_error($result)) {
142 wp_send_json_error($result->get_error_message());
143 }
144
145 wp_send_json_success();
146 }
147
148 /**
149 * Sends user to the welcome page on first activation of WPSTG as well as each
150 * time WPSTG is upgraded to a new version
151 *
152 * @access public
153 * @return void
154 * @since 1.0.1
155 */
156 public function welcome()
157 {
158 // Bail if pro is installed
159 if (wpstgGetProVersionNumberIfInstalled()) {
160 return;
161 }
162
163 // Bail if no activation redirect
164 if (get_transient('wpstg_activation_redirect') === false) {
165 return;
166 }
167
168 // Delete the redirect transient
169 delete_transient('wpstg_activation_redirect');
170
171 // Bail if activating from network, or bulk
172 if (is_network_admin() || isset($_GET['activate-multi'])) {
173 return;
174 }
175
176 wp_safe_redirect($this->getFirstScreenUrl());
177 exit;
178 }
179
180 /**
181 * Where a newly activated installation lands.
182 *
183 * The variant is drawn here rather than on the screen that follows, because
184 * which screen follows is what it decides. `control` keeps the welcome page
185 * the funnel is built on; `task_selector` goes straight to WP STAGING, where
186 * the consent question and then the first-run selector take over.
187 *
188 * An installation that is not in the experiment at all — Pro installed, an
189 * upgrade rather than a first install, a user without the capability — draws
190 * no variant and keeps the existing behaviour.
191 */
192 public function getFirstScreenUrl(): string
193 {
194 $onboarding = FreeOnboarding::resolve();
195
196 return $this->getScreenUrlForVariant($onboarding === null ? '' : $onboarding->getVariant());
197 }
198
199 /**
200 * @param string $variant The drawn variant, or empty for an installation
201 * that is not in the experiment.
202 */
203 public function getScreenUrlForVariant(string $variant): string
204 {
205 $isFirstRun = $variant === ExperimentsRegistry::VARIANT_TASK_SELECTOR;
206
207 // The experiment and variant are stamped on every event by the pipeline,
208 // so the payload only has to say where this installation was sent — which
209 // is the thing the variant was drawn to decide.
210 AnalyticsGenericEvent::logEvent(self::EVENT_ACTIVATION_REDIRECT, FreeOnboarding::ANALYTICS_GROUP, [
211 'destination' => $isFirstRun ? 'task_selector' : 'welcome_page',
212 ]);
213
214 if ($isFirstRun) {
215 return admin_url('admin.php?page=wpstg_clone');
216 }
217
218 return add_query_arg(self::QUERY_FROM_ACTIVATION, '1', admin_url('admin.php?page=wpstg-welcome'));
219 }
220
221 /**
222 * @return string The utm_content the buy button should carry, which depends
223 * on how the user arrived rather than on where they are.
224 */
225 public static function getUpgradeContext(): string
226 {
227 // Read-only routing detail on an admin page the user is already on: it
228 // selects an attribution label and nothing else. phpcs:ignore WordPress.Security.NonceVerification.Recommended
229 return isset($_GET[self::QUERY_FROM_ACTIVATION]) ? self::UTM_CONTENT_ACTIVATION : self::UTM_CONTENT_SIDEBAR;
230 }
231 }
232