| 1 |
<?php |
| 2 |
namespace BetterLinks\Link; |
| 3 |
if ( ! defined( 'ABSPATH' ) ) { exit; } |
| 4 |
|
| 5 |
use BetterLinks\Helper; |
| 6 |
use DeviceDetector\Parser\Device\AbstractDeviceParser; |
| 7 |
use BetterLinks\Traits\Links; |
| 8 |
use BetterLinks\Traits\ArgumentSchema; |
| 9 |
use DeviceDetector\DeviceDetector; |
| 10 |
use DeviceDetector\Parser\OperatingSystem; |
| 11 |
use DeviceDetector\Parser\Client\Browser; |
| 12 |
|
| 13 |
class Utils { |
| 14 |
use Links; |
| 15 |
use ArgumentSchema; |
| 16 |
|
| 17 |
public function __construct() { |
| 18 |
AbstractDeviceParser::setVersionTruncation( AbstractDeviceParser::VERSION_TRUNCATION_NONE ); |
| 19 |
} |
| 20 |
public function get_slug_raw( $slug ) { |
| 21 |
if ( BETTERLINKS_EXISTS_LINKS_JSON ) { |
| 22 |
return apply_filters( 'betterlinks/link/get_link_by_slug', Helper::get_link_from_json_file( $slug ) ); |
| 23 |
} |
| 24 |
$link_options = json_decode( get_option( BETTERLINKS_LINKS_OPTION_NAME, '{}' ), true ); |
| 25 |
$is_case_sensitive = isset( $link_options['is_case_sensitive'] ) ? $link_options['is_case_sensitive'] : false; |
| 26 |
$results = current( Helper::get_link_by_short_url( $slug, $is_case_sensitive ) ); |
| 27 |
if ( ! empty( $results ) ) { |
| 28 |
return apply_filters( 'betterlinks/link/get_link_by_slug', json_decode( wp_json_encode( $results ), true ) ); |
| 29 |
} |
| 30 |
// wildcards. |
| 31 |
$links_option = json_decode( get_option( BETTERLINKS_LINKS_OPTION_NAME ), true ); |
| 32 |
if ( isset( $links_option['wildcards'] ) && $links_option['wildcards'] ) { |
| 33 |
$results = Helper::get_link_by_wildcards( 1 ); |
| 34 |
if ( is_array( $results ) && count( $results ) > 0 ) { |
| 35 |
foreach ( $results as $key => $item ) { |
| 36 |
$postion = strpos( $item['short_url'], '/*' ); |
| 37 |
if ( false !== $postion ) { |
| 38 |
$item_short_url_substr = substr( $item['short_url'], 0, $postion ); |
| 39 |
$slug_substr = substr( $slug, 0, $postion ); |
| 40 |
if ( ! $is_case_sensitive ) { |
| 41 |
$item_short_url_substr = strtolower( $item_short_url_substr ); |
| 42 |
$slug_substr = strtolower( $slug_substr ); |
| 43 |
} |
| 44 |
if ( $item_short_url_substr === $slug_substr ) { |
| 45 |
$target_postion = strpos( $item['target_url'], '/*' ); |
| 46 |
if ( false !== $target_postion ) { |
| 47 |
$target_url = str_replace( '/*', substr( $slug, $postion ), $item['target_url'] ); |
| 48 |
$item['target_url'] = $target_url; |
| 49 |
return apply_filters( 'betterlinks/link/get_link_by_slug', json_decode( wp_json_encode( $item ), true ) ); |
| 50 |
} |
| 51 |
return apply_filters( 'betterlinks/link/get_link_by_slug', json_decode( wp_json_encode( $item ), true ) ); |
| 52 |
} |
| 53 |
} |
| 54 |
} |
| 55 |
} |
| 56 |
} |
| 57 |
} |
| 58 |
public function dispatch_redirect( $data, $param ) { |
| 59 |
global $betterlinks; |
| 60 |
|
| 61 |
$comparable_url = rtrim( preg_replace( '/https?\:\/\//', '', site_url( '/' ) ), '/' ) . '/' . $data['short_url']; |
| 62 |
$destination_url = rtrim( preg_replace( '/https?\:\/\//', '', $data['target_url'] ), '/' ); |
| 63 |
$comparable_url = rtrim( preg_replace( '/^www\.?/', '', $comparable_url ), '/' ); |
| 64 |
$destination_url = rtrim( preg_replace( '/^www\.?/', '', $destination_url ), '/' ); |
| 65 |
if ( ! $data || $comparable_url === $destination_url ) { |
| 66 |
return; |
| 67 |
} |
| 68 |
|
| 69 |
$target_url = $this->addScheme( $data['target_url'] ); |
| 70 |
$_query_params = array(); |
| 71 |
wp_parse_str( $param, $_query_params ); |
| 72 |
$data['pf'] = build_query( $_query_params ); |
| 73 |
if ( filter_var( $data['param_forwarding'], FILTER_VALIDATE_BOOLEAN ) && ! empty( $param ) && $param !== $data['link_slug'] ) { |
| 74 |
$_target_url = wp_parse_url( $target_url ); |
| 75 |
$target_url .= ( isset( $_target_url['query'] ) ? '&' : '?' ) . $data['pf']; |
| 76 |
} |
| 77 |
|
| 78 |
// A HEAD request is a metadata probe (uptime monitors, preview crawlers, CDN |
| 79 |
// health checks), not a real visit — resolve the redirect but never record a |
| 80 |
// click for it, otherwise those automated hits would inflate analytics. |
| 81 |
$is_head = isset( $_SERVER['REQUEST_METHOD'] ) && 'HEAD' === strtoupper( $_SERVER['REQUEST_METHOD'] ); // phpcs:ignore |
| 82 |
if ( ! $is_head && filter_var( $data['track_me'], FILTER_VALIDATE_BOOLEAN ) ) { |
| 83 |
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; // phpcs:ignore |
| 84 |
$dd = new DeviceDetector( $user_agent ); |
| 85 |
$dd->parse(); |
| 86 |
|
| 87 |
$data = apply_filters( 'betterlinks/extra_tracking_data', $data, $dd ); |
| 88 |
|
| 89 |
$data['os'] = OperatingSystem::getOsFamily( $dd->getOs( 'name' ) ); |
| 90 |
$data['browser'] = Browser::getBrowserFamily( $dd->getClient( 'name' ) ); |
| 91 |
$data['device'] = $dd->getDeviceName(); |
| 92 |
|
| 93 |
// Record which hits were bots so Analytics can split human vs bot |
| 94 |
// traffic. Pro's extra-data tracking sets a richer bot_name via the |
| 95 |
// filter above; this fills it in on free, where the detector already |
| 96 |
// ran for the disablebotclicks check, so it costs nothing extra. |
| 97 |
if ( empty( $data['bot_name'] ) && $dd->isBot() ) { |
| 98 |
$bot = $dd->getBot(); |
| 99 |
$bot_name = isset( $bot['name'] ) ? $bot['name'] : 'Unknown'; |
| 100 |
$data['bot_name'] = substr( $bot_name, 0, 20 ); // column is VARCHAR(20) |
| 101 |
} |
| 102 |
|
| 103 |
if ( isset( $betterlinks['disablebotclicks'] ) && $betterlinks['disablebotclicks'] ) { |
| 104 |
if ( ! $dd->isBot() ) { |
| 105 |
$this->start_trakcing( $data ); |
| 106 |
} |
| 107 |
} else { |
| 108 |
$this->start_trakcing( $data ); |
| 109 |
} |
| 110 |
} |
| 111 |
|
| 112 |
|
| 113 |
$robots_tags = array(); |
| 114 |
if ( filter_var( $data['sponsored'], FILTER_VALIDATE_BOOLEAN ) ) { |
| 115 |
$robots_tags[] = 'sponsored'; |
| 116 |
} |
| 117 |
if ( filter_var( $data['nofollow'], FILTER_VALIDATE_BOOLEAN ) ) { |
| 118 |
$robots_tags[] = 'noindex'; |
| 119 |
$robots_tags[] = 'nofollow'; |
| 120 |
} |
| 121 |
if ( ! empty( $robots_tags ) ) { |
| 122 |
header( 'X-Robots-Tag: ' . implode( ', ', $robots_tags ), true ); |
| 123 |
} |
| 124 |
|
| 125 |
header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' ); |
| 126 |
header( 'Cache-Control: post-check=0, pre-check=0', false ); |
| 127 |
header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' ); |
| 128 |
header( 'Cache-Control: no-cache' ); |
| 129 |
header( 'Pragma: no-cache' ); |
| 130 |
header( 'X-Redirect-Powered-By: https://www.betterlinks.io/' ); |
| 131 |
|
| 132 |
// phpcs:disable WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- BetterLinks redirects to the user-configured external target URL by design; wp_safe_redirect would block off-site URLs and break the plugin's core feature. |
| 133 |
switch ( $data['redirect_type'] ) { |
| 134 |
case '301': |
| 135 |
wp_redirect( esc_url_raw( $target_url ), 301 ); |
| 136 |
exit; |
| 137 |
case '302': |
| 138 |
wp_redirect( esc_url_raw( $target_url ), 302 ); |
| 139 |
exit; |
| 140 |
case '307': |
| 141 |
wp_redirect( esc_url_raw( $target_url ), 307 ); |
| 142 |
exit; |
| 143 |
case 'cloak': |
| 144 |
do_action( 'betterlinks/make_cloaked_redirect', $target_url, $data ); |
| 145 |
exit; |
| 146 |
default: |
| 147 |
wp_redirect( esc_url_raw( $target_url ) ); |
| 148 |
exit; |
| 149 |
} |
| 150 |
// phpcs:enable WordPress.Security.SafeRedirect.wp_redirect_wp_redirect |
| 151 |
} |
| 152 |
|
| 153 |
public function start_trakcing( $data ) { |
| 154 |
global $betterlinks; |
| 155 |
$is_disable_analytics_ip = isset( $betterlinks['is_disable_analytics_ip'] ) ? $betterlinks['is_disable_analytics_ip'] : false; |
| 156 |
do_action( 'betterlinks/link/before_start_tracking', $data ); |
| 157 |
$now = current_time( 'mysql' ); |
| 158 |
$now_gmt = current_time( 'mysql', 1 ); |
| 159 |
$visitor_cookie = 'betterlinks_visitor'; |
| 160 |
// A visitor seen before sends the cookie back; anyone else is new and gets |
| 161 |
// one issued now. PHP does not add a cookie set during this request to |
| 162 |
// $_COOKIE, so keep the generated id in $visitor_id — reading the cookie |
| 163 |
// back here would store an empty visitor_id for every first-time visitor |
| 164 |
// and make them invisible to the new-vs-returning report. |
| 165 |
$is_new_visitor = ! isset( $_COOKIE[ $visitor_cookie ] ); |
| 166 |
if ( $is_new_visitor ) { |
| 167 |
$visitor_cookie_expire_time = time() + 60 * 60 * 24 * 365; // 1 year |
| 168 |
$visitor_id = uniqid( 'bl' ); |
| 169 |
setcookie( $visitor_cookie, $visitor_id, $visitor_cookie_expire_time, '/' ); |
| 170 |
} else { |
| 171 |
$visitor_id = sanitize_text_field( wp_unslash( $_COOKIE[ $visitor_cookie ] ) ); |
| 172 |
} |
| 173 |
// checking if split tes enabled. |
| 174 |
$is_split_enabled = apply_filters( 'betterlinkspro/admin/split_test_tracking', false, $data ); |
| 175 |
|
| 176 |
$click_data = array( |
| 177 |
'link_id' => $data['ID'], |
| 178 |
'browser' => isset( $data['browser'] ) ? $data['browser'] : '', |
| 179 |
'os' => isset( $data['os'] ) ? $data['os'] : '', |
| 180 |
'device' => isset( $data['device'] ) ? $data['device'] : '', |
| 181 |
'referer' => isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '', // phpcs:ignore |
| 182 |
'uri' => $data['link_slug'], |
| 183 |
'click_count' => 0, |
| 184 |
'visitor_id' => $visitor_id, |
| 185 |
// 1 = visitor's first click, 2 = a later one. Deliberately not 0: |
| 186 |
// every click written before this existed has 0, and those rows carry |
| 187 |
// a visitor_id too, so 0 has to keep meaning "unknown" or the report |
| 188 |
// would count all of that history as returning visitors. |
| 189 |
'click_order' => $is_new_visitor ? 1 : 2, |
| 190 |
'created_at' => $now, |
| 191 |
'created_at_gmt' => $now_gmt, |
| 192 |
'rotation_target_url' => $data['target_url'], |
| 193 |
'target_url' => $data['target_url'], |
| 194 |
'is_split_enabled' => $is_split_enabled, |
| 195 |
); |
| 196 |
if ( ! $is_disable_analytics_ip ) { |
| 197 |
$IP = $this->get_current_client_IP(); |
| 198 |
$click_data['ip'] = $IP; |
| 199 |
$click_data['host'] = $IP; |
| 200 |
|
| 201 |
// Only process country data if BetterLinks Pro v2.5.0 or newer is installed |
| 202 |
$is_pro_version_valid = defined( 'BETTERLINKS_PRO_VERSION' ) && version_compare( BETTERLINKS_PRO_VERSION, '2.5.0', '>=' ); |
| 203 |
|
| 204 |
if ( $is_pro_version_valid ) { |
| 205 |
// Check if country data was provided from frontend geolocation |
| 206 |
$has_frontend_country = isset( $data['country_code'] ) && isset( $data['country_name'] ); |
| 207 |
|
| 208 |
if ( $has_frontend_country ) { |
| 209 |
// Use country data from frontend geolocation |
| 210 |
$click_data['country_code'] = $data['country_code']; |
| 211 |
$click_data['country_name'] = $data['country_name']; |
| 212 |
} else { |
| 213 |
// Fallback to server-side detection if frontend didn't provide country data |
| 214 |
if ( class_exists( '\BetterLinks\Services\CountryDetectionService' ) ) { |
| 215 |
$country_data = \BetterLinks\Services\CountryDetectionService::get_country_by_ip( $IP ); |
| 216 |
if ( $country_data ) { |
| 217 |
$click_data['country_code'] = $country_data['country_code']; |
| 218 |
$click_data['country_name'] = $country_data['country_name']; |
| 219 |
} |
| 220 |
} |
| 221 |
} |
| 222 |
} |
| 223 |
} |
| 224 |
|
| 225 |
if ( apply_filters( 'betterlinks/is_extra_data_tracking_compatible', false ) ) { |
| 226 |
$query_params = apply_filters( 'betterlinkspro/admin/parameter_tracking_values', array(), $data ); |
| 227 |
|
| 228 |
$click_data['brand_name'] = isset( $data['brand_name'] ) ? $data['brand_name'] : ''; |
| 229 |
$click_data['model'] = isset( $data['model'] ) ? $data['model'] : ''; |
| 230 |
$click_data['bot_name'] = isset( $data['bot_name'] ) ? $data['bot_name'] : ''; |
| 231 |
$click_data['browser_type'] = isset( $data['browser_type'] ) ? $data['browser_type'] : ''; |
| 232 |
$click_data['browser_version'] = isset( $data['browser_version'] ) ? $data['browser_version'] : ''; |
| 233 |
$click_data['os_version'] = isset( $data['os_version'] ) ? $data['os_version'] : ''; |
| 234 |
$click_data['language'] = isset( $data['language'] ) ? $data['language'] : ''; |
| 235 |
$click_data['query_params'] = wp_json_encode( $query_params ); |
| 236 |
} |
| 237 |
|
| 238 |
// Add user agent if tracking is enabled |
| 239 |
$settings = get_option( BETTERLINKS_LINKS_OPTION_NAME, '[]' ); |
| 240 |
if ( is_string( $settings ) ) { |
| 241 |
$settings = json_decode( $settings, true ); |
| 242 |
} |
| 243 |
if ( ! empty( $settings['enable_user_agent_tracking'] ) && isset( $_SERVER['HTTP_USER_AGENT'] ) ) { |
| 244 |
$click_data['user_agent'] = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ); |
| 245 |
} |
| 246 |
$arg = apply_filters( 'betterlinks/link/insert_click_arg', $click_data ); |
| 247 |
|
| 248 |
if ( BETTERLINKS_EXISTS_CLICKS_JSON ) { |
| 249 |
$this->insert_json_into_file( BETTERLINKS_UPLOAD_DIR_PATH . '/clicks.json', $arg ); |
| 250 |
} else { |
| 251 |
try { |
| 252 |
$click_id = Helper::insert_click( $arg ); |
| 253 |
if ( ! empty( $click_id ) && $is_split_enabled ) { |
| 254 |
do_action( 'betterlinks/link/after_insert_click', $arg['link_id'], $click_id, $arg['target_url'] ); |
| 255 |
} |
| 256 |
} catch ( \Throwable $th ) { |
| 257 |
echo esc_html( $th->getMessage() ); |
| 258 |
} |
| 259 |
} |
| 260 |
} |
| 261 |
|
| 262 |
/** |
| 263 |
* Resolve the visitor IP recorded against a click. |
| 264 |
* |
| 265 |
* This used to walk HTTP_CLIENT_IP / X-Forwarded / X-Forwarded-For / |
| 266 |
* Forwarded ahead of REMOTE_ADDR and take whichever was set. Every one of |
| 267 |
* those is a request header, so on a site that is not actually behind a |
| 268 |
* reverse proxy any visitor could name their own IP on the public redirect |
| 269 |
* path — the busiest unauthenticated entry point the plugin has. That let a |
| 270 |
* caller inflate COUNT(DISTINCT ip) unique-click figures at will, walk |
| 271 |
* straight past the `excluded_ips` analytics filter, and hand a fresh value |
| 272 |
* to the per-IP country lookup on every single hit. |
| 273 |
* |
| 274 |
* The same walk was already replaced in |
| 275 |
* CountryDetectionService::get_current_client_ip() and in |
| 276 |
* BetterLinksPro\Helper::get_current_client_ip(); this path was missed. |
| 277 |
* Delegate to the same resolver so all three agree: REMOTE_ADDR by default, |
| 278 |
* a forwarding header only when the peer is inside an operator-configured |
| 279 |
* trusted-proxy range. |
| 280 |
* |
| 281 |
* The service returns null for private/reserved space because it will not |
| 282 |
* geolocate it. Click tracking still wants that value — a LAN visitor is a |
| 283 |
* real visitor — so fall back to REMOTE_ADDR rather than storing nothing. |
| 284 |
* |
| 285 |
* @return string Client IP, or '' when none can be established. |
| 286 |
*/ |
| 287 |
public function get_current_client_IP() { |
| 288 |
if ( class_exists( '\\BetterLinks\\Services\\CountryDetectionService' ) ) { |
| 289 |
$resolved = \BetterLinks\Services\CountryDetectionService::get_current_client_ip(); |
| 290 |
|
| 291 |
if ( ! empty( $resolved ) ) { |
| 292 |
return $resolved; |
| 293 |
} |
| 294 |
} |
| 295 |
|
| 296 |
$address = isset( $_SERVER['REMOTE_ADDR'] ) |
| 297 |
? trim( sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) ) |
| 298 |
: ''; |
| 299 |
|
| 300 |
// Never store something that is not an address: the column is read back |
| 301 |
// as an identity for unique-visitor counting and IP exclusion. |
| 302 |
return filter_var( $address, FILTER_VALIDATE_IP ) ? $address : ''; |
| 303 |
} |
| 304 |
public function addScheme( $url, $scheme = 'http://' ) { |
| 305 |
if ( strpos( $url, '/' ) === 0 ) { |
| 306 |
return $url = site_url( '/' ) . $url; |
| 307 |
} |
| 308 |
return apply_filters( 'betterlinks/link/target_url', wp_parse_url( $url, PHP_URL_SCHEME ) === null ? $scheme . $url : $url ); |
| 309 |
} |
| 310 |
|
| 311 |
protected function insert_json_into_file( $file, $data ) { |
| 312 |
$existing_data = file_get_contents( $file ); |
| 313 |
$temp_array = (array) json_decode( $existing_data, true ); |
| 314 |
array_push( $temp_array, $data ); |
| 315 |
return file_put_contents( $file, wp_json_encode( $temp_array ) ); |
| 316 |
} |
| 317 |
|
| 318 |
|
| 319 |
/** |
| 320 |
* Create a Quick Link. |
| 321 |
* |
| 322 |
* @param string $title Link title. |
| 323 |
* @param string $target_url Destination. |
| 324 |
* @param array $settings BetterLinks settings. |
| 325 |
* @param bool $render When true (default) render the confirmation page |
| 326 |
* and exit, preserving the legacy front-end |
| 327 |
* behaviour. When false, return the created row so |
| 328 |
* the REST endpoint can answer with JSON. |
| 329 |
* @return array|false|void |
| 330 |
*/ |
| 331 |
public function create_new_link( $title, $target_url, $settings, $render = true ) { |
| 332 |
$date = wp_date( 'Y-m-d H:i:s' ); |
| 333 |
$helper = new Helper(); |
| 334 |
$slug = $helper->generate_random_slug(); |
| 335 |
$prefix = ! empty( $settings['prefix'] ) ? $settings['prefix'] . '/' : ''; |
| 336 |
$nofollow = ! empty( $settings['nofollow'] ) ? $settings['nofollow'] : null; |
| 337 |
$sponsored = ! empty( $settings['sponsored'] ) ? $settings['sponsored'] : null; |
| 338 |
$track_me = ! empty( $settings['track_me'] ) ? $settings['track_me'] : null; |
| 339 |
$param_forwarding = ! empty( $settings['param_forwarding'] ) ? $settings['param_forwarding'] : null; |
| 340 |
$powered_by = ! empty( $settings['cle']['powered_by'] ) ? sanitize_text_field( $settings['cle']['powered_by'] ) : ''; |
| 341 |
$short_url = $prefix . $slug; |
| 342 |
|
| 343 |
$initial_values = array( |
| 344 |
'link_title' => $title, |
| 345 |
'link_slug' => $slug, |
| 346 |
'target_url' => $target_url, |
| 347 |
'short_url' => $short_url, |
| 348 |
'redirect_type' => '307', |
| 349 |
'nofollow' => $nofollow, |
| 350 |
'sponsored' => $sponsored, |
| 351 |
'track_me' => $track_me, |
| 352 |
'param_forwarding' => $param_forwarding, |
| 353 |
'link_date' => $date, |
| 354 |
'link_date_gmt' => $date, |
| 355 |
'link_modified' => $date, |
| 356 |
'link_modified_gmt' => $date, |
| 357 |
'cat_id' => 1, |
| 358 |
'powered_by' => $powered_by, |
| 359 |
); |
| 360 |
$initial_values = apply_filters( 'betterlinks_before_cle', $initial_values, $settings ); |
| 361 |
|
| 362 |
$helper->clear_query_cache(); |
| 363 |
$args = $this->sanitize_links_data( $initial_values ); |
| 364 |
$results = $this->insert_link( $args ); |
| 365 |
|
| 366 |
if ( ! $render ) { |
| 367 |
if ( empty( $results ) ) { |
| 368 |
return false; |
| 369 |
} |
| 370 |
|
| 371 |
$created_short_url = ! empty( $results['short_url'] ) ? $results['short_url'] : $short_url; |
| 372 |
|
| 373 |
return array( |
| 374 |
'id' => isset( $results['ID'] ) ? (int) $results['ID'] : 0, |
| 375 |
'short_url' => $created_short_url, |
| 376 |
'permalink' => site_url( $created_short_url ), |
| 377 |
'results' => $results, |
| 378 |
); |
| 379 |
} |
| 380 |
|
| 381 |
if ( ! empty( $results ) ) { |
| 382 |
require_once BETTERLINKS_ROOT_DIR_PATH . '/includes/Views/create-link-externally.php'; |
| 383 |
exit; |
| 384 |
} |
| 385 |
wp_safe_redirect( home_url() ); |
| 386 |
exit; |
| 387 |
} |
| 388 |
|
| 389 |
public static function prevent_unwanted_cle() { |
| 390 |
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_url( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; |
| 391 |
|
| 392 |
$params = strpos($request_uri, 'action%3Dbtl_cle%26api_key'); |
| 393 |
if ( !empty( $params ) ) { // to prevent short link creation of the 'Here is your BetterLinks' page |
| 394 |
$prevent_unwanted_click = true; // phpcs:ignore |
| 395 |
require_once BETTERLINKS_ROOT_DIR_PATH . '/includes/Views/create-link-externally.php'; |
| 396 |
exit; |
| 397 |
} |
| 398 |
} |
| 399 |
} |
| 400 |
|