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/ChangeLog b/ChangeLog index 30ebc035f2..ccd133df9b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ DOMjudge Programming Contest Judging System +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..eaa23881ee 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ DOMjudge [](https://scan.coverity.com/projects/domjudge) [](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.1 DOMjudge is a system for running a programming contest, like the ICPC regional and world championship programming contests. diff --git a/etc/db-config.yaml b/etc/db-config.yaml index 9c6b0866b4..174c9791e8 100644 --- a/etc/db-config.yaml +++ b/etc/db-config.yaml @@ -201,6 +201,14 @@ - category: Display description: Options related to the DOMjudge user interface. items: + - name: default_submission_code_mode + type: int + default_value: 0 + public: true + description: Select the default submission method for the team + options: + 0: Paste + 1: Upload - name: output_display_limit type: int default_value: 2000 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/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/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/Controller/Team/SubmissionController.php b/webapp/src/Controller/Team/SubmissionController.php index e6cfbf223f..d5d9eb3d06 100644 --- a/webapp/src/Controller/Team/SubmissionController.php +++ b/webapp/src/Controller/Team/SubmissionController.php @@ -9,6 +9,7 @@ use App\Entity\Submission; use App\Entity\Testcase; use App\Form\Type\SubmitProblemType; +use App\Form\Type\SubmitProblemPasteType; use App\Service\ConfigurationService; use App\Service\DOMJudgeService; use App\Service\SubmissionService; @@ -54,32 +55,48 @@ public function createAction(Request $request, ?Problem $problem = null): Respon if ($problem !== null) { $data['problem'] = $problem; } - $form = $this->formFactory + $formUpload = $this->formFactory ->createBuilder(SubmitProblemType::class, $data) ->setAction($this->generateUrl('team_submit')) ->getForm(); - $form->handleRequest($request); + $formPaste = $this->formFactory + ->createBuilder(SubmitProblemPasteType::class, $data) + ->setAction($this->generateUrl('team_submit')) + ->getForm(); - if ($form->isSubmitted() && $form->isValid()) { + $formUpload->handleRequest($request); + $formPaste->handleRequest($request); + if ($formUpload->isSubmitted() && $formUpload->isValid()) { if ($contest === null) { $this->addFlash('danger', 'No active contest'); } elseif (!$this->dj->checkrole('jury') && !$contest->getFreezeData()->started()) { $this->addFlash('danger', 'Contest has not yet started'); } else { /** @var Problem $problem */ - $problem = $form->get('problem')->getData(); + $problem = $formUpload->get('problem')->getData(); /** @var Language $language */ - $language = $form->get('language')->getData(); + $language = $formUpload->get('language')->getData(); /** @var UploadedFile[] $files */ - $files = $form->get('code')->getData(); + $files = $formUpload->get('code')->getData(); if (!is_array($files)) { $files = [$files]; } - $entryPoint = $form->get('entry_point')->getData() ?: null; + $entryPoint = $formUpload->get('entry_point')->getData() ?: null; $submission = $this->submissionService->submitSolution( - $team, $this->dj->getUser(), $problem->getProbid(), $contest, $language, $files, 'team page', null, - null, $entryPoint, null, null, $message + $team, + $this->dj->getUser(), + $problem->getProbid(), + $contest, + $language, + $files, + 'team page', + null, + null, + $entryPoint, + null, + null, + $message ); if ($submission) { @@ -90,11 +107,77 @@ public function createAction(Request $request, ?Problem $problem = null): Respon } else { $this->addFlash('danger', $message); } + return $this->redirectToRoute('team_index'); + } + } elseif ($formPaste->isSubmitted() && $formPaste->isValid()) { + if ($contest === null) { + $this->addFlash('danger', 'No active contest'); + } elseif (!$this->dj->checkrole('jury') && !$contest->getFreezeData()->started()) { + $this->addFlash('danger', 'Contest has not yet started'); + } else { + $problem = $formPaste->get('problem')->getData(); + $language = $formPaste->get('language')->getData(); + $codeContent = $formPaste->get('code_content')->getData(); + if($codeContent == null || empty(trim($codeContent))) { + $this->addFlash('danger','No code content provided.'); + return $this->redirectToRoute('team_index'); + } + $tempDir = sys_get_temp_dir(); + $tempFileName = sprintf( + 'submission_%s_%s_%s.%s', + $user->getUsername(), + $problem->getName(), + date('Y-m-d_H-i-s'), + $language->getExtensions()[0] + ); + $tempFileName = preg_replace('/[^a-zA-Z0-9_.-]/', '_', $tempFileName); + $tempFilePath = $tempDir . DIRECTORY_SEPARATOR . $tempFileName; + file_put_contents($tempFilePath, $codeContent); + + $uploadedFile = new UploadedFile( + $tempFilePath, + $tempFileName, + 'application/octet-stream', + null, + true + ); + + $files = [$uploadedFile]; + $entryPoint = $tempFileName; + $submission = $this->submissionService->submitSolution( + $team, + $this->dj->getUser(), + $problem, + $contest, + $language, + $files, + 'team page', + null, + null, + $entryPoint, + null, + null, + $message + ); + if ($submission) { + $this->addFlash( + 'success', + 'Submission done! Watch for the verdict in the list below.' + ); + } else { + $this->addFlash('danger', $message); + } + return $this->redirectToRoute('team_index'); } } - $data = ['form' => $form->createView(), 'problem' => $problem]; + $data = [ + 'formupload' => $formUpload->createView(), + 'formpaste' => $formPaste->createView(), + 'problem' => $problem, + 'defaultSubmissionCodeMode' => (bool) $this->config->get('default_submission_code_mode'), + ]; $data['validFilenameRegex'] = SubmissionService::FILENAME_REGEX; if ($request->isXmlHttpRequest()) { 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/Form/Type/SubmitProblemPasteType.php b/webapp/src/Form/Type/SubmitProblemPasteType.php new file mode 100644 index 0000000000..71050b95a8 --- /dev/null +++ b/webapp/src/Form/Type/SubmitProblemPasteType.php @@ -0,0 +1,81 @@ +dj->getUser(); + $contest = $this->dj->getCurrentContest($user->getTeam()->getTeamid()); + + $builder->add('code_content', HiddenType::class, [ + 'required' => true, + ]); + $problemConfig = [ + 'class' => Problem::class, + 'query_builder' => fn(EntityRepository $er) => $er->createQueryBuilder('p') + ->join('p.contest_problems', 'cp', Join::WITH, 'cp.contest = :contest') + ->select('p', 'cp') + ->andWhere('cp.allowSubmit = 1') + ->setParameter('contest', $contest) + ->addOrderBy('cp.shortname'), + 'choice_label' => fn(Problem $problem) => sprintf( + '%s - %s', + $problem->getContestProblems()->first()->getShortName(), + $problem->getName() + ), + 'placeholder' => 'Select a problem', + ]; + $builder->add('problem', EntityType::class, $problemConfig); + + $builder->add('language', EntityType::class, [ + 'class' => Language::class, + 'query_builder' => fn(EntityRepository $er) => $er + ->createQueryBuilder('l') + ->andWhere('l.allowSubmit = 1'), + 'choice_label' => 'name', + 'placeholder' => 'Select a language', + ]); + + $builder->add('entry_point', TextType::class, [ + 'label' => 'Entry point', + 'required' => false, + 'help' => 'The entry point for your code.', + 'row_attr' => ['data-entry-point' => ''] + ]); + $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($problemConfig) { + $data = $event->getData(); + if (isset($data['problem'])) { + $problemConfig += ['row_attr' => ['class' => 'd-none']]; + $event->getForm()->add('problem', EntityType::class, $problemConfig); + } + }); + } +} \ No newline at end of file diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index 7238f43682..deff4a4f9c 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']]), @@ -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/base.html.twig b/webapp/templates/base.html.twig index 895750ede8..6ccf9845de 100644 --- a/webapp/templates/base.html.twig +++ b/webapp/templates/base.html.twig @@ -14,6 +14,8 @@ + + {% for file in customAssetFiles('js') %} {% endfor %} 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 @@