| [ 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', yourls_shunt_default(), $keyword, $clicks ); 93 if ( yourls_shunt_default() !== $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('write-update_clicks'); 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('read-get_stats')->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('read-get_db_stats')->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', yourls_shunt_default() ); 318 if ( yourls_shunt_default() !== $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', yourls_shunt_default() ); 360 if ( yourls_shunt_default() !== $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', yourls_shunt_default(), $keyword ); 519 if ( yourls_shunt_default() !== $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('write-log_redirect')->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', yourls_shunt_default(), $ip ); 647 if ( yourls_shunt_default() !== $pre ) { 648 return $pre; 649 } 650 651 yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here 652 653 // Raise white flag if installing or if no flood delay defined 654 if( 655 ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) || 656 !defined('YOURLS_FLOOD_DELAY_SECONDS') || 657 yourls_is_installing() 658 ) 659 return true; 660 661 // Don't throttle logged in users 662 if( yourls_is_private() ) { 663 if( yourls_is_valid_user() === true ) 664 return true; 665 } 666 667 // Don't throttle whitelist IPs 668 if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) { 669 $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST ); 670 foreach( (array)$whitelist_ips as $whitelist_ip ) { 671 $whitelist_ip = trim( $whitelist_ip ); 672 if ( $whitelist_ip == $ip ) 673 return true; 674 } 675 } 676 677 $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() ); 678 679 yourls_do_action( 'check_ip_flood', $ip ); 680 681 $table = YOURLS_DB_TABLE_URL; 682 $lasttime = yourls_get_db('read-check_ip_flood')->fetchValue( "SELECT `timestamp` FROM $table WHERE `ip` = :ip ORDER BY `timestamp` DESC LIMIT 1", [ 'ip' => $ip ] ); 683 if( $lasttime ) { 684 $now = date( 'U' ); 685 $then = date( 'U', strtotime( $lasttime ) ); 686 if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) { 687 // Flood! 688 yourls_do_action( 'ip_flood', $ip, $now - $then ); 689 yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Too Many Requests' ), 429 ); 690 } 691 } 692 693 return true; 694 } 695 696 /** 697 * Check if YOURLS is installing 698 * 699 * @since 1.6 700 * @return bool 701 */ 702 function yourls_is_installing() { 703 return (bool)yourls_apply_filter( 'is_installing', defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING ); 704 } 705 706 /** 707 * Check if YOURLS is upgrading 708 * 709 * @since 1.6 710 * @return bool 711 */ 712 function yourls_is_upgrading() { 713 return (bool)yourls_apply_filter( 'is_upgrading', defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING ); 714 } 715 716 /** 717 * Check if YOURLS is installed 718 * 719 * Checks property $ydb->installed that is created by yourls_get_all_options() 720 * 721 * See inline comment for updating from 1.3 or prior. 722 * 723 * @return bool 724 */ 725 function yourls_is_installed() { 726 return (bool)yourls_apply_filter( 'is_installed', yourls_get_db('read-is_installed')->is_installed() ); 727 } 728 729 /** 730 * Set installed state 731 * 732 * @since 1.7.3 733 * @param bool $bool whether YOURLS is installed or not 734 * @return void 735 */ 736 function yourls_set_installed( $bool ) { 737 yourls_get_db('read-set_installed')->set_installed( $bool ); 738 } 739 740 /** 741 * Generate random string of (int)$length length and type $type (see function for details) 742 * 743 * @param int $length 744 * @param int $type 745 * @param string $charlist 746 * @return mixed|string 747 */ 748 function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) { 749 $length = intval( $length ); 750 751 // define possible characters 752 switch ( $type ) { 753 754 // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords. 755 case '1': 756 $possible = "23456789bcdfghjkmnpqrstvwxyz"; 757 break; 758 759 // Same, with lower + upper 760 case '2': 761 $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ"; 762 break; 763 764 // all letters, lowercase 765 case '3': 766 $possible = "abcdefghijklmnopqrstuvwxyz"; 767 break; 768 769 // all letters, lowercase + uppercase 770 case '4': 771 $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 772 break; 773 774 // all digits & letters lowercase 775 case '5': 776 $possible = "0123456789abcdefghijklmnopqrstuvwxyz"; 777 break; 778 779 // all digits & letters lowercase + uppercase 780 case '6': 781 $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 782 break; 783 784 // custom char list, or comply to charset as defined in config 785 default: 786 case '0': 787 $possible = $charlist ? $charlist : yourls_get_shorturl_charset(); 788 break; 789 } 790 791 $str = substr( str_shuffle( $possible ), 0, $length ); 792 return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist ); 793 } 794 795 /** 796 * Check if we're in API mode. 797 * 798 * @return bool 799 */ 800 function yourls_is_API() { 801 return (bool)yourls_apply_filter( 'is_API', defined( 'YOURLS_API' ) && YOURLS_API ); 802 } 803 804 /** 805 * Check if we're in Ajax mode. 806 * 807 * @return bool 808 */ 809 function yourls_is_Ajax() { 810 return (bool)yourls_apply_filter( 'is_Ajax', defined( 'YOURLS_AJAX' ) && YOURLS_AJAX ); 811 } 812 813 /** 814 * Check if we're in GO mode (yourls-go.php). 815 * 816 * @return bool 817 */ 818 function yourls_is_GO() { 819 return (bool)yourls_apply_filter( 'is_GO', defined( 'YOURLS_GO' ) && YOURLS_GO ); 820 } 821 822 /** 823 * Check if we're displaying stats infos (yourls-infos.php). Returns bool 824 * 825 * @return bool 826 */ 827 function yourls_is_infos() { 828 return (bool)yourls_apply_filter( 'is_infos', defined( 'YOURLS_INFOS' ) && YOURLS_INFOS ); 829 } 830 831 /** 832 * Check if we're in the admin area. Returns bool. Does not relate with user rights. 833 * 834 * @return bool 835 */ 836 function yourls_is_admin() { 837 return (bool)yourls_apply_filter( 'is_admin', defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN ); 838 } 839 840 /** 841 * Check if the server seems to be running on Windows. Not exactly sure how reliable this is. 842 * 843 * @return bool 844 */ 845 function yourls_is_windows() { 846 return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\'; 847 } 848 849 /** 850 * Check if SSL is required. 851 * 852 * @return bool 853 */ 854 function yourls_needs_ssl() { 855 return (bool)yourls_apply_filter( 'needs_ssl', defined( 'YOURLS_ADMIN_SSL' ) && YOURLS_ADMIN_SSL ); 856 } 857 858 /** 859 * Check if SSL is used. Stolen from WP. 860 * 861 * @return bool 862 */ 863 function yourls_is_ssl() { 864 $is_ssl = false; 865 if ( isset( $_SERVER[ 'HTTPS' ] ) ) { 866 if ( 'on' == strtolower( $_SERVER[ 'HTTPS' ] ) ) { 867 $is_ssl = true; 868 } 869 if ( '1' == $_SERVER[ 'HTTPS' ] ) { 870 $is_ssl = true; 871 } 872 } 873 elseif ( isset( $_SERVER[ 'HTTP_X_FORWARDED_PROTO' ] ) ) { 874 if ( 'https' == strtolower( $_SERVER[ 'HTTP_X_FORWARDED_PROTO' ] ) ) { 875 $is_ssl = true; 876 } 877 } 878 elseif ( isset( $_SERVER[ 'SERVER_PORT' ] ) && ( '443' == $_SERVER[ 'SERVER_PORT' ] ) ) { 879 $is_ssl = true; 880 } 881 return (bool)yourls_apply_filter( 'is_ssl', $is_ssl ); 882 } 883 884 /** 885 * Get a remote page title 886 * 887 * This function returns a string: either the page title as defined in HTML, or the URL if not found 888 * The function tries to convert funky characters found in titles to UTF8, from the detected charset. 889 * Charset in use is guessed from HTML meta tag, or if not found, from server's 'content-type' response. 890 * 891 * @param string $url URL 892 * @return string Title (sanitized) or the URL if no title found 893 */ 894 function yourls_get_remote_title( $url ) { 895 // Allow plugins to short-circuit the whole function 896 $pre = yourls_apply_filter( 'shunt_get_remote_title', yourls_shunt_default(), $url ); 897 if ( yourls_shunt_default() !== $pre ) { 898 return $pre; 899 } 900 901 $url = yourls_sanitize_url( $url ); 902 903 // Only deal with http(s):// 904 if ( !in_array( yourls_get_protocol( $url ), [ 'http://', 'https://' ] ) ) { 905 return $url; 906 } 907 908 $title = $charset = false; 909 910 $max_bytes = yourls_apply_filter( 'get_remote_title_max_byte', 32768 ); // limit data fetching to 32K in order to find a <title> tag 911 912 $response = yourls_http_get( $url, [], [], [ 'max_bytes' => $max_bytes ] ); // can be a Request object or an error string 913 if ( is_string( $response ) ) { 914 return $url; 915 } 916 917 // Page content. No content? Return the URL 918 $content = $response->body; 919 if ( !$content ) { 920 return $url; 921 } 922 923 // look for <title>. No title found? Return the URL 924 if ( preg_match( '/<title>(.*?)<\/title>/is', $content, $found ) ) { 925 $title = $found[ 1 ]; 926 unset( $found ); 927 } 928 if ( !$title ) { 929 return $url; 930 } 931 932 // Now we have a title. We'll try to get proper utf8 from it. 933 934 // Get charset as (and if) defined by the HTML meta tag. We should match 935 // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 936 // or <meta charset='utf-8'> and all possible variations: see https://gist.github.com/ozh/7951236 937 if ( preg_match( '/<meta[^>]*charset\s*=["\' ]*([a-zA-Z0-9\-_]+)/is', $content, $found ) ) { 938 if ( yourls_is_valid_charset( $found[ 1 ] ) ) { 939 $charset = $found[ 1 ]; 940 } 941 unset( $found ); 942 } 943 if ( empty( $charset ) ) { 944 // No charset found in HTML. Get charset as (and if) defined by the server response 945 $_charset = current( $response->headers->getValues( 'content-type' ) ); 946 if ( preg_match( '/charset=(\S+)/', $_charset, $found ) ) { 947 $_charset = trim( $found[ 1 ], ';' ); 948 if ( yourls_is_valid_charset( $_charset ) ) { 949 $charset = $_charset; 950 } 951 unset( $found ); 952 } 953 } 954 955 // Conversion to utf-8 if what we have is not utf8 already 956 if ( strtolower( $charset ) != 'utf-8' && function_exists( 'mb_convert_encoding' ) ) { 957 // We use @ to remove warnings because mb_ functions are easily bitching about illegal chars 958 if ( $charset ) { 959 $title = @mb_convert_encoding( $title, 'UTF-8', $charset ); 960 } 961 else { 962 $title = @mb_convert_encoding( $title, 'UTF-8' ); 963 } 964 } 965 966 // Remove HTML entities 967 $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' ); 968 969 // Strip out evil things 970 $title = yourls_sanitize_title( $title, $url ); 971 972 return (string)yourls_apply_filter( 'get_remote_title', $title, $url ); 973 } 974 975 /** 976 * Is supported charset encoding for conversion. 977 * 978 * @return bool 979 */ 980 function yourls_is_valid_charset( $charset ) { 981 if ( ! function_exists( 'mb_list_encodings' ) ) { 982 return false; // Okay to return false if mb_list_encodings() is not available since we won't be able to convert the charset. 983 } 984 $charset = strtolower( $charset ); 985 $charsets = array_map( 'strtolower', mb_list_encodings() ); 986 987 return in_array( $charset, $charsets ); 988 } 989 990 /** 991 * Quick UA check for mobile devices. 992 * 993 * @return bool 994 */ 995 function yourls_is_mobile_device() { 996 // Strings searched 997 $mobiles = [ 998 'android', 'blackberry', 'blazer', 999 'compal', 'elaine', 'fennec', 'hiptop', 1000 'iemobile', 'iphone', 'ipod', 'ipad', 1001 'iris', 'kindle', 'opera mobi', 'opera mini', 1002 'palm', 'phone', 'pocket', 'psp', 'symbian', 1003 'treo', 'wap', 'windows ce', 'windows phone' 1004 ]; 1005 1006 // Current user-agent 1007 $current = strtolower( $_SERVER['HTTP_USER_AGENT'] ); 1008 1009 // Check and return 1010 $is_mobile = ( str_replace( $mobiles, '', $current ) != $current ); 1011 return (bool)yourls_apply_filter( 'is_mobile_device', $is_mobile ); 1012 } 1013 1014 /** 1015 * Get request in YOURLS base (eg in 'http://sho.rt/yourls/abcd' get 'abdc') 1016 * 1017 * With no parameter passed, this function will guess current page and consider 1018 * it is the requested page. 1019 * For testing purposes, parameters can be passed. 1020 * 1021 * @since 1.5 1022 * @param string $yourls_site Optional, YOURLS installation URL (default to constant YOURLS_SITE) 1023 * @param string $uri Optional, page requested (default to $_SERVER['REQUEST_URI'] eg '/yourls/abcd' ) 1024 * @return string request relative to YOURLS base (eg 'abdc') 1025 */ 1026 function yourls_get_request($yourls_site = '', $uri = '') { 1027 // Allow plugins to short-circuit the whole function 1028 $pre = yourls_apply_filter( 'shunt_get_request', yourls_shunt_default() ); 1029 if ( yourls_shunt_default() !== $pre ) { 1030 return $pre; 1031 } 1032 1033 yourls_do_action( 'pre_get_request', $yourls_site, $uri ); 1034 1035 // Default values 1036 if ( '' === $yourls_site ) { 1037 $yourls_site = yourls_get_yourls_site(); 1038 } 1039 if ( '' === $uri ) { 1040 $uri = $_SERVER[ 'REQUEST_URI' ]; 1041 } 1042 1043 // Even though the config sample states YOURLS_SITE should be set without trailing slash... 1044 $yourls_site = rtrim( $yourls_site, '/' ); 1045 1046 // Now strip the YOURLS_SITE path part out of the requested URI, and get the request relative to YOURLS base 1047 // +---------------------------+-------------------------+---------------------+--------------+ 1048 // | if we request | and YOURLS is hosted on | YOURLS path part is | "request" is | 1049 // +---------------------------+-------------------------+---------------------+--------------+ 1050 // | http://sho.rt/abc | http://sho.rt | / | abc | 1051 // | https://SHO.rt/subdir/abc | https://shor.rt/subdir/ | /subdir/ | abc | 1052 // +---------------------------+-------------------------+---------------------+--------------+ 1053 // and so on. You can find various test cases in /tests/tests/utilities/get_request.php 1054 1055 // Take only the URL_PATH part of YOURLS_SITE (ie "https://sho.rt:1337/path/to/yourls" -> "/path/to/yourls") 1056 $yourls_site = parse_url( $yourls_site, PHP_URL_PATH ).'/'; 1057 1058 // Strip path part from request if exists 1059 $request = $uri; 1060 if ( substr( $uri, 0, strlen( $yourls_site ) ) == $yourls_site ) { 1061 $request = ltrim( substr( $uri, strlen( $yourls_site ) ), '/' ); 1062 } 1063 1064 // Unless request looks like a full URL (ie request is a simple keyword) strip query string 1065 if ( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) { 1066 $request = current( explode( '?', $request ) ); 1067 } 1068 1069 $request = yourls_sanitize_url( $request ); 1070 1071 return (string)yourls_apply_filter( 'get_request', $request ); 1072 } 1073 1074 /** 1075 * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP. 1076 * 1077 * We also strip $_COOKIE from $_REQUEST to allow our lazy using $_REQUEST without 3rd party cookie interfering. 1078 * See #3383 for explanation. 1079 * 1080 * @since 1.5.1 1081 * @return void 1082 */ 1083 function yourls_fix_request_uri() { 1084 1085 $default_server_values = [ 1086 'SERVER_SOFTWARE' => '', 1087 'REQUEST_URI' => '', 1088 ]; 1089 $_SERVER = array_merge( $default_server_values, $_SERVER ); 1090 1091 // Make $_REQUEST with only $_GET and $_POST, not $_COOKIE. See #3383. 1092 $_REQUEST = array_merge( $_GET, $_POST ); 1093 1094 // Fix for IIS when running with PHP ISAPI 1095 if ( empty( $_SERVER[ 'REQUEST_URI' ] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER[ 'SERVER_SOFTWARE' ] ) ) ) { 1096 1097 // IIS Mod-Rewrite 1098 if ( isset( $_SERVER[ 'HTTP_X_ORIGINAL_URL' ] ) ) { 1099 $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'HTTP_X_ORIGINAL_URL' ]; 1100 } 1101 // IIS Isapi_Rewrite 1102 elseif ( isset( $_SERVER[ 'HTTP_X_REWRITE_URL' ] ) ) { 1103 $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'HTTP_X_REWRITE_URL' ]; 1104 } 1105 else { 1106 // Use ORIG_PATH_INFO if there is no PATH_INFO 1107 if ( !isset( $_SERVER[ 'PATH_INFO' ] ) && isset( $_SERVER[ 'ORIG_PATH_INFO' ] ) ) { 1108 $_SERVER[ 'PATH_INFO' ] = $_SERVER[ 'ORIG_PATH_INFO' ]; 1109 } 1110 1111 // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice) 1112 if ( isset( $_SERVER[ 'PATH_INFO' ] ) ) { 1113 if ( $_SERVER[ 'PATH_INFO' ] == $_SERVER[ 'SCRIPT_NAME' ] ) { 1114 $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'PATH_INFO' ]; 1115 } 1116 else { 1117 $_SERVER[ 'REQUEST_URI' ] = $_SERVER[ 'SCRIPT_NAME' ].$_SERVER[ 'PATH_INFO' ]; 1118 } 1119 } 1120 1121 // Append the query string if it exists and isn't null 1122 if ( !empty( $_SERVER[ 'QUERY_STRING' ] ) ) { 1123 $_SERVER[ 'REQUEST_URI' ] .= '?'.$_SERVER[ 'QUERY_STRING' ]; 1124 } 1125 } 1126 } 1127 } 1128 1129 /** 1130 * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP. 1131 * 1132 * @return void 1133 */ 1134 function yourls_check_maintenance_mode() { 1135 $dot_file = YOURLS_ABSPATH . '/.maintenance' ; 1136 1137 if ( !file_exists( $dot_file ) || yourls_is_upgrading() || yourls_is_installing() ) { 1138 return; 1139 } 1140 1141 global $maintenance_start; 1142 yourls_include_file_sandbox( $dot_file ); 1143 // If the $maintenance_start timestamp is older than 10 minutes, don't die. 1144 if ( ( time() - $maintenance_start ) >= 600 ) { 1145 return; 1146 } 1147 1148 // Use any /user/maintenance.php file 1149 $file = YOURLS_USERDIR . '/maintenance.php'; 1150 if(file_exists($file)) { 1151 if(yourls_include_file_sandbox( $file ) == true) { 1152 die(); 1153 } 1154 } 1155 1156 // Or use the default messages 1157 $title = yourls__('Service temporarily unavailable'); 1158 $message = yourls__('Our service is currently undergoing scheduled maintenance.') . "</p>\n<p>" . 1159 yourls__('Things should not last very long, thank you for your patience and please excuse the inconvenience'); 1160 yourls_die( $message, $title, 503 ); 1161 } 1162 1163 /** 1164 * Check if a URL protocol is allowed 1165 * 1166 * Checks a URL against a list of whitelisted protocols. Protocols must be defined with 1167 * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid 1168 * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either 1169 * 1170 * @since 1.6 1171 * @see yourls_get_protocol() 1172 * 1173 * @param string $url URL to be check 1174 * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols 1175 * @return bool true if protocol allowed, false otherwise 1176 */ 1177 function yourls_is_allowed_protocol( $url, $protocols = [] ) { 1178 if ( empty( $protocols ) ) { 1179 global $yourls_allowedprotocols; 1180 $protocols = $yourls_allowedprotocols; 1181 } 1182 1183 return yourls_apply_filter( 'is_allowed_protocol', in_array( yourls_get_protocol( $url ), $protocols ), $url, $protocols ); 1184 } 1185 1186 /** 1187 * Get protocol from a URL (eg mailto:, http:// ...) 1188 * 1189 * What we liberally call a "protocol" in YOURLS is the scheme name + colon + double slashes if present of a URI. Examples: 1190 * "something://blah" -> "something://" 1191 * "something:blah" -> "something:" 1192 * "something:/blah" -> "something:" 1193 * 1194 * Unit Tests for this function are located in tests/format/urls.php 1195 * 1196 * @since 1.6 1197 * 1198 * @param string $url URL to be check 1199 * @return string Protocol, with slash slash if applicable. Empty string if no protocol 1200 */ 1201 function yourls_get_protocol( $url ) { 1202 /* 1203 http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax 1204 The scheme name consists of a sequence of characters beginning with a letter and followed by any 1205 combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are 1206 case-insensitive, the canonical form is lowercase and documents that specify schemes must do so 1207 with lowercase letters. It is followed by a colon (":"). 1208 */ 1209 preg_match( '!^[a-zA-Z][a-zA-Z0-9+.-]+:(//)?!', $url, $matches ); 1210 return (string)yourls_apply_filter( 'get_protocol', isset( $matches[0] ) ? $matches[0] : '', $url ); 1211 } 1212 1213 /** 1214 * Get relative URL (eg 'abc' from 'http://sho.rt/abc') 1215 * 1216 * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is 1217 * or return empty string if $strict is true 1218 * 1219 * @since 1.6 1220 * @param string $url URL to relativize 1221 * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string 1222 * @return string URL 1223 */ 1224 function yourls_get_relative_url( $url, $strict = true ) { 1225 $url = yourls_sanitize_url( $url ); 1226 1227 // Remove protocols to make it easier 1228 $noproto_url = str_replace( 'https:', 'http:', $url ); 1229 $noproto_site = str_replace( 'https:', 'http:', yourls_get_yourls_site() ); 1230 1231 // Trim URL from YOURLS root URL : if no modification made, URL wasn't relative 1232 $_url = str_replace( $noproto_site.'/', '', $noproto_url ); 1233 if ( $_url == $noproto_url ) { 1234 $_url = ( $strict ? '' : $url ); 1235 } 1236 return yourls_apply_filter( 'get_relative_url', $_url, $url ); 1237 } 1238 1239 /** 1240 * Marks a function as deprecated and informs that it has been used. Stolen from WP. 1241 * 1242 * There is a hook deprecated_function that will be called that can be used 1243 * to get the backtrace up to what file and function called the deprecated 1244 * function. 1245 * 1246 * The current behavior is to trigger a user error if YOURLS_DEBUG is true. 1247 * 1248 * This function is to be used in every function that is deprecated. 1249 * 1250 * @since 1.6 1251 * 1252 * @param string $function The function that was called 1253 * @param string $version The version of WordPress that deprecated the function 1254 * @param string $replacement Optional. The function that should have been called 1255 * @return void 1256 */ 1257 function yourls_deprecated_function( $function, $version, $replacement = null ) { 1258 1259 yourls_do_action( 'deprecated_function', $function, $replacement, $version ); 1260 1261 // Allow plugin to filter the output error trigger 1262 if ( yourls_get_debug_mode() && yourls_apply_filter( 'deprecated_function_trigger_error', true ) ) { 1263 if ( ! is_null( $replacement ) ) 1264 trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) ); 1265 else 1266 trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) ); 1267 } 1268 } 1269 1270 /** 1271 * Explode a URL in an array of ( 'protocol' , 'slashes if any', 'rest of the URL' ) 1272 * 1273 * Some hosts trip up when a query string contains 'http://' - see http://git.io/j1FlJg 1274 * The idea is that instead of passing the whole URL to a bookmarklet, eg index.php?u=http://blah.com, 1275 * we pass it by pieces to fool the server, eg index.php?proto=http:&slashes=//&rest=blah.com 1276 * 1277 * Known limitation: this won't work if the rest of the URL itself contains 'http://', for example 1278 * if rest = blah.com/file.php?url=http://foo.com 1279 * 1280 * Sample returns: 1281 * 1282 * with 'mailto:[email protected]?subject=hey' : 1283 * array( 'protocol' => 'mailto:', 'slashes' => '', 'rest' => '[email protected]?subject=hey' ) 1284 * 1285 * with 'http://example.com/blah.html' : 1286 * array( 'protocol' => 'http:', 'slashes' => '//', 'rest' => 'example.com/blah.html' ) 1287 * 1288 * @since 1.7 1289 * @param string $url URL to be parsed 1290 * @param array $array Optional, array of key names to be used in returned array 1291 * @return array|false false if no protocol found, array of ('protocol' , 'slashes', 'rest') otherwise 1292 */ 1293 function yourls_get_protocol_slashes_and_rest( $url, $array = [ 'protocol', 'slashes', 'rest' ] ) { 1294 $proto = yourls_get_protocol( $url ); 1295 1296 if ( !$proto or count( $array ) != 3 ) { 1297 return false; 1298 } 1299 1300 list( $null, $rest ) = explode( $proto, $url, 2 ); 1301 1302 list( $proto, $slashes ) = explode( ':', $proto ); 1303 1304 return [ 1305 $array[ 0 ] => $proto.':', 1306 $array[ 1 ] => $slashes, 1307 $array[ 2 ] => $rest 1308 ]; 1309 } 1310 1311 /** 1312 * Set URL scheme (HTTP or HTTPS) to a URL 1313 * 1314 * @since 1.7.1 1315 * @param string $url URL 1316 * @param string $scheme scheme, either 'http' or 'https' 1317 * @return string URL with chosen scheme 1318 */ 1319 function yourls_set_url_scheme( $url, $scheme = '' ) { 1320 if ( in_array( $scheme, [ 'http', 'https' ] ) ) { 1321 $url = preg_replace( '!^[a-zA-Z0-9+.-]+://!', $scheme.'://', $url ); 1322 } 1323 return $url; 1324 } 1325 1326 /** 1327 * Tell if there is a new YOURLS version 1328 * 1329 * This function checks, if needed, if there's a new version of YOURLS and, if applicable, displays 1330 * an update notice. 1331 * 1332 * @since 1.7.3 1333 * @return void 1334 */ 1335 function yourls_tell_if_new_version() { 1336 yourls_debug_log( 'Check for new version: '.( yourls_maybe_check_core_version() ? 'yes' : 'no' ) ); 1337 yourls_new_core_version_notice(YOURLS_VERSION); 1338 } 1339 1340 /** 1341 * File include sandbox 1342 * 1343 * Attempt to include a PHP file, fail with an error message if the file isn't valid PHP code. 1344 * This function does not check first if the file exists : depending on use case, you may check first. 1345 * 1346 * @since 1.9.2 1347 * @param string $file filename (full path) 1348 * @return string|bool string if error, true if success 1349 */ 1350 function yourls_include_file_sandbox($file) { 1351 try { 1352 if (is_readable( $file )) { 1353 require_once $file; 1354 yourls_debug_log("loaded $file"); 1355 return true; 1356 } 1357 } catch ( \Throwable $e ) { 1358 yourls_debug_log("could not load $file"); 1359 return sprintf("%s (%s : %s)", $e->getMessage() , $e->getFile() , $e->getLine() ); 1360 } 1361 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Feb 25 05:10:41 2026 | Cross-referenced by PHPXref 0.7.1 |