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


Generated: Wed Sep 28 05:10:02 2022 Cross-referenced by PHPXref 0.7.1