| @@ -442,9 +442,9 @@ | ||
| 442 | 442 | $setup_amz_tracking_id = boolval( get_option( Enum::SETUP_AMZ_TRACKING_ID ) ) ? 15 : 0; |
| 443 | 443 | $follow_on_twitter = boolval( get_option( Enum::FOLLOW_ON_TWITTER ) ) ? 10 : 0; |
| 444 | 444 | $share_on_twitter = boolval( get_option( Enum::SHARE_ON_TWITTER ) ) ? 10 : 0; |
| 445 | 445 | $leave_a_review = boolval( get_option( Enum::LEAVE_A_REVIEW ) ) ? 5 : 0; |
| 446 | - $is_show_review_note = ! $leave_a_review && $total_links >= 20 && $enable_support && $setup_amz_tracking_id && $follow_on_twitter && $share_on_twitter ? 1 : 0; | |
| 446 | + $is_show_review_note = ! $leave_a_review && $total_links >= 5 && $enable_support && $setup_amz_tracking_id && $follow_on_twitter && $share_on_twitter ? 1 : 0; | |
| 447 | 447 | $progress = $enable_support + $setup_amz_tracking_id + $follow_on_twitter + $share_on_twitter + ( $links * 2 ) + $leave_a_review; |
| 448 | 448 | $progress = $progress ? $progress / 100 : 0; |
| 449 | 449 | $open_modal_add_link = $links < 20 ? 'btn-add-20-links' : ''; |
| 450 | 450 | |
| @@ -469,8 +469,74 @@ | ||
| 469 | 469 | return $data; |
| 470 | 470 | } |
| 471 | 471 | |
| 472 | 472 | /** |
| 473 | + * Whitelist landing-cookie attribution keys for Lite plugin signup (#788). | |
| 474 | + * | |
| 475 | + * @param mixed $raw POST attribution object or JSON string. | |
| 476 | + * @return array|null Sanitized payload or null when no signal. | |
| 477 | + */ | |
| 478 | + public static function sanitize_signup_attribution( $raw ) { | |
| 479 | + $allowed_keys = array( | |
| 480 | + 'url', | |
| 481 | + 'ref', | |
| 482 | + 'utm_source', | |
| 483 | + 'utm_medium', | |
| 484 | + 'utm_campaign', | |
| 485 | + 'utm_content', | |
| 486 | + 'ref_code', | |
| 487 | + 'dt', | |
| 488 | + ); | |
| 489 | + $signal_keys = array( | |
| 490 | + 'utm_source', | |
| 491 | + 'utm_medium', | |
| 492 | + 'utm_campaign', | |
| 493 | + 'utm_content', | |
| 494 | + 'ref_code', | |
| 495 | + ); | |
| 496 | + | |
| 497 | + if ( is_string( $raw ) ) { | |
| 498 | + $raw = json_decode( $raw, true ); | |
| 499 | + } | |
| 500 | + if ( ! is_array( $raw ) || empty( $raw ) ) { | |
| 501 | + return null; | |
| 502 | + } | |
| 503 | + | |
| 504 | + $payload = array(); | |
| 505 | + foreach ( $allowed_keys as $key ) { | |
| 506 | + if ( ! isset( $raw[ $key ] ) || ! is_scalar( $raw[ $key ] ) ) { | |
| 507 | + continue; | |
| 508 | + } | |
| 509 | + $text = trim( (string) $raw[ $key ] ); | |
| 510 | + if ( '' === $text ) { | |
| 511 | + continue; | |
| 512 | + } | |
| 513 | + if ( in_array( $key, array( 'url', 'ref' ), true ) ) { | |
| 514 | + $payload[ $key ] = substr( $text, 0, 2048 ); | |
| 515 | + } else { | |
| 516 | + $payload[ $key ] = substr( $text, 0, 500 ); | |
| 517 | + } | |
| 518 | + } | |
| 519 | + | |
| 520 | + if ( empty( $payload ) ) { | |
| 521 | + return null; | |
| 522 | + } | |
| 523 | + | |
| 524 | + $has_signal = false; | |
| 525 | + foreach ( $signal_keys as $key ) { | |
| 526 | + if ( ! empty( $payload[ $key ] ) ) { | |
| 527 | + $has_signal = true; | |
| 528 | + break; | |
| 529 | + } | |
| 530 | + } | |
| 531 | + if ( ! $has_signal && empty( $payload['url'] ) ) { | |
| 532 | + return null; | |
| 533 | + } | |
| 534 | + | |
| 535 | + return $payload; | |
| 536 | + } | |
| 537 | + | |
| 538 | + /** | |
| 473 | 539 | * Send request |
| 474 | 540 | * |
| 475 | 541 | * @param string $method Method (get or post). Default to get. |
| 476 | 542 | * @param string $url URL. Default to empty. |
| @@ -1256,8 +1322,87 @@ | ||
| 1256 | 1322 | return ! empty( ( new Lasso_DB() )->get_import_plugins( true ) ) ? true : false; |
| 1257 | 1323 | } |
| 1258 | 1324 | |
| 1259 | 1325 | /** |
| 1326 | + * Ordered onboarding step ids (tab-item data-step values). | |
| 1327 | + * | |
| 1328 | + * @return string[] | |
| 1329 | + */ | |
| 1330 | + public static function get_onboarding_step_ids() { | |
| 1331 | + return array( 'welcome', 'display', 'amazon', 'connect-lasso', 'import' ); | |
| 1332 | + } | |
| 1333 | + | |
| 1334 | + /** | |
| 1335 | + * @param string $step Step id. | |
| 1336 | + * @return bool | |
| 1337 | + */ | |
| 1338 | + public static function is_valid_onboarding_step( $step ) { | |
| 1339 | + return in_array( $step, self::get_onboarding_step_ids(), true ); | |
| 1340 | + } | |
| 1341 | + | |
| 1342 | + /** | |
| 1343 | + * Last saved onboarding tab for in-progress FTUE. | |
| 1344 | + * | |
| 1345 | + * @param bool $include_import Whether the import step is available for this install. | |
| 1346 | + * @return string | |
| 1347 | + */ | |
| 1348 | + public static function get_onboarding_current_step( $include_import = true ) { | |
| 1349 | + $step = (string) self::get_option( Enum::ONBOARDING_CURRENT_STEP, '' ); | |
| 1350 | + if ( ! self::is_valid_onboarding_step( $step ) ) { | |
| 1351 | + return 'welcome'; | |
| 1352 | + } | |
| 1353 | + if ( 'import' === $step && ! $include_import ) { | |
| 1354 | + return 'connect-lasso'; | |
| 1355 | + } | |
| 1356 | + return $step; | |
| 1357 | + } | |
| 1358 | + | |
| 1359 | + /** | |
| 1360 | + * @param string $step Step id. | |
| 1361 | + * @return bool | |
| 1362 | + */ | |
| 1363 | + public static function save_onboarding_current_step( $step ) { | |
| 1364 | + if ( ! self::is_valid_onboarding_step( $step ) ) { | |
| 1365 | + return false; | |
| 1366 | + } | |
| 1367 | + return self::update_option( Enum::ONBOARDING_CURRENT_STEP, $step ); | |
| 1368 | + } | |
| 1369 | + | |
| 1370 | + /** | |
| 1371 | + * @return bool | |
| 1372 | + */ | |
| 1373 | + public static function clear_onboarding_current_step() { | |
| 1374 | + return self::update_option( Enum::ONBOARDING_CURRENT_STEP, '' ); | |
| 1375 | + } | |
| 1376 | + | |
| 1377 | + /** | |
| 1378 | + * FTUE gate complete: stop redirecting to onboarding and drop saved step. | |
| 1379 | + * | |
| 1380 | + * Cleared after first link creation or an explicit Hub Connect skip. | |
| 1381 | + * | |
| 1382 | + * @return void | |
| 1383 | + */ | |
| 1384 | + public static function mark_onboarding_welcome_complete() { | |
| 1385 | + self::update_option( Enum::IS_VISITED_WELCOME_PAGE, 1 ); | |
| 1386 | + self::clear_onboarding_current_step(); | |
| 1387 | + } | |
| 1388 | + | |
| 1389 | + /** | |
| 1390 | + * Reset FTUE onboarding state for QA (`reset-onboarding=1`). | |
| 1391 | + * | |
| 1392 | + * @return void | |
| 1393 | + */ | |
| 1394 | + public static function reset_onboarding_for_testing() { | |
| 1395 | + self::update_option( Enum::IS_VISITED_WELCOME_PAGE, 0 ); | |
| 1396 | + self::clear_onboarding_current_step(); | |
| 1397 | + update_option( Enum::LASSO_LITE_ACTIVE, 1 ); | |
| 1398 | + self::update_option( Constant::LASSO_ACCOUNT_EMAIL, '' ); | |
| 1399 | + self::update_option( Constant::LASSO_ACCOUNT_API_KEY, '' ); | |
| 1400 | + self::update_option( Constant::LASSO_ACCOUNT_USER_ID, 0 ); | |
| 1401 | + self::update_option( Constant::LASSO_OPTION_IS_CONNECTED_AFFILIATE, '0' ); | |
| 1402 | + } | |
| 1403 | + | |
| 1404 | + /** | |
| 1260 | 1405 | * Get brag icon |
| 1261 | 1406 | * |
| 1262 | 1407 | * @param bool $force_to_show Force to show the brag. Default to false. |
| 1263 | 1408 | */ |
| @@ -1446,10 +1591,11 @@ | ||
| 1446 | 1591 | $lasso_review_allow = self::cast_to_boolean( self::get_option( Constant::LASSO_OPTION_REVIEW_ALLOW, '1' ) ); |
| 1447 | 1592 | $lasso_review_snooze = self::cast_to_boolean( self::get_option( Constant::LASSO_OPTION_REVIEW_SNOOZE, '0' ) ); |
| 1448 | 1593 | $lasso_review_link_count = intval( self::get_option( Constant::LASSO_OPTION_REVIEW_LINK_COUNT, $link_count ) ); |
| 1449 | 1594 | |
| 1450 | - $show = ! $lasso_review_snooze && $link_count >= 20; | |
| 1451 | - $snooze_but_show = $lasso_review_snooze && $link_count - $lasso_review_link_count >= 20; | |
| 1595 | + // Ask after early success (enough links to be real usage), not after a large catalog. | |
| 1596 | + $show = ! $lasso_review_snooze && $link_count >= 5; | |
| 1597 | + $snooze_but_show = $lasso_review_snooze && $link_count - $lasso_review_link_count >= 5; | |
| 1452 | 1598 | |
| 1453 | 1599 | if ( ! $lasso_review_allow ) { |
| 1454 | 1600 | return false; |
| 1455 | 1601 | } |
| @@ -1626,8 +1772,15 @@ | ||
| 1626 | 1772 | $query['refresh_image'] = 1; |
| 1627 | 1773 | } |
| 1628 | 1774 | |
| 1629 | 1775 | $request_url = Constant::LASSO_LINK . '/link/status/?' . http_build_query( $query, '', '&', PHP_QUERY_RFC3986 ); |
| 1776 | + if ( ! $is_lasso_save && defined( 'DOING_CRON' ) && DOING_CRON && ! Cron::should_send_scheduled_data_request( $url ) ) { | |
| 1777 | + return $get_res ? array( | |
| 1778 | + 'status_code' => 200, | |
| 1779 | + 'response' => array(), | |
| 1780 | + ) : 200; | |
| 1781 | + } | |
| 1782 | + Cron::maybe_pace_background_request( $url, $is_lasso_save ); | |
| 1630 | 1783 | $res = self::send_request( 'get', $request_url, array(), $headers ); |
| 1631 | 1784 | |
| 1632 | 1785 | // phpcs:ignore |
| 1633 | 1786 | // $res = self::send_request( 'get', LASSO_LINK . '/link/status/?' . $encrypted_base64, array(), $headers ); |
| @@ -1705,12 +1858,15 @@ | ||
| 1705 | 1858 | return $query; |
| 1706 | 1859 | } |
| 1707 | 1860 | |
| 1708 | 1861 | /** |
| 1709 | - * Remove all the script code from the HTML. | |
| 1710 | - * Remove script tags and event attributes (e.g., onload, onsubmit, etc.) | |
| 1862 | + * Sanitize HTML for safe display (formerly regex-based script strip). | |
| 1863 | + * Now delegates to wp_kses_post so event handlers and javascript: URLs are removed. | |
| 1711 | 1864 | * |
| 1712 | - * @param string $html HTML code. | |
| 1865 | + * Falsy input (null, false, empty string) is returned unchanged. | |
| 1866 | + * | |
| 1867 | + * @param string|null|false $html HTML code. | |
| 1868 | + * @return string|null|false Sanitized HTML, or the original falsy value. | |
| 1713 | 1869 | */ |
| 1714 | 1870 | public static function sanitize_script( $html ) { |
| 1715 | 1871 | if ( ! $html ) { |
| 1716 | 1872 | return $html; |
| @@ -1715,15 +1871,9 @@ | ||
| 1715 | 1871 | if ( ! $html ) { |
| 1716 | 1872 | return $html; |
| 1717 | 1873 | } |
| 1718 | 1874 | |
| 1719 | - // ? Remove <script> tags and their variations | |
| 1720 | - $html = preg_replace( '/<script\b[^>]*>.*?<\/script\s*>/is', '', $html ); | |
| 1721 | - | |
| 1722 | - // ? Remove event attributes (e.g., onload, onsubmit, etc.) and their values | |
| 1723 | - $html = preg_replace( '/\s+on\w+\s*=\s*["\'][^"\']*["\']/', ' ', $html ); | |
| 1724 | - | |
| 1725 | - return $html; | |
| 1875 | + return wp_kses_post( (string) $html ); | |
| 1726 | 1876 | } |
| 1727 | 1877 | |
| 1728 | 1878 | /** |
| 1729 | 1879 | * Verify access and nonce, then return wp_send_json_error if unverified. |
| @@ -1894,13 +2044,23 @@ | ||
| 1894 | 2044 | $use_bls = true; |
| 1895 | 2045 | } |
| 1896 | 2046 | |
| 1897 | 2047 | if ( is_wp_error( $res ) || $use_bls || 403 === $status_code ) { |
| 2048 | + $allow_bls = $is_lasso_save | |
| 2049 | + || ! defined( 'DOING_CRON' ) | |
| 2050 | + || ! DOING_CRON | |
| 2051 | + || Cron::should_send_scheduled_data_request( $url ); | |
| 2052 | + if ( ! $allow_bls ) { | |
| 2053 | + $result = $get_page_title ? array( $url, $page_title ) : $url; | |
| 2054 | + Cache_Per_Process::get_instance()->set_cache( $cache_prefix . md5( $url ) . $get_page_title, $result ); | |
| 2055 | + return $result; | |
| 2056 | + } | |
| 1898 | 2057 | $headers = self::get_headers(); |
| 1899 | 2058 | $data = array( |
| 1900 | 2059 | 'url' => $url, |
| 1901 | 2060 | ); |
| 1902 | 2061 | $encrypted_base64 = http_build_query( $data ); |
| 2062 | + Cron::maybe_pace_background_request( $url, $is_lasso_save ); | |
| 1903 | 2063 | $res = self::send_request( 'get', Constant::LASSO_LINK . '/link/final-url/?' . $encrypted_base64, array(), $headers ); |
| 1904 | 2064 | |
| 1905 | 2065 | $bls_response = ( isset( $res['response'] ) && is_object( $res['response'] ) ) ? $res['response'] : null; |
| 1906 | 2066 | $final_url = ( null !== $bls_response ) ? ( $bls_response->finalUrl ?? $url ) : $url; |