functions.php

Created Diff never expires
<?php
<?php
/*
/*
* YOURLS general functions
* YOURLS general functions
*
*
*/
*/


/**
/**
* Make an optimized regexp pattern from a string of characters
* Make an optimized regexp pattern from a string of characters
*
*
* @param string $string
* @param string $string
* @return string
* @return string
*/
*/
function yourls_make_regexp_pattern( $string ) {
function yourls_make_regexp_pattern( $string ) {
// Simple benchmarks show that regexp with smarter sequences (0-9, a-z, A-Z...) are not faster or slower than 0123456789 etc...
// Simple benchmarks show that regexp with smarter sequences (0-9, a-z, A-Z...) are not faster or slower than 0123456789 etc...
// add @ as an escaped character because @ is used as the regexp delimiter in yourls-loader.php
// add @ as an escaped character because @ is used as the regexp delimiter in yourls-loader.php
return preg_quote( $string, '@' );
return preg_quote( $string, '@' );
}
}


/**
/**
* Get client IP Address. Returns a DB safe string.
* Get client IP Address. Returns a DB safe string.
*
*
* @return string
* @return string
*/
*/
function yourls_get_IP() {
function yourls_get_IP() {
$ip = '';
$ip = '';


// Precedence: if set, X-Forwarded-For > HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR
// Precedence: if set, X-Forwarded-For > HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR
$headers = [ 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' ];
$headers = [ 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' ];
foreach( $headers as $header ) {
foreach( $headers as $header ) {
if ( !empty( $_SERVER[ $header ] ) ) {
if ( !empty( $_SERVER[ $header ] ) ) {
$ip = $_SERVER[ $header ];
$ip = $_SERVER[ $header ];
break;
break;
}
}
}
}


// headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one.
// headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one.
if ( strpos( $ip, ',' ) !== false )
if ( strpos( $ip, ',' ) !== false )
$ip = substr( $ip, 0, strpos( $ip, ',' ) );
$ip = substr( $ip, 0, strpos( $ip, ',' ) );


return (string)yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) );
return (string)yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) );
}
}


/**
/**
* Get next id a new link will have if no custom keyword provided
* Get next id a new link will have if no custom keyword provided
*
*
* @since 1.0
* @since 1.0
* @return int id of next link
* @return int id of next link
*/
*/
function yourls_get_next_decimal() {
function yourls_get_next_decimal() {
return (int)yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) );
return (int)yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) );
}
}


/**
/**
* Update id for next link with no custom keyword
* Update id for next link with no custom keyword
*
*
* Note: this function relies upon yourls_update_option(), which will return either true or false
* Note: this function relies upon yourls_update_option(), which will return either true or false
* depending upon if there has been an actual MySQL query updating the DB.
* depending upon if there has been an actual MySQL query updating the DB.
* In other words, this function may return false yet this would not mean it has functionally failed
* In other words, this function may return false yet this would not mean it has functionally failed
* In other words I'm not sure if we really need this function to return something :face_with_eyes_looking_up:
* In other words I'm not sure if we really need this function to return something :face_with_eyes_looking_up:
* See issue 2621 for more on this.
* See issue 2621 for more on this.
*
*
* @since 1.0
* @since 1.0
* @param integer $int id for next link
* @param integer $int id for next link
* @return bool true or false depending on if there has been an actual MySQL query. See note above.
* @return bool true or false depending on if there has been an actual MySQL query. See note above.
*/
*/
function yourls_update_next_decimal( $int = 0 ) {
function yourls_update_next_decimal( $int = 0 ) {
$int = ( $int == 0 ) ? yourls_get_next_decimal() + 1 : (int)$int ;
$int = ( $int == 0 ) ? yourls_get_next_decimal() + 1 : (int)$int ;
$update = yourls_update_option( 'next_id', $int );
$update = yourls_update_option( 'next_id', $int );
yourls_do_action( 'update_next_decimal', $int, $update );
yourls_do_action( 'update_next_decimal', $int, $update );
return $update;
return $update;
}
}


/**
/**
* Return XML output.
* Return XML output.
*
*
* @param array $array
* @param array $array
* @return string
* @return string
*/
*/
function yourls_xml_encode( $array ) {
function yourls_xml_encode( $array ) {
return (\Spatie\ArrayToXml\ArrayToXml::convert($array, '', true, 'UTF-8'));
return (\Spatie\ArrayToXml\ArrayToXml::convert($array, '', true, 'UTF-8'));
}
}


/**
/**
* Update click count on a short URL. Return 0/1 for error/success.
* Update click count on a short URL. Return 0/1 for error/success.
*
*
* @param string $keyword
* @param string $keyword
* @param false|int $clicks
* @param false|int $clicks
* @return int 0 or 1 for error/success
* @return int 0 or 1 for error/success
*/
*/
function yourls_update_clicks( $keyword, $clicks = false ) {
function yourls_update_clicks( $keyword, $clicks = false ) {
// Allow plugins to short-circuit the whole function
// Allow plugins to short-circuit the whole function
$pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks );
$pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks );
if ( false !== $pre ) {
if ( false !== $pre ) {
return $pre;
return $pre;
}
}


$keyword = yourls_sanitize_keyword( $keyword );
$keyword = yourls_sanitize_keyword( $keyword );
$table = YOURLS_DB_TABLE_URL;
$table = YOURLS_DB_TABLE_URL;
if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 ) {
if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 ) {
$update = "UPDATE `$table` SET `clicks` = :clicks WHERE `keyword` = :keyword";
$update = "UPDATE `$table` SET `clicks` = :clicks WHERE `keyword` = :keyword";
$values = [ 'clicks' => $clicks, 'keyword' => $keyword ];
$values = [ 'clicks' => $clicks, 'keyword' => $keyword ];
} else {
} else {
$update = "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = :keyword";
$update = "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = :keyword";
$values = [ 'keyword' => $keyword ];
$values = [ 'keyword' => $keyword ];
}
}


// Try and update click count. An error probably means a concurrency problem : just skip the update
// Try and update click count. An error probably means a concurrency problem : just skip the update
try {
try {
$result = yourls_get_db()->fetchAffected($update, $values);
$result = yourls_get_db()->fetchAffected($update, $values);
} catch (Exception $e) {
} catch (Exception $e) {
$result = 0;
$result = 0;
}
}


yourls_do_action( 'update_clicks', $keyword, $result, $clicks );
yourls_do_action( 'update_clicks', $keyword, $result, $clicks );


return $result;
return $result;
}
}




/**
/**
* Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
* Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
*
*
* @param string $filter 'bottom', 'last', 'rand' or 'top'
* @param string $filter 'bottom', 'last', 'rand' or 'top'
* @param int $limit Number of links to return
* @param int $limit Number of links to return
* @param int $start Offset to start from
* @param int $start Offset to start from
* @return array Array of links
* @return array Array of links
*/
*/
function yourls_get_stats($filter = 'top', $limit = 10, $start = 0) {
function yourls_get_stats($filter = 'top', $limit = 10, $start = 0) {
switch( $filter ) {
switch( $filter ) {
case 'bottom':
case 'bottom':
$sort_by = '`clicks`';
$sort_by = '`clicks`';
$sort_order = 'asc';
$sort_order = 'asc';
break;
break;
case 'last':
case 'last':
$sort_by = '`timestamp`';
$sort_by = '`timestamp`';
$sort_order = 'desc';
$sort_order = 'desc';
break;
break;
case 'rand':
case 'rand':
case 'random':
case 'random':
$sort_by = 'RAND()';
$sort_by = 'RAND()';
$sort_order = '';
$sort_order = '';
break;
break;
case 'top':
case 'top':
default:
default:
$sort_by = '`clicks`';
$sort_by = '`clicks`';
$sort_order = 'desc';
$sort_order = 'desc';
break;
break;
}
}


// Fetch links
// Fetch links
$limit = intval( $limit );
$limit = intval( $limit );
$start = intval( $start );
$start = intval( $start );
if ( $limit > 0 ) {
if ( $limit > 0 ) {


$table_url = YOURLS_DB_TABLE_URL;
$table_url = YOURLS_DB_TABLE_URL;
$results = yourls_get_db()->fetchObjects( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY $sort_by $sort_order LIMIT $start, $limit;" );
$results = yourls_get_db()->fetchObjects( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY $sort_by $sort_order LIMIT $start, $limit;" );


$return = [];
$return = [];
$i = 1;
$i = 1;


foreach ( (array)$results as $res ) {
foreach ( (array)$results as $res ) {
$return['links']['link_'.$i++] = [
$return['links']['link_'.$i++] = [
'shorturl' => yourls_link($res->keyword),
'shorturl' => yourls_link($res->keyword),
'url' => $res->url,
'url' => $res->url,
'title' => $res->title,
'title' => $res->title,
'timestamp'=> $res->timestamp,
'timestamp'=> $res->timestamp,
'ip' => $res->ip,
'ip' => $res->ip,
'clicks' => $res->clicks,
'clicks' => $res->clicks,
];
];
}
}
}
}


$return['stats'] = yourls_get_db_stats();
$return['stats'] = yourls_get_db_stats();


$return['statusCode'] = 200;
$return['statusCode'] = 200;


return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start );
return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start );
}
}


/**
/**
* Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array
* Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array
*
*
* The $where parameter will contain additional SQL arguments:
* The $where parameter will contain additional SQL arguments:
* $where['sql'] will concatenate SQL clauses: $where['sql'] = ' AND something = :value AND otherthing < :othervalue';
* $where['sql'] will concatenate SQL clauses: $where['sql'] = ' AND something = :value AND otherthing < :othervalue';
* $where['binds'] will hold the (name => value) placeholder pairs: $where['binds'] = array('value' => $value, 'othervalue' => $value2)
* $where['binds'] will hold the (name => value) placeholder pairs: $where['binds'] = array('value' => $value, 'othervalue' => $value2)
*
*
* @param array $where See comment above
* @param array $where See comment above
* @return array
* @return array
*/
*/
function yourls_get_db_stats( $where = [ 'sql' => '', 'binds' => [] ] ) {
function yourls_get_db_stats( $where = [ 'sql' => '', 'binds' => [] ] ) {
$table_url = YOURLS_DB_TABLE_URL;
$table_url = YOURLS_DB_TABLE_URL;


$totals = yourls_get_db()->fetchObject( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 " . $where['sql'] , $where['binds'] );
$totals = yourls_get_db()->fetchObject( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 " . $where['sql'] , $where['binds'] );
$return = [ 'total_links' => $totals->count, 'total_clicks' => $totals->sum ];
$return = [ 'total_links' => $totals->count, 'total_clicks' => $totals->sum ];


return yourls_apply_filter( 'get_db_stats', $return, $where );
return yourls_apply_filter( 'get_db_stats', $return, $where );
}
}


/**
/**
* Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.
* Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.
*
*
* @return string
* @return string
*/
*/
function yourls_get_user_agent() {
function yourls_get_user_agent() {
$ua = '-';
$ua = '-';


if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
$ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));
$ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));
$ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );
$ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );
}
}


return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 255 ) );
return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 255 ) );
}
}


/**
/**
* Returns the sanitized referrer submitted by the browser.
* Returns the sanitized referrer submitted by the browser.
*
*
* @return string HTTP Referrer or 'direct' if no referrer was provided
* @return string HTTP Referrer or 'direct' if no referrer was provided
*/
*/
function yourls_get_referrer() {
function yourls_get_referrer( $sanitized_keyword ) {
$referrer = isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url_safe( $_SERVER['HTTP_REFERER'] ) : 'direct';
$yourls_site = yourls_get_yourls_site();
$combined_url = $yourls_site . '/' . $sanitized_keyword;

$referrer = isset( $_SERVER['HTTP_REFERER'] ) ? yourls_sanitize_url_safe( $_SERVER['HTTP_REFERER'] ) : $combined_url ;


return yourls_apply_filter( 'get_referrer', substr( $referrer, 0, 200 ) );
return yourls_apply_filter( 'get_referrer', substr( $referrer, 0, 200 ) );
}
}


/**
/**
* Redirect to another page
* Redirect to another page
*
*
* YOURLS redirection, either to internal or external URLs. If headers have not been sent, redirection
* YOURLS redirection, either to internal or external URLs. If headers have not been sent, redirection
* is achieved with PHP's header(). If headers have been sent already and we're not in a command line
* is achieved with PHP's header(). If headers have been sent already and we're not in a command line
* client, redirection occurs with Javascript.
* client, redirection occurs with Javascript.
*
*
* Note: yourls_redirect() does not exit automatically, and should almost always be followed by a call to exit()
* Note: yourls_redirect() does not exit automatically, and should almost always be followed by a call to exit()
* to prevent the script from continuing.
* to prevent the script from continuing.
*
*
* @since 1.4
* @since 1.4
* @param string $location URL to redirect to
* @param string $location URL to redirect to
* @param int $code HTTP status code to send
* @param int $code HTTP status code to send
* @return int 1 for header redirection, 2 for js redirection, 3 otherwise (CLI)
* @return int 1 for header redirection, 2 for js redirection, 3 otherwise (CLI)
*/
*/
function yourls_redirect( $location, $code = 301 ) {
function yourls_redirect( $location, $code = 301 ) {
yourls_do_action( 'pre_redirect', $location, $code );
yourls_do_action( 'pre_redirect', $location, $code );
$location = yourls_apply_filter( 'redirect_location', $location, $code );
$location = yourls_apply_filter( 'redirect_location', $location, $code );
$code = yourls_apply_filter( 'redirect_code', $code, $location );
$code = yourls_apply_filter( 'redirect_code', $code, $location );


// Redirect, either properly if possible, or via Javascript otherwise
// Redirect, either properly if possible, or via Javascript otherwise
/*
if( !headers_sent() ) {
if( !headers_sent() ) {
yourls_status_header( $code );
yourls_status_header( $code );
header( "Location: $location" );
header( "Location: $location" );
return 1;
return 1;
}
}
*/
// By commenting out the section above hopefully it forces a Javascript redirection which allows for logging more info


// Headers sent : redirect with JS if not in CLI
// Headers sent : redirect with JS if not in CLI
if( php_sapi_name() !== 'cli') {
if( php_sapi_name() !== 'cli') {
yourls_redirect_javascript( $location );
yourls_redirect_javascript( $location );
return 2;
return 2;
}
}


// We're in CLI
// We're in CLI
return 3;
return 3;
}
}


/**
/**
* Redirect to an existing short URL
* Redirect to an existing short URL
*
*
* Redirect client to an existing short URL (no check performed) and execute misc tasks: update
* Redirect client to an existing short URL (no check performed) and execute misc tasks: update
* clicks for short URL, update logs, and send an X-Robots-Tag header to control indexing of a page.
* clicks for short URL, update logs, and send an X-Robots-Tag header to control indexing of a page.
*
*
* @since 1.7.3
* @since 1.7.3
* @param string $url
* @param string $url
* @param string $keyword
* @param string $keyword
* @return void
* @return void
*/
*/
function yourls_redirect_shorturl($url, $keyword) {
function yourls_redirect_shorturl($url, $keyword) {
yourls_do_action( 'redirect_shorturl', $url, $keyword );
yourls_do_action( 'redirect_shorturl', $url, $keyword );


// Attempt to update click count in main table
// Attempt to update click count in main table
yourls_update_clicks( $keyword );
yourls_update_clicks( $keyword );


// Update detailed log for stats
// Update detailed log for stats
yourls_log_redirect( $keyword );
yourls_log_redirect( $keyword );


// Send an X-Robots-Tag header
// Send an X-Robots-Tag header
yourls_robots_tag_header();
yourls_robots_tag_header();


yourls_redirect( $url, 301 );
yourls_redirect( $url, 301 );
}
}


/**
/**
* Send an X-Robots-Tag header. See #3486
* Send an X-Robots-Tag header. See #3486
*
*
* @since 1.9.2
* @since 1.9.2
* @return void
* @return void
*/
*/
function yourls_robots_tag_header() {
function yourls_robots_tag_header() {
// Allow plugins to short-circuit the whole function
// Allow plugins to short-circuit the whole function
$pre = yourls_apply_filter( 'shunt_robots_tag_header', false );
$pre = yourls_apply_filter( 'shunt_robots_tag_header', false );
if ( false !== $pre ) {
if ( false !== $pre ) {
return $pre;
return $pre;
}
}


// By default, we're sending a 'noindex' header
// By default, we're sending a 'noindex' header
$tag = yourls_apply_filter( 'robots_tag_header', 'noindex' );
$tag = yourls_apply_filter( 'robots_tag_header', 'noindex' );
$replace = yourls_apply_filter( 'robots_tag_header_replace', true );
$replace = yourls_apply_filter( 'robots_tag_header_replace', true );
if ( !headers_sent() ) {
if ( !headers_sent() ) {
header( "X-Robots-Tag: $tag", $replace );
header( "X-Robots-Tag: $tag", $replace );
}
}
}
}




/**
/**
* Send headers to explicitly tell browser not to cache content or redirection
* Send headers to explicitly tell browser not to cache content or redirection
*
*
* @since 1.7.10
* @since 1.7.10
* @return void
* @return void
*/
*/
function yourls_no_cache_headers() {
function yourls_no_cache_headers() {
if( !headers_sent() ) {
if( !headers_sent() ) {
header( 'Expires: Thu, 23 Mar 1972 07:00:00 GMT' );
header( 'Expires: Thu, 23 Mar 1972 07:00:00 GMT' );
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
header( 'Pragma: no-cache' );
header( 'Pragma: no-cache' );
}
}
}
}


/**
/**
* Send header to prevent display within a frame from another site (avoid clickjacking)
* Send header to prevent display within a frame from another site (avoid clickjacking)
*
*
* This header makes it impossible for an external site to display YOURLS admin within a frame,
* This header makes it impossible for an external site to display YOURLS admin within a frame,
* which allows for clickjacking.
* which allows for clickjacking.
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
* See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
* This said, the whole function is shuntable : legit uses of iframes should be still possible.
* This said, the whole function is shuntable : legit uses of iframes should be still possible.
*
*
* @since 1.8.1
* @since 1.8.1
* @return void|mixed
* @return void|mixed
*/
*/
function yourls_no_frame_header() {
function yourls_no_frame_header() {
// Allow plugins to short-circuit the whole function
// Allow plugins to short-circuit the whole function
$pre = yourls_apply_filter( 'shunt_no_frame_header', false );
$pre = yourls_apply_filter( 'shunt_no_frame_header', false );
if ( false !== $pre ) {
if ( false !== $pre ) {
return $pre;
return $pre;
}
}


if( !headers_sent() ) {
if( !headers_sent() ) {
header( 'X-Frame-Options: SAMEORIGIN' );
header( 'X-Frame-Options: SAMEORIGIN' );
}
}
}
}


/**
/**
* Send a filterable content type header
* Send a filterable content type header
*
*
* @since 1.7
* @since 1.7
* @param string $type content type ('text/html', 'application/json', ...)
* @param string $type content type ('text/html', 'application/json', ...)
* @return bool whether header was sent
* @return bool whether header was sent
*/
*/
function yourls_content_type_header( $type ) {
function yourls_content_type_header( $type ) {
yourls_do_action( 'content_type_header', $type );
yourls_do_action( 'content_type_header', $type );
if( !headers_sent() ) {
if( !headers_sent() ) {
$charset = yourls_apply_filter( 'content_type_header_charset', 'utf-8' );
$charset = yourls_apply_filter( 'content_type_header_charset', 'utf-8' );
header( "Content-Type: $type; charset=$charset" );
header( "Content-Type: $type; charset=$charset" );
return true;
return true;
}
}
return false;
return false;
}
}


/**
/**
* Set HTTP status header
* Set HTTP status header
*
*
* @since 1.4
* @since 1.4
* @param int $code status header code
* @param int $code status header code
* @return bool whether header was sent
* @return bool whether header was sent
*/
*/
function yourls_status_header( $code = 200 ) {
function yourls_status_header( $code = 200 ) {
yourls_do_action( 'status_header', $code );
yourls_do_action( 'status_header', $code );


if( headers_sent() )
if( headers_sent() )
return false;
return false;


$protocol = $_SERVER['SERVER_PROTOCOL'];
$protocol = $_SERVER['SERVER_PROTOCOL'];
if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
$protocol = 'HTTP/1.0';
$protocol = 'HTTP/1.0';


$code = intval( $code );
$code = intval( $code );
$desc = yourls_get_HTTP_status( $code );
$desc = yourls_get_HTTP_status( $code );


@header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups
@header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups


return true;
return true;
}
}


/**
/**
* Redirect to another page using Javascript.
* Redirect to another page using Javascript.
* Set optional (bool)$dontwait to false to force manual redirection (make sure a message has been read by user)
* Set optional (bool)$dontwait to false to force manual redirection (make sure a message has been read by user)
*
*
* @param string $location
* @param string $location
* @param bool $dontwait
* @param bool $dontwait
* @return void
* @return void
*/
*/
function yourls_redirect_javascript( $location, $dontwait = true ) {
function yourls_redirect_javascript( $location, $dontwait = true ) {
yourls_do_action( 'pre_redirect_javascript', $location, $dontwait );
yourls_do_action( 'pre_redirect_javascript', $location, $dontwait );
$location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait );
$location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait );
if ( $dontwait ) {
if ( $dontwait ) {
$message = yourls_s( 'if you are not redirected after 10 seconds, please <a href="%s">click here</a>', $location );
$message = yourls_s( 'If you are not redirected after 10 seconds, please <a href="%s">click here</a>.', $location );
echo <<<REDIR
echo <<<REDIR
<script type="text/javascript">
<script type="text/javascript">
window.location="$location";
window.location="$location";
</script>
</script>
<small>($message)</small>
<small>$message</small>
REDIR;
REDIR;
}
} else {
else {
echo '<p>'.yourls_s( 'Please <a href="%s">click here</a>.', $location ).'</p>';
echo '<p>'.yourls_s( 'Please <a href="%s">click here</a>', $location ).'</p>';
}
}
yourls_do_action( 'post_redirect_javascript', $location );
yourls_do_action( 'post_redirect_javascript', $location );
}
}


/**
/**
* Return an HTTP status code
* Return an HTTP status code
*
*
* @param int $code
* @param int $code
* @return string
* @return string
*/
*/
function yourls_get_HTTP_status( $code ) {
function yourls_get_HTTP_status( $code ) {
$code = intval( $code );
$code = intval( $code );
$headers_desc = [
$headers_desc = [
100 => 'Continue',
100 => 'Continue',
101 => 'Switching Protocols',
101 => 'Switching Protocols',
102 => 'Processing',
102 => 'Processing',


200 => 'OK',
200 => 'OK',
201 => 'Created',
201 => 'Created',
202 => 'Accepted',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
203 => 'Non-Authoritative Information',
204 => 'No Content',
204 => 'No Content',
205 => 'Reset Content',
205 => 'Reset Content',
206 => 'Partial Content',
206 => 'Partial Content',
207 => 'Multi-Status',
207 => 'Multi-Status',
226 => 'IM Used',
226 => 'IM Used',


300 => 'Multiple Choices',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
301 => 'Moved Permanently',
302 => 'Found',
302 => 'Found',
303 => 'See Other',
303 => 'See Other',
304 => 'Not Modified',
304 => 'Not Modified',
305 => 'Use Proxy',
305 => 'Use Proxy',
306 => 'Reserved',
306 => 'Reserved',
307 => 'Temporary Redirect',
307 => 'Temporary Redirect',


400 => 'Bad Request',
400 => 'Bad Request',
401 => 'Unauthorized',
401 => 'Unauthorized',
402 => 'Payment Required',
402 => 'Payment Required',
403 => 'Forbidden',
403 => 'Forbidden',
404 => 'Not Found',
404 => 'Not Found',
405 => 'Method Not Allowed',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
408 => 'Request Timeout',
409 => 'Conflict',
409 => 'Conflict',
410 => 'Gone',
410 => 'Gone',
411 => 'Length Required',
411 => 'Length Required',
412 => 'Precondition Failed',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
417 => 'Expectation Failed',
422 => 'Unprocessable Entity',
422 => 'Unprocessable Entity',
423 => 'Locked',
423 => 'Locked',
424 => 'Failed Dependency',
424 => 'Failed Dependency',
426 => 'Upgrade Required',
426 => 'Upgrade Required',


500 => 'Internal Server Error',
500 => 'Internal Server Error',
501 => 'Not Implemented',
501 => 'Not Implemented',
502 => 'Bad Gateway',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
507 => 'Insufficient Storage',
510 => 'Not Extended'
510 => 'Not Extended'
];
];


return $headers_desc[$code] ?? '';
return $headers_desc[$code] ?? '';
}
}


/**
/**
* Log a redirect (for stats)
* Log a redirect (for stats)
*
*
* This function does not check for the existence of a valid keyword, in order to save a query. Make sure the keyword
* This function does not check for the existence of a valid keyword, in order to save a query. Make sure the keyword
* exists before calling it.
* exists before calling it.
*
*
* @since 1.4
* @since 1.4
* @param string $keyword short URL keyword
* @param string $keyword short URL keyword
* @return mixed Result of the INSERT query (1 on success)
* @return mixed Result of the INSERT query (1 on success)
*/
*/
function yourls_log_redirect( $keyword ) {
function yourls_log_redirect( $keyword ) {
// Allow plugins to short-circuit the whole function
// Allow plugins to short-circuit the whole function
$pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword );
$pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword );
if ( false !== $pre ) {
if ( false !== $pre ) {
return $pre;
return $pre;
}
}


if (!yourls_do_log_redirect()) {
if (!yourls_do_log_redirect()) {
return true;
return true;
}
}


$table = YOURLS_DB_TABLE_LOG;
$table = YOURLS_DB_TABLE_LOG;
$ip = yourls_get_IP();
$ip = yourls_get_IP();
$sanitized_keyword = yourls_sanitize_keyword($keyword);
echo <<<EOD
<script>
// Orientation
var orientation = window.orientation;
//console.log("Orientation: ", orientation);
// Language
var language = navigator.language;
//console.log("Browser Language: ", language);
// Touch Screen
var isTouchScreen =
"ontouchstart" in window || navigator.maxTouchPoints > 0;
//console.log("Touch Screen: ", (isTouchScreen ? "Yes" : "No"));
// Screen Size
var width = screen.width;
var height = screen.height;
//console.log("Screen Size: ", width, "x", height);
// Incognito Mode
var isIncognito = false;
try {
isIncognito = window.sessionStorage.getItem("test");
window.sessionStorage.setItem("test", "test");
} catch (e) {
isIncognito = true;
}
//console.log("Incognito Mode: ", (isIncognito ? "Yes" : "No"));
// Ad Blocker
var adBlockEnabled = false;
async function detectAdBlock() {
const googleAdUrl = "https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js";
try {
await fetch(new Request(googleAdUrl)).catch(_ => adBlockEnabled = true);
} catch (e) {
adBlockEnabled = true;
} finally {
//console.log("AdBlock Enabled: ", (adBlockEnabled ? "Yes" : "No"));
}
}
detectAdBlock();
if ('getBattery' in navigator) {
// The getBattery() API is supported
navigator.getBattery().then((battery) => {
var batteryLevel = battery.level * 100;
//console.log("Battery Level: ", batteryLevel, "%");
// Construct deviceData string inside the battery promise
var deviceData =
"Ori:" + orientation +
" Lang:" + language +
" Touch:" + (isTouchScreen ? "Yes" : "No") +
" Bat:" + batteryLevel +
"% Incog:" + (isIncognito ? "Yes" : "No") +
" AdBlock:" + (adBlockEnabled ? "Yes" : "No") +
" Size:" + width + "x" + height;
//console.log(deviceData);
// Set a cookie with the device information to be fetched later
document.cookie = "deviceinfo=" + encodeURIComponent(deviceData) + "; path=/; SameSite=Lax; Secure";
});
} else {
// The getBattery() API is not supported
// Construct the deviceData string
var deviceData =
"Ori:" + orientation +
" Lang:" + language +
" Touch:" + (isTouchScreen ? "Yes" : "No") +
" Bat:undefined" +
" Incog:" + (isIncognito ? "Yes" : "No") +
" AdBlock:" + (adBlockEnabled ? "Yes" : "No") +
" Size:" + width + "x" + height;
//console.log(deviceData);
// Set a cookie with the device information to be fetched later
document.cookie = "deviceinfo=" + encodeURIComponent(deviceData) + "; path=/; SameSite=Lax; Secure";
}
</script>
EOD;
if (isset($_COOKIE['deviceinfo'])) {
// Retrieve the device information from the cookie
$deviceinfo = $_COOKIE['deviceinfo'];
// Unset or reset the cookie
setcookie('deviceinfo', '', time() - 3600, '/'); // expire the cookie
}
// Get the real referrer
$referrer = substr( yourls_get_referrer($sanitized_keyword), 0, 200 );
// Append the device info to it
$referrer_device = $referrer . " - " . $deviceinfo;
// Debugging: Print raw data to browser console
echo '<script>';
echo 'console.log("' . $referrer_device . '");';
echo '</script>';
$binds = [
$binds = [
'now' => date( 'Y-m-d H:i:s' ),
'now' => date( 'Y-m-d H:i:s' ),
'keyword' => yourls_sanitize_keyword($keyword),
'keyword' => $sanitized_keyword,
'referrer' => substr( yourls_get_referrer(), 0, 200 ),
'referrer' => $referrer_device,
'ua' => substr(yourls_get_user_agent(), 0, 255),
'ua' => substr(yourls_get_user_agent(), 0, 255),
'ip' => $ip,
'ip' => $ip,
'location' => yourls_geo_ip_to_countrycode($ip),
'location' => yourls_geo_ip_to_countrycode($ip),
];
];


// Try and log. An error probably means a concurrency problem : just skip the logging
// Try and log. An error probably means a concurrency problem : just skip the logging
try {
try {
$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 );
$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 );
} catch (Exception $e) {
} catch (Exception $e) {
$result = 0;
$result = 0;
}
}


return $result;
return $result;
}
}


/**
/**
* Check if we want to not log redirects (for stats)
* Check if we want to not log redirects (for stats)
*
*
* @return bool
* @return bool
*/
*/
function yourls_do_log_redirect() {
function yourls_do_log_redirect() {
return ( !defined( 'YOURLS_NOSTATS' ) || YOURLS_NOSTATS != true );
return ( !defined( 'YOURLS_NOSTATS' ) || YOURLS_NOSTATS != true );
}
}


/**
/**
* Check if an upgrade is needed
* Check if an upgrade is needed
*
*
* @return bool
* @return bool
*/
*/
function yourls_upgrade_is_needed() {
function yourls_upgrade_is_needed() {
// check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
// check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();
list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();
if ( $currentsql < YOURLS_DB_VERSION ) {
if ( $currentsql < YOURLS_DB_VERSION ) {
return true;
return true;
}
}


// Check if YOURLS_VERSION exist && match value stored in YOURLS_DB_TABLE_OPTIONS, update DB if required
// Check if YOURLS_VERSION exist && match value stored in YOURLS_DB_TABLE_OPTIONS, update DB if required
if ( $currentver < YOURLS_VERSION ) {
if ( $currentver < YOURLS_VERSION ) {
yourls_update_option( 'version', YOURLS_VERSION );
yourls_update_option( 'version', YOURLS_VERSION );
}
}


return false;
return false;
}
}


/**
/**
* Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
* Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
*
*
* @return array
* @return array
*/
*/
function yourls_get_current_version_from_sql() {
function yourls_get_current_version_from_sql() {
$currentver = yourls_get_option( 'version' );
$currentver = yourls_get_option( 'version' );
$currentsql = yourls_get_option( 'db_version' );
$currentsql = yourls_get_option( 'db_version' );


// Values if version is 1.3
// Values if version is 1.3
if ( !$currentver ) {
if ( !$currentver ) {
$currentver = '1.3';
$currentver = '1.3';
}
}
if ( !$currentsql ) {
if ( !$currentsql ) {
$currentsql = '100';
$currentsql = '100';
}
}


return [ $currentver, $currentsql ];
return [ $currentver, $currentsql ];
}
}


/**
/**
* Determine if the current page is private
* Determine if the current page is private
*
*
* @return bool
* @return bool
*/
*/
function yourls_is_private() {
function yourls_is_private() {
$private = defined( 'YOURLS_PRIVATE' ) && YOURLS_PRIVATE;
$private = defined( 'YOURLS_PRIVATE' ) && YOURLS_PRIVATE;


if ( $private ) {
if ( $private ) {


// Allow overruling for particular pages:
// Allow overruling for particular pages:


// API
// API
if ( yourls_is_API() && defined( 'YOURLS_PRIVATE_API' ) ) {
if ( yourls_is_API() && defined( 'YOURLS_PRIVATE_API' ) ) {
$private = YOURLS_PRIVATE_API;
$private = YOURLS_PRIVATE_API;
}
}
// Stat pages
// Stat pages
elseif ( yourls_is_infos() && defined( 'YOURLS_PRIVATE_INFOS' ) ) {
elseif ( yourls_is_infos() && defined( 'YOURLS_PRIVATE_INFOS' ) ) {
$private = YOURLS_PRIVATE_INFOS;
$private = YOURLS_PRIVATE_INFOS;
}
}
// Others future cases ?
// Others future cases ?
}
}


return yourls_apply_filter( 'is_private', $private );
return yourls_apply_filter( 'is_private', $private );
}
}


/**
/**
* Allow several short URLs for the same long URL ?
* Allow several short URLs for the same long URL ?
*
*
* @return bool
* @return bool
*/
*/
function yourls_allow_duplicate_longurls() {
function yourls_allow_duplicate_longurls() {
// special treatment if API to check for WordPress plugin requests
// special treatment if API to check for WordPress plugin requests
if ( yourls_is_API() && isset( $_REQUEST[ 'source' ] ) && $_REQUEST[ 'source' ] == 'plugin' ) {
if ( yourls_is_API() && isset( $_REQUEST[ 'source' ] ) && $_REQUEST[ 'source' ] == 'plugin' ) {
return false;
return false;
}
}


return yourls_apply_filter('allow_duplicate_longurls', defined('YOURLS_UNIQUE_URLS') && !YOURLS_UNIQUE_URLS);
return yourls_apply_filter('allow_duplicate_longurls', defined('YOURLS_UNIQUE_URLS') && !YOURLS_UNIQUE_URLS);
}
}


/**
/**
* Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
* Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
*
*
* @param string $ip
* @param string $ip
* @return bool|mixed|string
* @return bool|mixed|string
*/
*/
function yourls_check_IP_flood( $ip = '' ) {
function yourls_check_IP_flood( $ip = '' ) {


// Allow plugins to short-circuit the whole function
// Allow plugins to short-circuit the whole function
$pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip );
$pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip );
if ( false !== $pre )
if ( false !== $pre )
return $pre;
return $pre;


yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here


// Raise white flag if installing or if no flood delay defined
// Raise white flag if installing or if no flood delay defined
if(
if(
( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
!defined('YOURLS_FLOOD_DELAY_SECONDS') ||
!defined('YOURLS_FLOOD_DELAY_SECONDS') ||
yourls_is_installing()
yourls_is_installing()
)
)
return true;
return true;


// Don't throttle logged in users
// Don't throttle logged in users
if( yourls_is_private() ) {
if( yourls_is_private() ) {
if( yourls_is_valid_user() === true )
if( yourls_is_valid_user() === true )
return true;
return true;
}
}


// Don't throttle whitelist IPs
// Don't throttle whitelist IPs
if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) {
if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) {
$whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
$whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
foreach( (array)$whitelist_ips as $whitelist_ip ) {
foreach( (array)$whitelist_ips as $whitelist_ip ) {
$whitelist_ip = trim( $whitelist_ip );
$whitelist_ip = trim( $whitelist_ip );
if ( $whitelist_ip == $ip )
if ( $whitelist_ip == $ip )
return true;
return true;
}
}
}
}


$ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
$ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );


yourls_do_action( 'check_ip_flood', $ip );
yourls_do_action( 'check_ip_flood', $ip );


$table = YOURLS_DB_TABLE_URL;
$table = YOURLS_DB_TABLE_URL;
$lasttime = yourls_get_db()->fetchValue( "SELECT `timestamp` FROM $table WHERE `ip` = :ip ORDER BY `timestamp` DESC LIMIT 1", [ 'ip' => $ip ] );
$lasttime = yourls_get_db()->fetchValue( "SELECT `timestamp` FROM $table WHERE `ip` = :ip ORDER BY `timestamp` DESC LIMIT 1", [ 'ip' => $ip ] );
if( $lasttime ) {
if( $lasttime ) {
$now = date( 'U' );
$now = date( 'U' );
$then = date( 'U', strtotime( $lasttime ) );
$then = date( 'U', strtotime( $lasttime ) );
if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
// Flood!
// Flood!
yourls_do_action( 'ip_flood', $ip, $now - $then );
yourls_do_action( 'ip_flood', $ip, $now - $then );
yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Too Many Requests' ), 429 );
yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Too Many Requests' ), 429 );
}
}
}
}


return true;
return true;
}
}


/**
/**
* Check if YOURLS is installing
* Check if YOURLS is installing
*
*
* @since 1.6
* @since 1.6
* @return bool
* @return bool
*/
*/
function yourls_is_installing() {
function yourls_is_installing() {
return (bool)yourls_apply_filter( 'is_installing', defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING );
return (bool)yourls_apply_filter( 'is_installing', defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING );
}
}


/**
/**
* Check if YOURLS is upgrading
* Check if YOURLS is upgrading
*
*
* @since 1.6
* @since 1.6
* @return bool
* @return bool
*/
*/
function yourls_is_upgrading() {
function yourls_is_upgrading() {
return (bool)yourls_apply_filter( 'is_upgrading', defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING );
return (bool)yourls_apply_filter( 'is_upgrading', defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING );
}
}


/**
/**
* Check if YOURLS is installed
* Check if YOURLS is installed
*
*
* Checks property $ydb->installed that is created by yourls_get_all_options()
* Checks property $ydb->installed that is created by yourls_get_all_options()
*
*
* See inline comment for updating from 1.3 or prior.
* See inline comment for updating from 1.3 or prior.
*
*
* @return bool
* @return bool
*/
*/
function yourls_is_installed() {
function yourls_is_installed() {
return (bool)yourls_apply_filter( 'is_installed', yourls_get_db()->is_installed() );
return (bool)yourls_apply_filter( 'is_installed', yourls_get_db()->is_installed() );
}
}


/**
/**
* Set installed state
* Set installed s
*
* @since 1.7.3
* @param bool $bool whether YOURLS is installed or not
* @return void
*/
function yourls_set_installed( $bool ) {
yourls_get_db()->set_installed( $bool );
}

/**
* Generate random string of (int)$length length and type $type (see function for details)
*
* @param int $length
* @param int $type
* @param string $charlist
* @return mixed|string
*/
function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
$length = intval( $length );

// define possible characters
switch ( $type ) {

// no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
case '1':
$possible = "23456789bcdfghjkmnpqrstvwxyz";
break;

// Same, with lower + upper
case '2':
$possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
break;

// all letters, lowercase
case '3':
$possible = "abcdefghijklmnopqrstuvwxyz";
break;

// all letters, lowercase + uppercase
case '4':
$possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
break;

// all digits & letters lowercase
case '5':
$possible = "0123456789abcdefghijklmnopqrstuvwxyz";
break;

// all digits & letters lowercase + uppercase
case '6':
$possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
break;

// custom char list, or comply to charset as defined in config
default:
case '0':
$possible = $charlist ? $charlist : yourls_get_shorturl_charset();
break;
}

$str = substr( str_shuffle( $possible ), 0, $length );
return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
}

/**
* Check if we're in API mode.
*
* @return bool
*/
function yourls_is_API() {
return (bool)yourls_apply_filter( 'is_API', defined( 'YOURLS_API' ) && YOURLS_API );
}

/**
* Check if we're in Ajax mode.
*
* @return bool
*/
function yourls_is_Ajax() {
return (bool)yourls_apply_filter( 'is_Ajax', defined( 'YOURLS_AJAX' ) && YOURLS_AJAX );
}

/**
* Check if we're in GO mode (yourls-go.php).
*
* @return bool
*/
function yourls_is_GO() {
return (bool)yourls_apply_filter( 'is_GO', defined( 'YOURLS_GO' ) && YOURLS_GO );
}

/**
* Check if we're displaying stats infos (yourls-infos.php). Returns bool
*
* @return bool
*/
function yourls_is_infos() {
return (bool)yourls_apply_filter( 'is_infos', defined( 'YOURLS_INFOS' ) && YOURLS_INFOS );
}

/**
* Check if we're in the admin area. Returns bool. Does not relate with user rights.
*
* @return bool
*/
function yourls_is_admin() {
return (bool)yourls_apply_filter( 'is_admin', defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN );
}

/**
* Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
*
* @return bool
*/
function yourls_is_windows() {
return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
}

/**
* Check if SSL is required.
*
* @return bool
*/
function yourls_needs_ssl() {
return (bool)yourls_apply_filter( 'needs_ssl', defined( 'YOURLS_ADMIN_SSL' ) && YOURLS_ADMIN_SSL );
}

/**
* Check if SSL is used. Stolen from WP.
*
* @return bool
*/
function yourls_is_ssl() {
$is_ssl = false;
if ( isset( $_SERVER[ 'HTTPS' ] ) ) {
if ( 'on' == strtolower( $_SERVER[ 'HTTPS' ] ) ) {
$is_ssl = true;
}
if ( '1' == $_SERVER[ 'HTTPS' ] ) {
$is_ssl = true;
}
}
elseif ( isset( $_SERVER[ 'HTTP_X_FORWARDED_PROTO' ] ) ) {
if ( 'https' == strtolower( $_SERVER[ 'HTTP_X_FORWARDED_PROTO' ] ) ) {
$is_ssl = true;
}
}
elseif ( isset( $_SERVER[ 'SERVER_PORT' ] ) && ( '443' == $_SERVER[ 'SERVER_PORT' ] ) ) {
$is_ssl = true;
}
return (bool)yourls_apply_filter( 'is_ssl', $is_ssl );
}

/**
* Get a remote page title
*
* This function returns a string: either the page title as defined in HTML, or the URL if not found
* The function tries to convert funky characters found in titles to UTF8, from the detected cha