From 378db014b6854194d21948912689170f4245ebc9 Mon Sep 17 00:00:00 2001 From: Michael Vasseur <14887731+vmcj@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:29:36 +0200 Subject: [PATCH 1/3] Autocheck for new DOMjudge releases Discussed during the hackathon in 2023. We allow admins to toggle an autoquery to domjudge.org for 2 reasons: - Alerting users that a new version might exist, helping us in case of security releases. - Giving a gentle nudge for people to upgrade making support easier. - Getting some information on what installations are out there. --- etc/db-config.yaml | 5 +++ webapp/composer.json | 3 +- webapp/composer.lock | 58 ++++++++++++++++++++++++- webapp/src/Service/DOMJudgeService.php | 59 ++++++++++++++++++++++++++ webapp/src/Twig/TwigExtension.php | 1 + webapp/templates/jury/menu.html.twig | 7 +++ 6 files changed, 130 insertions(+), 3 deletions(-) diff --git a/etc/db-config.yaml b/etc/db-config.yaml index 665a48a6e8..99f7a33ac3 100644 --- a/etc/db-config.yaml +++ b/etc/db-config.yaml @@ -392,6 +392,11 @@ description: Time in seconds after an external contest source reader last checked in before showing its status as `critical`. regex: /^\d+$/ error_message: A non-negative number is required. + - name: check_new_version + type: bool + default_value: false + public: false + description: Automatically check for new DOMjudge release? - name: adminer_enabled type: bool default_value: false diff --git a/webapp/composer.json b/webapp/composer.json index b3930db922..00428b7d1d 100644 --- a/webapp/composer.json +++ b/webapp/composer.json @@ -111,7 +111,8 @@ "twig/extra-bundle": "^3.5", "twig/markdown-extra": "^3.5", "twig/string-extra": "^3.5", - "twig/twig": "^3.6" + "twig/twig": "^3.6", + "z4kn4fein/php-semver": "^3.0" }, "require-dev": { "ext-dom": "*", diff --git a/webapp/composer.lock b/webapp/composer.lock index c6b0c1bcbb..683c468a70 100644 --- a/webapp/composer.lock +++ b/webapp/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "88f6e44e48730e8efd3a8ccaeefb5ed7", + "content-hash": "9c5bc4f66f4633c676f69fc52096236b", "packages": [ { "name": "apalfrey/select2-bootstrap-5-theme", @@ -10538,6 +10538,60 @@ }, "time": "2022-01-30T20:08:53+00:00" }, + { + "name": "z4kn4fein/php-semver", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/z4kn4fein/php-semver.git", + "reference": "049a1d81e92235c8b3c9ab30a96fcbaa929a266d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/z4kn4fein/php-semver/zipball/049a1d81e92235c8b3c9ab30a96fcbaa929a266d", + "reference": "049a1d81e92235c8b3c9ab30a96fcbaa929a266d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.0", + "phpstan/phpstan": "^1.0", + "phpunit/phpunit": "^10" + }, + "type": "library", + "autoload": { + "psr-4": { + "z4kn4fein\\SemVer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Peter Csajtai", + "email": "peter.csajtai@outlook.com" + } + ], + "description": "Semantic Versioning library for PHP. It implements the full semantic version 2.0.0 specification and provides ability to parse, compare, and increment semantic versions along with validation against constraints.", + "homepage": "https://github.com/z4kn4fein/php-semver", + "keywords": [ + "comparison", + "semantic", + "semver", + "validation", + "version", + "versioning" + ], + "support": { + "issues": "https://github.com/z4kn4fein/php-semver/issues", + "source": "https://github.com/z4kn4fein/php-semver/tree/v3.0.0" + }, + "time": "2024-04-01T16:17:27+00:00" + }, { "name": "zircote/swagger-php", "version": "5.1.3", @@ -12873,5 +12927,5 @@ "platform-overrides": { "php": "8.1.0" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.2.0" } diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index 8571dfa81f..01f3d30b8c 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -38,6 +38,7 @@ use Doctrine\ORM\NoResultException; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; +use Exception; use InvalidArgumentException; use Psr\Log\LoggerInterface; use ReflectionClass; @@ -63,6 +64,7 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; use Twig\Environment; +use z4kn4fein\SemVer\Version; use ZipArchive; class DOMJudgeService @@ -107,6 +109,8 @@ public function __construct( protected string $projectDir, #[Autowire('%domjudge.vendordir%')] protected string $vendorDir, + #[Autowire('%domjudge.version%')] + protected readonly string $domjudgeVersion, ) {} /** @@ -1671,4 +1675,59 @@ public function getAllowedLanguagesForContest(?Contest $contest) : array { ->getQuery() ->getResult(); } + + public function checkNewVersion(): string|false { + if (!$this->config->get('check_new_version')) { + return false; + } + $versionLocalString = explode("/", str_replace("DEV", "-prerelease", $this->domjudgeVersion))[0]; + $versionLocal = Version::parse($versionLocalString, false); + $versionUrl = 'https://versions.domjudge.org'; + $options = ['http' => ['method' => 'GET', 'header' => "User-Agent: tarball/" . $versionLocalString . "\r\n"]]; + $context = stream_context_create($options); + $response = @file_get_contents($versionUrl, false, $context); + if ($response === false) { + return false; + } + $versions = json_decode($response, true); + /* Steer towards to the latest patch first + * the user can see on the website if there is a new Major/minor themselves + * otherwise the latest minor, or Major release. So the user might make the upgrade path: + * DJ6.0.0 -> DJ6.0.6 -> DJ6.6.0 -> DJ9.0.0 instead of + * -> DJ6.0.[1..6] -> DJ6.[1..6] -> DJ[7..9].0.0 + */ + $latestPatchString = $versionLocal; + if (isset($versions[$versionLocal->getMajor()][$versionLocal->getMinor()])) { + $latestPatchString = Version::rsortString($versions[$versionLocal->getMajor()][$versionLocal->getMinor()])[0]; + $latestPatch = Version::parse($latestPatchString); + if (Version::compare($versionLocal, $latestPatch) < 0) { + return $latestPatchString; + } + } + $latestMinorString = $versionLocal; + if (isset($versions[$versionLocal->getMajor()])) { + $highestMinorInMajor = array_keys($versions[$versionLocal->getMajor()]); + rsort($highestMinorInMajor); + $latestMinorString = Version::rsortString($versions[$versionLocal->getMajor()][$highestMinorInMajor[0]])[0]; + $latestMinor = Version::parse($latestMinorString); + if (Version::compare($versionLocal, $latestMinor) < 0) { + return $latestMinorString; + } + } + $latestMajorString = $versionLocal; + try { + $highestMajor = array_keys($versions); + rsort($highestMajor); + $highestMinorInMajor = array_keys($versions[$highestMajor[0]]); + rsort($highestMinorInMajor); + $latestMajorString = Version::rsortString($versions[$highestMajor[0]][$highestMinorInMajor[0]])[0]; + $latestMajor = Version::parse($latestMajorString); + if (Version::compare($versionLocal, $latestMajor) < 0) { + return $latestMajorString; + } + } catch (Exception $e) { + return false; + } + return false; + } } diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index aed9adf2ae..2dd3ebc81b 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -159,6 +159,7 @@ public function getGlobals(): array 'doc_links' => $this->dj->getDocLinks(), 'allow_registration' => $selfRegistrationCategoriesCount !== 0, 'enable_ranking' => $this->config->get('enable_ranking'), + 'new_version_available' => $this->dj->checkNewVersion(), 'editor_themes' => [ 'vs' => ['name' => 'Visual Studio (light)'], 'vs-dark' => ['name' => 'Visual Studio (dark)'], diff --git a/webapp/templates/jury/menu.html.twig b/webapp/templates/jury/menu.html.twig index 12bddba889..1f8a5d4353 100644 --- a/webapp/templates/jury/menu.html.twig +++ b/webapp/templates/jury/menu.html.twig @@ -93,6 +93,13 @@ {% endif %} + {% if new_version_available %} + + {% endif %}