PluginProbe
Lasso Lite – Affiliate Link Manager & Product Displays / 112
Lasso Lite – Affiliate Link Manager & Product Displays v112
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 112, at classes/class-helper.php

1,351 lines 40.0 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\Admin\Constant;
11
12 use LassoLite\Classes\Amazon_Api;
13 use LassoLite\Classes\Enum;
14 use LassoLite\Classes\Cache_Per_Process;
15 use LassoLite\Classes\Setting;
16
17 use LassoLite\Models\Model;
18 use LassoLite\Models\Url_Details;
19
20
21 use Exception;
22
23 /**
24 * Lasso_Helper
25 */
26 class Helper {
27
28 /**
29 * User agent
30 *
31 * @var string $user_agent
32 */
33 public static $user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0';
34
35 /**
36 * GET PHP POST
37 *
38 * @return array|string
39 */
40 public static function POST() { // phpcs:ignore
41 return wp_unslash( $_POST ); // phpcs:ignore
42 }
43
44 /**
45 * GET PHP GET
46 *
47 * @return array|string
48 */
49 public static function GET() { // phpcs:ignore
50 return wp_unslash( $_GET ); // phpcs:ignore
51 }
52
53 /**
54 * Include variables
55 *
56 * @param string $file_path File path.
57 * @param array $variables List of variables.
58 * @param bool $output_ajax_html Output a ajax html string or not.
59 */
60 public static function include_with_variables( $file_path, $variables = array(), $output_ajax_html = true ) {
61 $output = null;
62 if ( file_exists( $file_path ) ) {
63 extract( $variables ); // phpcs:ignore
64 if ( $output_ajax_html ) {
65 ob_start();
66 include $file_path;
67 $output = ob_get_clean();
68 return $output;
69 } else {
70 require $file_path;
71 }
72 }
73
74 return $output;
75 }
76
77 /**
78 * Get path to views folder
79 *
80 * @return string
81 */
82 public static function get_path_views_folder() {
83 return SIMPLE_URLS_DIR . 'admin/views/';
84 }
85
86 /**
87 * Enqueue a Lasso script.
88 *
89 * @param string $handle Name of the script. Should be unique.
90 * @param string $file_name Lasso script file name.
91 * @param array $deps Optional. An array of registered script handles this script depends on. Default empty array.
92 * @param bool $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>. Default 'false'.
93 */
94 public static function enqueue_script( $handle, $file_name, $deps = array(), $in_footer = false ) {
95 $handle = SIMPLE_URLS_SLUG . '-' . $handle;
96 $file_path = SIMPLE_URLS_DIR . '/admin/assets/js/' . $file_name;
97
98 if ( file_exists( $file_path ) ) {
99 $src = SIMPLE_URLS_URL . '/admin/assets/js/' . $file_name;
100 $ver = strval( @filemtime( $file_path ) ); // phpcs:ignore
101
102 wp_enqueue_script( $handle, $src, $deps, $ver, $in_footer );
103 }
104 }
105
106 /**
107 * Enqueue a Lasso CSS stylesheet.
108 *
109 * @param string $handle Name of the stylesheet. Should be unique.
110 * @param string $file_name Lasso stylesheet file name.
111 * @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
112 * @param string $media Optional. The media for which this stylesheet has been defined.
113 * Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
114 * '(orientation: portrait)' and '(max-width: 640px)'.
115 * @param bool $apply_handle_prefix Is apply prefix for handle. Default to true.
116 */
117 public static function enqueue_style( $handle, $file_name, $deps = array(), $media = 'all', $apply_handle_prefix = true ) {
118 $handle = $apply_handle_prefix ? SIMPLE_URLS_SLUG . '-' . $handle : $handle;
119 $file_path = SIMPLE_URLS_DIR . '/admin/assets/css/' . $file_name;
120
121 if ( file_exists( $file_path ) ) {
122 $src = SIMPLE_URLS_URL . '/admin/assets/css/' . $file_name;
123 $ver = strval( @filemtime( $file_path ) ); // phpcs:ignore
124
125 wp_enqueue_style( $handle, $src, $deps, $ver, $media );
126 }
127 }
128
129 /**
130 * Get list page
131 *
132 * @return Page[]
133 */
134 public static function available_pages() {
135 $pages[ Enum::PAGE_DASHBOARD ] = new Page( 'Dashboard', Enum::PAGE_DASHBOARD, 'dashboard/index.php' );
136 $pages[ Enum::PAGE_OPPORTUNITIES ] = new Page( 'Opportunities', Enum::PAGE_OPPORTUNITIES, 'opportunities/index.php' );
137 $pages[ Enum::PAGE_IMPORT ] = new Page( 'Import', Enum::PAGE_IMPORT, 'import/index.php' );
138 $pages[ Enum::PAGE_TABLES ] = new Page( 'Tables', Enum::PAGE_TABLES, 'tables/index.php' );
139 $pages[ Enum::PAGE_URL_DETAILS ] = new Page( 'Link Details', Enum::PAGE_URL_DETAILS, '/dashboard/url-details.php' );
140
141 $pages[ Enum::PAGE_SETTINGS_GENERAL ] = new Page( 'General', Enum::PAGE_SETTINGS_GENERAL, 'settings/general.php' );
142 $pages[ Enum::PAGE_SETTINGS_DISPLAY ] = new Page( 'Display', Enum::PAGE_SETTINGS_DISPLAY, 'settings/display.php' );
143 $pages[ Enum::PAGE_SETTINGS_AMAZON ] = new Page( 'Amazon', Enum::PAGE_SETTINGS_AMAZON, 'settings/amazon.php' );
144 $pages[ Enum::PAGE_GROUPS ] = new Page( 'Groups', Enum::PAGE_GROUPS, '/groups/index.php' );
145 $pages[ Enum::PAGE_GROUP_DETAIL ] = new Page( 'Group Detail', Enum::PAGE_GROUP_DETAIL, '/groups/detail.php' );
146
147 if ( get_option( Enum::LASSO_LITE_ACTIVE ) && ! self::get_option( Enum::IS_VISITED_WELCOME_PAGE ) ) {
148 $pages[ Enum::PAGE_ONBOARDING ] = new Page( 'Onboarding', Enum::PAGE_ONBOARDING, 'onboarding/index.php' );
149 }
150
151 return $pages;
152 }
153
154 /**
155 * Convert date time to WordPress format
156 *
157 * @param string $datetime Date time. Format must be 'Y-m-d H:i:s' (example: 2018-09-14 10:34:54).
158 * @param bool $time Is it time. Default to true.
159 */
160 public static function convert_datetime_format( $datetime, $time = true ) {
161 if ( ! $datetime ) {
162 return $datetime;
163 }
164
165 $date_format = get_option( 'date_format' );
166 $time_format = 'g:i a T';
167 $datetime_format = $date_format . ' ' . $time_format;
168 $format = ( $time ) ? $datetime_format : $date_format;
169
170 try {
171 $result = date_create_from_format( 'Y-m-d H:i:s', $datetime );
172 $result = $result->format( $format );
173 } catch ( \Exception $e ) {
174 $result = $datetime;
175 }
176
177 return $result;
178 }
179
180 /**
181 * Add surl prefix
182 *
183 * @param string $page Page name.
184 * @return string
185 */
186 public static function add_prefix_page( $page ) {
187 return SIMPLE_URLS_SLUG . '-' . $page;
188 }
189
190 /**
191 * Print a wrapper for js render library
192 *
193 * @param string $html_id_selector Html id selector.
194 * @param string $file_path Absolute path file.
195 * @return string|null
196 */
197 public static function wrapper_js_render( $html_id_selector, $file_path ) {
198 $output = '<script id="' . $html_id_selector . '" type="text/x-jsrender">';
199 $output .= self::include_with_variables( $file_path, array(), true );
200 $output .= '</script>';
201 return $output;
202 }
203
204 /**
205 * Check a url has protocol or not
206 *
207 * @param string $url URL.
208 * @return bool
209 */
210 public static function has_protocol( $url ) {
211 if ( strpos( $url, 'http' ) === 0 || strpos( $url, 'https' ) === 0 ) {
212 return true;
213 }
214 return false;
215 }
216
217 /**
218 * Check if Classic Editor plugin is active.
219 *
220 * @return bool
221 */
222 public static function is_classic_editor_plugin_active() {
223 return self::get_is_plugin_active( 'classic-editor/classic-editor.php' );
224 }
225
226 /**
227 * If the Lasso Pro plugin is installed, return true. Otherwise, return false
228 */
229 public static function is_lasso_pro_installed() {
230 return self::get_is_plugin_active( 'lasso/affiliate-plugin.php' );
231 }
232
233 /**
234 * If the Lasso Pro plugin is active, return true. Otherwise, return false
235 */
236 public static function is_lasso_pro_plugin_active() {
237 return self::is_lasso_pro_installed() && self::get_license_status();
238 }
239
240 /**
241 * Get license status in DB
242 */
243 public static function get_license_status() {
244 $db_status = get_option( 'lasso_license_status', '' );
245 $active_license = boolval( $db_status );
246
247 return $active_license;
248 }
249
250 /**
251 * Check whether slug exists or not
252 *
253 * @param string $post_name Post name.
254 * @param int $post_id Post id. Default to 0.
255 */
256 public static function the_slug_exists( $post_name, $post_id = 0 ) {
257
258 $posts_tbl = Model::get_wp_table_name( 'posts' );
259 $sql = '
260 SELECT
261 ID,
262 post_name,
263 post_type
264 FROM '
265 . $posts_tbl . '
266 WHERE
267 post_name = %s
268 AND ID != %d
269 AND post_status <> "trash"
270 LIMIT 1
271 ';
272
273 $prepare = Model::prepare( $sql, $post_name, $post_id ); // phpcs:ignore
274 $row = Model::get_row( $prepare, 'ARRAY_A' ); // phpcs:ignore
275
276 return $row ? $row : false;
277 }
278
279 /**
280 * Add https to the url
281 *
282 * @param string $url URL.
283 */
284 public static function add_https( $url ) {
285 $invalid_url = array(
286 'https://%20https:/',
287 'https://xhttps://',
288 'http:/https://',
289 'http://https://',
290 'https://https://',
291 'https://hhttps://',
292 'https://]https://',
293 'https://&quot;https://',
294 '[gift_item link=&quot;https://',
295 ']https://',
296 );
297 $url = trim( $url );
298 $url = str_replace( $invalid_url, 'https://', $url );
299
300 // ? fix mailto in <a> href
301 if ( strpos( $url, 'mailto:' ) !== false || filter_var( $url, FILTER_VALIDATE_EMAIL ) ) {
302 $email = explode( 'mailto:', $url )[1] ?? '';
303 if ( filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
304 $url = 'mailto:' . $email;
305 }
306
307 return $url;
308 }
309
310 if ( '' === $url || is_null( $url ) || strpos( $url, '[' ) === 0 ) {
311 return $url;
312 }
313
314 if ( strpos( $url, 'http://' ) !== 0 && strpos( $url, 'https://' ) !== 0 && strpos( $url, '.' ) !== false && '#' !== $url ) {
315 $url = 'https://' . $url;
316 }
317
318 return $url;
319 }
320
321 /**
322 * Format URL before sending request
323 *
324 * @param string $url URL.
325 * @param bool $encode Encode url or not. Default to false.
326 */
327 public static function format_url_before_requesting( $url, $encode = false ) {
328 $url = trim( $url );
329 $url = $encode ? rawurlencode( $url ) : $url;
330
331 return $url;
332 }
333
334 /**
335 * Get title by url
336 *
337 * @param string $url URL.
338 */
339 public static function get_title_by_url( $url ) {
340 $url = self::add_https( $url );
341 $parse = wp_parse_url( $url );
342 $host = $parse['host'] ?? '';
343 $host = str_replace( 'www.', '', $host );
344 $host = explode( '.', $host );
345 $host = $host[ count( $host ) - 2 ] ?? '';
346 $host = str_replace( '-', ' ', $host );
347 $host = ucwords( $host );
348
349 return $host;
350 }
351
352 /**
353 * Remove a action out of WordPress hook
354 *
355 * Example 1: Lasso_Helper::remove_action('admin_print_footer_scripts', 'register_tinymce_quicktags'); $callback is a function name.
356 * Example 2: Lasso_Helper::remove_action('admin_print_footer_scripts', array('EarnistProductPicker', 'register_tinymce_quicktags')); $callback is array.
357 *
358 * @param string $hook_name Hook name.
359 * @param array|string $callback Callback function. $callback[0] is a class name, $callback[1] is a function name.
360 * @param int $priority Priority.
361 */
362 public static function remove_action( $hook_name, $callback, $priority = 10 ) {
363 global $wp_filter;
364
365 if ( ! isset( $wp_filter[ $hook_name ]->callbacks[ $priority ] ) ) {
366 return;
367 }
368
369 foreach ( $wp_filter[ $hook_name ]->callbacks[ $priority ] as $key_function_name_wp => $data ) {
370 $should_remove = false;
371 $obj_name_wp = null;
372 $function_name_wp = is_array( $data['function'] ) ? $data['function'][1] : $data['function'];
373 if ( ! is_array( $callback ) ) {
374 $function_name = $callback;
375 if ( $function_name_wp === $function_name ) {
376 $should_remove = true;
377 }
378 } else {
379 list( $object_name, $function_name ) = $callback;
380 if ( gettype( $object_name ) === 'object' ) {
381 $object_name = get_class( $object_name );
382 }
383
384 if ( ! $data['function'] instanceof \Closure ) {
385 $obj_name_wp = $data['function'][0];
386 if ( gettype( $obj_name_wp ) === 'object' ) {
387 $obj_name_wp = get_class( $obj_name_wp );
388 if ( $obj_name_wp === $object_name && $function_name_wp === $function_name ) {
389 $should_remove = true;
390 }
391 }
392 }
393 }
394
395 if ( $should_remove ) {
396 unset( $wp_filter[ $hook_name ]->callbacks[ $priority ][ $key_function_name_wp ] );
397 break;
398 }
399 }
400 }
401
402 /**
403 * It includes the HTML for the display modal dialogs
404 *
405 * @return string the html for the modal.
406 */
407 public static function get_display_modal_html() {
408 $html = self::include_with_variables( Helper::get_path_views_folder() . 'modals/display-add.php' ); // phpcs:ignore
409 $html .= self::include_with_variables( Helper::get_path_views_folder() . 'modals/url-add.php', array( 'is_from_editor' => true, ) ); // phpcs:ignore
410 $html .= self::include_with_variables( Helper::get_path_views_folder() . 'modals/url-quick-detail.php' ); // phpcs:ignore
411 $html .= self::wrapper_js_render( 'single-list', Helper::get_path_views_folder() . 'modals/single-jsrender.html' ); // phpcs:ignore
412 $html .= self::wrapper_js_render( 'url-quick-detail-jsrender', Helper::get_path_views_folder() . 'components/url-quick-detail-jsrender.html' ); // phpcs:ignore
413
414 return $html;
415 }
416
417 /**
418 * Get countries of Amazon
419 *
420 * @param string $selected_country Country code.
421 */
422 public static function get_countries_dd( $selected_country ) {
423 $countries = Amazon_Api::get_amazon_api_countries();
424
425 $countries_dd = '<select id="amazon_default_tracking_country" name="amazon_default_tracking_country" class="form-control">';
426 foreach ( $countries as $key => $country ) {
427 if ( strlen( $key ) !== 2 ) {
428 continue;
429 }
430
431 $selected = '';
432 if ( $selected_country === $key ) {
433 $selected = 'selected';
434 }
435 $countries_dd .= '<option value="' . $key . '" ' . $selected . ' >' . $country['name'] . '</option>';
436 }
437 $countries_dd .= '</select>';
438
439 return $countries_dd;
440 }
441
442 /**
443 * Get setup progress information
444 *
445 * @return array
446 */
447 public static function get_setup_progress_information() {
448 $enable_support = boolval( Setting::get_setting( Enum::SUPPORT_ENABLED ) ) ? 20 : 0;
449 $total_links = SURL::total();
450 $links = $total_links > 20 ? 20 : $total_links;
451 $links_percent = 20 === $links ? 100 : ( $links / 20 ) * 100;
452 $setup_amz_tracking_id = boolval( get_option( Enum::SETUP_AMZ_TRACKING_ID ) ) ? 15 : 0;
453 $follow_on_twitter = boolval( get_option( Enum::FOLLOW_ON_TWITTER ) ) ? 10 : 0;
454 $share_on_twitter = boolval( get_option( Enum::SHARE_ON_TWITTER ) ) ? 10 : 0;
455 $leave_a_review = boolval( get_option( Enum::LEAVE_A_REVIEW ) ) ? 5 : 0;
456 $is_show_review_note = ! $leave_a_review && $total_links >= 20 && $enable_support && $setup_amz_tracking_id && $follow_on_twitter && $share_on_twitter ? 1 : 0;
457 $progress = $enable_support + $setup_amz_tracking_id + $follow_on_twitter + $share_on_twitter + ( $links * 2 ) + $leave_a_review;
458 $progress = $progress ? $progress / 100 : 0;
459 $open_modal_add_link = $links < 20 ? 'btn-add-20-links' : '';
460
461 $data = array(
462 'progress' => round( $progress, 2 ),
463 'progress_percent' => round( $progress * 100 ),
464 'links' => $links,
465 'links_percent' => $links_percent,
466 'setup_amz_tracking_id' => $setup_amz_tracking_id,
467 'follow_on_twitter' => $follow_on_twitter,
468 'share_on_twitter' => $share_on_twitter,
469 'leave_a_review' => $leave_a_review,
470 'is_show_review_note' => $is_show_review_note,
471 'enable_support' => $enable_support,
472 'setting_amz_url' => Page::get_lite_page_url( Enum::PAGE_SETTINGS_AMAZON ),
473 'follow_twitter_url' => home_url() . '?' . Enum::SLUG_CLOAK_FOLLOW_TWITTER,
474 'share_twitter_url' => home_url() . '?' . Enum::SLUG_CLOAK_SHARE_TWITTER,
475 'review_url' => home_url() . '?' . Enum::SLUG_CLOAK_LASSO_REVIEW_URL,
476 'open_modal_add_link' => $open_modal_add_link,
477 );
478
479 return $data;
480 }
481
482 /**
483 * Send request
484 *
485 * @param string $method Method (get or post). Default to get.
486 * @param string $url URL. Default to empty.
487 * @param array $data Post data. Default to empty array.
488 * @param array $headers Headers. Default to empty array.
489 * @param bool $is_lasso_save Is Lasso save data action. Default to false.
490 */
491 public static function send_request( $method = 'get', $url = '', $data = array(), $headers = array(), $is_lasso_save = false ) {
492 $method = strtolower( $method );
493 $request_options = array(
494 'headers' => $headers,
495 'timeout' => Constant::TIME_OUT,
496 'sslverify' => Constant::SSL_VERIFY,
497 );
498 $body = wp_json_encode( $data );
499 $headers_expect = ! empty( $body ) && strlen( $body ) > 1048576 ? '100-Continue' : '';
500 if ( 'get' === $method ) {
501 $res = wp_remote_get( $url, $request_options );
502 } elseif ( 'post' === $method ) {
503 $request_options['headers']['expect'] = $headers_expect;
504 $request_options['body'] = $body;
505 $res = wp_remote_post( $url, $request_options );
506 } elseif ( 'put' === $method ) {
507 $request_options['headers']['expect'] = $headers_expect;
508 $request_options['body'] = $body;
509 $request_options['method'] = 'PUT';
510 $res = wp_remote_request( $url, $request_options );
511 }
512
513 if ( is_wp_error( $res ) ) {
514 return array(
515 'status_code' => 500,
516 'response' => array(),
517 );
518 }
519
520 $body = wp_remote_retrieve_body( $res );
521 $status = wp_remote_retrieve_response_code( $res );
522
523 return array(
524 'status_code' => $status,
525 'response' => json_decode( $body ),
526 );
527 }
528
529 /**
530 * Get Lasso Lite - WP option
531 *
532 * @param string $option_name Option name.
533 * @param mixed $default Default value.
534 * @return mixed|void
535 */
536 public static function get_option( $option_name, $default = false ) {
537 return get_option( SIMPLE_URLS_SLUG . '_' . $option_name, $default );
538 }
539
540 /**
541 * Update Lasso Lite - WP option
542 *
543 * @param string $option_name Option name.
544 * @param mixed $option_value Option value.
545 * @param bool $autoload Autoload.
546 * @return bool
547 */
548 public static function update_option( $option_name, $option_value, $autoload = null ) {
549 return update_option( SIMPLE_URLS_SLUG . '_' . $option_name, $option_value, $autoload );
550 }
551
552 /**
553 * Validate URL
554 *
555 * @param string $url URL.
556 */
557 public static function validate_url( $url ) {
558 if ( ! is_string( $url ) ) {
559 return false;
560 }
561
562 $url = str_replace( ' ', '%20', $url );
563 $url = preg_replace( '/[^\00-\255]+/u', '', $url );
564
565 return ( ( strpos( $url, 'http://' ) === 0 || strpos( $url, 'https://' ) === 0 ) &&
566 filter_var( $url, FILTER_VALIDATE_URL ) !== false );
567 }
568
569 /**
570 * Get argument from url
571 *
572 * @param string $link Amazon link.
573 * @param string $argument URL argument.
574 * @return string
575 */
576 public static function get_argument_from_url( $link, $argument ) {
577 if ( ! $argument ) {
578 return '';
579 }
580
581 $link = str_replace( '&amp;', '&', $link );
582 $parse = wp_parse_url( $link );
583 $queries = array();
584 parse_str( $parse['query'] ?? '', $queries );
585
586 return $queries[ $argument ] ?? '';
587 }
588
589 /**
590 * Build url parameter string
591 * Parameter example: array( 'rel_=abc', 'maas=def' );
592 *
593 * @param array $parameters Parameter key=value array.
594 * @return string|mixed
595 */
596 public static function build_url_parameter_string( $parameters = array() ) {
597 $result = implode( '&', $parameters );
598 $result = preg_replace( '!\&+!', '&', $result );
599 $result = trim( $result, '&' );
600
601 return $result;
602 }
603
604 /**
605 * Convert query array to a string (in a url)
606 *
607 * @param array $query Query.
608 */
609 public static function get_query_from_array( $query ) {
610 $result = array();
611 foreach ( $query as $key => $value ) {
612 $result[] = $key . '=' . rawurlencode( $value );
613 }
614 $result = implode( '&', $result );
615 return $result;
616 }
617
618 /**
619 * Convert parse_url() to a url
620 *
621 * @param array $parse Parse data from a URL.
622 * @param bool $host Is it host. Default to false.
623 */
624 public static function get_url_from_parse( $parse, $host = false ) {
625 $parse['host'] = $parse['host'] ?? '';
626 $host = false !== $host ? $host : $parse['host'];
627 $parse['host'] = '://' . $host;
628 $parse['query'] = isset( $parse['query'] ) ? '?' . $parse['query'] : '';
629
630 return implode( '', $parse );
631 }
632
633 /**
634 * Get param of $_SERVER
635 *
636 * @param string $name Name of param.
637 */
638 public static function get_server_param( $name ) {
639 return wp_unslash( $_SERVER[ $name ] ?? '' ); // phpcs:ignore
640 }
641
642 /**
643 * Check if Lasso URL description is empty.
644 *
645 * @param string $description Lasso URL description.
646 * @return bool
647 */
648 public static function is_description_empty( $description ) {
649 if ( empty( $description ) || '<p><br></p>' === $description ) {
650 return true;
651 }
652
653 return false;
654 }
655
656 /**
657 * Get base domain
658 * Ex: "http://domain.com" would return "domain.com"
659 *
660 * @param string $domain Domain. It must be passed WITH protocol. Default to empty.
661 */
662 public static function get_base_domain( $domain = '' ) {
663 $domain = self::add_https( $domain );
664 if ( ! self::validate_url( $domain ) ) {
665 return '';
666 }
667
668 $url = @wp_parse_url( $domain ); // phpcs:ignore
669 $host = $url['host'] ?? '';
670 $host = str_replace( 'www.', '', $host );
671 $host = trim( $host );
672
673 return $host ? $host : '';
674 }
675
676 /**
677 * Convert stdclass object to array
678 *
679 * @param bool $obj StdClass object.
680 */
681 public static function convert_stdclass_to_array( $obj ) {
682 $array = json_decode( wp_json_encode( $obj ), true );
683
684 return $array;
685 }
686
687 /**
688 * Check if mysql error relative to Lasso's table does not exist
689 *
690 * @param string $mysql_error mysql error.
691 * @return boolean
692 */
693 public static function is_lasso_tables_does_not_exist_error( $mysql_error ) {
694 return (bool) preg_match( "/(\.)(.)*lasso_(.)*doesn\'t(\s)exist/", $mysql_error );
695 }
696
697 /**
698 * If the AAWP plugin is active, return true. Otherwise, return false
699 */
700 public static function is_aawp_active() {
701 return is_plugin_active( 'aawp/aawp.php' );
702 }
703
704 /**
705 * If the Amalinks Pro plugin is active, return true. Otherwise, return false
706 */
707 public static function is_amalinks_pro_active() {
708 return is_plugin_active( 'amalinkspro/amalinkspro.php' );
709 }
710
711 /**
712 * Format importable data before showing/importing/reverting
713 *
714 * @param object $p Importable post.
715 */
716 public static function format_importable_data( $p ) {
717 $lasso_db = new Lasso_DB();
718 $lasso_helper = new Helper();
719 $lasso_amazon_api = new Amazon_Api();
720
721 $home_url = home_url();
722 $p->import_permalink = get_permalink( $p->id );
723
724 if ( 'Pretty Links' === $p->import_source ) {
725 $pretty_link_data = $lasso_db->get_pretty_link_by_id( $p->id );
726 $defaul_permalink = $home_url . '/' . $pretty_link_data->slug . '/';
727
728 $prlipro = get_option( 'prlipro_options', array() );
729 $prlipro = is_array( $prlipro ) ? $prlipro : array();
730 $base_slug_prefix = $prlipro['base_slug_prefix'] ?? '';
731 $p->import_permalink = '' !== $base_slug_prefix && strpos( $p->post_name, $base_slug_prefix ) === false
732 ? $home_url . '/' . $base_slug_prefix . '/' . $p->post_name . '/'
733 : $defaul_permalink;
734 } elseif ( 'AAWP' === $p->import_source ) {
735 $aawp_row = $lasso_db->get_aawp_product( $p->id );
736 $p->import_permalink = $aawp_row->url ?? '';
737 $shortcode = '[amazon link="' . $p->post_name . '"]';
738 $p->shortcode = $shortcode;
739
740 // ? AAWP list
741 if ( 'aawp_list' === $p->post_type ) {
742 $p->import_permalink = 'https://amazon.com/s?k=' . $p->post_title;
743 $p->check_status = self::check_aawp_list_is_imported( $p->id ) ? 'checked' : '';
744
745 $cat = term_exists( $p->post_title, Constant::LASSO_CATEGORY );
746 $p->check_status = $cat ? 'checked' : $p->check_status;
747
748 $aawp_list = $lasso_db->get_aawp_list( $p->id );
749 if ( $aawp_list ) {
750 $items_count = $aawp_list->items_count ?? 0;
751 $attr_type = 'bestseller' === $aawp_list->type ? 'bestseller' : 'link';
752 $attr_type = 'new_releases' === $aawp_list->type ? 'new' : $attr_type;
753 $shortcode = '[amazon ' . $attr_type . '="' . $aawp_list->keywords . '" items="' . $items_count . '"]';
754 $p->shortcode = $shortcode;
755 }
756 }
757 } elseif ( 'EasyAzon' === $p->import_source ) {
758 $product = get_option( $p->id, true );
759 $p->post_title = $product['title'];
760 $p->id = $product['identifier'];
761 $p->import_permalink = $product['url'];
762 $p->post_name = strtolower( $product['identifier'] );
763 $shortcode = '[easyazon_link identifier="' . $p->id . '"]' . $p->post_title . '[/easyazon_link]';
764 $p->shortcode = $shortcode;
765
766 $revert = $lasso_db->is_easyazon_product_imported( $product['identifier'] );
767 if ( $revert ) {
768 $p->check_status = 'checked';
769 $p->id = $revert->lasso_id;
770 }
771 } elseif ( 'AmaLinks Pro' === $p->import_source ) {
772 $shortcode = $p->post_name;
773 $attributes = $lasso_helper->get_attributes( $shortcode );
774 $p->shortcode = $shortcode;
775 $p->import_permalink = $attributes['apilink'] ?? '';
776 if ( empty( $p->id ) ) {
777 $p->id = $attributes['asin'] ?? $p->id;
778
779 $url_details = $lasso_db->get_url_details_by_product_id( $p->id, Amazon_Api::PRODUCT_TYPE );
780 $lasso_id = $url_details->lasso_id ?? 0;
781 $p->check_status = $lasso_id > 0 ? 'checked' : '';
782
783 }
784 if ( empty( $p->import_permalink ) ) {
785 $p->import_permalink = $lasso_amazon_api->get_amazon_link_by_product_id( $p->id );
786 }
787 }
788
789 return $p;
790 }
791
792 /**
793 * Get attributes of shortcode
794 *
795 * @param string $tags_data Shortcode string.
796 */
797 public static function get_attributes( $tags_data ) {
798 // ? fix shortcode contains content: [shortcode]content[/shortcode]
799 preg_match_all(
800 '/' . get_shortcode_regex() . '/',
801 $tags_data,
802 $matches,
803 PREG_SET_ORDER
804 );
805 $content_between = $matches[0][5] ?? '';
806 $shortcode_name = $matches[0][2] ?? '';
807 if ( ! empty( $shortcode_name ) ) {
808 // ? remove content between content/anchor text
809 $tags_data = str_replace( ']' . $content_between . '[', '][', $tags_data );
810 // ? remove the end shortcode
811 $tags_data = str_replace( '[/' . $shortcode_name . ']', '', $tags_data );
812 }
813 $tags_data = str_replace( '/]', ']', $tags_data );
814
815 $attributes = array();
816 $parse = shortcode_parse_atts( esc_html( $tags_data ) );
817
818 $temp_key = 'temp';
819 foreach ( $parse as $key => $value ) {
820 if ( ! is_integer( $key ) ) {
821 $temp_key = $key;
822 $attributes[ $key ] = self::remove_special_character_in_attributes( $value );
823 } else {
824 // ? join data with old key.
825 $attributes[ $temp_key ] = trim( ( $attributes[ $temp_key ] ?? '' ) . ' ' . self::remove_special_character_in_attributes( $value ) );
826 }
827 }
828 unset( $attributes['temp'] );
829
830 return $attributes;
831 }
832
833 /**
834 * Remove special character in attributes
835 *
836 * @param string $text Text string.
837 */
838 public static function remove_special_character_in_attributes( $text ) {
839 $text = str_replace( 'u0026', '&', $text );
840 $text = str_replace( array( '\\', 'u0022', '&quot;', 'u003c', 'u003e', '&lt;', '&gt;' ), '', $text );
841 return htmlspecialchars_decode( trim( $text, ']' ), ENT_QUOTES );
842 }
843
844 /**
845 * Check whether aawp list id is imported to Lasso or not
846 *
847 * @param int $id Aawp list id.
848 */
849 public static function check_aawp_list_is_imported( $id ) {
850 $lasso_db = new Lasso_DB();
851
852 $sql = '
853 SELECT lud.product_id
854 FROM ' . ( new Url_Details() )->get_table_name() . ' AS lud
855 LEFT JOIN ' . $lasso_db->posts . ' AS p
856 ON lud.lasso_id = p.ID
857 WHERE lud.product_id != \'\'
858 AND p.ID is not null
859 AND lud.product_type = \'' . Amazon_Api::PRODUCT_TYPE . '\'
860 ';
861 $row = $lasso_db->get_col( $sql );
862 $amazon_ids = $row ? $row : array();
863
864 $aawp_list = $lasso_db->get_aawp_list( $id );
865 $aawp_amazon_ids = $aawp_list->product_asins ?? '';
866 $aawp_amazon_ids = '' !== $aawp_amazon_ids ? explode( ',', $aawp_amazon_ids ) : array();
867
868 $same_elements = array_intersect( $aawp_amazon_ids, $amazon_ids );
869
870 return $aawp_amazon_ids === $same_elements;
871 }
872
873 /**
874 * Paginate items by a sql query
875 * Reset page number if results is empty.
876 *
877 * @param string $sql Sql query.
878 * @param int $page Number of page.
879 * @param int $limit Number of results. Default to 10.
880 */
881 public static function paginate( $sql, &$page, $limit = 10 ) {
882 $start_index = ( $page - 1 ) * $limit;
883 $pagination_sql = $sql . ' LIMIT ' . $start_index . ', ' . $limit;
884
885 if ( $page > 1 ) {
886 $result = Model::get_row( $pagination_sql );
887
888 if ( ! $result ) {
889 $page = 1;
890 $pagination_sql = $sql . ' LIMIT 0, ' . $limit;
891 }
892 }
893
894 return $pagination_sql;
895 }
896
897 /**
898 * Format "post title" to escape html and apply limit length.
899 *
900 * @param Title $title lasso urls title.
901 * @param int $limit_length limit length of title.
902 */
903 public static function format_post_title( $title, $limit_length = 200 ) {
904 $title = esc_html( $title );
905
906 if ( strlen( $title ) > $limit_length ) {
907 $title = substr( $title, 0, $limit_length ) . '...';
908 }
909
910 return $title;
911 }
912
913 /**
914 * Get unique post name of lasso post
915 *
916 * @param int $post_id Post id.
917 * @param string $post_name Post name.
918 */
919 public static function lasso_unique_post_name( $post_id, $post_name ) {
920 if ( intval( $post_id ) > 0 && ! empty( $post_name ) && self::the_slug_exists( $post_name, $post_id ) ) {
921 $post_name .= '-link';
922 $post_name = wp_unique_post_slug( $post_name, $post_id, 'publish', Constant::LASSO_POST_TYPE, 0 );
923 }
924
925 return $post_name;
926 }
927
928 /**
929 * Get plugin status result
930 *
931 * @param string $plugin Plugin key.
932 * @return bool
933 */
934 public static function get_is_plugin_active( $plugin ) {
935 $cache_result = Cache_Per_Process::get_instance()->get_cache( 'is_plugin_active_' . md5( $plugin ), null );
936 if ( null !== $cache_result ) {
937 return $cache_result;
938 }
939
940 if ( ! function_exists( 'is_plugin_active' ) ) {
941 include_once ABSPATH . 'wp-admin/includes/plugin.php';
942 }
943
944 $result = \is_plugin_active( $plugin );
945 Cache_Per_Process::get_instance()->set_cache( 'is_plugin_active_' . md5( $plugin ), $result );
946
947 return $result;
948 }
949
950 /**
951 *
952 * Check plugin earnist is loaded https://www.getearnist.com.
953 *
954 * @return bool
955 */
956 public static function is_earnist_plugin_loaded() {
957 return self::get_is_plugin_active( 'earnist/earnist.php' );
958 }
959
960 /**
961 *
962 * Check plugin Shortcode Star Rating is loaded https://github.com/modshrink/shortcode-star-rating.
963 *
964 * @return bool
965 */
966 public static function is_shortcode_start_rating_plugin_loaded() {
967 return self::get_is_plugin_active( 'shortcode-star-rating/shortcode-star-rating.php' );
968 }
969
970 /**
971 * Check plugin "Easy Table of Contents" is activated
972 *
973 * @return bool
974 */
975 public static function is_plugin_easy_table_of_contents_activated() {
976 return self::get_is_plugin_active( 'easy-table-of-contents/easy-table-of-contents.php' );
977 }
978
979 /**
980 * Check if Gravity Perks plugin is active.
981 *
982 * @return bool
983 */
984 public static function is_gravity_perks_plugin_active() {
985 return self::get_is_plugin_active( 'gravityperks/gravityperks.php' );
986 }
987
988 /**
989 * Check if Ezoic plugin is active.
990 *
991 * @return bool
992 */
993 public static function is_ezoic_plugin_active() {
994 return self::get_is_plugin_active( 'ezoic-integration/ezoic-integration.php' );
995 }
996
997 /**
998 * Check if WP Rocket - Lazyload is enabled.
999 *
1000 * @return bool
1001 */
1002 public static function is_wp_rocket_lazyload_image_enabled() {
1003 if ( self::get_is_plugin_active( 'wp-rocket/wp-rocket.php' ) ) {
1004 // ? Check if layzyload enabled
1005 $wp_rocket_settings = get_option( 'wp_rocket_settings', array() );
1006 if ( $wp_rocket_settings['lazyload'] ?? false ) {
1007 return true;
1008 }
1009 }
1010
1011 return false;
1012 }
1013
1014 /**
1015 * Check whether current page is WP post page
1016 */
1017 public static function is_wordpress_post() {
1018 global $pagenow;
1019
1020 $get = wp_unslash( $_GET ); // phpcs:ignore
1021 $action = $get['action'] ?? '';
1022 $add_new_page = 'post-new.php' === $pagenow;
1023 $edit_page = 'post.php' === $pagenow && 'edit' === $action;
1024 $post_type = $get['post_type'] ?? '';
1025
1026 if ( ( 'edit.php' === $pagenow || $add_new_page ) && '' === $post_type ) {
1027 $post_type = 'post';
1028 } elseif ( $add_new_page ) {
1029 $post_type = $get['post_type'] ?? $post_type;
1030 } elseif ( $edit_page ) {
1031 $post_id = intval( $get['post'] ?? 0 );
1032 $post_type = $post_id > 0 ? get_post_type( $post_id ) : $post_type;
1033 }
1034
1035 if ( 'term.php' === $pagenow ) {
1036 $post_type = '';
1037 }
1038
1039 return 'post' === $post_type || 'page' === $post_type;
1040 }
1041
1042 /**
1043 * Cast the value to boolean
1044 *
1045 * @param bool|string $value A string boolean like "true" or "false".
1046 *
1047 * @return bool
1048 */
1049 public static function cast_to_boolean( $value ) {
1050 return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
1051 }
1052
1053 /**
1054 * Get CPU load of server/hosting
1055 */
1056 public static function get_cpu_load() {
1057 $load = null;
1058
1059 if ( stristr( PHP_OS, 'win' ) ) {
1060 $cmd = 'wmic cpu get loadpercentage /all';
1061 @exec( $cmd, $output ); // phpcs:ignore
1062
1063 if ( $output ) {
1064 foreach ( $output as $line ) {
1065 if ( $line && preg_match( '/^[0-9]+$/', $line ) ) {
1066 $load = $line;
1067 break;
1068 }
1069 }
1070 }
1071 } else {
1072 try {
1073 if ( @is_readable( '/proc/stat' ) ) { // phpcs:ignore
1074 $cached_cpu_load = Cache_Per_Process::get_instance()->get_cache( 'cpu_load', null );
1075 if ( $cached_cpu_load ) {
1076 $stat_data1 = $cached_cpu_load;
1077 } else {
1078 $stat_data1 = self::get_server_load_linux_data();
1079 }
1080
1081 // ? Collect 2 samples - each with 1 second period
1082 // ? See: https://de.wikipedia.org/wiki/Load#Der_Load_Average_auf_Unix-Systemen
1083 sleep( 1 );
1084
1085 $stat_data2 = self::get_server_load_linux_data();
1086 Cache_Per_Process::get_instance()->set_cache( 'cpu_load', $stat_data2 );
1087
1088 if ( ( ! is_null( $stat_data1 ) ) && ( ! is_null( $stat_data2 ) ) ) {
1089 // ? Get difference
1090 $stat_data2[0] -= $stat_data1[0];
1091 $stat_data2[1] -= $stat_data1[1];
1092 $stat_data2[2] -= $stat_data1[2];
1093 $stat_data2[3] -= $stat_data1[3];
1094
1095 // ? Sum up the 4 values for User, Nice, System and Idle and calculate
1096 // ? the percentage of idle time (which is part of the 4 values!)
1097 $cpu_time = $stat_data2[0] + $stat_data2[1] + $stat_data2[2] + $stat_data2[3];
1098
1099 // ? Invert percentage to get CPU time, not idle time
1100 $load = 100 - ( $stat_data2[3] * 100 / max( $cpu_time, 1 ) );
1101 }
1102 }
1103 } catch ( Exception $e ) {
1104 $load = 0; // Just run because we can't detect CPU load.
1105 }
1106 }
1107
1108 return round( $load, 2 );
1109 }
1110
1111 /**
1112 * Get server load linux data
1113 */
1114 private static function get_server_load_linux_data() {
1115 if ( @is_readable( '/proc/stat' ) ) { // phpcs:ignore
1116 $stats = @file_get_contents( '/proc/stat' ); // phpcs:ignore
1117
1118 if ( false !== $stats ) {
1119 // ? Remove double spaces to make it easier to extract values with explode()
1120 $stats = preg_replace( '/[[:blank:]]+/', ' ', $stats );
1121
1122 // ? Separate lines
1123 $stats = str_replace( array( "\r\n", "\n\r", "\r" ), "\n", $stats );
1124 $stats = explode( "\n", $stats );
1125
1126 // ? Separate values and find line for main CPU load
1127 foreach ( $stats as $stat_line ) {
1128 $stat_line_data = explode( ' ', trim( $stat_line ) );
1129
1130 // ? Found
1131 if ( count( $stat_line_data ) >= 5 && 'cpu' === $stat_line_data[0] ) {
1132 return array(
1133 $stat_line_data[1],
1134 $stat_line_data[2],
1135 $stat_line_data[3],
1136 $stat_line_data[4],
1137 );
1138 }
1139 }
1140 }
1141 }
1142
1143 return null;
1144 }
1145
1146 /**
1147 * Remove unexpected character from post title
1148 *
1149 * @param string $post_title Post title.
1150 * @return string
1151 */
1152 public static function remove_unexpected_characters_from_post_title( $post_title ) {
1153 // ? Remove unexpected character.
1154 $post_title = preg_replace( "/[^A-Za-z0-9\s`~!@#$%^&:;\/\?\"\'\+\=\.\,\-\_\*\(\)\|\[\]\<\>\{\}\\\]/", ' ', $post_title );
1155 // ? Remove duplicated space.
1156 $post_title = preg_replace( '/\s\s+/', ' ', $post_title );
1157
1158 return $post_title;
1159 }
1160
1161 /**
1162 * Check whether this install is new
1163 */
1164 public static function is_new_install() {
1165 return boolval( get_option( Enum::LASSO_LITE_ACTIVE ) );
1166 }
1167
1168 /**
1169 * Build image lazyload attributes
1170 *
1171 * @return string
1172 */
1173 public static function build_img_lazyload_attributes() {
1174 $result = 'loading="lazy"'; // ? WP default lazyload.
1175
1176 if ( self::is_ezoic_plugin_active() ) {
1177 $result = 'class="ezlazyload"';
1178 } elseif ( self::is_wp_rocket_lazyload_image_enabled() ) {
1179 $result = 'class="rocket-lazyload"';
1180 }
1181
1182 return $result;
1183 }
1184
1185 /**
1186 * Check whether import page should display
1187 *
1188 * @return bool
1189 */
1190 public static function should_show_import_page() {
1191 return ! empty( ( new Lasso_DB() )->get_import_plugins( true ) ) ? true : false;
1192 }
1193
1194 /**
1195 * Get brag icon
1196 *
1197 * @param bool $force_to_show Force to show the brag. Default to false.
1198 */
1199 public static function get_brag_icon( $force_to_show = false ) {
1200 $cache_key = 'lasso_lite_brag_icon';
1201 $brag_cache = Cache_Per_Process::get_instance()->get_cache( $cache_key, '' );
1202
1203 if ( $brag_cache ) {
1204 return $brag_cache;
1205 }
1206
1207 $lasso_settings = Setting::get_settings();
1208
1209 $enable_brag_mode = $lasso_settings['enable_brag_mode'] ?? false;
1210 $lasso_url = $lasso_settings['lasso_affiliate_URL'] ?? false;
1211
1212 if ( $lasso_url && ( $force_to_show || $enable_brag_mode ) ) {
1213 $icon_brag = esc_url( SIMPLE_URLS_URL . '/admin/assets/images/lasso-icon-brag.svg' );
1214 $lasso_affiliate_url = self::add_params_to_url( $lasso_url, array( 'utm_source' => 'brag' ) );
1215 $img_attr = self::build_img_lazyload_attributes();
1216 $icon = '
1217 <a class="lasso-brag" href="' . $lasso_affiliate_url . '" target="_blank" rel="nofollow noindex">
1218 <img src="' . $icon_brag . '" ' . $img_attr . ' alt="Lasso Brag" width="30" height="30">
1219 </a>
1220 ';
1221
1222 Cache_Per_Process::get_instance()->set_cache( $cache_key, $icon );
1223
1224 return $icon;
1225 }
1226
1227 return '';
1228 }
1229
1230 /**
1231 * Add params to URL
1232 *
1233 * @param string $url URL.
1234 * @param array $params Params.
1235 */
1236 public static function add_params_to_url( $url, $params ) {
1237 // ? parse url
1238 $parse = wp_parse_url( $url );
1239 parse_str( $parse['query'] ?? '', $query );
1240
1241 $query = array_merge( $query, $params );
1242 $query = self::get_query_from_array( $query );
1243 $parse['query'] = $query;
1244
1245 return self::get_url_from_parse( $parse );
1246
1247 }
1248
1249 /**
1250 * Get final url in the url
1251 * Example: https://affiliate.com/redirect?url=https://getlasso.co
1252 *
1253 * @param string $url URL.
1254 */
1255 public static function get_final_url_from_url_param( $url ) {
1256 if ( ! self::validate_url( $url ) ) {
1257 return false;
1258 }
1259
1260 $final_url = false;
1261 $base_domain = self::get_base_domain( $url );
1262
1263 if ( self::is_shareasale_url( $url ) ) {
1264 $final_url = self::get_argument_from_url( $url, 'urllink' );
1265 } elseif ( 'pntra.com' === $base_domain ) {
1266 $final_url = self::get_argument_from_url( $url, 'url' );
1267 } elseif ( 'wordseed.com' === $base_domain ) {
1268 $final_url = self::get_argument_from_url( $url, 'url' );
1269 } elseif ( 'titan.fitness' === $base_domain ) {
1270 $final_url = 'https://www.titan.fitness' . self::get_argument_from_url( $url, 'redirect' );
1271 } else { // ? other urls
1272 $parse = wp_parse_url( $url );
1273 $queries = array();
1274 parse_str( $parse['query'] ?? '', $queries );
1275 foreach ( $queries as $key => $param ) {
1276 if ( 'referrer' === $key ) {
1277 continue;
1278 }
1279
1280 $param = str_replace( ' ', '%20', $param );
1281 if ( self::validate_url( $param ) ) {
1282 $final_url = $param;
1283 break;
1284 }
1285 }
1286 }
1287 $final_url = self::add_https( $final_url );
1288
1289 if ( empty( $final_url ) || ! self::validate_url( $final_url ) ) {
1290 $final_url = false;
1291 } else {
1292 $final_url = trim( $final_url, '/' );
1293 }
1294
1295 return $final_url;
1296 }
1297
1298 /**
1299 * Check whether url is shareasale domain or not
1300 *
1301 * @param string $url URL.
1302 * @return bool
1303 */
1304 public static function is_shareasale_url( $url ) {
1305 $allow_domains = array( 'shareasale.com', 'shareasale-analytics.com' );
1306 $domain = self::get_base_domain( $url );
1307
1308 return in_array( $domain, $allow_domains, true );
1309 }
1310
1311 /**
1312 * Check importable
1313 *
1314 * @return bool
1315 */
1316 public static function is_importable() {
1317 $lasso_lite_db = new Lasso_DB();
1318 $sql = $lasso_lite_db->get_importable_urls_query( true );
1319 $post = Model::get_row( $sql );
1320
1321 if ( ! empty( $post ) ) {
1322 $post->post_title = self::format_post_title( $post->post_title ?? '' );
1323 $post->shortcode = '';
1324
1325 // ? Get import target permalinks
1326 $post = self::format_importable_data( $post );
1327
1328 // ? Check first record from list import
1329 return 'checked' !== $post->check_status;
1330 }
1331
1332 return false;
1333 }
1334
1335 /**
1336 * Check whether Lite is using new or old UI
1337 *
1338 * @return bool true: new UI. false: old UI.
1339 */
1340 public static function is_lite_using_new_ui() {
1341 $new_ui = get_option( Enum::SWITCH_TO_NEW_UI );
1342 $new_ui = self::cast_to_boolean( $new_ui );
1343
1344 if ( ! $new_ui ) {
1345 return false;
1346 }
1347
1348 return true;
1349 }
1350 }
1351