* Implemented support for timezones
* Removed OFFSET const from config
* Localized yet sortable date in admin shorturl list
* Unit tests
* Code cleanup

Co-authored-by: Jérémy K <JeremyKeusters@users.noreply.github.com>
This commit is contained in:
྅༻ Ǭɀħ ༄༆ཉ 2020-05-15 14:41:10 +02:00 committed by GitHub
parent 4a78d72936
commit 6dc5423f5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 180 additions and 49 deletions

View File

@ -132,6 +132,9 @@ td.url small a{
body.desktop td.actions input,body.desktop td.actions a {
visibility:hidden;
}
td.timestamp span.timestamp {
display:none;
}
td.actions input.disabled, td.actions input.loading {
visibility:visible;
}

View File

@ -27,7 +27,7 @@ class Init {
$this->include_core_functions();
}
// Enforce UTC timezone to suppress PHP warnings -- correct date/time will be managed using the config time offset
// Enforce UTC timezone. Date/time can be adjusted with a plugin.
if ($actions->default_timezone === true) {
date_default_timezone_set( 'UTC' );
}

View File

@ -722,3 +722,62 @@ function yourls_make_bookmarklet( $code ) {
$book = new \Ozh\Bookmarkletgen\Bookmarkletgen;
return $book->crunch( $code );
}
/**
* Return a timestamp, plus or minus the time offset if defined
*
* @since 1.7.10
* @param string|int $timestamp a timestamp
* @return int a timestamp, plus or minus offset if defined
*/
function yourls_get_timestamp( $timestamp ) {
$offset = yourls_get_time_offset();
$timestamp_offset = $timestamp + ($offset * 3600);
return yourls_apply_filter( 'get_timestamp', $timestamp_offset, $timestamp, $offset );
}
/**
* Get time offset, as defined in config, filtered
*
* @since 1.7.10
* @return int Time offset
*/
function yourls_get_time_offset() {
$offset = defined('YOURLS_HOURS_OFFSET') ? (int)YOURLS_HOURS_OFFSET : 0;
return yourls_apply_filter( 'get_time_offset', $offset );
}
/**
* Return a date() format for a full date + time, filtered
*
* @since 1.7.10
* @param string $format Date format string
* @return string Date format string
*/
function yourls_get_datetime_format( $format ) {
return yourls_apply_filter( 'get_datetime_format', (string)$format );
}
/**
* Return a date() format for date (no time), filtered
*
* @since 1.7.10
* @param string $format Date format string
* @return string Date format string
*/
function yourls_get_date_format( $format ) {
return yourls_apply_filter( 'get_date_format', (string)$format );
}
/**
* Return a date() format for a time (no date), filtered
*
* @since 1.7.10
* @param string $format Date format string
* @return string Date format string
*/
function yourls_get_time_format( $format ) {
return yourls_apply_filter( 'get_time_format', (string)$format );
}

View File

@ -599,8 +599,9 @@ function yourls_table_add_row( $keyword, $url, $title = '', $ip, $clicks, $times
'warning' => $protocol_warning,
),
'timestamp' => array(
'template' => '%date%',
'date' => date( 'M d, Y H:i', $timestamp +( YOURLS_HOURS_OFFSET * 3600 ) ),
'template' => '<span class="timestamp" aria-hidden="true">%timestamp%</span> %date%',
'timestamp' => $timestamp,
'date' => yourls_date_i18n( yourls_get_datetime_format('M d, Y H:i'), yourls_get_timestamp( $timestamp )),
),
'ip' => array(
'template' => '%ip%',
@ -1001,4 +1002,3 @@ function yourls_get_html_context() {
global $ydb;
$ydb->get_html_context();
}

View File

@ -121,27 +121,27 @@ function yourls__( $text, $domain = 'default' ) {
* @return string Translated text
*/
function yourls_s( $pattern ) {
// Get pattern and pattern arguments
// Get pattern and pattern arguments
$args = func_get_args();
// If yourls_s() called by yourls_se(), all arguments are wrapped in the same array key
if( count( $args ) == 1 && is_array( $args[0] ) ) {
$args = $args[0];
}
$pattern = $args[0];
// get list of sprintf tokens (%s and such)
$num_of_tokens = substr_count( $pattern, '%' ) - 2 * substr_count( $pattern, '%%' );
$domain = 'default';
// More arguments passed than needed for the sprintf? The last one will be the domain
if( $num_of_tokens < ( count( $args ) - 1 ) ) {
$domain = array_pop( $args );
}
// Translate text
$args[0] = yourls__( $pattern, $domain );
return call_user_func_array( 'sprintf', $args );
return call_user_func_array( 'sprintf', $args );
}
/**
@ -378,7 +378,7 @@ function yourls_nx($single, $plural, $number, $context, $domain = 'default') {
function yourls_n_noop( $singular, $plural, $domain = null ) {
return array(
0 => $singular,
1 => $plural,
1 => $plural,
'singular' => $singular,
'plural' => $plural,
'context' => null,
@ -507,7 +507,7 @@ function yourls_unload_textdomain( $domain ) {
*/
function yourls_load_default_textdomain() {
$yourls_locale = yourls_get_locale();
if( !empty( $yourls_locale ) )
return yourls_load_textdomain( 'default', YOURLS_LANG_DIR . "/$yourls_locale.mo" );
}
@ -563,13 +563,13 @@ function yourls_translate_user_role( $name ) {
*/
function yourls_get_available_languages( $dir = null ) {
$languages = array();
$dir = is_null( $dir) ? YOURLS_LANG_DIR : $dir;
foreach( (array) glob( $dir . '/*.mo' ) as $lang_file ) {
$languages[] = basename( $lang_file, '.mo' );
}
return yourls_apply_filter( 'get_available_languages', $languages );
}
@ -586,7 +586,7 @@ function yourls_number_format_i18n( $number, $decimals = 0 ) {
global $yourls_locale_formats;
if( !isset( $yourls_locale_formats ) )
$yourls_locale_formats = new YOURLS_Locale_Formats();
$formatted = number_format( $number, abs( intval( $decimals ) ), $yourls_locale_formats->number_format['decimal_point'], $yourls_locale_formats->number_format['thousands_sep'] );
return yourls_apply_filter( 'number_format_i18n', $formatted );
}
@ -635,7 +635,7 @@ function yourls_date_i18n( $dateformatstring, $unixtimestamp = false, $gmt = fal
$dateweekday_abbrev = $yourls_locale_formats->get_weekday_abbrev( $dateweekday );
$datemeridiem = $yourls_locale_formats->get_meridiem( $datefunc( 'a', $i ) );
$datemeridiem_capital = $yourls_locale_formats->get_meridiem( $datefunc( 'A', $i ) );
$dateformatstring = ' '.$dateformatstring;
$dateformatstring = preg_replace( "/([^\\\])D/", "\\1" . yourls_backslashit( $dateweekday_abbrev ), $dateformatstring );
$dateformatstring = preg_replace( "/([^\\\])F/", "\\1" . yourls_backslashit( $datemonth ), $dateformatstring );
@ -649,7 +649,7 @@ function yourls_date_i18n( $dateformatstring, $unixtimestamp = false, $gmt = fal
$timezone_formats = array( 'P', 'I', 'O', 'T', 'Z', 'e' );
$timezone_formats_re = implode( '|', $timezone_formats );
if ( preg_match( "/$timezone_formats_re/", $dateformatstring ) ) {
// TODO: implement a timezone option
$timezone_string = yourls_get_option( 'timezone_string' );
if ( $timezone_string ) {
@ -689,15 +689,13 @@ function yourls_date_i18n( $dateformatstring, $unixtimestamp = false, $gmt = fal
function yourls_current_time( $type, $gmt = 0 ) {
switch ( $type ) {
case 'mysql':
return ( $gmt ) ? gmdate( 'Y-m-d H:i:s' ) : gmdate( 'Y-m-d H:i:s', time() + YOURLS_HOURS_OFFSET * 3600 );
return ( $gmt ) ? gmdate( 'Y-m-d H:i:s' ) : gmdate( 'Y-m-d H:i:s', yourls_get_timestamp( time() ));
break;
case 'timestamp':
return ( $gmt ) ? time() : time() + YOURLS_HOURS_OFFSET * 3600;
break;
return ( $gmt ) ? time() : yourls_get_timestamp( time() );
}
}
/**
* Class that loads the calendar locale.
*
@ -955,7 +953,7 @@ class YOURLS_Locale_Formats {
* @return string Translated full month name
*/
function get_month( $month_number ) {
return $this->month[ sprintf( '%02s', $month_number ) ];
return $this->month[ sprintf( '%02s', $month_number ) ];
}
/**
@ -1060,7 +1058,7 @@ function yourls_is_rtl() {
global $yourls_locale_formats;
if( !isset( $yourls_locale_formats ) )
$yourls_locale_formats = new YOURLS_Locale_Formats();
return $yourls_locale_formats->is_rtl();
}
@ -1078,10 +1076,10 @@ function yourls_l10n_weekday_abbrev( $weekday = '' ){
global $yourls_locale_formats;
if( !isset( $yourls_locale_formats ) )
$yourls_locale_formats = new YOURLS_Locale_Formats();
if( $weekday === '' )
return $yourls_locale_formats->weekday_abbrev;
if( is_int( $weekday ) ) {
$day = $yourls_locale_formats->weekday[ $weekday ];
return $yourls_locale_formats->weekday_abbrev[ $day ];
@ -1104,10 +1102,10 @@ function yourls_l10n_weekday_initial( $weekday = '' ){
global $yourls_locale_formats;
if( !isset( $yourls_locale_formats ) )
$yourls_locale_formats = new YOURLS_Locale_Formats();
if( $weekday === '' )
return $yourls_locale_formats->weekday_initial;
if( is_int( $weekday ) ) {
$weekday = $yourls_locale_formats->weekday[ $weekday ];
return $yourls_locale_formats->weekday_initial[ $weekday ];
@ -1130,10 +1128,10 @@ function yourls_l10n_month_abbrev( $month = '' ){
global $yourls_locale_formats;
if( !isset( $yourls_locale_formats ) )
$yourls_locale_formats = new YOURLS_Locale_Formats();
if( $month === '' )
return $yourls_locale_formats->month_abbrev;
if( intval( $month ) > 0 ) {
$month = sprintf('%02d', intval( $month ) );
$month = $yourls_locale_formats->month[ $month ];
@ -1153,6 +1151,6 @@ function yourls_l10n_months(){
global $yourls_locale_formats;
if( !isset( $yourls_locale_formats ) )
$yourls_locale_formats = new YOURLS_Locale_Formats();
return $yourls_locale_formats->month;
}

View File

@ -0,0 +1,72 @@
<?php
/**
* Formatting functions for time & dates
*
* @group formatting
* @group timedate
* @since 0.1
*/
class Format_Test_Dates extends PHPUnit_Framework_TestCase {
protected function tearDown() {
yourls_remove_all_filters( 'get_time_offset' );
}
/**
* Test yourls_get_timestamp returns an int
*/
function test_get_time_offset() {
$this->assertInternalType( "int", yourls_get_time_offset() );
}
/**
* Test yourls_get_datetime_format returns a string
*/
function test_get_datetime_format() {
$this->assertInternalType( "string", yourls_get_datetime_format('M d, Y H:i') );
$this->assertInternalType( "string", yourls_get_datetime_format( 10 ) );
$this->assertInternalType( "string", yourls_get_datetime_format(false) );
}
/**
* Test yourls_get_date_format returns a string
*/
function test_get_date_format() {
$this->assertInternalType( "string", yourls_get_date_format('M d, Y') );
$this->assertInternalType( "string", yourls_get_date_format( 10 ) );
$this->assertInternalType( "string", yourls_get_date_format(false) );
}
/**
* Test yourls_get_time_format returns a string
*/
function test_get_time_format() {
$this->assertInternalType( "string", yourls_get_time_format('H:i') );
$this->assertInternalType( "string", yourls_get_time_format( 10 ) );
$this->assertInternalType( "string", yourls_get_time_format(false) );
}
/**
* Test yourls_get_timestamp returns unmodified timestamp if no offset
*/
function test_get_time_offset_zero() {
$now = time();
yourls_add_filter('get_time_offset', 'yourls_return_zero' );
$this->assertEquals( $now, yourls_get_timestamp( $now ) );
}
/**
* Test yourls_get_timestamp returns a timestamp with offset
*/
function test_get_time_offset_non_zero() {
$now = time();
$offset = mt_rand(-12,12);
yourls_add_filter('get_time_offset', function() use($offset) {return $offset;} );
$this->assertEquals( $now + ($offset * 3600), yourls_get_timestamp( $now ) );
}
}

View File

@ -34,14 +34,11 @@ define( 'YOURLS_DB_PREFIX', 'yourls_' );
** If you define it to "http://sho.rt", don't use "http://www.sho.rt" in your browser (and vice-versa) */
define( 'YOURLS_SITE', 'http://your-own-domain-here.com' );
/** Server timezone GMT offset */
define( 'YOURLS_HOURS_OFFSET', 0 );
/** YOURLS language
** Change this setting to use a translation file for your language, instead of the default English.
** That translation file (a .mo file) must be installed in the user/language directory.
** See http://yourls.org/translations for more information */
define( 'YOURLS_LANG', '' );
define( 'YOURLS_LANG', '' );
/** Allow multiple short URLs for a same long URL
** Set to true to have only one pair of shortURL/longURL (default YOURLS behavior)
@ -68,7 +65,7 @@ $yourls_user_passwords = array(
/** Debug mode to output some internal information
** Default is false for live site. Enable when coding or before submitting a new issue */
define( 'YOURLS_DEBUG', false );
/*
** URL Shortening settings
*/
@ -81,7 +78,7 @@ define( 'YOURLS_URL_CONVERT', 36 );
* Stick to one setting. It's best not to change after you've started creating links.
*/
/**
/**
* Reserved keywords (so that generated URLs won't match them)
* Define here negative, unwanted or potentially misleading keywords.
*/
@ -92,4 +89,3 @@ $yourls_reserved_URL = array(
/*
** Personal settings would go after here.
*/

View File

@ -25,14 +25,14 @@ function ozh_yourls_samplepage_do_page() {
if( isset( $_POST['test_option'] ) ) {
// Check nonce
yourls_verify_nonce( 'sample_page' );
// Process form
ozh_yourls_samplepage_update_option();
}
// Get value from database
$test_option = yourls_get_option( 'test_option' );
// Create nonce
$nonce = yourls_create_nonce( 'sample_page' );
@ -51,13 +51,13 @@ HTML;
// Update option in database
function ozh_yourls_samplepage_update_option() {
$in = $_POST['test_option'];
if( $in ) {
// Validate test_option. ALWAYS validate and sanitize user input.
// Here, we want an integer
$in = intval( $in);
// Update value in database
yourls_update_option( 'test_option', $in );
}
}
}

View File

@ -149,12 +149,14 @@ if( yourls_do_log_redirect() ) {
unset($_lists);
}
$offset = yourls_get_time_offset();
// *** Last 24 hours : array of $last_24h[ $hour ] = number of click ***
$sql = "SELECT
DATE_FORMAT(DATE_ADD(`click_time`, INTERVAL " . YOURLS_HOURS_OFFSET . " HOUR), '%H %p') AS `time`,
DATE_FORMAT(DATE_ADD(`click_time`, INTERVAL " . $offset . " HOUR), '%H %p') AS `time`,
COUNT(*) AS `count`
FROM `$table`
WHERE `shorturl` $keyword_range AND DATE_ADD(`click_time`, INTERVAL " . YOURLS_HOURS_OFFSET . " HOUR) > (DATE_ADD(CURRENT_TIMESTAMP, INTERVAL " . YOURLS_HOURS_OFFSET . " HOUR) - INTERVAL 1 DAY)
WHERE `shorturl` $keyword_range AND DATE_ADD(`click_time`, INTERVAL " . $offset . " HOUR) > (DATE_ADD(CURRENT_TIMESTAMP, INTERVAL " . $offset . " HOUR) - INTERVAL 1 DAY)
GROUP BY `time`;";
$sql = yourls_apply_filter('stat_query_last24h', $sql);
$rows = $ydb->fetchObjects($sql, $keyword_binds);
@ -167,7 +169,7 @@ if( yourls_do_log_redirect() ) {
$now = intval( date('U') );
for ($i = 23; $i >= 0; $i--) {
$h = date('H A', ($now - ($i * 60 * 60) + (YOURLS_HOURS_OFFSET * 60 * 60)) );
$h = date('H A', ($now - ($i * 60 * 60) + ($offset * 60 * 60)) );
// If the $last_24h doesn't have all the hours, insert missing hours with value 0
$last_24h[ $h ] = array_key_exists( $h, $_last_24h ) ? $_last_24h[ $h ] : 0 ;
}
@ -335,14 +337,15 @@ yourls_html_menu();
<td valign="top">
<h3><?php yourls_e( 'Historical click count' ); ?></h3>
<?php
$ago = round( (date('U') - strtotime($timestamp)) / (24* 60 * 60 ) );
$timestamp = strtotime( $timestamp );
$ago = round( (date('U') - $timestamp) / (24* 60 * 60 ) );
if( $ago <= 1 ) {
$daysago = '';
} else {
$daysago = ' (' . sprintf( yourls_n( 'about 1 day ago', 'about %s days ago', $ago ), $ago ) . ')';
}
?>
<p><?php echo /* //translators: eg Short URL created on March 23rd 1972 */ yourls_s( 'Short URL created on %s', yourls_date_i18n( "F j, Y @ g:i a", ( strtotime( $timestamp ) + YOURLS_HOURS_OFFSET * 3600 ) ) ) . $daysago; ?></p>
<p><?php echo /* //translators: eg Short URL created on March 23rd 1972 */ yourls_s( 'Short URL created on %s', yourls_date_i18n( yourls_get_datetime_format("F j, Y @ g:i a"), yourls_get_timestamp( $timestamp ) ) ) . $daysago; ?></p>
<div class="wrap_unfloat">
<ul class="no_bullet toggle_display stat_line" id="historical_clicks">
<?php
@ -381,7 +384,7 @@ yourls_html_menu();
$best_time['month'] = date( "m", strtotime( $best['day'] ) );
$best_time['year'] = date( "Y", strtotime( $best['day'] ) );
?>
<p><?php echo sprintf( /* //translators: eg. 43 hits on January 1, 1970 */ yourls_n( '<strong>%1$s</strong> hit on %2$s', '<strong>%1$s</strong> hits on %2$s', $best['max'] ), $best['max'], yourls_date_i18n( "F j, Y", strtotime( $best['day'] ) ) ); ?>.
<p><?php echo sprintf( /* //translators: eg. 43 hits on January 1, 1970 */ yourls_n( '<strong>%1$s</strong> hit on %2$s', '<strong>%1$s</strong> hits on %2$s', $best['max'] ), $best['max'], yourls_date_i18n( yourls_get_date_format("F j, Y"), strtotime( $best['day'] ) ) ); ?>.
<a href="" class='details hide-if-no-js' id="more_clicks"><?php yourls_e( 'Click for more details' ); ?></a></p>
<ul id="details_clicks" style="display:none">
<?php