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


Generated: Wed Nov 5 05:10:36 2025 Cross-referenced by PHPXref 0.7.1