From f2786e74b1303227acf912ec1373bf81a127267d Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Sun, 15 Dec 2024 09:54:11 -0800 Subject: [PATCH] Killing time() --- src/Craft.php | 3 +- src/cache/DbCache.php | 5 +-- .../controllers/ProjectConfigController.php | 3 +- src/helpers/Api.php | 2 +- src/helpers/DateTimeHelper.php | 8 ++--- src/helpers/ProjectConfig.php | 2 +- src/models/ImageTransformIndex.php | 3 +- src/queue/Queue.php | 26 +++++++++----- src/services/Elements.php | 2 +- src/services/Fields.php | 3 +- src/services/TemplateCaches.php | 2 +- src/web/Response.php | 4 ++- src/web/User.php | 9 ++--- src/web/twig/nodes/ExpiresNode.php | 6 +++- .../unit/helpers/ProjectConfigHelperTest.php | 8 +++-- tests/unit/web/ResponseTest.php | 8 +++-- tests/unit/web/UserTest.php | 35 +++++++++++++------ 17 files changed, 82 insertions(+), 47 deletions(-) diff --git a/src/Craft.php b/src/Craft.php index 3c03ca35a0b..31d626c7ffe 100644 --- a/src/Craft.php +++ b/src/Craft.php @@ -12,6 +12,7 @@ use craft\helpers\App; use craft\helpers\ArrayHelper; use craft\helpers\Component; +use craft\helpers\DateTimeHelper; use craft\helpers\FileHelper; use craft\helpers\StringHelper; use GuzzleHttp\Client; @@ -334,7 +335,7 @@ private static function _generateCustomFieldBehavior(array $fieldHandles, ?strin // Delete any CustomFieldBehavior files that are over 10 seconds old $basename = basename($filePath); - $time = time() - 10; + $time = DateTimeHelper::currentTimeStamp() - 10; FileHelper::clearDirectory($dir, [ 'filter' => function(string $path) use ($basename, $time): bool { $b = basename($path); diff --git a/src/cache/DbCache.php b/src/cache/DbCache.php index a1895937284..f95a9574b24 100644 --- a/src/cache/DbCache.php +++ b/src/cache/DbCache.php @@ -9,6 +9,7 @@ use Craft; use craft\db\Connection; +use craft\helpers\DateTimeHelper; use craft\helpers\Db; use Exception; use PDO; @@ -60,7 +61,7 @@ protected function setValue($key, $value, $duration): bool $this->db->noCache(function(Connection $db) use ($key, $value, $duration) { Db::upsert($this->cacheTable, [ 'id' => $key, - 'expire' => $duration > 0 ? $duration + time() : 0, + 'expire' => $duration > 0 ? DateTimeHelper::currentTimeStamp() + $duration : 0, 'data' => new PdoValue($value, PDO::PARAM_LOB), ], db: $db); }); @@ -83,7 +84,7 @@ protected function addValue($key, $value, $duration): bool $this->db->noCache(function(Connection $db) use ($key, $value, $duration) { Db::insert($this->cacheTable, [ 'id' => $key, - 'expire' => $duration > 0 ? $duration + time() : 0, + 'expire' => $duration > 0 ? DateTimeHelper::currentTimeStamp() + $duration : 0, 'data' => new PdoValue($value, PDO::PARAM_LOB), ], $db); }); diff --git a/src/console/controllers/ProjectConfigController.php b/src/console/controllers/ProjectConfigController.php index 6f68a1bab13..d0f49ad983f 100644 --- a/src/console/controllers/ProjectConfigController.php +++ b/src/console/controllers/ProjectConfigController.php @@ -11,6 +11,7 @@ use craft\console\Controller; use craft\events\ConfigEvent; use craft\helpers\Console; +use craft\helpers\DateTimeHelper; use craft\helpers\FileHelper; use craft\helpers\ProjectConfig; use craft\services\ProjectConfig as ProjectConfigService; @@ -459,7 +460,7 @@ public function actionRebuild(): int */ public function actionTouch(): int { - $time = time(); + $time = DateTimeHelper::currentTimeStamp(); ProjectConfig::touch($time); $this->stdout("The dateModified value in project.yaml is now set to $time." . PHP_EOL, Console::FG_GREEN); return ExitCode::OK; diff --git a/src/helpers/Api.php b/src/helpers/Api.php index 8fdb0f31329..7b86157e5de 100644 --- a/src/helpers/Api.php +++ b/src/helpers/Api.php @@ -206,7 +206,7 @@ public static function processResponseHeaders(array $headers): void ) { $timestamp = $oldLicenseInfo[$handle]['timestamp']; } else { - $timestamp = time(); + $timestamp = DateTimeHelper::currentTimeStamp(); } } $licenseInfo[$handle] = [ diff --git a/src/helpers/DateTimeHelper.php b/src/helpers/DateTimeHelper.php index 8cf93c97cf3..0833bd98004 100644 --- a/src/helpers/DateTimeHelper.php +++ b/src/helpers/DateTimeHelper.php @@ -524,9 +524,7 @@ public static function currentUTCDateTime(): DateTime */ public static function currentTimeStamp(): int { - $date = static::currentUTCDateTime(); - - return $date->getTimestamp(); + return static::now()->getTimestamp(); } /** @@ -671,9 +669,7 @@ public static function isWithinLast(mixed $date, mixed $timeInterval): bool */ public static function isInThePast(mixed $date): bool { - $date = static::toDateTime($date); - - return $date->getTimestamp() < time(); + return static::toDateTime($date)->getTimestamp() < static::currentTimeStamp(); } /** diff --git a/src/helpers/ProjectConfig.php b/src/helpers/ProjectConfig.php index bd3c1495c6a..0e100026b4e 100644 --- a/src/helpers/ProjectConfig.php +++ b/src/helpers/ProjectConfig.php @@ -632,7 +632,7 @@ public static function diff(bool $invert = false): string public static function touch(?int $timestamp = null): void { if ($timestamp === null) { - $timestamp = time(); + $timestamp = DateTimeHelper::currentTimeStamp(); } $timestampLine = "dateModified: $timestamp\n"; diff --git a/src/models/ImageTransformIndex.php b/src/models/ImageTransformIndex.php index f8f1550d03d..46891be8100 100644 --- a/src/models/ImageTransformIndex.php +++ b/src/models/ImageTransformIndex.php @@ -9,6 +9,7 @@ use craft\base\imagetransforms\ImageTransformerInterface; use craft\base\Model; +use craft\helpers\DateTimeHelper; use craft\helpers\ImageTransforms; use craft\validators\DateTimeValidator; use DateTime; @@ -102,7 +103,7 @@ public function init(): void // Only respect inProgress if it's been less than 30 seconds since the last time the index was updated if ($this->inProgress) { - $duration = time() - ($this->dateUpdated?->getTimestamp() ?? 0); + $duration = DateTimeHelper::currentTimeStamp() - ($this->dateUpdated?->getTimestamp() ?? 0); if ($duration > 30) { $this->inProgress = false; } diff --git a/src/queue/Queue.php b/src/queue/Queue.php index 03772665123..d68a68cfaac 100644 --- a/src/queue/Queue.php +++ b/src/queue/Queue.php @@ -13,6 +13,7 @@ use craft\errors\MutexException; use craft\helpers\App; use craft\helpers\ArrayHelper; +use craft\helpers\DateTimeHelper; use craft\helpers\Db; use craft\helpers\Json; use craft\helpers\Queue as QueueHelper; @@ -381,7 +382,7 @@ public function setProgress(int $progress, ?string $label = null): void $this->_lock(function() use ($progress, $label) { $data = [ 'progress' => $progress, - 'timeUpdated' => time(), + 'timeUpdated' => DateTimeHelper::currentTimeStamp(), ]; if ($label !== null) { @@ -546,7 +547,9 @@ public function getJobInfo(?int $limit = null): array // then running first ->addOrderBy(new Expression("CASE WHEN $runningSql THEN 1 ELSE 0 END DESC")) // then earliest start time (now or timePushed + delay) - ->addOrderBy(new Expression('GREATEST(:time, [[timePushed]] + [[delay]]) ASC', [':time' => time()])) + ->addOrderBy(new Expression('GREATEST(:time, [[timePushed]] + [[delay]]) ASC', [ + ':time' => DateTimeHelper::currentTimeStamp(), + ])) // then priority and ID ->addOrderBy(['priority' => SORT_ASC, 'id' => SORT_ASC]) ->limit($limit); @@ -661,7 +664,7 @@ protected function pushMessage($message, $ttr, $delay, $priority): string 'channel' => $this->channel(), 'job' => $message, 'description' => $this->_jobDescription, - 'timePushed' => time(), + 'timePushed' => DateTimeHelper::currentTimeStamp(), 'ttr' => $ttr, 'delay' => $delay, 'priority' => $priority ?: 1024, @@ -733,7 +736,9 @@ protected function reserve(?string $id = null): ?array $payload = $this->db->usePrimary(function() use ($id) { $query = $this->_createJobQuery() ->andWhere(['fail' => false, 'timeUpdated' => null]) - ->andWhere('[[timePushed]] + [[delay]] <= :time', ['time' => time()]) + ->andWhere('[[timePushed]] + [[delay]] <= :time', [ + 'time' => DateTimeHelper::currentTimeStamp(), + ]) ->orderBy(['priority' => SORT_ASC, 'id' => SORT_ASC]) ->limit(1); @@ -793,8 +798,9 @@ private function _jobData(mixed $job): string */ private function _moveExpired(): void { - if ($this->_reserveTime !== time()) { - $this->_reserveTime = time(); + $timestamp = DateTimeHelper::currentTimeStamp(); + if ($this->_reserveTime !== $timestamp) { + $this->_reserveTime = $timestamp; $expiredIds = $this->db->usePrimary(function() { return (new Query()) @@ -856,7 +862,9 @@ private function _createWaitingJobQuery(): Query { return $this->_createJobQuery() ->andWhere(['fail' => false, 'timeUpdated' => null]) - ->andWhere('[[timePushed]] + [[delay]] <= :time', ['time' => time()]); + ->andWhere('[[timePushed]] + [[delay]] <= :time', [ + 'time' => DateTimeHelper::currentTimeStamp(), + ]); } /** @@ -868,7 +876,9 @@ private function _createDelayedJobQuery(): Query { return $this->_createJobQuery() ->andWhere(['fail' => false, 'timeUpdated' => null]) - ->andWhere('[[timePushed]] + [[delay]] > :time', ['time' => time()]); + ->andWhere('[[timePushed]] + [[delay]] > :time', [ + 'time' => DateTimeHelper::currentTimeStamp(), + ]); } /** diff --git a/src/services/Elements.php b/src/services/Elements.php index b89923fed02..c8bebf445bc 100644 --- a/src/services/Elements.php +++ b/src/services/Elements.php @@ -619,7 +619,7 @@ public function setCacheExpiryDate(DateTime $expiryDate): void return; } - $duration = $expiryDate->getTimestamp() - time(); + $duration = $expiryDate->getTimestamp() - DateTimeHelper::currentTimeStamp(); if ($duration > 0 && (!$this->_cacheDuration || $duration < $this->_cacheDuration)) { $this->_cacheDuration = $duration; diff --git a/src/services/Fields.php b/src/services/Fields.php index 7b4ea793c94..69dfc959285 100644 --- a/src/services/Fields.php +++ b/src/services/Fields.php @@ -50,6 +50,7 @@ use craft\fields\Users as UsersField; use craft\helpers\ArrayHelper; use craft\helpers\Component as ComponentHelper; +use craft\helpers\DateTimeHelper; use craft\helpers\Db; use craft\helpers\ElementHelper; use craft\helpers\FieldHelper; @@ -1840,7 +1841,7 @@ private function _backupFieldColumn(Connection $db, string $table, string $colum $shortTableName = Db::rawTableShortName($table); $schema = $db->getSchema(); $prefix = "{$shortTableName}_$column"; - $timestamp = time(); + $timestamp = DateTimeHelper::currentTimeStamp(); $n = 1; do { $suffix = $n === 1 ? '' : "_$n"; diff --git a/src/services/TemplateCaches.php b/src/services/TemplateCaches.php index 1f5a2b5d222..559b617fb90 100644 --- a/src/services/TemplateCaches.php +++ b/src/services/TemplateCaches.php @@ -245,7 +245,7 @@ public function endTemplateCache(string $key, bool $global, ?string $duration, m $expiration = (new DateTime($duration)); } if ($expiration !== null) { - $duration = DateTimeHelper::toDateTime($expiration)->getTimestamp() - time(); + $duration = DateTimeHelper::toDateTime($expiration)->getTimestamp() - DateTimeHelper::currentTimeStamp(); } if ($duration <= 0) { diff --git a/src/web/Response.php b/src/web/Response.php index 46e142d60a1..3d2ca29af42 100644 --- a/src/web/Response.php +++ b/src/web/Response.php @@ -9,6 +9,7 @@ use Craft; use craft\helpers\ArrayHelper; +use craft\helpers\DateTimeHelper; use craft\helpers\Session; use craft\helpers\UrlHelper; use Throwable; @@ -122,7 +123,8 @@ public function setCacheHeaders(int $duration = 31536000, bool $overwrite = true return $this; } - $this->setHeader('Expires', sprintf('%s GMT', gmdate('D, d M Y H:i:s', time() + $duration)), $overwrite); + $expires = DateTimeHelper::currentTimeStamp() + $duration; + $this->setHeader('Expires', sprintf('%s GMT', gmdate('D, d M Y H:i:s', $expires)), $overwrite); $this->setHeader('Pragma', 'cache', $overwrite); $this->setHeader('Cache-Control', "public, max-age=$duration", $overwrite); return $this; diff --git a/src/web/User.php b/src/web/User.php index 394a369106a..458ed8915fb 100644 --- a/src/web/User.php +++ b/src/web/User.php @@ -14,6 +14,7 @@ use craft\errors\UserLockedException; use craft\events\LoginFailureEvent; use craft\helpers\ConfigHelper; +use craft\helpers\DateTimeHelper; use craft\helpers\Db; use craft\helpers\Session as SessionHelper; use craft\helpers\UrlHelper; @@ -99,7 +100,7 @@ public function sendUsernameCookie(UserElement $user): void $cookie = new Cookie($this->usernameCookie); $cookie->value = $user->username; $seconds = ConfigHelper::durationInSeconds($generalConfig->rememberUsernameDuration); - $cookie->expire = time() + $seconds; + $cookie->expire = DateTimeHelper::currentTimeStamp() + $seconds; Craft::$app->getResponse()->getCookies()->add($cookie); } else { Craft::$app->getResponse()->getCookies()->remove(new Cookie($this->usernameCookie)); @@ -234,7 +235,7 @@ public function getRemainingSessionTime(): int } $expire = SessionHelper::get($this->authTimeoutParam); - $time = time(); + $time = DateTimeHelper::currentTimeStamp(); if ($expire !== null && $expire > $time) { return $expire - $time; @@ -285,7 +286,7 @@ public function getElevatedSessionTimeout(): int|false $expires = SessionHelper::get($this->elevatedSessionTimeoutParam); if ($expires !== null) { - $currentTime = time(); + $currentTime = DateTimeHelper::currentTimeStamp(); if ($expires > $currentTime) { return $expires - $currentTime; @@ -364,7 +365,7 @@ public function startElevatedSession(string $password): bool } // Set the elevated session expiration date - $timeout = time() + $generalConfig->elevatedSessionDuration; + $timeout = DateTimeHelper::currentTimeStamp() + $generalConfig->elevatedSessionDuration; SessionHelper::set($this->elevatedSessionTimeoutParam, $timeout); return true; diff --git a/src/web/twig/nodes/ExpiresNode.php b/src/web/twig/nodes/ExpiresNode.php index b47e7304989..55540f49c33 100644 --- a/src/web/twig/nodes/ExpiresNode.php +++ b/src/web/twig/nodes/ExpiresNode.php @@ -31,7 +31,11 @@ public function compile(Compiler $compiler): void ->write('$expiration = ') ->subcompile($expiration) ->raw(";\n") - ->write('$duration = \craft\helpers\DateTimeHelper::toDateTime($expiration)->getTimestamp() - time();'); + ->write(sprintf( + '$duration = %s::toDateTime($expiration)->getTimestamp() - %s::currentTimeStamp();', + DateTimeHelper::class, + DateTimeHelper::class, + )); } else { $duration = DateTimeHelper::relativeTimeToSeconds( $this->getAttribute('durationNum'), diff --git a/tests/unit/helpers/ProjectConfigHelperTest.php b/tests/unit/helpers/ProjectConfigHelperTest.php index fb8e7f0ed94..598020bac96 100644 --- a/tests/unit/helpers/ProjectConfigHelperTest.php +++ b/tests/unit/helpers/ProjectConfigHelperTest.php @@ -8,6 +8,7 @@ namespace crafttests\unit\helpers; use Craft; +use craft\helpers\DateTimeHelper; use craft\helpers\FileHelper; use craft\helpers\ProjectConfig as ProjectConfigHelper; use craft\helpers\StringHelper; @@ -68,10 +69,11 @@ public function testTouch(string $input, string $expected): void FileHelper::writeToFile($path, $input); // Test - $timestamp = time(); - $expected = str_replace('__TIMESTAMP__', (string)$timestamp, $expected); - ProjectConfigHelper::touch($timestamp); + DateTimeHelper::pause(); + $expected = str_replace('__TIMESTAMP__', (string)DateTimeHelper::currentTimeStamp(), $expected); + ProjectConfigHelper::touch(); self::assertSame($expected, file_get_contents($path)); + DateTimeHelper::resume(); // Put the old project.yaml back FileHelper::unlink($path); diff --git a/tests/unit/web/ResponseTest.php b/tests/unit/web/ResponseTest.php index e3e40f56849..d8451408fa2 100644 --- a/tests/unit/web/ResponseTest.php +++ b/tests/unit/web/ResponseTest.php @@ -8,6 +8,7 @@ namespace crafttests\unit\web; use Codeception\Test\Unit; +use craft\helpers\DateTimeHelper; use craft\test\TestCase; use craft\web\Response; @@ -47,15 +48,16 @@ public function testGetContentType(?string $expected, ?string $format = null, ?s */ public function testSetCacheHeaders(): void { + DateTimeHelper::pause(); $this->response->setCacheHeaders(); $headers = $this->response->getHeaders(); - $cacheTime = 31536000; // 1 year - self::assertSame('cache', $headers->get('Pragma')); self::assertSame('cache', $headers->get('Pragma')); self::assertSame('public, max-age=31536000', $headers->get('Cache-Control')); - self::assertSame(gmdate('D, d M Y H:i:s', time() + $cacheTime) . ' GMT', $headers->get('Expires')); + $expectedExpires = DateTimeHelper::currentTimeStamp() + $cacheTime; + self::assertSame(gmdate('D, d M Y H:i:s', $expectedExpires) . ' GMT', $headers->get('Expires')); + DateTimeHelper::resume(); } /** diff --git a/tests/unit/web/UserTest.php b/tests/unit/web/UserTest.php index 2df8d1a11ba..7d3b2eeb142 100644 --- a/tests/unit/web/UserTest.php +++ b/tests/unit/web/UserTest.php @@ -10,6 +10,7 @@ use Craft; use craft\elements\User as UserElement; use craft\errors\UserLockedException; +use craft\helpers\DateTimeHelper; use craft\helpers\Session; use craft\services\Config; use craft\test\TestCase; @@ -51,6 +52,8 @@ class UserTest extends TestCase */ public function testSendUsernameCookie(): void { + DateTimeHelper::pause(); + // Send the cookie with a hardcoded time value $this->config->getGeneral()->rememberUsernameDuration = 20; $this->user->sendUsernameCookie($this->userElement); @@ -59,7 +62,9 @@ public function testSendUsernameCookie(): void $cookie = Craft::$app->getResponse()->getCookies()->get($this->_getUsernameCookieName()); self::assertSame($this->userElement->username, $cookie->value); - self::assertSame(time() + 20, $cookie->expire); + self::assertSame(DateTimeHelper::currentTimeStamp() + 20, $cookie->expire); + + DateTimeHelper::resume(); } /** @@ -96,19 +101,21 @@ public function testGetRemainingSessionTime(): void } /** - * Test that the current time() is subtracted from the session expiration value. + * Test that the current timestamp is subtracted from the session expiration value. * We use a stub to ensure Craft::$app->getSession()->get() always returns 50 PHP sessions are difficult(ish) in testing. */ public function testGetRemainingSessionTimeMath(): void { + DateTimeHelper::pause(); $this->user->setIdentity($this->userElement); - // ensure Craft::$app->getSession()->get() always returns time() + 50. - $this->_sessionGetStub(time() + 50); + // ensure Craft::$app->getSession()->get() always returns the current timestamp + 50. + $this->_sessionGetStub(DateTimeHelper::currentTimeStamp() + 50); // Give a few seconds depending on how fast tests run. - self::assertContains(Craft::$app->getUser()->getRemainingSessionTime(), [48, 49, 50]); + self::assertEquals(Craft::$app->getUser()->getRemainingSessionTime(), 50); + DateTimeHelper::resume(); Session::reset(); } @@ -149,15 +156,17 @@ public function testGetHasElevatedSessionVoid(): void */ public function testGetHasElevatedSessionMath(): void { + DateTimeHelper::pause(); $this->user->setIdentity($this->userElement); - $this->_sessionGetStub(time() + 50); - self::assertEqualsWithDelta(50, $this->user->getElevatedSessionTimeout(), 2.0); + $this->_sessionGetStub(DateTimeHelper::currentTimeStamp() + 50); + self::assertEquals(50, $this->user->getElevatedSessionTimeout()); // If the session->get() return value is smaller than time 0 is returned - $this->_sessionGetStub(time() - 50); - self::assertEqualsWithDelta(0, $this->user->getElevatedSessionTimeout(), 2.0); + $this->_sessionGetStub(DateTimeHelper::currentTimeStamp() - 50); + self::assertEquals(0, $this->user->getElevatedSessionTimeout()); + DateTimeHelper::resume(); Session::reset(); } @@ -194,6 +203,8 @@ public function testGetElevatedSession(): void */ public function testStartElevatedSessionSetting(): void { + DateTimeHelper::pause(); + $passwordHash = Craft::$app->getSecurity()->hashPassword('this is not the correct password'); $this->user->setIdentity($this->userElement); @@ -201,7 +212,7 @@ public function testStartElevatedSessionSetting(): void $this->_passwordValidationStub(true); // Ensure a specific value is set to when setting a session - $this->_ensureSetSessionIsOfValue(time() + $this->config->getGeneral()->elevatedSessionDuration); + $this->_ensureSetSessionIsOfValue(DateTimeHelper::currentTimeStamp() + $this->config->getGeneral()->elevatedSessionDuration); // With a user and Craft::$app->getSecurity()->validatePassword() returning true it should return null because the current user doesnt exist or doesnt have a password self::assertFalse($this->user->startElevatedSession($passwordHash)); @@ -211,6 +222,8 @@ public function testStartElevatedSessionSetting(): void // With all the above and a current user with a password. Starting should work. self::assertTrue($this->user->startElevatedSession($passwordHash)); + + DateTimeHelper::resume(); } /** @@ -243,7 +256,7 @@ private function _ensureSetSessionIsOfValue(int $value) { $this->tester->mockCraftMethods('session', [ 'set' => function($name, $val) use ($value) { - self::assertEqualsWithDelta($value, $val, 1); + self::assertEquals($value, $val); }, ]); }