Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

+98 -87
View File
@@ -11,37 +11,47 @@
namespace IPS;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DateInterval;
use DateTimeZone;
use Exception;
use UnexpectedValueException;
use function count;
use function defined;
use function in_array;
use function substr;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Date/Time Class
*/
class _DateTime extends \DateTime
class DateTime extends \DateTime
{
/**
* Create from timestamp
*
* @param int $timestamp UNIX Timestamp
* @param bool $bypassTimezone Ignore timezone (useful for things like rfc1123() which forces to GMT anyways)
* @return \IPS\DateTime
* @param int $timestamp UNIX Timestamp
* @param bool $bypassTimezone Ignore timezone (useful for things like rfc1123() which forces to GMT anyways)
* @return DateTime
*/
public static function ts( $timestamp, $bypassTimezone=FALSE )
public static function ts( int $timestamp, bool $bypassTimezone=FALSE ): DateTime
{
$obj = new static;
$obj->setTimestamp( (int) $timestamp );
if ( !$bypassTimezone AND \IPS\Dispatcher::hasInstance() and \IPS\Member::loggedIn()->timezone )
$obj->setTimestamp( $timestamp );
if ( !$bypassTimezone AND Dispatcher::hasInstance() and Member::loggedIn()->timezone )
{
if( \in_array( \IPS\Member::loggedIn()->timezone, static::getTimezoneIdentifiers() ) )
if( in_array( Member::loggedIn()->timezone, static::getTimezoneIdentifiers() ) )
{
try
{
$obj->setTimezone( new \DateTimeZone( \IPS\Member::loggedIn()->timezone ) );
$obj->setTimezone( new DateTimeZone( Member::loggedIn()->timezone ) );
}
catch ( \Exception $e )
catch ( Exception $e )
{
}
@@ -53,7 +63,7 @@ class _DateTime extends \DateTime
/**
* @brief Cached timezone identifiers
*/
protected static $timeZoneIdentifiers = NULL;
protected static ?array $timeZoneIdentifiers = NULL;
/**
* Get the valid time zone identifiers
@@ -61,11 +71,11 @@ class _DateTime extends \DateTime
* @note Abstracted to implement caching
* @return array
*/
public static function getTimezoneIdentifiers()
public static function getTimezoneIdentifiers(): array
{
if( static::$timeZoneIdentifiers === NULL )
{
static::$timeZoneIdentifiers = \DateTimeZone::listIdentifiers();
static::$timeZoneIdentifiers = DateTimeZone::listIdentifiers();
}
return static::$timeZoneIdentifiers;
@@ -74,14 +84,14 @@ class _DateTime extends \DateTime
/**
* Helper method to fix Argentinian Timezones https://bugs.webkit.org/show_bug.cgi?id=218542
*
* @param string $timezone
* @param string|null $timezone
* @return string|null
*/
public static function getFixedTimezone( ?string $timezone=NULL ) :? string
{
if ( \in_array( $timezone, [ 'America/Buenos_Aires', 'America/Catamarca', 'America/Cordoba', 'America/Jujuy', 'America/La_Rioja', 'America/Mendoza', 'America/Rio_Gallegos', 'America/Salta', 'America/San_Juan', 'America/San_Luis', 'America/Tucuman', 'America/Ushuaia' ] ) )
if ( in_array( $timezone, [ 'America/Buenos_Aires', 'America/Catamarca', 'America/Cordoba', 'America/Jujuy', 'America/La_Rioja', 'America/Mendoza', 'America/Rio_Gallegos', 'America/Salta', 'America/San_Juan', 'America/San_Luis', 'America/Tucuman', 'America/Ushuaia' ] ) )
{
return 'America/Argentina/' . \substr( $timezone, 8 );
return 'America/Argentina/' . substr( $timezone, 8 );
}
/* Chromium reports the wrong timezones for these locations - https://bugs.chromium.org/p/chromium/issues/detail?id=580195 */
@@ -96,9 +106,9 @@ class _DateTime extends \DateTime
/**
* Create New
*
* @return \IPS|DateTime
* @return DateTime
*/
public static function create()
public static function create(): DateTime
{
return new static;
}
@@ -106,12 +116,12 @@ class _DateTime extends \DateTime
/**
* Format a DateInterval showing only the relevant pieces.
*
* @param \DateInterval $diff The interval
* @param int $restrictParts The maximum number of "pieces" to return. Restricts "1 year, 1 month, 1 day, 1 hour, 1 minute, 1 second" to just "1 year, 1 month". Pass 0 to not reduce.
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param DateInterval $diff The interval
* @param int $restrictParts The maximum number of "pieces" to return. Restricts "1 year, 1 month, 1 day, 1 hour, 1 minute, 1 second" to just "1 year, 1 month". Pass 0 to not reduce.
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public static function formatInterval( \DateInterval $diff, $restrictParts=2, $memberOrLanguage=NULL )
public static function formatInterval( DateInterval $diff, int $restrictParts=2, Lang|Member $memberOrLanguage=NULL ): string
{
$language = static::determineLanguage( $memberOrLanguage );
@@ -144,7 +154,7 @@ class _DateTime extends \DateTime
}
/* If we don't have anything but seconds, return "less than a minute ago" */
if( !\count($format) )
if( !count($format) )
{
if( $diff->s !== 0 )
{
@@ -187,31 +197,32 @@ class _DateTime extends \DateTime
*/
public function __toString()
{
return (string) static::strftime( '%x ' . static::localeTimeFormat(), $this->getTimestamp() + $this->getTimezone()->getOffset( $this ), \IPS\Member::loggedIn()->language() );
return static::strftime( '%x ' . static::localeTimeFormat(), $this->getTimestamp() + $this->getTimezone()->getOffset( $this ), Member::loggedIn()->language() );
}
/**
* Get HTML output
*
* @param bool $capitalize TRUE if by itself, FALSE if in the middle of a sentence
* @param bool $short Whether or not to use the short form
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param bool $capitalize TRUE if by itself, FALSE if in the middle of a sentence
* @param bool $short Whether or not to use the short form
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function html( $capitalize=TRUE, $short=FALSE, $memberOrLanguage=NULL )
public function html(bool $capitalize=TRUE, bool $short=FALSE, Lang|Member $memberOrLanguage=NULL ): string
{
$format = $short ? 1 : ( $capitalize ? static::RELATIVE_FORMAT_NORMAL : static::RELATIVE_FORMAT_LOWER );
$shortLong = $short ? 'short' : 'long';
return "<time datetime='{$this->rfc3339()}' title='{$this}' data-short='" . trim( $this->relative( 1, $memberOrLanguage ) ) . "'>" . trim( $this->relative( $format, $memberOrLanguage ) ) . "</time>";
return "<time datetime='{$this->rfc3339()}' title='{$this}' data-short='" . trim( $this->relative( 1, $memberOrLanguage ) ) . "' class='ipsTime ipsTime--{$shortLong}'><span class='ipsTime__long'>" . trim( $this->relative( $format, $memberOrLanguage ) ) . "</span><span class='ipsTime__short'>" . trim( $this->relative( 1, $memberOrLanguage ) ) . "</span></time>";
}
/**
* Get total number of days from DateInterval
*
* @param \DateInterval $interval DateInterval
* @param DateInterval $interval DateInterval
* @return int
*/
static public function intervalToDays( \DateInterval $interval ): int
static public function intervalToDays( DateInterval $interval ): int
{
$days = 0;
if ( $interval->y )
@@ -233,10 +244,10 @@ class _DateTime extends \DateTime
/**
* Format the date according to the user's locale (without the time)
*
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function localeDate( $memberOrLanguage=NULL )
public function localeDate( Lang|Member $memberOrLanguage=NULL ): string
{
return static::strftime( '%x', $this->getTimestamp() + $this->getTimezone()->getOffset( $this ), static::determineLanguage( $memberOrLanguage ) );
}
@@ -244,10 +255,10 @@ class _DateTime extends \DateTime
/**
* Format the date to return month and day without a year
*
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function dayAndMonth( $memberOrLanguage=NULL )
public function dayAndMonth( Lang|Member $memberOrLanguage=NULL ): string
{
$language = static::determineLanguage( $memberOrLanguage );
return $language->addToStack(
@@ -265,10 +276,10 @@ class _DateTime extends \DateTime
/**
* Format the date to return short month and day without a year
*
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function dayAndShortMonth( $memberOrLanguage=NULL )
public function dayAndShortMonth( Lang|Member $memberOrLanguage=NULL ): string
{
$language = static::determineLanguage( $memberOrLanguage );
return $language->addToStack(
@@ -286,10 +297,10 @@ class _DateTime extends \DateTime
/**
* Format the date to return short month and full year.
*
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function shortMonthAndFullYear( $memberOrLanguage=NULL )
public function shortMonthAndFullYear( Lang|Member $memberOrLanguage=NULL ): string
{
$language = static::determineLanguage( $memberOrLanguage );
return $language->addToStack(
@@ -310,10 +321,10 @@ class _DateTime extends \DateTime
/**
* Get locale date, forced to 4-digit year format
*
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function fullYearLocaleDate( $memberOrLanguage=NULL )
public function fullYearLocaleDate( Lang|Member $memberOrLanguage=NULL ): string
{
$language = static::determineLanguage( $memberOrLanguage );
$timestamp = $this->getTimestamp() + $this->getTimezone()->getOffset( $this );
@@ -333,14 +344,14 @@ class _DateTime extends \DateTime
* countries prefer 12-hour format, so we override
* specifically for them
*
* @param bool $seconds If TRUE, will include seconds
* @param bool $minutes If TRUE, will include minutes
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param bool $seconds If TRUE, will include seconds
* @param bool $minutes If TRUE, will include minutes
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public static function localeTimeFormat( $seconds=FALSE, $minutes=TRUE, $memberOrLanguage=NULL )
public static function localeTimeFormat( bool $seconds=FALSE, bool $minutes=TRUE, Lang|Member $memberOrLanguage=NULL ): string
{
if ( \in_array( preg_replace( '/\.UTF-?8$/', '', static::determineLanguage( $memberOrLanguage )->short ), array(
if ( in_array( preg_replace( '/\.UTF-?8$/', '', static::determineLanguage( $memberOrLanguage )->short ), array(
'sq_AL', // Albanian - Albania
'zh_SG', 'sgp', 'singapore', // Chinese - Singapore
'zh_TW', 'twn', 'taiwan', // Chinese - Taiwan
@@ -366,21 +377,21 @@ class _DateTime extends \DateTime
return '%l' . ( $minutes ? ':%M' : '' ) . ( $seconds ? ':%S ' : ' ' ) . ' %p';
}
}
return '%H' . ( $minutes ? ':%M' : '' ) . ( $seconds ? ':%S ' : '' );
}
/**
* Format the time according to the user's locale (without the date)
*
* @param bool $seconds If TRUE, will include seconds
* @param bool $minutes If TRUE, will include minutes
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param bool $seconds If TRUE, will include seconds
* @param bool $minutes If TRUE, will include minutes
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function localeTime( $seconds=TRUE, $minutes=TRUE, $memberOrLanguage=NULL )
public function localeTime( bool $seconds=TRUE, bool $minutes=TRUE, Lang|Member $memberOrLanguage=NULL ): string
{
return static::strftime( static::localeTimeFormat( $seconds, $minutes, $memberOrLanguage ), $this->getTimestamp() + $this->getTimezone()->getOffset( $this ), static::determineLanguage( $memberOrLanguage ) );
return static::strftime( static::localeTimeFormat($seconds, $minutes, $memberOrLanguage), $this->getTimestamp() + $this->getTimezone()->getOffset( $this ), static::determineLanguage( $memberOrLanguage ) );
}
/**
@@ -402,17 +413,17 @@ class _DateTime extends \DateTime
* Format the date relative to the current date/time
* e.g. "30 minutes ago"
*
* @param int $format The format (see RELATIVE_FORMAT_* constants)
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param int $format The format (see RELATIVE_FORMAT_* constants)
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function relative( $format=0, $memberOrLanguage=NULL )
public function relative( int $format=0, Lang|Member $memberOrLanguage=NULL ): string
{
$language = static::determineLanguage( $memberOrLanguage );
if( \IPS\Settings::i()->relative_dates_enable )
if( Settings::i()->relative_dates_enable )
{
$now = static::ts( time() );
$now = static::ts(time());
$difference = $this->diff( $now );
$capitalKey = ( $format == static::RELATIVE_FORMAT_LOWER ) ? '' : '_c';
@@ -466,7 +477,7 @@ class _DateTime extends \DateTime
return $language->addToStack( 'f_days_short', FALSE, array( 'pluralize' => array( $difference->d ) ) );
}
/* Yesterday: "Yesterday at 23:56" */
elseif ( $difference->d == 1 && ( $compare->add( new \DateInterval( 'P1D' ) )->format( 'Y-m-d', $language ) == $now->format( 'Y-m-d', $language ) ) )
elseif ( $difference->d == 1 && ( $compare->add( new DateInterval( 'P1D' ) )->format( 'Y-m-d', $language ) == $now->format( 'Y-m-d', $language ) ) )
{
return $language->addToStack( "_date_yesterday{$capitalKey}", FALSE, array( 'sprintf' => array( $this->localeTime( FALSE, TRUE, $language ) ) ) );
}
@@ -556,11 +567,11 @@ class _DateTime extends \DateTime
/**
* Format times based on strftime() calls instead of date() calls, and convert to UTF-8 if necessary
*
* @param string $format Format accepted by strftime()
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param string $format Format accepted by strftime()
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function strFormat( $format, $memberOrLanguage=NULL )
public function strFormat( string $format, Lang|Member $memberOrLanguage=NULL ): string
{
/* We only do this on Windows - Windows does not support the %e formatter */
if( mb_strtoupper( mb_substr( PHP_OS, 0, 3 ) ) === 'WIN' )
@@ -575,10 +586,10 @@ class _DateTime extends \DateTime
* Wrapper for format() so we can convert to UTF-8 if needed
*
* @param string $format Format accepted by date()
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function format( $format, $memberOrLanguage=NULL )
public function format( string $format, Lang|Member $memberOrLanguage=NULL ): string
{
/* If this is just the year, which we do periodically, then we can skip UTF-8 conversion stuff since the result is always an integer -
saves resources, especially from the relative() method which can be called 25+ times per page load and can call this method 4 times each*/
@@ -596,7 +607,7 @@ class _DateTime extends \DateTime
*
* @return string
*/
public function rfc3339()
public function rfc3339(): string
{
return date( 'Y-m-d', $this->getTimestamp() ) . 'T' . date( 'H:i:s', $this->getTimestamp() ) . 'Z';
}
@@ -607,7 +618,7 @@ class _DateTime extends \DateTime
*
* @return string
*/
public function rfc1123()
public function rfc1123(): string
{
return gmdate( "D, d M Y H:i:s", $this->getTimestamp() ) . ' GMT';
}
@@ -615,44 +626,44 @@ class _DateTime extends \DateTime
/**
* Determine the language object to use based on the passed in parameter, which could be NULL, \IPS\Member or \IPS\Lang
*
* @param \IPS\Lang|\IPS\Member|NULL $formatter Value we are using to determine how to format the result
* @return \IPS\Lang
* @param Lang|Member|NULL $formatter Value we are using to determine how to format the result
* @return Lang
*/
protected static function determineLanguage( $formatter=NULL )
protected static function determineLanguage( Lang|Member|null $formatter=NULL ): Lang
{
/* If nothing is passed in (the norm) we want the current viewing user's language selection */
if( $formatter === NULL )
{
return \IPS\Member::loggedIn()->language();
return Member::loggedIn()->language();
}
elseif( $formatter instanceof \IPS\Member )
elseif( $formatter instanceof Member)
{
return $formatter->language();
}
elseif( $formatter instanceof \IPS\Lang )
elseif( $formatter instanceof Lang)
{
return $formatter;
}
throw new \UnexpectedValueException;
throw new UnexpectedValueException;
}
/**
* Run strftime() call using a given language object
*
* @param string $formatter Time format to be passed to stftime()
* @param int $time Timestamp
* @param \IPS\Lang $language Language to use
* @param string $formatter Time format to be passed to stftime()
* @param int $time Timestamp
* @param Lang $language Language to use
* @return string
*/
protected static function strftime( $formatter, $time, $language )
protected static function strftime( string $formatter, int $time, Lang $language ): string
{
$curLocale = setlocale( LC_ALL, '0' );
$language->setLocale();
$result = $language->convertString( strftime( $formatter, $time ) );
\IPS\Lang::restoreLocale( $curLocale );
Lang::restoreLocale( $curLocale );
return $result;
}
@@ -661,11 +672,11 @@ class _DateTime extends \DateTime
* Show a datetime diff rounded for human readability
* e.g. "4 days, 5 hours"
*
* @param \IPS\DateTime $date Date to compare against
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param DateTime $date Date to compare against
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public function roundedDiff( \IPS\DateTime $date, $memberOrLanguage=NULL )
public function roundedDiff( DateTime $date, Lang|Member $memberOrLanguage=NULL ): string
{
$language = static::determineLanguage( $memberOrLanguage );
$difference = $this->diff( $date );
@@ -750,14 +761,14 @@ class _DateTime extends \DateTime
* Show a datetime diff rounded for human readability based purely on a number of seconds
* e.g. "4 days, 5 hours"
*
* @param int $seconds Number of seconds difference
* @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @param int $seconds Number of seconds difference
* @param Lang|Member|null $memberOrLanguage The language or member to use, or NULL for currently logged in member
* @return string
*/
public static function roundedDiffFromSeconds( $seconds, $memberOrLanguage=NULL )
public static function roundedDiffFromSeconds( int $seconds, Lang|Member $memberOrLanguage=NULL ): string
{
$now = static::ts( time() );
$then = static::ts( time() - $seconds );
$now = static::ts(time());
$then = static::ts(time() - $seconds);
return $now->roundedDiff( $then, $memberOrLanguage );
}