PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 150
Lasso Lite – Affiliate Link Manager & Product Displays v150
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 150, at pages/class-ajax.php

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