diff --git a/etc/db-config.yaml b/etc/db-config.yaml index 665a48a6e8..41e45a15d7 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 and notify for new DOMjudge releases? - name: adminer_enabled type: bool default_value: false diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index 8571dfa81f..3bc82a8936 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; @@ -107,6 +108,8 @@ public function __construct( protected string $projectDir, #[Autowire('%domjudge.vendordir%')] protected string $vendorDir, + #[Autowire('%domjudge.version%')] + protected readonly string $domjudgeVersion, ) {} /** @@ -1671,4 +1674,42 @@ public function getAllowedLanguagesForContest(?Contest $contest) : array { ->getQuery() ->getResult(); } + + public function checkNewVersion(): string|false { + if (!$this->config->get('check_new_version')) { + return false; + } + $versionLocalString = explode("/", $this->domjudgeVersion)[0]; + $patch = "/" . substr($this->domjudgeVersion, 0, strrpos($this->domjudgeVersion, '.')) . ".\d/"; + $minor = "/" . substr($this->domjudgeVersion, 0, strpos($this->domjudgeVersion, '.')) . ".\d.\d/"; + $major = "/\d.\d.\d/"; + + $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 + */ + $newer_releases = []; + foreach ([$patch, $minor, $major] as $regex) { + foreach ($versions as $release) { + if (preg_match($regex, $release)) { + $newer_releases[] = $release; + } + } + if (count($newer_releases) > 0) { + natsort($newer_releases); + return end($newer_releases); + } + } + 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 %}