| [ Index ] |
PHP Cross Reference of YOURLS |
[Summary view] [Print] [Text view]
1 <?php 2 /* 3 * YOURLS general functions 4 * 5 */ 6 7 /** 8 * Make an optimized regexp pattern from a string of characters 9 * 10 * @param string $string 11 * @return string 12 */ 13 function yourls_make_regexp_pattern( $string ) { 14 // Simple benchmarks show that regexp with smarter sequences (0-9, a-z, A-Z...) are not faster or slower than 0123456789 etc... 15 // add @ as an escaped character because @ is used as the regexp delimiter in yourls-loader.php 16 return preg_quote( $string, '@' ); 17 } 18 19 /** 20 * Get client IP Address. Returns a DB safe string. 21 * 22 * @return string 23 */ 24 function yourls_get_IP() { 25 $ip = ''; 26 27 // Precedence: if set, X-Forwarded-For > HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR 28 $headers = [ 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' ]; 29 foreach( $headers as $header ) { 30 if ( !empty( $_SERVER[ $header ] ) ) { 31 $ip = $_SERVER[ $header ]; 32 break; 33 } 34 } 35 36 // headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one. 37 if ( strpos( $ip, ',' ) !== false ) 38 $ip = substr( $ip, 0, strpos( $ip, ',' ) ); 39 40 return (string)yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) ); 41 } 42 43 /** 44 * Get next id a new link will have if no custom keyword provided 45 * 46 * @since 1.0 47 * @return int id of next link 48 */ 49 function yourls_get_next_decimal() { 50 return (int)yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) ); 51 } 52 53 /** 54 * Update id for next link with no custom keyword 55 * 56 * Note: this function relies upon yourls_update_option(), which will return either true or false 57 * depending upon if there has been an actual MySQL query updating the DB. 58 * In other words, this function may return false yet this would not mean it has functionally failed 59 * In other words I'm not sure if we really need this function to return something :face_with_eyes_looking_up: 60 * See issue 2621 for more on this. 61 * 62 * @since 1.0 63 * @param integer $int id for next link 64 * @return bool true or false depending on if there has been an actual MySQL query. See note above. 65 */ 66 function yourls_update_next_decimal( $int = 0 ) { 67 $int = ( $int == 0 ) ? yourls_get_next_decimal() + 1 : (int)$int ; 68 $update = yourls_update_option( 'next_id', $int ); 69 yourls_do_action( 'update_next_decimal', $int, $update ); 70 return $update; 71 } 72 73 /** 74 * Return XML output. 75 * 76 * @param array $array 77 * @return string 78 */ 79 function yourls_xml_encode( $array ) { 80 return (\Spatie\ArrayToXml\ArrayToXml::convert($array, '', true, 'UTF-8')); 81 } 82 83 /** 84 * Update click count on a short URL. Return 0/1 for error/success. 85 * 86 * @param string $keyword 87 * @param false|int $clicks 88 * @return int 0 or 1 for error/success 89 */ 90 function yourls_update_clicks( $keyword, $clicks = false ) { 91 // Allow plugins to short-circuit the whole function 92 $pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks ); 93 if ( false !== $pre ) { 94 return $pre; 95 } 96 97 $keyword = yourls_sanitize_keyword( $keyword ); 98 $table = YOURLS_DB_TABLE_URL; 99 if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 ) { 100 $update = "UPDATE `$table` SET `clicks` = :clicks WHERE `keyword` = :keyword"; 101 $values = [ 'clicks' => $clicks, 'keyword' => $keyword ]; 102 $update_type = 'set'; 103 } else { 104 $update = "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = :keyword"; 105 $values = [ 'keyword' => $keyword ]; 106 $update_type = 'increment'; 107 } 108 109 $ydb = yourls_get_db(); 110 111 // Try and update click count. An error probably means a concurrency problem : just skip the update 112 try { 113 $result = $ydb->fetchAffected($update, $values); 114 } catch (Exception $e) { 115 $result = 0; 116 } 117 118 if ( $result ) { 119 if ( $ydb->has_infos($keyword) ) { 120 if ( $update_type === 'increment' ) { 121 $infos = $ydb->get_infos($keyword); 122 if ( isset( $infos['clicks'] ) ) { 123 $infos['clicks']++; 124 $ydb->set_infos($keyword, $infos); 125 } else { 126 $ydb->delete_infos($keyword); // We don't know why it's missing, so just purge the cache. 127 } 128 } elseif ( $update_type === 'set' ) { 129 $ydb->update_infos_if_exists($keyword, ['clicks' => $clicks]); 130 } 131 } 132 } 133 134 yourls_do_action( 'update_clicks', $keyword, $result, $clicks ); 135 136 return $result; 137 } 138 139 140 /** 141 * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return 142 * 143 * @param string $filter 'bottom', 'last', 'rand' or 'top' 144 * @param int $limit Number of links to return 145 * @param int $start Offset to start from 146 * @return array Array of links 147 */ 148 function yourls_get_stats($filter = 'top', $limit = 10, $start = 0) { 149 switch( $filter ) { 150 case 'bottom': 151 $sort_by = '`clicks`'; 152 $sort_order = 'asc'; 153 break; 154 case 'last': 155 $sort_by = '`timestamp`'; 156 $sort_order = 'desc'; 157 break; 158 case 'rand': 159 case 'random': 160 $sort_by = 'RAND()'; 161 $sort_order = ''; 162 break; 163 case 'top': 164 default: 165 $sort_by = '`clicks`'; 166 $sort_order = 'desc'; 167 break; 168 } 169 170 // Fetch links 171 $limit = intval( $limit ); 172 $start = intval( $start ); 173 if ( $limit > 0 ) { 174 175 $table_url = YOURLS_DB_TABLE_URL; 176 $results = yourls_get_db()->fetchObjects( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY $sort_by $sort_order LIMIT $start, $limit;" ); 177 178 $return = []; 179 $i = 1; 180 181 foreach ( (array)$results as $res ) { 182 $return['links']['link_'.$i++] = [ 183 'shorturl' => yourls_link($res->keyword), 184 'url' => $res->url, 185 'title' => $res->title, 186 'timestamp'=> $res->timestamp, 187 'ip' => $res->ip, 188 'clicks' => $res->clicks, 189 ]; 190 } 191 } 192 193 $return['stats'] = yourls_get_db_stats(); 194 195 $return['statusCode'] = '200'; 196 197 return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start ); 198 } 199 200 /** 201 * Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array 202 * 203 * The $where parameter will contain additional SQL arguments: 204 * $where['sql'] will concatenate SQL clauses: $where['sql'] = ' AND something = :value AND otherthing < :othervalue'; 205 * $where['binds'] will hold the (name => value) placeholder pairs: $where['binds'] = array('value' => $value, 'othervalue' => $value2) 206 * 207 * @param array $where See comment above 208 * @return array 209 */ 210 function yourls_get_db_stats( $where = [ 'sql' => '', 'binds' => [] ] ) { 211 $table_url = YOURLS_DB_TABLE_URL; 212 213 $totals = yourls_get_db()->fetchObject( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 " . $where['sql'] , $where['binds'] ); 214 $return = [ 'total_links' => $totals->count, 'total_clicks' => $totals->sum ]; 215 216 return yourls_apply_filter( 'get_db_stats', $return, $where ); 217 } 218 219 /** 220 * Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK. 221 * 222 * @return string 223 */ 224 function yourls_get_user_agent() { 225 $ua = '-'; 226 227 if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) { 228 $ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] )); 229 $ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua ); 230 } 231 232 return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 255 ) ); 233 } 234 235 /** 236 * Returns the sanitized referrer submitted by the browser. 237 * 238 * @return string HTTP Referrer or 'direct' if no referrer was provided 239 */ 240 function yourls_get_referrer() { 241 $referrer = isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url_safe( $_SERVER['HTTP_REFERER'] ) : 'direct'; 242 243 return yourls_apply_filter( 'get_referrer', substr( $referrer, 0, 200 ) ); 244 } 245 246 /** 247 * Redirect to another page 248 * 249 * YOURLS redirection, either to internal or external URLs. If headers have not been sent, redirection 250 * is achieved with PHP's header(). If headers have been sent already and we're not in a command line 251 * client, redirection occurs with Javascript. 252 * 253 * Note: yourls_redirect() does not exit automatically, and should almost always be followed by a call to exit() 254 * to prevent the script from continuing. 255 * 256 * @since 1.4 257 * @param string $location URL to redirect to 258 * @param int $code HTTP status code to send 259 * @return int 1 for header redirection, 2 for js redirection, 3 otherwise (CLI) 260 */ 261 function yourls_redirect( $location, $code = 301 ) { 262 yourls_do_action( 'pre_redirect', $location, $code ); 263 $location = yourls_apply_filter( 'redirect_location', $location, $code ); 264 $code = yourls_apply_filter( 'redirect_code', $code, $location ); 265 266 // Redirect, either properly if possible, or via Javascript otherwise 267 if( !headers_sent() ) { 268 yourls_status_header( $code ); 269 header( "Location: $location" ); 270 return 1; 271 } 272 273 // Headers sent : redirect with JS if not in CLI 274 if( php_sapi_name() !== 'cli') { 275 yourls_redirect_javascript( $location ); 276 return 2; 277 } 278 279 // We're in CLI 280 return 3; 281 } 282 283 /** 284 * Redirect to an existing short URL 285 * 286 * Redirect client to an existing short URL (no check performed) and execute misc tasks: update 287 * clicks for short URL, update logs, and send an X-Robots-Tag header to control indexing of a page. 288 * 289 * @since 1.7.3 290 * @param string $url 291 * @param string $keyword 292 * @return void 293 */ 294 function yourls_redirect_shorturl($url, $keyword) { 295 yourls_do_action( 'redirect_shorturl', $url, $keyword ); 296 297 // Attempt to update click count in main table 298 yourls_update_clicks( $keyword ); 299 300 // Update detailed log for stats 301 yourls_log_redirect( $keyword ); 302 303 // Send an X-Robots-Tag header 304 yourls_robots_tag_header(); 305 306 yourls_redirect( $url, 301 ); 307 } 308 309 /** 310 * Send an X-Robots-Tag header. See #3486 311 * 312 * @since 1.9.2 313 * @return void 314 */ 315 function yourls_robots_tag_header() { 316 // Allow plugins to short-circuit the whole function 317 $pre = yourls_apply_filter( 'shunt_robots_tag_header', false ); 318 if ( false !== $pre ) { 319 return $pre; 320 } 321 322 // By default, we're sending a 'noindex' header 323 $tag = yourls_apply_filter( 'robots_tag_header', 'noindex' ); 324 $replace = yourls_apply_filter( 'robots_tag_header_replace', true ); 325 if ( !headers_sent() ) { 326 header( "X-Robots-Tag: $tag", $replace ); 327 } 328 } 329 330 331 /** 332 * Send headers to explicitly tell browser not to cache content or redirection 333 * 334 * @since 1.7.10 335 * @return void 336 */ 337 function yourls_no_cache_headers() { 338 if( !headers_sent() ) { 339 header( 'Expires: Thu, 23 Mar 1972 07:00:00 GMT' ); 340 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' ); 341 header( 'Cache-Control: no-cache, must-revalidate, max-age=0' ); 342 header( 'Pragma: no-cache' ); 343 } 344 } 345 346 /** 347 * Send header to prevent display within a frame from another site (avoid clickjacking) 348 * 349 * This header makes it impossible for an external site to display YOURLS admin within a frame, 350 * which allows for clickjacking. 351 * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 352 * This said, the whole function is shuntable : legit uses of iframes should be still possible. 353 * 354 * @since 1.8.1 355 * @return void|mixed 356 */ 357 function yourls_no_frame_header() { 358 // Allow plugins to short-circuit the whole function 359 $pre = yourls_apply_filter( 'shunt_no_frame_header', false ); 360 if ( false !== $pre ) { 361 return $pre; 362 } 363 364 if( !headers_sent() ) { 365 header( 'X-Frame-Options: SAMEORIGIN' ); 366 } 367 } 368 369 /** 370 * Send a filterable content type header 371 * 372 * @since 1.7 373 * @param string $type content type ('text/html', 'application/json', ...) 374 * @return bool whether header was sent 375 */ 376 function yourls_content_type_header( $type ) { 377 yourls_do_action( 'content_type_header', $type ); 378 if( !headers_sent() ) { 379 $charset = yourls_apply_filter( 'content_type_header_charset', 'utf-8' ); 380 header( "Content-Type: $type; charset=$charset" ); 381 return true; 382 } 383 return false; 384 } 385 386 /** 387 * Set HTTP status header 388 * 389 * @since 1.4 390 * @param int $code status header code 391 * @return bool whether header was sent 392 */ 393 function yourls_status_header( $code = 200 ) { 394 yourls_do_action( 'status_header', $code ); 395 396 if( headers_sent() ) 397 return false; 398 399 $protocol = $_SERVER['SERVER_PROTOCOL']; 400 if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol ) 401 $protocol = 'HTTP/1.0'; 402 403 $code = intval( $code ); 404 $desc = yourls_get_HTTP_status( $code ); 405 406 @header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups 407 408 return true; 409 } 410 411 /** 412 * Redirect to another page using Javascript. 413 * Set optional (bool)$dontwait to false to force manual redirection (make sure a message has been read by user) 414 * 415 * @param string $location 416 * @param bool $dontwait 417 * @return void 418 */ 419 function yourls_redirect_javascript( $location, $dontwait = true ) { 420 yourls_do_action( 'pre_redirect_javascript', $location, $dontwait ); 421 $location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait ); 422 if ( $dontwait ) { 423 $message = yourls_s( 'if you are not redirected after 10 seconds, please <a href="%s">click here</a>', $location ); 424 echo <<<REDIR 425 <script type="text/javascript"> 426 window.location="$location"; 427 </script> 428 <small>($message)</small> 429 REDIR; 430 } 431 else { 432 echo '<p>'.yourls_s( 'Please <a href="%s">click here</a>', $location ).'</p>'; 433 } 434 yourls_do_action( 'post_redirect_javascript', $location ); 435 } 436 437 /** 438 * Return an HTTP status code 439 * 440 * @param int $code 441 * @return string 442 */ 443 function yourls_get_HTTP_status( $code ) { 444 $code = intval( $code ); 445 $headers_desc = [ 446 100 => 'Continue', 447 101 => 'Switching Protocols', 448 102 => 'Processing', 449 450 200 => 'OK', 451 201 => 'Created', 452 202 => 'Accepted', 453 203 => 'Non-Authoritative Information', 454 204 => 'No Content', 455 205 => 'Reset Content', 456 206 => 'Partial Content', 457 207 => 'Multi-Status', 458 226 => 'IM Used', 459 460 300 => 'Multiple Choices', 461 301 => 'Moved Permanently', 462 302 => 'Found', 463 303 => 'See Other', 464 304 => 'Not Modified', 465 305 => 'Use Proxy', 466 306 => 'Reserved', 467 307 => 'Temporary Redirect', 468 469 400 => 'Bad Request', 470 401 => 'Unauthorized', 471 402 => 'Payment Required', 472 403 => 'Forbidden', 473 404 => 'Not Found', 474 405 => 'Method Not Allowed', 475 406 => 'Not Acceptable', 476 407 => 'Proxy Authentication Required', 477 408 => 'Request Timeout', 478 409 => 'Conflict', 479 410 => 'Gone', 480 411 => 'Length Required', 481 412 => 'Precondition Failed', 482 413 => 'Request Entity Too Large', 483 414 => 'Request-URI Too Long', 484 415 => 'Unsupported Media Type', 485 416 => 'Requested Range Not Satisfiable', 486 417 => 'Expectation Failed', 487 422 => 'Unprocessable Entity', 488 423 => 'Locked', 489 424 => 'Failed Dependency', 490 426 => 'Upgrade Required', 491 492 500 => 'Internal Server Error', 493 501 => 'Not Implemented', 494 502 => 'Bad Gateway', 495 503 => 'Service Unavailable', 496 504 => 'Gateway Timeout', 497 505 => 'HTTP Version Not Supported', 498 506 => 'Variant Also Negotiates', 499 507 => 'Insufficient Storage', 500 510 => 'Not Extended' 501 ]; 502 503 return $headers_desc[$code] ?? ''; 504 } 505 506 /** 507 * Log a redirect (for stats) 508 * 509 * This function does not check for the existence of a valid keyword, in order to save a query. Make sure the keyword 510 * exists before calling it. 511 * 512 * @since 1.4 513 * @param string $keyword short URL keyword 514 * @return mixed Result of the INSERT query (1 on success) 515 */ 516 function yourls_log_redirect( $keyword ) { 517 // Allow plugins to short-circuit the whole function 518 $pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword ); 519 if ( false !== $pre ) { 520 return $pre; 521 } 522 523 if (!yourls_do_log_redirect()) { 524 return true; 525 } 526 527 $table = YOURLS_DB_TABLE_LOG; 528 $ip = yourls_get_IP(); 529 $binds = [ 530 'now' => date( 'Y-m-d H:i:s' ), 531 'keyword' => yourls_sanitize_keyword($keyword), 532 'referrer' => substr( yourls_get_referrer(), 0, 200 ), 533 'ua' => substr(yourls_get_user_agent(), 0, 255), 534 'ip' => $ip, 535 'location' => yourls_geo_ip_to_countrycode($ip), 536 ]; 537 538 // Try and log. An error probably means a concurrency problem : just skip the logging 539 try { 540 $result = yourls_get_db()->fetchAffected("INSERT INTO `$table` (click_time, shorturl, referrer, user_agent, ip_address, country_code) VALUES (:now, :keyword, :referrer, :ua, :ip, :location)", $binds ); 541 } catch (Exception $e) { 542 $result = 0; 543 } 544 545 return $result; 546 } 547 548 /** 549 * Check if we want to not log redirects (for stats) 550 * 551 * @return bool 552 */ 553 function yourls_do_log_redirect() { 554 return ( !defined( 'YOURLS_NOSTATS' ) || YOURLS_NOSTATS != true ); 555 } 556 557 /** 558 * Check if an upgrade is needed 559 * 560 * @return bool 561 */ 562 function yourls_upgrade_is_needed() { 563 // check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS 564 list( $currentver, $currentsql ) = yourls_get_current_version_from_sql(); 565 if ( $currentsql < YOURLS_DB_VERSION ) { 566 return true; 567 } 568 569 // Check if YOURLS_VERSION exist && match value stored in YOURLS_DB_TABLE_OPTIONS, update DB if required 570 if ( $currentver < YOURLS_VERSION ) { 571 yourls_update_option( 'version', YOURLS_VERSION ); 572 } 573 574 return false; 575 } 576 577 /** 578 * Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table. 579 * 580 * @return array 581 */ 582 function yourls_get_current_version_from_sql() { 583 $currentver = yourls_get_option( 'version' ); 584 $currentsql = yourls_get_option( 'db_version' ); 585 586 // Values if version is 1.3 587 if ( !$currentver ) { 588 $currentver = '1.3'; 589 } 590 if ( !$currentsql ) { 591 $currentsql = '100'; 592 } 593 594 return [ $currentver, $currentsql ]; 595 } 596 597 /** 598 * Determine if the current page is private 599 * 600 * @return bool 601 */ 602 function yourls_is_private() { 603 $private = defined( 'YOURLS_PRIVATE' ) && YOURLS_PRIVATE; 604 605 if ( $private ) { 606 607 // Allow overruling for particular pages: 608 609 // API 610 if ( yourls_is_API() && defined( 'YOURLS_PRIVATE_API' ) ) { 611 $private = YOURLS_PRIVATE_API; 612 } 613 // Stat pages 614 elseif ( yourls_is_infos() && defined( 'YOURLS_PRIVATE_INFOS' ) ) { 615 $private = YOURLS_PRIVATE_INFOS; 616 } 617 // Others future cases ? 618 } 619 620 return yourls_apply_filter( 'is_private', $private ); 621 } 622 623 /** 624 * Allow several short URLs for the same long URL ? 625 * 626 * @return bool 627 */ 628 function yourls_allow_duplicate_longurls() { 629 // special treatment if API to check for WordPress plugin requests 630 if ( yourls_is_API() && isset( $_REQUEST[ 'source' ] ) && $_REQUEST[ 'source' ] == 'plugin' ) { 631 return false; 632 } 633 634 return yourls_apply_filter('allow_duplicate_longurls', defined('YOURLS_UNIQUE_URLS') && !YOURLS_UNIQUE_URLS); 635 } 636 637 /** 638 * Check if an IP shortens URL too fast to prevent DB flood. Return true, or die. 639 * 640 * @param string $ip 641 * @return bool|mixed|string 642 */ 643 function yourls_check_IP_flood( $ip = '' ) { 644 645 // Allow plugins to short-circuit the whole function 646 $pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip ); 647 if ( false !== $pre ) 648 return $pre; 649 650 yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here 651 652 // Raise white flag if installing or if no flood delay defined 653 if( 654 ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) || 655 !defined('YOURLS_FLOOD_DELAY_SECONDS') || 656 yourls_is_installing() 657 ) 658 return true; 659 660 // Don't throttle logged in users 661 if( yourls_is_private() ) { 662 if( yourls_is_valid_user() === true ) 663 return true; 664 } 665 666 // Don't throttle whitelist IPs 667 if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) { 668 $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST ); 669 foreach( (array)$whitelist_ips as $whitelist_ip ) { 670 $whitelist_ip = trim( $whitelist_ip ); 671 if ( $whitelist_ip == $ip ) 672 return true; 673 } 674 } 675 676 $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() ); 677 678 yourls_do_action( 'check_ip_flood', $ip ); 679 680 $table = YOURLS_DB_TABLE_URL; 681 $lasttime = yourls_get_db()->fetchValue( "SELECT `timestamp` FROM $table WHERE `ip` = :ip ORDER BY `timestamp` DESC LIMIT 1", [ 'ip' => $ip ] ); 682 if( $lasttime ) { 683 $now = date( 'U' ); 684 $then = date( 'U', strtotime( $lasttime ) ); 685 if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) { 686 // Flood! 687 yourls_do_action( 'ip_flood', $ip, $now - $then ); 688 yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Too Many Requests' ), 429 ); 689 } 690 } 691 692 return true; 693 } 694 695 /** 696 * Check if YOURLS is installing 697 * 698 * @since 1.6 699 * @return bool 700 */ 701 function yourls_is_installing() { 702 return (bool)yourls_apply_filter( 'is_installing', defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING ); 703 } 704 705 /** 706 * Check if YOURLS is upgrading 707 * 708 * @since 1.6 709 * @return bool 710 */ 711 function yourls_is_upgrading() { 712 return (bool)yourls_apply_filter( 'is_upgrading', defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING ); 713 } 714 715 /** 716 * Check if YOURLS is installed 717 * 718 * Checks property $ydb->installed that is created by yourls_get_all_options() 719 * 720 * See inline comment for updating from 1.3 or prior. 721 * 722 * @return bool 723 */ 724 function yourls_is_installed() { 725 return (bool)yourls_apply_filter( 'is_installed', yourls_get_db()->is_installed() ); 726 } 727 728 /** 729 * Set installed state 730 * 731 * @since 1.7.3 732 * @param bool $bool whether YOURLS is installed or not 733 * @return void 734 */ 735 function yourls_set_installed( $bool ) { 736 yourls_get_db()->set_installed( $bool ); 737 } 738 739 /** 740 * Generate random string of (int)$length length and type $type (see function for details) 741 * 742 * @param int $length 743 * @param int $type 744 * @param string $charlist 745 * @return mixed|string 746 */ 747 function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) { 748 $length = intval( $length ); 749 750 // define possible characters 751 switch ( $type ) { 752 753 // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords. 754 case '1': 755 $possible = "23456789bcdfghjkmnpqrstvwxyz"; 756 break; 757 758 // Same, with lower + upper 759 case '2': 760 $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ"; 761 break; 762 763 // all letters, lowercase 764 case '3': 765 $possible = "abcdefghijklmnopqrstuvwxyz"; 766 break; 767 768 // all letters, lowercase + uppercase 769 case '4': 770 $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 771 break; 772 773 // all digits & letters lowercase 774 case '5': 775 $possible = "0123456789abcdefghijklmnopqrstuvwxyz"; 776 break; 777 778 // all digits & letters lowercase + uppercase 779 case '6': 780 $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 781 break; 782 783 // custom char list, or comply to charset as defined in config 784 default: 785 case '0': 786 $possible = $charlist ? $charlist : yourls_get_shorturl_charset(); 787 break; 788 } 789 790 $str = substr( str_shuffle( $possible ), 0, $length ); 791 return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist ); 792 } 793 794 /** 795 * Check if we're in API mode. 796 * 797 * @return bool 798 */ 799 function yourls_is_API() { 800 return (bool)yourls_apply_filter( 'is_API', defined( 'YOURLS_API' ) && YOURLS_API ); 801 } 802 803 /** 804 * Check if we're in Ajax mode. 805 * 806 * @return bool 807 */ 808 function yourls_is_Ajax() { 809 return (bool)yourls_apply_filter( 'is_Ajax', defined( 'YOURLS_AJAX' ) && YOURLS_AJAX ); 810 } 811 812 /** 813 * Check if we're in GO mode (yourls-go.php). 814 * 815 * @return bool 816 */ 817 function yourls_is_GO() { 818 return (bool)yourls_apply_filter( 'is_GO', defined( 'YOURLS_GO' ) && YOURLS_GO ); 819 } 820 821 /** 822 * Check if we're displaying stats infos (yourls-infos.php). Returns bool 823 * 824 * @return bool 825 */ 826 function yourls_is_infos() { 827 return (bool)yourls_apply_filter( 'is_infos', defined( 'YOURLS_INFOS' ) && YOURLS_INFOS ); 828 } 829 830 /** 831 * Check if we're in the admin area. Returns bool. Does not relate with user rights. 832 * 833 * @return bool 834 */ 835 function yourls_is_admin() { 836 return (bool)yourls_apply_filter( 'is_admin', defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN ); 837 } 838 839 /** 840 * Check if the server seems to be running on Windows. Not exactly sure how reliable this is. 841 * 842 * @return bool 843 */ 844 function yourls_is_windows() { 845 return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\'; 846 } 847 848 /** 849 * Check if SSL is required. 850 * 851 * @return bool 852 */ 853 function yourls_needs_ssl() { 854 return (bool)yourls_apply_filter( 'needs_ssl', defined( 'YOURLS_ADMIN_SSL' ) && YOURLS_ADMIN_SSL ); 855 } 856 857 /** 858 * Check if SSL is used. Stolen from WP. 859 * 860 * @return bool 861 */ 862 function yourls_is_ssl() { 863 $is_ssl = false; 864 if ( isset( $_SERVER[ 'HTTPS' ] ) ) { 865 if ( 'on' == strtolower( $_SERVER[ 'HTTPS' ] ) ) { 866 $is_ssl = true; 867 } 868 if ( '1' == $_SERVER[ 'HTTPS' ] ) { 869 $is_ssl = true; 870 } 871 } 872 elseif ( isset( $_SERVER[ 'HTTP_X_FORWARDED_PROTO' ] ) ) { 873 if ( 'https' == strtolower( $_SERVER[ 'HTTP_X_FORWARDED_PROTO' ] ) ) { 874 $is_ssl = true; 875 } 876 } 877 elseif ( isset( $_SERVER[ 'SERVER_PORT' ] ) && ( '443' == $_SERVER[ 'SERVER_PORT' ] ) ) { 878 $is_ssl = true; 879 } 880 return (bool)yourls_apply_filter( 'is_ssl', $is_ssl ); 881 } 882 883 /** 884 * Get a remote page title 885 * 886 * This function returns a string: either the page title as defined in HTML, or the URL if not found 887 * The function tries to convert funky characters found in titles to UTF8, from the detected charset. 888 * Charset in use is guessed from HTML meta tag, or if not found, from server's 'content-type' response. 889 * 890 * @param string $url URL 891 * @return string Title (sanitized) or the URL if no title found 892 */ 893 function yourls_get_remote_title( $url ) { 894 // Allow plugins to short-circuit the whole function 895 $pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url ); 896 if ( false !== $pre ) { 897 return $pre; 898 } 899 900 $url = yourls_sanitize_url( $url ); 901 902 // Only deal with http(s):// 903 if ( !in_array( yourls_get_protocol( $url ), [ 'http://', 'https://' ] ) ) { 904 return $url; 905 } 906 907 $title = $charset = false; 908 909 $max_bytes = yourls_apply_filter( 'get_remote_title_max_byte', 32768 ); // limit data fetching to 32K in order to find a <title> tag 910 911 $response = yourls_http_get( $url, [], [], [ 'max_bytes' => $max_bytes ] ); // can be a Request object or an error string 912 if ( is_string( $response ) ) { 913 return $url; 914 } 915 916 // Page content. No content? Return the URL 917 $content = $response->body; 918 if ( !$content ) { 919 return $url; 920 } 921 922 // look for <title>. No title found? Return the URL 923 if ( preg_match( '/<title>(.*?)<\/title>/is', $content, $found ) ) { 924 $title = $found[ 1 ]; 925 unset( $found ); 926 } 927 if ( !$title ) { 928 return $url; 929 } 930 931 // Now we have a title. We'll try to get proper utf8 from it. 932 933 // Get charset as (and if) defined by the HTML meta tag. We should match 934 // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 935 // or <meta charset='utf-8'> and all possible variations: see https://gist.github.com/ozh/7951236 936 if ( preg_match( '/<meta[^>]*charset\s*=["\' ]*([a-zA-Z0-9\-_]+)/is', $content, $found ) ) { 937 if ( yourls_is_valid_charset( $found[ 1 ] ) ) { 938 $charset = $found[ 1 ]; 939 } 940 unset( $found ); 941 } 942 if ( empty( $charset ) ) { 943 // No charset found in HTML. Get charset as (and if) defined by the server response 944 $_charset = current( $response->headers->getValues( 'content-type' ) ); 945 if ( preg_match( '/charset=(\S+)/', $_charset, $found ) ) { 946 $_charset = trim( $found[ 1 ], ';' ); 947 if ( yourls_is_valid_charset( $_charset ) ) { 948 $charset = $_charset; 949 } 950 unset( $found ); 951 } 952 } 953 954 // Conversion to utf-8 if what we have is not utf8 already 955 if ( strtolower( $charset ) != 'utf-8' && function_exists( 'mb_convert_encoding' ) ) { 956 // We use @ to remove warnings because mb_ functions are easily bitching about illegal chars 957 if ( $charset ) { 958 $title = @mb_convert_encoding( $title, 'UTF-8', $charset ); 959 } 960 else { 961 $title = @mb_convert_encoding( $title, 'UTF-8' ); 962 } 963 } 964 965 // Remove HTML entities 966 $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' ); 967 968 // Strip out evil things 969 $title = yourls_sanitize_title( $title, $url ); 970 971 return (string)yourls_apply_filter( 'get_remote_title', $title, $url ); 972 } 973 974 /** 975 * Is supported charset encoding for conversion. 976 * 977 * @return bool 978 */ 979 function yourls_is_valid_charset( $charset ) { 980 if ( ! function_exists( 'mb_list_encodings' ) ) { 981 return false; // Okay to return false if mb_list_encodings() is not available since we won't be able to convert the charset. 982 } 983 $charset = strtolower( $charset ); 984 $charsets = array_map( 'strtolower', mb_list_encodings() ); 985 986 return in_array( $charset, $charsets ); 987 } 988 989 /** 990 * Quick UA check for mobile devices. 991 * 992 * @return bool 993 */ 994 function yourls_is_mobile_device() { 995 // Strings searched 996 $mobiles = [ 997 'android', 'blackberry', 'blazer', 998 'compal', 'elaine', 'fennec', 'hiptop', 999 'iemobile', 'iphone', 'ipod', 'ipad', 1000 'iris', 'kindle', 'opera mobi', 'opera mini', 1001 'palm', 'phone', 'pocket', 'psp', 'symbian', 1002 'treo', 'wap', 'windows ce', 'windows phone' 1003 ]; 1004 1005 // Current user-agent 1006 $current = strtolower( $_SERVER['HTTP_USER_AGENT'] ); 1007 1008 // Check and return 1009 $is_mobile = ( str_replace( $mobiles, '', $current ) != $current ); 1010 return (bool)yourls_apply_filter( 'is_mobile_device', $is_mobile ); 1011 } 1012 1013 /** 1014 * Get request in YOURLS base (eg in 'http://sho.rt/yourls/abcd' get 'abdc') 1015 * 1016 * With no parameter passed, this function will guess current page and consider 1017 * it is the requested page. 1018 * For testing purposes, parameters can be passed. 1019 * 1020 * @since 1.5 1021 * @param string $yourls_site Optional, YOURLS installation URL (default to constant YOURLS_SITE) 1022 * @param string $uri Optional, page requested (default to $_SERVER['REQUEST_URI'] eg '/yourls/abcd' ) 1023 * @return string request relative to YOURLS base (eg 'abdc') 1024 */ 1025 function yourls_get_request($yourls_site = '', $uri = '') { 1026 // Allow plugins to short-circuit the whole function 1027 $pre = yourls_apply_filter( 'shunt_get_request', false ); 1028 if ( false !== $pre ) { 1029 return $pre; 1030 } 1031 1032 yourls_do_action( 'pre_get_request', $yourls_site, $uri ); 1033 1034 // Default values 1035 if ( '' === $yourls_site ) { 1036 $yourls_site = yourls_get_yourls_site(); 1037 } 1038 if ( '' === $uri ) { 1039 $uri = $_SERVER[ 'REQUEST_URI' ]; 1040 } 1041 1042 // Even though the config sample states YOURLS_SITE should be set without trailing slash... 1043 $yourls_site = rtrim( $yourls_site, '/' ); 1044 1045 // Now strip the YOURLS_SITE path part out of the requested URI, and get the request relative to YOURLS base 1046 // +---------------------------+-------------------------+---------------------+--------------+ 1047 // | if we request | and YOURLS is hosted on | YOURLS path part is | "request" is | 1048 // +---------------------------+-------------------------+---------------------+--------------+ 1049 // | http://sho.rt/abc | http://sho.rt | / | abc | 1050 // | https://SHO.rt/subdir/abc | https://shor.rt/subdir/ | /subdir/ | abc | 1051 // +---------------------------+-------------------------+---------------------+--------------+ 1052 // and so on. You can find various test cases in /tests/tests/utilities/get_request.php 1053 1054 // Take only the URL_PATH part of YOURLS_SITE (ie "https://sho.rt:1337/path/to/yourls" -> "/path/to/yourls") 1055 $yourls_site = parse_url( $yourls_site, PHP_URL_PATH ).'/'; 1056 1057 // Strip path part from request if exists 1058 $request = $uri; 1059 if ( substr( $uri, 0, strlen( $yourls_site ) ) == $yourls_site ) { 1060 $request = ltrim( substr( $uri, strlen( $yourls_site ) ), '/' ); 1061 } 1062 1063 // Unless request looks like a full URL (ie request is a simple keyword) strip query string 1064 if ( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) { 1065 $request = current( explode( '?', $request ) ); 1066 } 1067 1068 $request = yourls_sanitize_url( $request ); 1069 1070 return (string)yourls_apply_filter( 'get_request', $request ); 1071 } 1072 1073 /** 1074 * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP. 1075 * 1076 * We also strip $_COOKIE from $_REQUEST to allow our lazy using $_REQUEST without 3rd party cookie interfering. 1077 * See #3383 for explanation. 1078 * 1079 * @since 1.5.1 1080 * @return void 1081 */ 1082 function yourls_fix_request_uri() { 1083 1084 $default_server_values = [ 1085 'SERVER_SOFTWARE' => '', 1086 'REQUEST_URI' => '', 1087 ]; 1088 $_SERVER = array_merge( $default_server_values, $_SERVER ); 1089 1090 // Make $_REQUEST with only $_GET and $_POST, not $_COOKIE. See #3383. 1091 $_REQUEST = array_merge( $_GET, $_POST ); 1092 1093 // Fix for IIS when running with PHP ISAPI 1094 if ( empty( $_SERVER[ 'REQUEST_URI' ] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER[ 'SERVER_SOFTWARE' ] ) ) ) { 1095 1096 // IIS Mod-Rewrite 1097 if ( isset( $_SERVER[ 'HTTP_X_ORIGINAL_URL' ] ) ) { 1098 $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'HTTP_X_ORIGINAL_URL' ]; 1099 } 1100 // IIS Isapi_Rewrite 1101 elseif ( isset( $_SERVER[ 'HTTP_X_REWRITE_URL' ] ) ) { 1102 $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'HTTP_X_REWRITE_URL' ]; 1103 } 1104 else { 1105 // Use ORIG_PATH_INFO if there is no PATH_INFO 1106 if ( !isset( $_SERVER[ 'PATH_INFO' ] ) && isset( $_SERVER[ 'ORIG_PATH_INFO' ] ) ) { 1107 $_SERVER[ 'PATH_INFO' ] = $_SERVER[ 'ORIG_PATH_INFO' ]; 1108 } 1109 1110 // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice) 1111 if ( isset( $_SERVER[ 'PATH_INFO' ] ) ) { 1112 if ( $_SERVER[ 'PATH_INFO' ] == $_SERVER[ 'SCRIPT_NAME' ] ) { 1113 $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'PATH_INFO' ]; 1114 } 1115 else { 1116 $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'SCRIPT_NAME' ].$_SERVER[ 'PATH_INFO' ]; 1117 } 1118 } 1119 1120 // Append the query string if it exists and isn't null 1121 if ( !empty( $_SERVER[ 'QUERY_STRING' ] ) ) { 1122 $_SERVER[ 'REQUEST_URI' ] .= '?'.$_SERVER[ 'QUERY_STRING' ]; 1123 } 1124 } 1125 } 1126 } 1127 1128 /** 1129 * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP. 1130 * 1131 * @return void 1132 */ 1133 function yourls_check_maintenance_mode() { 1134 $dot_file = YOURLS_ABSPATH . '/.maintenance' ; 1135 1136 if ( !file_exists( $dot_file ) || yourls_is_upgrading() || yourls_is_installing() ) { 1137 return; 1138 } 1139 1140 global $maintenance_start; 1141 yourls_include_file_sandbox( $dot_file ); 1142 // If the $maintenance_start timestamp is older than 10 minutes, don't die. 1143 if ( ( time() - $maintenance_start ) >= 600 ) { 1144 return; 1145 } 1146 1147 // Use any /user/maintenance.php file 1148 $file = YOURLS_USERDIR . '/maintenance.php'; 1149 if(file_exists($file)) { 1150 if(yourls_include_file_sandbox( $file ) == true) { 1151 die(); 1152 } 1153 } 1154 1155 // Or use the default messages 1156 $title = yourls__('Service temporarily unavailable'); 1157 $message = yourls__('Our service is currently undergoing scheduled maintenance.') . "</p>\n<p>" . 1158 yourls__('Things should not last very long, thank you for your patience and please excuse the inconvenience'); 1159 yourls_die( $message, $title, 503 ); 1160 } 1161 1162 /** 1163 * Check if a URL protocol is allowed 1164 * 1165 * Checks a URL against a list of whitelisted protocols. Protocols must be defined with 1166 * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid 1167 * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either 1168 * 1169 * @since 1.6 1170 * @see yourls_get_protocol() 1171 * 1172 * @param string $url URL to be check 1173 * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols 1174 * @return bool true if protocol allowed, false otherwise 1175 */ 1176 function yourls_is_allowed_protocol( $url, $protocols = [] ) { 1177 if ( empty( $protocols ) ) { 1178 global $yourls_allowedprotocols; 1179 $protocols = $yourls_allowedprotocols; 1180 } 1181 1182 return yourls_apply_filter( 'is_allowed_protocol', in_array( yourls_get_protocol( $url ), $protocols ), $url, $protocols ); 1183 } 1184 1185 /** 1186 * Get protocol from a URL (eg mailto:, http:// ...) 1187 * 1188 * What we liberally call a "protocol" in YOURLS is the scheme name + colon + double slashes if present of a URI. Examples: 1189 * "something://blah" -> "something://" 1190 * "something:blah" -> "something:" 1191 * "something:/blah" -> "something:" 1192 * 1193 * Unit Tests for this function are located in tests/format/urls.php 1194 * 1195 * @since 1.6 1196 * 1197 * @param string $url URL to be check 1198 * @return string Protocol, with slash slash if applicable. Empty string if no protocol 1199 */ 1200 function yourls_get_protocol( $url ) { 1201 /* 1202 http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax 1203 The scheme name consists of a sequence of characters beginning with a letter and followed by any 1204 combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are 1205 case-insensitive, the canonical form is lowercase and documents that specify schemes must do so 1206 with lowercase letters. It is followed by a colon (":"). 1207 */ 1208 preg_match( '!^[a-zA-Z][a-zA-Z0-9+.-]+:(//)?!', $url, $matches ); 1209 return (string)yourls_apply_filter( 'get_protocol', isset( $matches[0] ) ? $matches[0] : '', $url ); 1210 } 1211 1212 /** 1213 * Get relative URL (eg 'abc' from 'http://sho.rt/abc') 1214 * 1215 * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is 1216 * or return empty string if $strict is true 1217 * 1218 * @since 1.6 1219 * @param string $url URL to relativize 1220 * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string 1221 * @return string URL 1222 */ 1223 function yourls_get_relative_url( $url, $strict = true ) { 1224 $url = yourls_sanitize_url( $url ); 1225 1226 // Remove protocols to make it easier 1227 $noproto_url = str_replace( 'https:', 'http:', $url ); 1228 $noproto_site = str_replace( 'https:', 'http:', yourls_get_yourls_site() ); 1229 1230 // Trim URL from YOURLS root URL : if no modification made, URL wasn't relative 1231 $_url = str_replace( $noproto_site.'/', '', $noproto_url ); 1232 if ( $_url == $noproto_url ) { 1233 $_url = ( $strict ? '' : $url ); 1234 } 1235 return yourls_apply_filter( 'get_relative_url', $_url, $url ); 1236 } 1237 1238 /** 1239 * Marks a function as deprecated and informs that it has been used. Stolen from WP. 1240 * 1241 * There is a hook deprecated_function that will be called that can be used 1242 * to get the backtrace up to what file and function called the deprecated 1243 * function. 1244 * 1245 * The current behavior is to trigger a user error if YOURLS_DEBUG is true. 1246 * 1247 * This function is to be used in every function that is deprecated. 1248 * 1249 * @since 1.6 1250 * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead, 1251 * and the version the function was deprecated in. 1252 * @uses yourls_apply_filter() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do 1253 * trigger or false to not trigger error. 1254 * 1255 * @param string $function The function that was called 1256 * @param string $version The version of WordPress that deprecated the function 1257 * @param string $replacement Optional. The function that should have been called 1258 * @return void 1259 */ 1260 function yourls_deprecated_function( $function, $version, $replacement = null ) { 1261 1262 yourls_do_action( 'deprecated_function', $function, $replacement, $version ); 1263 1264 // Allow plugin to filter the output error trigger 1265 if ( yourls_get_debug_mode() && yourls_apply_filter( 'deprecated_function_trigger_error', true ) ) { 1266 if ( ! is_null( $replacement ) ) 1267 trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) ); 1268 else 1269 trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) ); 1270 } 1271 } 1272 1273 /** 1274 * Explode a URL in an array of ( 'protocol' , 'slashes if any', 'rest of the URL' ) 1275 * 1276 * Some hosts trip up when a query string contains 'http://' - see http://git.io/j1FlJg 1277 * The idea is that instead of passing the whole URL to a bookmarklet, eg index.php?u=http://blah.com, 1278 * we pass it by pieces to fool the server, eg index.php?proto=http:&slashes=//&rest=blah.com 1279 * 1280 * Known limitation: this won't work if the rest of the URL itself contains 'http://', for example 1281 * if rest = blah.com/file.php?url=http://foo.com 1282 * 1283 * Sample returns: 1284 * 1285 * with 'mailto:[email protected]?subject=hey' : 1286 * array( 'protocol' => 'mailto:', 'slashes' => '', 'rest' => '[email protected]?subject=hey' ) 1287 * 1288 * with 'http://example.com/blah.html' : 1289 * array( 'protocol' => 'http:', 'slashes' => '//', 'rest' => 'example.com/blah.html' ) 1290 * 1291 * @since 1.7 1292 * @param string $url URL to be parsed 1293 * @param array $array Optional, array of key names to be used in returned array 1294 * @return array|false false if no protocol found, array of ('protocol' , 'slashes', 'rest') otherwise 1295 */ 1296 function yourls_get_protocol_slashes_and_rest( $url, $array = [ 'protocol', 'slashes', 'rest' ] ) { 1297 $proto = yourls_get_protocol( $url ); 1298 1299 if ( !$proto or count( $array ) != 3 ) { 1300 return false; 1301 } 1302 1303 list( $null, $rest ) = explode( $proto, $url, 2 ); 1304 1305 list( $proto, $slashes ) = explode( ':', $proto ); 1306 1307 return [ 1308 $array[ 0 ] => $proto.':', 1309 $array[ 1 ] => $slashes, 1310 $array[ 2 ] => $rest 1311 ]; 1312 } 1313 1314 /** 1315 * Set URL scheme (HTTP or HTTPS) to a URL 1316 * 1317 * @since 1.7.1 1318 * @param string $url URL 1319 * @param string $scheme scheme, either 'http' or 'https' 1320 * @return string URL with chosen scheme 1321 */ 1322 function yourls_set_url_scheme( $url, $scheme = '' ) { 1323 if ( in_array( $scheme, [ 'http', 'https' ] ) ) { 1324 $url = preg_replace( '!^[a-zA-Z0-9+.-]+://!', $scheme.'://', $url ); 1325 } 1326 return $url; 1327 } 1328 1329 /** 1330 * Tell if there is a new YOURLS version 1331 * 1332 * This function checks, if needed, if there's a new version of YOURLS and, if applicable, displays 1333 * an update notice. 1334 * 1335 * @since 1.7.3 1336 * @return void 1337 */ 1338 function yourls_tell_if_new_version() { 1339 yourls_debug_log( 'Check for new version: '.( yourls_maybe_check_core_version() ? 'yes' : 'no' ) ); 1340 yourls_new_core_version_notice(YOURLS_VERSION); 1341 } 1342 1343 /** 1344 * File include sandbox 1345 * 1346 * Attempt to include a PHP file, fail with an error message if the file isn't valid PHP code. 1347 * This function does not check first if the file exists : depending on use case, you may check first. 1348 * 1349 * @since 1.9.2 1350 * @param string $file filename (full path) 1351 * @return string|bool string if error, true if success 1352 */ 1353 function yourls_include_file_sandbox($file) { 1354 try { 1355 if (is_readable( $file )) { 1356 require_once $file; 1357 yourls_debug_log("loaded $file"); 1358 return true; 1359 } 1360 } catch ( \Throwable $e ) { 1361 yourls_debug_log("could not load $file"); 1362 return sprintf("%s (%s : %s)", $e->getMessage() , $e->getFile() , $e->getLine() ); 1363 } 1364 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Nov 5 05:10:36 2025 | Cross-referenced by PHPXref 0.7.1 |