PluginProbe
BetterLinks – Link Shortener, Link Cloaking, Redirects, Affiliate Link Manager & MCP / 2.4.13
BetterLinks – Link Shortener, Link Cloaking, Redirects, Affiliate Link Manager & MCP v2.4.13
3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 3.0.0 2.4.13 2.4.12 2.4.11 2.4.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 All 110 releases
betterlinks / includes / Helper.php

Helper.php in BetterLinks – Link Shortener, Link Cloaking, Redirects, Affiliate Link Manager & MCP 2.4.13, at includes/Helper.php

972 lines 34.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace BetterLinks;
3 if ( ! defined( 'ABSPATH' ) ) { exit; }
4
5 use BetterLinks\Admin\Cache;
6 use WP_Http;
7 use DeviceDetector\DeviceDetector;
8 use DeviceDetector\Parser\OperatingSystem;
9 use DeviceDetector\Parser\Device\AbstractDeviceParser;
10 use DeviceDetector\Parser\Client\Browser;
11
12 class Helper {
13
14 use Traits\Query;
15 use Traits\Clicks;
16
17 // phpcs:disable PluginCheck.Security.DirectDB, WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL
18
19 public static function btl_menu_notice() {
20 return BETTERLINKS_MENU_NOTICE !== get_option( 'betterlinks_menu_notice', 0 );
21 }
22 public static function get_links() {
23 if ( BETTERLINKS_EXISTS_LINKS_JSON ) {
24 $data = json_decode( file_get_contents( BETTERLINKS_UPLOAD_DIR_PATH . '/links.json' ), true );
25 if ( empty( $data ) ) {
26 $cron = new Cron();
27 $cron->write_json_links();
28 return json_decode( file_get_contents( BETTERLINKS_UPLOAD_DIR_PATH . '/links.json' ), true );
29 }
30 return $data;
31 }
32 $options = json_decode( get_option( BETTERLINKS_LINKS_OPTION_NAME ), true );
33 return is_array( $options )
34 ? array(
35 'wildcards_is_active' => isset( $options['wildcards'] ) ? $options['wildcards'] : false,
36 'disablebotclicks' => isset( $options['disablebotclicks'] ) ? $options['disablebotclicks'] : false,
37 'force_https' => isset( $options['force_https'] ) ? $options['force_https'] : false,
38 'autolink_disable_post_types' => isset( $options['autolink_disable_post_types'] ) ? $options['autolink_disable_post_types'] : array(),
39 'is_autolink_icon' => isset( $options['is_autolink_icon'] ) ? $options['is_autolink_icon'] : false,
40 'is_autolink_headings' => isset( $options['is_autolink_headings'] ) ? $options['is_autolink_headings'] : false,
41 'uncloaked_categories' => isset( $options['uncloaked_categories'] ) ? $options['uncloaked_categories'] : array(),
42 'is_disable_analytics_ip' => isset( $options['is_disable_analytics_ip'] ) ? $options['is_disable_analytics_ip'] : false,
43 'excluded_ips' => isset( $options['excluded_ips'] ) ? $options['excluded_ips'] : array(),
44 )
45 : array(
46 'wildcards_is_active' => false,
47 'disablebotclicks' => false,
48 'force_https' => false,
49 'excluded_ips' => array(),
50 );
51 }
52
53 public static function get_link_from_json_file( $short_url ) {
54 if ( empty( $short_url ) ) {
55 return;
56 }
57 global $betterlinks;
58 if ( ! ( isset( $betterlinks['is_case_sensitive'] ) && $betterlinks['is_case_sensitive'] ) ) {
59 $short_url = strtolower( $short_url );
60 }
61 if ( isset( $betterlinks['links'][ $short_url ] ) ) {
62 return $betterlinks['links'][ $short_url ];
63 }
64 if ( isset( $betterlinks['wildcards_is_active'] ) && $betterlinks['wildcards_is_active'] ) {
65 if ( isset( $betterlinks['wildcards'] ) && count( $betterlinks['wildcards'] ) > 0 ) {
66 foreach ( $betterlinks['wildcards'] as $key => $item ) {
67 $postion = strpos( $key, '/*' );
68 if ( false !== $postion ) {
69 if ( substr( $key, 0, $postion ) === substr( $short_url, 0, $postion ) ) {
70 $target_postion = strpos( $item['target_url'], '/*' );
71 if ( false !== $target_postion ) {
72 $target_url = str_replace( '/*', substr( $short_url, $postion ), $item['target_url'] );
73 $item['target_url'] = $target_url;
74 return $item;
75 }
76 return $item;
77 }
78 }
79 }
80 }
81 }
82 }
83
84 public static function get_menu_items() {
85 // $enable_custom_domain_menu = get_option(BETTERLINKS_CUSTOM_DOMAIN_MENU, 0);
86 $enable_custom_domain_menu = Cache::get_json_settings();
87 $enable_custom_domain_menu = !empty( $enable_custom_domain_menu['enable_custom_domain_menu'] ) ? $enable_custom_domain_menu['enable_custom_domain_menu'] : false;
88
89 $menu_items = array(
90 BETTERLINKS_PLUGIN_SLUG => array(
91 'title' => __( 'Manage Links', 'betterlinks' ),
92 'capability' => 'manage_options',
93 ),
94 BETTERLINKS_PLUGIN_SLUG . '-manage-tags-and-categories' => array(
95 'title' => __( 'Tags & Categories', 'betterlinks' ),
96 'capability' => 'manage_options',
97 ),
98 BETTERLINKS_PLUGIN_SLUG . '-analytics' => array(
99 'title' => __( 'Analytics', 'betterlinks' ),
100 'capability' => 'manage_options',
101 ),
102 BETTERLINKS_PLUGIN_SLUG . '-link-scanner' => array(
103 'title' => __( 'Link Scanner', 'betterlinks' ),
104 'capability' => 'manage_options',
105 ),
106 BETTERLINKS_PLUGIN_SLUG . '-settings' => array(
107 'title' => __( 'Settings', 'betterlinks' ),
108 'capability' => 'manage_options',
109 ),
110 );
111
112
113 if( get_option( 'betterlinks_quick_setup_step' ) !== 'complete' ){
114 $menu_items[BETTERLINKS_PLUGIN_SLUG . '-quick-setup'] = array(
115 'title' => __( 'Quick Setup', 'betterlinks' ),
116 'capability' => 'manage_options',
117 );
118 }
119
120 if( !empty( $enable_custom_domain_menu ) ){
121 $before = array_splice( $menu_items, 0, 2 );
122 $inserted = array(
123 BETTERLINKS_PLUGIN_SLUG . '-custom-domain' => array(
124 'title' => __( 'Custom Domain', 'betterlinks' ),
125 'capability' => 'manage_options',
126 ),
127 );
128 $menu_items = $before + $inserted + $menu_items;
129 }
130
131 return apply_filters( 'betterlinks/helper/menu_items', $menu_items );
132 }
133
134 /**
135 * Check Supported Post type for admin page and plugin main settings page
136 *
137 * @return bool
138 */
139 public static function plugin_page_hook_suffix( $hook ) {
140 if ( 'toplevel_page_' . BETTERLINKS_PLUGIN_SLUG === $hook ) {
141 return true;
142 } else {
143 foreach ( self::get_menu_items() as $key => $value ) {
144 if ( BETTERLINKS_PLUGIN_SLUG . '_page_' . $key === $hook || strpos( $hook, BETTERLINKS_PLUGIN_SLUG . '_page_' . $key ) || strpos( '_' . $hook, 'betterlinks' ) ) {
145 return true;
146 }
147 }
148 }
149 return false;
150 }
151
152 public static function make_slug( $str ) {
153 if ( empty( $str ) ) {
154 return;
155 }
156 if ( $str !== mb_convert_encoding( mb_convert_encoding( $str, 'UTF-32', 'UTF-8' ), 'UTF-8', 'UTF-32' ) ) {
157 $str = mb_convert_encoding( $str, 'UTF-8', mb_detect_encoding( $str ) );
158 }
159 $str = htmlentities( $str, ENT_NOQUOTES, 'UTF-8' );
160 $str = preg_replace( '`&([a-z]{1,2})(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig);`i', '\\1', $str );
161 $str = html_entity_decode( $str, ENT_NOQUOTES, 'UTF-8' );
162 $str = preg_replace( array( '`[^a-z0-9]`i', '`[-]+`' ), '-', $str );
163 $str = strtolower( trim( $str, '-' ) );
164 $str = substr( $str, 0, 100 );
165 return $str;
166 }
167
168 public static function link_exists( $title, $slug = '' ) {
169 global $wpdb;
170
171 $link_title = wp_unslash( sanitize_post_field( 'link_title', $title, 0, 'db' ) );
172 $short_url = wp_unslash( sanitize_post_field( 'short_url', $slug, 0, 'db' ) );
173 $betterlinks = $wpdb->prefix . 'betterlinks';
174 $query = "SELECT link_title, short_url FROM $betterlinks WHERE ";
175 $args = array();
176
177 if ( ! empty( $title ) ) {
178 $query .= ' link_title = %s';
179 $args[] = $link_title;
180 }
181
182 if ( ! empty( $slug ) ) {
183 $query .= ' AND short_url = %s';
184 $args[] = $short_url;
185 }
186
187 if ( ! empty( $args ) ) {
188 $results = $wpdb->get_var( $wpdb->prepare( $query, $args ) );
189 if ( ! empty( $results ) ) {
190 return true;
191 }
192 return;
193 }
194 }
195 public static function term_exists( $slug ) {
196 global $wpdb;
197
198 $term_slug = wp_unslash( sanitize_post_field( 'term_slug', $slug, 0, 'db' ) );
199 $betterlinks = $wpdb->prefix . 'betterlinks_terms';
200 $query = "SELECT term_slug FROM $betterlinks WHERE ";
201 $args = array();
202
203 if ( ! empty( $slug ) ) {
204 $query .= ' term_slug = %s';
205 $args[] = $term_slug;
206 }
207
208 if ( ! empty( $args ) ) {
209 $results = $wpdb->get_var( $wpdb->prepare( $query, $args ) );
210 if ( ! empty( $results ) ) {
211 return true;
212 }
213 return;
214 }
215 }
216 public static function click_exists( $ID ) {
217 global $wpdb;
218 $click_ID = wp_unslash( sanitize_post_field( 'ID', $ID, 0, 'db' ) );
219 $betterlinks = $wpdb->prefix . 'betterlinks_clicks';
220 $query = "SELECT ID FROM $betterlinks WHERE ";
221 $args = array();
222
223 if ( ! empty( $click_ID ) ) {
224 $query .= ' ID = %d';
225 $args[] = $click_ID;
226 }
227
228 if ( ! empty( $args ) ) {
229 $results = $wpdb->get_var( $wpdb->prepare( $query, $args ) );
230 if ( ! empty( $results ) ) {
231 return true;
232 }
233 return;
234 }
235 }
236
237 public static function create_cron_jobs_for_json_links() {
238 wp_clear_scheduled_hook( 'betterlinks/write_json_links' );
239 wp_schedule_single_event( time() + 5, 'betterlinks/write_json_links' );
240 }
241
242 public static function write_links_inside_json() {
243 $cron = new Cron();
244 $cron->write_json_links();
245 }
246
247 public static function create_cron_jobs_for_analytics() {
248 wp_clear_scheduled_hook( 'betterlinks/analytics' );
249 wp_schedule_single_event( time() + 5, 'betterlinks/analytics' );
250 }
251
252 public static function clear_query_cache() {
253 delete_transient( BETTERLINKS_CACHE_LINKS_NAME );
254 }
255
256 public static function create_links_cache() {
257 $results = self::get_prepare_all_links();
258 set_transient( BETTERLINKS_CACHE_LINKS_NAME, json_encode( $results ) );
259 }
260
261 public static function parse_link_response( $items, $analytic, $broken_links ) {
262 $results = array();
263 $broken_link_status_codes = array( 401, 403, 404 );
264
265 $tags_list = array();
266
267 foreach ( $items as $item ) {
268 if ( null !== $item->ID && 'tags' === $item->term_type ) {
269 array_push( $tags_list, $item );
270 }
271 }
272
273 foreach ( $items as $item ) {
274 if ( 'category' === $item->term_type ) {
275 if ( null === $item->ID ) {
276 continue;
277 }
278 // insert analytic data.
279 if ( isset( $analytic[ $item->ID ] ) ) {
280 $item->analytic = $analytic[ $item->ID ];
281 }
282 if ( ! empty( $item->param_struct ) ) {
283 $item->param_struct = unserialize( $item->param_struct );
284 }
285 if ( class_exists( '\BetterLinksPro' ) ) {
286 $custom_tracking_scripts = self::get_link_meta( $item->ID, 'btl_custom_tracking_scripts' );
287 if ( ! empty( $custom_tracking_scripts ) ) {
288 $custom_tracking_scripts = unserialize( $custom_tracking_scripts );
289 $item->enable_custom_scripts = isset( $custom_tracking_scripts['enable'] ) ? $custom_tracking_scripts['enable'] : false;
290 $item->custom_tracking_scripts = isset( $custom_tracking_scripts['script'] ) ? $custom_tracking_scripts['script'] : '';
291 }
292 }
293
294 if ( isset( $broken_links[ $item->ID ] ) && is_array( $broken_links[ $item->ID ] ) && isset( $broken_links[ $item->ID ]['status']['status_code'] ) && in_array( $broken_links[ $item->ID ]['status']['status_code'], $broken_link_status_codes ) && empty( $broken_links[ $item->ID ]['is_log_removed'] ) ) {
295 $item->link_status = 'broken';
296 } elseif ( 'broken' === $item->link_status && isset( $broken_links[ $item->ID ] ) && is_array( $broken_links[ $item->ID ] ) && isset( $broken_links[ $item->ID ]['old_link_status'] ) && 'broken' !== $broken_links[ $item->ID ]['old_link_status'] ) {
297 // if the link is fixed, but if db is not updated it to fixed link immediately then it will be marked as old status code.
298 $item->link_status = $broken_links[ $item->ID ]['old_link_status'];
299 }
300 $item->tags_data = array();
301 $item->tags_id = array(); // Initialize tags_id array for form submission
302
303 foreach ( $tags_list as $tag ) {
304 if ( $tag->ID === $item->ID ) {
305 array_push(
306 $item->tags_data,
307 array(
308 'term_id' => $tag->cat_id,
309 'term_name' => $tag->term_name,
310 'term_slug' => $tag->term_slug,
311 )
312 );
313 // Also add to tags_id array for form submission
314 array_push( $item->tags_id, $tag->cat_id );
315 }
316 }
317
318 // formatting response.
319 if ( ! isset( $results[ $item->cat_id ] ) ) {
320 $results[ $item->cat_id ] = array(
321 'term_name' => $item->term_name,
322 'term_slug' => $item->term_slug,
323 'term_type' => $item->term_type,
324 );
325 if ( null !== $item->ID ) {
326 $results[ $item->cat_id ]['lists'][] = $item;
327 } else {
328 $results[ $item->cat_id ]['lists'] = array();
329 }
330 } else {
331 $results[ $item->cat_id ]['lists'][] = $item;
332 }
333 }
334 }
335 return $results;
336 }
337 public static function json_link_formatter( $data ) {
338 $res = array(
339 'ID' => $data['ID'],
340 'link_slug' => $data['link_slug'],
341 'link_status' => ( isset( $data['link_status'] ) ? $data['link_status'] : 'publish' ),
342 'short_url' => $data['short_url'],
343 'redirect_type' => ( isset( $data['redirect_type'] ) ? $data['redirect_type'] : '307' ),
344 'target_url' => $data['target_url'],
345 'nofollow' => ( isset( $data['nofollow'] ) ? $data['nofollow'] : false ),
346 'sponsored' => ( isset( $data['sponsored'] ) ? $data['sponsored'] : false ),
347 'param_forwarding' => ( isset( $data['param_forwarding'] ) ? $data['param_forwarding'] : false ),
348 'track_me' => ( isset( $data['track_me'] ) ? $data['track_me'] : false ),
349 'wildcards' => ( isset( $data['wildcards'] ) ? $data['wildcards'] : false ),
350 'expire' => ( isset( $data['expire'] ) ? $data['expire'] : null ),
351 'dynamic_redirect' => ( isset( $data['dynamic_redirect'] ) ? $data['dynamic_redirect'] : null ),
352 'cat_id' => isset( $data['cat_id'] ) ? $data['cat_id'] : null,
353 );
354 if ( isset( $data['uncloaked'] ) && $data['uncloaked'] ) {
355 $res['uncloaked'] = $data['uncloaked'];
356 }
357 return $res;
358 }
359 public static function insert_json_into_file( $file, $data ) {
360 $existingData = file_get_contents( $file );
361 $existingData = json_decode( $existingData, true );
362 $case_sensitive_is_enabled = isset( $existingData['is_case_sensitive'] ) ? $existingData['is_case_sensitive'] : false;
363 $short_url = $case_sensitive_is_enabled ? $data['short_url'] : strtolower( $data['short_url'] );
364 if ( isset( $data['wildcards'] ) && $data['wildcards'] ) {
365 $tempArray = $existingData['wildcards'];
366 // Remove any existing entry with the same ID to prevent duplicates
367 if ( isset( $data['ID'] ) ) {
368 foreach ( $tempArray as $key => $entry ) {
369 if ( isset( $entry['ID'] ) && $entry['ID'] == $data['ID'] ) {
370 unset( $tempArray[ $key ] );
371 break;
372 }
373 }
374 }
375 $tempArray[ $short_url ] = self::json_link_formatter( $data );
376 $existingData['wildcards'] = $tempArray;
377 } else {
378 $tempArray = ( isset( $existingData['links'] ) ? $existingData['links'] : array() );
379 // Remove any existing entry with the same ID to prevent duplicates
380 if ( isset( $data['ID'] ) ) {
381 foreach ( $tempArray as $key => $entry ) {
382 if ( isset( $entry['ID'] ) && $entry['ID'] == $data['ID'] ) {
383 unset( $tempArray[ $key ] );
384 break;
385 }
386 }
387 }
388 $tempArray[ $short_url ] = self::json_link_formatter( $data );
389 $existingData['links'] = $tempArray;
390 }
391 return file_put_contents( $file, wp_json_encode( $existingData ) );
392 }
393 public static function update_json_into_file( $file, $data, $old_short_url = '' ) {
394 if ( ! isset( $data['short_url'] ) ) {
395 return false;
396 }
397 $existingData = file_get_contents( $file );
398 $existingData = json_decode( $existingData, true );
399 // make sure we always have an array to work with
400 if ( ! is_array( $existingData ) ) {
401 $existingData = array();
402 }
403 $case_sensitive_is_enabled = isset( $existingData['is_case_sensitive'] ) ? $existingData['is_case_sensitive'] : false;
404 $short_url = $case_sensitive_is_enabled ? $data['short_url'] : strtolower( $data['short_url'] );
405
406 if ( isset( $data['wildcards'] ) && ! empty( $data['wildcards'] ) ) {
407 $tempArray = isset( $existingData['wildcards'] ) && is_array( $existingData['wildcards'] ) ? $existingData['wildcards'] : array();
408 if ( is_array( $tempArray ) ) {
409 $found_old_entry = false;
410
411 // First, try to find and remove by old_short_url if provided
412 if ( ! empty( $old_short_url ) ) {
413 $old_short_url_lower = strtolower( $old_short_url );
414 if ( isset( $tempArray[ $old_short_url ] ) ) {
415 unset( $tempArray[ $old_short_url ] );
416 $found_old_entry = true;
417 } elseif ( isset( $tempArray[ $old_short_url_lower ] ) ) {
418 unset( $tempArray[ $old_short_url_lower ] );
419 $found_old_entry = true;
420 }
421 }
422
423 // If old entry not found by short_url, search by ID to remove all duplicates
424 if ( ! $found_old_entry && isset( $data['ID'] ) ) {
425 foreach ( $tempArray as $key => $entry ) {
426 if ( isset( $entry['ID'] ) && $entry['ID'] == $data['ID'] ) {
427 unset( $tempArray[ $key ] );
428 }
429 }
430 }
431
432 $tempArray[ $short_url ] = self::json_link_formatter( $data );
433 $existingData['wildcards'] = $tempArray;
434 return file_put_contents( $file, wp_json_encode( $existingData ) );
435 }
436 } else {
437 $tempArray = isset( $existingData['links'] ) && is_array( $existingData['links'] ) ? $existingData['links'] : array();
438 $previous_data = array();
439 if ( is_array( $tempArray ) ) {
440 $found_old_entry = false;
441
442 // First, try to find and remove by old_short_url if provided
443 if ( ! empty( $old_short_url ) ) {
444 $old_short_url_lower = strtolower( $old_short_url );
445 if ( isset( $tempArray[ $old_short_url ] ) ) {
446 $previous_data = $tempArray[ $old_short_url ];
447 unset( $tempArray[ $old_short_url ] );
448 $found_old_entry = true;
449 } elseif ( isset( $tempArray[ $old_short_url_lower ] ) ) {
450 $previous_data = $tempArray[ $old_short_url_lower ];
451 unset( $tempArray[ $old_short_url_lower ] );
452 $found_old_entry = true;
453 }
454 }
455
456 // If old entry not found by short_url, search by ID to remove all duplicates
457 if ( ! $found_old_entry && isset( $data['ID'] ) ) {
458 foreach ( $tempArray as $key => $entry ) {
459 if ( isset( $entry['ID'] ) && $entry['ID'] == $data['ID'] ) {
460 $previous_data = $entry;
461 unset( $tempArray[ $key ] );
462 }
463 }
464 }
465
466 $data = wp_parse_args( $data, $previous_data );
467 $tempArray[ $short_url ] = self::json_link_formatter( $data );
468 $existingData['links'] = $tempArray;
469 return file_put_contents( $file, wp_json_encode( $existingData ) );
470 }
471 }
472 }
473 public static function delete_json_into_file( $file, $short_url ) {
474 if ( ! is_string( $short_url ) || '' === $short_url ) {
475 return;
476 }
477 $existingData = file_get_contents( $file );
478 $existingData = json_decode( $existingData, true );
479 if ( ! is_array( $existingData ) ) {
480 $existingData = array();
481 }
482 if ( isset( $existingData['wildcards'][ $short_url ] ) || isset( $existingData['wildcards'][ strToLower( $short_url ) ] ) ) {
483 $tempArray = $existingData['wildcards'];
484 if ( is_array( $tempArray ) ) {
485 unset( $tempArray[ $short_url ] );
486 unset( $tempArray[ strToLower( $short_url ) ] );
487 $existingData['wildcards'] = $tempArray;
488 return file_put_contents( $file, wp_json_encode( $existingData ) );
489 }
490 } elseif ( isset( $existingData['links'][ $short_url ] ) || isset( $existingData['links'][ strtolower( $short_url ) ] ) ) {
491 $tempArray = $existingData['links'];
492 if ( is_array( $tempArray ) ) {
493 unset( $tempArray[ $short_url ] );
494 unset( $tempArray[ strtolower( $short_url ) ] );
495 $existingData['links'] = $tempArray;
496 return file_put_contents( $file, wp_json_encode( $existingData ) );
497 }
498 }
499 return;
500 }
501
502 public static function is_exists_short_url( $short_url ) {
503 $resutls = self::get_link_by_short_url( $short_url );
504 if ( count( $resutls ) > 0 ) {
505 return true;
506 }
507 return false;
508 }
509
510 public static function sanitize_text_or_array_field( $array_or_string, $key = '' ) {
511
512 $boolean = array( 'true', 'false', '1', '0' );
513 $skip = array( 'affiliate_disclosure_text', 'allow_contact_text', 'form_title', 'customFields', 'autolink_custom_icon' );
514 $url_keys = array( 'link', 'target_url' );
515 if ( is_string( $array_or_string ) ) {
516 if ( in_array( $key, $url_keys, true ) ) {
517 return esc_url_raw( $array_or_string );
518 }
519 $array_or_string = in_array( $array_or_string, $boolean ) || is_bool( $array_or_string ) ? rest_sanitize_boolean( $array_or_string ) : sanitize_text_field( $array_or_string );
520 } elseif ( is_array( $array_or_string ) ) {
521 foreach ( $array_or_string as $field_key => &$value ) {
522 if ( in_array( $field_key, $skip ) ) {
523 continue;
524 }
525 if ( is_array( $value ) ) {
526 $value = self::sanitize_text_or_array_field( $value, $field_key );
527 } elseif ( in_array( $field_key, $url_keys, true ) ) {
528 $value = esc_url_raw( $value );
529 } else {
530 $value = in_array( $value, $boolean ) || is_bool( $value ) ? rest_sanitize_boolean( $value ) : sanitize_text_field( $value );
531 }
532 }
533 }
534 return $array_or_string;
535 }
536 public static function fresh_ajax_request_data( $data ) {
537 $remove = array( 'action', 'security' );
538 return array_diff_key( $data, array_flip( $remove ) );
539 }
540
541 public static function force_relative_url( $url ) {
542 return preg_replace( '/^(http)?s?:?\/\/[^\/]*(\/?.*)$/i', '$2', '' . $url );
543 }
544
545 /**
546 * Normalizing Clicks Data
547 *
548 * This function is responsible for manualy filter the duplicates IPs and link_id's from the data.
549 *
550 * @internal this is used in update_links_analytics for clicks on cron hook called 'betterlinks/analytics'
551 *
552 * @since 1.3.1
553 *
554 * @param array $data This should be the clicks data for IP's and links.
555 * @return array
556 */
557 public static function normalize_ips_data( &$data ) {
558 $_results = array();
559 if ( ! empty( $data ) ) {
560 foreach ( $data as &$analytic ) {
561 $_link_id = $analytic['link_id'];
562 $_link_count = $analytic['lidc'];
563 $_ip = isset( $analytic['ip'] ) ? trim( $analytic['ip'] ) : '';
564 $_ip_count = $analytic['ipc'];
565
566 if ( ! isset( $_results[ $_link_id ] ) ) {
567 $_results[ $_link_id ] = array(
568 'link_count' => $_link_count,
569 'ip' => array(),
570 );
571 }
572
573 if ( $_ip && ! isset( $_results[ $_link_id ]['ip'][ $_ip ] ) ) {
574 $_results[ $_link_id ]['ip'][ $_ip ] = $_ip_count;
575 }
576 }
577 }
578
579 return $_results;
580 }
581
582 public static function update_links_analytics() {
583 $results = array();
584 $clicks_count = self::get_clicks_count();
585
586 $total_clicks = $clicks_count['total_clicks'];
587 $unique_clicks = $clicks_count['unique_clicks'];
588
589 for ( $i = 0; $i < count( $total_clicks ); $i++ ) {
590 $results[ $total_clicks[ $i ]['link_id'] ] = array(
591 'link_count' => $total_clicks[ $i ]['total_clicks'],
592 'ip' => isset( $unique_clicks[ $i ]['unique_clicks'] ) ? $unique_clicks[ $i ]['unique_clicks'] : 1,
593 );
594 }
595
596 return update_option( 'betterlinks_analytics_data', wp_json_encode( $results ), false );
597 }
598
599 public static function maybe_json( $data, $sanitize_text = true ) {
600 if ( is_array( $data ) || is_object( $data ) ) {
601 return wp_json_encode( $data );
602 }
603
604 if ( is_string( $data ) && $sanitize_text ) {
605 return sanitize_text_field( $data );
606 }
607
608 return $data;
609 }
610 public static function generate_short_url( $short_url ) {
611 return site_url( '/' ) . trim( $short_url, '/' );
612 }
613
614 public static function btl_get_option( $option_name ) {
615 global $wpdb;
616 $result = $wpdb->get_row(
617 $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}options WHERE option_name=%s", $option_name ),
618 ARRAY_A
619 );
620 $value = false;
621 if ( ! empty( $result['option_id'] ) ) {
622 $value = maybe_unserialize( $result['option_value'] );
623 }
624 return $value;
625 }
626 public static function btl_update_option( $option_name, $option_value, $careless_insert = false, $careless_update = false ) {
627 global $wpdb;
628 $option_value = maybe_serialize( $option_value );
629 $result = false;
630 if ( ! $careless_insert && ! $careless_update ) {
631 $result = $wpdb->get_row(
632 $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}options WHERE option_name=%s", $option_name ),
633 ARRAY_A
634 );
635 }
636 if ( $careless_insert || ( ! $careless_update && empty( $result['option_id'] ) ) ) {
637 $result = $wpdb->query(
638 $wpdb->prepare(
639 "INSERT INTO {$wpdb->prefix}options ( option_name, option_value, autoload ) VALUES ( %s, %s, %s )",
640 array(
641 $option_name,
642 $option_value,
643 'no',
644 )
645 )
646 );
647 return $result;
648 }
649 if ( $careless_update || ! empty( $result['option_id'] ) ) {
650 $result = $wpdb->update(
651 "{$wpdb->prefix}options",
652 array(
653 'option_value' => $option_value,
654 'autoload' => 'no',
655 ),
656 array( 'option_name' => $option_name )
657 );
658 return $result !== false;
659 }
660 }
661 public static function btl_update_autoload_option( $option_name, $autoload = false ) {
662 global $wpdb;
663 $result = $wpdb->get_row(
664 $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}options WHERE option_name=%s", $option_name ),
665 ARRAY_A
666 );
667
668 if ( ! empty( $result['option_id'] ) && ! empty( $result['option_value'] ) ) {
669 if ( $autoload === false ) {
670 $result = $wpdb->update(
671 "{$wpdb->prefix}options",
672 array(
673 'option_value' => $result['option_value'],
674 'autoload' => 'no',
675 ),
676 array( 'option_name' => $option_name )
677 );
678 } elseif ( $autoload === true ) {
679 $result = $wpdb->update(
680 "{$wpdb->prefix}options",
681 array(
682 'option_value' => $result['option_value'],
683 'autoload' => 'yes',
684 ),
685 array( 'option_name' => $option_name )
686 );
687 }
688 return $result !== false;
689 }
690 }
691 public static function run_migration_for_ptrl_links_in_background( $installer, $links_count ) {
692 global $wpdb;
693 $per_page = 10000;
694 $total_page = ceil( $links_count / $per_page );
695 for ( $page = 1; $page <= $total_page; $page++ ) {
696 $offset = ( $page - 1 ) * $per_page;
697 $links = $wpdb->get_col(
698 "SELECT concat('prli_links-', ID) AS ID FROM {$wpdb->prefix}prli_links LIMIT $per_page OFFSET {$offset}",
699 0
700 );
701 $installer->data( $links )->save();
702 }
703 $installer->data( array( 'betterlinks_ptl_links_migrated' ) )->save();
704 return $installer;
705 }
706 public static function run_migration_for_ptrl_clicks_in_background( $installer, $clicks_count ) {
707 global $wpdb;
708 $per_page = 10000;
709 $total_page = ceil( $clicks_count / $per_page );
710 for ( $page = 1; $page <= $total_page; $page++ ) {
711 $offset = ( $page - 1 ) * $per_page;
712 $clicks = $wpdb->get_col(
713 "SELECT concat('prli_clicks-', ID) AS ID FROM {$wpdb->prefix}prli_clicks LIMIT $per_page OFFSET {$offset}",
714 0
715 );
716 $installer->data( $clicks )->save();
717 }
718 $installer->data( array( 'betterlinks_ptl_clicks_migrated' ) )->save();
719 return $installer;
720 }
721
722 public function generate_random_slug( $length = 3 ) {
723 $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
724 $random_string = '';
725 $characters_length = strlen( $characters );
726
727 for ( $i = 0; $i < $length; $i++ ) {
728 $random_string .= $characters[ wp_rand( 0, $characters_length - 1 ) ];
729 }
730 $random_num = wp_rand( 0, 10 ) . wp_rand( 0, 10 ) . wp_rand( 0, 10 );
731 return $random_string . $random_num;
732 }
733 public function get_betterlinks_prefix() {
734 if ( BETTERLINKS_EXISTS_SETTINGS_JSON ) {
735 $data = Cache::get_json_settings();
736
737 if ( empty( $data ) ) {
738 $data = Cache::write_json_settings();
739 }
740 $prefix = ! empty( $data['prefix'] ) ? $data['prefix'] . '/' : '';
741 return $prefix;
742 }
743
744 $betterlinks_links = get_option( 'betterlinks_links', array() );
745 if ( is_string( $betterlinks_links ) ) {
746 $betterlinks_links = json_decode( $betterlinks_links, true );
747 }
748 $prefix = ! empty( $betterlinks_links['prefix'] ) ? $betterlinks_links['prefix'] . '/' : '';
749 return $prefix;
750 }
751
752 public function fetch_target_url( $target_url ) {
753 if ( empty( $target_url ) ) {
754 return false;
755 }
756
757 $http = new WP_Http();
758 $result = $http->get( $target_url, array( 'sslverify' => false ) );
759 $title = '';
760 if ( ! is_wp_error( $result ) && ! empty( $result['body'] ) && preg_match( '/<title>(.*)<\/title>/siU', $result['body'], $title_matches ) ) {
761 $title = html_entity_decode( $title_matches[1] );
762 }
763 return $title;
764 }
765 public static function insert_new_category( $slug ) {
766 if ( ! ! intval( $slug ) ) {
767 return $slug;
768 }
769
770 // Check if category exists and get its ID for AI
771 $existing_term = self::get_term_by_slug( self::make_slug( $slug ), 'category' );
772 if ( ! empty( $existing_term ) && is_array( $existing_term ) && count( $existing_term ) > 0 ) {
773 // Category exists, return its ID
774 return $existing_term[0]['ID'];
775 }
776
777 // Category doesn't exist, create it
778 $insert_id = self::insert_term(
779 array(
780 'term_name' => $slug,
781 'term_slug' => self::make_slug( $slug ),
782 'term_type' => 'category',
783 )
784 );
785 if ( $insert_id ) {
786 self::clear_query_cache();
787 return $insert_id;
788 }
789
790 return $slug;
791 }
792
793 public static function isJson( $string ) {
794 json_decode( $string );
795 return json_last_error() === JSON_ERROR_NONE;
796 }
797
798 public static function get_migratable_plugins() {
799 return [
800 'simple301redirects' => defined('SIMPLE301REDIRECTS_VERSION'),
801 'thirstyaffiliates' => class_exists('ThirstyAffiliates'),
802 'prettylinks' => defined('PRLI_VERSION'),
803 ];
804 }
805
806 public static function init_tracking( $data, $utils ) {
807 global $betterlinks;
808 $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; // phpcs:ignore
809 $dd = new DeviceDetector( $user_agent );
810 $dd->parse();
811
812 $data['is_bot'] = $dd->isBot();
813 if ( empty( $data['target_url'] ) || ! apply_filters( 'betterlinks/pre_before_redirect', $data ) ) {
814 // password protection logics
815 if( empty( $data['skip_password_protection'] ) ){
816 do_action( 'betterlinkspro/admin/check_password_protection', $data['short_url'], $data );
817 }
818
819 if ( empty( $data['target_url'] ) || ! apply_filters( 'betterlinks/pre_before_redirect', $data ) ) { // phpcs:ignore
820 return false;
821 }
822 }
823 $data = apply_filters( 'betterlinks/link/before_dispatch_redirect', $data ); // phpcs:ignore.
824 if ( empty( $data ) ) {
825 return false;
826 }
827 do_action( 'betterlinks/before_redirect', $data ); // phpcs:ignore.
828
829 $comparable_url = rtrim( preg_replace( '/https?\:\/\//', '', site_url( '/' ) ), '/' ) . '/' . $data['short_url'];
830 $destination_url = rtrim( preg_replace( '/https?\:\/\//', '', $data['target_url'] ), '/' );
831 $comparable_url = rtrim( preg_replace( '/^www\.?/', '', $comparable_url ), '/' );
832 $destination_url = rtrim( preg_replace( '/^www\.?/', '', $destination_url ), '/' );
833 if ( ! $data || $comparable_url === $destination_url ) {
834 return;
835 }
836
837 if ( filter_var( $data['track_me'], FILTER_VALIDATE_BOOLEAN ) ) {
838 $data = apply_filters( 'betterlinks/extra_tracking_data', $data, $dd );
839
840 $data['os'] = OperatingSystem::getOsFamily( $dd->getOs( 'name' ) );
841 $data['browser'] = Browser::getBrowserFamily( $dd->getClient( 'name' ) );
842 $data['device'] = $dd->getDeviceName();
843
844 if ( isset( $betterlinks['disablebotclicks'] ) && $betterlinks['disablebotclicks'] ) {
845 if ( ! $dd->isBot() ) {
846 $utils->start_trakcing( $data );
847 }
848 } else {
849 $utils->start_trakcing( $data );
850 }
851 }
852 }
853
854 /**
855 * Rebuild links JSON from database
856 * Useful when JSON file is corrupted or completely out of sync
857 *
858 * @since 2.6.0
859 * @return bool True if rebuild successful
860 */
861 public static function rebuild_links_json() {
862 if ( ! BETTERLINKS_EXISTS_LINKS_JSON ) {
863 return false;
864 }
865
866 $formattedArray = self::get_links_for_json();
867 $json_file = trailingslashit( BETTERLINKS_UPLOAD_DIR_PATH ) . 'links.json';
868
869 return (bool) file_put_contents( $json_file, wp_json_encode( $formattedArray ) );
870 }
871
872 /**
873 * Sync all missing links from database to JSON file
874 * Checks if all database links exist in JSON and adds missing ones
875 * Called when admin page loads to ensure complete synchronization
876 *
877 * @since 2.6.2
878 * @return array Results: ['total' => count, 'synced' => count]
879 */
880 public static function sync_all_missing_links_to_json() {
881 if ( ! BETTERLINKS_EXISTS_LINKS_JSON ) {
882 return array( 'total' => 0, 'synced' => 0 );
883 }
884
885 if ( ! function_exists( 'file_get_contents' ) || ! function_exists( 'file_put_contents' ) ) {
886 return array( 'total' => 0, 'synced' => 0 );
887 }
888
889 $json_file = trailingslashit( BETTERLINKS_UPLOAD_DIR_PATH ) . 'links.json';
890
891 // Read JSON file
892 if ( ! file_exists( $json_file ) ) {
893 // JSON doesn't exist, rebuild from scratch
894 self::rebuild_links_json();
895 return array( 'total' => 0, 'synced' => 0 );
896 }
897
898 $json_content = file_get_contents( $json_file );
899 $json_data = json_decode( $json_content, true );
900
901 // If JSON is corrupted, rebuild
902 if ( ! is_array( $json_data ) ) {
903 self::rebuild_links_json();
904 return array( 'total' => 0, 'synced' => 0 );
905 }
906
907 // Get all published links from database
908 global $wpdb;
909 $all_db_links = $wpdb->get_results(
910 "SELECT ID, short_url, wildcards FROM {$wpdb->prefix}betterlinks WHERE link_status = 'publish'",
911 ARRAY_A
912 );
913
914 $synced_count = 0;
915 $total_links = count( $all_db_links );
916
917 // Check each database link and add if missing from JSON
918 foreach ( $all_db_links as $db_link ) {
919 $short_url = $db_link['short_url'];
920 $link_id = $db_link['ID'];
921 $is_wildcard = isset( $db_link['wildcards'] ) && $db_link['wildcards'];
922
923 // Determine where to look
924 $target_section = $is_wildcard ? 'wildcards' : 'links';
925
926 // Check if link exists in JSON
927 $link_exists = false;
928 if ( isset( $json_data[ $target_section ] ) && is_array( $json_data[ $target_section ] ) ) {
929 if ( isset( $json_data[ $target_section ][ $short_url ] ) ) {
930 $link_exists = true;
931 }
932 }
933
934 // If missing, add it
935 if ( ! $link_exists ) {
936 $full_link_data = $wpdb->get_row(
937 $wpdb->prepare(
938 "SELECT * FROM {$wpdb->prefix}betterlinks WHERE ID = %d",
939 $link_id
940 ),
941 ARRAY_A
942 );
943
944 if ( $full_link_data ) {
945 $formatted_link = self::json_link_formatter( $full_link_data );
946
947 if ( $formatted_link ) {
948 // Ensure target section exists
949 if ( ! isset( $json_data[ $target_section ] ) ) {
950 $json_data[ $target_section ] = array();
951 }
952
953 // Add missing link to JSON
954 $json_data[ $target_section ][ $short_url ] = $formatted_link;
955 $synced_count++;
956 }
957 }
958 }
959 }
960
961 // Write back to file if any links were synced
962 if ( $synced_count > 0 ) {
963 file_put_contents( $json_file, wp_json_encode( $json_data ) );
964 }
965
966 return array(
967 'total' => $total_links,
968 'synced' => $synced_count,
969 );
970 }
971 }
972