diff --git a/.github/jobs/configure-checks/all.bats b/.github/jobs/configure-checks/all.bats index 8f82797f8a..666d391564 100755 --- a/.github/jobs/configure-checks/all.bats +++ b/.github/jobs/configure-checks/all.bats @@ -67,9 +67,9 @@ repo-remove () { assert_line "checking for gcc... no" assert_line "checking for cc... no" assert_line "checking for cl.exe... no" - assert_line "configure: error: in \`${test_path}':" + assert_regex "configure: error: in .${test_path}':" assert_line 'configure: error: no acceptable C compiler found in $PATH' - assert_line "See \`config.log' for more details" + assert_regex "See [\`']config.log' for more details" } compiler_assertions () { @@ -111,6 +111,10 @@ compile_assertions_finished () { } @test "Install GNU C only" { + if [ "$distro_id" = "ID=fedora" ]; then + # Fedora ships with a gcc with enough C++ support + skip + fi repo-remove clang g++ repo-install gcc libcgroup-dev compiler_assertions gcc '' diff --git a/.github/workflows/autoconf-check-different-distro.yml b/.github/workflows/autoconf-check-different-distro.yml index 8216eb3a26..9db54dfaa8 100644 --- a/.github/workflows/autoconf-check-different-distro.yml +++ b/.github/workflows/autoconf-check-different-distro.yml @@ -20,7 +20,7 @@ jobs: image: ${{ matrix.os }}:${{ matrix.version }} steps: - name: Install git so we get the .github directory - run: dnf install -y git + run: dnf install -y git composer - uses: actions/checkout@v4 - name: Setup image and run bats tests run: .github/jobs/configure-checks/setup_configure_image.sh diff --git a/.github/workflows/autoconf-check.yml b/.github/workflows/autoconf-check.yml index 016ec024f7..58f6f691d1 100644 --- a/.github/workflows/autoconf-check.yml +++ b/.github/workflows/autoconf-check.yml @@ -32,7 +32,7 @@ jobs: image: ${{ matrix.os }}:${{ matrix.version }} steps: - name: Install git so we get the .github directory - run: apt-get update; apt-get install -y git + run: apt-get update; apt-get install -y git composer - uses: actions/checkout@v4 - name: Setup image and run bats tests run: .github/jobs/configure-checks/setup_configure_image.sh diff --git a/.github/workflows/mayhem-api-template.yml b/.github/workflows/mayhem-api-template.yml deleted file mode 100644 index 9556bb3718..0000000000 --- a/.github/workflows/mayhem-api-template.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: "Mayhem API analysis (Template)" - -on: - workflow_call: - inputs: - version: - required: true - type: string - duration: - required: true - type: string - secrets: - MAPI_TOKEN: - required: true - -jobs: - mayhem: - name: Mayhem API analysis - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - env: - DB_DATABASE: domjudge - DB_USER: user - DB_PASSWORD: password - steps: - - uses: actions/checkout@v4 - - - name: Install DOMjudge - run: .github/jobs/baseinstall.sh ${{ inputs.version }} - - - name: Dump the OpenAPI - run: .github/jobs/getapi.sh - - - uses: actions/upload-artifact@v3 - if: ${{ inputs.version == 'guest' }} - with: - name: all-apispec - path: | - /home/runner/work/domjudge/domjudge/openapi.json - - - name: Mayhem for API - uses: ForAllSecure/mapi-action@v1 - if: ${{ inputs.version == 'guest' }} - continue-on-error: true - with: - mapi-token: ${{ secrets.MAPI_TOKEN }} - api-url: http://localhost/domjudge - api-spec: http://localhost/domjudge/api/doc.json # swagger/openAPI doc hosted here - duration: "auto" # Only spend time if we need to recheck issues from last time or find issues - sarif-report: mapi.sarif - run-args: | - --config - .github/jobs/data/mapi.config - --ignore-endpoint - ".*strict=true.*" - --ignore-endpoint - ".*strict=True.*" - - - name: Mayhem for API (For application role) - uses: ForAllSecure/mapi-action@v1 - if: ${{ inputs.version != 'guest' }} - continue-on-error: true - with: - mapi-token: ${{ secrets.MAPI_TOKEN }} - target: domjudge-${{ inputs.version }} - api-url: http://localhost/domjudge - api-spec: http://localhost/domjudge/api/doc.json # swagger/openAPI doc hosted here - duration: "${{ inputs.duration }}" - sarif-report: mapi.sarif - run-args: | - --config - .github/jobs/data/mapi.config - --basic-auth - admin:password - --ignore-endpoint - ".*strict=true.*" - --ignore-endpoint - ".*strict=True.*" - - - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: mapi.sarif - - - uses: actions/upload-artifact@v3 - with: - name: ${{ inputs.version }}-logs - path: | - /var/log/nginx - /opt/domjudge/domserver/webapp/var/log/*.log diff --git a/.github/workflows/mayhem-daily.yml b/.github/workflows/mayhem-daily.yml deleted file mode 100644 index 2118bf6920..0000000000 --- a/.github/workflows/mayhem-daily.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: "Mayhem API daily (admin role only)" - -on: - schedule: - - cron: '0 23 * * *' - -jobs: - mayhem-template: - uses: ./.github/workflows/mayhem-api-template.yml - with: - version: "admin" - duration: "auto" - secrets: - MAPI_TOKEN: ${{ secrets.MAPI_TOKEN }} diff --git a/.github/workflows/mayhem-weekly.yml b/.github/workflows/mayhem-weekly.yml deleted file mode 100644 index 71cc90ecba..0000000000 --- a/.github/workflows/mayhem-weekly.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: "Mayhem API weekly (all roles)" - -on: - schedule: - - cron: '0 23 * * 0' - -jobs: - mayhem-template: - strategy: - matrix: - include: - - version: "team" - duration: "5m" - - version: "guest" - duration: "auto" - - version: "jury" - duration: "5min" - - version: "admin" - duration: "10m" - uses: ./.github/workflows/mayhem-api-template.yml - with: - version: "${{ matrix.version }}" - duration: "${{ matrix.duration }}" - secrets: - MAPI_TOKEN: ${{ secrets.MAPI_TOKEN }} diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index d2577b6018..94197d7421 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -16,8 +16,9 @@ jobs: - uses: actions/checkout@v4 - name: Install DOMjudge run: .github/jobs/baseinstall.sh admin - - uses: php-actions/phpstan@v3 + - uses: php-actions/phpstan@v3.0.2 with: configuration: phpstan.dist.neon path: webapp/src webapp/tests php_extensions: gd intl mysqli pcntl zip + version: composer diff --git a/ChangeLog b/ChangeLog index 30ebc035f2..841b02c982 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,14 @@ DOMjudge Programming Contest Judging System +Version 8.3.2 - 9 July 2025 +--------------------------- + - Expose samples/problemset after the contest start + +Version 8.3.1 - 13 September 2024 +--------------------------------- + - Create autoload_runtime.php as normal user to prevent a composer warning. + - Fix saving new problems with problem statement from web UI. + Version 8.3.0 - 31 May 2024 --------------------------- - [security] Close metadata file descriptor for the child in runguard. diff --git a/Makefile b/Makefile index df4f2069a0..1f8bea7047 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ endif domserver: domserver-configure paths.mk config judgehost: judgehost-configure paths.mk config docs: paths.mk config -install-domserver: domserver composer-dump-autoload domserver-create-dirs +install-domserver: domserver domserver-create-dirs install-judgehost: judgehost judgehost-create-dirs install-docs: docs-create-dirs dist: configure composer-dependencies @@ -76,12 +76,6 @@ endif composer-dependencies-dev: composer $(subst 1,-q,$(QUIET)) install --prefer-dist --no-scripts --no-plugins -# Dump autoload dependencies (including plugins) -# This is needed since symfony/runtime is a Composer plugin that runs while dumping -# the autoload file -composer-dump-autoload: - composer $(subst 1,-q,$(QUIET)) dump-autoload -o -a - composer-dump-autoload-dev: composer $(subst 1,-q,$(QUIET)) dump-autoload @@ -101,7 +95,7 @@ build-scripts: # List of SUBDIRS for recursive targets: build: SUBDIRS= lib misc-tools -domserver: SUBDIRS=etc sql misc-tools webapp +domserver: SUBDIRS=etc lib sql misc-tools webapp install-domserver: SUBDIRS=etc lib sql misc-tools webapp example_problems judgehost: SUBDIRS=etc judge misc-tools install-judgehost: SUBDIRS=etc lib judge misc-tools @@ -222,7 +216,7 @@ webapp/.env.local: # symlinks where necessary to let it work from the source tree. # This stuff is a hack! maintainer-install: inplace-install composer-dump-autoload-dev -inplace-install: build composer-dump-autoload domserver-create-dirs judgehost-create-dirs +inplace-install: build domserver-create-dirs judgehost-create-dirs inplace-install-l: # Replace libjudgedir with symlink to prevent lots of symlinks: -rmdir $(judgehost_libjudgedir) @@ -341,5 +335,5 @@ clean-autoconf: $(addprefix inplace-,conf conf-common install uninstall) \ $(addprefix maintainer-,conf install) clean-autoconf config distdocs \ composer-dependencies composer-dependencies-dev \ - composer-dump-autoload composer-dump-autoload-dev \ + composer-dump-autoload-dev \ coverity-conf coverity-build diff --git a/README.md b/README.md index 5499da3e0a..3361eb3ffc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ DOMjudge [![Coverity Scan Status](https://img.shields.io/coverity/scan/671.svg)](https://scan.coverity.com/projects/domjudge) [![CodeQL alerts](https://github.com/DOMjudge/domjudge/actions/workflows/codeql-analysis.yml/badge.svg?branch=main&event=push)](https://github.com/DOMjudge/domjudge/actions/workflows/codeql-analysis.yml) -This is the Programming Contest Jury System "DOMjudge" version 8.3.0 +This is the Programming Contest Jury System "DOMjudge" version 8.3.2 DOMjudge is a system for running a programming contest, like the ICPC regional and world championship programming contests. diff --git a/doc/manual/config-advanced.rst b/doc/manual/config-advanced.rst index 72b8016740..96150a8aeb 100644 --- a/doc/manual/config-advanced.rst +++ b/doc/manual/config-advanced.rst @@ -238,7 +238,7 @@ selected in the ``special_run`` and/or ``special_compare`` fields of the problem (an empty value means that the default run and compare scripts should be used; the defaults can be set in the global configuration settings). When creating custom run and compare -programs, we recommend re-using wrapper scripts that handle the +programs, we recommend reusing wrapper scripts that handle the tedious, standard part. See the boolfind example for details. Compare programs diff --git a/doc/manual/import.rst b/doc/manual/import.rst index 2b685943b4..6994210f16 100644 --- a/doc/manual/import.rst +++ b/doc/manual/import.rst @@ -405,11 +405,11 @@ and click `Import`. To import the file using the API run the following commands:: - http --check-status -b -f POST "/contests//problems" data@problems.yaml + http --check-status -b -f POST "/contests//problems/add-data" data@problems.yaml To import the file using the CLI run the following command:: - /bin/console api:call -m POST -f data=problems.yaml contests//problems + /bin/console api:call -m POST -f data=problems.yaml contests//problems/add-data Replace ```` with the contest ID that was returned when importing the contest metadata. diff --git a/doc/manual/install-judgehost.rst b/doc/manual/install-judgehost.rst index fafe28c7f8..454e38246f 100644 --- a/doc/manual/install-judgehost.rst +++ b/doc/manual/install-judgehost.rst @@ -68,6 +68,15 @@ example to install DOMjudge in the directory ``domjudge`` under `/opt`:: make judgehost sudo make install-judgehost +Example service files for the judgehost and the judgedaemon are provided in +``judge/create-cgroups.service`` and ``judge/domjudge-judgedaemon@.service``. The rest of the manual assumes you install those +in a location which is picked up by ``systemd``, for example ``/etc/systemd/system``. + +.. parsed-literal:: + + cp judge/domjudge-judgedaemon@.service /etc/systemd/system/ + cp judge/create-cgroups.service /etc/systemd/system/ + The judgedaemon can be run on various hardware configurations; - A virtual machine, typically these have 1 or 2 cores and no hyperthreading, because the kernel will schedule its own tasks on CPU 0, we advice CPU 1, @@ -172,7 +181,8 @@ any other tasks on the same CPU core the judgedaemon is using: On modern distros (e.g. Debian bullseye and Ubuntu Jammy Jellyfish) which have cgroup v2 enabled by default, you need to add ``systemd.unified_cgroup_hierarchy=0`` -as well. Then run ``update-grub`` and reboot. +as well. If you are running systemd v257, you also need to add `SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1`. +Then run ``update-grub`` and reboot. After rebooting check that ``/proc/cmdline`` actually contains the added kernel options. On VM hosting providers such as Google Cloud or DigitalOcean, ``GRUB_CMDLINE_LINUX_DEFAULT`` may be overwritten diff --git a/doc/manual/problem-format.rst b/doc/manual/problem-format.rst index e9f6cbc300..4439cedc2d 100644 --- a/doc/manual/problem-format.rst +++ b/doc/manual/problem-format.rst @@ -54,4 +54,4 @@ problem by uploading a zip file that contains only testcase files. Any jury solutions present will be automatically submitted when ``allow_submit`` is ``1`` and there's a team associated with the uploading user. -.. _ICPC problem package specification: https://icpc.io/problem-package-format/spec/problem_package_format +.. _ICPC problem package specification: https://icpc.io/problem-package-format/spec/legacy-icpc diff --git a/gitlab/ci/template.yml b/gitlab/ci/template.yml index 5b954aff68..1c75a358a9 100644 --- a/gitlab/ci/template.yml +++ b/gitlab/ci/template.yml @@ -36,7 +36,6 @@ - /bin/true services: - name: mysql - command: ["--mysql-native-password", "--authentication_policy=mysql_native_password"] alias: sqlserver .mariadb_job: diff --git a/lib/Makefile b/lib/Makefile index 5f2e2dea76..24cd0f82de 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -1,6 +1,9 @@ ifndef TOPDIR TOPDIR=.. endif + +REC_TARGETS = domserver + include $(TOPDIR)/Makefile.global OBJECTS = $(addsuffix $(OBJEXT),lib.error lib.misc) @@ -31,3 +34,5 @@ install-domserver: install-judgehost: $(INSTALL_DATA) -t $(DESTDIR)$(judgehost_libdir) *.php *.sh $(INSTALL_PROG) -t $(DESTDIR)$(judgehost_libdir) alert + +domserver: SUBDIRS=vendor diff --git a/lib/vendor/Makefile b/lib/vendor/Makefile new file mode 100644 index 0000000000..b6ee26a361 --- /dev/null +++ b/lib/vendor/Makefile @@ -0,0 +1,12 @@ +ifndef TOPDIR +TOPDIR=../.. +endif +include $(TOPDIR)/Makefile.global + +clean-l: + rm -f autoload_runtime.php + +autoload_runtime.php: + composer $(subst 1,-q,$(QUIET)) dump-autoload -o -a -d $(TOPDIR) + +domserver: autoload_runtime.php diff --git a/misc-tools/dj_make_chroot.in b/misc-tools/dj_make_chroot.in index aa9f80e772..3d03f9935c 100755 --- a/misc-tools/dj_make_chroot.in +++ b/misc-tools/dj_make_chroot.in @@ -178,7 +178,7 @@ if [ "$DISTRO" = 'Debian' ]; then REMOVEDEBS="" # Which debootstrap package to install on non-Debian systems: - DEBOOTDEB="debootstrap_1.0.128+nmu2+deb12u1_all.deb" + DEBOOTDEB="debootstrap_1.0.128+nmu2+deb12u2_all.deb" # The Debian mirror/proxy below can be passed as environment # variables; if none are given the following defaults are used. diff --git a/misc-tools/import-contest.in b/misc-tools/import-contest.in index 756c03d7f0..79d6ffaa9a 100755 --- a/misc-tools/import-contest.in +++ b/misc-tools/import-contest.in @@ -149,10 +149,10 @@ if import_file('organizations', ['organizations.json']): # Also import logos if we have any # We prefer the 64x64 logo. If it doesn't exist, accept a generic logo (which might be a SVG) # We also prefer PNG/SVG before JPG - import_images('organizations', 'logo', ['^logo\.64x\d+\.png$', '^logo\.(png|svg)$', '^logo\.64x\d+\.jpg$', '^logo\.jpg$']) + import_images('organizations', 'logo', ['^logo\\.64x\\d+\\.png$', '^logo\\.(png|svg)$', '^logo\\.64x\\d+\\.jpg$', '^logo\\.jpg$']) if import_file('teams', ['teams.json', 'teams2.tsv']): # Also import photos if we have any, but prefer JPG over SVG and PNG - import_images('teams', 'photo', ['^photo\.jpg$', '^photo\.(png|svg)$']) + import_images('teams', 'photo', ['^photo\\.jpg$', '^photo\\.(png|svg)$']) import_file('accounts', ['accounts.json', 'accounts.yaml', 'accounts.tsv']) problems_imported = False diff --git a/webapp/config/packages/nelmio_api_doc.yaml b/webapp/config/packages/nelmio_api_doc.yaml index 2453372b42..5904171595 100644 --- a/webapp/config/packages/nelmio_api_doc.yaml +++ b/webapp/config/packages/nelmio_api_doc.yaml @@ -1,9 +1,9 @@ nelmio_api_doc: documentation: servers: - - url: "%domjudge.baseurl%api" + - url: ~ # Will be set by App\NelmioApiDocBundle\ExternalDocDescriber description: API used at this contest - - url: https://www.domjudge.org/demoweb/api + - url: https://www.domjudge.org/demoweb description: New API in development info: title: DOMjudge diff --git a/webapp/src/Controller/API/AbstractApiController.php b/webapp/src/Controller/API/AbstractApiController.php index 163f000bba..b5c43a76d9 100644 --- a/webapp/src/Controller/API/AbstractApiController.php +++ b/webapp/src/Controller/API/AbstractApiController.php @@ -34,9 +34,11 @@ public function __construct( * Get the query builder used for getting contests. * * @param bool $onlyActive return only contests that are active + * @param bool $filterBeforeContest return only contests that have started */ - protected function getContestQueryBuilder(bool $onlyActive = false): QueryBuilder - { + protected function getContestQueryBuilder( + bool $onlyActive = false, bool $filterBeforeContest = true + ): QueryBuilder { $now = Utils::now(); $qb = $this->em->createQueryBuilder(); $qb @@ -63,6 +65,10 @@ protected function getContestQueryBuilder(bool $onlyActive = false): QueryBuilde } else { $qb->andWhere('c.public = 1'); } + if ($filterBeforeContest) { + $qb->andWhere('c.starttime <= :now') + ->setParameter('now', $now); + } } return $qb; @@ -77,7 +83,10 @@ protected function getContestId(Request $request): int throw new BadRequestHttpException('cid parameter missing'); } - $qb = $this->getContestQueryBuilder($request->query->getBoolean('onlyActive', false)); + $qb = $this->getContestQueryBuilder( + onlyActive: $request->query->getBoolean('onlyActive', false), + filterBeforeContest: false + ); $qb ->andWhere(sprintf('c.%s = :cid', $this->getContestIdField())) ->setParameter('cid', $request->attributes->get('cid')); diff --git a/webapp/src/Controller/API/ContestController.php b/webapp/src/Controller/API/ContestController.php index 317d49cdcb..b0f4ce8ceb 100644 --- a/webapp/src/Controller/API/ContestController.php +++ b/webapp/src/Controller/API/ContestController.php @@ -179,7 +179,7 @@ public function singleAction(Request $request, string $cid): Response public function bannerAction(Request $request, string $cid): Response { /** @var Contest|null $contest */ - $contest = $this->getQueryBuilder($request) + $contest = $this->getQueryBuilder($request, filterBeforeContest: false) ->andWhere(sprintf('%s = :id', $this->getIdField())) ->setParameter('id', $cid) ->getQuery() @@ -934,10 +934,10 @@ public function samplesDataZipAction(Request $request): Response return $this->dj->getSamplesZipForContest($contest); } - protected function getQueryBuilder(Request $request): QueryBuilder + protected function getQueryBuilder(Request $request, bool $filterBeforeContest = true): QueryBuilder { try { - return $this->getContestQueryBuilder($request->query->getBoolean('onlyActive', false)); + return $this->getContestQueryBuilder($request->query->getBoolean('onlyActive', false), $filterBeforeContest); } catch (TypeError) { throw new BadRequestHttpException('\'onlyActive\' must be a boolean.'); } @@ -954,7 +954,7 @@ protected function getIdField(): string */ protected function getContestWithId(Request $request, string $id): Contest { - $queryBuilder = $this->getQueryBuilder($request) + $queryBuilder = $this->getQueryBuilder($request, filterBeforeContest: false) ->andWhere(sprintf('%s = :id', $this->getIdField())) ->setParameter('id', $id); @@ -971,7 +971,7 @@ protected function getContestWithId(Request $request, string $id): Contest private function getContestAndCheckIfLocked(Request $request, string $cid): Contest { /** @var Contest|null $contest */ - $contest = $this->getQueryBuilder($request) + $contest = $this->getQueryBuilder($request, filterBeforeContest: false) ->andWhere(sprintf('%s = :id', $this->getIdField())) ->setParameter('id', $cid) ->getQuery() diff --git a/webapp/src/Controller/Jury/ProblemController.php b/webapp/src/Controller/Jury/ProblemController.php index 56ed94d5f8..95d40eafdb 100644 --- a/webapp/src/Controller/Jury/ProblemController.php +++ b/webapp/src/Controller/Jury/ProblemController.php @@ -1087,7 +1087,6 @@ public function addAction(Request $request): Response $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $this->em->persist($problem); $this->saveEntity($this->em, $this->eventLogService, $this->dj, $problem, null, true); return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); } diff --git a/webapp/src/Controller/Jury/TeamCategoryController.php b/webapp/src/Controller/Jury/TeamCategoryController.php index 2cdba5b922..0cc075696c 100644 --- a/webapp/src/Controller/Jury/TeamCategoryController.php +++ b/webapp/src/Controller/Jury/TeamCategoryController.php @@ -250,7 +250,7 @@ public function requestRemainingRunsWholeTeamCategoryAction(string $categoryId): ->join('t.category', 'tc') ->andWhere('j.valid = true') ->andWhere('j.result != :compiler_error') - ->andWhere('tc.category = :categoryId') + ->andWhere('tc.categoryid = :categoryId') ->setParameter('compiler_error', 'compiler-error') ->setParameter('categoryId', $categoryId); if ($contestId > -1) { diff --git a/webapp/src/Controller/Team/MiscController.php b/webapp/src/Controller/Team/MiscController.php index 648b4bf727..e714d6737b 100644 --- a/webapp/src/Controller/Team/MiscController.php +++ b/webapp/src/Controller/Team/MiscController.php @@ -95,12 +95,10 @@ public function homeAction(Request $request): Response $clarifications = $this->em->createQueryBuilder() ->from(Clarification::class, 'c') ->leftJoin('c.problem', 'p') - ->leftJoin('p.contest_problems', 'cp') ->leftJoin('c.sender', 's') ->leftJoin('c.recipient', 'r') - ->select('c', 'cp', 'p') + ->select('c', 'p') ->andWhere('c.contest = :contest') - ->andWhere('cp.contest = :contest') ->andWhere('c.sender IS NULL') ->andWhere('c.recipient = :team OR c.recipient IS NULL') ->setParameter('contest', $contest) @@ -114,12 +112,10 @@ public function homeAction(Request $request): Response $clarificationRequests = $this->em->createQueryBuilder() ->from(Clarification::class, 'c') ->leftJoin('c.problem', 'p') - ->leftJoin('p.contest_problems', 'cp') ->leftJoin('c.sender', 's') ->leftJoin('c.recipient', 'r') - ->select('c', 'cp', 'p') + ->select('c', 'p') ->andWhere('c.contest = :contest') - ->andWhere('cp.contest = :contest') ->andWhere('c.sender = :team') ->setParameter('contest', $contest) ->setParameter('team', $team) diff --git a/webapp/src/DataTransferObject/Shadowing/ProblemEvent.php b/webapp/src/DataTransferObject/Shadowing/ProblemEvent.php index 2aa47e6e27..7c5b76d348 100644 --- a/webapp/src/DataTransferObject/Shadowing/ProblemEvent.php +++ b/webapp/src/DataTransferObject/Shadowing/ProblemEvent.php @@ -7,7 +7,7 @@ class ProblemEvent implements EventData public function __construct( public readonly string $id, public readonly string $name, - public readonly int $timeLimit, + public readonly float $timeLimit, public readonly ?string $label, public readonly ?string $rgb, ) {} diff --git a/webapp/src/Entity/Clarification.php b/webapp/src/Entity/Clarification.php index ed62d6013f..b52765b218 100644 --- a/webapp/src/Entity/Clarification.php +++ b/webapp/src/Entity/Clarification.php @@ -236,6 +236,14 @@ public function getProblem(): ?Problem return $this->problem; } + public function getContestProblem(): ?ContestProblem + { + if (!$this->problem) { + return null; + } + return $this->contest->getContestProblem($this->problem); + } + #[OA\Property(nullable: true)] #[Serializer\VirtualProperty] #[Serializer\SerializedName('problem_id')] diff --git a/webapp/src/Entity/Contest.php b/webapp/src/Entity/Contest.php index 0b88932cd6..9c5cfe6ab3 100644 --- a/webapp/src/Entity/Contest.php +++ b/webapp/src/Entity/Contest.php @@ -913,6 +913,16 @@ public function getProblems(): Collection return $this->problems; } + public function getContestProblem(Problem $problem): ?ContestProblem + { + foreach ($this->getProblems() as $contestProblem) { + if ($contestProblem->getProblem() === $problem) { + return $contestProblem; + } + } + return null; + } + public function addClarification(Clarification $clarification): Contest { $this->clarifications[] = $clarification; diff --git a/webapp/src/NelmioApiDocBundle/ExternalDocDescriber.php b/webapp/src/NelmioApiDocBundle/ExternalDocDescriber.php new file mode 100644 index 0000000000..2805d51b55 --- /dev/null +++ b/webapp/src/NelmioApiDocBundle/ExternalDocDescriber.php @@ -0,0 +1,29 @@ +requestStack->getCurrentRequest(); + $this->decorated->describe($api); + Util::merge($api->servers[0], ['url' => $request->getSchemeAndHttpHost(),], true); + } +} diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index 7238f43682..3972b77ed0 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -108,7 +108,7 @@ public function getFilters(): array new TwigFilter('tsvField', $this->toTsvField(...)), new TwigFilter('fileTypeIcon', $this->fileTypeIcon(...)), new TwigFilter('problemBadge', $this->problemBadge(...), ['is_safe' => ['html']]), - new TwigFilter('problemBadgeForProblemAndContest', $this->problemBadgeForProblemAndContest(...), ['is_safe' => ['html']]), + new TwigFilter('problemBadgeForContest', $this->problemBadgeForContest(...), ['is_safe' => ['html']]), new TwigFilter('printMetadata', $this->printMetadata(...), ['is_safe' => ['html']]), new TwigFilter('printWarningContent', $this->printWarningContent(...), ['is_safe' => ['html']]), new TwigFilter('entityIdBadge', $this->entityIdBadge(...), ['is_safe' => ['html']]), @@ -1032,8 +1032,8 @@ public function hexColorToRGBA(string $text, float $opacity = 1): string if (is_null($col)) { return $text; } - preg_match_all("/[0-9A-Fa-f]{2}/", $col, $m); - if (!count($m)) { + $ret = preg_match_all("/[0-9A-Fa-f]{2}/", $col, $m); + if (!($ret && count($m[0]))) { return $text; } @@ -1093,14 +1093,11 @@ public function problemBadge(ContestProblem $problem): string ); } - public function problemBadgeForProblemAndContest(Problem $problem, ?Contest $contest): string + public function problemBadgeForContest(Problem $problem, ?Contest $contest = null): string { - foreach ($problem->getContestProblems() as $contestProblem) { - if ($contestProblem->getContest() === $contest) { - return $this->problemBadge($contestProblem); - } - } - return ''; + $contest ??= $this->dj->getCurrentContest(); + $contestProblem = $contest?->getContestProblem($problem); + return $contestProblem === null ? '' : $this->problemBadge($contestProblem); } public function printMetadata(?string $metadata): string diff --git a/webapp/templates/jury/executable.html.twig b/webapp/templates/jury/executable.html.twig index 95cbe3956e..0cf94b261c 100644 --- a/webapp/templates/jury/executable.html.twig +++ b/webapp/templates/jury/executable.html.twig @@ -48,14 +48,14 @@ {% if executable.type == 'compare' %} {% for problem in executable.problemsCompare %} - p{{ problem.probid }} {{ problem | problemBadgeForProblemAndContest(current_contest) }} + p{{ problem.probid }} {{ problem | problemBadgeForContest }} {% set used = true %} {% endfor %} {% elseif executable.type == 'run' %} {% for problem in executable.problemsRun %} - p{{ problem.probid }} {{ problem | problemBadgeForProblemAndContest(current_contest) }} + p{{ problem.probid }} {{ problem | problemBadgeForContest }} {% set used = true %} {% endfor %} diff --git a/webapp/templates/jury/partials/clarification_list.html.twig b/webapp/templates/jury/partials/clarification_list.html.twig index 337499bb1c..09874023be 100644 --- a/webapp/templates/jury/partials/clarification_list.html.twig +++ b/webapp/templates/jury/partials/clarification_list.html.twig @@ -71,7 +71,7 @@ {%- if clarification.problem -%} - problem {{ clarification.problem.contestProblems.first | problemBadge -}} + problem {{ clarification.contestProblem | problemBadge -}} {%- elseif clarification.category -%} {{- categories[clarification.category]|default('general') -}} {%- else -%} diff --git a/webapp/templates/partials/problem_list.html.twig b/webapp/templates/partials/problem_list.html.twig index f00ef14482..0733241ddd 100644 --- a/webapp/templates/partials/problem_list.html.twig +++ b/webapp/templates/partials/problem_list.html.twig @@ -94,7 +94,7 @@ data-bs-toggle="tooltip" data-placement="top" data-html="true" - title="Between {{ stat.start.timestamp | printtime(null, contest) }} and {{ stat.end.timestamp | printtime(null, contest) }}:
{{ label }}"> + title="Between {{ stat.start.timestamp | printtime(null, contest) }} and {{ stat.end.timestamp | printtime(null, contest) }}:{{ '\n' }}{{ label }}"> {% endfor %} diff --git a/webapp/templates/team/partials/clarification.html.twig b/webapp/templates/team/partials/clarification.html.twig index eda98ce293..5004e4c0d4 100644 --- a/webapp/templates/team/partials/clarification.html.twig +++ b/webapp/templates/team/partials/clarification.html.twig @@ -4,7 +4,7 @@
Subject: {% if clarification.problem %} - Problem {{ clarification.problem.contestProblems.first.shortname }}: {{ clarification.problem.name }} + Problem {{ clarification.contestProblem.shortname }}: {{ clarification.problem.name }} {% elseif clarification.category %} {{ categories[clarification.category]|default('general') }} {% else %} diff --git a/webapp/templates/team/partials/clarification_list.html.twig b/webapp/templates/team/partials/clarification_list.html.twig index a36c1e0f30..47b154c542 100644 --- a/webapp/templates/team/partials/clarification_list.html.twig +++ b/webapp/templates/team/partials/clarification_list.html.twig @@ -44,7 +44,7 @@ {%- if clarification.problem -%} - problem {{ clarification.problem.contestProblems.first | problemBadge -}} + problem {{ clarification.contestProblem | problemBadge -}} {%- elseif clarification.category -%} {{- categories[clarification.category]|default('general') -}} {%- else -%} diff --git a/webapp/tests/Unit/Controller/Jury/JuryMiscControllerTest.php b/webapp/tests/Unit/Controller/Jury/JuryMiscControllerTest.php index 31e9b0bf89..bda8ae8583 100644 --- a/webapp/tests/Unit/Controller/Jury/JuryMiscControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/JuryMiscControllerTest.php @@ -91,7 +91,7 @@ public function testBalloonScoreboard(array $fixtures, bool $public, string $con $elements = ["3 tries",'Demo contest','Utrecht University']; } elseif (in_array($contestStage, ['preEnd','preUnfreeze'])) { $elements = ["0 + 4 tries","3 tries","2 + 1 tries",'Demo contest','Utrecht University']; - if ($contestStage === 'preFreeze') { + if ($contestStage === 'preUnfreeze') { $elements[] = 'contest over, waiting for results'; } } else { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy