diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 25d2136001..404cc381e5 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -3610,7 +3610,7 @@ parameters: path: src/Controllers/Table/StructureController.php - - message: '#^Parameter \#2 \$entries of method PhpMyAdmin\\Tracking\\Tracking\:\:getDownloadInfoForExport\(\) expects array\\>, array\ given\.$#' + message: '#^Parameter \#3 \$entries of method PhpMyAdmin\\Tracking\\Tracking\:\:getDownloadInfoForExport\(\) expects array\\>, array\ given\.$#' identifier: argument.type count: 1 path: src/Controllers/Table/TrackingController.php @@ -16647,7 +16647,7 @@ parameters: Use dependency injection instead\.$# ''' identifier: staticMethod.deprecated - count: 4 + count: 3 path: tests/unit/Tracking/TrackerTest.php - @@ -16671,7 +16671,7 @@ parameters: Use dependency injection instead\.$# ''' identifier: staticMethod.deprecated - count: 3 + count: 2 path: tests/unit/Tracking/TrackingTest.php - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 19d70caa63..a1e9da5cc8 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -10642,7 +10642,6 @@ - selectedServer]]> @@ -10663,7 +10662,6 @@ - diff --git a/src/Controllers/Table/TrackingController.php b/src/Controllers/Table/TrackingController.php index a744006add..94c2e7d04c 100644 --- a/src/Controllers/Table/TrackingController.php +++ b/src/Controllers/Table/TrackingController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Controllers\Table; use DateTimeImmutable; +use PhpMyAdmin\Clock\Clock; use PhpMyAdmin\Controllers\InvocableController; use PhpMyAdmin\Core; use PhpMyAdmin\Current; @@ -130,7 +131,7 @@ final class TrackingController implements InvocableController // Export as file download if ($reportExportType === 'sqldumpfile') { - $downloadInfo = $this->tracking->getDownloadInfoForExport($tableParam, $entries); + $downloadInfo = $this->tracking->getDownloadInfoForExport(new Clock(), $tableParam, $entries); $response = $this->responseFactory->createResponse(); Core::downloadHeader($downloadInfo['filename'], 'text/x-sql', mb_strlen($downloadInfo['dump'])); diff --git a/src/Tracking/Tracker.php b/src/Tracking/Tracker.php index 8a7d886639..a271aa0f05 100644 --- a/src/Tracking/Tracker.php +++ b/src/Tracking/Tracker.php @@ -7,6 +7,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Tracking; +use PhpMyAdmin\Clock\Clock; use PhpMyAdmin\Config; use PhpMyAdmin\ConfigStorage\Features\TrackingFeature; use PhpMyAdmin\ConfigStorage\Relation; @@ -25,6 +26,7 @@ use PhpMyAdmin\SqlParser\Statements\RenameStatement; use PhpMyAdmin\SqlParser\Statements\TruncateStatement; use PhpMyAdmin\SqlParser\Statements\UpdateStatement; use PhpMyAdmin\Util; +use Psr\Clock\ClockInterface; use function in_array; use function preg_quote; @@ -134,9 +136,9 @@ class Tracker * * @return string Comment, contains date and username */ - public static function getLogComment(): string + public static function getLogComment(ClockInterface $clock): string { - $date = Util::date('Y-m-d H:i:s'); + $date = $clock->now()->format('Y-m-d H:i:s'); $user = preg_replace('/\s+/', ' ', Config::getInstance()->selectedServer['user']); return '# log ' . $date . ' ' . $user . "\n"; @@ -174,7 +176,8 @@ class Tracker $exportSqlPlugin->useSqlBackquotes(true); - $date = Util::date('Y-m-d H:i:s'); + $clock = new Clock(); + $date = $clock->now()->format('Y-m-d H:i:s'); // Get data definition snapshot of table @@ -199,18 +202,17 @@ class Tracker // Get DROP TABLE / DROP VIEW and CREATE TABLE SQL statements $createSql = ''; - if ($config->selectedServer['tracking_add_drop_table'] && ! $isView) { - $createSql .= self::getLogComment() + $createSql .= self::getLogComment($clock) . 'DROP TABLE IF EXISTS ' . Util::backquote($tableName) . ";\n"; } if ($config->selectedServer['tracking_add_drop_view'] && $isView) { - $createSql .= self::getLogComment() + $createSql .= self::getLogComment($clock) . 'DROP VIEW IF EXISTS ' . Util::backquote($tableName) . ";\n"; } - $createSql .= self::getLogComment() . $exportSqlPlugin->getTableDef($dbName, $tableName); + $createSql .= self::getLogComment($clock) . $exportSqlPlugin->getTableDef($dbName, $tableName); // Save version $trackingFeature = $relation->getRelationParameters()->trackingFeature; @@ -259,7 +261,8 @@ class Tracker $dbi = DatabaseInterface::getInstance(); $relation = new Relation($dbi); - $date = Util::date('Y-m-d H:i:s'); + $clock = new Clock(); + $date = $clock->now()->format('Y-m-d H:i:s'); $config = Config::getInstance(); if ($trackingSet === '') { @@ -267,12 +270,11 @@ class Tracker } $createSql = ''; - if ($config->selectedServer['tracking_add_drop_database']) { - $createSql .= self::getLogComment() . 'DROP DATABASE IF EXISTS ' . Util::backquote($dbName) . ";\n"; + $createSql .= self::getLogComment($clock) . 'DROP DATABASE IF EXISTS ' . Util::backquote($dbName) . ";\n"; } - $createSql .= self::getLogComment() . $query; + $createSql .= self::getLogComment($clock) . $query; $trackingFeature = $relation->getRelationParameters()->trackingFeature; if ($trackingFeature === null) { @@ -619,7 +621,8 @@ class Tracker default => '', }; - $date = Util::date('Y-m-d H:i:s'); + $clock = new Clock(); + $date = $clock->now()->format('Y-m-d H:i:s'); // Cut off `dbname`. from query $query = preg_replace( @@ -629,7 +632,7 @@ class Tracker ); // Add log information - $query = self::getLogComment() . $query; + $query = self::getLogComment($clock) . $query; $relation = new Relation($dbi); $trackingFeature = $relation->getRelationParameters()->trackingFeature; diff --git a/src/Tracking/Tracking.php b/src/Tracking/Tracking.php index 9eb144a135..a767aa5ff4 100644 --- a/src/Tracking/Tracking.php +++ b/src/Tracking/Tracking.php @@ -8,6 +8,7 @@ declare(strict_types=1); namespace PhpMyAdmin\Tracking; use DateTimeImmutable; +use PhpMyAdmin\Clock\Clock; use PhpMyAdmin\Config; use PhpMyAdmin\ConfigStorage\Relation; use PhpMyAdmin\Core; @@ -20,12 +21,12 @@ use PhpMyAdmin\SqlQueryForm; use PhpMyAdmin\Template; use PhpMyAdmin\Url; use PhpMyAdmin\Util; +use Psr\Clock\ClockInterface; use Webmozart\Assert\Assert; use function __; use function array_merge; use function array_multisort; -use function date; use function explode; use function htmlspecialchars; use function in_array; @@ -616,7 +617,8 @@ class Tracking $logSchemaEntries = explode('# log ', (string) $mixed['schema_sql']); $logDataEntries = explode('# log ', (string) $mixed['data_sql']); - $ddlDateFrom = $date = Util::date('Y-m-d H:i:s'); + $clock = new Clock(); + $ddlDateFrom = $date = $clock->now()->format('Y-m-d H:i:s'); $ddlog = []; $firstIteration = true; @@ -829,14 +831,14 @@ class Tracking * @return array * @psalm-return array{filename: non-empty-string, dump: non-empty-string} */ - public function getDownloadInfoForExport(string $table, array $entries): array + public function getDownloadInfoForExport(ClockInterface $clock, string $table, array $entries): array { ini_set('url_rewriter.tags', ''); // Replace all multiple whitespaces by a single space $table = htmlspecialchars((string) preg_replace('/\s+/', ' ', $table)); $dump = '# ' . sprintf(__('Tracking report for table `%s`'), $table) . "\n" - . '# ' . date('Y-m-d H:i:sP') . "\n"; + . '# ' . $clock->now()->format('Y-m-d H:i:sP') . "\n"; foreach ($entries as $entry) { $dump .= $entry['statement']; } diff --git a/src/Util.php b/src/Util.php index 98c99cb91f..3b4520e9e2 100644 --- a/src/Util.php +++ b/src/Util.php @@ -26,7 +26,6 @@ use function bin2hex; use function chr; use function count; use function ctype_digit; -use function date; use function decbin; use function explode; use function extension_loaded; @@ -1558,16 +1557,6 @@ class Util return $asHex ? bin2hex($result) : $result; } - /** - * Wrapper around PHP date function - * - * @param string $format Date format string - */ - public static function date(string $format): string - { - return date($format); - } - /** * Wrapper around php's set_time_limit * diff --git a/tests/unit/Tracking/TrackerTest.php b/tests/unit/Tracking/TrackerTest.php index d233825a1f..cf0d1dc576 100644 --- a/tests/unit/Tracking/TrackerTest.php +++ b/tests/unit/Tracking/TrackerTest.php @@ -12,9 +12,9 @@ use PhpMyAdmin\Current; use PhpMyAdmin\Dbal\ConnectionType; use PhpMyAdmin\Dbal\DatabaseInterface; use PhpMyAdmin\Tests\AbstractTestCase; +use PhpMyAdmin\Tests\Clock\MockClock; use PhpMyAdmin\Tests\Stubs\DummyResult; use PhpMyAdmin\Tracking\Tracker; -use PhpMyAdmin\Util; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use ReflectionMethod; @@ -136,12 +136,12 @@ class TrackerTest extends AbstractTestCase */ public function testGetLogComment(): void { - $date = Util::date('Y-m-d H:i:s'); - Config::getInstance()->selectedServer['user'] = 'pma_test_user'; + $config = Config::$instance = new Config(); + $config->selectedServer['user'] = 'pma_test_user'; self::assertSame( - '# log ' . $date . " pma_test_user\n", - Tracker::getLogComment(), + "# log 2015-10-21 05:28:00 pma_test_user\n", + Tracker::getLogComment(MockClock::from('2015-10-21T05:28:00-02:00')), ); } diff --git a/tests/unit/Tracking/TrackingTest.php b/tests/unit/Tracking/TrackingTest.php index 97429e78a0..818da87645 100644 --- a/tests/unit/Tracking/TrackingTest.php +++ b/tests/unit/Tracking/TrackingTest.php @@ -14,6 +14,7 @@ use PhpMyAdmin\Dbal\DatabaseInterface; use PhpMyAdmin\SqlQueryForm; use PhpMyAdmin\Template; use PhpMyAdmin\Tests\AbstractTestCase; +use PhpMyAdmin\Tests\Clock\MockClock; use PhpMyAdmin\Tests\Stubs\DummyResult; use PhpMyAdmin\Tracking\LogType; use PhpMyAdmin\Tracking\TrackedData; @@ -21,14 +22,12 @@ use PhpMyAdmin\Tracking\TrackedDataType; use PhpMyAdmin\Tracking\Tracking; use PhpMyAdmin\Tracking\TrackingChecker; use PhpMyAdmin\Url; -use PhpMyAdmin\Util; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use ReflectionProperty; use function __; use function _pgettext; -use function date; use function htmlspecialchars; use function ini_get; use function ini_restore; @@ -558,9 +557,10 @@ final class TrackingTest extends AbstractTestCase ini_set('url_rewriter.tags', 'a=href,area=href,frame=src,form=,fieldset='); $entries = [['statement' => 'first statement'], ['statement' => 'second statement']]; $expectedDump = '# Tracking report for table `test> table`' . "\n" - . '# ' . date('Y-m-d H:i:sP') . "\n" + . '# 2015-10-21 05:28:00-02:00' . "\n" . 'first statementsecond statement'; - $actual = $tracking->getDownloadInfoForExport('test> table', $entries); + $clock = MockClock::from('2015-10-21T05:28:00-02:00'); + $actual = $tracking->getDownloadInfoForExport($clock, 'test> table', $entries); self::assertSame('log_test> table.sql', $actual['filename']); self::assertSame($expectedDump, $actual['dump']); self::assertSame('', ini_get('url_rewriter.tags')); @@ -600,61 +600,35 @@ final class TrackingTest extends AbstractTestCase self::assertTrue($tracking->deleteTracking('testdb', 'testtable')); } - /** - * Test for changeTrackingData() - */ public function testChangeTrackingData(): void { - $dbi = $this->getMockBuilder(DatabaseInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $sqlQuery1 = 'UPDATE `pmadb`.`tracking`' . - " SET `schema_sql` = '# new_data_processed'" . - " WHERE `db_name` = 'pma_db'" . - " AND `table_name` = 'pma_table'" . - " AND `version` = '1.0'"; - - $date = Util::date('Y-m-d H:i:s'); - $newData = [ - ['date' => $date, 'username' => 'user1', 'statement' => 'test_statement1'], - ['date' => $date, 'username' => 'user2', 'statement' => 'test_statement2'], + ['date' => '2026-02-25 19:29:34', 'username' => 'user1', 'statement' => 'test_statement1'], + ['date' => '2026-02-25 19:29:34', 'username' => 'user2', 'statement' => 'test_statement2'], ]; - $sqlQuery2 = 'UPDATE `pmadb`.`tracking`' . - " SET `data_sql` = '# log " . $date . " user1test_statement1\n" . - '# log ' . $date . " user2test_statement2\n'" . - " WHERE `db_name` = 'pma_db'" . - " AND `table_name` = 'pma_table'" . - " AND `version` = '1.0'"; - - $resultStub1 = self::createMock(DummyResult::class); - $resultStub2 = self::createMock(DummyResult::class); - - $dbi->method('queryAsControlUser') - ->willReturnMap([[$sqlQuery1, $resultStub1], [$sqlQuery2, $resultStub2]]); - - $dbi->expects(self::any())->method('quoteString') - ->willReturnCallback(static fn (string $string): string => "'" . $string . "'"); + $dbiDummy = $this->createDbiDummy(); + $dbiDummy->addResult( + 'UPDATE `pmadb`.`tracking`' . + " SET `data_sql` = '# log 2026-02-25 19:29:34 user1test_statement1 " . + "# log 2026-02-25 19:29:34 user2test_statement2 '" . + " WHERE `db_name` = 'pma_db'" . + " AND `table_name` = 'pma_table'" . + " AND `version` = '1.0'", + true, + ); + $dbi = $this->createDatabaseInterface($dbiDummy); $tracking = new Tracking( self::createStub(SqlQueryForm::class), self::createStub(Template::class), - new Relation(DatabaseInterface::getInstance()), + new Relation($dbi, new Config()), $dbi, self::createStub(TrackingChecker::class), ); - self::assertTrue( - $tracking->changeTrackingData( - 'pma_db', - 'pma_table', - '1.0', - TrackedDataType::DML, - $newData, - ), - ); + self::assertTrue($tracking->changeTrackingData('pma_db', 'pma_table', '1.0', TrackedDataType::DML, $newData)); + $dbiDummy->assertAllQueriesConsumed(); } /**