[ Index ]

PHP Cross Reference of YOURLS

title

Body

[close]

/includes/ -> functions.php (source)

   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  }


Generated: Wed Feb 25 05:10:41 2026 Cross-referenced by PHPXref 0.7.1