PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 154
Lasso Lite – Affiliate Link Manager & Product Displays v154
158 157 155 156 154 153 152 151 150 149 148 trunk 0.9.9 104 105 106 107 108 109 110 111 112 113 114 115 All 57 releases
simple-urls / pages / class-ajax.php

class-ajax.php in Lasso Lite – Affiliate Link Manager & Product Displays 154, at pages/class-ajax.php

1,022 lines 28.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Lasso Lite General - Ajax.
4 *
5 * @package Pages
6 */
7
8 namespace LassoLite\Pages;
9
10 use LassoLite\Admin\Constant;
11
12 use LassoLite\Classes\Affiliate_Link;
13 use LassoLite\Classes\Estimate_Earning;
14 use LassoLite\Classes\Enum;
15 use LassoLite\Classes\Meta_Enum;
16 use LassoLite\Classes\Helper;
17 use LassoLite\Classes\Realtime_Click;
18 use LassoLite\Classes\Setting;
19 use LassoLite\Classes\SURL;
20
21 /**
22 * Lasso General - Ajax.
23 */
24 class Ajax {
25 /**
26 * Declare "Lasso Lite ajax requests" to WordPress.
27 */
28 public function register_hooks() {
29 add_action( 'wp_ajax_lasso_lite_add_a_new_link', array( $this, 'lasso_lite_add_a_new_link' ) );
30 add_action( 'wp_ajax_lasso_lite_get_single', array( $this, 'lasso_lite_get_single' ) );
31 add_action( 'wp_ajax_lasso_lite_get_shortcode_content', array( $this, 'lasso_lite_get_shortcode_content' ) );
32 add_action( 'wp_ajax_lasso_lite_get_display_html', array( $this, 'lasso_lite_get_display_html' ) );
33 add_action( 'wp_ajax_lasso_lite_get_link_quick_detail', array( $this, 'lasso_lite_get_link_quick_detail' ) );
34 add_action( 'wp_ajax_lasso_lite_save_link_quick_detail', array( $this, 'lasso_lite_save_link_quick_detail' ) );
35 add_action( 'wp_ajax_lasso_lite_get_click_snapshot', array( $this, 'lasso_lite_get_click_snapshot' ) );
36 add_action( 'wp_ajax_lasso_lite_get_link_issues_snapshot', array( $this, 'lasso_lite_get_link_issues_snapshot' ) );
37 add_action( 'wp_ajax_lasso_lite_get_links_issues_totals', array( $this, 'lasso_lite_get_links_issues_totals' ) );
38 add_action( 'wp_ajax_lasso_lite_get_earnings_estimate', array( $this, 'lasso_lite_get_earnings_estimate' ) );
39 add_action( 'wp_ajax_lasso_lite_get_external_signup_config', array( $this, 'lasso_lite_get_external_signup_config' ) );
40 add_action( 'wp_ajax_lasso_lite_external_signup', array( $this, 'lasso_lite_external_signup' ) );
41 add_action( 'wp_ajax_lasso_lite_external_signup_exchange', array( $this, 'lasso_lite_external_signup_exchange' ) );
42 add_action( 'wp_ajax_lasso_lite_get_setup_progress', array( $this, 'lasso_lite_get_setup_progress' ) );
43 add_action( 'wp_ajax_lasso_lite_save_support', array( $this, 'lasso_lite_save_support' ) );
44 add_action( 'wp_ajax_lasso_lite_save_lasso_account', array( $this, 'lasso_lite_save_lasso_account' ) );
45 add_action( 'wp_ajax_lasso_lite_skip_hub_connect', array( $this, 'lasso_lite_skip_hub_connect' ) );
46 add_action( 'wp_ajax_lasso_lite_save_onboarding_step', array( $this, 'lasso_lite_save_onboarding_step' ) );
47 add_action( 'wp_ajax_lasso_lite_check_existing_account', array( $this, 'lasso_lite_check_existing_account' ) );
48 add_action( 'wp_ajax_lasso_lite_review_snooze', array( $this, 'lasso_lite_review_snooze' ) );
49 add_action( 'wp_ajax_lasso_lite_disable_review', array( $this, 'lasso_lite_disable_review' ) );
50 add_action( 'wp_ajax_lasso_lite_dismiss_notice', array( $this, 'lasso_lite_dismiss_notice' ) );
51 add_action( 'wp_ajax_lasso_lite_disable_affiliate_promotions', array( $this, 'lasso_lite_disable_affiliate_promotions' ) );
52 add_action( 'wp_ajax_nopriv_lasso_lite_realtime_ingest', array( $this, 'lasso_lite_realtime_ingest' ) );
53 add_action( 'wp_ajax_lasso_lite_realtime_ingest', array( $this, 'lasso_lite_realtime_ingest' ) );
54 add_action( 'wp_ajax_lasso_lite_realtime_pull', array( $this, 'lasso_lite_realtime_pull' ) );
55 }
56
57 /**
58 * Add a new Lasso link
59 */
60 public function lasso_lite_add_a_new_link() {
61 Helper::verify_access_and_nonce( true );
62
63 $lasso_lite_affiliate_link = new Affiliate_Link();
64 return $lasso_lite_affiliate_link->add_a_new_link();
65 }
66
67 /**
68 * Get display html
69 */
70 public function lasso_lite_get_single() {
71 Helper::verify_access_and_nonce( true );
72
73 // phpcs:ignore
74 $post = Helper::POST();
75 $page = intval( $post['page'] ) ?? 1;
76 $limit = intval( $post['limit'] ) ?? 5;
77 $keyword = $post['keyword'] ?? '';
78 $list = SURL::get_list( $keyword, $page, $limit );
79 $output = array();
80
81 foreach ( $list as $surl ) {
82 $lasso_url = Affiliate_Link::get_lasso_url( $surl->get_id() );
83
84 $output[] = array(
85 'post_id' => $lasso_url->id,
86 'title' => $lasso_url->name,
87 'permalink' => $lasso_url->permalink,
88 'link_detail' => $lasso_url->edit_link,
89 'img_src' => $lasso_url->image_src,
90 'redirect' => $lasso_url->target_url,
91 'slug' => $lasso_url->slug,
92 );
93 }
94
95 $data['output'] = $output;
96 $data['total'] = SURL::total( $keyword );
97 $data['limit_on_page'] = $limit;
98 $data['page'] = $page;
99
100 wp_send_json_success( $data );
101 }
102
103 /**
104 * Get display html
105 */
106 public function lasso_lite_get_shortcode_content() {
107 Helper::verify_access_and_nonce( true );
108
109 // Prefer POST so long [lasso ref="…"] strings are not truncated by URL/proxy limits in the Elementor editor.
110 $shortcode = '';
111 $post_data = Helper::POST();
112 if ( is_array( $post_data ) && ! empty( $post_data['shortcode'] ) && is_string( $post_data['shortcode'] ) ) {
113 $shortcode = wp_unslash( $post_data['shortcode'] );
114 }
115 if ( '' === $shortcode ) {
116 $get_data = Helper::GET();
117 if ( is_array( $get_data ) && ! empty( $get_data['shortcode'] ) && is_string( $get_data['shortcode'] ) ) {
118 $shortcode = wp_unslash( $get_data['shortcode'] );
119 }
120 }
121 $html = '';
122
123 if ( '' !== $shortcode ) {
124 $html = do_shortcode( $shortcode );
125 }
126
127 wp_send_json_success(
128 array(
129 'shortcode' => $shortcode,
130 'html' => $html,
131 )
132 );
133 }
134
135 /**
136 * Get display html
137 */
138 public function lasso_lite_get_display_html() {
139 Helper::verify_access_and_nonce( true );
140 $html = Helper::get_display_modal_html();
141
142 wp_send_json_success(
143 array(
144 'html' => $html,
145 )
146 );
147 }
148
149 /**
150 * Get a new Lasso quick link
151 */
152 public function lasso_lite_get_link_quick_detail() {
153 Helper::verify_access_and_nonce( true );
154
155 $lasso_id = absint( Helper::POST()['post_id'] ?? 0 ); // phpcs:ignore
156
157 if ( $lasso_id <= 0 ) {
158 wp_send_json_error( 'No affiliate link to get.' );
159 }
160
161 $lasso_post = get_post( $lasso_id );
162 if ( $lasso_post && SIMPLE_URLS_SLUG === $lasso_post->post_type ) {
163 if ( ! current_user_can( 'edit_post', $lasso_id ) ) {
164 wp_send_json_error( 'Access denied.' );
165 }
166 }
167
168 $lasso_url = Affiliate_Link::get_lasso_url( $lasso_id, true );
169
170 wp_send_json_success(
171 array(
172 'success' => true,
173 'lasso_url' => $lasso_url,
174 )
175 );
176 }
177
178 /**
179 * Save a lasso link with basic data
180 */
181 public function lasso_lite_save_link_quick_detail() {
182 Helper::verify_access_and_nonce( true );
183
184 $data = Helper::POST(); // phpcs:ignore
185 $lasso_id = absint( $data['lasso_id'] ?? 0 ); // phpcs:ignore
186 $lasso_post = get_post( $lasso_id );
187 $thumbnail_id = intval( $data['thumbnail_id'] ?? 0 );
188
189 if ( ! $lasso_post || SIMPLE_URLS_SLUG !== $lasso_post->post_type ) {
190 wp_send_json_success(
191 array(
192 'success' => false,
193 'msg' => 'No affiliate link existed.',
194 )
195 );
196 }
197
198 if ( ! current_user_can( 'edit_post', $lasso_id ) ) {
199 wp_send_json_error( 'Access denied.' );
200 }
201
202 $lasso_lite_post = array(
203 'ID' => $lasso_post->ID,
204 'post_title' => $this->quick_detail_scalar_string( $data['affiliate_name'] ?? '' ),
205 'meta_input' => array(
206 Meta_Enum::LASSO_LITE_CUSTOM_THUMBNAIL => $this->quick_detail_scalar_url( $data['thumbnail_image_url'] ?? '' ),
207 Meta_Enum::BUY_BTN_TEXT => $this->quick_detail_scalar_string( $data['buy_btn_text'] ?? '' ),
208 Meta_Enum::DESCRIPTION => $this->quick_detail_scalar_html( $data['description'] ?? '' ),
209 Meta_Enum::BADGE_TEXT => $this->quick_detail_scalar_string( $data['badge_text'] ?? '' ),
210 ),
211 );
212
213 wp_update_post( $lasso_lite_post );
214
215 // ? update thumbnail
216 if ( $thumbnail_id > 0 ) {
217 set_post_thumbnail( $lasso_id, $thumbnail_id );
218 $image_url = esc_url_raw( wp_get_attachment_url( $thumbnail_id ) );
219 update_post_meta( $lasso_id, Meta_Enum::LASSO_LITE_CUSTOM_THUMBNAIL, $image_url );
220 } else {
221 delete_post_thumbnail( $lasso_id );
222 }
223
224 clean_post_cache( $lasso_id ); // ? clean post cache
225 wp_send_json_success(
226 array(
227 'success' => true,
228 )
229 );
230 }
231
232 /**
233 * Get click snapshot data
234 */
235 public function lasso_lite_get_click_snapshot() {
236 Helper::verify_access_and_nonce();
237
238 $post = Helper::POST(); // phpcs:ignore
239 $use_cache = '0' !== (string) ( $post['use_cache'] ?? '1' ); // phpcs:ignore
240
241 $transient_key = 'lasso_lite_click_snapshot_' . md5( \site_url() );
242 $cache_ttl = (int) \apply_filters( 'lasso_lite_click_snapshot_cache_ttl', 6 * \HOUR_IN_SECONDS );
243
244 if ( $use_cache ) {
245 $cached = \get_transient( $transient_key );
246 if ( false !== $cached ) {
247 wp_send_json_success( $cached );
248 }
249 }
250
251 $response = Helper::send_request(
252 'get',
253 Constant::LASSO_LINK . '/clicks/lasso-lite/monthly',
254 array(),
255 array(
256 'site-url' => \site_url(),
257 )
258 );
259
260 if ( empty( $response['response'] ) || $response['status_code'] >= 400 ) {
261 wp_send_json_error(
262 array(
263 'msg' => 'Unable to fetch click snapshot.',
264 'status_code' => $response['status_code'] ?? null,
265 'response' => $response['response'] ?? null,
266 )
267 );
268 }
269
270 if ( $use_cache ) {
271 \set_transient( $transient_key, $response['response'], $cache_ttl );
272 }
273
274 wp_send_json_success( $response['response'] );
275 }
276
277 /**
278 * Get link issues snapshot data
279 */
280 public function lasso_lite_get_link_issues_snapshot() {
281 Helper::verify_access_and_nonce();
282
283 $post = Helper::POST(); // phpcs:ignore
284 $use_cache = '0' !== (string) ( $post['use_cache'] ?? '1' ); // phpcs:ignore
285
286 $transient_key = 'lasso_lite_link_issues_snapshot_' . md5( \site_url() );
287 $cache_ttl = (int) \apply_filters( 'lasso_lite_link_issues_snapshot_cache_ttl', 6 * \HOUR_IN_SECONDS );
288
289 if ( $use_cache ) {
290 $cached = \get_transient( $transient_key );
291 if ( false !== $cached ) {
292 wp_send_json_success( $cached );
293 }
294 }
295
296 $response = Helper::send_request(
297 'get',
298 Constant::LASSO_LINK . '/lite/notify-snappshot',
299 array(),
300 array(
301 'site-url' => \site_url(),
302 )
303 );
304
305 if ( empty( $response['response'] ) || $response['status_code'] >= 400 ) {
306 wp_send_json_error(
307 array(
308 'msg' => 'Unable to fetch link issues snapshot.',
309 'status_code' => $response['status_code'] ?? null,
310 'response' => $response['response'] ?? null,
311 )
312 );
313 }
314
315 if ( $use_cache ) {
316 \set_transient( $transient_key, $response['response'], $cache_ttl );
317 }
318
319 wp_send_json_success( $response['response'] );
320 }
321
322 /**
323 * Get link issues totals data (broken/out-of-stock/opportunities) for dashboard pills.
324 */
325 public function lasso_lite_get_links_issues_totals() {
326 Helper::verify_access_and_nonce();
327
328 $post = Helper::POST(); // phpcs:ignore
329 $use_cache = '0' !== (string) ( $post['use_cache'] ?? '1' ); // phpcs:ignore
330
331 $transient_key = 'lasso_lite_links_issues_totals_' . md5( \site_url() );
332 $cache_ttl = (int) \apply_filters( 'lasso_lite_links_issues_totals_cache_ttl', 6 * \HOUR_IN_SECONDS );
333
334 if ( $use_cache ) {
335 $cached = \get_transient( $transient_key );
336 if ( false !== $cached ) {
337 wp_send_json_success( $cached );
338 }
339 }
340
341 $response = Helper::send_request(
342 'get',
343 Constant::LASSO_LINK . '/api/links/issues',
344 array(),
345 array(
346 'site-url' => \site_url(),
347 )
348 );
349
350 if ( empty( $response['response'] ) || $response['status_code'] >= 400 ) {
351 wp_send_json_error(
352 array(
353 'msg' => 'Unable to fetch link issues totals.',
354 'status_code' => $response['status_code'] ?? null,
355 'response' => $response['response'] ?? null,
356 )
357 );
358 }
359
360 if ( $use_cache ) {
361 \set_transient( $transient_key, $response['response'], $cache_ttl );
362 }
363
364 wp_send_json_success( $response['response'] );
365 }
366
367 /**
368 * Get estimated earnings for this site (used for weekly notification).
369 *
370 * Reads the weekly cron cache (POST /api/estimate-earning). Refreshes once on cache miss.
371 */
372 public function lasso_lite_get_earnings_estimate() {
373 Helper::verify_access_and_nonce();
374
375 if ( Estimate_Earning::has_linked_lasso_account() ) {
376 wp_send_json_error(
377 array(
378 'msg' => 'Linked Lasso account.',
379 )
380 );
381 }
382
383 $post = Helper::POST(); // phpcs:ignore
384 $use_cache = '0' !== (string) ( $post['use_cache'] ?? '1' ); // phpcs:ignore
385
386 if ( $use_cache ) {
387 $cached = Estimate_Earning::get_cached();
388 if ( null === $cached ) {
389 Estimate_Earning::fetch_and_cache();
390 $cached = Estimate_Earning::get_cached();
391 }
392 } else {
393 Estimate_Earning::fetch_and_cache();
394 $cached = Estimate_Earning::get_cached();
395 }
396
397 if ( null === $cached ) {
398 wp_send_json_error(
399 array(
400 'msg' => 'No earnings estimate available.',
401 )
402 );
403 }
404
405 $payout = (float) ( $cached['estimated_payout'] ?? 0 );
406 if ( $payout <= 0 ) {
407 wp_send_json_error(
408 array(
409 'msg' => 'No earnings to display.',
410 )
411 );
412 }
413
414 wp_send_json_success( Estimate_Earning::format_for_ui( $cached ) );
415 }
416
417 /**
418 * Get external signup config
419 */
420 public function lasso_lite_get_external_signup_config() {
421 Helper::verify_access_and_nonce();
422
423 $post = Helper::POST(); // phpcs:ignore
424 $callback_url = esc_url_raw( $post['callback_url'] ?? '' );
425 $source = sanitize_text_field( $post['source'] ?? 'lasso-lite' );
426
427 if ( empty( $callback_url ) ) {
428 wp_send_json_error(
429 array(
430 'msg' => 'Missing callback URL.',
431 )
432 );
433 }
434
435 $query_args = array(
436 'source' => $source,
437 'callback_url' => $callback_url,
438 );
439 $attribution = Helper::sanitize_signup_attribution( $post['attribution'] ?? null );
440 if ( $attribution ) {
441 $query_args['attribution'] = wp_json_encode( $attribution );
442 }
443
444 $request_url = add_query_arg(
445 $query_args,
446 Constant::get_lasso_hub_url() . '/api/account/external/config'
447 );
448
449 $response = Helper::send_request( 'get', $request_url );
450
451 if ( empty( $response['response'] ) || $response['status_code'] >= 400 ) {
452 $error_message = Helper::hub_request_error_message( $response, 'Unable to fetch signup config.' );
453
454 wp_send_json_error(
455 array(
456 'msg' => $error_message,
457 'error' => $error_message,
458 )
459 );
460 }
461
462 wp_send_json_success( $response['response'] );
463 }
464
465 /**
466 * External signup
467 */
468 public function lasso_lite_external_signup() {
469 Helper::verify_access_and_nonce();
470
471 $post = Helper::POST(); // phpcs:ignore
472 $email = sanitize_email( $post['email'] ?? '' );
473 $password = (string) ( $post['password'] ?? '' );
474 $source = sanitize_text_field( $post['source'] ?? 'lasso-lite' );
475
476 if ( empty( $email ) || empty( $password ) ) {
477 wp_send_json_error(
478 array(
479 'msg' => 'Missing required fields.',
480 )
481 );
482 }
483
484 $data = array(
485 'email' => $email,
486 'password' => $password,
487 'source' => $source,
488 );
489 $attribution = Helper::sanitize_signup_attribution( $post['attribution'] ?? null );
490 if ( $attribution ) {
491 $data['attribution'] = $attribution;
492 }
493 $headers = array(
494 'Content-Type' => 'application/json',
495 );
496 $signup_url = Constant::get_lasso_hub_url() . '/api/signup/external';
497
498 $response = Helper::send_request( 'post', $signup_url, $data, $headers );
499
500 if ( empty( $response['response'] ) || $response['status_code'] >= 400 ) {
501 $error_message = Helper::hub_request_error_message( $response, 'Signup failed.' );
502
503 wp_send_json_error(
504 array(
505 'msg' => $error_message,
506 'error' => $error_message,
507 'status_code' => $response['status_code'],
508 )
509 );
510 }
511
512 wp_send_json_success( $response['response'] );
513 }
514
515 /**
516 * External signup exchange
517 */
518 public function lasso_lite_external_signup_exchange() {
519 Helper::verify_access_and_nonce();
520
521 $post = Helper::POST(); // phpcs:ignore
522 $exchange_code = sanitize_text_field( $post['exchange_code'] ?? '' );
523
524 if ( empty( $exchange_code ) ) {
525 wp_send_json_error(
526 array(
527 'msg' => 'Missing exchange code.',
528 )
529 );
530 }
531
532 $data = array(
533 'exchange_code' => $exchange_code,
534 );
535 $headers = array(
536 'Content-Type' => 'application/json',
537 );
538 $exchange_url = Constant::get_lasso_hub_url() . '/api/signup/external/exchange';
539
540 $response = Helper::send_request( 'post', $exchange_url, $data, $headers );
541
542 if ( empty( $response['response'] ) || $response['status_code'] >= 400 ) {
543 $error_message = Helper::hub_request_error_message( $response, 'Exchange failed.' );
544
545 wp_send_json_error(
546 array(
547 'msg' => $error_message,
548 'error' => $error_message,
549 'status_code' => $response['status_code'],
550 )
551 );
552 }
553
554 wp_send_json_success( $response['response'] );
555 }
556
557 /**
558 * Get setup progress information
559 */
560 public function lasso_lite_get_setup_progress() {
561 wp_send_json_success( Helper::get_setup_progress_information() );
562 }
563
564 /**
565 * Do save support to open intercom chat
566 */
567 public function lasso_lite_save_support() {
568 Helper::verify_access_and_nonce();
569
570 Setting::save_support();
571 }
572
573 /**
574 * Save Lasso account credentials after signup
575 */
576 public function lasso_lite_save_lasso_account() {
577 Helper::verify_access_and_nonce();
578
579 $post = Helper::POST();
580 $email = sanitize_email( $post['email'] ?? '' );
581 $api_key = sanitize_text_field( $post['api_key'] ?? '' );
582 $user_id = intval( $post['user_id'] ?? 0 );
583
584 if ( empty( $email ) || empty( $api_key ) ) {
585 wp_send_json_error( array( 'msg' => 'Missing required fields' ) );
586 return;
587 }
588
589 Helper::update_option( Constant::LASSO_ACCOUNT_EMAIL, $email );
590 Helper::update_option( Constant::LASSO_ACCOUNT_API_KEY, $api_key );
591 Helper::update_option( Constant::LASSO_ACCOUNT_USER_ID, $user_id );
592 Setting::set_setting( Enum::EMAIL_SUPPORT, $email );
593
594 wp_send_json_success(
595 array(
596 'success' => true,
597 'msg' => 'Account saved successfully',
598 )
599 );
600 }
601
602 /**
603 * Record explicit Hub Connect skip during Lite onboarding.
604 */
605 public function lasso_lite_skip_hub_connect() {
606 Helper::verify_access_and_nonce();
607
608 Helper::mark_onboarding_welcome_complete();
609
610 wp_send_json_success(
611 array(
612 'success' => true,
613 )
614 );
615 }
616
617 /**
618 * Persist the current Lite onboarding tab while FTUE is in progress.
619 */
620 public function lasso_lite_save_onboarding_step() {
621 Helper::verify_access_and_nonce();
622
623 if ( intval( Helper::get_option( Enum::IS_VISITED_WELCOME_PAGE, 0 ) ) ) {
624 Helper::clear_onboarding_current_step();
625 wp_send_json_success( array( 'step' => '' ) );
626 return;
627 }
628
629 $step = sanitize_text_field( Helper::POST()['step'] ?? '' );
630 if ( '' === $step ) {
631 Helper::clear_onboarding_current_step();
632 wp_send_json_success( array( 'step' => '' ) );
633 return;
634 }
635
636 if ( ! Helper::is_valid_onboarding_step( $step ) ) {
637 wp_send_json_error( array( 'msg' => 'Invalid onboarding step' ) );
638 return;
639 }
640
641 if ( 'import' === $step && ! Helper::should_show_import_page() ) {
642 wp_send_json_error( array( 'msg' => 'Import step is not available' ) );
643 return;
644 }
645
646 Helper::save_onboarding_current_step( $step );
647 wp_send_json_success( array( 'step' => $step ) );
648 }
649
650 /**
651 * Check whether current site already has an account in Lasso (via lasso.link),
652 * and if yes sync WP options so the footer signup CTA won't show.
653 */
654 public function lasso_lite_check_existing_account() {
655 Helper::verify_access_and_nonce();
656
657 $is_connected_aff = intval( Helper::get_option( Constant::LASSO_OPTION_IS_CONNECTED_AFFILIATE, '0' ) );
658 $lasso_account_email = Helper::get_option( Constant::LASSO_ACCOUNT_EMAIL, '' );
659 $lasso_account_user_id = intval( Helper::get_option( Constant::LASSO_ACCOUNT_USER_ID, 0 ) );
660 $is_lasso_connected = ! empty( $lasso_account_email ) || $lasso_account_user_id > 0 || $is_connected_aff > 0;
661
662 if ( $is_lasso_connected ) {
663 wp_send_json_success(
664 array(
665 'connected' => true,
666 'synced' => false,
667 )
668 );
669 }
670
671 $lookup = Helper::send_request(
672 'get',
673 rtrim( Constant::LASSO_LINK, '/' ) . '/account/existing',
674 array(),
675 array(
676 'site-url' => \site_url(),
677 )
678 );
679
680 $lookup_body = $lookup['response'] ?? null;
681 if ( empty( $lookup_body ) || ( $lookup['status_code'] ?? 500 ) >= 400 || empty( $lookup_body->data ) ) {
682 wp_send_json_success(
683 array(
684 'connected' => false,
685 'synced' => false,
686 )
687 );
688 }
689
690 if ( empty( $lookup_body->data->exists ) ) {
691 wp_send_json_success(
692 array(
693 'connected' => false,
694 'synced' => false,
695 'exists' => false,
696 )
697 );
698 }
699
700 $email = sanitize_email( $lookup_body->data->email ?? '' );
701 $user_id = intval( $lookup_body->data->user_id ?? 0 );
702 if ( empty( $email ) || $user_id <= 0 ) {
703 wp_send_json_success(
704 array(
705 'connected' => false,
706 'synced' => false,
707 'exists' => true,
708 )
709 );
710 }
711
712 Helper::update_option( Constant::LASSO_ACCOUNT_EMAIL, $email );
713 // Do not auto-sync user_id here — orphan earnings banner/cron key off user_id=0 only.
714 Setting::set_setting( Enum::EMAIL_SUPPORT, $email );
715
716 wp_send_json_success(
717 array(
718 'connected' => true,
719 'synced' => true,
720 'email' => $email,
721 'user_id' => $user_id,
722 )
723 );
724 }
725
726 /**
727 * Do save support to open intercom chat
728 */
729 public function lasso_lite_review_snooze() {
730 Helper::verify_access_and_nonce();
731
732 $link_count = SURL::total();
733
734 Helper::update_option( Constant::LASSO_OPTION_REVIEW_SNOOZE, '1' );
735 Helper::update_option( Constant::LASSO_OPTION_REVIEW_LINK_COUNT, $link_count );
736
737 wp_send_json_success(
738 array(
739 'status' => 1,
740 )
741 );
742 }
743
744 /**
745 * Disable Review notification
746 */
747 public function lasso_lite_disable_review() {
748 Helper::verify_access_and_nonce();
749
750 Helper::update_option( Constant::LASSO_OPTION_REVIEW_ALLOW, '0' );
751
752 wp_send_json_success(
753 array(
754 'status' => 1,
755 )
756 );
757 }
758
759 /**
760 * Dismiss Performance promotion in the dashboard
761 */
762 public function lasso_lite_dismiss_notice() {
763 Helper::verify_access_and_nonce();
764 $option_name = Helper::POST()['option_name'] ?? Constant::LASSO_OPTION_DISMISS_PERFORMANCE_NOTICE; // phpcs:ignore
765
766 if ( Constant::LASSO_OPTION_AMAZON_CREDENTIALS_NOTICE_DISMISSED === $option_name ) {
767 $dismissed = Helper::cast_to_boolean(
768 Helper::get_option( Constant::LASSO_OPTION_AMAZON_CREDENTIALS_NOTICE_DISMISSED, '0' )
769 );
770 if ( $dismissed ) {
771 Helper::update_option( Constant::LASSO_OPTION_AMAZON_CREDENTIALS_NOTICE_DISMISSED_FINAL, '1' );
772 } else {
773 Helper::update_option( Constant::LASSO_OPTION_AMAZON_CREDENTIALS_NOTICE_DISMISSED, '1' );
774 }
775
776 wp_send_json_success(
777 array(
778 'status' => 1,
779 'show_amazon_credentials_notice' => Helper::show_amazon_credentials_notice(),
780 )
781 );
782
783 return;
784 }
785
786 if ( ! in_array( $option_name, array( Constant::LASSO_OPTION_DISMISS_PERFORMANCE_NOTICE, Constant::LASSO_OPTION_DISMISS_PROMOTIONS ), true ) ) {
787 wp_send_json_error( 'Invalid option name.' );
788 }
789
790 Helper::update_option( $option_name, '1' );
791
792 wp_send_json_success(
793 array(
794 'status' => 1,
795 )
796 );
797 }
798
799 /**
800 * Disable Affiliate+ Promotions notification
801 */
802 public function lasso_lite_disable_affiliate_promotions() {
803 Helper::verify_access_and_nonce();
804
805 Helper::update_option( Constant::LASSO_OPTION_AFFILIATE_PROMOTIONS, '0' );
806
807 wp_send_json_success(
808 array(
809 'status' => 1,
810 )
811 );
812 }
813
814 /**
815 * Public ingest for front-end click beacon (HMAC + Sig).
816 */
817 public function lasso_lite_realtime_ingest() {
818 if ( ! Realtime_Click::is_ingest_enabled() ) {
819 wp_send_json_error( array( 'message' => 'Realtime ingest disabled.' ), 403 );
820 }
821
822 if ( ! Realtime_Click::rate_limit_allow() ) {
823 wp_send_json_error( array( 'message' => 'Rate limited.' ), 429 );
824 }
825
826 $post = Helper::POST();
827 $body = null;
828 $maybe = $post['channel_id'] ?? null;
829 if ( null !== $maybe && is_scalar( $maybe ) && ! is_bool( $maybe ) && '' !== (string) $maybe ) {
830 $body = array(
831 'channel_id' => $post['channel_id'] ?? '',
832 'ts' => $post['ts'] ?? null,
833 'lid' => $post['lid'] ?? null,
834 'type' => $post['type'] ?? null,
835 'sig' => $post['sig'] ?? null,
836 'beacon_token' => $post['beacon_token'] ?? null,
837 'beacon_bucket' => $post['beacon_bucket'] ?? null,
838 );
839 } else {
840 $raw = file_get_contents( 'php://input' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
841 $body = json_decode( $raw, true );
842 }
843 if ( ! is_array( $body ) ) {
844 wp_send_json_error( array( 'message' => 'Invalid body.' ), 400 );
845 }
846
847 $body = $this->normalize_realtime_ingest_body( $body );
848
849 $channel_id = $body['channel_id'];
850 $ts = $body['ts'];
851 $lid = $body['lid'];
852 $type = $body['type'];
853 $sig = $body['sig'];
854 $b_token = $body['beacon_token'];
855 $b_bucket = $body['beacon_bucket'];
856
857 $now = time();
858 $ok = false;
859 if ( abs( $now - $ts ) > Realtime_Click::TS_SKEW ) {
860 wp_send_json_error( array( 'message' => 'Stale timestamp.' ), 403 );
861 }
862 if ( '' !== $sig ) {
863 $ok = Realtime_Click::verify_signature( $channel_id, $ts, $lid, $type, $sig );
864 } elseif ( '' !== $b_token ) {
865 $ok = Realtime_Click::verify_beacon_token( $channel_id, $b_bucket, $b_token );
866 }
867 if ( ! $ok ) {
868 wp_send_json_error( array( 'message' => 'Invalid authentication.' ), 403 );
869 }
870
871 $seq = Realtime_Click::enqueue_event( $lid, $type );
872 wp_send_json_success( array( 'seq' => $seq ) );
873 }
874
875 /**
876 * Admin pull for queued click events (poll transport).
877 */
878 public function lasso_lite_realtime_pull() {
879 Helper::verify_access_and_nonce();
880
881 if ( ! Realtime_Click::is_ingest_enabled() ) {
882 wp_send_json_error( array( 'message' => 'Disabled.' ), 403 );
883 }
884
885 $post = Helper::POST();
886 $since = isset( $post['since'] ) ? absint( $post['since'] ) : 0;
887 $data = Realtime_Click::pull_events_since( $since );
888
889 wp_send_json_success( $data );
890 }
891
892 /**
893 * Coerce realtime ingest body to scalar strings/ints and sanitize text fields.
894 *
895 * @param array $body Raw body from POST subset or JSON decode.
896 * @return array{
897 * channel_id: string,
898 * ts: int,
899 * lid: int,
900 * type: string,
901 * sig: string,
902 * beacon_token: string,
903 * beacon_bucket: int
904 * }
905 */
906 private function normalize_realtime_ingest_body( array $body ) {
907 return array(
908 'channel_id' => $this->realtime_ingest_scalar_string( $body['channel_id'] ?? null ),
909 'ts' => $this->realtime_ingest_scalar_int( $body['ts'] ?? null ),
910 'lid' => $this->realtime_ingest_scalar_int( $body['lid'] ?? null ),
911 'type' => $this->realtime_ingest_scalar_string( $body['type'] ?? null ),
912 'sig' => $this->realtime_ingest_scalar_string( $body['sig'] ?? null ),
913 'beacon_token' => $this->realtime_ingest_beacon_token( $body ),
914 'beacon_bucket' => $this->realtime_ingest_beacon_bucket( $body ),
915 );
916 }
917
918 /**
919 * Quick-detail text field: scalar only; arrays/objects become empty string.
920 *
921 * @param mixed $value Raw value.
922 * @return string
923 */
924 private function quick_detail_scalar_string( $value ) {
925 if ( null === $value || is_bool( $value ) || ! is_scalar( $value ) ) {
926 return '';
927 }
928
929 return sanitize_text_field( wp_unslash( (string) $value ) );
930 }
931
932 /**
933 * Quick-detail URL field: scalar only; arrays/objects become empty string.
934 *
935 * @param mixed $value Raw value.
936 * @return string
937 */
938 private function quick_detail_scalar_url( $value ) {
939 if ( null === $value || is_bool( $value ) || ! is_scalar( $value ) ) {
940 return '';
941 }
942
943 return esc_url_raw( wp_unslash( (string) $value ) );
944 }
945
946 /**
947 * Quick-detail HTML field: scalar only; arrays/objects become empty string.
948 *
949 * @param mixed $value Raw value.
950 * @return string
951 */
952 private function quick_detail_scalar_html( $value ) {
953 if ( null === $value || is_bool( $value ) || ! is_scalar( $value ) ) {
954 return '';
955 }
956
957 return wp_kses_post( wp_unslash( (string) $value ) );
958 }
959
960 /**
961 * Ingest string field: scalar only; arrays/objects become empty string.
962 *
963 * @param mixed $value Raw value.
964 * @return string
965 */
966 private function realtime_ingest_scalar_string( $value ) {
967 if ( null === $value || is_bool( $value ) || ! is_scalar( $value ) ) {
968 return '';
969 }
970
971 return sanitize_text_field( wp_unslash( (string) $value ) );
972 }
973
974 /**
975 * Ingest int field: numeric scalars only.
976 *
977 * @param mixed $value Raw value.
978 * @return int
979 */
980 private function realtime_ingest_scalar_int( $value ) {
981 if ( null === $value || is_bool( $value ) || ! is_scalar( $value ) || ! is_numeric( $value ) ) {
982 return 0;
983 }
984
985 return (int) $value;
986 }
987
988 /**
989 * Beacon token from snake_case or camelCase JSON.
990 *
991 * @param array $body Raw body.
992 * @return string
993 */
994 private function realtime_ingest_beacon_token( array $body ) {
995 if ( isset( $body['beacon_token'] ) ) {
996 return $this->realtime_ingest_scalar_string( $body['beacon_token'] );
997 }
998 if ( isset( $body['beaconToken'] ) ) {
999 return $this->realtime_ingest_scalar_string( $body['beaconToken'] );
1000 }
1001
1002 return '';
1003 }
1004
1005 /**
1006 * Beacon bucket from snake_case or camelCase JSON.
1007 *
1008 * @param array $body Raw body.
1009 * @return int
1010 */
1011 private function realtime_ingest_beacon_bucket( array $body ) {
1012 if ( isset( $body['beacon_bucket'] ) ) {
1013 return $this->realtime_ingest_scalar_int( $body['beacon_bucket'] );
1014 }
1015 if ( isset( $body['beaconBucket'] ) ) {
1016 return $this->realtime_ingest_scalar_int( $body['beaconBucket'] );
1017 }
1018
1019 return 0;
1020 }
1021 }
1022