PluginProbe ʕ •ᴥ•ʔ
Presto Player / 4.3.1
Presto Player v4.3.1
4.3.1 4.3.0 4.2.4 4.2.3 4.2.2 4.2.0 4.2.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.13.0 1.14.0 1.14.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.13 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3-beta1 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.0-beta1 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.1.0 3.1.1 3.1.2 3.1.3 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4
presto-player / inc / Services / Onboarding.php
presto-player / inc / Services Last commit date
API 1 month ago Blocks 10 months ago License 1 month ago AdminNotice.php 3 weeks ago AdminNotices.php 3 weeks ago AjaxActions.php 3 weeks ago Blocks.php 1 year ago Compatibility.php 10 months ago Learn.php 1 month ago Menu.php 1 month ago Migrations.php 1 year ago NpsSurvey.php 5 months ago Onboarding.php 3 weeks ago Player.php 3 days ago PluginInstaller.php 1 month ago PreloadService.php 1 year ago ProCompatibility.php 1 month ago ReusableVideos.php 2 weeks ago RewriteRulesManager.php 1 year ago Scripts.php 2 weeks ago Settings.php 1 month ago Shortcodes.php 1 month ago Streamer.php 3 weeks ago Translation.php 3 weeks ago Usage.php 3 weeks ago VideoPostType.php 2 weeks ago
Onboarding.php
348 lines
1 <?php
2 /**
3 * Onboarding wizard tracking service.
4 *
5 * @package PrestoPlayer
6 * @subpackage Services
7 */
8
9 namespace PrestoPlayer\Services;
10
11 /**
12 * Stores onboarding completion state and exposes REST endpoints for the wizard UI.
13 */
14 class Onboarding {
15
16 /**
17 * Option key for onboarding completion status.
18 *
19 * @var string
20 */
21 private $status_option = 'presto_player_onboarding_completed';
22
23 /**
24 * Option key recording how the wizard ended ('completed' or 'skipped').
25 *
26 * Only the wizard endpoints write this — unlike the completion flag,
27 * which legacy installs and the 4.2.0 backfill also set — so the
28 * analytics sweep can tell a real wizard outcome from old state.
29 *
30 * @var string
31 */
32 private $result_option = 'presto_player_onboarding_result';
33
34 /**
35 * Option key storing the step the user bailed on.
36 *
37 * @var string
38 */
39 private $skipped_step_option = 'presto_player_onboarding_skipped_step';
40
41 /**
42 * Register hooks and REST routes.
43 */
44 public function register() {
45 add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
46 add_action( 'admin_init', array( $this, 'maybe_redirect_to_onboarding' ) );
47 add_action( 'admin_init', array( $this, 'suppress_partner_redirects' ), 1 );
48 }
49
50 /**
51 * Suppress activation redirects from partner plugins while PP onboarding is in progress.
52 * Runs at priority 1 so it fires before partner plugins' own admin_init hooks.
53 */
54 public function suppress_partner_redirects() {
55 if ( $this->is_completed() ) {
56 return;
57 }
58 // Each branch is gated so we only touch the DB when something is
59 // actually pending — without this, every admin pageload during
60 // onboarding does 3 reads + 2 timeout-table SELECTs from delete_option.
61 if ( false !== get_option( '__suredash_do_redirect', false ) ) {
62 update_option( '__suredash_do_redirect', false );
63 }
64 if ( false !== get_option( 'suremails_do_redirect', false ) ) {
65 update_option( 'suremails_do_redirect', false );
66 }
67 // OttoKit — transient-based redirect (no filter available).
68 if ( false !== get_transient( 'st-redirect-after-activation' ) ) {
69 delete_transient( 'st-redirect-after-activation' );
70 }
71 }
72
73 /**
74 * Register REST API routes for onboarding.
75 */
76 public function register_rest_routes() {
77 register_rest_route(
78 'presto-player/v1',
79 '/onboarding/set-status',
80 array(
81 'methods' => 'POST',
82 'callback' => array( $this, 'set_status' ),
83 'permission_callback' => function () {
84 return current_user_can( 'manage_options' );
85 },
86 'args' => array(
87 'status' => array(
88 'type' => 'string',
89 'required' => true,
90 'enum' => array( 'completed', 'skipped' ),
91 // Custom sanitize_callback suppresses the auto-attached
92 // enum check, so wire validation back in explicitly.
93 'validate_callback' => 'rest_validate_request_arg',
94 'sanitize_callback' => 'sanitize_text_field',
95 ),
96 'skipped_on_step' => array(
97 'type' => 'string',
98 'required' => false,
99 'enum' => array( 'welcome', 'user_info', 'premium_features', 'integrations' ),
100 'validate_callback' => 'rest_validate_request_arg',
101 'sanitize_callback' => 'sanitize_key',
102 ),
103 ),
104 )
105 );
106
107 register_rest_route(
108 'presto-player/v1',
109 '/onboarding/get-status',
110 array(
111 'methods' => 'GET',
112 'callback' => array( $this, 'get_status' ),
113 'permission_callback' => function () {
114 return current_user_can( 'manage_options' );
115 },
116 )
117 );
118
119 register_rest_route(
120 'presto-player/v1',
121 '/onboarding/save-user-info',
122 array(
123 'methods' => 'POST',
124 'callback' => array( $this, 'save_user_info' ),
125 'permission_callback' => function () {
126 return current_user_can( 'manage_options' );
127 },
128 'args' => array(
129 'firstName' => array(
130 'type' => 'string',
131 'required' => false,
132 'sanitize_callback' => 'sanitize_text_field',
133 ),
134 'lastName' => array(
135 'type' => 'string',
136 'required' => false,
137 'sanitize_callback' => 'sanitize_text_field',
138 ),
139 'email' => array(
140 'type' => 'string',
141 'required' => true,
142 'format' => 'email',
143 'sanitize_callback' => 'sanitize_email',
144 'validate_callback' => function ( $value ) {
145 return is_string( $value ) && is_email( $value );
146 },
147 ),
148 // Explicit boolean. REST coerces '0', '', 'false' to false correctly;
149 // PHP's (bool) cast on the string 'false' would coerce it to true.
150 'optIn' => array(
151 'type' => 'boolean',
152 'required' => false,
153 'default' => false,
154 ),
155 ),
156 )
157 );
158 }
159
160 /**
161 * Set onboarding completion status.
162 *
163 * @param \WP_REST_Request $request Request object.
164 * @return \WP_REST_Response
165 */
166 public function set_status( $request ) {
167 $status = (string) $request->get_param( 'status' );
168
169 // Mark onboarding finished either way so activation redirects stop.
170 update_option( $this->status_option, 'yes' );
171
172 if ( 'skipped' === $status ) {
173 // A completion is sticky — a later re-run that gets bailed on
174 // shouldn't downgrade the recorded outcome.
175 if ( 'completed' !== get_option( $this->result_option ) ) {
176 update_option( $this->result_option, 'skipped', false );
177 $step = (string) $request->get_param( 'skipped_on_step' );
178 if ( '' !== $step ) {
179 update_option( $this->skipped_step_option, $step, false );
180 } else {
181 // Re-skip without a step: clear any stale step from a prior
182 // skip so the recorded value always reflects the latest skip.
183 delete_option( $this->skipped_step_option );
184 }
185 }
186 } else {
187 if ( 'skipped' === get_option( $this->result_option ) ) {
188 // The skip may already be queued or sent as
189 // `onboarding_completed = no`; force-track the corrected value
190 // so the funnel ends up right (overwrites pending, bypasses
191 // the pushed dedup).
192 $events = Usage::events();
193 if ( $events ) {
194 $events->track( 'onboarding_completed', 'yes', array(), true );
195 }
196 }
197 update_option( $this->result_option, 'completed', false );
198 delete_option( $this->skipped_step_option );
199 }
200
201 return new \WP_REST_Response( array( 'success' => true ), 200 );
202 }
203
204 /**
205 * Get onboarding completion status.
206 *
207 * @return \WP_REST_Response
208 */
209 public function get_status() {
210 $status = get_option( $this->status_option, 'no' );
211
212 return new \WP_REST_Response(
213 array(
214 'success' => true,
215 'status' => $status,
216 ),
217 200
218 );
219 }
220
221 /**
222 * Check if onboarding is completed.
223 *
224 * @return bool
225 */
226 public function is_completed() {
227 return 'yes' === get_option( $this->status_option, 'no' );
228 }
229
230 /**
231 * Redirect to onboarding on first activation.
232 */
233 public function maybe_redirect_to_onboarding() {
234 $redirect = get_transient( 'presto_player_activation_redirect' );
235
236 if ( ! $redirect ) {
237 return;
238 }
239
240 delete_transient( 'presto_player_activation_redirect' );
241
242 // Don't redirect if onboarding is already completed.
243 if ( $this->is_completed() ) {
244 return;
245 }
246
247 // Don't redirect on multisite network activation.
248 if ( is_network_admin() ) {
249 return;
250 }
251
252 // Don't redirect during AJAX or REST requests.
253 if ( wp_doing_ajax() || defined( 'REST_REQUEST' ) ) {
254 return;
255 }
256
257 // Don't redirect if activating multiple plugins.
258 if ( isset( $_GET['activate-multi'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
259 return;
260 }
261
262 wp_safe_redirect(
263 admin_url( 'admin.php?page=presto-dashboard&post_type=pp_video_block&tab=Onboarding' )
264 );
265 exit;
266 }
267
268 /**
269 * Save user info from onboarding and send lead to BSF Metrics Server.
270 *
271 * @param \WP_REST_Request $request Request object.
272 * @return \WP_REST_Response
273 */
274 public function save_user_info( $request ) {
275 $first_name = $request->get_param( 'firstName' );
276 $last_name = $request->get_param( 'lastName' );
277 $email = $request->get_param( 'email' );
278 $opt_in = (bool) $request->get_param( 'optIn' );
279
280 $option_key = 'presto_player_onboarding_user_info';
281 $user_info = array(
282 'first_name' => $first_name,
283 'last_name' => $last_name,
284 'email' => $email,
285 'opt_in' => $opt_in,
286 );
287
288 // Save locally with autoload=no — this option holds PII (email, name)
289 // and should not be loaded into every pageload's autoload cache.
290 if ( false === get_option( $option_key, false ) ) {
291 add_option( $option_key, $user_info, '', false );
292 } else {
293 update_option( $option_key, $user_info );
294 }
295
296 // Set usage tracking opt-in for BSF Analytics and send lead to BSF Metrics Server.
297 // Both depend on the user's opt-in choice — no PII leaves the site otherwise.
298 if ( $opt_in ) {
299 update_option( 'presto-player_usage_optin', 'yes' );
300 $this->generate_lead( $first_name, $last_name, $email );
301 }
302
303 return new \WP_REST_Response( array( 'success' => true ), 200 );
304 }
305
306 /**
307 * Send lead data to BSF Metrics Server.
308 *
309 * Follows the BSF ecosystem pattern for lead generation.
310 * See: Astra Sites class-astra-sites-astra-onboarding.php::generate_lead()
311 *
312 * @param string $first_name First name.
313 * @param string $last_name Last name.
314 * @param string $email Email address.
315 */
316 private function generate_lead( $first_name, $last_name, $email ) {
317 if ( empty( $email ) ) {
318 return;
319 }
320
321 $metrics_domain = trailingslashit(
322 defined( 'BSF_METRICS_REMOTE_URL' )
323 ? BSF_METRICS_REMOTE_URL
324 : apply_filters( 'nps_survey_api_domain', 'https://metrics.brainstormforce.com/' )
325 );
326 $lead_endpoint = $metrics_domain . 'wp-json/bsf-metrics-server/presto-player/v1/subscribe';
327
328 $lead_data = array(
329 'email' => $email,
330 'first_name' => $first_name,
331 'last_name' => $last_name,
332 );
333
334 wp_safe_remote_post(
335 $lead_endpoint,
336 array(
337 'method' => 'POST',
338 'body' => wp_json_encode( $lead_data ),
339 'headers' => array(
340 'Content-Type' => 'application/json',
341 ),
342 'timeout' => 5,
343 'blocking' => false,
344 )
345 );
346 }
347 }
348