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


Generated: Fri Mar 28 05:10:25 2025 Cross-referenced by PHPXref 0.7.1