PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 153
Lasso Lite – Affiliate Link Manager & Product Displays v153
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 116 All 56 releases
simple-urls / classes / class-helper.php

class-helper.php in Lasso Lite – Affiliate Link Manager & Product Displays 153, at classes/class-helper.php

2,071 lines 64.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Declare class Helper
4 *
5 * @package Helper
6 */
7
8 namespace LassoLite\Classes;
9
10 use LassoLite\Classes\Helper\Url_Format;
11
12 use LassoLite\Admin\Constant;
13
14 use LassoLite\Classes\Affiliate_Link;
15 use LassoLite\Classes\Amazon_Api;
16 use LassoLite\Classes\Cache_Per_Process;
17 use LassoLite\Classes\Enum;
18 use LassoLite\Classes\Import;
19 use LassoLite\Classes\License;
20 use LassoLite\Classes\Setting;
21 use LassoLite\Classes\SURL;
22
23 use LassoLite\Models\Model;
24 use LassoLite\Models\Url_Details;
25
26
27 use Exception;
28
29 /**
30 * Lasso_Helper
31 */
32 class Helper {
33 /**
34 * User agent
35 *
36 * @var string $user_agent
37 */
38 public static $user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0';
39
40 /**
41 * GET PHP POST
42 *
43 * @return array|string
44 */
45 public static function POST() { // phpcs:ignore
46 $post = wp_unslash( $_POST ); // phpcs:ignore
47
48 return $post;
49 }
50
51 /**
52 * GET PHP GET
53 *
54 * @return array|string
55 */
56 public static function GET() { // phpcs:ignore
57 $get = wp_unslash( $_GET ); // phpcs:ignore
58
59 return $get;
60 }
61
62 /**
63 * Include variables
64 *
65 * @param string $file_path File path.
66 * @param array $variables List of variables.
67 * @param bool $output_ajax_html Output a ajax html string or not.
68 */
69 public static function include_with_variables( $file_path, $variables = array(), $output_ajax_html = true ) {
70 $output = null;
71 if ( file_exists( $file_path ) ) {
72 extract( $variables ); // phpcs:ignore
73 if ( $output_ajax_html ) {
74 ob_start();
75 include $file_path;
76 $output = ob_get_clean();
77 return $output;
78 } else {
79 require $file_path;
80 }
81 }
82
83 return $output;
84 }
85
86 /**
87 * Get path to views folder
88 *
89 * @return string
90 */
91 public static function get_path_views_folder() {
92 return SIMPLE_URLS_DIR . 'admin/views/';
93 }
94
95 /**
96 * Enqueue a Lasso script.
97 *
98 * @param string $handle Name of the script. Should be unique.
99 * @param string $file_name Lasso script file name.
100 * @param array $deps Optional. An array of registered script handles this script depends on. Default empty array.
101 * @param bool $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>. Default 'false'.
102 */
103 public static function enqueue_script( $handle, $file_name, $deps = array(), $in_footer = false ) {
104 $handle = SIMPLE_URLS_SLUG . '-' . $handle;
105 $file_path = SIMPLE_URLS_DIR . '/admin/assets/js/' . $file_name;
106
107 if ( file_exists( $file_path ) ) {
108 $src = SIMPLE_URLS_URL . '/admin/assets/js/' . $file_name;
109 $ver = strval( @filemtime( $file_path ) ); // phpcs:ignore
110
111 wp_enqueue_script( $handle, $src, $deps, $ver, $in_footer );
112 }
113 }
114
115 /**
116 * Enqueue a Lasso CSS stylesheet.
117 *
118 * @param string $handle Name of the stylesheet. Should be unique.
119 * @param string $file_name Lasso stylesheet file name.
120 * @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
121 * @param string $media Optional. The media for which this stylesheet has been defined.
122 * Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
123 * '(orientation: portrait)' and '(max-width: 640px)'.
124 * @param bool $apply_handle_prefix Is apply prefix for handle. Default to true.
125 */
126 public static function enqueue_style( $handle, $file_name, $deps = array(), $media = 'all', $apply_handle_prefix = true ) {
127 $handle = $apply_handle_prefix ? SIMPLE_URLS_SLUG . '-' . $handle : $handle;
128 $file_path = SIMPLE_URLS_DIR . '/admin/assets/css/' . $file_name;
129
130 if ( file_exists( $file_path ) ) {
131 $src = SIMPLE_URLS_URL . '/admin/assets/css/' . $file_name;
132 $ver = strval( @filemtime( $file_path ) ); // phpcs:ignore
133
134 wp_enqueue_style( $handle, $src, $deps, $ver, $media );
135 }
136 }
137
138 /**
139 * Get list page
140 *
141 * @return Page[]
142 */
143 public static function available_pages() {
144 $pages[ Enum::PAGE_DASHBOARD ] = new Page( 'Dashboard', Enum::PAGE_DASHBOARD, 'dashboard/index.php' );
145 $pages[ Enum::PAGE_OPPORTUNITIES ] = new Page( 'Opportunities', Enum::PAGE_OPPORTUNITIES, 'opportunities/index.php' );
146 $pages[ Enum::PAGE_IMPORT ] = new Page( 'Import', Enum::PAGE_IMPORT, 'import/index.php' );
147 $pages[ Enum::PAGE_TABLES ] = new Page( 'Tables', Enum::PAGE_TABLES, 'tables/index.php' );
148 $pages[ Enum::PAGE_URL_DETAILS ] = new Page( 'Link Details', Enum::PAGE_URL_DETAILS, '/dashboard/url-details.php' );
149
150 $pages[ Enum::PAGE_SETTINGS_GENERAL ] = new Page( 'General', Enum::PAGE_SETTINGS_GENERAL, 'settings/general.php' );
151 $pages[ Enum::PAGE_SETTINGS_DISPLAY ] = new Page( 'Display', Enum::PAGE_SETTINGS_DISPLAY, 'settings/display.php' );
152 $pages[ Enum::PAGE_SETTINGS_AMAZON ] = new Page( 'Amazon', Enum::PAGE_SETTINGS_AMAZON, 'settings/amazon.php' );
153 $pages[ Enum::PAGE_GROUPS ] = new Page( 'Groups', Enum::PAGE_GROUPS, '/groups/index.php' );
154 $pages[ Enum::PAGE_GROUP_DETAIL ] = new Page( 'Group Detail', Enum::PAGE_GROUP_DETAIL, '/groups/detail.php' );
155
156 if ( get_option( Enum::LASSO_LITE_ACTIVE ) && ! self::get_option( Enum::IS_VISITED_WELCOME_PAGE ) ) {
157 $pages[ Enum::PAGE_ONBOARDING ] = new Page( 'Onboarding', Enum::PAGE_ONBOARDING, 'onboarding/index.php' );
158 }
159
160 return $pages;
161 }
162
163 /**
164 * Convert date time to WordPress format
165 *
166 * @param string $datetime Date time. Format must be 'Y-m-d H:i:s' (example: 2018-09-14 10:34:54).
167 * @param bool $time Is it time. Default to true.
168 */
169 public static function convert_datetime_format( $datetime, $time = true ) {
170 if ( ! $datetime ) {
171 return $datetime;
172 }
173
174 $date_format = get_option( 'date_format' );
175 $time_format = 'g:i a T';
176 $datetime_format = $date_format . ' ' . $time_format;
177 $format = ( $time ) ? $datetime_format : $date_format;
178
179 try {
180 $result = date_create_from_format( 'Y-m-d H:i:s', $datetime );
181 $result = $result->format( $format );
182 } catch ( \Exception $e ) {
183 $result = $datetime;
184 }
185
186 return $result;
187 }
188
189 /**
190 * Add surl prefix
191 *
192 * @param string $page Page name.
193 * @return string
194 */
195 public static function add_prefix_page( $page ) {
196 return SIMPLE_URLS_SLUG . '-' . $page;
197 }
198
199 /**
200 * Print a wrapper for js render library
201 *
202 * @param string $html_id_selector Html id selector.
203 * @param string $file_path Absolute path file.
204 * @return string|null
205 */
206 public static function wrapper_js_render( $html_id_selector, $file_path ) {
207 $output = '<script id="' . $html_id_selector . '" type="text/x-jsrender">';
208 $output .= self::include_with_variables( $file_path, array(), true );
209 $output .= '</script>';
210 return $output;
211 }
212
213 /**
214 * Check a url has protocol or not
215 *
216 * @param string $url URL.
217 * @return bool
218 */
219 public static function has_protocol( $url ) {
220 return Url_Format::has_protocol( $url );
221 }
222
223 /**
224 * Check if Classic Editor plugin is active.
225 *
226 * @return bool
227 */
228 public static function is_classic_editor_plugin_active() {
229 return self::get_is_plugin_active( 'classic-editor/classic-editor.php' );
230 }
231
232 /**
233 * Check if Disable Gutenberg plugin is active.
234 *
235 * @return bool
236 */
237 public static function is_disable_gutenberg_plugin_active() {
238 if ( ! function_exists( 'is_plugin_active' ) ) {
239 include_once ABSPATH . 'wp-admin/includes/plugin.php';
240 }
241
242 return self::get_is_plugin_active( 'disable-gutenberg/disable-gutenberg.php' );
243 }
244
245 /**
246 * If the Lasso Pro plugin is installed, return true. Otherwise, return false
247 */
248 public static function is_lasso_pro_installed() {
249 return self::get_is_plugin_active( 'lasso/affiliate-plugin.php' );
250 }
251
252 /**
253 * If the Lasso Pro plugin is active, return true. Otherwise, return false
254 */
255 public static function is_lasso_pro_plugin_active() {
256 return self::is_lasso_pro_installed() && self::get_license_status();
257 }
258
259 /**
260 * Get license status in DB
261 */
262 public static function get_license_status() {
263 $db_status = get_option( 'lasso_lite_license_status', '' );
264 $active_license = boolval( $db_status );
265
266 return $active_license;
267 }
268
269 /**
270 * Check whether slug exists or not
271 *
272 * @param string $post_name Post name.
273 * @param int $post_id Post id. Default to 0.
274 */
275 public static function the_slug_exists( $post_name, $post_id = 0 ) {
276 if ( empty( $post_name ) ) {
277 return false;
278 }
279
280 $posts_tbl = Model::get_wp_table_name( 'posts' );
281 $sql = '
282 SELECT
283 ID,
284 post_name,
285 post_type
286 FROM '
287 . $posts_tbl . '
288 WHERE
289 post_name = %s
290 AND ID != %d
291 AND post_status <> "trash"
292 AND post_type = %s
293 LIMIT 1
294 ';
295
296 $prepare = Model::prepare( $sql, $post_name, $post_id, Constant::LASSO_POST_TYPE ); // phpcs:ignore
297 $row = Model::get_row( $prepare, 'ARRAY_A' ); // phpcs:ignore
298
299 return $row ? $row : false;
300 }
301
302 /**
303 * Add https to the url
304 *
305 * @param string $url URL.
306 */
307 public static function add_https( $url ) {
308 return Url_Format::add_https( $url );
309 }
310
311 /**
312 * Format URL before sending request
313 *
314 * @param string $url URL.
315 * @param bool $encode Encode url or not. Default to false.
316 */
317 public static function format_url_before_requesting( $url, $encode = false ) {
318 $url = trim( $url );
319 $url = $encode ? rawurlencode( $url ) : $url;
320
321 return $url;
322 }
323
324 /**
325 * Get title by url
326 *
327 * @param string $url URL.
328 */
329 public static function get_title_by_url( $url ) {
330 $url = self::add_https( $url );
331 $parse = wp_parse_url( $url );
332 $host = $parse['host'] ?? '';
333 $host = str_replace( 'www.', '', $host );
334 $host = explode( '.', $host );
335 $host = $host[ count( $host ) - 2 ] ?? '';
336 $host = str_replace( '-', ' ', $host );
337 $host = ucwords( $host );
338
339 return $host;
340 }
341
342 /**
343 * Remove a action out of WordPress hook
344 *
345 * Example 1: Lasso_Helper::remove_action('admin_print_footer_scripts', 'register_tinymce_quicktags'); $callback is a function name.
346 * Example 2: Lasso_Helper::remove_action('admin_print_footer_scripts', array('EarnistProductPicker', 'register_tinymce_quicktags')); $callback is array.
347 *
348 * @param string $hook_name Hook name.
349 * @param array|string $callback Callback function. $callback[0] is a class name, $callback[1] is a function name.
350 * @param int $priority Priority.
351 */
352 public static function remove_action( $hook_name, $callback, $priority = 10 ) {
353 global $wp_filter;
354
355 if ( ! isset( $wp_filter[ $hook_name ]->callbacks[ $priority ] ) ) {
356 return;
357 }
358
359 foreach ( $wp_filter[ $hook_name ]->callbacks[ $priority ] as $key_function_name_wp => $data ) {
360 $should_remove = false;
361 $obj_name_wp = null;
362 $function_name_wp = is_array( $data['function'] ) ? $data['function'][1] : $data['function'];
363 if ( ! is_array( $callback ) ) {
364 $function_name = $callback;
365 if ( $function_name_wp === $function_name ) {
366 $should_remove = true;
367 }
368 } else {
369 list( $object_name, $function_name ) = $callback;
370 if ( gettype( $object_name ) === 'object' ) {
371 $object_name = get_class( $object_name );
372 }
373
374 if ( ! $data['function'] instanceof \Closure ) {
375 $obj_name_wp = $data['function'][0];
376 if ( gettype( $obj_name_wp ) === 'object' ) {
377 $obj_name_wp = get_class( $obj_name_wp );
378 if ( $obj_name_wp === $object_name && $function_name_wp === $function_name ) {
379 $should_remove = true;
380 }
381 }
382 }
383 }
384
385 if ( $should_remove ) {
386 unset( $wp_filter[ $hook_name ]->callbacks[ $priority ][ $key_function_name_wp ] );
387 break;
388 }
389 }
390 }
391
392 /**
393 * It includes the HTML for the display modal dialogs
394 *
395 * @return string the html for the modal.
396 */
397 public static function get_display_modal_html() {
398 $html = self::include_with_variables( Helper::get_path_views_folder() . 'modals/display-add.php' ); // phpcs:ignore
399 $html .= self::include_with_variables( Helper::get_path_views_folder() . 'modals/url-add.php', array( 'is_from_editor' => true, ) ); // phpcs:ignore
400 $html .= self::include_with_variables( Helper::get_path_views_folder() . 'modals/url-quick-detail.php' ); // phpcs:ignore
401 $html .= self::wrapper_js_render( 'single-list', Helper::get_path_views_folder() . 'modals/single-jsrender.html' ); // phpcs:ignore
402 $html .= self::wrapper_js_render( 'url-quick-detail-jsrender', Helper::get_path_views_folder() . 'components/url-quick-detail-jsrender.html' ); // phpcs:ignore
403
404 return $html;
405 }
406
407 /**
408 * Get countries of Amazon
409 *
410 * @param string $selected_country Country code.
411 */
412 public static function get_countries_dd( $selected_country ) {
413 $countries = Amazon_Api::get_amazon_api_countries();
414
415 $countries_dd = '<select id="amazon_default_tracking_country" name="amazon_default_tracking_country" class="form-control">';
416 foreach ( $countries as $key => $country ) {
417 if ( strlen( $key ) !== 2 ) {
418 continue;
419 }
420
421 $selected = '';
422 if ( $selected_country === $key ) {
423 $selected = 'selected';
424 }
425 $countries_dd .= '<option value="' . $key . '" ' . $selected . ' >' . $country['name'] . '</option>';
426 }
427 $countries_dd .= '</select>';
428
429 return $countries_dd;
430 }
431
432 /**
433 * Get setup progress information
434 *
435 * @return array
436 */
437 public static function get_setup_progress_information() {
438 $enable_support = boolval( Setting::get_setting( Enum::SUPPORT_ENABLED ) ) ? 20 : 0;
439 $total_links = SURL::total();
440 $links = $total_links > 20 ? 20 : $total_links;
441 $links_percent = 20 === $links ? 100 : ( $links / 20 ) * 100;
442 $setup_amz_tracking_id = boolval( get_option( Enum::SETUP_AMZ_TRACKING_ID ) ) ? 15 : 0;
443 $follow_on_twitter = boolval( get_option( Enum::FOLLOW_ON_TWITTER ) ) ? 10 : 0;
444 $share_on_twitter = boolval( get_option( Enum::SHARE_ON_TWITTER ) ) ? 10 : 0;
445 $leave_a_review = boolval( get_option( Enum::LEAVE_A_REVIEW ) ) ? 5 : 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 $progress = $enable_support + $setup_amz_tracking_id + $follow_on_twitter + $share_on_twitter + ( $links * 2 ) + $leave_a_review;
448 $progress = $progress ? $progress / 100 : 0;
449 $open_modal_add_link = $links < 20 ? 'btn-add-20-links' : '';
450
451 $data = array(
452 'progress' => round( $progress, 2 ),
453 'progress_percent' => round( $progress * 100 ),
454 'links' => $links,
455 'links_percent' => $links_percent,
456 'setup_amz_tracking_id' => $setup_amz_tracking_id,
457 'follow_on_twitter' => $follow_on_twitter,
458 'share_on_twitter' => $share_on_twitter,
459 'leave_a_review' => $leave_a_review,
460 'is_show_review_note' => $is_show_review_note,
461 'enable_support' => $enable_support,
462 'setting_amz_url' => Page::get_lite_page_url( Enum::PAGE_SETTINGS_AMAZON ),
463 'follow_twitter_url' => home_url() . '?' . Enum::SLUG_CLOAK_FOLLOW_TWITTER,
464 'share_twitter_url' => home_url() . '?' . Enum::SLUG_CLOAK_SHARE_TWITTER,
465 'review_url' => home_url() . '?' . Enum::SLUG_CLOAK_LASSO_REVIEW_URL,
466 'open_modal_add_link' => $open_modal_add_link,
467 );
468
469 return $data;
470 }
471
472 /**
473 * Send request
474 *
475 * @param string $method Method (get or post). Default to get.
476 * @param string $url URL. Default to empty.
477 * @param array $data Post data. Default to empty array.
478 * @param array $headers Headers. Default to empty array.
479 * @param bool $is_lasso_save Is Lasso save data action. Default to false.
480 */
481 public static function send_request( $method = 'get', $url = '', $data = array(), $headers = array(), $is_lasso_save = false ) {
482 $method = strtolower( $method );
483 $request_options = array(
484 'headers' => $headers,
485 'timeout' => Constant::TIME_OUT,
486 'sslverify' => Constant::SSL_VERIFY,
487 );
488 $body = wp_json_encode( $data );
489 $headers_expect = ! empty( $body ) && strlen( $body ) > 1048576 ? '100-Continue' : '';
490 if ( 'get' === $method ) {
491 $res = wp_remote_get( $url, $request_options );
492 } elseif ( 'post' === $method ) {
493 $request_options['headers']['expect'] = $headers_expect;
494 $request_options['body'] = $body;
495 $res = wp_remote_post( $url, $request_options );
496 } elseif ( 'put' === $method ) {
497 $request_options['headers']['expect'] = $headers_expect;
498 $request_options['body'] = $body;
499 $request_options['method'] = 'PUT';
500 $res = wp_remote_request( $url, $request_options );
501 }
502
503 if ( is_wp_error( $res ) ) {
504 // Return structured info so callers can surface what failed (e.g. cURL error).
505 $error_payload = array(
506 'error' => array(
507 'code' => $res->get_error_code(),
508 'message' => $res->get_error_message(),
509 'data' => $res->get_error_data(),
510 ),
511 );
512 return array(
513 'status_code' => 500,
514 'response' => json_decode( wp_json_encode( $error_payload ) ),
515 );
516 }
517
518 $body = wp_remote_retrieve_body( $res );
519 $status = wp_remote_retrieve_response_code( $res );
520
521 return array(
522 'status_code' => $status,
523 'response' => json_decode( $body ),
524 );
525 }
526
527 /**
528 * Plain-text error from send_request() output (WP_Error payload or API JSON body).
529 *
530 * @param array $response Return value from send_request().
531 * @param string $default Default message.
532 * @return string
533 */
534 public static function hub_request_error_message( $response, $default = 'Request failed.' ) {
535 $default = (string) $default;
536 if ( empty( $response ) || ! is_array( $response ) ) {
537 return $default;
538 }
539 $r = $response['response'] ?? null;
540 if ( empty( $r ) || ! is_object( $r ) ) {
541 return $default;
542 }
543 if ( isset( $r->error ) ) {
544 if ( is_object( $r->error ) && isset( $r->error->message ) ) {
545 return (string) $r->error->message;
546 }
547 if ( is_string( $r->error ) && $r->error !== '' ) {
548 return $r->error;
549 }
550 }
551 if ( isset( $r->message ) && is_string( $r->message ) && $r->message !== '' ) {
552 return $r->message;
553 }
554 return $default;
555 }
556
557 /**
558 * Get Lasso Lite - WP option
559 *
560 * @param string $option_name Option name.
561 * @param mixed $default Default value.
562 * @return mixed|void
563 */
564 public static function get_option( $option_name, $default = false ) {
565 return get_option( SIMPLE_URLS_SLUG . '_' . $option_name, $default );
566 }
567
568 /**
569 * Update Lasso Lite - WP option
570 *
571 * @param string $option_name Option name.
572 * @param mixed $option_value Option value.
573 * @param bool $autoload Autoload.
574 * @return bool
575 */
576 public static function update_option( $option_name, $option_value, $autoload = null ) {
577 return update_option( SIMPLE_URLS_SLUG . '_' . $option_name, $option_value, $autoload );
578 }
579
580 /**
581 * Validate URL
582 *
583 * @param string $url URL.
584 */
585 public static function validate_url( $url ) {
586 return Url_Format::validate_url( $url );
587 }
588
589 /**
590 * Remove specific parameters from URL
591 *
592 * @param string $url URL to clean.
593 * @param array|string $params Parameter(s) to remove.
594 * @return string Cleaned URL
595 */
596 public static function remove_url_params( $url, $params ) {
597 $parsed_url = wp_parse_url( $url );
598
599 if ( ! isset( $parsed_url['query'] ) ) {
600 return $url;
601 }
602
603 parse_str( $parsed_url['query'], $query_params );
604
605 // ? Handle both array and single parameter
606 $params = (array) $params;
607 foreach ( $params as $param ) {
608 unset( $query_params[ $param ] );
609 }
610
611 // ? Rebuild URL
612 $clean_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . $parsed_url['path'];
613 if ( ! empty( $query_params ) ) {
614 $clean_url .= '?' . http_build_query( $query_params );
615 }
616
617 // ? Add fragment if exists
618 if ( isset( $parsed_url['fragment'] ) ) {
619 $clean_url .= '#' . $parsed_url['fragment'];
620 }
621
622 return $clean_url;
623 }
624
625 /**
626 * Get argument from url
627 *
628 * @param string $link Amazon link.
629 * @param string $argument URL argument.
630 * @return string
631 */
632 public static function get_argument_from_url( $link, $argument ) {
633 if ( ! $argument ) {
634 return '';
635 }
636
637 $link = str_replace( '&amp;', '&', $link );
638 $parse = wp_parse_url( $link );
639 $queries = array();
640 parse_str( $parse['query'] ?? '', $queries );
641
642 return $queries[ $argument ] ?? '';
643 }
644
645 /**
646 * Build url parameter string
647 * Parameter example: array( 'rel_=abc', 'maas=def' );
648 *
649 * @param array $parameters Parameter key=value array.
650 * @return string|mixed
651 */
652 public static function build_url_parameter_string( $parameters = array() ) {
653 $result = implode( '&', $parameters );
654 $result = preg_replace( '!\&+!', '&', $result );
655 $result = trim( $result, '&' );
656
657 return $result;
658 }
659
660 /**
661 * Convert query array to a string (in a url)
662 *
663 * @param array $query Query.
664 */
665 public static function get_query_from_array( $query ) {
666 $result = array();
667 foreach ( $query as $key => $value ) {
668 $result[] = $key . '=' . rawurlencode( $value );
669 }
670 $result = implode( '&', $result );
671 return $result;
672 }
673
674 /**
675 * Convert parse_url() to a url
676 *
677 * @param array $parse Parse data from a URL.
678 * @param bool $host Is it host. Default to false.
679 */
680 public static function get_url_from_parse( $parse, $host = false ) {
681 $parse['host'] = $parse['host'] ?? '';
682 $host = false !== $host ? $host : $parse['host'];
683 $parse['host'] = '://' . $host;
684 $parse['query'] = isset( $parse['query'] ) ? '?' . $parse['query'] : '';
685
686 return implode( '', $parse );
687 }
688
689 /**
690 * Get param of $_SERVER
691 *
692 * @param string $name Name of param.
693 */
694 public static function get_server_param( $name ) {
695 return wp_unslash( $_SERVER[ $name ] ?? '' ); // phpcs:ignore
696 }
697
698 /**
699 * Check if Lasso URL description is empty.
700 *
701 * @param string $description Lasso URL description.
702 * @return bool
703 */
704 public static function is_description_empty( $description ) {
705 if ( empty( $description ) || '<p><br></p>' === $description ) {
706 return true;
707 }
708
709 return false;
710 }
711
712 /**
713 * Get base domain
714 * Ex: "http://domain.com" would return "domain.com"
715 *
716 * @param string $domain Domain. It must be passed WITH protocol. Default to empty.
717 */
718 public static function get_base_domain( $domain = '' ) {
719 $domain = self::add_https( $domain );
720 if ( ! self::validate_url( $domain ) ) {
721 return '';
722 }
723
724 $url = @wp_parse_url( $domain ); // phpcs:ignore
725 $host = $url['host'] ?? '';
726 $host = str_replace( 'www.', '', $host );
727 $host = trim( $host );
728
729 return $host ? $host : '';
730 }
731
732 /**
733 * Convert stdclass object to array
734 *
735 * @param bool $obj StdClass object.
736 */
737 public static function convert_stdclass_to_array( $obj ) {
738 $array = json_decode( wp_json_encode( $obj ), true );
739
740 return $array;
741 }
742
743 /**
744 * Check if mysql error relative to Lasso's table does not exist
745 *
746 * @param string $mysql_error mysql error.
747 * @return boolean
748 */
749 public static function is_lasso_tables_does_not_exist_error( $mysql_error ) {
750 return (bool) preg_match( "/(\.)(.)*lasso_(.)*doesn\'t(\s)exist/", $mysql_error );
751 }
752
753 /**
754 * If the AAWP plugin is active, return true. Otherwise, return false
755 */
756 public static function is_aawp_active() {
757 return is_plugin_active( 'aawp/aawp.php' );
758 }
759
760 /**
761 * If the Amalinks Pro plugin is active, return true. Otherwise, return false
762 */
763 public static function is_amalinks_pro_active() {
764 return is_plugin_active( 'amalinkspro/amalinkspro.php' );
765 }
766
767 /**
768 * Format importable data before showing/importing/reverting
769 *
770 * @param object $p Importable post.
771 */
772 public static function format_importable_data( $p ) {
773 $lasso_db = new Lasso_DB();
774 $lasso_helper = new Helper();
775 $lasso_amazon_api = new Amazon_Api();
776
777 $home_url = home_url();
778 $p->import_permalink = get_permalink( $p->id );
779
780 if ( 'Pretty Links' === $p->import_source ) {
781 $pretty_link_data = $lasso_db->get_pretty_link_by_id( $p->id );
782 $defaul_permalink = $home_url . '/' . $pretty_link_data->slug . '/';
783
784 $prlipro = get_option( 'prlipro_options', array() );
785 $prlipro = is_array( $prlipro ) ? $prlipro : array();
786 $base_slug_prefix = $prlipro['base_slug_prefix'] ?? '';
787 $p->import_permalink = '' !== $base_slug_prefix && strpos( $p->post_name, $base_slug_prefix ) === false
788 ? $home_url . '/' . $base_slug_prefix . '/' . $p->post_name . '/'
789 : $defaul_permalink;
790 } elseif ( 'AAWP' === $p->import_source ) {
791 $aawp_row = $lasso_db->get_aawp_product( $p->id );
792 $p->import_permalink = $aawp_row->url ?? '';
793 $shortcode = '[amazon link="' . $p->post_name . '"]';
794 $p->shortcode = $shortcode;
795
796 // ? AAWP list
797 if ( 'aawp_list' === $p->post_type ) {
798 $p->import_permalink = 'https://amazon.com/s?k=' . $p->post_title;
799 $p->check_status = self::check_aawp_list_is_imported( $p->id ) ? 'checked' : '';
800
801 $cat = term_exists( $p->post_title, Constant::LASSO_CATEGORY );
802 $p->check_status = $cat ? 'checked' : $p->check_status;
803
804 $aawp_list = $lasso_db->get_aawp_list( $p->id );
805 if ( $aawp_list ) {
806 $items_count = $aawp_list->items_count ?? 0;
807 $attr_type = 'bestseller' === $aawp_list->type ? 'bestseller' : 'link';
808 $attr_type = 'new_releases' === $aawp_list->type ? 'new' : $attr_type;
809 $shortcode = '[amazon ' . $attr_type . '="' . $aawp_list->keywords . '" items="' . $items_count . '"]';
810 $p->shortcode = $shortcode;
811 }
812 }
813 } elseif ( 'EasyAzon' === $p->import_source ) {
814 $product = Lasso_DB::get_easyazon_option( $p->post_title );
815 $p->post_title = $product['title'];
816 $p->id = $product['identifier'];
817 $p->import_permalink = $product['url'];
818 $p->post_name = strtolower( $product['identifier'] );
819 $shortcode = '[easyazon_link identifier="' . $p->id . '"]' . $p->post_title . '[/easyazon_link]';
820 $p->shortcode = $shortcode;
821
822 $revert = $lasso_db->is_easyazon_product_imported( $product['identifier'] );
823 if ( $revert ) {
824 $p->id = $revert->lasso_id;
825 }
826 } elseif ( 'AmaLinks Pro' === $p->import_source ) {
827 $shortcode = $p->post_name;
828 $attributes = $lasso_helper->get_attributes( $shortcode );
829 $p->shortcode = $shortcode;
830 $p->import_permalink = $attributes['apilink'] ?? '';
831 if ( empty( $p->id ) ) {
832 $p->id = $attributes['asin'] ?? $p->id;
833
834 $url_details = $lasso_db->get_url_details_by_product_id( $p->id, Amazon_Api::PRODUCT_TYPE );
835 $lasso_id = $url_details->lasso_id ?? 0;
836 $p->check_status = $lasso_id > 0 ? 'checked' : '';
837
838 }
839 if ( empty( $p->import_permalink ) ) {
840 $p->import_permalink = $lasso_amazon_api->get_amazon_link_by_product_id( $p->id );
841 }
842 } elseif ( 'Lasso Pro' === $p->import_source ) {
843 $target_url = Import::get_lasso_pro_target_url( $p->id );
844 $p->import_permalink = $target_url;
845 $p->shortcode = '[lasso rel="' . $p->post_name . '" id="' . $p->id . '"]';
846 }
847
848 $p->post_title_attr = esc_attr( $p->post_title ?? '' );
849 $p->import_permalink_attr = esc_attr( $p->import_permalink ?? '' );
850 if ( ! empty( $p->shortcode ) ) {
851 $p->shortcode_attr = esc_attr( $p->shortcode );
852 }
853
854 return $p;
855 }
856
857 /**
858 * Get attributes of shortcode
859 *
860 * @param string $tags_data Shortcode string.
861 */
862 public static function get_attributes( $tags_data ) {
863 // ? fix shortcode contains content: [shortcode]content[/shortcode]
864 preg_match_all(
865 '/' . get_shortcode_regex() . '/',
866 $tags_data,
867 $matches,
868 PREG_SET_ORDER
869 );
870 $content_between = $matches[0][5] ?? '';
871 $shortcode_name = $matches[0][2] ?? '';
872 if ( ! empty( $shortcode_name ) ) {
873 // ? remove content between content/anchor text
874 $tags_data = str_replace( ']' . $content_between . '[', '][', $tags_data );
875 // ? remove the end shortcode
876 $tags_data = str_replace( '[/' . $shortcode_name . ']', '', $tags_data );
877 }
878 $tags_data = str_replace( '/]', ']', $tags_data );
879
880 $attributes = array();
881 $parse = shortcode_parse_atts( esc_html( $tags_data ) );
882
883 $temp_key = 'temp';
884 foreach ( $parse as $key => $value ) {
885 if ( ! is_integer( $key ) ) {
886 $temp_key = $key;
887 $attributes[ $key ] = self::remove_special_character_in_attributes( $value );
888 } else {
889 // ? join data with old key.
890 $attributes[ $temp_key ] = trim( ( $attributes[ $temp_key ] ?? '' ) . ' ' . self::remove_special_character_in_attributes( $value ) );
891 }
892 }
893 unset( $attributes['temp'] );
894
895 return $attributes;
896 }
897
898 /**
899 * Remove special character in attributes
900 *
901 * @param string $text Text string.
902 */
903 public static function remove_special_character_in_attributes( $text ) {
904 $text = str_replace( 'u0026', '&', $text );
905 $text = str_replace( array( '\\', 'u0022', '&quot;', 'u003c', 'u003e', '&lt;', '&gt;' ), '', $text );
906 return htmlspecialchars_decode( trim( $text, ']' ), ENT_QUOTES );
907 }
908
909 /**
910 * Check whether aawp list id is imported to Lasso or not
911 *
912 * @param int $id Aawp list id.
913 */
914 public static function check_aawp_list_is_imported( $id ) {
915 $lasso_db = new Lasso_DB();
916
917 $sql = '
918 SELECT lud.product_id
919 FROM ' . ( new Url_Details() )->get_table_name() . ' AS lud
920 LEFT JOIN ' . $lasso_db->posts . ' AS p
921 ON lud.lasso_id = p.ID
922 WHERE lud.product_id != \'\'
923 AND p.ID is not null
924 AND lud.product_type = \'' . Amazon_Api::PRODUCT_TYPE . '\'
925 ';
926 $row = $lasso_db->get_col( $sql );
927 $amazon_ids = $row ? $row : array();
928
929 $aawp_list = $lasso_db->get_aawp_list( $id );
930 $aawp_amazon_ids = $aawp_list->product_asins ?? '';
931 $aawp_amazon_ids = '' !== $aawp_amazon_ids ? explode( ',', $aawp_amazon_ids ) : array();
932
933 $same_elements = array_intersect( $aawp_amazon_ids, $amazon_ids );
934
935 return $aawp_amazon_ids === $same_elements;
936 }
937
938 /**
939 * Paginate items by a sql query
940 * Reset page number if results is empty.
941 *
942 * @param string $sql Sql query.
943 * @param int $page Number of page.
944 * @param int $limit Number of results. Default to 10.
945 */
946 public static function paginate( $sql, &$page, $limit = 10 ) {
947 $start_index = ( $page - 1 ) * $limit;
948 $pagination_sql = $sql . ' LIMIT ' . $start_index . ', ' . $limit;
949
950 if ( $page > 1 ) {
951 $result = Model::get_row( $pagination_sql );
952
953 if ( ! $result ) {
954 $page = 1;
955 $pagination_sql = $sql . ' LIMIT 0, ' . $limit;
956 }
957 }
958
959 return $pagination_sql;
960 }
961
962 /**
963 * Format "post title" to escape html and apply limit length.
964 *
965 * @param Title $title lasso urls title.
966 * @param int $limit_length limit length of title.
967 */
968 public static function format_post_title( $title, $limit_length = 200 ) {
969 $title = esc_html( $title );
970
971 if ( strlen( $title ) > $limit_length ) {
972 $title = substr( $title, 0, $limit_length ) . '...';
973 }
974
975 return $title;
976 }
977
978 /**
979 * Get unique post name of lasso post
980 *
981 * @param int $post_id Post id.
982 * @param string $post_name Post name.
983 */
984 public static function lasso_unique_post_name( $post_id, $post_name ) {
985 if ( intval( $post_id ) > 0 && ! empty( $post_name ) && self::the_slug_exists( $post_name, $post_id ) ) {
986 $post_name = rtrim( $post_name, '-link' ); // ? Fix the issue adding multiple "-link" string to the end.
987 $post_name = wp_unique_post_slug( $post_name, $post_id, 'publish', Constant::LASSO_POST_TYPE, 0 );
988 }
989
990 return $post_name;
991 }
992
993 /**
994 * Get plugin status result
995 *
996 * @param string $plugin Plugin key.
997 * @return bool
998 */
999 public static function get_is_plugin_active( $plugin ) {
1000 $cache_result = Cache_Per_Process::get_instance()->get_cache( 'is_plugin_active_' . md5( $plugin ), null );
1001 if ( null !== $cache_result ) {
1002 return $cache_result;
1003 }
1004
1005 if ( ! function_exists( 'is_plugin_active' ) ) {
1006 include_once ABSPATH . 'wp-admin/includes/plugin.php';
1007 }
1008
1009 $result = \is_plugin_active( $plugin );
1010 Cache_Per_Process::get_instance()->set_cache( 'is_plugin_active_' . md5( $plugin ), $result );
1011
1012 return $result;
1013 }
1014
1015 /**
1016 *
1017 * Check plugin earnist is loaded https://www.getearnist.com.
1018 *
1019 * @return bool
1020 */
1021 public static function is_earnist_plugin_loaded() {
1022 return self::get_is_plugin_active( 'earnist/earnist.php' );
1023 }
1024
1025 /**
1026 *
1027 * Check plugin Shortcode Star Rating is loaded https://github.com/modshrink/shortcode-star-rating.
1028 *
1029 * @return bool
1030 */
1031 public static function is_shortcode_start_rating_plugin_loaded() {
1032 return self::get_is_plugin_active( 'shortcode-star-rating/shortcode-star-rating.php' );
1033 }
1034
1035 /**
1036 * Check plugin "Easy Table of Contents" is activated
1037 *
1038 * @return bool
1039 */
1040 public static function is_plugin_easy_table_of_contents_activated() {
1041 return self::get_is_plugin_active( 'easy-table-of-contents/easy-table-of-contents.php' );
1042 }
1043
1044 /**
1045 * Check if Gravity Perks plugin is active.
1046 *
1047 * @return bool
1048 */
1049 public static function is_gravity_perks_plugin_active() {
1050 return self::get_is_plugin_active( 'gravityperks/gravityperks.php' );
1051 }
1052
1053 /**
1054 * Check if Ezoic plugin is active.
1055 *
1056 * @return bool
1057 */
1058 public static function is_ezoic_plugin_active() {
1059 return self::get_is_plugin_active( 'ezoic-integration/ezoic-integration.php' );
1060 }
1061
1062 /**
1063 * Check if WP Rocket - Lazyload is enabled.
1064 *
1065 * @return bool
1066 */
1067 public static function is_wp_rocket_lazyload_image_enabled() {
1068 if ( self::get_is_plugin_active( 'wp-rocket/wp-rocket.php' ) ) {
1069 // ? Check if layzyload enabled
1070 $wp_rocket_settings = get_option( 'wp_rocket_settings', array() );
1071 if ( $wp_rocket_settings['lazyload'] ?? false ) {
1072 return true;
1073 }
1074 }
1075
1076 return false;
1077 }
1078
1079 /**
1080 * Check whether current page is WP post page
1081 */
1082 public static function is_wordpress_post() {
1083 global $pagenow;
1084
1085 $get = self::GET(); // phpcs:ignore
1086 $action = $get['action'] ?? '';
1087 $add_new_page = 'post-new.php' === $pagenow;
1088 $edit_page = 'post.php' === $pagenow && 'edit' === $action;
1089 $post_type = $get['post_type'] ?? '';
1090
1091 if ( ( 'edit.php' === $pagenow || $add_new_page ) && '' === $post_type ) {
1092 $post_type = 'post';
1093 } elseif ( $add_new_page ) {
1094 $post_type = $get['post_type'] ?? $post_type;
1095 } elseif ( $edit_page ) {
1096 $post_id = intval( $get['post'] ?? 0 );
1097 $post_type = $post_id > 0 ? get_post_type( $post_id ) : $post_type;
1098 }
1099
1100 if ( 'term.php' === $pagenow ) {
1101 $post_type = '';
1102 }
1103
1104 return 'post' === $post_type || 'page' === $post_type;
1105 }
1106
1107 /**
1108 * Cast the value to boolean
1109 *
1110 * @param bool|string $value A string boolean like "true" or "false".
1111 *
1112 * @return bool
1113 */
1114 public static function cast_to_boolean( $value ) {
1115 return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
1116 }
1117
1118 /**
1119 * Get CPU load of server/hosting
1120 */
1121 public static function get_cpu_load() {
1122 $load = null;
1123
1124 if ( stristr( PHP_OS, 'win' ) ) {
1125 $cmd = 'wmic cpu get loadpercentage /all';
1126 @exec( $cmd, $output ); // phpcs:ignore
1127
1128 if ( $output ) {
1129 foreach ( $output as $line ) {
1130 if ( $line && preg_match( '/^[0-9]+$/', $line ) ) {
1131 $load = $line;
1132 break;
1133 }
1134 }
1135 }
1136 } else {
1137 try {
1138 if ( @is_readable( '/proc/stat' ) ) { // phpcs:ignore
1139 $cached_cpu_load = Cache_Per_Process::get_instance()->get_cache( 'cpu_load', null );
1140 if ( $cached_cpu_load ) {
1141 $stat_data1 = $cached_cpu_load;
1142 } else {
1143 $stat_data1 = self::get_server_load_linux_data();
1144 }
1145
1146 // ? Collect 2 samples - each with 1 second period
1147 // ? See: https://de.wikipedia.org/wiki/Load#Der_Load_Average_auf_Unix-Systemen
1148 sleep( 1 );
1149
1150 $stat_data2 = self::get_server_load_linux_data();
1151 Cache_Per_Process::get_instance()->set_cache( 'cpu_load', $stat_data2 );
1152
1153 if ( ( ! is_null( $stat_data1 ) ) && ( ! is_null( $stat_data2 ) ) ) {
1154 // ? Get difference
1155 $stat_data2[0] -= $stat_data1[0];
1156 $stat_data2[1] -= $stat_data1[1];
1157 $stat_data2[2] -= $stat_data1[2];
1158 $stat_data2[3] -= $stat_data1[3];
1159
1160 // ? Sum up the 4 values for User, Nice, System and Idle and calculate
1161 // ? the percentage of idle time (which is part of the 4 values!)
1162 $cpu_time = $stat_data2[0] + $stat_data2[1] + $stat_data2[2] + $stat_data2[3];
1163
1164 // ? Invert percentage to get CPU time, not idle time
1165 $load = 100 - ( $stat_data2[3] * 100 / max( $cpu_time, 1 ) );
1166 }
1167 }
1168 } catch ( Exception $e ) {
1169 $load = 0; // Just run because we can't detect CPU load.
1170 }
1171 }
1172
1173 return round( $load, 2 );
1174 }
1175
1176 /**
1177 * Get server load linux data
1178 */
1179 private static function get_server_load_linux_data() {
1180 if ( @is_readable( '/proc/stat' ) ) { // phpcs:ignore
1181 $stats = @file_get_contents( '/proc/stat' ); // phpcs:ignore
1182
1183 if ( false !== $stats ) {
1184 // ? Remove double spaces to make it easier to extract values with explode()
1185 $stats = preg_replace( '/[[:blank:]]+/', ' ', $stats );
1186
1187 // ? Separate lines
1188 $stats = str_replace( array( "\r\n", "\n\r", "\r" ), "\n", $stats );
1189 $stats = explode( "\n", $stats );
1190
1191 // ? Separate values and find line for main CPU load
1192 foreach ( $stats as $stat_line ) {
1193 $stat_line_data = explode( ' ', trim( $stat_line ) );
1194
1195 // ? Found
1196 if ( count( $stat_line_data ) >= 5 && 'cpu' === $stat_line_data[0] ) {
1197 return array(
1198 $stat_line_data[1],
1199 $stat_line_data[2],
1200 $stat_line_data[3],
1201 $stat_line_data[4],
1202 );
1203 }
1204 }
1205 }
1206 }
1207
1208 return null;
1209 }
1210
1211 /**
1212 * Remove unexpected character from post title
1213 *
1214 * @param string $post_title Post title.
1215 * @return string
1216 */
1217 public static function remove_unexpected_characters_from_post_title( $post_title ) {
1218 // ? Remove unexpected character.
1219 $post_title = preg_replace( "/[^A-Za-z0-9\s`~!@#$%^&:;\/\?\"\'\+\=\.\,\-\_\*\(\)\|\[\]\<\>\{\}\\\]/", ' ', $post_title );
1220 // ? Remove duplicated space.
1221 $post_title = preg_replace( '/\s\s+/', ' ', $post_title );
1222
1223 return $post_title;
1224 }
1225
1226 /**
1227 * Check whether this install is new
1228 */
1229 public static function is_new_install() {
1230 return boolval( get_option( Enum::LASSO_LITE_ACTIVE ) );
1231 }
1232
1233 /**
1234 * Build image lazyload attributes
1235 *
1236 * @return string
1237 */
1238 public static function build_img_lazyload_attributes() {
1239 $result = 'loading="lazy"'; // ? WP default lazyload.
1240
1241 if ( self::is_ezoic_plugin_active() ) {
1242 $result = 'class="ezlazyload"';
1243 } elseif ( self::is_wp_rocket_lazyload_image_enabled() ) {
1244 $result = 'class="rocket-lazyload"';
1245 }
1246
1247 return $result;
1248 }
1249
1250 /**
1251 * Check whether import page should display
1252 *
1253 * @return bool
1254 */
1255 public static function should_show_import_page() {
1256 return ! empty( ( new Lasso_DB() )->get_import_plugins( true ) ) ? true : false;
1257 }
1258
1259 /**
1260 * Get brag icon
1261 *
1262 * @param bool $force_to_show Force to show the brag. Default to false.
1263 */
1264 public static function get_brag_icon( $force_to_show = false ) {
1265 $cache_key = 'lasso_lite_brag_icon';
1266 $brag_cache = Cache_Per_Process::get_instance()->get_cache( $cache_key, '' );
1267
1268 if ( $brag_cache ) {
1269 return $brag_cache;
1270 }
1271
1272 $lasso_settings = Setting::get_settings();
1273
1274 // Brag mode is always enabled in Lite.
1275 $enable_brag_mode = true;
1276 $lasso_url = $lasso_settings['lasso_affiliate_URL'] ?? false;
1277
1278 if ( $lasso_url && ( $force_to_show || $enable_brag_mode ) ) {
1279 $icon_brag = esc_url( SIMPLE_URLS_URL . '/admin/assets/images/lasso-icon-brag.svg' );
1280 $lasso_affiliate_url = self::add_params_to_url( $lasso_url, array( 'utm_source' => 'brag' ) );
1281 $img_attr = self::build_img_lazyload_attributes();
1282 $icon = '
1283 <a class="lasso-brag" href="' . esc_url( $lasso_affiliate_url ) . '" target="_blank" rel="nofollow noindex">
1284 <img src="' . esc_url( $icon_brag ) . '" ' . $img_attr . ' alt="Lasso Brag" width="30" height="30">
1285 </a>
1286 ';
1287
1288 Cache_Per_Process::get_instance()->set_cache( $cache_key, $icon );
1289
1290 return $icon;
1291 }
1292
1293 return '';
1294 }
1295
1296 /**
1297 * Add params to URL
1298 *
1299 * @param string $url URL.
1300 * @param array $params Params.
1301 */
1302 public static function add_params_to_url( $url, $params ) {
1303 // ? parse url
1304 $parse = wp_parse_url( $url );
1305 parse_str( $parse['query'] ?? '', $query );
1306
1307 $query = array_merge( $query, $params );
1308 $query = self::get_query_from_array( $query );
1309 $parse['query'] = $query;
1310
1311 return self::get_url_from_parse( $parse );
1312 }
1313
1314 /**
1315 * Get final url in the url
1316 * Example: https://affiliate.com/redirect?url=https://getlasso.co
1317 *
1318 * @param string $url URL.
1319 */
1320 public static function get_final_url_from_url_param( $url ) {
1321 if ( ! self::validate_url( $url ) ) {
1322 return false;
1323 }
1324
1325 $final_url = false;
1326 $base_domain = self::get_base_domain( $url );
1327
1328 if ( self::is_shareasale_url( $url ) ) {
1329 $final_url = self::get_argument_from_url( $url, 'urllink' );
1330 } elseif ( 'pntra.com' === $base_domain ) {
1331 $final_url = self::get_argument_from_url( $url, 'url' );
1332 } elseif ( 'wordseed.com' === $base_domain ) {
1333 $final_url = self::get_argument_from_url( $url, 'url' );
1334 } elseif ( 'titan.fitness' === $base_domain ) {
1335 $final_url = 'https://www.titan.fitness' . self::get_argument_from_url( $url, 'redirect' );
1336 } else { // ? other urls
1337 $parse = wp_parse_url( $url );
1338 $queries = array();
1339 parse_str( $parse['query'] ?? '', $queries );
1340 foreach ( $queries as $key => $param ) {
1341 if ( 'referrer' === $key ) {
1342 continue;
1343 }
1344
1345 $param = str_replace( ' ', '%20', $param );
1346 if ( self::validate_url( $param ) ) {
1347 $final_url = $param;
1348 break;
1349 }
1350 }
1351 }
1352 $final_url = self::add_https( $final_url );
1353
1354 if ( empty( $final_url ) || ! self::validate_url( $final_url ) ) {
1355 $final_url = false;
1356 } else {
1357 $final_url = trim( $final_url, '/' );
1358 }
1359
1360 return $final_url;
1361 }
1362
1363 /**
1364 * Check whether url is shareasale domain or not
1365 *
1366 * @param string $url URL.
1367 * @return bool
1368 */
1369 public static function is_shareasale_url( $url ) {
1370 $allow_domains = array( 'shareasale.com', 'shareasale-analytics.com' );
1371 $domain = self::get_base_domain( $url );
1372
1373 return in_array( $domain, $allow_domains, true );
1374 }
1375
1376 /**
1377 * Check importable
1378 *
1379 * @return bool
1380 */
1381 public static function is_importable() {
1382 $lasso_lite_db = new Lasso_DB();
1383 $sql = $lasso_lite_db->get_importable_urls_query( true );
1384 $post = Model::get_row( $sql );
1385
1386 if ( ! empty( $post ) ) {
1387 $post->post_title = self::format_post_title( $post->post_title ?? '' );
1388 $post->shortcode = '';
1389
1390 // ? Get import target permalinks
1391 $post = self::format_importable_data( $post );
1392
1393 // ? Check first record from list import
1394 return 'checked' !== $post->check_status;
1395 }
1396
1397 return false;
1398 }
1399
1400 /**
1401 * Check whether Lite is using new or old UI
1402 *
1403 * @return bool true: new UI. false: old UI.
1404 */
1405 public static function is_lite_using_new_ui() {
1406 $new_ui = get_option( Enum::SWITCH_TO_NEW_UI );
1407 $new_ui = self::cast_to_boolean( $new_ui );
1408
1409 if ( ! $new_ui ) {
1410 return false;
1411 }
1412
1413 return true;
1414 }
1415
1416 /**
1417 * CSS custom properties for Lasso display colors (shared admin + block editor iframe).
1418 *
1419 * @param bool $important Append `!important` to each variable value.
1420 * @return string
1421 */
1422 public static function get_lasso_display_css_variables( $important = false ) {
1423 $settings = Setting::get_settings();
1424 $suffix = $important ? ' !important' : '';
1425
1426 // @codingStandardsIgnoreStart
1427 return ':root{
1428 --lasso-main: ' . $settings['display_color_main'] . $suffix . ';
1429 --lasso-title: ' . $settings['display_color_title'] . $suffix . ';
1430 --lasso-button: ' . $settings['display_color_button'] . $suffix . ';
1431 --lasso-secondary-button: ' . $settings['display_color_secondary_button'] . $suffix . ';
1432 --lasso-button-text: ' . $settings['display_color_button_text'] . $suffix . ';
1433 --lasso-background: ' . $settings['display_color_background'] . $suffix . ';
1434 --lasso-pros: ' . $settings['display_color_pros'] . $suffix . ';
1435 --lasso-cons: ' . $settings['display_color_cons'] . $suffix . ';
1436 }';
1437 // @codingStandardsIgnoreEnd
1438 }
1439
1440 /**
1441 * Whether show Request Review at the top of the page
1442 */
1443 public static function show_request_review() {
1444 $link_count = SURL::total();
1445
1446 $lasso_review_allow = self::cast_to_boolean( self::get_option( Constant::LASSO_OPTION_REVIEW_ALLOW, '1' ) );
1447 $lasso_review_snooze = self::cast_to_boolean( self::get_option( Constant::LASSO_OPTION_REVIEW_SNOOZE, '0' ) );
1448 $lasso_review_link_count = intval( self::get_option( Constant::LASSO_OPTION_REVIEW_LINK_COUNT, $link_count ) );
1449
1450 // Ask after early success (enough links to be real usage), not after a large catalog.
1451 $show = ! $lasso_review_snooze && $link_count >= 5;
1452 $snooze_but_show = $lasso_review_snooze && $link_count - $lasso_review_link_count >= 5;
1453
1454 if ( ! $lasso_review_allow ) {
1455 return false;
1456 }
1457
1458 if ( $show || $snooze_but_show ) {
1459 return true;
1460 }
1461
1462 return false;
1463 }
1464
1465 /**
1466 * Whether to show the Amazon Creators API migration notice (legacy PA-API keys not required).
1467 *
1468 * @return bool
1469 */
1470 public static function show_amazon_credentials_notice() {
1471 $dismissed = self::cast_to_boolean( self::get_option( Constant::LASSO_OPTION_AMAZON_CREDENTIALS_NOTICE_DISMISSED, '0' ) );
1472 $dismissed_final = self::cast_to_boolean( self::get_option( Constant::LASSO_OPTION_AMAZON_CREDENTIALS_NOTICE_DISMISSED_FINAL, '0' ) );
1473 $updated = self::cast_to_boolean( self::get_option( Constant::LASSO_OPTION_AMAZON_CREDENTIALS_UPDATED, '0' ) );
1474
1475 $settings = Setting::get_settings();
1476 $creators_credential_id = trim( (string) ( $settings['amazon_creators_credential_id'] ?? '' ) );
1477 $creators_secret = trim( (string) ( $settings['amazon_creators_secret'] ?? '' ) );
1478 $creators_version = trim( (string) ( $settings['amazon_creators_version'] ?? '' ) );
1479 $creators_partner_tag = trim( (string) ( $settings['amazon_creators_partner_tag'] ?? '' ) );
1480 $has_creators_credentials = '' !== $creators_credential_id
1481 && '' !== $creators_secret
1482 && '' !== $creators_version
1483 && '' !== $creators_partner_tag;
1484
1485 $dismissed_fully = $dismissed && $dismissed_final;
1486
1487 return ! $dismissed_fully && ! $updated && ! $has_creators_credentials;
1488 }
1489
1490 /**
1491 * Whether the stored thumbnail is still the default placeholder.
1492 *
1493 * @param string $stored_thumbnail Post meta thumbnail.
1494 * @return bool
1495 */
1496 private static function uses_default_thumbnail( $stored_thumbnail ) {
1497 $stored_thumbnail = (string) $stored_thumbnail;
1498
1499 if ( '' === $stored_thumbnail ) {
1500 return true;
1501 }
1502
1503 if ( false !== strpos( $stored_thumbnail, Constant::DEFAULT_THUMBNAIL ) ) {
1504 return true;
1505 }
1506
1507 return false !== strpos( $stored_thumbnail, 'lasso-no-thumbnail.jpg' );
1508 }
1509
1510 /**
1511 * Whether the footer credentials banner should block the upsell CTA.
1512 *
1513 * The upsell may show after the customer dismisses the banner once, even if the
1514 * banner reappears on a later page load until the second dismiss is recorded.
1515 *
1516 * @return bool
1517 */
1518 private static function amazon_credentials_banner_blocks_upsell() {
1519 if ( ! self::show_amazon_credentials_notice() ) {
1520 return false;
1521 }
1522
1523 $dismissed_once = self::cast_to_boolean(
1524 self::get_option( Constant::LASSO_OPTION_AMAZON_CREDENTIALS_NOTICE_DISMISSED, '0' )
1525 );
1526
1527 return ! $dismissed_once;
1528 }
1529
1530 /**
1531 * Whether to show the Get Amazon Images upsell on URL Details.
1532 *
1533 * @param bool $is_amazon_link Whether the link is an Amazon URL.
1534 * @param string $stored_thumbnail Post meta thumbnail (before Amazon DB enrichment).
1535 * @param bool $is_amazon_configured Whether valid Amazon API credentials exist.
1536 * @return bool
1537 */
1538 public static function should_show_get_amazon_images_upsell( $is_amazon_link, $stored_thumbnail, $is_amazon_configured ) {
1539 if ( ! $is_amazon_link || $is_amazon_configured ) {
1540 return false;
1541 }
1542
1543 if ( ! self::uses_default_thumbnail( $stored_thumbnail ) ) {
1544 return false;
1545 }
1546
1547 return ! self::amazon_credentials_banner_blocks_upsell();
1548 }
1549
1550 /**
1551 * Whether the URL Details upsell should render hidden until the credentials banner is dismissed.
1552 *
1553 * @param bool $is_amazon_link Whether the link is an Amazon URL.
1554 * @param string $stored_thumbnail Post meta thumbnail (before Amazon DB enrichment).
1555 * @param bool $is_amazon_configured Whether valid Amazon API credentials exist.
1556 * @return bool
1557 */
1558 public static function should_defer_get_amazon_images_upsell( $is_amazon_link, $stored_thumbnail, $is_amazon_configured ) {
1559 if ( ! $is_amazon_link || $is_amazon_configured || ! self::uses_default_thumbnail( $stored_thumbnail ) ) {
1560 return false;
1561 }
1562
1563 return self::amazon_credentials_banner_blocks_upsell();
1564 }
1565
1566 /**
1567 * Get Ajax URL
1568 */
1569 public static function get_ajax_url() {
1570 return admin_url( 'admin-ajax.php' );
1571 }
1572
1573 /**
1574 * Get price value from price text including currency symbol.
1575 *
1576 * @param string $price_text Price text.
1577 * @param string $price_symbol Price symbol.
1578 * @return mixed|string
1579 */
1580 public static function get_price_value_from_price_text( $price_text, $price_symbol = '' ) {
1581 if ( preg_match( '/[]|R\$|TL|kr|zł/', $price_text ) || in_array( $price_symbol, array( '', 'R$', 'TL', 'kr', '' ), true ) ) {
1582 // ? For price use , as decimal separator and . as thousands separator.
1583 $replace_character = '.';
1584 $reg_pattern = '/\d+,?\d*/';
1585 } else {
1586 // ? For price use . as decimal separator and , as thousands separator.
1587 $replace_character = ',';
1588 $reg_pattern = '/\d+\.?\d*/';
1589 }
1590
1591 $price_without_thousands_separator = str_replace( $replace_character, '', $price_text );
1592 preg_match( $reg_pattern, $price_without_thousands_separator, $matches );
1593
1594 // ? Final format to general float number by replace ',' to '.'.
1595 return isset( $matches[0] ) ? str_replace( ',', '.', $matches[0] ) : '';
1596 }
1597
1598 /**
1599 * Get status from BLS
1600 *
1601 * @param string $url URL.
1602 * @param bool $get_res Get response or not. Default to false.
1603 * @param bool $is_lasso_save Is Lasso save data action. Default to false.
1604 * @param bool $url_version_param Is add version param to request api url to ignore cache. Default to false.
1605 * @param bool $force_bls Force fetch product from BLS or not. Default to false.
1606 * @param bool $refresh_image Bypass cache and fetch fresh product image/metadata. Default to false.
1607 */
1608 public static function get_url_status_code_by_broken_link_service( $url, $get_res = false, $is_lasso_save = false, $url_version_param = false, $force_bls = false, $refresh_image = false ) {
1609 $status = 200;
1610 $url = Amazon_Api::get_amazon_product_url( $url, false );
1611 $url = self::format_url_before_requesting( $url );
1612
1613 $headers = self::get_headers();
1614 $query = array(
1615 'url' => $url,
1616 );
1617
1618 if ( $url_version_param ) {
1619 $query['ver'] = time();
1620 }
1621
1622 if ( $force_bls ) {
1623 $query['force_bls'] = 1;
1624 }
1625
1626 if ( $refresh_image ) {
1627 $query['refresh_image'] = 1;
1628 }
1629
1630 $request_url = Constant::LASSO_LINK . '/link/status/?' . http_build_query( $query, '', '&', PHP_QUERY_RFC3986 );
1631 $res = self::send_request( 'get', $request_url, array(), $headers );
1632
1633 // phpcs:ignore
1634 // $res = self::send_request( 'get', LASSO_LINK . '/link/status/?' . $encrypted_base64, array(), $headers );
1635 if ( $get_res ) {
1636 return $res;
1637 }
1638
1639 $bls_response = $res['response'] ?? null;
1640 if ( ! is_object( $bls_response ) ) {
1641 return 500;
1642 }
1643
1644 return intval( $bls_response->status ?? $status );
1645 }
1646
1647 /**
1648 * Get currency symbol from ISO currency code
1649 *
1650 * @param string $iso Iso currency code.
1651 * @return string
1652 */
1653 public static function get_currency_symbol_from_iso_code( $iso ) {
1654 $iso = strtoupper( $iso );
1655 $result = '$';
1656
1657 $currencies = array(
1658 'USD' => '$',
1659 'AUD' => '$',
1660 'CAD' => '$',
1661 'EUR' => '',
1662 'MXN' => '$',
1663 'CNY' => '¥',
1664 'JPY' => '¥',
1665 'INR' => '',
1666 'SEK' => 'kr',
1667 'BRL' => 'R$',
1668 'TRY' => 'TL',
1669 'GBP' => '£',
1670 'PLN' => '',
1671 'EGP' => '',
1672 'SGD' => 'S$',
1673 'AED' => 'AED',
1674 );
1675
1676 return isset( $currencies[ $iso ] ) ? $currencies[ $iso ] : $result;
1677 }
1678
1679 /**
1680 * Check using WP classic editor
1681 *
1682 * @return bool
1683 */
1684 public static function is_classic_editor() {
1685 return self::is_classic_editor_plugin_active() || self::is_disable_gutenberg_plugin_active();
1686 }
1687
1688 /**
1689 * Escape string prevent SQL injection
1690 *
1691 * @param string $keyword Keyword.
1692 * @param bool $search_from_after Option to check search string from end of string.
1693 * @return string
1694 */
1695 public static function esc_like_query( $keyword, $search_from_after = false ) {
1696 global $wpdb;
1697
1698 $wild = '%';
1699 if ( $search_from_after ) {
1700 $query = $wpdb->esc_like( $keyword ) . $wild;
1701 } else {
1702 $query = $wild . $wpdb->esc_like( $keyword ) . $wild;
1703 }
1704
1705 $query = str_replace( ' ', '%', $query );
1706 return $query;
1707 }
1708
1709 /**
1710 * Sanitize HTML for safe display (formerly regex-based script strip).
1711 * Now delegates to wp_kses_post so event handlers and javascript: URLs are removed.
1712 *
1713 * Falsy input (null, false, empty string) is returned unchanged.
1714 *
1715 * @param string|null|false $html HTML code.
1716 * @return string|null|false Sanitized HTML, or the original falsy value.
1717 */
1718 public static function sanitize_script( $html ) {
1719 if ( ! $html ) {
1720 return $html;
1721 }
1722
1723 return wp_kses_post( (string) $html );
1724 }
1725
1726 /**
1727 * Verify access and nonce, then return wp_send_json_error if unverified.
1728 *
1729 * @param bool $allow_edit_post_access Allow access for editor, author, contributor.
1730 * @return void
1731 */
1732 public static function verify_access_and_nonce( $allow_edit_post_access = false ) {
1733 try {
1734 // ? Verify access token.
1735 if ( ! current_user_can( 'manage_options' ) ) {
1736 if ( $allow_edit_post_access ) {
1737 // ? Allow access for editor, author, contributor.
1738 // ? WP User Roles: https://wordpress.com/support/invite-people/user-roles/#:~:text=Editor%3A%20Has%20access%20to%20all,posts%20until%20they%20are%20published.
1739 $current_user_role = self::get_current_user_role();
1740 if ( ! in_array( $current_user_role, array( 'editor', 'author', 'contributor' ), true ) ) {
1741 wp_send_json_error( 'Access denied.' );
1742 }
1743 } else {
1744 wp_send_json_error( 'Access denied.' );
1745 }
1746 }
1747
1748 // ? Verify nonce.
1749 $data = array();
1750 $server = wp_unslash( $_SERVER );
1751 $method = $server['REQUEST_METHOD'] ?? '';
1752 if ( 'POST' === $method ) {
1753 $data = self::POST();
1754 } elseif ( 'GET' === $method ) {
1755 $data = self::GET();
1756 }
1757
1758 $nonce = $data['nonce'] ?? '';
1759 if ( false === wp_verify_nonce( $nonce, Constant::LASSO_LITE_NONCE . wp_salt() ) ) {
1760 wp_send_json_error( 'Nonce not verified.' );
1761 }
1762 } catch ( WPAjaxDieStopException $e ) {
1763 throw $e;
1764 } catch ( \Exception $e ) {
1765 wp_send_json_error( 'Verify access and nonce error.' );
1766 }
1767 }
1768
1769 /**
1770 * Get current user role
1771 *
1772 * @return string
1773 */
1774 public static function get_current_user_role() {
1775 if ( is_user_logged_in() ) {
1776 $user = wp_get_current_user();
1777 $roles = (array) $user->roles;
1778
1779 return $roles[0];
1780 } else {
1781 return 'guest';
1782 }
1783 }
1784
1785 /**
1786 * Build headers for Lasso API
1787 *
1788 * @param string $license_id License ID. Default to null.
1789 * @return array
1790 */
1791 public static function get_headers( $license_id = null ) {
1792 $license = $license_id ? $license_id : License::get_license();
1793 if ( ! is_string( $license ) && ! is_numeric( $license ) ) {
1794 $license = '';
1795 }
1796 $license = (string) $license;
1797
1798 $site_id = License::get_site_id();
1799 if ( ! is_string( $site_id ) && ! is_numeric( $site_id ) ) {
1800 $site_id = '';
1801 }
1802 $site_id = (string) $site_id;
1803
1804 $headers = array(
1805 'Content-Type' => 'application/json',
1806 'license' => $license,
1807 'site_id' => $site_id,
1808 'site_url' => rawurlencode( site_url() ),
1809 'is_lasso_lite' => '1',
1810 'email' => (string) get_option( 'admin_email', '' ),
1811 );
1812
1813 return $headers;
1814 }
1815
1816 /**
1817 * Build lsid
1818 *
1819 * @return false|string
1820 */
1821 public static function build_lsid() {
1822 $lsid = session_create_id( 'ls-' );
1823 if ( ! $lsid ) {
1824 $lsid = 'ls-' . md5( uniqid( wp_rand(), true ) );
1825 }
1826
1827 return $lsid;
1828 }
1829
1830 /**
1831 * Check if WP Elementor plugin is active.
1832 *
1833 * @return bool
1834 */
1835 public static function is_wp_elementor_plugin_actived() {
1836 return self::get_is_plugin_active( 'elementor/elementor.php' );
1837 }
1838
1839 /**
1840 * FOLLOW ALL REDIRECTS
1841 * This makes multiple requests, following each redirect until it reaches the final destination.
1842 *
1843 * @param string $url URL.
1844 * @param bool $is_lasso_save Is Lasso save data action. Default to false.
1845 * @param bool $get_page_title Get page title or not. Default to false.
1846 */
1847 public static function get_redirect_final_target( $url, $is_lasso_save = false, $get_page_title = false ) {
1848 // ? Get final url for amazon shortlink from cache
1849 $amazon_shortlink_final_url_cached = Amazon_Api::get_shortlink_final_url_cached( $url );
1850 if ( $amazon_shortlink_final_url_cached ) {
1851 return $get_page_title ? array( $amazon_shortlink_final_url_cached, get_option( Amazon_Api::build_shortlink_cache_key( $url ) . '_page_title' ) ) : $amazon_shortlink_final_url_cached;
1852 }
1853
1854 $origin_url = $url;
1855 $url = Amazon_Api::get_amazon_product_url( $url, $is_lasso_save ? true : false );
1856 $url = self::format_url_before_requesting( $url );
1857 $base_domain = self::get_base_domain( $url );
1858 $browser = self::get_server_param( 'HTTP_USER_AGENT' );
1859 $browser = '' !== $browser ? $browser : self::$user_agent;
1860 $use_bls = apply_filters( 'get_final_url_domain_bls', false, $url );
1861
1862 $cache_prefix = 'get_final_url_';
1863 $page_title = '';
1864
1865 // ? check result in cache first, it may be use before in the same request.
1866 $final_url_cache = Cache_Per_Process::get_instance()->get_cache( $cache_prefix . md5( $url ) . $get_page_title );
1867 if ( $final_url_cache ) {
1868 return $final_url_cache;
1869 }
1870 if ( Amazon_Api::is_amazon_url( $url ) && Amazon_Api::get_product_id_by_url( $url ) ) {
1871 return $get_page_title ? array( $url, $page_title ) : $url;
1872 }
1873
1874 $res = wp_remote_get(
1875 $url,
1876 array(
1877 'headers' => array(
1878 'user-agent' => $browser,
1879 ),
1880 )
1881 );
1882
1883 $is_amazon_shortened_url = Amazon_Api::is_amazon_shortened_url( $url );
1884
1885 $status_code = is_wp_error( $res ) ? 500 : $res['response']['code'] ?? '';
1886 if ( 200 === $status_code || 429 === $status_code ) {
1887 $new_url = $res['http_response']->get_response_object()->url;
1888 $use_bls = apply_filters( 'get_final_url_domain_bls', false, $new_url );
1889 }
1890
1891 if ( $is_amazon_shortened_url ) {
1892 $use_bls = true;
1893 }
1894
1895 if ( is_wp_error( $res ) || $use_bls || 403 === $status_code ) {
1896 $headers = self::get_headers();
1897 $data = array(
1898 'url' => $url,
1899 );
1900 $encrypted_base64 = http_build_query( $data );
1901 $res = self::send_request( 'get', Constant::LASSO_LINK . '/link/final-url/?' . $encrypted_base64, array(), $headers );
1902
1903 $bls_response = ( isset( $res['response'] ) && is_object( $res['response'] ) ) ? $res['response'] : null;
1904 $final_url = ( null !== $bls_response ) ? ( $bls_response->finalUrl ?? $url ) : $url;
1905 $page_title = ( null !== $bls_response ) ? ( $bls_response->pageTitle ?? '' ) : '';
1906
1907 $bls_status = ( null !== $bls_response && isset( $bls_response->status ) ) ? intval( $bls_response->status ) : 0;
1908 if ( $bls_status ) {
1909 $response_status = $bls_status;
1910 } elseif ( 200 === intval( $res['status_code'] ?? 0 ) ) {
1911 $response_status = 500;
1912 } else {
1913 $response_status = intval( $res['status_code'] ?? 500 );
1914 }
1915
1916 // ? Set the response status code for add new link process
1917 Cache_Per_Process::get_instance()->set_cache( Affiliate_Link::ADD_NEW_LINK_RESPONSE_STATUS . md5( $origin_url ), $response_status );
1918
1919 $tmp_url = self::get_final_url_from_url_param( $final_url );
1920 if ( $tmp_url ) {
1921 $page_title = self::get_title_by_url( $tmp_url );
1922 $final_url = $tmp_url;
1923 }
1924
1925 // ? cache result
1926 $result = $get_page_title ? array( $final_url, $page_title ) : $final_url;
1927 Cache_Per_Process::get_instance()->set_cache( $cache_prefix . md5( $url ) . $get_page_title, $result );
1928
1929 // ? Cache the final url of amazon shortlink
1930 if ( $is_amazon_shortened_url ) {
1931 $shortlink_cache_key = Amazon_Api::build_shortlink_cache_key( $url );
1932 update_option( $shortlink_cache_key, $final_url );
1933 // ? Cache the page title of amazon shortlink
1934 update_option( $shortlink_cache_key . '_page_title', $page_title );
1935 }
1936
1937 return $result;
1938 }
1939
1940 $http_response = $res['http_response']->get_response_object();
1941 $status = wp_remote_retrieve_response_code( $res );
1942
1943 // ? Set the response status code for add new link process
1944 Cache_Per_Process::get_instance()->set_cache( Affiliate_Link::ADD_NEW_LINK_RESPONSE_STATUS . md5( $origin_url ), $status );
1945
1946 $final_url = $http_response->url;
1947 $page_title = self::get_page_title( $http_response->body );
1948 if ( strpos( $page_title, 'Please Wait...' ) !== false
1949 || strpos( $page_title, 'Cloudflare' ) !== false
1950 || strpos( $page_title, 'Access Denied' ) !== false
1951 || strpos( $page_title, 'Just a moment...' ) !== false
1952 ) {
1953 $page_title = self::get_title_by_url( $final_url );
1954 }
1955
1956 $tmp_url = self::get_final_url_from_url_param( $final_url );
1957 if ( $tmp_url ) {
1958 $page_title = self::get_title_by_url( $tmp_url );
1959 $final_url = $tmp_url;
1960 }
1961
1962 // ? Cache the final url of amazon shortlink
1963 if ( $is_amazon_shortened_url ) {
1964 $shortlink_cache_key = Amazon_Api::build_shortlink_cache_key( $url );
1965 update_option( $shortlink_cache_key, $final_url );
1966 // ? Cache the page title of amazon shortlink
1967 update_option( $shortlink_cache_key . '_page_title', $page_title );
1968 }
1969
1970 if ( ! $page_title || Affiliate_Link::DEFAULT_TITLE === $page_title ) {
1971 $page_title = self::get_title_by_url( $final_url );
1972 }
1973
1974 // ? cache result
1975 $result = $get_page_title ? array( $final_url, $page_title ) : $final_url;
1976 Cache_Per_Process::get_instance()->set_cache( $cache_prefix . md5( $url ) . $get_page_title, $result );
1977
1978 return $result;
1979 }
1980
1981 /**
1982 * Get page title from HTML
1983 *
1984 * @param string $html HTML string.
1985 */
1986 public static function get_page_title( $html ) {
1987 $temp = explode( '<title', $html )[1] ?? '';
1988 $html = $temp ? '<title' . $temp : $html;
1989 $temp = explode( '</title>', $html )[0] ?? '';
1990 $html = $temp ? $temp . '</title>' : $html;
1991 $res = preg_match( '/<title\s*(.*?)>(.*?)<\/title>/siU', $html, $title_matches );
1992 if ( ! $res ) {
1993 return '';
1994 }
1995
1996 // ? Clean up title: remove EOL's and excessive whitespace.
1997 $title = preg_replace( '/\s+/', ' ', $title_matches[2] ?? '' );
1998 // ? String – to UTF8 is \xe2\x80\x93, replace by -
1999 $title = str_replace( '', '-', $title );
2000 // ? Remove all non-US-ASCII (i.e. outside 0x0-0x7F) characters
2001 $title = preg_replace( '/[^\x00-\x7F]/', '', $title );
2002 $title = trim( $title );
2003 $title = self::format_post_title( $title );
2004
2005 return $title;
2006 }
2007
2008 /**
2009 * Build the dynamic query variable name for serving the performance snippet.
2010 *
2011 * Derive a domain-specific key to reduce collisions and support per-domain routing.
2012 *
2013 * @return string Query var name (md5 hash of the base domain)
2014 */
2015 public static function get_snippet_query() {
2016 $domain = self::get_base_domain( site_url() );
2017 if ( empty( $domain ) ) {
2018 return 'lasso_connect_snippet_lite';
2019 }
2020 return md5( $domain );
2021 }
2022
2023 /**
2024 * Derive Intercom user_id from email with optional JWT override.
2025 * If a JWT with a non-empty `user_id` claim is present, that value is used;
2026 * otherwise the md5 hash of the lowercased+trimmed email is returned.
2027 * Callers may pass raw or pre-normalized email; the function normalizes
2028 * defensively to keep the derived user_id deterministic.
2029 *
2030 * @param string $user_email Email (raw or pre-normalized); trimmed and lowercased internally.
2031 * @param string $intercom_user_jwt Optional Intercom JWT token used to override
2032 * the email-based user_id when valid.
2033 *
2034 * @return string Intercom user_id derived from JWT or email hash.
2035 */
2036 public static function get_intercom_user_id( $user_email, $intercom_user_jwt ) {
2037 // MD5 is used only for a deterministic Intercom identifier; not for secrets.
2038 // Normalize email casing to keep intercom user_id deterministic.
2039 $normalized_email = is_string( $user_email ) ? strtolower( trim( $user_email ) ) : '';
2040 $intercom_user_id = md5( $normalized_email );
2041 if ( ! is_string( $intercom_user_jwt ) || '' === trim( $intercom_user_jwt ) ) {
2042 return $intercom_user_id;
2043 }
2044
2045 // JWT is expected to be issued by our backend; no signature verification here.
2046 $jwt_parts = explode( '.', $intercom_user_jwt );
2047 if ( 3 === count( $jwt_parts ) ) {
2048 $payload = $jwt_parts[1];
2049 $payload = strtr( $payload, '-_', '+/' );
2050 $payload_length = strlen( $payload );
2051 $payload_padding = $payload_length % 4;
2052 // Pad base64 payload to the next multiple of 4 so decode succeeds.
2053 if ( 0 !== $payload_padding ) {
2054 $payload = str_pad( $payload, $payload_length + 4 - $payload_padding, '=', STR_PAD_RIGHT );
2055 }
2056 $payload_decoded = base64_decode( $payload, true );
2057 if ( false !== $payload_decoded ) {
2058 $decoded = json_decode( $payload_decoded );
2059 if ( JSON_ERROR_NONE === json_last_error() && is_object( $decoded ) && isset( $decoded->user_id ) && is_scalar( $decoded->user_id ) ) {
2060 $user_id_claim = (string) $decoded->user_id;
2061 if ( '' !== trim( $user_id_claim ) ) {
2062 $intercom_user_id = $user_id_claim;
2063 }
2064 }
2065 }
2066 }
2067
2068 return $intercom_user_id;
2069 }
2070 }
2071