diff --git a/includes/class-mysql.php b/includes/class-mysql.php index d2ca295..c25c183 100644 --- a/includes/class-mysql.php +++ b/includes/class-mysql.php @@ -4,6 +4,7 @@ * Connect to DB * * @since 1.0 + * @return \YOURLS\Database\YDB */ function yourls_db_connect() { global $ydb; @@ -101,6 +102,7 @@ function yourls_get_db() { * * @since 1.7.10 * @param mixed $db Either a \YOURLS\Database\YDB instance, or anything. If null, the function will unset $ydb + * @return void */ function yourls_set_db($db) { global $ydb; diff --git a/includes/functions-api.php b/includes/functions-api.php index baf3cb1..45395ac 100644 --- a/includes/functions-api.php +++ b/includes/functions-api.php @@ -163,8 +163,12 @@ function yourls_api_output( $mode, $output, $send_headers = true, $echo = true ) /** * Return array for API stat requests * + * @param string $filter either "top", "bottom" , "rand" or "last" + * @param int $limit maximum number of links to return + * @param int $start offset + * @return array */ -function yourls_api_stats( $filter = 'top', $limit = 10, $start = 0 ) { +function yourls_api_stats($filter = 'top', $limit = 10, $start = 0 ) { $return = yourls_get_stats( $filter, $limit, $start ); $return['simple'] = 'Need either XML or JSON format for stats'; $return['message'] = 'success'; @@ -174,6 +178,7 @@ function yourls_api_stats( $filter = 'top', $limit = 10, $start = 0 ) { /** * Return array for counts of shorturls and clicks * + * @return array */ function yourls_api_db_stats() { $return = array( @@ -189,6 +194,8 @@ function yourls_api_db_stats() { /** * Return array for API stat requests * + * @param string $shorturl Short URL to check + * @return array */ function yourls_api_url_stats( $shorturl ) { $keyword = str_replace( yourls_get_yourls_site() . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc' @@ -202,6 +209,8 @@ function yourls_api_url_stats( $shorturl ) { /** * Expand short url to long url * + * @param string $shorturl Short URL to expand + * @return array */ function yourls_api_expand( $shorturl ) { $keyword = str_replace( yourls_get_yourls_site() . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc' diff --git a/includes/functions-auth.php b/includes/functions-auth.php index 52a677a..2fd76d9 100644 --- a/includes/functions-auth.php +++ b/includes/functions-auth.php @@ -7,6 +7,7 @@ /** * Show login form if required * + * @return void */ function yourls_maybe_require_auth() { if( yourls_is_private() ) { @@ -37,7 +38,7 @@ function yourls_is_valid_user() { // The logout nonce is associated to fake user 'logout' since at this point we don't know the real user yourls_verify_nonce('admin_logout', $_REQUEST['nonce'], 'logout'); yourls_do_action( 'logout' ); - yourls_store_cookie( null ); + yourls_store_cookie( '' ); return yourls__( 'Logged out successfully' ); } @@ -124,6 +125,7 @@ function yourls_is_valid_user() { /** * Check auth against list of login=>pwd. Sets user if applicable, returns bool * + * @return bool true if login/pwd pair is valid (and sets user if applicable), false otherwise */ function yourls_check_username_password() { global $yourls_user_passwords; @@ -143,8 +145,11 @@ function yourls_check_username_password() { /** * Check a submitted password sent in plain text against stored password which can be a salted hash * + * @param string $user + * @param string $submitted_password + * @return bool */ -function yourls_check_password_hash( $user, $submitted_password ) { +function yourls_check_password_hash($user, $submitted_password ) { global $yourls_user_passwords; if( !isset( $yourls_user_passwords[ $user ] ) ) @@ -173,11 +178,15 @@ function yourls_check_password_hash( $user, $submitted_password ) { * @return true|string if overwrite was successful, an error message otherwise */ function yourls_hash_passwords_now( $config_file ) { - if( !is_readable( $config_file ) ) - return 'cannot read file'; // not sure that can actually happen... + if( !is_readable( $config_file ) ) { + yourls_debug_log( 'Cannot hash passwords: cannot read file ' . $config_file ); + return 'cannot read file'; // not sure that can actually happen... + } - if( !is_writable( $config_file ) ) + if( !is_writable( $config_file ) ) { + yourls_debug_log( 'Cannot hash passwords: cannot write file ' . $config_file ); return 'cannot write file'; + } $yourls_user_passwords = []; // Include file to read value of $yourls_user_passwords @@ -188,11 +197,16 @@ function yourls_hash_passwords_now( $config_file ) { error_reporting( $errlevel ); $configdata = file_get_contents( $config_file ); - if( $configdata == false ) - return 'could not read file'; + + if( $configdata == false ) { + yourls_debug_log('Cannot hash passwords: file_get_contents() false with ' . $config_file); + return 'could not read file'; + } $to_hash = 0; // keep track of number of passwords that need hashing foreach ( $yourls_user_passwords as $user => $password ) { + // avoid "deprecated" warning when password is null -- see test case in tests/data/auth/preg_replace_problem.php + $password ??= ''; if ( !yourls_has_phpass_password( $user ) && !yourls_has_md5_password( $user ) ) { $to_hash++; $hash = yourls_phpass_hash( $password ); @@ -211,8 +225,10 @@ function yourls_hash_passwords_now( $config_file ) { } } - if( $to_hash == 0 ) - return 0; // There was no password to encrypt + if( $to_hash == 0 ) { + yourls_debug_log('Cannot hash passwords: no password found in ' . $config_file); + return 'no password found'; + } $success = file_put_contents( $config_file, $configdata ); if ( $success === FALSE ) { @@ -320,6 +336,7 @@ function yourls_has_phpass_password( $user ) { /** * Check auth against encrypted COOKIE data. Sets user if applicable, returns bool * + * @return bool true if authenticated, false otherwise */ function yourls_check_auth_cookie() { global $yourls_user_passwords; @@ -406,6 +423,8 @@ function yourls_check_signature() { /** * Generate secret signature hash * + * @param false|string $username Username to generate signature for, or false to use current user + * @return string Signature */ function yourls_auth_signature( $username = false ) { if( !$username && defined('YOURLS_USER') ) { @@ -417,6 +436,8 @@ function yourls_auth_signature( $username = false ) { /** * Check if timestamp is not too old * + * @param int $time Timestamp to check + * @return bool True if timestamp is valid */ function yourls_check_timestamp( $time ) { $now = time(); @@ -427,9 +448,10 @@ function yourls_check_timestamp( $time ) { /** * Store new cookie. No $user will delete the cookie. * - * @param mixed $user String, user login, or null to delete cookie + * @param string $user User login, or empty string to delete cookie + * @return void */ -function yourls_store_cookie( $user = null ) { +function yourls_store_cookie( $user = '' ) { // No user will delete the cookie with a cookie time from the past if( !$user ) { @@ -463,7 +485,6 @@ function yourls_store_cookie( $user = null ) { * * @see https://github.com/GoogleChromeLabs/samesite-examples/blob/master/php.md * @see https://stackoverflow.com/a/59654832/36850 - * @see https://3v4l.org/uKEtH for compat tests * @see https://www.php.net/manual/en/function.setcookie.php * * @since 1.7.7 @@ -479,24 +500,21 @@ function yourls_store_cookie( $user = null ) { function yourls_setcookie($name, $value, $expire, $path, $domain, $secure, $httponly) { $samesite = yourls_apply_filter('setcookie_samesite', 'Lax' ); - if (PHP_VERSION_ID < 70300) { - return(setcookie($name, $value, $expire, "$path; samesite=$samesite", $domain, $secure, $httponly)); - } - else { - return(setcookie($name, $value, array( - 'expires' => $expire, - 'path' => $path, - 'domain' => $domain, - 'samesite' => $samesite, - 'secure' => $secure, - 'httponly' => $httponly, - ))); - } + return(setcookie($name, $value, array( + 'expires' => $expire, + 'path' => $path, + 'domain' => $domain, + 'samesite' => $samesite, + 'secure' => $secure, + 'httponly' => $httponly, + ))); } /** * Set user name * + * @param string $user Username + * @return void */ function yourls_set_user( $user ) { if( !defined( 'YOURLS_USER' ) ) @@ -554,7 +572,7 @@ function yourls_cookie_name() { * @return string cookie value */ function yourls_cookie_value( $user ) { - return yourls_apply_filter( 'set_cookie_value', yourls_salt( $user ), $user ); + return yourls_apply_filter( 'set_cookie_value', yourls_salt( $user ?? '' ), $user ); } /** @@ -562,6 +580,7 @@ function yourls_cookie_value( $user ) { * * Actually, this returns a float: ceil rounds up a value but is of type float, see https://www.php.net/ceil * + * @return float */ function yourls_tick() { return ceil( time() / yourls_get_nonce_life() ); @@ -598,8 +617,11 @@ function yourls_hmac_algo() { /** * Create a time limited, action limited and user limited token * + * @param string $action Action to create nonce for + * @param false|string $user Optional user string, false for current user + * @return string Nonce token */ -function yourls_create_nonce( $action, $user = false ) { +function yourls_create_nonce($action, $user = false ) { if( false === $user ) { $user = defined('YOURLS_USER') ? YOURLS_USER : '-1'; } @@ -610,10 +632,15 @@ function yourls_create_nonce( $action, $user = false ) { } /** - * Create a nonce field for inclusion into a form + * Echoes or returns a nonce field for inclusion into a form * + * @param string $action Action to create nonce for + * @param string $name Optional name of nonce field -- defaults to 'nonce' + * @param false|string $user Optional user string, false if unspecified + * @param bool $echo True to echo, false to return nonce field + * @return string Nonce field */ -function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) { +function yourls_nonce_field($action, $name = 'nonce', $user = false, $echo = true ) { $field = ''; if( $echo ) echo $field."\n"; @@ -623,8 +650,13 @@ function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = tr /** * Add a nonce to a URL. If URL omitted, adds nonce to current URL * + * @param string $action Action to create nonce for + * @param string $url Optional URL to add nonce to -- defaults to current URL + * @param string $name Optional name of nonce field -- defaults to 'nonce' + * @param false|string $user Optional user string, false if unspecified + * @return string URL with nonce added */ -function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) { +function yourls_nonce_url($action, $url = false, $name = 'nonce', $user = false ) { $nonce = yourls_create_nonce( $action, $user ); return yourls_add_query_arg( $name, $nonce, $url ); } @@ -632,11 +664,16 @@ function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false /** * Check validity of a nonce (ie time span, user and action match). * - * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined) - * if $nonce is false or unspecified, it will use $_REQUEST['nonce'] + * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined). + * If $nonce is false or unspecified, it will use $_REQUEST['nonce'] * + * @param string $action + * @param false|string $nonce Optional, string: nonce value, or false to use $_REQUEST['nonce'] + * @param false|string $user Optional, string user, false for current user + * @param string $return Optional, string: message to die with if nonce is invalid + * @return bool|void True if valid, dies otherwise */ -function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) { +function yourls_verify_nonce($action, $nonce = false, $user = false, $return = '' ) { // Get user if( false === $user ) { $user = defined('YOURLS_USER') ? YOURLS_USER : '-1'; @@ -668,7 +705,7 @@ function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = * Check if YOURLS_USER comes from environment variables * * @since 1.8.2 - * @return bool true if YOURLS_USER and YOURLS_PASSWORD are defined as environment variables + * @return bool true if YOURLS_USER and YOURLS_PASSWORD are defined as environment variables */ function yourls_is_user_from_env() { return yourls_apply_filter('is_user_from_env', getenv('YOURLS_USER') && getenv('YOURLS_PASSWORD')); diff --git a/includes/functions-debug.php b/includes/functions-debug.php index 85b4f0b..1aeee65 100644 --- a/includes/functions-debug.php +++ b/includes/functions-debug.php @@ -34,6 +34,7 @@ function yourls_get_debug_log() { /** * Get number of SQL queries performed * + * @return int */ function yourls_get_num_queries() { return yourls_apply_filter( 'get_num_queries', yourls_get_db()->get_num_queries() ); @@ -44,6 +45,7 @@ function yourls_get_num_queries() { * * @since 1.7.3 * @param bool $bool Debug on or off + * @return void */ function yourls_debug_mode( $bool ) { // log queries if true diff --git a/includes/functions-deprecated.php b/includes/functions-deprecated.php index a481f77..bbc4f3c 100644 --- a/includes/functions-deprecated.php +++ b/includes/functions-deprecated.php @@ -5,10 +5,61 @@ * * Note to devs: when deprecating a function, move it here. Then check all the places * in core that might be using it, including core plugins. + * + * Usage : yourls_deprecated_function( 'function_name', 'version', 'replacement' ); + * Output: "{function_name} is deprecated since version {version}! Use {replacement} instead." + * + * Usage : yourls_deprecated_function( 'function_name', 'version' ); + * Output: "{function_name} is deprecated since version {version} with no alternative available." + * + * @see yourls_deprecated_function() */ // @codeCoverageIgnoreStart +/** + * Return current admin page, or null if not an admin page. Was not used anywhere. + * + * @return mixed string if admin page, null if not an admin page + * @since 1.6 + * @deprecated 1.9.1 + */ +function yourls_current_admin_page() { + yourls_deprecated_function( __FUNCTION__, '1.9.1' ); + if( yourls_is_admin() ) { + $current = substr( yourls_get_request(), 6 ); + if( $current === false ) + $current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php + + return $current; + } + return null; +} + +/** + * PHP emulation of JS's encodeURI + * + * @link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI + * @deprecated 1.9.1 + * @param string $url + * @return string + */ +function yourls_encodeURI($url) { + yourls_deprecated_function( __FUNCTION__, '1.9.1', '' ); + // Decode URL all the way + $result = yourls_rawurldecode_while_encoded( $url ); + // Encode once + $result = strtr( rawurlencode( $result ), array ( + '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@', + '%26' => '&', '%3D' => '=', '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*', + '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#', + ) ); + // @TODO: + // Known limit: this will most likely break IDN URLs such as http://www.académie-française.fr/ + // To fully support IDN URLs, advocate use of a plugin. + return yourls_apply_filter( 'encodeURI', $result, $url ); +} + /** * Check if a file is a plugin file * diff --git a/includes/functions-formatting.php b/includes/functions-formatting.php index 08ac8d9..83fc396 100644 --- a/includes/functions-formatting.php +++ b/includes/functions-formatting.php @@ -7,15 +7,18 @@ /** * Convert an integer (1337) to a string (3jk). * + * @param int $num Number to convert + * @param string $chars Characters to use for conversion + * @return string Converted number */ -function yourls_int2string( $num, $chars = null ) { +function yourls_int2string($num, $chars = null) { if( $chars == null ) $chars = yourls_get_shorturl_charset(); $string = ''; $len = strlen( $chars ); while( $num >= $len ) { - $mod = bcmod( $num, $len ); - $num = bcdiv( $num, $len ); + $mod = bcmod( (string)$num, (string)$len ); + $num = bcdiv( (string)$num, (string)$len ); $string = $chars[ $mod ] . $string; } $string = $chars[ intval( $num ) ] . $string; @@ -26,8 +29,11 @@ function yourls_int2string( $num, $chars = null ) { /** * Convert a string (3jk) to an integer (1337) * + * @param string $string String to convert + * @param string $chars Characters to use for conversion + * @return string Number (as a string) */ -function yourls_string2int( $string, $chars = null ) { +function yourls_string2int($string, $chars = null) { if( $chars == null ) $chars = yourls_get_shorturl_charset(); $integer = 0; @@ -36,7 +42,7 @@ function yourls_string2int( $string, $chars = null ) { $inputlen = strlen( $string ); for ($i = 0; $i < $inputlen; $i++) { $index = strpos( $chars, $string[$i] ); - $integer = bcadd( $integer, bcmul( $index, bcpow( $baselen, $i ) ) ); + $integer = bcadd( (string)$integer, bcmul( (string)$index, bcpow( (string)$baselen, (string)$i ) ) ); } return yourls_apply_filter( 'string2int', $integer, $string, $chars ); @@ -48,7 +54,6 @@ function yourls_string2int( $string, $chars = null ) { * @since 1.8.3 * @param string $prefix Optional prefix * @return string The unique string - * */ function yourls_unique_element_id($prefix = 'yid') { static $id_counter = 0; @@ -139,8 +144,11 @@ function yourls_sanitize_url_safe( $unsafe_url, $protocols = array() ) { * * Stolen from WP's _deep_replace * + * @param string|array $search Needle, or array of needles. + * @param string $subject Haystack. + * @return string The string with the replaced values. */ -function yourls_deep_replace( $search, $subject ){ +function yourls_deep_replace($search, $subject ){ $found = true; while($found) { $found = false; @@ -158,24 +166,31 @@ function yourls_deep_replace( $search, $subject ){ /** * Make sure an integer is a valid integer (PHP's intval() limits to too small numbers) * + * @param int $int Integer to check + * @return string Integer as a string */ -function yourls_sanitize_int( $int ) { +function yourls_sanitize_int($int ) { return ( substr( preg_replace( '/[^0-9]/', '', strval( $int ) ), 0, 20 ) ); } /** * Sanitize an IP address + * No check on validity, just return a sanitized string * + * @param string $ip IP address + * @return string IP address */ -function yourls_sanitize_ip( $ip ) { +function yourls_sanitize_ip($ip ) { return preg_replace( '/[^0-9a-fA-F:., ]/', '', $ip ); } /** * Make sure a date is m(m)/d(d)/yyyy, return false otherwise * + * @param string $date Date to check + * @return false|mixed Date in format m(m)/d(d)/yyyy or false if invalid */ -function yourls_sanitize_date( $date ) { +function yourls_sanitize_date($date ) { if( !preg_match( '!^\d{1,2}/\d{1,2}/\d{4}$!' , $date ) ) { return false; } @@ -185,18 +200,24 @@ function yourls_sanitize_date( $date ) { /** * Sanitize a date for SQL search. Return false if malformed input. * + * @param string $date Date + * @return false|string String in Y-m-d format for SQL search or false if malformed input */ -function yourls_sanitize_date_for_sql( $date ) { +function yourls_sanitize_date_for_sql($date) { if( !yourls_sanitize_date( $date ) ) return false; return date( 'Y-m-d', strtotime( $date ) ); } /** - * Return trimmed string + * Return trimmed string, optionally append '[...]' if string is too long * + * @param string $string String to trim + * @param int $length Maximum length of string + * @param string $append String to append if trimmed + * @return string Trimmed string */ -function yourls_trim_long_string( $string, $length = 60, $append = '[...]' ) { +function yourls_trim_long_string($string, $length = 60, $append = '[...]') { $newstring = $string; if ( mb_strlen( $newstring ) > $length ) { $newstring = mb_substr( $newstring, 0, $length - mb_strlen( $append ), 'UTF-8' ) . $append; @@ -225,8 +246,10 @@ function yourls_sanitize_version( $version ) { /** * Sanitize a filename (no Win32 stuff) * + * @param string $file File name + * @return string|null Sanitized file name (or null if it's just backslashes, ok...) */ -function yourls_sanitize_filename( $file ) { +function yourls_sanitize_filename($file) { $file = str_replace( '\\', '/', $file ); // sanitize for Win32 installs $file = preg_replace( '|/+|' ,'/', $file ); // remove any duplicate slash return $file; @@ -235,8 +258,10 @@ function yourls_sanitize_filename( $file ) { /** * Check if a string seems to be UTF-8. Stolen from WP. * + * @param string $str String to check + * @return bool Whether string seems valid UTF-8 */ -function yourls_seems_utf8( $str ) { +function yourls_seems_utf8($str) { $length = strlen( $str ); for ( $i=0; $i < $length; $i++ ) { $c = ord( $str[ $i ] ); @@ -417,6 +442,8 @@ function yourls_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) { $others = array( '<' => '<', '<' => '<', '>' => '>', '>' => '>', '&' => '&', '&' => '&', '&' => '&' ); $others_preg = array( '/*60;/' => '<', '/*62;/' => '>', '/*38;/' => '&', '/*26;/i' => '&' ); + $translation = $translation_preg = []; + if ( $quote_style === ENT_QUOTES ) { $translation = array_merge( $single, $double, $others ); $translation_preg = array_merge( $single_preg, $double_preg, $others_preg ); @@ -654,40 +681,16 @@ function yourls_esc_textarea( $text ) { return yourls_apply_filter( 'esc_textarea', $safe_text, $text ); } - -/** -* PHP emulation of JS's encodeURI -* -* @link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI -* @param $url -* @return string -*/ -function yourls_encodeURI( $url ) { - // Decode URL all the way - $result = yourls_rawurldecode_while_encoded( $url ); - // Encode once - $result = strtr( rawurlencode( $result ), array ( - '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@', - '%26' => '&', '%3D' => '=', '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*', - '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#', - ) ); - // @TODO: - // Known limit: this will most likely break IDN URLs such as http://www.académie-française.fr/ - // To fully support IDN URLs, advocate use of a plugin. - return yourls_apply_filter( 'encodeURI', $result, $url ); -} - /** * Adds backslashes before letters and before a number at the start of a string. Stolen from WP. * * @since 1.6 - * * @param string $string Value to which backslashes will be added. * @return string String with backslashes inserted. */ function yourls_backslashit($string) { - $string = preg_replace('/^([0-9])/', '\\\\\\\\\1', $string); - $string = preg_replace('/([a-z])/i', '\\\\\1', $string); + $string = preg_replace('/^([0-9])/', '\\\\\\\\\1', (string)$string); + $string = preg_replace('/([a-z])/i', '\\\\\1', (string)$string); return $string; } @@ -745,7 +748,7 @@ function yourls_make_bookmarklet( $code ) { */ function yourls_get_timestamp( $timestamp ) { $offset = yourls_get_time_offset(); - $timestamp_offset = $timestamp + ($offset * 3600); + $timestamp_offset = (int)$timestamp + ($offset * 3600); return yourls_apply_filter( 'get_timestamp', $timestamp_offset, $timestamp, $offset ); } @@ -793,4 +796,3 @@ function yourls_get_date_format( $format ) { function yourls_get_time_format( $format ) { return yourls_apply_filter( 'get_time_format', (string)$format ); } - diff --git a/includes/functions-html.php b/includes/functions-html.php index 2ae620f..a619ffd 100644 --- a/includes/functions-html.php +++ b/includes/functions-html.php @@ -3,6 +3,7 @@ /** * Display