diff --git a/ChangeLog b/ChangeLog index 39bcd6f2c6..4790aa85fe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -346,7 +346,7 @@ Version 7.1.0 - 25 September 2019 - Upgrade Symfony and dependencies to version 4.3. - Use Doctrine for migrations and initial data. - Add option (enabled by default) to limit files passed to - language compilers by langauge extension list. + language compilers by language extension list. - Add option to add a whole team category to a contest. - Allow admin to specify print command using configuration setting. - Show metadata for judging runs. diff --git a/judge/judgedaemon.main.php b/judge/judgedaemon.main.php index 2953e8a781..9448713a18 100644 --- a/judge/judgedaemon.main.php +++ b/judge/judgedaemon.main.php @@ -624,7 +624,7 @@ private function checkDiskSpace(string $workdirpath): void private function judgingDirectory(string $workdirpath, array $judgeTask): string { - if (filter_var($judgeTask['submitid'], FILTER_VALIDATE_INT) === false || + if (filter_var($judgeTask['submitid'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^[a-zA-Z0-9_.-]+$/']]) === false || filter_var($judgeTask['jobid'], FILTER_VALIDATE_INT) === false) { error("Malformed data returned in judgeTask IDs: " . var_export($judgeTask, true)); } @@ -1289,7 +1289,7 @@ private function compile( // Revoke readablity for domjudge-run user to this workdir. chmod($workdir, 0700); - logmsg(LOG_NOTICE, "Judging s$judgeTask[submitid], task $judgeTask[judgetaskid]: compile error"); + logmsg(LOG_NOTICE, "Judging $judgeTask[submitid], task $judgeTask[judgetaskid]: compile error"); return false; } @@ -1353,7 +1353,7 @@ private function compile( // What does the exitcode mean? if (!isset($this->EXITCODES[$retval])) { alert('error'); - $description = "Unknown exitcode from compile.sh for s$judgeTask[submitid]: $retval"; + $description = "Unknown exitcode from compile.sh for $judgeTask[submitid]: $retval"; logmsg(LOG_ERR, $description); $this->disable('compile_script', 'compile_script_id', $judgeTask['compile_script_id'], $description, $judgeTask['judgetaskid'], $compile_output); @@ -1568,7 +1568,7 @@ private function runTestcase( // What does the exitcode mean? if (!isset($this->EXITCODES[$retval])) { alert('error'); - error("Unknown exitcode ($retval) from testcase_run.sh for s$judgeTask[submitid]"); + error("Unknown exitcode ($retval) from testcase_run.sh for $judgeTask[submitid]"); } $result = $this->EXITCODES[$retval]; diff --git a/webapp/migrations/Version20251024122021.php b/webapp/migrations/Version20251024122021.php new file mode 100644 index 0000000000..fbb5a116a3 --- /dev/null +++ b/webapp/migrations/Version20251024122021.php @@ -0,0 +1,141 @@ +addSql('ALTER TABLE submission DROP FOREIGN KEY submission_ibfk_4'); + $this->addSql('ALTER TABLE problemlanguage DROP FOREIGN KEY FK_46B150BB2271845'); + $this->addSql('ALTER TABLE version DROP FOREIGN KEY FK_BF1CD3C32271845'); + $this->addSql('ALTER TABLE contestlanguage DROP FOREIGN KEY FK_ADCB43232271845'); + + $this->addSql('ALTER TABLE language ADD langid_int INT UNSIGNED AUTO_INCREMENT UNIQUE AFTER langid'); + $this->addSql('ALTER TABLE contestlanguage ADD langid_int INT UNSIGNED NOT NULL AFTER langid'); + $this->addSql('ALTER TABLE problemlanguage ADD langid_int INT UNSIGNED NOT NULL AFTER langid'); + $this->addSql('ALTER TABLE submission ADD langid_int INT UNSIGNED DEFAULT NULL AFTER langid'); + $this->addSql('ALTER TABLE version ADD langid_int INT UNSIGNED DEFAULT NULL AFTER langid'); + + $this->addSql('UPDATE contestlanguage c JOIN language l ON c.langid = l.langid SET c.langid_int = l.langid_int'); + $this->addSql('UPDATE problemlanguage p JOIN language l ON p.langid = l.langid SET p.langid_int = l.langid_int'); + $this->addSql('UPDATE submission s JOIN language l ON s.langid = l.langid SET s.langid_int = l.langid_int WHERE s.langid IS NOT NULL'); + $this->addSql('UPDATE version v JOIN language l ON v.langid = l.langid SET v.langid_int = l.langid_int WHERE v.langid IS NOT NULL'); + $this->addSql('UPDATE auditlog a JOIN language l ON a.dataid = l.langid AND a.datatype = "language" SET a.dataid = l.langid_int WHERE a.dataid IS NOT NULL'); + + $this->addSql('ALTER TABLE language DROP PRIMARY KEY'); + $this->addSql('ALTER TABLE language DROP COLUMN langid'); + $this->addSql('ALTER TABLE contestlanguage DROP PRIMARY KEY'); + $this->addSql('ALTER TABLE contestlanguage DROP COLUMN langid'); + $this->addSql('ALTER TABLE problemlanguage DROP PRIMARY KEY'); + $this->addSql('ALTER TABLE problemlanguage DROP COLUMN langid'); + $this->addSql('ALTER TABLE submission DROP COLUMN langid'); + $this->addSql('ALTER TABLE version DROP COLUMN langid'); + + $this->addSql('ALTER TABLE language CHANGE langid_int langid INT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT \'Language ID\''); + $this->addSql('ALTER TABLE contestlanguage CHANGE langid_int langid INT UNSIGNED NOT NULL COMMENT \'Language ID\''); + $this->addSql('ALTER TABLE problemlanguage CHANGE langid_int langid INT UNSIGNED NOT NULL COMMENT \'Language ID\''); + $this->addSql('ALTER TABLE submission CHANGE langid_int langid INT UNSIGNED DEFAULT NULL COMMENT \'Language ID\''); + $this->addSql('ALTER TABLE version CHANGE langid_int langid INT UNSIGNED DEFAULT NULL COMMENT \'Language ID\''); + + $this->addSql('ALTER TABLE language ADD PRIMARY KEY (langid), DROP INDEX langid_int'); + $this->addSql('ALTER TABLE contestlanguage ADD PRIMARY KEY (cid, langid)'); + $this->addSql('ALTER TABLE problemlanguage ADD PRIMARY KEY (probid, langid)'); + + $this->addSql('ALTER TABLE submission ADD INDEX langid (langid), ADD CONSTRAINT submission_ibfk_4 FOREIGN KEY (langid) REFERENCES language (langid) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE problemlanguage ADD INDEX IDX_46B150BB2271845 (langid), ADD CONSTRAINT FK_46B150BB2271845 FOREIGN KEY (langid) REFERENCES language (langid) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE version ADD INDEX IDX_BF1CD3C32271845 (langid), ADD CONSTRAINT FK_BF1CD3C32271845 FOREIGN KEY (langid) REFERENCES language (langid) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE contestlanguage ADD INDEX IDX_ADCB43232271845 (langid), ADD CONSTRAINT FK_ADCB43232271845 FOREIGN KEY (langid) REFERENCES language (langid) ON DELETE CASCADE'); + } + + public function down(Schema $schema): void + { + // When migrating back, we do the logic in reverse but we have custom logic for setting + // the string langid. We set it to the external ID except for a set of 10 known languages + // where the external ID differs from our previously used langid. + + $this->addSql('ALTER TABLE submission DROP FOREIGN KEY submission_ibfk_4'); + $this->addSql('ALTER TABLE problemlanguage DROP FOREIGN KEY FK_46B150BB2271845'); + $this->addSql('ALTER TABLE version DROP FOREIGN KEY FK_BF1CD3C32271845'); + $this->addSql('ALTER TABLE contestlanguage DROP FOREIGN KEY FK_ADCB43232271845'); + + $this->addSql('ALTER TABLE language ADD langid_str VARCHAR(32) NOT NULL AFTER langid'); + $this->addSql('ALTER TABLE contestlanguage ADD langid_str VARCHAR(32) NOT NULL AFTER langid'); + $this->addSql('ALTER TABLE problemlanguage ADD langid_str VARCHAR(32) NOT NULL AFTER langid'); + $this->addSql('ALTER TABLE submission ADD langid_str VARCHAR(32) DEFAULT NULL AFTER langid'); + $this->addSql('ALTER TABLE version ADD langid_str VARCHAR(32) DEFAULT NULL AFTER langid'); + + $this->addSql("UPDATE language SET langid_str = CASE externalid + WHEN 'ada' THEN 'adb' + WHEN 'haskell' THEN 'hs' + WHEN 'javascript' THEN 'js' + WHEN 'kotlin' THEN 'kt' + WHEN 'pascal' THEN 'pas' + WHEN 'prolog' THEN 'plg' + WHEN 'python3' THEN 'py3' + WHEN 'python2' THEN 'py2' + WHEN 'ruby' THEN 'rb' + WHEN 'rust' THEN 'rs' + ELSE externalid + END"); + + $this->addSql('UPDATE contestlanguage c JOIN language l ON c.langid = l.langid SET c.langid_str = l.langid_str'); + $this->addSql('UPDATE problemlanguage p JOIN language l ON p.langid = l.langid SET p.langid_str = l.langid_str'); + $this->addSql('UPDATE submission s JOIN language l ON s.langid = l.langid SET s.langid_str = l.langid_str WHERE s.langid IS NOT NULL'); + $this->addSql('UPDATE version v JOIN language l ON v.langid = l.langid SET v.langid_str = l.langid_str WHERE v.langid IS NOT NULL'); + $this->addSql('UPDATE auditlog a JOIN language l ON a.dataid = l.langid AND a.datatype = "language" SET a.dataid = l.langid_str WHERE a.dataid IS NOT NULL'); + + $this->addSql('ALTER TABLE language DROP PRIMARY KEY'); + $this->addSql('ALTER TABLE language DROP COLUMN langid'); + $this->addSql('ALTER TABLE contestlanguage DROP PRIMARY KEY'); + $this->addSql('ALTER TABLE contestlanguage DROP COLUMN langid'); + $this->addSql('ALTER TABLE problemlanguage DROP PRIMARY KEY'); + $this->addSql('ALTER TABLE problemlanguage DROP COLUMN langid'); + $this->addSql('ALTER TABLE submission DROP COLUMN langid'); + $this->addSql('ALTER TABLE version DROP COLUMN langid'); + + $this->addSql('ALTER TABLE language CHANGE langid_str langid VARCHAR(32) NOT NULL COMMENT \'Language ID\''); + $this->addSql('ALTER TABLE contestlanguage CHANGE langid_str langid VARCHAR(32) NOT NULL COMMENT \'Language ID\''); + $this->addSql('ALTER TABLE problemlanguage CHANGE langid_str langid VARCHAR(32) NOT NULL COMMENT \'Language ID\''); + $this->addSql('ALTER TABLE submission CHANGE langid_str langid VARCHAR(32) DEFAULT NULL COMMENT \'Language ID\''); + $this->addSql('ALTER TABLE version CHANGE langid_str langid VARCHAR(32) DEFAULT NULL COMMENT \'Language ID\''); + + $this->addSql('ALTER TABLE language ADD PRIMARY KEY (langid)'); + $this->addSql('ALTER TABLE contestlanguage ADD PRIMARY KEY (cid, langid)'); + $this->addSql('ALTER TABLE problemlanguage ADD PRIMARY KEY (probid, langid)'); + + $this->addSql('ALTER TABLE submission ADD INDEX langid (langid), ADD CONSTRAINT submission_ibfk_4 FOREIGN KEY (langid) REFERENCES language (langid) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE problemlanguage ADD INDEX IDX_46B150BB2271845 (langid), ADD CONSTRAINT FK_46B150BB2271845 FOREIGN KEY (langid) REFERENCES language (langid) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE version ADD INDEX IDX_BF1CD3C32271845 (langid), ADD CONSTRAINT FK_BF1CD3C32271845 FOREIGN KEY (langid) REFERENCES language (langid) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE contestlanguage ADD INDEX IDX_ADCB43232271845 (langid), ADD CONSTRAINT FK_ADCB43232271845 FOREIGN KEY (langid) REFERENCES language (langid) ON DELETE CASCADE'); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/webapp/migrations/Version20251026092516.php b/webapp/migrations/Version20251026092516.php new file mode 100644 index 0000000000..c0a838e108 --- /dev/null +++ b/webapp/migrations/Version20251026092516.php @@ -0,0 +1,66 @@ + 'clarid', + 'contest' => 'cid', + 'language' => 'langid', + 'problem' => 'probid', + 'submission' => 'submitid', + 'team' => 'teamid', + 'team_affiliation' => 'affilid', + 'team_category' => 'categoryid', + 'user' => ['userid', 'username'], + ]; + + public function getDescription(): string + { + return 'Make auditlog IDs reference external IDs'; + } + + public function up(Schema $schema): void + { + // Note: this migration only works for entities that still exist. For others, it will not work but there is nothing we can do + $this->addSql('ALTER TABLE auditlog CHANGE cid cid VARCHAR(255) DEFAULT NULL COMMENT \'External contest ID associated to this entry\', CHANGE dataid dataid VARCHAR(64) DEFAULT NULL COMMENT \'(External) identifier in reference table\''); + $this->addSql('UPDATE auditlog INNER JOIN contest ON CAST(contest.cid AS CHAR) = auditlog.cid SET auditlog.cid = contest.externalid'); + foreach (static::ENTITIES as $table => $fields) { + if (!is_array($fields)) { + $fields = [$fields]; + } + foreach ($fields as $field) { + $this->addSql(sprintf( + 'UPDATE auditlog INNER JOIN %s ON CAST(%s.%s AS CHAR) = auditlog.dataid AND auditlog.datatype = "%s" SET auditlog.dataid = %s.externalid', + $table, $table, $field, $table, $table + )); + } + } + } + + public function down(Schema $schema): void + { + // Note: this migration only works for entities that still exist. For others, it will not work but there is nothing we can do + $this->addSql('UPDATE auditlog INNER JOIN contest ON contest.externalid = auditlog.cid SET auditlog.cid = contest.cid'); + foreach (static::ENTITIES as $table => $fields) { + $field = is_array($fields) ? $fields[0] : $fields; + $this->addSql(sprintf( + 'UPDATE auditlog INNER JOIN %s ON %s.externalid = auditlog.dataid AND auditlog.datatype = "%s" SET auditlog.dataid = %s.%s', + $table, $table, $table, $table, $field + )); + } + $this->addSql('ALTER TABLE auditlog CHANGE cid cid INT UNSIGNED DEFAULT NULL COMMENT \'Contest ID associated to this entry\', CHANGE dataid dataid VARCHAR(64) DEFAULT NULL COMMENT \'Identifier in reference table\''); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/webapp/public/js/domjudge.js b/webapp/public/js/domjudge.js index 68d8a7373b..2f979eb5d5 100644 --- a/webapp/public/js/domjudge.js +++ b/webapp/public/js/domjudge.js @@ -998,14 +998,16 @@ function initializeKeyboardShortcuts() { var parts = window.location.href.split('/'); var lastPart = parts[parts.length - 1]; var params = lastPart.split('?'); - var currentNumber = parseInt(params[0]); - if (isNaN(currentNumber)) { - return; - } if (key === 'j') { - parts[parts.length - 1] = currentNumber + 1; + if (!window.nextEntity) { + return; + } + parts[parts.length - 1] = window.nextEntity; } else if (key === 'k') { - parts[parts.length - 1] = currentNumber - 1; + if (!window.previousEntity) { + return; + } + parts[parts.length - 1] = window.previousEntity; } if (params.length > 1) { parts[parts.length - 1] += '?' + params[1]; @@ -1039,9 +1041,13 @@ function initializeKeyboardShortcuts() { sequence = ''; return; } - if (e.key >= '0' && e.key <= '9') { + if (/^[a-zA-Z0-9_.-]$/.test(e.key)) { sequence += e.key; box.text(type + sequence); + } else if (e.key === 'Backspace') { + e.preventDefault(); + sequence = sequence.slice(0, -1); + box.text(type + sequence); } else { ignore = false; if (box) { @@ -1302,8 +1308,8 @@ function initScoreboardSubmissions() { const linkEl = e.currentTarget; e.preventDefault(); const $modal = $('[data-submissions-modal] .modal').clone(); - const $teamEl = $(`[data-team-external-id="${linkEl.dataset.teamId}"]`); - const $problemEl = $(`[data-problem-external-id="${linkEl.dataset.problemId}"]`); + const $teamEl = $(`tr[data-team-id="${linkEl.dataset.teamId}"]`); + const $problemEl = $(`th[data-problem-id="${linkEl.dataset.problemId}"]`); $modal.find('[data-team]').html($teamEl.data('teamName')); $modal.find('[data-problem-badge]').html($problemEl.data('problemBadge')); $modal.find('[data-problem-name]').html($problemEl.data('problemName')); @@ -1403,7 +1409,7 @@ function initDiffEditor(editorId) { 'onDiffSelectChange': (f) => { select.change((e) => { const noDiff = e.target.value === ""; - const submitId = parseInt(e.target.value); + const submitId = e.target.value; f(submitId, noDiff); }); } @@ -1432,10 +1438,10 @@ function initDiffEditor(editorId) { diffTitle.style.display = 'inline'; diffTag.innerText = selected.dataset.tag; diffLink.href = selected.dataset.url; - diffLink.innerText = `s${submitId}`; + diffLink.innerText = submitId; } }; - updateSelect(parseInt(select[0].value), select[0].value === ""); + updateSelect(select[0].value, select[0].value === ""); editor.onDiffSelectChange(updateSelect); } @@ -1559,7 +1565,7 @@ function initDiffEditorTab(editorId, diffId, submissionId, models) { return; } - const submitId = parseInt(editors[editorId].getDiffSelection()); + const submitId = editors[editorId].getDiffSelection(); const noDiff = editors[editorId].getDiffSelection() === ""; if (noDiff) { setIcon('file'); diff --git a/webapp/src/Controller/API/BalloonController.php b/webapp/src/Controller/API/BalloonController.php index 0ef708184b..6ae826dfe3 100644 --- a/webapp/src/Controller/API/BalloonController.php +++ b/webapp/src/Controller/API/BalloonController.php @@ -59,14 +59,14 @@ public function listAction( foreach ($balloonsData as $b) { /** @var Team $team */ $team = $b['data']['team']; - $teamName = "t" . $team->getTeamid() . ": " . $team->getEffectiveName(); + $teamName = $team->getExternalid() . ": " . $team->getEffectiveName(); $balloons[] = new Balloon( balloonid: $b['data']['balloonid'], time: $b['data']['time'], problem: $b['data']['problem'], contestproblem: $b['data']['contestproblem'], team: $teamName, - teamid: $team->getTeamid(), + teamid: $team->getExternalid(), location: $b['data']['location'], affiliation: $b['data']['affiliation'], affiliationid: $b['data']['affiliationid'], diff --git a/webapp/src/Controller/API/ClarificationController.php b/webapp/src/Controller/API/ClarificationController.php index a502b64c82..6f9df8f8b6 100644 --- a/webapp/src/Controller/API/ClarificationController.php +++ b/webapp/src/Controller/API/ClarificationController.php @@ -249,7 +249,7 @@ public function addAction( $this->em->persist($clarification); $this->em->flush(); - $this->dj->auditlog('clarification', $clarification->getClarid(), 'added', null, null, $contestId); + $this->dj->auditlog('clarification', $clarification->getExternalid(), 'added', null, null, $contest->getExternalid()); $this->eventLogService->log('clarification', $clarification->getClarid(), 'create', $contestId); // Refresh the clarification since the event log service will have unloaded it. diff --git a/webapp/src/Controller/API/JudgehostController.php b/webapp/src/Controller/API/JudgehostController.php index e9b184606f..e1fe673604 100644 --- a/webapp/src/Controller/API/JudgehostController.php +++ b/webapp/src/Controller/API/JudgehostController.php @@ -114,7 +114,7 @@ public function getJudgehostsAction( * Add a new judgehost to the list of judgehosts. * Also restarts (and returns) unfinished judgings. * - * @return array + * @return array * @throws NonUniqueResultException */ #[IsGranted('ROLE_JUDGEHOST')] @@ -127,7 +127,7 @@ public function getJudgehostsAction( items: new OA\Items( properties: [ new OA\Property(property: 'jobid', type: 'integer'), - new OA\Property(property: 'submitid', type: 'integer'), + new OA\Property(property: 'submitid', type: 'string'), ], type: 'object' ) @@ -188,7 +188,7 @@ public function createJudgehostAction(Request $request): array return array_map(fn(Judging $judging) => [ 'jobid' => $judging->getJudgingid(), - 'submitid' => $judging->getSubmission()->getSubmitid(), + 'submitid' => $judging->getSubmission()->getExternalid(), ], $judgings); } @@ -442,8 +442,9 @@ public function updateJudgingAction( $judgingId = $judging->getJudgingid(); $contestId = $judging->getSubmission()->getContest()->getCid(); - $this->dj->auditlog('judging', $judgingId, 'judged', - 'compiler-error', $judgehost->getHostname(), $contestId); + $contestExernalid = $judging->getSubmission()->getContest()->getExternalid(); + $this->dj->auditlog('judging', (string)$judgingId, 'judged', + 'compiler-error', $judgehost->getHostname(), $contestExernalid); $this->maybeUpdateActiveJudging($judging); $this->em->flush(); @@ -459,8 +460,8 @@ public function updateJudgingAction( $problem = $submission->getProblem(); $this->scoreboardService->calculateScoreRow($contest, $team, $problem); - $message = sprintf("submission %d, judging %d: compiler-error", - $submission->getSubmitid(), $judging->getJudgingid()); + $message = sprintf("submission %s, judging %d: compiler-error", + $submission->getExternalid(), $judging->getJudgingid()); $this->dj->alert('reject', $message); }); } @@ -905,9 +906,9 @@ protected function giveBackJudging(int $judgingId, ?Judgehost $judgehost): void $this->em->flush(); } - $this->dj->auditlog('judging', $judgingId, 'given back' + $this->dj->auditlog('judging', (string)$judgingId, 'given back' . ($judgehost === null ? '' : ' for judgehost ' . $judgehost->getHostname()), null, - $judgehost?->getHostname(), $judging->getContest()->getCid()); + $judgehost?->getHostname(), $judging->getContest()->getExternalid()); } } @@ -1118,7 +1119,7 @@ private function addSingleJudgingRun( // this means that these alert messages should be treated as // confidential information. $msg = sprintf("submission %s, judging %s: %s", - $submission->getSubmitid(), $judging->getJudgingid(), $result); + $submission->getExternalid(), $judging->getJudgingid(), $result); $this->dj->alert($result === 'correct' ? 'accept' : 'reject', $msg); // Potentially send a balloon, i.e. if no verification required (case of verification required is @@ -1127,7 +1128,7 @@ private function addSingleJudgingRun( $this->balloonService->updateBalloons($contest, $submission, $judging); } - $this->dj->auditlog('judging', $judging->getJudgingid(), 'judged', $result, $hostname); + $this->dj->auditlog('judging', (string)$judging->getJudgingid(), 'judged', $result, $hostname); } // Send an event for an endtime (and max runtime update). @@ -1223,7 +1224,7 @@ private function maybeUpdateActiveJudging(Judging $judging): void * @return JudgehostFile[] */ #[IsGranted(new Expression("is_granted('ROLE_JURY') or is_granted('ROLE_JUDGEHOST')"))] - #[Rest\Get('/get_files/{type}/{id<\d+>}')] + #[Rest\Get('/get_files/{type}/{id}')] #[OA\Response( response: 200, description: 'The files for the submission, testcase or script.', @@ -1270,7 +1271,7 @@ public function getVersionCommands(string $judgetaskid): array } $submission = $this->em->getRepository(Submission::class) - ->findOneBy(['submitid' => $judgeTask->getSubmitid()]); + ->findByExternalId($judgeTask->getSubmitid()); if (!$submission) { throw new HttpException(500, 'Unknown submission with submitid ' . $judgeTask->getSubmitid()); } @@ -1334,7 +1335,7 @@ public function checkVersions(Request $request, string $judgetaskid): array } $submission = $this->em->getRepository(Submission::class) - ->findOneBy(['submitid' => $judgeTask->getSubmitid()]); + ->findByExternalId($judgeTask->getSubmitid()); if (!$submission) { throw new BadRequestHttpException('Unknown submission with submitid ' . $judgeTask->getSubmitid()); } @@ -1418,7 +1419,8 @@ private function getSourceFiles(string $id): array $queryBuilder = $this->em->createQueryBuilder() ->from(SubmissionFile::class, 'f') ->select('f') - ->andWhere('f.submission = :submitid') + ->join('f.submission', 's') + ->andWhere('s.externalid = :submitid') ->setParameter('submitid', $id) ->orderBy('f.ranknumber'); @@ -1836,7 +1838,7 @@ private function getJudgetasks(string|int|null $jobId, int $max_batchsize, Judge ->getQuery() ->execute(); $this->em->flush(); - $this->dj->auditlog('queuetask', $jobId, 'deleted'); + $this->dj->auditlog('queuetask', (string)$jobId, 'deleted'); } else { return $this->serializeJudgeTasks($judgetasks, $judgehost); } diff --git a/webapp/src/Controller/API/LanguageController.php b/webapp/src/Controller/API/LanguageController.php index 7073e7bace..525b1cb5ab 100644 --- a/webapp/src/Controller/API/LanguageController.php +++ b/webapp/src/Controller/API/LanguageController.php @@ -82,7 +82,7 @@ public function singleAction(Request $request, string $id): Response #[OA\Parameter(ref: '#/components/parameters/id')] public function updateExecutableActions(Request $request, string $id): void { - $language = $this->em->getRepository(Language::class)->find($id); + $language = $this->em->getRepository(Language::class)->findByExternalId($id); if (!$language) { throw new BadRequestHttpException("Unknown language '$id'."); } @@ -124,7 +124,7 @@ public function updateExecutableActions(Request $request, string $id): void $language->getCompileExecutable()->setImmutableExecutable($immutableExecutable); $this->em->flush(); - $this->dj->auditlog('executable', $language->getLangid(), 'updated'); + $this->dj->auditlog('executable', $language->getExternalid(), 'updated'); } #[IsGranted('ROLE_ADMIN')] diff --git a/webapp/src/Controller/API/PrintController.php b/webapp/src/Controller/API/PrintController.php index 221e3a1864..e979fecbd7 100644 --- a/webapp/src/Controller/API/PrintController.php +++ b/webapp/src/Controller/API/PrintController.php @@ -80,10 +80,11 @@ public function printAsTeam( ): JsonResponse { $langid = null; if ($print->language !== null) { + /** @var string|null $langid */ $langid = $this->em ->createQueryBuilder() ->from(Language::class, "l") - ->select("l.langid") + ->select("l.externalid") ->andWhere("l.name = :name") ->setParameter("name", $print->language) ->getQuery() diff --git a/webapp/src/Controller/API/ProblemController.php b/webapp/src/Controller/API/ProblemController.php index 479c96b105..d00ad05f86 100644 --- a/webapp/src/Controller/API/ProblemController.php +++ b/webapp/src/Controller/API/ProblemController.php @@ -281,7 +281,7 @@ public function unlinkProblemAction(Request $request, string $id): Response } $this->em->remove($contestProblem); - $id = [$contestProblem->getCid(), $contestProblem->getProbid()]; + $id = [$contestProblem->getExternalId(), $contestProblem->getExternalId()]; $this->dj->auditlog('contest_problem', implode(', ', $id), 'deleted'); $this->eventLogService->log('problem', $contestProblem->getProbid(), EventLogService::ACTION_DELETE, $cid, @@ -354,7 +354,7 @@ public function linkProblemAction( $this->em->persist($contestProblem); $this->em->flush(); - $fullId = [$contestProblem->getCid(), $contestProblem->getProbid()]; + $fullId = [$contestProblem->getExternalId(), $contestProblem->getExternalId()]; $this->dj->auditlog('contest_problem', implode(', ', $fullId), 'added'); $this->eventLogService->log('problem', $contestProblem->getProbid(), EventLogService::ACTION_CREATE, $cid, diff --git a/webapp/src/Controller/API/ScoreboardController.php b/webapp/src/Controller/API/ScoreboardController.php index 51f0c23a3c..b0f1c6b672 100644 --- a/webapp/src/Controller/API/ScoreboardController.php +++ b/webapp/src/Controller/API/ScoreboardController.php @@ -64,7 +64,7 @@ public function __construct( name: 'category', description: 'Get the scoreboard for only this category', in: 'query', - schema: new OA\Schema(type: 'integer') + schema: new OA\Schema(type: 'string') )] #[OA\Parameter( name: 'country', @@ -76,7 +76,7 @@ public function __construct( name: 'affiliation', description: 'Get the scoreboard for only this affiliation', in: 'query', - schema: new OA\Schema(type: 'integer') + schema: new OA\Schema(type: 'string') )] #[OA\Parameter( name: 'public', @@ -93,11 +93,11 @@ public function __construct( public function getScoreboardAction( Request $request, #[MapQueryParameter] - ?int $category = null, + ?string $category = null, #[MapQueryParameter] ?string $country = null, #[MapQueryParameter] - ?int $affiliation = null, + ?string $affiliation = null, #[MapQueryParameter(name: 'allteams')] bool $allTeams = false, #[MapQueryParameter(name: 'public')] diff --git a/webapp/src/Controller/API/UserController.php b/webapp/src/Controller/API/UserController.php index e3135ce267..2315fca0be 100644 --- a/webapp/src/Controller/API/UserController.php +++ b/webapp/src/Controller/API/UserController.php @@ -410,7 +410,7 @@ protected function addOrUpdateUser(AddUser $addUser, Request $request): Response $this->em->persist($user); $this->em->flush(); - $this->dj->auditlog('user', $user->getUserid(), 'added'); + $this->dj->auditlog('user', $user->getExternalid(), 'added'); return $this->renderCreateData($request, $user, 'user', $user->getUserid()); } diff --git a/webapp/src/Controller/BaseController.php b/webapp/src/Controller/BaseController.php index 00b17e4424..a8c77e4703 100644 --- a/webapp/src/Controller/BaseController.php +++ b/webapp/src/Controller/BaseController.php @@ -8,6 +8,7 @@ use App\Entity\Contest; use App\Entity\ContestProblem; use App\Entity\ExternalIdFromInternalIdInterface; +use App\Entity\HasExternalIdInterface; use App\Entity\Problem; use App\Entity\RankCache; use App\Entity\ScoreCache; @@ -151,7 +152,12 @@ protected function saveEntity( } } - $this->dj->auditlog($auditLogType, $id, $isNewEntity ? 'added' : 'updated'); + if ($entity instanceof HasExternalIdInterface) { + $dataid = $entity->getExternalId(); + } else { + $dataid = $id; + } + $this->dj->auditlog($auditLogType, (string)$dataid, $isNewEntity ? 'added' : 'updated'); } /** @@ -216,7 +222,12 @@ protected function commitDeleteEntity(object $entity, array $primaryKeyData): vo // Add an audit log entry. $auditLogType = Utils::tableForEntity($entity); - $this->dj->auditlog($auditLogType, implode(', ', $primaryKeyData), 'deleted'); + if ($entity instanceof HasExternalIdInterface) { + $dataid = $entity->getExternalId(); + } else { + $dataid = implode(', ', $primaryKeyData); + } + $this->dj->auditlog($auditLogType, $dataid, 'deleted'); // Trigger the delete event. We need to do this before deleting the entity to make // sure we can still find the entity in the table. @@ -296,7 +307,7 @@ protected function commitDeleteEntity(object $entity, array $primaryKeyData): vo * @param Object[] $entities * @param array> $relations * - * @return array{0: bool, 1: array, 2: string[]} + * @return array{0: bool, 1: array, 2: string[], 3: array} */ protected function buildDeleteTree(array $entities, array $relations): array { $isError = false; @@ -305,9 +316,13 @@ protected function buildDeleteTree(array $entities, array $relations): array { $readableType = str_replace('_', ' ', Utils::tableForEntity($entities[0])); $metadata = $this->em->getClassMetadata($entities[0]::class); $primaryKeyData = []; + $externalIdData = []; $messages = []; foreach ($entities as $entity) { $primaryKeyDataTemp = []; + if ($entity instanceof HasExternalIdInterface) { + $externalIdData[] = $entity->getExternalId(); + } foreach ($metadata->getIdentifierColumnNames() as $primaryKeyColumn) { $primaryKeyColumnValue = $propertyAccessor->getValue($entity, $primaryKeyColumn); $primaryKeyDataTemp[] = $primaryKeyColumnValue; @@ -372,7 +387,7 @@ protected function buildDeleteTree(array $entities, array $relations): array { } $primaryKeyData[] = $primaryKeyDataTemp; } - return [$isError, $primaryKeyData, array_values(array_unique($messages))]; + return [$isError, $primaryKeyData, array_values(array_unique($messages)), $externalIdData]; } /** @@ -404,6 +419,7 @@ protected function deleteEntities( $isError, $primaryKeyData, $deleteTreeMessages, + $externalIdData, ] = $this->buildDeleteTree($entities, $relations); if (!empty($deleteTreeMessages)) { $messages = $deleteTreeMessages; @@ -419,7 +435,7 @@ protected function deleteEntities( $this->commitDeleteEntity($entity, $primaryKeyData[$id]); $description = $entity->getShortDescription(); $msgList[] = sprintf('Successfully deleted %s %s "%s"', - $readableType, implode(', ', $primaryKeyData[$id]), $description); + $readableType, $externalIdData[$id] ?? implode(', ', $primaryKeyData[$id]), $description); } $msg = implode("\n", $msgList); @@ -438,7 +454,7 @@ protected function deleteEntities( $data = [ 'type' => $readableType, - 'primaryKey' => implode(', ', array_merge(...$primaryKeyData)), + 'primaryKey' => !empty($externalIdData) ? implode(', ', $externalIdData) : implode(', ', array_merge(...$primaryKeyData)), 'description' => implode(',', $descriptions), 'messages' => $messages, 'isError' => $isError, @@ -547,7 +563,7 @@ protected function addEntityCheckbox(array &$data, object $entity, mixed $identi '', $identifierValue, $checkboxClass - ) + ), ]; } } @@ -649,4 +665,120 @@ protected function processAddFormForExternalIdEntity( return null; } + + /** + * Get the previous and next object IDs for navigation. + * + * @param class-string $entityClass Entity class to query + * @param mixed $currentIdValue Current value of the ID field + * @param string $idField Field to return as the ID (e.g., 'externalid', 'submitid') + * @param array $orderBy Sort criteria as field => direction (e.g., ['e.submittime' => 'ASC', 'e.submitid' => 'ASC']) + * @param bool $filterOnContest Whether to filter results by current contests + * + * @return array{previous: string|int|null, next: string|int|null} + */ + protected function getPreviousAndNextObjectIds( + string $entityClass, + mixed $currentIdValue, + string $idField = 'externalid', + array $orderBy = ['e.externalid' => 'ASC'], + bool $filterOnContest = false, + ): array { + $result = ['previous' => null, 'next' => null]; + + // Fetch the current entity once to get field values + $currentEntity = $this->em->getRepository($entityClass)->findOneBy([$idField => $currentIdValue]); + if ($currentEntity === null) { + return $result; + } + + $accessor = PropertyAccess::createPropertyAccessor(); + + // Pre-compute field values for comparison + $fieldValues = []; + foreach (array_keys($orderBy) as $field) { + $fieldName = str_replace('e.', '', $field); + $fieldValues[$field] = $accessor->getValue($currentEntity, $fieldName); + } + + // Build the comparison conditions based on the sort criteria. + // For multi-column ordering, we need: (col1 < val1) OR (col1 = val1 AND col2 < val2) etc. + $buildComparisonConditions = function (string $operator) use ($orderBy, $fieldValues): array { + $conditions = []; + $parameters = []; + $fields = array_keys($orderBy); + $directions = array_values($orderBy); + + for ($i = 0; $i < count($fields); $i++) { + $equalityParts = []; + // Add equality conditions for all previous columns + for ($j = 0; $j < $i; $j++) { + $field = $fields[$j]; + $paramName = 'eq_' . $j; + $equalityParts[] = "$field = :$paramName"; + $parameters[$paramName] = $fieldValues[$field]; + } + + // Add the comparison for this column + $field = $fields[$i]; + $direction = $directions[$i]; + // For "previous": if ASC, we want < ; if DESC, we want > + // For "next": if ASC, we want > ; if DESC, we want < + $compOp = ($operator === 'previous') + ? ($direction === 'ASC' ? '<' : '>') + : ($direction === 'ASC' ? '>' : '<'); + $paramName = 'cmp_' . $i; + $comparisonPart = "$field $compOp :$paramName"; + $parameters[$paramName] = $fieldValues[$field]; + + if (!empty($equalityParts)) { + $conditions[] = '(' . implode(' AND ', $equalityParts) . ' AND ' . $comparisonPart . ')'; + } else { + $conditions[] = '(' . $comparisonPart . ')'; + } + } + + return ['condition' => implode(' OR ', $conditions), 'parameters' => $parameters]; + }; + + foreach (['previous', 'next'] as $direction) { + $qb = $this->em->createQueryBuilder() + ->select("e.$idField") + ->from($entityClass, 'e'); + + // Build and apply the comparison conditions + $comp = $buildComparisonConditions($direction); + if (!empty($comp['condition'])) { + $qb->andWhere($comp['condition']); + foreach ($comp['parameters'] as $param => $value) { + $qb->setParameter($param, $value); + } + } + + // Apply contest filter + if ($filterOnContest && $contest = $this->dj->getCurrentContest()) { + $qb->andWhere('e.contest = :contest') + ->setParameter('contest', $contest); + } + + // Apply ordering (reversed for previous) + foreach ($orderBy as $field => $dir) { + $actualDir = $direction === 'previous' + ? ($dir === 'ASC' ? 'DESC' : 'ASC') + : $dir; + $qb->addOrderBy($field, $actualDir); + } + + $qb->setMaxResults(1); + + try { + $value = $qb->getQuery()->getSingleScalarResult(); + $result[$direction] = $value; + } catch (NoResultException) { + // No previous/next found, leave as null + } + } + + return $result; + } } diff --git a/webapp/src/Controller/Jury/AnalysisController.php b/webapp/src/Controller/Jury/AnalysisController.php index 26fa10b436..8d4bf6c97d 100644 --- a/webapp/src/Controller/Jury/AnalysisController.php +++ b/webapp/src/Controller/Jury/AnalysisController.php @@ -55,12 +55,12 @@ public function indexAction( $delayedJudgings = $em->createQueryBuilder() ->from(Submission::class, 's') ->innerJoin(Judging::class, 'j', Expr\Join::WITH, 's.submitid = j.submission') - ->select('s.submitid, MIN(j.judgingid) AS judgingid, s.submittime, MIN(j.starttime) - s.submittime AS timediff, COUNT(j.judgingid) AS num_judgings') + ->select('s.externalid as submitexternalid, MIN(j.judgingid) AS judgingid, s.submittime, MIN(j.starttime) - s.submittime AS timediff, COUNT(j.judgingid) AS num_judgings') ->andWhere('s.contest = :contest') ->setParameter('contest', $contest) ->andWhere('s.team IN (:teams)') ->setParameter('teams', $teams) - ->groupBy('s.submitid') + ->groupBy('s.externalid') ->andHaving('timediff > :timediff') ->setParameter('timediff', $delayedTimeDiff) ->orderBy('timediff', 'DESC') @@ -83,8 +83,10 @@ public function indexAction( } #[Route(path: '/team/{team}', name: 'analysis_team')] - public function teamAction(Team $team): Response - { + public function teamAction( + #[MapEntity(mapping: ['team' => 'externalid'])] + Team $team, + ): Response { $contest = $this->dj->getCurrentContest(); if ($contest === null) { @@ -100,7 +102,7 @@ public function teamAction(Team $team): Response #[Route(path: '/problem/{probid}', name: 'analysis_problem')] public function problemAction( - #[MapEntity(id: 'probid')] + #[MapEntity(mapping: ['probid' => 'externalid'])] Problem $problem, #[MapQueryParameter] ?string $view = null diff --git a/webapp/src/Controller/Jury/AuditLogController.php b/webapp/src/Controller/Jury/AuditLogController.php index ad000bc413..ec1d670958 100644 --- a/webapp/src/Controller/Jury/AuditLogController.php +++ b/webapp/src/Controller/Jury/AuditLogController.php @@ -79,7 +79,7 @@ public function indexAction( $cid = $logline->getCid(); if ($cid) { - $data['where']['value'] = "c" . $cid; + $data['where']['value'] = $cid; $data['where']['sortvalue'] = $cid; $data['where']['link'] = $this->generateUrl('jury_contest', ['contestId' => $cid]); } else { @@ -148,11 +148,6 @@ private function generateDatatypeUrl(string $type, int|string|null $id): ?string case 'team_category': return $this->generateUrl('jury_team_category', ['categoryId' => $id]); case 'user': - // Pre 6.1, usernames were stored instead of numeric IDs. - if (!is_numeric($id)) { - $user = $this->em->getRepository(User::class)->findOneBy(['username'=>$id]); - $id = $user->getUserId(); - } return $this->generateUrl('jury_user', ['userId' => $id]); case 'testcase': // For testcase audit logs, the ID is actually the problem ID diff --git a/webapp/src/Controller/Jury/BalloonController.php b/webapp/src/Controller/Jury/BalloonController.php index 9098772310..1520e54c2f 100644 --- a/webapp/src/Controller/Jury/BalloonController.php +++ b/webapp/src/Controller/Jury/BalloonController.php @@ -88,7 +88,7 @@ public function indexAction(BalloonService $balloonService): Response $filteredAffiliations = $this->em->createQueryBuilder() ->from(TeamAffiliation::class, 'a') ->select('a') - ->where('a.affilid IN (:affilIds)') + ->where('a.externalid IN (:affilIds)') ->setParameter('affilIds', $filters['affiliation-id']) ->getQuery() ->getResult(); @@ -123,7 +123,7 @@ public function indexAction(BalloonService $balloonService): Response $filteredCategories = $this->em->createQueryBuilder() ->from(TeamCategory::class, 'c') ->select('c') - ->where('c.categoryid IN (:categories)') + ->where('c.externalid IN (:categories)') ->setParameter('categories', $filters['category-id']) ->getQuery() ->getResult(); @@ -131,7 +131,7 @@ public function indexAction(BalloonService $balloonService): Response $availableCategories = $this->em->createQueryBuilder() ->from(TeamCategory::class, 'c') ->select('c') - ->where('c.categoryid NOT IN (:categories)') + ->where('c.externalid NOT IN (:categories)') ->setParameter('categories', $filters['category-id']) ->getQuery() ->getResult(); @@ -145,11 +145,11 @@ public function indexAction(BalloonService $balloonService): Response } $defaultCategories = $this->em->createQueryBuilder() ->from(TeamCategory::class, 'c') - ->select('c.categoryid') + ->select('c.externalid') ->where('c.visible = true') ->getQuery() ->getArrayResult(); - $defaultCategories = array_column($defaultCategories, "categoryid"); + $defaultCategories = array_column($defaultCategories, "externalid"); return $this->render('jury/balloons.html.twig', [ 'refresh' => [ diff --git a/webapp/src/Controller/Jury/ClarificationController.php b/webapp/src/Controller/Jury/ClarificationController.php index 4eda5224e2..6268b7b081 100644 --- a/webapp/src/Controller/Jury/ClarificationController.php +++ b/webapp/src/Controller/Jury/ClarificationController.php @@ -2,6 +2,7 @@ namespace App\Controller\Jury; +use App\Controller\BaseController; use App\Entity\Clarification; use App\Entity\Contest; use App\Entity\Problem; @@ -20,19 +21,23 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Attribute\MapQueryParameter; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; #[IsGranted('ROLE_CLARIFICATION_RW')] #[Route(path: '/jury/clarifications')] -class ClarificationController extends AbstractController +class ClarificationController extends BaseController { public function __construct( - protected readonly EntityManagerInterface $em, - protected readonly DOMJudgeService $dj, + EntityManagerInterface $em, + DOMJudgeService $dj, protected readonly ConfigurationService $config, - protected readonly EventLogService $eventLogService - ) {} + EventLogService $eventLogService, + KernelInterface $kernel, + ) { + parent::__construct($em, $eventLogService, $dj, $kernel); + } #[Route(path: '', name: 'jury_clarifications')] public function indexAction( @@ -43,12 +48,12 @@ public function indexAction( ): Response { $categories = $this->config->get('clar_categories'); if ($contest = $this->dj->getCurrentContest()) { - $contestIds = [$contest->getCid()]; + $contestIds = [$contest->getExternalid()]; } else { $contestIds = array_keys($this->dj->getCurrentContests()); - // cid -1 will never happen, but otherwise the array is empty and that is not supported. + // cid @ will never happen, but otherwise the array is empty and that is not supported. if (empty($contestIds)) { - $contestIds = [-1]; + $contestIds = ['@']; } } @@ -59,9 +64,10 @@ public function indexAction( $queryBuilder = $this->em->createQueryBuilder() ->from(Clarification::class, 'clar') ->leftJoin('clar.problem', 'p') + ->innerJoin('clar.contest', 'c') ->leftJoin('p.contest_problems', 'cp', Join::WITH, 'cp.contest = clar.contest') ->select('clar', 'p', 'cp') - ->andWhere('clar.contest in (:contestIds)') + ->andWhere('c.externalid in (:contestIds)') ->setParameter('contestIds', $contestIds) ->orderBy('clar.submittime', 'DESC') ->addOrderBy('clar.clarid', 'DESC'); @@ -113,10 +119,10 @@ public function indexAction( ]); } - #[Route(path: '/{id<\d+>}', name: 'jury_clarification')] - public function viewAction(Request $request, int $id): Response + #[Route(path: '/{id}', name: 'jury_clarification')] + public function viewAction(Request $request, string $id): Response { - $clarification = $this->em->getRepository(Clarification::class)->find($id); + $clarification = $this->em->getRepository(Clarification::class)->findByExternalId($id); if (!$clarification) { throw new NotFoundHttpException(sprintf('Clarification with ID %s not found', $id)); } @@ -132,9 +138,14 @@ public function viewAction(Request $request, int $id): Response $parameters = ['list' => []]; + if ($clarification->getProblem()?->getExternalid()) { + $subject = sprintf('%s|%s', $clarification->getContest()->getExternalid(), $clarification->getProblem()->getExternalid()); + } else { + $subject = sprintf('%s#%s', $clarification->getContest()->getExternalid(), $clarification->getCategory()); + } $formData = [ 'recipient' => JuryClarificationType::RECIPIENT_MUST_SELECT, - 'subject' => sprintf('%s-%s', $clarification->getContest()->getCid(), $clarification->getProblem()?->getProbid() ?? $clarification->getCategory()), + 'subject' => $subject, ]; if ($clarification->getRecipient()) { $formData['recipient'] = $clarification->getRecipient()->getTeamid(); @@ -194,14 +205,14 @@ public function viewAction(Request $request, int $id): Response $data['subjectlink'] = null; if ($clar->getProblem()) { if ($clar->getContestProblem()) { - $concernssubject = $contest->getCid() . "-" . $clar->getProblem()->getProbid(); + $concernssubject = $contest->getExternalid() . "|" . $clar->getProblem()->getExternalid(); } else { // Very special case, this problem is unlinked. $concernssubject = ""; } - $data['subjectlink'] = $this->generateUrl('jury_problem', ['probId' => $clar->getProblem()->getProbid()]); + $data['subjectlink'] = $this->generateUrl('jury_problem', ['probId' => $clar->getProblem()->getExternalid()]); } elseif ($clar->getCategory()) { - $concernssubject = $contest->getCid() . "-" . $clar->getCategory(); + $concernssubject = $contest->getExternalid() . "#" . $clar->getCategory(); } else { $concernssubject = ""; } @@ -230,10 +241,15 @@ public function viewAction(Request $request, int $id): Response ->getQuery() ->getSingleResult()['jury_member']; + $parameters['previousNext'] = $this->getPreviousAndNextObjectIds( + Clarification::class, + $clarification->getExternalid(), + ); + return $this->render('jury/clarification.html.twig', $parameters); } - #[Route(path: '/send', name: 'jury_clarification_new')] + #[Route(path: '/send', name: 'jury_clarification_new', priority: 1)] public function composeClarificationAction( Request $request, #[MapQueryParameter] @@ -256,12 +272,12 @@ public function composeClarificationAction( return $this->render('jury/clarification_new.html.twig', ['form' => $form->createView()]); } - #[Route(path: '/{clarId<\d+>}/claim', name: 'jury_clarification_claim')] - public function toggleClaimAction(Request $request, int $clarId): Response + #[Route(path: '/{clarId}/claim', name: 'jury_clarification_claim')] + public function toggleClaimAction(Request $request, string $clarId): Response { - $clarification = $this->em->getReference(Clarification::class, $clarId); + $clarification = $this->em->getRepository(Clarification::class)->findByExternalId($clarId); if (!$clarification) { - throw new NotFoundHttpException(sprintf('Clarification with ID %d not found', $clarId)); + throw new NotFoundHttpException(sprintf('Clarification with ID %s not found', $clarId)); } if ($request->request->getBoolean('claimed')) { @@ -275,12 +291,12 @@ public function toggleClaimAction(Request $request, int $clarId): Response } } - #[Route(path: '/{clarId<\d+>}/set-answered', name: 'jury_clarification_set_answered')] - public function toggleAnsweredAction(Request $request, int $clarId): Response + #[Route(path: '/{clarId}/set-answered', name: 'jury_clarification_set_answered')] + public function toggleAnsweredAction(Request $request, string $clarId): Response { - $clarification = $this->em->getReference(Clarification::class, $clarId); + $clarification = $this->em->getRepository(Clarification::class)->findByExternalId($clarId); if (!$clarification) { - throw new NotFoundHttpException(sprintf('Clarification with ID %d not found', $clarId)); + throw new NotFoundHttpException(sprintf('Clarification with ID %s not found', $clarId)); } $answered = $request->request->getBoolean('answered'); @@ -294,27 +310,33 @@ public function toggleAnsweredAction(Request $request, int $clarId): Response } } - #[Route(path: '/{clarId<\d+>}/change-subject', name: 'jury_clarification_change_subject')] - public function changeSubjectAction(Request $request, int $clarId): Response + #[Route(path: '/{clarId}/change-subject', name: 'jury_clarification_change_subject')] + public function changeSubjectAction(Request $request, string $clarId): Response { - $clarification = $this->em->getReference(Clarification::class, $clarId); + $clarification = $this->em->getRepository(Clarification::class)->findByExternalId($clarId); if (!$clarification) { - throw new NotFoundHttpException(sprintf('Clarification with ID %d not found', $clarId)); + throw new NotFoundHttpException(sprintf('Clarification with ID %s not found', $clarId)); } $subject = $request->request->get('subject'); - [$cid, $probid] = explode('-', $subject); + $problemId = null; + $category = null; + if (str_contains($subject, '#')) { + [$cid, $category] = explode('#', $subject); + } else { + [$cid, $problemId] = explode('|', $subject); + } - $contest = $this->em->getReference(Contest::class, $cid); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($cid); $clarification->setContest($contest); - if (ctype_digit($probid)) { - $problem = $this->em->getReference(Problem::class, $probid); + if ($problemId) { + $problem = $this->em->getRepository(Problem::class)->findByExternalId($problemId); $clarification->setProblem($problem); $clarification->setCategory(null); } else { $clarification->setProblem(null); - $clarification->setCategory($probid); + $clarification->setCategory($category); } $this->em->flush(); @@ -322,10 +344,10 @@ public function changeSubjectAction(Request $request, int $clarId): Response return $this->redirectToRoute('jury_clarification', ['id' => $clarId]); } - #[Route(path: '/{clarId<\d+>}/change-queue', name: 'jury_clarification_change_queue')] - public function changeQueueAction(Request $request, int $clarId): Response + #[Route(path: '/{clarId}/change-queue', name: 'jury_clarification_change_queue')] + public function changeQueueAction(Request $request, string $clarId): Response { - $clarification = $this->em->getReference(Clarification::class, $clarId); + $clarification = $this->em->getRepository(Clarification::class)->findByExternalId($clarId); if (!$clarification) { throw new NotFoundHttpException(sprintf('Clarification with ID %d not found', $clarId)); } @@ -363,25 +385,31 @@ protected function processSubmittedClarification( if (empty($recipient)) { $recipient = null; } else { - $team = $this->em->getReference(Team::class, $recipient); + $team = $this->em->getRepository(Team::class)->findByExternalId($recipient); $clarification->setRecipient($team); } $subject = $formData['subject']; - [$cid, $probid] = explode('-', $subject); + $problemId = null; + $category = null; + if (str_contains($subject, '#')) { + [$cid, $category] = explode('#', $subject); + } else { + [$cid, $problemId] = explode('|', $subject); + } - $contest = $this->em->getReference(Contest::class, $cid); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($cid); $clarification->setContest($contest); - if (ctype_digit($probid)) { - $problem = $this->em->getReference(Problem::class, $probid); + if ($problemId) { + $problem = $this->em->getRepository(Problem::class)->findByExternalId($problemId); $clarification->setProblem($problem); $clarification->setCategory(null); } else { $clarification->setProblem(null); - if ($probid !== "") { - $clarification->setCategory($probid); + if ($category !== "") { + $clarification->setCategory($category); } else { $clarification->setCategory(null); } @@ -410,8 +438,8 @@ protected function processSubmittedClarification( $this->em->flush(); $clarId = $clarification->getClarId(); - $this->dj->auditlog('clarification', $clarId, 'added', null, null, $cid); - $this->eventLogService->log('clarification', $clarId, 'create', (int)$cid); + $this->dj->auditlog('clarification', $clarification->getExternalid(), 'added', null, null, $contest->getExternalid()); + $this->eventLog->log('clarification', $clarId, 'create', $contest->getCid()); // Reload clarification to make sure we have a fresh one after calling the event log service. $clarification = $this->em->getRepository(Clarification::class)->find($clarId); diff --git a/webapp/src/Controller/Jury/ContestController.php b/webapp/src/Controller/Jury/ContestController.php index b8c4c5e419..6625286ce6 100644 --- a/webapp/src/Controller/Jury/ContestController.php +++ b/webapp/src/Controller/Jury/ContestController.php @@ -81,8 +81,7 @@ public function indexAction(Request $request): Response ->getQuery()->getResult(); $table_fields = [ - 'cid' => ['title' => 'CID', 'sort' => true], - 'externalid' => ['title' => "external ID", 'sort' => true], + 'externalid' => ['title' => "ID", 'sort' => true], 'shortname' => ['title' => 'shortname', 'sort' => true], 'name' => ['title' => 'name', 'sort' => true], 'scoreboard_type' => ['title' => 'scoreboard type', 'sort' => true], @@ -151,7 +150,7 @@ public function indexAction(Request $request): Response 'icon' => 'file-' . $contest->getContestProblemsetType(), 'title' => 'view contest problemset document', 'link' => $this->generateUrl('jury_contest_problemset', [ - 'cid' => $contest->getCid(), + 'cid' => $contest->getExternalid(), ]) ]; } else { @@ -170,14 +169,14 @@ public function indexAction(Request $request): Response 'icon' => 'edit', 'title' => 'edit this contest', 'link' => $this->generateUrl('jury_contest_edit', [ - 'contestId' => $contest->getCid(), + 'contestId' => $contest->getExternalid(), ]) ]; $contestactions[] = [ 'icon' => 'trash-alt', 'title' => 'delete this contest', 'link' => $this->generateUrl('jury_contest_delete', [ - 'contestId' => $contest->getCid(), + 'contestId' => $contest->getExternalid(), ]), 'ajaxModal' => true, ]; @@ -275,13 +274,13 @@ public function indexAction(Request $request): Response if (!$contest->getEnabled()) { $styles[] = 'disabled'; } - if (in_array($contest->getCid(), array_keys($currentContests))) { + if (in_array($contest->getExternalid(), array_keys($currentContests))) { $styles[] = 'highlight'; } $contests_table[] = [ 'data' => $contestdata, 'actions' => $contestactions, - 'link' => $this->generateUrl('jury_contest', ['contestId' => $contest->getCid()]), + 'link' => $this->generateUrl('jury_contest', ['contestId' => $contest->getExternalid()]), 'cssclass' => implode(' ', $styles), ]; } @@ -305,10 +304,10 @@ public function indexAction(Request $request): Response ]); } - #[Route(path: '/{contestId<\d+>}', name: 'jury_contest')] - public function viewAction(Request $request, int $contestId): Response + #[Route(path: '/{contestId}', name: 'jury_contest')] + public function viewAction(Request $request, string $contestId): Response { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); } @@ -353,6 +352,10 @@ public function viewAction(Request $request, int $contestId): Response return $this->render('jury/contest.html.twig', [ 'contest' => $contest, + 'previousNext' => $this->getPreviousAndNextObjectIds( + Contest::class, + $contest->getExternalid(), + ), 'allowRemovedIntervals' => $this->getParameter('removed_intervals'), 'removedIntervalForm' => $form, 'removedIntervals' => $removedIntervals, @@ -368,7 +371,7 @@ public function toggleSubmitAction( string $contestId, string $type ): Response { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); } @@ -401,7 +404,7 @@ public function toggleSubmitAction( } $this->em->flush(); - $this->dj->auditlog('contest', $contestId, $label, $value ? 'yes' : 'no'); + $this->dj->auditlog('contest', $contest->getExternalid(), $label, $value ? 'yes' : 'no'); return $this->redirectToLocalReferrer( $router, $request, @@ -409,10 +412,10 @@ public function toggleSubmitAction( ); } - #[Route(path: '/{contestId<\d+>}/remove-interval/{intervalId}', name: 'jury_contest_remove_interval', methods: ['POST'])] - public function removeIntervalAction(int $contestId, int $intervalId): RedirectResponse + #[Route(path: '/{contestId}/remove-interval/{intervalId}', name: 'jury_contest_remove_interval', methods: ['POST'])] + public function removeIntervalAction(string $contestId, int $intervalId): RedirectResponse { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); } @@ -437,14 +440,14 @@ public function removeIntervalAction(int $contestId, int $intervalId): RedirectR $this->addFlash('scoreboard_refresh', 'After removing a removed time interval, it is ' . 'necessary to recalculate any cached scoreboards.'); - return $this->redirectToRoute('jury_contest', ['contestId' => $contest->getCid()]); + return $this->redirectToRoute('jury_contest', ['contestId' => $contest->getExternalid()]); } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{contestId<\d+>}/edit', name: 'jury_contest_edit')] - public function editAction(Request $request, int $contestId): Response + #[Route(path: '/{contestId}/edit', name: 'jury_contest_edit')] + public function editAction(Request $request, string $contestId): Response { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); } @@ -466,7 +469,7 @@ public function editAction(Request $request, int $contestId): Response if ($problems = $contestData['problems'] ?? null) { $existingProblemIndices = []; foreach ($contest->getProblems() as $index => $problem) { - $existingProblemIndices[$problem->getProbId()] = $index; + $existingProblemIndices[$problem->getExternalId()] = $index; } $indexForNew = $contest->getProblems()->count(); $newProblems = []; @@ -566,7 +569,7 @@ public function editAction(Request $request, int $contestId): Response $this->eventLogService->log('problems', $problem->getProbid(), EventLogService::ACTION_DELETE, $contest->getCid(), null, null, false); } - return $this->redirectToRoute('jury_contest', ['contestId' => $contest->getcid()]); + return $this->redirectToRoute('jury_contest', ['contestId' => $contest->getExternalId()]); } $this->em->refresh($contest); @@ -578,10 +581,10 @@ public function editAction(Request $request, int $contestId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{contestId<\d+>}/delete', name: 'jury_contest_delete')] - public function deleteAction(Request $request, int $contestId): Response + #[Route(path: '/{contestId}/delete', name: 'jury_contest_delete')] + public function deleteAction(Request $request, string $contestId): Response { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); } @@ -595,13 +598,10 @@ public function deleteAction(Request $request, int $contestId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{contestId<\d+>}/problems/{probId<\d+>}/delete', name: 'jury_contest_problem_delete')] - public function deleteProblemAction(Request $request, int $contestId, int $probId): Response + #[Route(path: '/{contestId}/problems/{probId}/delete', name: 'jury_contest_problem_delete')] + public function deleteProblemAction(Request $request, string $contestId, string $probId): Response { - $contestProblem = $this->em->getRepository(ContestProblem::class)->find([ - 'contest' => $contestId, - 'problem' => $probId - ]); + $contestProblem = $this->em->getRepository(ContestProblem::class)->findByProblemAndContest($contestId, $probId); if (!$contestProblem) { throw new NotFoundHttpException( sprintf('Contest problem with contest ID %s and problem ID %s not found', @@ -618,7 +618,7 @@ public function deleteProblemAction(Request $request, int $contestId, int $probI } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/add', name: 'jury_contest_add')] + #[Route(path: '/add', name: 'jury_contest_add', priority: 1)] public function addAction(Request $request): Response { $contest = new Contest(); @@ -631,7 +631,7 @@ public function addAction(Request $request): Response if ($response = $this->processAddFormForExternalIdEntity( $form, $contest, - fn () => $this->generateUrl('jury_contest', ['contestId' => $contest->getcid()]), + fn () => $this->generateUrl('jury_contest', ['contestId' => $contest->getExternalid()]), function () use ($form, $contest) { $response = $this->checkTimezones($form); if ($response !== null) { @@ -675,12 +675,12 @@ function () use ($form, $contest) { ]); } - #[Route(path: '/{contestId<\d+>}/prefetch', name: 'jury_contest_prefetch')] - public function prefetchAction(Request $request, int $contestId): Response + #[Route(path: '/{contestId}/prefetch', name: 'jury_contest_prefetch')] + public function prefetchAction(Request $request, string $contestId): Response { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if ($contest === null) { - throw new BadRequestHttpException("Contest with cid=$contestId not found."); + throw new BadRequestHttpException("Contest with ID $contestId not found."); } $judgehosts = $this->em->getRepository(Judgehost::class)->findBy([ 'enabled' => true, @@ -756,11 +756,11 @@ public function prefetchAction(Request $request, int $contestId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{contestId<\d+>}/finalize', name: 'jury_contest_finalize')] - public function finalizeAction(Request $request, int $contestId): Response + #[Route(path: '/{contestId}/finalize', name: 'jury_contest_finalize')] + public function finalizeAction(Request $request, string $contestId): Response { /** @var Contest $contest */ - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); $blockers = []; if (Utils::difftime((float)$contest->getEndtime(), Utils::now()) > 0) { $blockers[] = sprintf('Contest not ended yet (will end at %s)', @@ -771,24 +771,24 @@ public function finalizeAction(Request $request, int $contestId): Response $submissionIds = array_map(fn(array $data) => $data['submitid'], $this->em->createQueryBuilder() ->from(Submission::class, 's') ->join('s.judgings', 'j', Join::WITH, 'j.valid = 1') - ->select('s.submitid') + ->select('s.externalid') ->andWhere('s.contest = :contest') ->andWhere('s.valid = true') ->andWhere('j.result IS NULL') ->setParameter('contest', $contest) - ->orderBy('s.submitid') + ->orderBy('s.externalid') ->getQuery() ->getResult() ); if (count($submissionIds) > 0) { - $blockers[] = 'Unjudged submissions found: s' . implode(', s', $submissionIds); + $blockers[] = 'Unjudged submissions found: ' . implode(', ', $submissionIds); } /** @var int[] $clarificationIds */ $clarificationIds = array_map(fn(array $data) => $data['clarid'], $this->em->createQueryBuilder() ->from(Clarification::class, 'c') - ->select('c.clarid') + ->select('c.externalid') ->andWhere('c.contest = :contest') ->andWhere('c.answered = false') ->setParameter('contest', $contest) @@ -810,9 +810,9 @@ public function finalizeAction(Request $request, int $contestId): Response if ($form->isSubmitted() && $form->isValid()) { $contest->setFinalizetime(Utils::now()); $this->em->flush(); - $this->dj->auditlog('contest', $contest->getCid(), 'finalized', + $this->dj->auditlog('contest', $contest->getExternalid(), 'finalized', $contest->getFinalizecomment()); - return $this->redirectToRoute('jury_contest', ['contestId' => $contest->getCid()]); + return $this->redirectToRoute('jury_contest', ['contestId' => $contest->getExternalid()]); } } @@ -824,14 +824,14 @@ public function finalizeAction(Request $request, int $contestId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{contestId<\d+>}/{time}/doNow', name: 'jury_contest_donow')] - public function doNowAction(Request $request, int $contestId, string $time): Response + #[Route(path: '/{contestId}/{time}/doNow', name: 'jury_contest_donow')] + public function doNowAction(Request $request, string $contestId, string $time): Response { $times = ['activate', 'start', 'freeze', 'end', 'unfreeze', 'finalize', 'deactivate']; $start_actions = ['delay_start', 'resume_start']; $actions = array_merge($times, $start_actions); - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); } @@ -841,12 +841,12 @@ public function doNowAction(Request $request, int $contestId, string $time): Res } if ($time === 'finalize') { - return $this->redirectToRoute('jury_contest_finalize', ['contestId' => $contest->getCid()]); + return $this->redirectToRoute('jury_contest_finalize', ['contestId' => $contest->getExternalid()]); } $now = (int)floor(Utils::now()); $nowstring = date('Y-m-d H:i:s ', $now) . date_default_timezone_get(); - $this->dj->auditlog('contest', $contest->getCid(), $time . ' now', $nowstring); + $this->dj->auditlog('contest', $contest->getExternalid(), $time . ' now', $nowstring); // Special case delay/resume start (only sets/unsets starttime_undefined). $maxSeconds = Contest::STARTTIME_UPDATE_MIN_SECONDS_BEFORE; @@ -919,10 +919,10 @@ public function doNowAction(Request $request, int $contestId, string $time): Res return $this->redirectToRoute('jury_contests'); } - #[Route(path: '/{contestId<\d+>}/request-remaining', name: 'jury_contest_request_remaining')] - public function requestRemainingRunsWholeContestAction(int $contestId): RedirectResponse + #[Route(path: '/{contestId}/request-remaining', name: 'jury_contest_request_remaining')] + public function requestRemainingRunsWholeContestAction(string $contestId): RedirectResponse { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); } @@ -930,13 +930,10 @@ public function requestRemainingRunsWholeContestAction(int $contestId): Redirect return $this->redirectToRoute('jury_contest', ['contestId' => $contestId]); } - #[Route(path: '/{contestId<\d+>}/problems/{probId<\d+>}/request-remaining', name: 'jury_contest_problem_request_remaining')] - public function requestRemainingRunsContestProblemAction(int $contestId, int $probId): RedirectResponse + #[Route(path: '/{contestId}/problems/{probId}/request-remaining', name: 'jury_contest_problem_request_remaining')] + public function requestRemainingRunsContestProblemAction(string $contestId, string $probId): RedirectResponse { - $contestProblem = $this->em->getRepository(ContestProblem::class)->find([ - 'contest' => $contestId, - 'problem' => $probId - ]); + $contestProblem = $this->em->getRepository(ContestProblem::class)->findByProblemAndContest($contestId, $probId); if (!$contestProblem) { throw new NotFoundHttpException( sprintf('Contest problem with contest ID %s and problem ID %s not found', @@ -987,23 +984,23 @@ private function checkTimezones(FormInterface $form): ?Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{contestId<\d+>}/lock', name: 'jury_contest_lock')] - public function lockAction(Request $request, int $contestId): Response + #[Route(path: '/{contestId}/lock', name: 'jury_contest_lock')] + public function lockAction(Request $request, string $contestId): Response { return $this->doLock($contestId, true); } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{contestId<\d+>}/unlock', name: 'jury_contest_unlock')] - public function unlockAction(Request $request, int $contestId): Response + #[Route(path: '/{contestId}/unlock', name: 'jury_contest_unlock')] + public function unlockAction(Request $request, string $contestId): Response { return $this->doLock($contestId, false); } - #[Route(path: '/{contestId<\d+>}/samples.zip', name: 'jury_contest_samples_data_zip')] - public function samplesDataZipAction(Request $request, int $contestId): Response + #[Route(path: '/{contestId}/samples.zip', name: 'jury_contest_samples_data_zip')] + public function samplesDataZipAction(Request $request, string $contestId): Response { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); } @@ -1011,14 +1008,14 @@ public function samplesDataZipAction(Request $request, int $contestId): Response return $this->dj->getSamplesZipForContest($contest); } - private function doLock(int $contestId, bool $locked): Response + private function doLock(string $contestId, bool $locked): Response { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found.', $contestId)); } - $this->dj->auditlog('contest', $contest->getCid(), $locked ? 'lock' : 'unlock'); + $this->dj->auditlog('contest', $contest->getExternalid(), $locked ? 'lock' : 'unlock'); $contest->setIsLocked($locked); $this->em->flush(); @@ -1030,25 +1027,25 @@ private function doLock(int $contestId, bool $locked): Response return $this->redirectToRoute('jury_contest', ['contestId' => $contestId]); } - #[Route(path: '/{contestId<\d+>}/{type}-scoreboard.zip', name: 'jury_scoreboard_data_zip')] + #[Route(path: '/{contestId}/{type}-scoreboard.zip', name: 'jury_scoreboard_data_zip')] public function scoreboardDataZipAction( - int $contestId, + string $contestId, string $type, RequestStack $requestStack, Request $request, ScoreboardService $scoreboardService ): Response { - $contest = $this->em->getRepository(Contest::class)->find($contestId); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($contestId); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $contestId)); } return $this->dj->getScoreboardZip($request, $requestStack, $contest, $scoreboardService, $type === 'unfrozen'); } - #[Route(path: '/{cid<\d+>}/problemset', name: 'jury_contest_problemset')] - public function viewProblemsetAction(int $cid): StreamedResponse + #[Route(path: '/{cid}/problemset', name: 'jury_contest_problemset')] + public function viewProblemsetAction(string $cid): StreamedResponse { - $contest = $this->em->getRepository(Contest::class)->find($cid); + $contest = $this->em->getRepository(Contest::class)->findByExternalId($cid); if (!$contest) { throw new NotFoundHttpException(sprintf('Contest with ID %s not found', $cid)); } diff --git a/webapp/src/Controller/Jury/ExecutableController.php b/webapp/src/Controller/Jury/ExecutableController.php index 27a6c32ebc..c2c60bf9c8 100644 --- a/webapp/src/Controller/Jury/ExecutableController.php +++ b/webapp/src/Controller/Jury/ExecutableController.php @@ -368,6 +368,12 @@ public function viewAction( 'uploadForm' => $uploadForm->createView(), 'selected' => $index, 'executable' => $executable, + 'previousNext' => $this->getPreviousAndNextObjectIds( + Executable::class, + $executable->getExecid(), + 'execid', + ['e.execid' => 'ASC'], + ), 'default_compare' => (string)$this->config->get('default_compare'), 'default_run' => (string)$this->config->get('default_run'), 'default_full_debug' => (string)$this->config->get('default_full_debug'), diff --git a/webapp/src/Controller/Jury/ImportExportController.php b/webapp/src/Controller/Jury/ImportExportController.php index 6f07dfd71a..ad0df070cb 100644 --- a/webapp/src/Controller/Jury/ImportExportController.php +++ b/webapp/src/Controller/Jury/ImportExportController.php @@ -168,7 +168,7 @@ public function indexAction(Request $request): Response ); $allMessages = array_merge($allMessages, $messages); if ($newProblem) { - $this->dj->auditlog('problem', $newProblem->getProbid(), 'upload zip', + $this->dj->auditlog('problem', $newProblem->getExternalid(), 'upload zip', $clientName); } else { $this->postMessages($allMessages); @@ -184,7 +184,7 @@ public function indexAction(Request $request): Response $this->postMessages($allMessages); if ($newProblem !== null) { - return $this->redirectToRoute('jury_problem', ['probId' => $newProblem->getProbid()]); + return $this->redirectToRoute('jury_problem', ['probId' => $newProblem->getExternalid()]); } else { return $this->redirectToRoute('jury_problems'); } @@ -400,7 +400,7 @@ protected function getResultsHtml( ->getResult(); $categoryIds = []; foreach ($categories as $category) { - $categoryIds[] = $category->getCategoryid(); + $categoryIds[] = $category->getExternalid(); } $contest = $this->dj->getCurrentContest(); @@ -416,7 +416,7 @@ protected function getResultsHtml( $teamNames = []; foreach ($teams as $team) { - $teamNames[$team->getIcpcId()] = $team->getEffectiveName(); + $teamNames[$team->getIcpcId() ?? $team->getExternalid()] = $team->getEffectiveName(); } $awarded = []; @@ -496,8 +496,8 @@ protected function getResultsHtml( $firstToSolve[$problem->getProbid()] = [ 'problem' => $problem->getShortname(), 'problem_name' => $problem->getProblem()->getName(), - 'team' => $teamNames[$team->getIcpcId()], - 'rank' => $rankPerTeam[$team->getIcpcId()] ?: '-', + 'team' => $teamNames[$team->getIcpcId() ?? $team->getExternalid()], + 'rank' => $rankPerTeam[$team->getIcpcId() ?? $team->getExternalid()] ?: '-', 'time' => Utils::scoretime($matrixItem->time, $scoreIsInSeconds), ]; } @@ -574,7 +574,7 @@ protected function getClarificationsHtml(): Response if (!isset($grouped[$queue])) { $grouped[$queue] = []; } - $grouped[$queue][$clarification->getClarid()] = $clarification; + $grouped[$queue][$clarification->getExternalid()] = $clarification; } } diff --git a/webapp/src/Controller/Jury/InternalErrorController.php b/webapp/src/Controller/Jury/InternalErrorController.php index 04642dab40..919d569415 100644 --- a/webapp/src/Controller/Jury/InternalErrorController.php +++ b/webapp/src/Controller/Jury/InternalErrorController.php @@ -7,6 +7,7 @@ use App\Entity\InternalError; use App\Entity\Judgehost; use App\Entity\JudgeTask; +use App\Entity\Language; use App\Entity\Problem; use App\Service\DOMJudgeService; use App\Service\EventLogService; @@ -102,8 +103,8 @@ public function viewAction(int $errorId): Response $affectedLink = $affectedText = null; switch ($disabled['kind']) { case 'problem': - $affectedLink = $this->generateUrl('jury_problem', ['probId' => $disabled['probid']]); $problem = $this->em->getRepository(Problem::class)->find($disabled['probid']); + $affectedLink = $this->generateUrl('jury_problem', ['probId' => $problem->getExternalid()]); $affectedText = $problem->getName(); break; case 'judgehost': @@ -117,8 +118,9 @@ public function viewAction(int $errorId): Response } break; case 'language': - $affectedLink = $this->generateUrl('jury_language', ['langId' => $disabled['langid']]); - $affectedText = $disabled['langid']; + $language = $this->em->getRepository(Language::class)->find($disabled['langid']); + $affectedLink = $this->generateUrl('jury_language', ['langId' => $language->getExternalid()]); + $affectedText = $language->getName(); break; case 'executable': $affectedLink = $this->generateUrl('jury_executable', ['execId' => $disabled['execid']]); @@ -151,7 +153,7 @@ public function handleAction(Request $request, ?Profiler $profiler, int $errorId ->getSingleResult(); if ($action === 'ignore') { $internalError->setStatus(InternalErrorStatusType::STATUS_IGNORED); - $this->dj->auditlog('internal_error', $internalError->getErrorid(), + $this->dj->auditlog('internal_error', (string)$internalError->getErrorid(), sprintf('internal error: %s', InternalErrorStatusType::STATUS_IGNORED)); $this->em->flush(); return $this->redirectToRoute('jury_internal_error', ['errorId' => $internalError->getErrorid()]); @@ -175,7 +177,7 @@ public function handleAction(Request $request, ?Profiler $profiler, int $errorId ); $this->em->flush(); - $this->dj->auditlog('internal_error', $internalError->getErrorid(), + $this->dj->auditlog('internal_error', (string)$internalError->getErrorid(), sprintf('internal error: %s', InternalErrorStatusType::STATUS_RESOLVED)); $affectedJudgings = $internalError->getAffectedJudgings(); diff --git a/webapp/src/Controller/Jury/JudgeRemainingTrait.php b/webapp/src/Controller/Jury/JudgeRemainingTrait.php index 8466616198..e1419be140 100644 --- a/webapp/src/Controller/Jury/JudgeRemainingTrait.php +++ b/webapp/src/Controller/Jury/JudgeRemainingTrait.php @@ -79,7 +79,7 @@ protected function judgeRemainingJudgings(array $judgings): void } } - public function judgeRemaining(int $contestId = -1, string $categoryId = '', string|int $probId = '', string $langId = ''): void + public function judgeRemaining(?string $contestId = null, ?string $categoryId = null, ?string $probId = null, ?string $langId = null): void { $query = $this->em->createQueryBuilder() ->from(Judging::class, 'j') @@ -90,24 +90,27 @@ public function judgeRemaining(int $contestId = -1, string $categoryId = '', str ->andWhere('j.valid = true') ->andWhere('j.result != :compiler_error') ->setParameter('compiler_error', 'compiler-error'); - if ($contestId > -1) { + if ($contestId !== null) { $query - ->andWhere('s.contest = :contestId') + ->join('s.contest', 'c') + ->andWhere('c.externalid = :contestId') ->setParameter('contestId', $contestId); } - if ($categoryId > -1) { + if ($categoryId !== null) { $query - ->andWhere('tc.categoryid = :categoryId') + ->andWhere('tc.externalid = :categoryId') ->setParameter('categoryId', $categoryId); } - if ($probId !== '') { + if ($probId !== null) { $query - ->andWhere('s.problem = :probId') + ->join('s.problem', 'p') + ->andWhere('p.externalid = :probId') ->setParameter('probId', $probId); } - if ($langId !== '') { + if ($langId !== null) { $query - ->andWhere('s.language = :langId') + ->join('s.language', 'l') + ->andWhere('l.externalid = :langId') ->setParameter('langId', $langId); } $judgings = $query->getQuery()->getResult(); diff --git a/webapp/src/Controller/Jury/JudgehostController.php b/webapp/src/Controller/Jury/JudgehostController.php index 3679c196b2..84c49c4772 100644 --- a/webapp/src/Controller/Jury/JudgehostController.php +++ b/webapp/src/Controller/Jury/JudgehostController.php @@ -280,6 +280,12 @@ public function viewAction(Request $request, int $judgehostid): Response $data = [ 'judgehost' => $judgehost, + 'previousNext' => $this->getPreviousAndNextObjectIds( + Judgehost::class, + $judgehost->getJudgehostid(), + 'judgehostid', + ['e.judgehostid' => 'ASC'], + ), 'status' => $status, 'statusIcon' => $statusIcon, 'judgings' => $judgings, @@ -325,7 +331,7 @@ public function enableAction(RouterInterface $router, Request $request, int $jud $judgehost = $this->em->getRepository(Judgehost::class)->find($judgehostid); $judgehost->setEnabled(true); $this->em->flush(); - $this->dj->auditlog('judgehost', $judgehost->getJudgehostid(), 'marked enabled'); + $this->dj->auditlog('judgehost', (string)$judgehost->getJudgehostid(), 'marked enabled'); return $this->redirectToLocalReferrer($router, $request, $this->generateUrl('jury_judgehosts')); } @@ -337,7 +343,7 @@ public function disableAction(RouterInterface $router, Request $request, int $ju $judgehost = $this->em->getRepository(Judgehost::class)->find($judgehostid); $judgehost->setEnabled(false); $this->em->flush(); - $this->dj->auditlog('judgehost', $judgehost->getJudgehostid(), 'marked disabled'); + $this->dj->auditlog('judgehost', (string)$judgehost->getJudgehostid(), 'marked disabled'); return $this->redirectToLocalReferrer($router, $request, $this->generateUrl('jury_judgehosts')); } diff --git a/webapp/src/Controller/Jury/JuryMiscController.php b/webapp/src/Controller/Jury/JuryMiscController.php index 9f64a3994f..df6d62a455 100644 --- a/webapp/src/Controller/Jury/JuryMiscController.php +++ b/webapp/src/Controller/Jury/JuryMiscController.php @@ -85,7 +85,7 @@ public function ajaxDataAction(Request $request, string $datatype): JsonResponse if ($datatype === 'affiliations') { $affiliations = $qb->from(TeamAffiliation::class, 'a') - ->select('a.affilid', 'a.name', 'a.shortname') + ->select('a.externalid', 'a.name', 'a.shortname') ->where($qb->expr()->like('a.name', '?1')) ->orWhere($qb->expr()->like('a.shortname', '?1')) ->orWhere($qb->expr()->eq('a.affilid', '?2')) @@ -95,9 +95,9 @@ public function ajaxDataAction(Request $request, string $datatype): JsonResponse ->getResult(); $results = array_map(function (array $affiliation) { - $displayname = $affiliation['name'] . " (" . $affiliation['affilid'] . ")"; + $displayname = $affiliation['name'] . " (" . $affiliation['externalid'] . ")"; return [ - 'id' => $affiliation['affilid'], + 'id' => $affiliation['externalid'], 'text' => $displayname, ]; }, $affiliations); @@ -117,7 +117,7 @@ public function ajaxDataAction(Request $request, string $datatype): JsonResponse throw new AccessDeniedHttpException('Permission denied'); } elseif ($datatype === 'problems') { $problems = $qb->from(Problem::class, 'p') - ->select('p.probid', 'p.name') + ->select('p.externalid', 'p.name') ->where($qb->expr()->like('p.name', '?1')) ->orWhere($qb->expr()->eq('p.probid', '?2')) ->orderBy('p.name', 'ASC') @@ -126,15 +126,15 @@ public function ajaxDataAction(Request $request, string $datatype): JsonResponse ->getResult(); $results = array_map(function (array $problem) { - $displayname = $problem['name'] . " (p" . $problem['probid'] . ")"; + $displayname = $problem['name'] . " (" . $problem['externalid'] . ")"; return [ - 'id' => $problem['probid'], + 'id' => $problem['externalid'], 'text' => $displayname, ]; }, $problems); } elseif ($datatype === 'teams') { $teams = $qb->from(Team::class, 't') - ->select('t.teamid', 't.display_name', 't.name', 'COALESCE(t.display_name, t.name) AS order') + ->select('t.externalid', 't.display_name', 't.name', 'COALESCE(t.display_name, t.name) AS order') ->where($qb->expr()->like('t.name', '?1')) ->orWhere($qb->expr()->like('t.display_name', '?1')) ->orWhere($qb->expr()->eq('t.teamid', '?2')) @@ -144,32 +144,32 @@ public function ajaxDataAction(Request $request, string $datatype): JsonResponse ->getResult(); $results = array_map(function (array $team) { - $displayname = ($team['display_name'] ?? $team['name']) . " (t" . $team['teamid'] . ")"; + $displayname = ($team['display_name'] ?? $team['name']) . " (" . $team['externalid'] . ")"; return [ - 'id' => $team['teamid'], + 'id' => $team['externalid'], 'text' => $displayname, ]; }, $teams); } elseif ($datatype === 'languages') { $languages = $qb->from(Language::class, 'l') - ->select('l.langid', 'l.name') + ->select('l.externalid', 'l.name') ->where($qb->expr()->like('l.name', '?1')) - ->orWhere($qb->expr()->eq('l.langid', '?2')) + ->orWhere($qb->expr()->eq('l.externalid', '?2')) ->orderBy('l.name', 'ASC') ->getQuery()->setParameter(1, '%' . $q . '%') ->setParameter(2, $q) ->getResult(); $results = array_map(function (array $language) { - $displayname = $language['name'] . " (" . $language['langid'] . ")"; + $displayname = $language['name'] . " (" . $language['externalid'] . ")"; return [ - 'id' => $language['langid'], + 'id' => $language['externalid'], 'text' => $displayname, ]; }, $languages); } elseif ($datatype === 'contests') { $query = $qb->from(Contest::class, 'c') - ->select('c.cid', 'c.name', 'c.shortname') + ->select('c.externalid', 'c.name', 'c.shortname') ->where($qb->expr()->like('c.name', '?1')) ->orWhere($qb->expr()->like('c.shortname', '?1')) ->orWhere($qb->expr()->eq('c.cid', '?2')) @@ -187,9 +187,9 @@ public function ajaxDataAction(Request $request, string $datatype): JsonResponse $contests = $query->getResult(); $results = array_map(function (array $contest) { - $displayname = $contest['name'] . " (" . $contest['shortname'] . " - c" . $contest['cid'] . ")"; + $displayname = $contest['name'] . " (" . $contest['shortname'] . " - " . $contest['externalid'] . ")"; return [ - 'id' => $contest['cid'], + 'id' => $contest['externalid'], 'text' => $displayname, ]; }, $contests); @@ -215,7 +215,7 @@ public function refreshCacheAction(Request $request, ScoreboardService $scoreboa $contests = [$cid => $contests[$cid]]; } elseif ($request->cookies->has('domjudge_cid') && ($contest = $this->dj->getCurrentContest())) { - $contests = [$contest->getCid() => $contest]; + $contests = [$contest->getExternalid() => $contest]; } if ($request->isXmlHttpRequest() && $request->isMethod('POST')) { @@ -288,7 +288,7 @@ public function judgingVerifierAction(Request $request): Response $judging = $submission->getJudgings()->first(); /** @var string[] $expectedResults */ $expectedResults = $submission->getExpectedResults(); - $submissionId = $submission->getSubmitid(); + $submissionId = $submission->getExternalid(); $submissionFiles = $submission->getFiles(); $result = mb_strtoupper($judging->getResult()); @@ -340,20 +340,20 @@ public function judgingVerifierAction(Request $request): Response 'nomatch' => $nomatch, 'earlier' => $earlier, 'problems' => $problems, - 'contestId' => $this->dj->getCurrentContest()?->getCid(), + 'contestId' => $this->dj->getCurrentContest()?->getExternalid(), 'verifyMultiple' => $verifyMultiple, ]); } - #[Route(path: '/change-contest/{contestId<-?\d+>}', name: 'jury_change_contest')] - public function changeContestAction(Request $request, RouterInterface $router, int $contestId): Response + #[Route(path: '/change-contest/{contestId}', name: 'jury_change_contest')] + public function changeContestAction(Request $request, RouterInterface $router, string $contestId): Response { if ($this->isLocalReferer($router, $request)) { $response = new RedirectResponse($request->headers->get('referer')); } else { $response = $this->redirectToRoute('jury_index'); } - return $this->dj->setCookie('domjudge_cid', (string)$contestId, 0, null, '', false, false, + return $this->dj->setCookie('domjudge_cid', $contestId, 0, null, '', false, false, $response); } diff --git a/webapp/src/Controller/Jury/LanguageController.php b/webapp/src/Controller/Jury/LanguageController.php index f9c3981834..e714052926 100644 --- a/webapp/src/Controller/Jury/LanguageController.php +++ b/webapp/src/Controller/Jury/LanguageController.php @@ -52,8 +52,7 @@ public function indexAction(): Response ->orderBy('lang.name', 'ASC') ->getQuery()->getResult(); $table_fields = [ - 'langid' => ['title' => 'ID', 'sort' => true], - 'externalid' => ['title' => 'external ID', 'sort' => true], + 'externalid' => ['title' => 'ID', 'sort' => true], 'name' => ['title' => 'name', 'sort' => true, 'default_sort' => true], 'entrypoint' => ['title' => 'entry point', 'sort' => true], 'allowjudge' => ['title' => 'allow judge', 'sort' => true], @@ -80,14 +79,14 @@ public function indexAction(): Response 'icon' => 'edit', 'title' => 'edit this language', 'link' => $this->generateUrl('jury_language_edit', [ - 'langId' => $lang->getLangid() + 'langId' => $lang->getExternalid() ]) ]; $langactions[] = [ 'icon' => 'trash-alt', 'title' => 'delete this language', 'link' => $this->generateUrl('jury_language_delete', [ - 'langId' => $lang->getLangid(), + 'langId' => $lang->getExternalid(), ]), 'ajaxModal' => true, ]; @@ -126,14 +125,14 @@ public function indexAction(): Response $enabled_languages[] = [ 'data' => $langdata, 'actions' => $langactions, - 'link' => $this->generateUrl('jury_language', ['langId' => $lang->getLangid()]), + 'link' => $this->generateUrl('jury_language', ['langId' => $lang->getExternalid()]), 'cssclass' => '', ]; } else { $disabled_languages[] = [ 'data' => $langdata, 'actions' => $langactions, - 'link' => $this->generateUrl('jury_language', ['langId' => $lang->getLangid()]), + 'link' => $this->generateUrl('jury_language', ['langId' => $lang->getExternalid()]), 'cssclass' => 'disabled', ]; } @@ -145,10 +144,8 @@ public function indexAction(): Response ]); } - // Note that the add action appears before the view action to make sure - // /add is not seen as a language. #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/add', name: 'jury_language_add')] + #[Route(path: '/add', name: 'jury_language_add', priority: 1)] public function addAction(Request $request): Response { $language = new Language(); @@ -159,7 +156,7 @@ public function addAction(Request $request): Response if ($response = $this->processAddFormForExternalIdEntity( $form, $language, - fn() => $this->generateUrl('jury_language', ['langId' => $language->getLangid()]), + fn() => $this->generateUrl('jury_language', ['langId' => $language->getExternalid()]), function () use ($language) { // Normalize extensions if ($language->getExtensions()) { @@ -187,7 +184,7 @@ function () use ($language) { #[Route(path: '/{langId}', name: 'jury_language')] public function viewAction(Request $request, SubmissionService $submissionService, string $langId): Response { - $language = $this->em->getRepository(Language::class)->find($langId); + $language = $this->em->getRepository(Language::class)->findByExternalId($langId); if (!$language) { throw new NotFoundHttpException(sprintf('Language with ID %s not found', $langId)); } @@ -201,13 +198,17 @@ public function viewAction(Request $request, SubmissionService $submissionServic $data = [ 'language' => $language, + 'previousNext' => $this->getPreviousAndNextObjectIds( + Language::class, + $language->getExternalid(), + ), 'submissions' => $submissions, 'submissionCounts' => $submissionCounts, 'showContest' => count($this->dj->getCurrentContests(honorCookie: true)) > 1, 'showExternalResult' => $this->dj->shadowMode(), 'refresh' => [ 'after' => 15, - 'url' => $this->generateUrl('jury_language', ['langId' => $language->getLangid()]), + 'url' => $this->generateUrl('jury_language', ['langId' => $language->getExternalid()]), 'ajax' => true, ], ]; @@ -224,7 +225,7 @@ public function viewAction(Request $request, SubmissionService $submissionServic #[Route(path: '/{langId}/toggle-submit', name: 'jury_language_toggle_submit')] public function toggleSubmitAction(Request $request, string $langId): Response { - $language = $this->em->getRepository(Language::class)->find($langId); + $language = $this->em->getRepository(Language::class)->findByExternalId($langId); if (!$language) { throw new NotFoundHttpException(sprintf('Language with ID %s not found', $langId)); } @@ -232,7 +233,7 @@ public function toggleSubmitAction(Request $request, string $langId): Response $language->setAllowSubmit($request->request->getBoolean('value')); $this->em->flush(); - $this->dj->auditlog('language', $langId, 'set allow submit', + $this->dj->auditlog('language', $language->getExternalid(), 'set allow submit', $request->request->getBoolean('value') ? 'yes' : 'no'); return $this->redirectToRoute('jury_language', ['langId' => $langId]); } @@ -243,7 +244,7 @@ public function toggleJudgeAction( Request $request, string $langId ): Response { - $language = $this->em->getRepository(Language::class)->find($langId); + $language = $this->em->getRepository(Language::class)->findByExternalId($langId); if (!$language) { throw new NotFoundHttpException(sprintf('Language with ID %s not found', $langId)); } @@ -253,10 +254,10 @@ public function toggleJudgeAction( $this->em->flush(); if ($enabled) { - $this->dj->unblockJudgeTasksForLanguage($langId); + $this->dj->unblockJudgeTasksForLanguage($language->getLangid()); } - $this->dj->auditlog('language', $langId, 'set allow judge', + $this->dj->auditlog('language', $language->getExternalid(), 'set allow judge', $request->request->getBoolean('value') ? 'yes' : 'no'); return $this->redirectToLocalReferrer( $router, @@ -268,7 +269,7 @@ public function toggleJudgeAction( #[Route(path: '/{langId}/toggle-filter-compiler-flags', name: 'jury_language_toggle_filter_compiler_files')] public function toggleFilterCompilerFlagsAction(Request $request, string $langId): Response { - $language = $this->em->getRepository(Language::class)->find($langId); + $language = $this->em->getRepository(Language::class)->findByExternalId($langId); if (!$language) { throw new NotFoundHttpException(sprintf('Language with ID %s not found', $langId)); } @@ -277,7 +278,7 @@ public function toggleFilterCompilerFlagsAction(Request $request, string $langId $language->setFilterCompilerFiles($enabled); $this->em->flush(); - $this->dj->auditlog('language', $langId, 'set filter compiler flags', + $this->dj->auditlog('language', $language->getExternalid(), 'set filter compiler flags', $request->request->getBoolean('value') ? 'yes' : 'no'); return $this->redirectToRoute('jury_language', ['langId' => $langId]); } @@ -286,7 +287,7 @@ public function toggleFilterCompilerFlagsAction(Request $request, string $langId #[Route(path: '/{langId}/edit', name: 'jury_language_edit')] public function editAction(Request $request, string $langId): Response { - $language = $this->em->getRepository(Language::class)->find($langId); + $language = $this->em->getRepository(Language::class)->findByExternalId($langId); if (!$language) { throw new NotFoundHttpException(sprintf('Language with ID %s not found', $langId)); } @@ -302,7 +303,7 @@ public function editAction(Request $request, string $langId): Response } $this->saveEntity($language, $language->getLangid(), false); if ($language->getAllowJudge()) { - $this->dj->unblockJudgeTasksForLanguage($langId); + $this->dj->unblockJudgeTasksForLanguage($language->getLangid()); } return $this->redirectToRoute('jury_language', ['langId' => $language->getLangid()]); } @@ -317,7 +318,7 @@ public function editAction(Request $request, string $langId): Response #[Route(path: '/{langId}/delete', name: 'jury_language_delete')] public function deleteAction(Request $request, string $langId): Response { - $language = $this->em->getRepository(Language::class)->find($langId); + $language = $this->em->getRepository(Language::class)->findByExternalId($langId); if (!$language) { throw new NotFoundHttpException(sprintf('Language with ID %s not found', $langId)); } @@ -329,11 +330,11 @@ public function deleteAction(Request $request, string $langId): Response #[Route(path: '/{langId}/request-remaining', name: 'jury_language_request_remaining')] public function requestRemainingRunsWholeLanguageAction(string $langId): RedirectResponse { - $language = $this->em->getRepository(Language::class)->find($langId); + $language = $this->em->getRepository(Language::class)->findByExternalId($langId); if (!$language) { throw new NotFoundHttpException(sprintf('Language with ID %s not found', $langId)); } - $contestId = $this->dj->getCurrentContest()->getCid(); + $contestId = $this->dj->getCurrentContest()->getExternalid(); $this->judgeRemaining(contestId: $contestId, langId: $langId); return $this->redirectToRoute('jury_language', ['langId' => $langId]); } diff --git a/webapp/src/Controller/Jury/ProblemController.php b/webapp/src/Controller/Jury/ProblemController.php index e10cc3f6be..4b8c33967c 100644 --- a/webapp/src/Controller/Jury/ProblemController.php +++ b/webapp/src/Controller/Jury/ProblemController.php @@ -76,10 +76,9 @@ public function indexAction(): Response ->getQuery()->getResult(); $table_fields = [ - 'probid' => ['title' => 'ID', 'sort' => true, 'default_sort' => true], - 'externalid' => ['title' => 'external ID', 'sort' => true], + 'externalid' => ['title' => 'ID', 'sort' => true], 'name' => ['title' => 'name', 'sort' => true], - 'badges' => ['title' => '', 'sort' => false], + 'badges' => ['title' => '', 'sort' => true], 'num_contests' => ['title' => '# contests', 'sort' => true], 'timelimit' => ['title' => 'time limit', 'sort' => true], 'memlimit' => ['title' => 'memory limit', 'sort' => true], @@ -88,6 +87,12 @@ public function indexAction(): Response 'type' => ['title' => 'type', 'sort' => true], ]; + if ($this->dj->getCurrentContest() !== null) { + $table_fields['badges']['default_sort'] = true; + } else { + $table_fields['badges']['externalid'] = true; + } + $this->addSelectAllCheckbox($table_fields, 'problems'); $contestCountData = $this->em->createQueryBuilder() @@ -112,7 +117,7 @@ public function indexAction(): Response $problemdata = []; $problemactions = []; - $this->addEntityCheckbox($problemdata, $p, $p->getProbid(), 'problem-checkbox', fn(Problem $problem) => !$problem->isLocked()); + $this->addEntityCheckbox($problemdata, $p, $p->getExternalid(), 'problem-checkbox', fn(Problem $problem) => !$problem->isLocked()); // Get whatever fields we can from the problem object itself. foreach ($table_fields as $k => $v) { @@ -127,7 +132,7 @@ public function indexAction(): Response 'icon' => 'file-' . $p->getProblemstatementType(), 'title' => 'view problem statement', 'link' => $this->generateUrl('jury_problem_statement', [ - 'probId' => $p->getProbid(), + 'probId' => $p->getExternalid(), ]) ]; } else { @@ -137,7 +142,7 @@ public function indexAction(): Response 'icon' => 'save', 'title' => 'export problem as zip-file', 'link' => $this->generateUrl('jury_export_problem', [ - 'problemId' => $p->getProbid(), + 'problemId' => $p->getExternalid(), ]) ]; @@ -146,7 +151,7 @@ public function indexAction(): Response 'icon' => 'edit', 'title' => 'edit this problem', 'link' => $this->generateUrl('jury_problem_edit', [ - 'probId' => $p->getProbid(), + 'probId' => $p->getExternalid(), ]) ]; @@ -161,7 +166,7 @@ public function indexAction(): Response 'icon' => 'trash-alt', 'title' => 'delete this problem', 'link' => $this->generateUrl('jury_problem_delete', [ - 'probId' => $p->getProbid(), + 'probId' => $p->getExternalid(), ]), 'ajaxModal' => true, ]; @@ -206,7 +211,10 @@ public function indexAction(): Response if ($this->dj->getCurrentContest() !== null) { $badges = array_filter($contestProblems, fn($cp) => $cp->getCid() === $this->dj->getCurrentContest()->getCid()); } - $problemdata['badges'] = ['value' => $badges]; + $problemdata['badges'] = [ + 'value' => $badges, + 'sortvalue' => implode(', ', array_map(fn(ContestProblem $problem) => $problem->getShortname(), $badges)), + ]; // merge in the rest of the data $problemdata = array_merge($problemdata, [ @@ -218,7 +226,7 @@ public function indexAction(): Response $data_to_add = [ 'data' => $problemdata, 'actions' => $problemactions, - 'link' => $this->generateUrl('jury_problem', ['probId' => $p->getProbid()]), + 'link' => $this->generateUrl('jury_problem', ['probId' => $p->getExternalid()]), ]; if ($badges) { $problems_table_current[] = $data_to_add; @@ -246,16 +254,13 @@ public function problemsetAction(StatisticsService $stats): Response $this->dj->getTwigDataForProblemsAction($stats, teamId: $teamId, forJury: true)); } - #[Route(path: '/{probId<\d+>}/samples.zip', name: 'jury_problem_sample_zip')] - public function sampleZipAction(int $probId): StreamedResponse + #[Route(path: '/{probId}/samples.zip', name: 'jury_problem_sample_zip')] + public function sampleZipAction(string $probId): StreamedResponse { $contest = $this->dj->getCurrentContest(); - $contestProblem = $this->em->getRepository(ContestProblem::class)->find([ - 'problem' => $probId, - 'contest' => $contest, - ]); + $contestProblem = $this->em->getRepository(ContestProblem::class)->findByProblemAndContest($contest, $probId); if (!$contestProblem) { - throw new NotFoundHttpException(sprintf('Problem p%d not found or not available', $probId)); + throw new NotFoundHttpException(sprintf('Problem %s not found or not available', $probId)); } return $this->dj->getSamplesZipStreamedResponse($contestProblem); } @@ -264,8 +269,8 @@ public function sampleZipAction(int $probId): StreamedResponse * @throws NonUniqueResultException */ #[IsGranted('ROLE_JURY')] - #[Route(path: '/{problemId<\d+>}/export', name: 'jury_export_problem')] - public function exportAction(int $problemId): StreamedResponse + #[Route(path: '/{problemId}/export', name: 'jury_export_problem')] + public function exportAction(string $problemId): StreamedResponse { // This might take a while. Utils::extendMaxExecutionTime(300); @@ -275,7 +280,7 @@ public function exportAction(int $problemId): StreamedResponse ->leftJoin('p.contest_problems', 'cp', Join::WITH, 'cp.contest = :contest') ->leftJoin('p.problemStatementContent', 'content') ->select('p', 'cp', 'content') - ->andWhere('p.probid = :problemId') + ->andWhere('p.externalid = :problemId') ->setParameter('problemId', $problemId) ->setParameter('contest', $this->dj->getCurrentContest()) ->getQuery() @@ -412,7 +417,7 @@ public function exportAction(int $problemId): StreamedResponse // since we can't change the filename to something unique, since // that could break e.g. Java sources, even if _we_ support this // by default. - $directory = sprintf('submissions/%s/s%d/', $problemResult, $solution->getSubmitid()); + $directory = sprintf('submissions/%s/%s/', $problemResult, $solution->getExternalid()); /** @var SubmissionFile $source */ foreach ($solution->getFiles() as $source) { $zip->addFromString($directory . $source->getFilename(), $source->getSourcecode()); @@ -423,7 +428,7 @@ public function exportAction(int $problemId): StreamedResponse if ($contestProblem && $contestProblem->getShortname()) { $zipFilename = sprintf('%s.zip', $contestProblem->getShortname()); } else { - $zipFilename = sprintf('p%d.zip', $problem->getProbid()); + $zipFilename = sprintf('%s.zip', $problem->getExternalid()); } return Utils::streamZipFile($tempFilename, $zipFilename); @@ -433,10 +438,10 @@ public function exportAction(int $problemId): StreamedResponse * @throws NoResultException * @throws NonUniqueResultException */ - #[Route(path: '/{probId<\d+>}', name: 'jury_problem')] - public function viewAction(Request $request, SubmissionService $submissionService, int $probId): Response + #[Route(path: '/{probId}', name: 'jury_problem')] + public function viewAction(Request $request, SubmissionService $submissionService, string $probId): Response { - $problem = $this->em->getRepository(Problem::class)->find($probId); + $problem = $this->em->getRepository(Problem::class)->findByExternalId($probId); if (!$problem) { throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); } @@ -446,7 +451,7 @@ public function viewAction(Request $request, SubmissionService $submissionServic /** @var ContestProblem $contestProblem */ if ($contestProblem->getContest()->isLocked()) { if (!$request->isXmlHttpRequest()) { - $this->addFlash('warning', 'Cannot edit problem, it belongs to locked contest c' . $contestProblem->getContest()->getCid()); + $this->addFlash('warning', 'Cannot edit problem, it belongs to locked contest ' . $contestProblem->getContest()->getExternalid()); } $lockedProblem = true; } @@ -498,6 +503,10 @@ public function viewAction(Request $request, SubmissionService $submissionServic $data = [ 'problem' => $problem, + 'previousNext' => $this->getPreviousAndNextObjectIds( + Problem::class, + $problem->getExternalid(), + ), 'problemAttachmentForm' => $problemAttachmentForm->createView(), 'submissions' => $submissions, 'submissionCounts' => $submissionCounts, @@ -511,7 +520,7 @@ public function viewAction(Request $request, SubmissionService $submissionServic 'lockedProblem' => $lockedProblem, 'refresh' => [ 'after' => 15, - 'url' => $this->generateUrl('jury_problem', ['probId' => $problem->getProbid()]), + 'url' => $this->generateUrl('jury_problem', ['probId' => $problem->getExternalid()]), 'ajax' => true, ], ]; @@ -525,10 +534,10 @@ public function viewAction(Request $request, SubmissionService $submissionServic return $this->render('jury/problem.html.twig', $data); } - #[Route(path: '/{probId<\d+>}/statement', name: 'jury_problem_statement')] - public function viewTextAction(int $probId): StreamedResponse + #[Route(path: '/{probId}/statement', name: 'jury_problem_statement')] + public function viewTextAction(string $probId): StreamedResponse { - $problem = $this->em->getRepository(Problem::class)->find($probId); + $problem = $this->em->getRepository(Problem::class)->findByExternalId($probId); if (!$problem) { throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); } @@ -536,10 +545,10 @@ public function viewTextAction(int $probId): StreamedResponse return $problem->getProblemStatementStreamedResponse(); } - #[Route(path: '/{probId<\d+>}/testcases', name: 'jury_problem_testcases')] - public function testcasesAction(Request $request, int $probId): Response + #[Route(path: '/{probId}/testcases', name: 'jury_problem_testcases')] + public function testcasesAction(Request $request, string $probId): Response { - $problem = $this->em->getRepository(Problem::class)->find($probId); + $problem = $this->em->getRepository(Problem::class)->findByExternalId($probId); if (!$problem) { throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); } @@ -548,7 +557,7 @@ public function testcasesAction(Request $request, int $probId): Response foreach ($problem->getContestProblems() as $contestproblem) { /** @var ContestProblem $contestproblem */ if ($contestproblem->getContest()->isLocked()) { - $lockedContests[] = 'c' . $contestproblem->getContest()->getCid(); + $lockedContests[] = $contestproblem->getContest()->getExternalid(); break; } } @@ -571,7 +580,7 @@ public function testcasesAction(Request $request, int $probId): Response if (!empty($lockedContests)) { $this->addFlash('danger', 'Cannot edit problem / testcases, it belongs to locked contest(s) ' . join(', ', $lockedContests)); - return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); + return $this->redirectToRoute('jury_problem', ['probId' => $problem->getExternalid()]); } $messages = []; $maxrank = 0; @@ -637,7 +646,7 @@ public function testcasesAction(Request $request, int $probId): Response } } - $this->dj->auditlog('testcase', $probId, 'updated', + $this->dj->auditlog('testcase', $problem->getExternalid(), 'updated', sprintf('%s rank %d', $type, $rank)); $message = sprintf('Updated %s for testcase %d with file %s (%s)', @@ -739,7 +748,7 @@ public function testcasesAction(Request $request, int $probId): Response } $this->em->persist($newTestcase); - $this->dj->auditlog('testcase', $probId, 'added', sprintf("rank %d", $maxrank)); + $this->dj->auditlog('testcase', $problem->getExternalid(), 'added', sprintf("rank %d", $maxrank)); $inFile = $request->files->get('add_input'); $outFile = $request->files->get('add_output'); @@ -803,10 +812,10 @@ public function testcasesAction(Request $request, int $probId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{probId<\d+>}/testcases/{rank<\d+>}/move/{direction}', name: 'jury_problem_testcase_move')] - public function moveTestcaseAction(int $probId, int $rank, string $direction): Response + #[Route(path: '/{probId}/testcases/{rank<\d+>}/move/{direction}', name: 'jury_problem_testcase_move')] + public function moveTestcaseAction(string $probId, int $rank, string $direction): Response { - $problem = $this->em->getRepository(Problem::class)->find($probId); + $problem = $this->em->getRepository(Problem::class)->findByExternalId($probId); if (!$problem) { throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); } @@ -815,7 +824,7 @@ public function moveTestcaseAction(int $probId, int $rank, string $direction): R /** @var ContestProblem $contestProblem */ if ($contestProblem->getContest()->isLocked()) { $this->addFlash('danger', 'Cannot edit problem, it belongs to locked contest c' . $contestProblem->getContest()->getCid()); - return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); + return $this->redirectToRoute('jury_problem', ['probId' => $problem->getExternalid()]); } } @@ -866,7 +875,7 @@ public function moveTestcaseAction(int $probId, int $rank, string $direction): R $other->setRank($currentRank); }); - $this->dj->auditlog('testcase', $probId, 'switch rank', + $this->dj->auditlog('testcase', $problem->getExternalid(), 'switch rank', sprintf("%d <=> %d", $current->getRank(), $other->getRank())); } @@ -876,28 +885,29 @@ public function moveTestcaseAction(int $probId, int $rank, string $direction): R /** * @throws NonUniqueResultException */ - #[Route(path: '/{probId<\d+>}/testcases/{rank<\d+>}/fetch/{type}', name: 'jury_problem_testcase_fetch')] - public function fetchTestcaseAction(int $probId, int $rank, string $type): Response + #[Route(path: '/{probId}/testcases/{rank<\d+>}/fetch/{type}', name: 'jury_problem_testcase_fetch')] + public function fetchTestcaseAction(string $probId, int $rank, string $type): Response { /** @var Testcase|null $testcase */ $testcase = $this->em->createQueryBuilder() ->from(Testcase::class, 'tc') ->join('tc.content', 'tcc') + ->join('tc.problem', 'p') ->select('tc', 'tcc') - ->andWhere('tc.problem = :problem') + ->andWhere('p.externalid = :problem') ->andWhere('tc.ranknumber = :ranknumber') ->setParameter('problem', $probId) ->setParameter('ranknumber', $rank) ->getQuery() ->getOneOrNullResult(); if (!$testcase) { - throw new NotFoundHttpException(sprintf('Testcase with rank %d for problem %d not found', $rank, $probId)); + throw new NotFoundHttpException(sprintf('Testcase with rank %d for problem %s not found', $rank, $probId)); } if ($type === 'image') { $extension = $testcase->getImageType(); $mimetype = sprintf('image/%s', $extension); - $filename = sprintf('p%d.t%d.%s', $probId, $rank, $extension); + $filename = sprintf('%s.t%d.%s', $probId, $rank, $extension); } else { $extension = Testcase::EXTENSION_MAPPING[$type]; $mimetype = 'text/plain'; @@ -930,10 +940,10 @@ public function fetchTestcaseAction(int $probId, int $rank, string $type): Respo } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{probId<\d+>}/edit', name: 'jury_problem_edit')] - public function editAction(Request $request, int $probId): Response + #[Route(path: '/{probId}/edit', name: 'jury_problem_edit')] + public function editAction(Request $request, string $probId): Response { - $problem = $this->em->getRepository(Problem::class)->find($probId); + $problem = $this->em->getRepository(Problem::class)->findByExternalId($probId); if (!$problem) { throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); } @@ -941,8 +951,8 @@ public function editAction(Request $request, int $probId): Response foreach ($problem->getContestProblems() as $contestProblem) { /** @var ContestProblem $contestProblem */ if ($contestProblem->getContest()->isLocked()) { - $this->addFlash('danger', 'Cannot edit problem, it belongs to locked contest c' . $contestProblem->getContest()->getCid()); - return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); + $this->addFlash('danger', 'Cannot edit problem, it belongs to locked contest ' . $contestProblem->getContest()->getExternalid()); + return $this->redirectToRoute('jury_problem', ['probId' => $problem->getExternalid()]); } } @@ -952,7 +962,7 @@ public function editAction(Request $request, int $probId): Response if ($form->isSubmitted() && $form->isValid()) { $this->saveEntity($problem, $problem->getProbid(), false); - return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); + return $this->redirectToRoute('jury_problem', ['probId' => $problem->getExternalid()]); } $data = []; @@ -981,10 +991,10 @@ public function editAction(Request $request, int $probId): Response if ($this->importProblemService->importZippedProblem( $zip, $clientName, $problem, $contest, $messages )) { - $this->dj->auditlog('problem', $problem->getProbid(), 'upload zip', $clientName); + $this->dj->auditlog('problem', $problem->getExternalid(), 'upload zip', $clientName); } else { $this->postMessages($messages); - return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); + return $this->redirectToRoute('jury_problem', ['probId' => $problem->getExternalid()]); } } catch (Exception $e) { $messages['danger'][] = $e->getMessage(); @@ -995,7 +1005,7 @@ public function editAction(Request $request, int $probId): Response } $this->postMessages($messages); - return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); + return $this->redirectToRoute('jury_problem', ['probId' => $problem->getExternalid()]); } return $this->render('jury/problem_edit.html.twig', [ @@ -1006,13 +1016,13 @@ public function editAction(Request $request, int $probId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/delete-multiple', name: 'jury_problem_delete_multiple', methods: ['GET', 'POST'])] + #[Route(path: '/delete-multiple', name: 'jury_problem_delete_multiple', methods: ['GET', 'POST'], priority: 1)] public function deleteMultipleAction(Request $request): Response { return $this->deleteMultiple( $request, Problem::class, - 'probid', + 'externalid', 'jury_problems', 'No problems could be deleted (they might be locked).', fn(Problem $problem) => !$problem->isLocked() @@ -1020,10 +1030,10 @@ public function deleteMultipleAction(Request $request): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{probId<\d+>}/delete', name: 'jury_problem_delete')] - public function deleteAction(Request $request, int $probId): Response + #[Route(path: '/{probId}/delete', name: 'jury_problem_delete')] + public function deleteAction(Request $request, string $probId): Response { - $problem = $this->em->getRepository(Problem::class)->find($probId); + $problem = $this->em->getRepository(Problem::class)->findByExternalId($probId); if (!$problem) { throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); } @@ -1031,7 +1041,7 @@ public function deleteAction(Request $request, int $probId): Response foreach ($problem->getContestProblems() as $contestProblem) { /** @var ContestProblem $contestProblem */ if ($contestProblem->getContest()->isLocked()) { - $this->addFlash('danger', 'Cannot delete problem, it belongs to locked contest c' . $contestProblem->getContest()->getCid()); + $this->addFlash('danger', 'Cannot delete problem, it belongs to locked contest ' . $contestProblem->getContest()->getExternalid()); return $this->redirectToRoute('jury_problem', ['probId' => $probId]); } } @@ -1061,7 +1071,7 @@ public function deleteAttachmentAction(Request $request, int $attachmentId): Res } $problem = $attachment->getProblem(); - $probId = $problem->getProbid(); + $probId = $problem->getExternalid(); foreach ($problem->getContestProblems() as $contestProblem) { /** @var ContestProblem $contestProblem */ @@ -1086,8 +1096,8 @@ public function deleteTestcaseAction(Request $request, int $testcaseId): Respons foreach ($problem->getContestProblems() as $contestProblem) { /** @var ContestProblem $contestProblem */ if ($contestProblem->getContest()->isLocked()) { - $this->addFlash('danger', 'Cannot edit problem, it belongs to locked contest c' . $contestProblem->getContest()->getCid()); - return $this->redirectToRoute('jury_problem', ['probId' => $problem->getProbid()]); + $this->addFlash('danger', 'Cannot edit problem, it belongs to locked contest ' . $contestProblem->getContest()->getExternalid()); + return $this->redirectToRoute('jury_problem', ['probId' => $problem->getExternalid()]); } } $testcase->setDeleted(true); @@ -1103,12 +1113,12 @@ public function deleteTestcaseAction(Request $request, int $testcaseId): Respons } } $this->em->flush(); - $this->addFlash('danger', sprintf('Testcase %d removed from problem %s. Consider rejudging the problem.', $testcaseId, $problem->getProbid())); - return $this->redirectToRoute('jury_problem_testcases', ['probId' => $problem->getProbid()]); + $this->addFlash('danger', sprintf('Testcase %d removed from problem %s. Consider rejudging the problem.', $testcaseId, $problem->getExternalid())); + return $this->redirectToRoute('jury_problem_testcases', ['probId' => $problem->getExternalid()]); } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/add', name: 'jury_problem_add')] + #[Route(path: '/add', name: 'jury_problem_add', priority: 1)] public function addAction(Request $request): Response { $problem = new Problem(); @@ -1119,7 +1129,7 @@ public function addAction(Request $request): Response if ($response = $this->processAddFormForExternalIdEntity( $form, $problem, - fn() => $this->generateUrl('jury_problem', ['probId' => $problem->getProbid()]) + fn() => $this->generateUrl('jury_problem', ['probId' => $problem->getExternalid()]) )) { return $response; } @@ -1176,14 +1186,14 @@ private function addTestcasesToZip(array $testcases, ZipArchive $zip, bool $isSa } } - #[Route(path: '/{probId<\d+>}/request-remaining', name: 'jury_problem_request_remaining')] + #[Route(path: '/{probId}/request-remaining', name: 'jury_problem_request_remaining')] public function requestRemainingRunsWholeProblemAction(string $probId): RedirectResponse { - $problem = $this->em->getRepository(Problem::class)->find($probId); + $problem = $this->em->getRepository(Problem::class)->findByExternalId($probId); if (!$problem) { throw new NotFoundHttpException(sprintf('Problem with ID %s not found', $probId)); } - $contestId = $this->dj->getCurrentContest()->getCid(); + $contestId = $this->dj->getCurrentContest()->getExternalid(); $this->judgeRemaining(contestId: $contestId, probId: $probId); return $this->redirectToRoute('jury_problem', ['probId' => $probId]); } @@ -1196,10 +1206,7 @@ public function toggleSubmitAction( string $probId, string $type ): Response { - $contestProblem = $this->em->getRepository(ContestProblem::class)->find([ - 'contest' => $contestId, - 'problem' => $probId, - ]); + $contestProblem = $this->em->getRepository(ContestProblem::class)->findByProblemAndContest($contestId, $probId); if (!$contestProblem) { throw new NotFoundHttpException(sprintf('Problem with ID %s not found for contest %s', $probId, $contestId)); } @@ -1220,7 +1227,7 @@ public function toggleSubmitAction( } $this->em->flush(); - $id = [$contestProblem->getCid(), $contestProblem->getProbid()]; + $id = [$contestProblem->getExternalId(), $contestProblem->getExternalId()]; $this->dj->auditlog('contest_problem', implode(', ', $id), $label, $value ? 'yes' : 'no'); return $this->redirectToLocalReferrer($router, $request, $this->generateUrl('jury_problems')); } diff --git a/webapp/src/Controller/Jury/QueueTaskController.php b/webapp/src/Controller/Jury/QueueTaskController.php index b7e505df62..0e9c505df7 100644 --- a/webapp/src/Controller/Jury/QueueTaskController.php +++ b/webapp/src/Controller/Jury/QueueTaskController.php @@ -79,7 +79,7 @@ public function indexAction(): Response $queueTaskData['priority']['value'] = static::PRIORITY_MAP[$queueTaskData['priority']['value']]; // Add some links. - $queueTaskData['team.name']['link'] = $this->generateUrl('jury_team', ['teamId' => $queueTask->getTeam()->getTeamid()]); + $queueTaskData['team.name']['link'] = $this->generateUrl('jury_team', ['teamId' => $queueTask->getTeam()->getExternalid()]); $queueTaskData['judgingid']['link'] = $this->generateUrl('jury_submission_by_judging', ['jid' => $queueTask->getJudging()->getJudgingid()]); // Format start time. diff --git a/webapp/src/Controller/Jury/RejudgingController.php b/webapp/src/Controller/Jury/RejudgingController.php index da05c10a79..9487efe444 100644 --- a/webapp/src/Controller/Jury/RejudgingController.php +++ b/webapp/src/Controller/Jury/RejudgingController.php @@ -392,6 +392,12 @@ public function viewAction( $data = [ 'rejudging' => $rejudging, + 'previousNext' => $this->getPreviousAndNextObjectIds( + Rejudging::class, + $rejudging->getRejudgingid(), + 'rejudgingid', + ['e.rejudgingid' => 'ASC'], + ), 'todo' => $todo, 'done' => $done, 'verdicts' => $verdicts, @@ -700,13 +706,13 @@ public function createAction(Request $request): Response // These are the tables that we can deal with. $tablemap = [ - 'contest' => 's.contest', + 'contest' => 'c.externalid', 'judgehost' => 'jt.judgehost', - 'language' => 's.language', - 'problem' => 's.problem', - 'submission' => 's.submitid', - 'team' => 's.team', - 'user' => 's.user', + 'language' => 'l.externalid', + 'problem' => 'p.externalid', + 'submission' => 's.externalid', + 'team' => 't.externalid', + 'user' => 'u.externalid', 'rejudging' => 'j2.rejudging', ]; @@ -737,7 +743,11 @@ public function createAction(Request $request): Response ->from(Judging::class, 'j') ->leftJoin('j.submission', 's') ->leftJoin('s.rejudging', 'r') + ->leftJoin('s.contest', 'c') + ->leftJoin('s.language', 'l') + ->leftJoin('s.problem', 'p') ->leftJoin('s.team', 't') + ->leftJoin('s.user', 'u') ->leftJoin('j.runs', 'jr') ->leftJoin('jr.judgetask', 'jt') ->select('j', 's', 'r', 't') @@ -804,10 +814,10 @@ private function generateFlashMessagesForSkippedJudgings(array $skipped): void /** @var Judging $judging */ foreach ($skipped as $judging) { $submission = $judging->getSubmission(); - $submitid = $submission->getSubmitid(); + $submitid = $submission->getExternalid(); $rejudgingid = $submission->getRejudging()->getRejudgingid(); $msg = sprintf( - 'Skipping submission s%d since it is ' . + 'Skipping submission %s since it is ' . 'already part of rejudging r%d.', $submitid, $rejudgingid @@ -837,7 +847,7 @@ private function getStats(Rejudging $rejudging): array ->leftJoin('j.rejudging', 'r') ->leftJoin('j.submission', 's') ->leftJoin('jt.judgehost', 'jh') - ->select('r.rejudgingid, j.judgingid', 's.submitid', 'jh.hostname', 'j.result', + ->select('r.rejudgingid, j.judgingid', 's.externalid as submitid', 'jh.hostname', 'j.result', 'AVG(jr.runtime) AS runtime_avg', 'COUNT(jr.runtime) AS ntestcases', '(j.endtime - j.starttime) AS duration' ) @@ -896,7 +906,8 @@ private function getStats(Rejudging $rejudging): array ->select('t.ranknumber', 'MAX(jr.runtime) - MIN(jr.runtime) AS spread') ->leftJoin('jr.judging', 'j') ->leftJoin('jr.testcase', 't') - ->andWhere('j.submission = :submitid') + ->leftJoin('j.submission', 's') + ->andWhere('s.externalid = :submitid') ->setParameter('submitid', $submitid) ->groupBy('jr.testcase') ->getQuery() diff --git a/webapp/src/Controller/Jury/SubmissionController.php b/webapp/src/Controller/Jury/SubmissionController.php index 3097765723..d3a3cdea74 100644 --- a/webapp/src/Controller/Jury/SubmissionController.php +++ b/webapp/src/Controller/Jury/SubmissionController.php @@ -119,25 +119,25 @@ public function indexAction( $contests = $this->dj->getCurrentContests(); if ($contest = $this->dj->getCurrentContest()) { - $contests = [$contest->getCid() => $contest]; + $contests = [$contest->getExternalid() => $contest]; } // Load preselected filters $filtersFromCookie = Utils::jsonDecode((string)$this->dj->getCookie('domjudge_submissionsfilter') ?: '[]'); $formAssociationFields = [ - 'problem_id' => [Problem::class, 'probid'], - 'language_id' => [Language::class, 'langid'], - 'team_id' => [Team::class, 'teamid'], - 'category_id' => [TeamCategory::class, 'categoryid'], - 'affiliation_id' => [TeamAffiliation::class, 'affilid'], + 'problem_id' => Problem::class, + 'language_id' => Language::class, + 'team_id' => Team::class, + 'category_id' => TeamCategory::class, + 'affiliation_id' => TeamAffiliation::class, ]; // Build the filter form. $filtersForForm = ['result' => $filtersFromCookie['result'] ?? []]; $hasFilters = !empty($filtersForForm['result']); - foreach ($formAssociationFields as $field => [$entityClass, $idField]) { - $filtersForForm[$field] = $this->em->getRepository($entityClass)->findBy([$idField => $filtersFromCookie[$field] ?? []]); + foreach ($formAssociationFields as $field => $entityClass) { + $filtersForForm[$field] = $this->em->getRepository($entityClass)->findBy(['externalid' => $filtersFromCookie[$field] ?? []]); $hasFilters = $hasFilters || !empty($filtersForForm[$field]); } $appliedFilters = $filtersForForm; @@ -149,13 +149,13 @@ public function indexAction( if ($form->isSubmitted() && $form->isValid()) { $filtersForCookie = ['result' => $form->get('result')->getData()]; $hasFilters = !empty($filtersForCookie['result']); - foreach ($formAssociationFields as $field => [$entityClass, $idField]) { - $method = 'get' . ucfirst($idField); - $filtersForCookie[$field] = array_map(fn($entity) => $entity->$method(), $form->get($field)->getData()); + $appliedFilters = []; + foreach ($formAssociationFields as $field => $entityClass) { + $filtersForCookie[$field] = array_map(fn($entity) => $entity->getExternalid(), $form->get($field)->getData()); $hasFilters = $hasFilters || !empty($filtersForCookie[$field]); + $appliedFilters[$field] = $form->get($field)->getData(); } $response = $this->dj->setCookie('domjudge_submissionsfilter', Utils::jsonEncode($filtersForCookie), response: $response); - $appliedFilters = $filtersForCookie; } if (!empty($appliedFilters['result'])) { @@ -188,10 +188,10 @@ public function indexAction( $disabledLangs = []; foreach ($submissions as $submission) { if (!$submission->getContestProblem()->getAllowJudge()) { - $disabledProblems[$submission->getProblemId()] = $submission->getProblem()->getName(); + $disabledProblems[$submission->getExternalid()] = $submission->getProblem()->getName(); } if (!$submission->getLanguage()->getAllowJudge()) { - $disabledLangs[$submission->getLanguage()->getLangid()] = $submission->getLanguage()->getName(); + $disabledLangs[$submission->getLanguage()->getExternalid()] = $submission->getLanguage()->getName(); } } @@ -225,10 +225,10 @@ public function indexAction( /** * @throws NonUniqueResultException */ - #[Route(path: '/{submitId<\d+>}', name: 'jury_submission')] + #[Route(path: '/{submitId}', name: 'jury_submission')] public function viewAction( Request $request, - int $submitId, + string $submitId, #[MapQueryParameter(name: 'jid')] ?int $judgingId = null, #[MapQueryParameter(name: 'rejudgingid')] @@ -240,11 +240,17 @@ public function viewAction( // If judging ID is not set but rejudging ID is, try to deduce the judging ID from the database. if (!isset($judgingId) && isset($rejudgingId)) { - $judging = $this->em->getRepository(Judging::class) - ->findOneBy([ - 'submission' => $submitId, - 'rejudging' => $rejudgingId - ]); + /** @var Judging|null $judging */ + $judging = $this->em->createQueryBuilder() + ->from(Judging::class, 'j') + ->select('j') + ->innerJoin('j.submission', 's') + ->andWhere('j.rejudging = :rejudgingId') + ->andWhere('s.externalid = :submitId') + ->setParameter('rejudgingId', $rejudgingId) + ->setParameter('submitId', $submitId) + ->getQuery() + ->getOneOrNullResult(); if ($judging) { $judgingId = $judging->getJudgingid(); } @@ -261,13 +267,13 @@ public function viewAction( ->leftJoin('s.external_judgements', 'ej', Join::WITH, 'ej.valid = 1') ->leftJoin('s.contest_problem', 'cp') ->select('s', 't', 'p', 'l', 'c', 'f', 'cp', 'ej') - ->andWhere('s.submitid = :submitid') + ->andWhere('s.externalid = :submitid') ->setParameter('submitid', $submitId) ->getQuery() ->getOneOrNullResult(); if (!$submission) { - throw new NotFoundHttpException(sprintf('No submission found with ID %d', $submitId)); + throw new NotFoundHttpException(sprintf('No submission found with ID %s', $submitId)); } $judgingData = $this->em->createQueryBuilder() @@ -562,6 +568,12 @@ public function viewAction( $twigData = [ 'submission' => $submission, + 'previousNext' => $this->getPreviousAndNextObjectIds( + Submission::class, + $submission->getExternalid(), + orderBy: ['e.submittime' => 'ASC', 'e.submitid' => 'ASC'], + filterOnContest: true, + ), 'lastSubmission' => $lastSubmission, 'judgings' => $judgings, 'maxRunTimes' => $maxRunTimes, @@ -590,7 +602,7 @@ public function viewAction( // Automatically refresh page while we wait for judging data. $twigData['refresh'] = [ 'after' => 15, - 'url' => $this->generateUrl('jury_submission', ['submitId' => $submission->getSubmitid()]), + 'url' => $this->generateUrl('jury_submission', ['submitId' => $submission->getExternalid()]), ]; } else { $contestProblem = $submission->getContestProblem(); @@ -681,7 +693,7 @@ public function requestFullDebug(Request $request, Judging $jid): RedirectRespon $this->em->flush(); } return $this->redirectToLocalReferrer($this->router, $request, $this->generateUrl('jury_submission', [ - 'submitId' => $jid->getSubmission()->getSubmitid(), + 'submitId' => $jid->getSubmission()->getExternalid(), 'jid' => $jid->getJudgingid(), ])); } @@ -715,7 +727,7 @@ public function requestOutput(Request $request, Judging $jid, JudgingRun $jrid): $this->em->persist($judgeTask); $this->em->flush(); return $this->redirectToLocalReferrer($this->router, $request, $this->generateUrl('jury_submission', [ - 'submitId' => $jid->getSubmission()->getSubmitid(), + 'submitId' => $jid->getSubmission()->getExternalid(), 'jid' => $jid->getJudgingid(), ])); } @@ -724,7 +736,7 @@ public function requestOutput(Request $request, Judging $jid, JudgingRun $jrid): public function viewForJudgingAction(Judging $jid): RedirectResponse { return $this->redirectToRoute('jury_submission', [ - 'submitId' => $jid->getSubmission()->getSubmitid(), + 'submitId' => $jid->getSubmission()->getExternalid(), 'jid' => $jid->getJudgingid(), ]); } @@ -733,7 +745,7 @@ public function viewForJudgingAction(Judging $jid): RedirectResponse public function viewForExternalJudgementAction(ExternalJudgement $externalJudgement): RedirectResponse { return $this->redirectToRoute('jury_submission', [ - 'submitId' => $externalJudgement->getSubmission()->getSubmitid(), + 'submitId' => $externalJudgement->getSubmission()->getExternalid(), ]); } @@ -756,35 +768,18 @@ public function viewForContestExternalIdAction(string $externalContestId, string } return $this->redirectToRoute('jury_submission', [ - 'submitId' => $submission->getSubmitid(), - ]); - } - - #[Route(path: '/by-external-id/{externalId}', name: 'jury_submission_by_external_id')] - public function viewForExternalIdAction(string $externalId): RedirectResponse - { - if (!$this->dj->getCurrentContest()) { - throw new BadRequestHttpException("Cannot determine submission from external ID without selecting a contest."); - } - - $submission = $this->em->getRepository(Submission::class) - ->findOneBy([ - 'contest' => $this->dj->getCurrentContest(), - 'externalid' => $externalId - ]); - - if (!$submission) { - throw new NotFoundHttpException(sprintf('No submission found with external ID %s', $externalId)); - } - - return $this->redirectToRoute('jury_submission', [ - 'submitId' => $submission->getSubmitid(), + 'submitId' => $submission->getExternalid(), ]); } #[Route(path: '/{submission}/runs/{contest}/{run}/team-output', name: 'jury_submission_team_output')] - public function teamOutputAction(Submission $submission, Contest $contest, JudgingRun $run): StreamedResponse - { + public function teamOutputAction( + #[MapEntity(mapping: ['submission' => 'externalid'])] + Submission $submission, + #[MapEntity(mapping: ['contest' => 'externalid'])] + Contest $contest, + JudgingRun $run, + ): StreamedResponse { if ($run->getJudging()->getSubmission()->getSubmitid() !== $submission->getSubmitid() || $submission->getContest()->getCid() !== $contest->getCid()) { throw new BadRequestHttpException('Integrity problem while fetching team output.'); } @@ -792,9 +787,9 @@ public function teamOutputAction(Submission $submission, Contest $contest, Judgi throw new NotFoundHttpException('No team output available (yet).'); } - $filename = sprintf('p%d.t%d.%s.run%d.team%d.out', $submission->getProblem()->getProbid(), $run->getTestcase()->getRank(), + $filename = sprintf('%s.t%d.%s.run%d.%s.out', $submission->getProblem()->getExternalid(), $run->getTestcase()->getRank(), $submission->getContestProblem()->getShortname(), $run->getRunid(), - $submission->getTeam()->getTeamid()); + $submission->getTeam()->getExternalid()); $outputRun = $run->getOutput()->getOutputRun(); return Utils::streamAsBinaryFile($outputRun, $filename); @@ -809,6 +804,7 @@ private function allowEdit(): bool { */ #[Route(path: '/{submission}/source', name: 'jury_submission_source')] public function sourceAction( + #[MapEntity(mapping: ['submission' => 'externalid'])] Submission $submission, #[MapQueryParameter] ?int $fetch = null @@ -840,7 +836,7 @@ public function sourceAction( } /** @var array{ - * submitid: int, + * submitid: string, * tag?: string * } otherSubmissions */ @@ -848,7 +844,7 @@ public function sourceAction( $originalSubmission = $submission->getOriginalSubmission(); if ($originalSubmission) { $otherSubmissions[] = [ - 'submitid' => $originalSubmission->getSubmitid(), + 'submitid' => $originalSubmission->getExternalid(), 'tag' => 'original', ]; /** @var Submission $oldSubmission */ @@ -888,20 +884,21 @@ public function sourceAction( } if ($oldSubmission !== null) { $otherSubmissions[] = [ - 'submitid' => $oldSubmission->getSubmitid(), + 'submitid' => $oldSubmission->getExternalid(), 'tag' => 'previous', ]; } $files_query = array_map(fn($s) => $s['submitid'], $otherSubmissions); - $files_query[] = $submission->getSubmitid(); + $files_query[] = $submission->getExternalid(); /** @var SubmissionFile[] $oldFiles */ $oldFiles = $this->em->createQueryBuilder() ->from(SubmissionFile::class, 'file') ->select('file') - ->andWhere('file.submission in (:submissions)') + ->join('file.submission', 's') + ->andWhere('s.externalid in (:submissions)') ->setParameter('submissions', $files_query) - ->orderBy('file.submission, file.ranknumber') + ->orderBy('s.externalid, file.ranknumber') ->getQuery() ->getResult(); @@ -915,7 +912,7 @@ public function sourceAction( /** @var array $renames */ $renames = []; foreach ($oldFiles as $f) { - $submitId = $f->getSubmission()->getSubmitid(); + $submitId = $f->getSubmission()->getExternalid(); $files[$f->getFilename()] ??= []; $files[$f->getFilename()][$submitId] = [ 'rank' => $f->getRank(), @@ -928,7 +925,7 @@ public function sourceAction( } // Handle file renaming for a single-file submission. - $renamedTo = $renames[$submission->getSubmitid()]; + $renamedTo = $renames[$submission->getExternalid()]; if ($renamedTo !== false) { foreach ($renames as $submitId => $filename) { if ($filename !== false && $filename !== $renamedTo) { @@ -954,13 +951,18 @@ public function sourceAction( } #[Route(path: '/{submission}/edit-source', name: 'jury_submission_edit_source')] - public function editSourceAction(Request $request, Submission $submission, #[MapQueryParameter] ?int $rank = null): Response - { + public function editSourceAction( + Request $request, + #[MapEntity(mapping: ['submission' => 'externalid'])] + Submission $submission, + #[MapQueryParameter] + ?int $rank = null + ): Response { if (!$this->allowEdit()) { $this->addFlash('danger', 'You cannot re-submit code without being a team.'); return $this->redirectToLocalReferrer($this->router, $request, $this->generateUrl( 'jury_submission', - ['submitId' => $submission->getSubmitid()] + ['submitId' => $submission->getExternalid()] )); } @@ -988,6 +990,7 @@ public function editSourceAction(Request $request, Submission $submission, #[Map ->add('problem', EntityType::class, [ 'class' => Problem::class, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'query_builder' => fn(EntityRepository $er) => $er->createQueryBuilder('p') ->join('p.contest_problems', 'cp') ->andWhere('cp.allowSubmit = 1') @@ -998,6 +1001,7 @@ public function editSourceAction(Request $request, Submission $submission, #[Map ->add('language', EntityType::class, [ 'class' => Language::class, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'query_builder' => fn(EntityRepository $er) => $er->createQueryBuilder('lang') ->andWhere('lang.allowSubmit = 1') ->orderBy('lang.name') @@ -1058,10 +1062,10 @@ public function editSourceAction(Request $request, Submission $submission, #[Map if (!$submittedSubmission) { $this->addFlash('danger', $message); - return $this->redirectToRoute('jury_submission', ['submitId' => $submission->getSubmitid()]); + return $this->redirectToRoute('jury_submission', ['submitId' => $submission->getExternalid()]); } - return $this->redirectToRoute('jury_submission', ['submitId' => $submittedSubmission->getSubmitid()]); + return $this->redirectToRoute('jury_submission', ['submitId' => $submittedSubmission->getExternalid()]); } $twigData = [ @@ -1098,14 +1102,14 @@ public function requestRemainingRuns(Request $request, int $judgingId): Redirect * @throws DBALException */ #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{submitId<\d+>}/update-status', name: 'jury_submission_update_status', methods: ['POST'])] + #[Route(path: '/{submitId}/update-status', name: 'jury_submission_update_status', methods: ['POST'])] public function updateStatusAction( EventLogService $eventLogService, ScoreboardService $scoreboardService, Request $request, - int $submitId + string $submitId ): RedirectResponse { - $submission = $this->em->getRepository(Submission::class)->find($submitId); + $submission = $this->em->getRepository(Submission::class)->findByExternalId($submitId); $valid = $request->request->getBoolean('valid'); $submission->setValid($valid); $this->em->flush(); @@ -1118,7 +1122,7 @@ public function updateStatusAction( // FIXME: We should also delete/recreate any dependent judging(runs). $eventLogService->log('submission', $submission->getSubmitid(), ($valid ? 'create' : 'delete'), $submission->getContest()->getCid(), null, null, $valid); - $this->dj->auditlog('submission', $submission->getSubmitid(), + $this->dj->auditlog('submission', $submission->getExternalid(), 'marked ' . ($valid ? 'valid' : 'invalid')); $contest = $this->em->getRepository(Contest::class)->find($contestId); $team = $this->em->getRepository(Team::class)->find($teamId); @@ -1126,7 +1130,7 @@ public function updateStatusAction( $scoreboardService->calculateScoreRow($contest, $team, $problem); return $this->redirectToLocalReferrer($this->router, $request, - $this->generateUrl('jury_submission', ['submitId' => $submission->getSubmitid()]) + $this->generateUrl('jury_submission', ['submitId' => $submission->getExternalid()]) ); } @@ -1155,7 +1159,7 @@ public function verifyAction( ->setVerifyComment($comment); $this->em->flush(); - $this->dj->auditlog('judging', $judging->getJudgingid(), + $this->dj->auditlog('judging', (string)$judging->getJudgingid(), $verified ? 'set verified' : 'set unverified'); if ((bool)$this->config->get('verification_required')) { @@ -1206,7 +1210,6 @@ public function verifyAction( #[Route(path: '/shadow-difference/{extjudgementid<\d+>}/verify', name: 'jury_shadow_difference_verify', methods: ['POST'])] public function verifyShadowDifferenceAction( - EventLogService $eventLogService, Request $request, int $extjudgementid ): RedirectResponse { @@ -1221,7 +1224,7 @@ public function verifyShadowDifferenceAction( ->setVerifyComment($comment); $this->em->flush(); - $this->dj->auditlog('external_judgement', $judgement->getExtjudgementid(), + $this->dj->auditlog('external_judgement', (string)$judgement->getExtjudgementid(), $verified ? 'set verified' : 'set unverified'); }); @@ -1267,10 +1270,10 @@ protected function processClaim( $auditLogType = 'judging'; $auditLogId = $judging->getJudgingid(); } - $this->dj->auditlog($auditLogType, $auditLogId, $action . 'ed'); + $this->dj->auditlog($auditLogType, (string)$auditLogId, $action . 'ed'); if ($action === 'claim') { - return $this->redirectToRoute('jury_submission', ['submitId' => $judging->getSubmission()->getSubmitid()]); + return $this->redirectToRoute('jury_submission', ['submitId' => $judging->getSubmission()->getExternalid()]); } else { return $this->redirectToLocalReferrer($this->router, $request, $this->generateUrl('jury_submissions') @@ -1282,7 +1285,7 @@ protected function processClaim( return null; } - #[Route(path: '/{submitId<\d+>}/create-tasks', name: 'jury_submission_create_tasks')] + #[Route(path: '/{submitId}/create-tasks', name: 'jury_submission_create_tasks')] public function createJudgeTasks(string $submitId): RedirectResponse { $this->dj->unblockJudgeTasksForSubmission($submitId); diff --git a/webapp/src/Controller/Jury/TeamAffiliationController.php b/webapp/src/Controller/Jury/TeamAffiliationController.php index ef0ab44ee5..9dd4c9183b 100644 --- a/webapp/src/Controller/Jury/TeamAffiliationController.php +++ b/webapp/src/Controller/Jury/TeamAffiliationController.php @@ -37,10 +37,7 @@ public function __construct( } #[Route(path: '', name: 'jury_team_affiliations')] - public function indexAction( - #[Autowire('%kernel.project_dir%')] - string $projectDir - ): Response { + public function indexAction(): Response { $em = $this->em; $teamAffiliations = $em->createQueryBuilder() ->select('a', 'COUNT(t.teamid) AS num_teams') @@ -53,8 +50,7 @@ public function indexAction( $showFlags = $this->config->get('show_flags'); $table_fields = [ - 'affilid' => ['title' => 'ID', 'sort' => true], - 'externalid' => ['title' => 'external ID', 'sort' => true], + 'externalid' => ['title' => 'ID', 'sort' => true], 'icpcid' => ['title' => 'ICPC ID', 'sort' => true], 'shortname' => ['title' => 'shortname', 'sort' => true], 'name' => ['title' => 'name', 'sort' => true, 'default_sort' => true], @@ -77,7 +73,7 @@ public function indexAction( $affiliationdata = []; $affiliationactions = []; - $this->addEntityCheckbox($affiliationdata, $teamAffiliation, $teamAffiliation->getAffilid(), 'affiliation-checkbox'); + $this->addEntityCheckbox($affiliationdata, $teamAffiliation, $teamAffiliation->getExternalid(), 'affiliation-checkbox'); // Get whatever fields we can from the affiliation object itself. foreach ($table_fields as $k => $v) { @@ -91,14 +87,14 @@ public function indexAction( 'icon' => 'edit', 'title' => 'edit this affiliation', 'link' => $this->generateUrl('jury_team_affiliation_edit', [ - 'affilId' => $teamAffiliation->getAffilid(), + 'affilId' => $teamAffiliation->getExternalid(), ]) ]; $affiliationactions[] = [ 'icon' => 'trash-alt', 'title' => 'delete this affiliation', 'link' => $this->generateUrl('jury_team_affiliation_delete', [ - 'affilId' => $teamAffiliation->getAffilid(), + 'affilId' => $teamAffiliation->getExternalid(), ]), 'ajaxModal' => true, ]; @@ -114,14 +110,14 @@ public function indexAction( } $affiliationdata['affiliation_logo'] = [ - 'value' => $teamAffiliation->getExternalid() ?? $teamAffiliation->getAffilid(), + 'value' => $teamAffiliation->getExternalid(), 'title' => $teamAffiliation->getShortname(), ]; $team_affiliations_table[] = [ 'data' => $affiliationdata, 'actions' => $affiliationactions, - 'link' => $this->generateUrl('jury_team_affiliation', ['affilId' => $teamAffiliation->getAffilid()]), + 'link' => $this->generateUrl('jury_team_affiliation', ['affilId' => $teamAffiliation->getExternalid()]), ]; } @@ -131,20 +127,24 @@ public function indexAction( ]); } - #[Route(path: '/{affilId<\d+>}', name: 'jury_team_affiliation')] - public function viewAction(Request $request, ScoreboardService $scoreboardService, int $affilId): Response + #[Route(path: '/{affilId}', name: 'jury_team_affiliation')] + public function viewAction(Request $request, ScoreboardService $scoreboardService, string $affilId): Response { - $teamAffiliation = $this->em->getRepository(TeamAffiliation::class)->find($affilId); + $teamAffiliation = $this->em->getRepository(TeamAffiliation::class)->findByExternalId($affilId); if (!$teamAffiliation) { throw new NotFoundHttpException(sprintf('Team affiliation with ID %s not found', $affilId)); } $data = [ 'teamAffiliation' => $teamAffiliation, + 'previousNext' => $this->getPreviousAndNextObjectIds( + TeamAffiliation::class, + $teamAffiliation->getExternalid(), + ), 'showFlags' => $this->config->get('show_flags'), 'refresh' => [ 'after' => 30, - 'url' => $this->generateUrl('jury_team_affiliation', ['affilId' => $teamAffiliation->getAffilid()]), + 'url' => $this->generateUrl('jury_team_affiliation', ['affilId' => $teamAffiliation->getExternalid()]), 'ajax' => true, ], 'maxWidth' => $this->config->get('team_column_width'), @@ -171,10 +171,10 @@ public function viewAction(Request $request, ScoreboardService $scoreboardServic } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{affilId<\d+>}/edit', name: 'jury_team_affiliation_edit')] - public function editAction(Request $request, int $affilId): Response + #[Route(path: '/{affilId}/edit', name: 'jury_team_affiliation_edit')] + public function editAction(Request $request, string $affilId): Response { - $teamAffiliation = $this->em->getRepository(TeamAffiliation::class)->find($affilId); + $teamAffiliation = $this->em->getRepository(TeamAffiliation::class)->findByExternalId($affilId); if (!$teamAffiliation) { throw new NotFoundHttpException(sprintf('Team affiliation with ID %s not found', $affilId)); } @@ -186,7 +186,7 @@ public function editAction(Request $request, int $affilId): Response if ($form->isSubmitted() && $form->isValid()) { $this->assetUpdater->updateAssets($teamAffiliation); $this->saveEntity($teamAffiliation, $teamAffiliation->getAffilid(), false); - return $this->redirectToRoute('jury_team_affiliation', ['affilId' => $teamAffiliation->getAffilid()]); + return $this->redirectToRoute('jury_team_affiliation', ['affilId' => $teamAffiliation->getExternalid()]); } return $this->render('jury/team_affiliation_edit.html.twig', [ @@ -196,10 +196,10 @@ public function editAction(Request $request, int $affilId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{affilId<\d+>}/delete', name: 'jury_team_affiliation_delete')] - public function deleteAction(Request $request, int $affilId): Response + #[Route(path: '/{affilId}/delete', name: 'jury_team_affiliation_delete')] + public function deleteAction(Request $request, string $affilId): Response { - $teamAffiliation = $this->em->getRepository(TeamAffiliation::class)->find($affilId); + $teamAffiliation = $this->em->getRepository(TeamAffiliation::class)->findByExternalId($affilId); if (!$teamAffiliation) { throw new NotFoundHttpException(sprintf('Team affiliation with ID %s not found', $affilId)); } @@ -208,20 +208,20 @@ public function deleteAction(Request $request, int $affilId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/delete-multiple', name: 'jury_team_affiliation_delete_multiple', methods: ['GET', 'POST'])] + #[Route(path: '/delete-multiple', name: 'jury_team_affiliation_delete_multiple', methods: ['GET', 'POST'], priority: 1)] public function deleteMultipleAction(Request $request): Response { return $this->deleteMultiple( $request, TeamAffiliation::class, - 'affilid', + 'externalid', 'jury_team_affiliations', 'No affiliations could be deleted.' ); } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/add', name: 'jury_team_affiliation_add')] + #[Route(path: '/add', name: 'jury_team_affiliation_add', priority: 1)] public function addAction(Request $request): Response { $teamAffiliation = new TeamAffiliation(); @@ -232,7 +232,7 @@ public function addAction(Request $request): Response if ($response = $this->processAddFormForExternalIdEntity( $form, $teamAffiliation, - fn() => $this->generateUrl('jury_team_affiliation', ['affilId' => $teamAffiliation->getAffilid()]), + fn() => $this->generateUrl('jury_team_affiliation', ['affilId' => $teamAffiliation->getExternalid()]), function () use ($teamAffiliation) { $this->em->persist($teamAffiliation); $this->assetUpdater->updateAssets($teamAffiliation); diff --git a/webapp/src/Controller/Jury/TeamCategoryController.php b/webapp/src/Controller/Jury/TeamCategoryController.php index 19f845c94f..b3f7cca5d4 100644 --- a/webapp/src/Controller/Jury/TeamCategoryController.php +++ b/webapp/src/Controller/Jury/TeamCategoryController.php @@ -56,8 +56,7 @@ public function indexAction(): Response ->groupBy('c.categoryid') ->getQuery()->getResult(); $table_fields = [ - 'categoryid' => ['title' => 'ID', 'sort' => true], - 'externalid' => ['title' => 'external ID', 'sort' => true], + 'externalid' => ['title' => 'ID', 'sort' => true], 'icpcid' => ['title' => 'ICPC ID', 'sort' => true], 'sortorder' => ['title' => 'sort', 'sort' => true, 'default_sort' => true], 'name' => ['title' => 'name', 'sort' => true], @@ -76,7 +75,7 @@ public function indexAction(): Response $categorydata = []; $categoryactions = []; - $this->addEntityCheckbox($categorydata, $teamCategory, $teamCategory->getCategoryid(), 'category-checkbox'); + $this->addEntityCheckbox($categorydata, $teamCategory, $teamCategory->getExternalid(), 'category-checkbox'); // Get whatever fields we can from the category object itself. foreach ($table_fields as $k => $v) { @@ -90,14 +89,14 @@ public function indexAction(): Response 'icon' => 'edit', 'title' => 'edit this category', 'link' => $this->generateUrl('jury_team_category_edit', [ - 'categoryId' => $teamCategory->getCategoryid(), + 'categoryId' => $teamCategory->getExternalid(), ]) ]; $categoryactions[] = [ 'icon' => 'trash-alt', 'title' => 'delete this category', 'link' => $this->generateUrl('jury_team_category_delete', [ - 'categoryId' => $teamCategory->getCategoryid(), + 'categoryId' => $teamCategory->getExternalid(), ]), 'ajaxModal' => true, ]; @@ -110,7 +109,7 @@ public function indexAction(): Response $team_categories_table[] = [ 'data' => $categorydata, 'actions' => $categoryactions, - 'link' => $this->generateUrl('jury_team_category', ['categoryId' => $teamCategory->getCategoryid()]), + 'link' => $this->generateUrl('jury_team_category', ['categoryId' => $teamCategory->getExternalid()]), 'style' => $teamCategory->getColor() ? sprintf('background-color: %s;', $teamCategory->getColor()) : '', ]; } @@ -124,10 +123,10 @@ public function indexAction(): Response * @throws NoResultException * @throws NonUniqueResultException */ - #[Route(path: '/{categoryId<\d+>}', name: 'jury_team_category')] - public function viewAction(Request $request, SubmissionService $submissionService, int $categoryId): Response + #[Route(path: '/{categoryId}', name: 'jury_team_category')] + public function viewAction(Request $request, SubmissionService $submissionService, string $categoryId): Response { - $teamCategory = $this->em->getRepository(TeamCategory::class)->find($categoryId); + $teamCategory = $this->em->getRepository(TeamCategory::class)->findByExternalId($categoryId); if (!$teamCategory) { throw new NotFoundHttpException(sprintf('Team category with ID %s not found', $categoryId)); } @@ -141,13 +140,17 @@ public function viewAction(Request $request, SubmissionService $submissionServic $data = [ 'teamCategory' => $teamCategory, + 'previousNext' => $this->getPreviousAndNextObjectIds( + TeamCategory::class, + $teamCategory->getExternalid(), + ), 'submissions' => $submissions, 'submissionCounts' => $submissionCounts, 'showContest' => count($this->dj->getCurrentContests(honorCookie: true)) > 1, 'showExternalResult' => $this->dj->shadowMode(), 'refresh' => [ 'after' => 15, - 'url' => $this->generateUrl('jury_team_category', ['categoryId' => $teamCategory->getCategoryid()]), + 'url' => $this->generateUrl('jury_team_category', ['categoryId' => $teamCategory->getExternalid()]), 'ajax' => true, ], ]; @@ -162,10 +165,10 @@ public function viewAction(Request $request, SubmissionService $submissionServic } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{categoryId<\d+>}/edit', name: 'jury_team_category_edit')] - public function editAction(Request $request, int $categoryId): Response + #[Route(path: '/{categoryId}/edit', name: 'jury_team_category_edit')] + public function editAction(Request $request, string $categoryId): Response { - $teamCategory = $this->em->getRepository(TeamCategory::class)->find($categoryId); + $teamCategory = $this->em->getRepository(TeamCategory::class)->findByExternalId($categoryId); if (!$teamCategory) { throw new NotFoundHttpException(sprintf('Team category with ID %s not found', $categoryId)); } @@ -193,7 +196,7 @@ public function editAction(Request $request, int $categoryId): Response } } $this->addFlash('scoreboard_refresh', 'If the category sort order was changed, it may be necessary to recalculate any cached scoreboards.'); - return $this->redirectToRoute('jury_team_category', ['categoryId' => $teamCategory->getCategoryid()]); + return $this->redirectToRoute('jury_team_category', ['categoryId' => $teamCategory->getExternalid()]); } return $this->render('jury/team_category_edit.html.twig', [ @@ -203,10 +206,10 @@ public function editAction(Request $request, int $categoryId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{categoryId<\d+>}/delete', name: 'jury_team_category_delete')] - public function deleteAction(Request $request, int $categoryId): Response + #[Route(path: '/{categoryId}/delete', name: 'jury_team_category_delete')] + public function deleteAction(Request $request, string $categoryId): Response { - $teamCategory = $this->em->getRepository(TeamCategory::class)->find($categoryId); + $teamCategory = $this->em->getRepository(TeamCategory::class)->findByExternalId($categoryId); if (!$teamCategory) { throw new NotFoundHttpException(sprintf('Team category with ID %s not found', $categoryId)); } @@ -215,7 +218,7 @@ public function deleteAction(Request $request, int $categoryId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/add', name: 'jury_team_category_add')] + #[Route(path: '/add', name: 'jury_team_category_add', priority: 1)] public function addAction(Request $request): Response { $teamCategory = new TeamCategory(); @@ -226,7 +229,7 @@ public function addAction(Request $request): Response if ($response = $this->processAddFormForExternalIdEntity( $form, $teamCategory, - fn() => $this->generateUrl('jury_team_category', ['categoryId' => $teamCategory->getCategoryid()]) + fn() => $this->generateUrl('jury_team_category', ['categoryId' => $teamCategory->getExternalid()]) )) { return $response; } @@ -237,26 +240,26 @@ public function addAction(Request $request): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/delete-multiple', name: 'jury_team_category_delete_multiple', methods: ['GET', 'POST'])] + #[Route(path: '/delete-multiple', name: 'jury_team_category_delete_multiple', methods: ['GET', 'POST'], priority: 1)] public function deleteMultipleAction(Request $request): Response { return $this->deleteMultiple( $request, TeamCategory::class, - 'categoryid', + 'externalid', 'jury_team_categories', 'No categories could be deleted.' ); } - #[Route(path: '/{categoryId<\d+>}/request-remaining', name: 'jury_team_category_request_remaining')] + #[Route(path: '/{categoryId}/request-remaining', name: 'jury_team_category_request_remaining')] public function requestRemainingRunsWholeTeamCategoryAction(string $categoryId): RedirectResponse { $category = $this->em->getRepository(TeamCategory::class)->find($categoryId); if (!$category) { throw new NotFoundHttpException(sprintf('Team category with ID %s not found', $categoryId)); } - $contestId = $this->dj->getCurrentContest()->getCid(); + $contestId = $this->dj->getCurrentContest()->getExternalid(); $this->judgeRemaining(contestId: $contestId, categoryId: $categoryId); return $this->redirectToRoute('jury_team_category', ['categoryId' => $categoryId]); } diff --git a/webapp/src/Controller/Jury/TeamController.php b/webapp/src/Controller/Jury/TeamController.php index 54522fcf26..87ae2dc466 100644 --- a/webapp/src/Controller/Jury/TeamController.php +++ b/webapp/src/Controller/Jury/TeamController.php @@ -5,6 +5,8 @@ use App\Controller\BaseController; use App\DataTransferObject\SubmissionRestriction; use App\Entity\Contest; +use App\Entity\Language; +use App\Entity\Problem; use App\Entity\Role; use App\Entity\Team; use App\Entity\User; @@ -89,8 +91,7 @@ public function indexAction(): Response $teams_that_solved = array_column($teams_that_solved, 'num_correct', 'teamid'); $table_fields = [ - 'teamid' => ['title' => 'ID', 'sort' => true, 'default_sort' => true], - 'externalid' => ['title' => 'external ID', 'sort' => true], + 'externalid' => ['title' => 'ID', 'sort' => true, 'default_sort' => true], 'label' => ['title' => 'label', 'sort' => true,], 'effective_name' => ['title' => 'name', 'sort' => true,], 'category' => ['title' => 'category', 'sort' => true,], @@ -118,7 +119,7 @@ public function indexAction(): Response $teamdata = []; $teamactions = []; - $this->addEntityCheckbox($teamdata, $t, $t->getTeamid(), 'team-checkbox', fn(Team $team) => !$team->isLocked()); + $this->addEntityCheckbox($teamdata, $t, $t->getExternalid(), 'team-checkbox', fn(Team $team) => !$team->isLocked()); // Get whatever fields we can from the team object itself. foreach ($table_fields as $k => $v) { @@ -153,14 +154,14 @@ public function indexAction(): Response 'icon' => 'edit', 'title' => 'edit this team', 'link' => $this->generateUrl('jury_team_edit', [ - 'teamId' => $t->getTeamid(), + 'teamId' => $t->getExternalid(), ]), ]; $teamactions[] = [ 'icon' => 'trash-alt', 'title' => 'delete this team', 'link' => $this->generateUrl('jury_team_delete', [ - 'teamId' => $t->getTeamId(), + 'teamId' => $t->getExternalid(), ]), 'ajaxModal' => true, ]; @@ -169,7 +170,7 @@ public function indexAction(): Response 'icon' => 'envelope', 'title' => 'send clarification to this team', 'link' => $this->generateUrl('jury_clarification_new', [ - 'teamto' => $t->getTeamId(), + 'teamto' => $t->getExternalid(), ]) ]; @@ -216,8 +217,8 @@ public function indexAction(): Response $teams_table[] = [ 'data' => $teamdata, 'actions' => $teamactions, - 'link' => $this->generateUrl('jury_team', ['teamId' => $t->getTeamId()]), - 'cssclass' => ($t->getCategory() ? ("category" . $t->getCategory()->getCategoryId()) : '') . + 'link' => $this->generateUrl('jury_team', ['teamId' => $t->getExternalid()]), + 'cssclass' => ($t->getCategory() ? ("category" . $t->getCategory()->getExternalid()) : '') . ($t->getEnabled() ? '' : ' disabled'), ]; } @@ -227,16 +228,16 @@ public function indexAction(): Response ]); } - #[Route(path: '/{teamId<\d+>}', name: 'jury_team')] + #[Route(path: '/{teamId}', name: 'jury_team')] public function viewAction( Request $request, - int $teamId, + string $teamId, ScoreboardService $scoreboardService, SubmissionService $submissionService, #[MapQueryParameter] - ?int $cid = null, + ?string $cid = null, ): Response { - $team = $this->em->getRepository(Team::class)->find($teamId); + $team = $this->em->getRepository(Team::class)->findByExternalId($teamId); if (!$team) { throw new NotFoundHttpException(sprintf('Team with ID %s not found', $teamId)); } @@ -252,6 +253,10 @@ public function viewAction( 'ajax' => true, ], 'team' => $team, + 'previousNext' => $this->getPreviousAndNextObjectIds( + Team::class, + $team->getExternalid(), + ), 'showAffiliations' => (bool)$this->config->get('show_affiliations'), 'showFlags' => (bool)$this->config->get('show_flags'), 'showContest' => count($this->dj->getCurrentContests()) > 1, @@ -266,7 +271,7 @@ public function viewAction( if ($currentContest) { $scoreboard = $scoreboardService - ->getTeamScoreboard($currentContest, $teamId, true); + ->getTeamScoreboard($currentContest, $team->getExternalid(), true); $data = array_merge( $data, $scoreboardService->getScoreboardTwigData( @@ -289,12 +294,17 @@ public function viewAction( 'judgehost' => 'judgehost', default => throw new BadRequestHttpException(sprintf('Restriction on %s not allowed.', $key)), }; - $restrictions->$key = is_numeric($value) ? (int)$value : $value; + $restrictionValue = match ($key) { + 'problemId' => $this->em->getRepository(Problem::class)->findByExternalId($value)->getProbid(), + 'languageId' => $this->em->getRepository(Language::class)->findByExternalId($value)->getLangid(), + default => $value, + }; + $restrictions->$key = is_numeric($restrictionValue) ? (int)$restrictionValue : $restrictionValue; $restrictionTexts[] = sprintf('%s %s', $restrictionKeyText, $value); } $restrictionText = implode(', ', $restrictionTexts); } - $restrictions->teamId = $teamId; + $restrictions->teamId = $team->getTeamid(); [$submissions, $submissionCounts] = $submissionService->getSubmissionList( $this->dj->getCurrentContests(honorCookie: true), @@ -318,10 +328,10 @@ public function viewAction( } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{teamId<\d+>}/edit', name: 'jury_team_edit')] - public function editAction(Request $request, int $teamId): Response + #[Route(path: '/{teamId}/edit', name: 'jury_team_edit')] + public function editAction(Request $request, string $teamId): Response { - $team = $this->em->getRepository(Team::class)->find($teamId); + $team = $this->em->getRepository(Team::class)->findByExternalId($teamId); if (!$team) { throw new NotFoundHttpException(sprintf('Team with ID %s not found', $teamId)); } @@ -334,7 +344,7 @@ public function editAction(Request $request, int $teamId): Response $this->possiblyAddUser($team); $this->assetUpdater->updateAssets($team); $this->saveEntity($team, $team->getTeamid(), false); - return $this->redirectToRoute('jury_team', ['teamId' => $team->getTeamid()]); + return $this->redirectToRoute('jury_team', ['teamId' => $team->getExternalid()]); } return $this->render('jury/team_edit.html.twig', [ @@ -344,10 +354,10 @@ public function editAction(Request $request, int $teamId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{teamId<\d+>}/delete', name: 'jury_team_delete')] - public function deleteAction(Request $request, int $teamId): Response + #[Route(path: '/{teamId}/delete', name: 'jury_team_delete')] + public function deleteAction(Request $request, string $teamId): Response { - $team = $this->em->getRepository(Team::class)->find($teamId); + $team = $this->em->getRepository(Team::class)->findByExternalId($teamId); if (!$team) { throw new NotFoundHttpException(sprintf('Team with ID %s not found', $teamId)); } @@ -356,13 +366,13 @@ public function deleteAction(Request $request, int $teamId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/delete-multiple', name: 'jury_team_delete_multiple', methods: ['GET', 'POST'])] + #[Route(path: '/delete-multiple', name: 'jury_team_delete_multiple', methods: ['GET', 'POST'], priority: 1)] public function deleteMultipleAction(Request $request): Response { return $this->deleteMultiple( $request, Team::class, - 'teamid', + 'externalid', 'jury_teams', 'No teams could be deleted (they might be in a locked contest).', fn(Team $team) => !$team->isLocked() @@ -370,7 +380,7 @@ public function deleteMultipleAction(Request $request): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/add', name: 'jury_team_add')] + #[Route(path: '/add', name: 'jury_team_add', priority: 1)] public function addAction(Request $request): Response { $team = new Team(); @@ -381,7 +391,7 @@ public function addAction(Request $request): Response if ($response = $this->processAddFormForExternalIdEntity( $form, $team, - fn() => $this->generateUrl('jury_team', ['teamId' => $team->getTeamid()]), + fn() => $this->generateUrl('jury_team', ['teamId' => $team->getExternalid()]), function () use ($team) { $this->possiblyAddUser($team); $this->em->persist($team); diff --git a/webapp/src/Controller/Jury/UserController.php b/webapp/src/Controller/Jury/UserController.php index f696d82ae1..80380f5de8 100644 --- a/webapp/src/Controller/Jury/UserController.php +++ b/webapp/src/Controller/Jury/UserController.php @@ -62,7 +62,7 @@ public function indexAction(): Response $table_fields = [ 'username' => ['title' => 'username', 'sort' => true, 'default_sort' => true], - 'externalid' => ['title' => 'external ID', 'sort' => true], + 'externalid' => ['title' => 'ID', 'sort' => true], 'name' => ['title' => 'name', 'sort' => true], 'email' => ['title' => 'email', 'sort' => true], 'user_roles' => ['title' => 'roles', 'sort' => true], @@ -86,7 +86,7 @@ public function indexAction(): Response $userdata = []; $useractions = []; - $this->addEntityCheckbox($userdata, $u, $u->getUserid(), 'user-checkbox', fn(User $user) => $user->getUserid() !== $this->dj->getUser()->getUserid()); + $this->addEntityCheckbox($userdata, $u, $u->getExternalid(), 'user-checkbox', fn(User $user) => $user->getUserid() !== $this->dj->getUser()->getUserid()); // Get whatever fields we can from the user object itself. foreach ($table_fields as $k => $v) { @@ -105,13 +105,12 @@ public function indexAction(): Response if ($u->getTeam()) { $userdata['teamid'] = [ 'value' => $u->getTeam(), - 'idPrefix' => 't', ]; $userdata['team'] = [ 'value' => $u->getTeamName(), - 'sortvalue' => $u->getTeam()->getTeamid(), + 'sortvalue' => $u->getTeam()->getExternalid(), 'link' => $this->generateUrl('jury_team', [ - 'teamId' => $u->getTeam()->getTeamid(), + 'teamId' => $u->getTeam()->getExternalid(), ]), 'title' => $u->getTeam()->getEffectiveName(), ]; @@ -139,14 +138,14 @@ public function indexAction(): Response 'icon' => 'edit', 'title' => 'edit this user', 'link' => $this->generateUrl('jury_user_edit', [ - 'userId' => $u->getUserid(), + 'userId' => $u->getExternalid(), ]) ]; $useractions[] = [ 'icon' => 'trash-alt', 'title' => 'delete this user', 'link' => $this->generateUrl('jury_user_delete', [ - 'userId' => $u->getUserid(), + 'userId' => $u->getExternalid(), ]), 'ajaxModal' => true, ]; @@ -164,7 +163,7 @@ public function indexAction(): Response $users_table[] = [ 'data' => $userdata, 'actions' => $useractions, - 'link' => $this->generateUrl('jury_user', ['userId' => $u->getUserid()]), + 'link' => $this->generateUrl('jury_user', ['userId' => $u->getExternalid()]), 'cssclass' => $u->getEnabled() ? '' : 'disabled', ]; } @@ -175,10 +174,10 @@ public function indexAction(): Response ]); } - #[Route(path: '/{userId<\d+>}', name: 'jury_user')] - public function viewAction(Request $request, int $userId, SubmissionService $submissionService): Response + #[Route(path: '/{userId}', name: 'jury_user')] + public function viewAction(Request $request, string $userId, SubmissionService $submissionService): Response { - $user = $this->em->getRepository(User::class)->find($userId); + $user = $this->em->getRepository(User::class)->findByExternalId($userId); if (!$user) { throw new NotFoundHttpException(sprintf('User with ID %s not found', $userId)); } @@ -192,6 +191,11 @@ public function viewAction(Request $request, int $userId, SubmissionService $sub return $this->render('jury/user.html.twig', [ 'user' => $user, + 'previousNext' => $this->getPreviousAndNextObjectIds( + User::class, + $user->getExternalid(), + orderBy: ['e.username' => 'ASC'], + ), 'submissions' => $submissions, 'submissionCounts' => $submissionCounts, 'showContest' => count($this->dj->getCurrentContests(honorCookie: true)) > 1, @@ -219,10 +223,10 @@ public function checkPasswordLength(User $user, FormInterface $form): ?Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{userId<\d+>}/edit', name: 'jury_user_edit')] - public function editAction(Request $request, int $userId): Response + #[Route(path: '/{userId}/edit', name: 'jury_user_edit')] + public function editAction(Request $request, string $userId): Response { - $user = $this->em->getRepository(User::class)->find($userId); + $user = $this->em->getRepository(User::class)->findByExternalId($userId); if (!$user) { throw new NotFoundHttpException(sprintf('User with ID %s not found', $userId)); } @@ -248,7 +252,7 @@ public function editAction(Request $request, int $userId): Response $this->tokenStorage->setToken($token); } - return $this->redirectToRoute('jury_user', ['userId' => $user->getUserid()]); + return $this->redirectToRoute('jury_user', ['userId' => $user->getExternalid()]); } return $this->render('jury/user_edit.html.twig', [ @@ -258,10 +262,10 @@ public function editAction(Request $request, int $userId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/{userId<\d+>}/delete', name: 'jury_user_delete')] - public function deleteAction(Request $request, int $userId): Response + #[Route(path: '/{userId}/delete', name: 'jury_user_delete')] + public function deleteAction(Request $request, string $userId): Response { - $user = $this->em->getRepository(User::class)->find($userId); + $user = $this->em->getRepository(User::class)->findByExternalId($userId); if (!$user) { throw new NotFoundHttpException(sprintf('User with ID %s not found', $userId)); } @@ -270,7 +274,7 @@ public function deleteAction(Request $request, int $userId): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/add', name: 'jury_user_add')] + #[Route(path: '/add', name: 'jury_user_add', priority: 1)] public function addAction( Request $request, #[MapQueryParameter] @@ -287,7 +291,7 @@ public function addAction( if ($response = $this->processAddFormForExternalIdEntity( $form, $user, - fn() => $this->generateUrl('jury_user', ['userId' => $user->getUserid()]), + fn() => $this->generateUrl('jury_user', ['userId' => $user->getExternalid()]), function () use ($user, $form) { if ($errorResult = $this->checkPasswordLength($user, $form)) { return $errorResult; @@ -307,7 +311,7 @@ function () use ($user, $form) { } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/generate-passwords', name: 'jury_generate_passwords')] + #[Route(path: '/generate-passwords', name: 'jury_generate_passwords', priority: 1)] public function generatePasswordsAction(Request $request): Response { $form = $this->createForm(GeneratePasswordsType::class); @@ -345,7 +349,7 @@ public function generatePasswordsAction(Request $request): Response if ($doit) { $newpass = Utils::generatePassword(false); $user->setPlainPassword($newpass); - $this->dj->auditlog('user', $user->getUserid(), 'set password'); + $this->dj->auditlog('user', $user->getExternalid(), 'set password'); $changes[] = [ 'type' => $role, 'fullname' => $user->getName(), @@ -372,7 +376,7 @@ public function generatePasswordsAction(Request $request): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/reset_login_status', name: 'jury_reset_login_status')] + #[Route(path: '/reset_login_status', name: 'jury_reset_login_status', priority: 1)] public function resetTeamLoginStatus(Request $request): Response { /** @var Role $teamRole */ @@ -393,13 +397,13 @@ public function resetTeamLoginStatus(Request $request): Response } #[IsGranted('ROLE_ADMIN')] - #[Route(path: '/delete-multiple', name: 'jury_user_delete_multiple', methods: ['GET', 'POST'])] + #[Route(path: '/delete-multiple', name: 'jury_user_delete_multiple', methods: ['GET', 'POST'], priority: 1)] public function deleteMultipleAction(Request $request): Response { return $this->deleteMultiple( $request, User::class, - 'userid', + 'externalid', 'jury_users', 'No users could be deleted (you cannot delete your own account).', fn(User $user) => $user->getUserid() !== $this->dj->getUser()->getUserid() diff --git a/webapp/src/Controller/PublicController.php b/webapp/src/Controller/PublicController.php index 6a22a2d6e1..96380e09b3 100644 --- a/webapp/src/Controller/PublicController.php +++ b/webapp/src/Controller/PublicController.php @@ -74,7 +74,7 @@ public function scoreboardAction( if ($requestedContest = $this->getContestFromRequest($contestId)) { $contest = $requestedContest; - $refreshParams['contest'] = $contest->getCid(); + $refreshParams['contest'] = $contest->getExternalid(); } $refreshUrl = sprintf('?%s', http_build_query($refreshParams)); @@ -143,7 +143,7 @@ protected function getContestFromRequest(?string $contestId = null): ?Contest } else { // Find the contest with the given ID. foreach ($this->dj->getCurrentContests(onlyPublic: true) as $possibleContest) { - if ($possibleContest->getCid() == $contestId || $possibleContest->getExternalid() == $contestId) { + if ($possibleContest->getExternalid() == $contestId) { $contest = $possibleContest; break; } @@ -158,23 +158,23 @@ protected function getContestFromRequest(?string $contestId = null): ?Contest return $contest; } - #[Route(path: '/change-contest/{contestId<-?\d+>}', name: 'public_change_contest')] - public function changeContestAction(Request $request, RouterInterface $router, int $contestId): Response + #[Route(path: '/change-contest/{contestId}', name: 'public_change_contest')] + public function changeContestAction(Request $request, RouterInterface $router, string $contestId): Response { if ($this->isLocalReferer($router, $request)) { $response = new RedirectResponse($request->headers->get('referer')); } else { $response = $this->redirectToRoute('public_index'); } - return $this->dj->setCookie('domjudge_cid', (string)$contestId, 0, null, '', false, false, + return $this->dj->setCookie('domjudge_cid', $contestId, 0, null, '', false, false, $response); } - #[Route(path: '/team/{teamId<\d+>}', name: 'public_team')] - public function teamAction(Request $request, int $teamId): Response + #[Route(path: '/team/{teamId}', name: 'public_team')] + public function teamAction(Request $request, string $teamId): Response { /** @var Team|null $team */ - $team = $this->em->getRepository(Team::class)->find($teamId); + $team = $this->em->getRepository(Team::class)->findByExternalId($teamId); if ($team && $team->getCategory() && !$team->getCategory()->getVisible()) { $team = null; } @@ -203,11 +203,11 @@ public function problemsAction(): Response $this->dj->getTwigDataForProblemsAction($this->stats)); } - #[Route(path: '/problems/{probId<\d+>}/statement', name: 'public_problem_statement')] - public function problemStatementAction(int $probId): StreamedResponse + #[Route(path: '/problems/{probId}/statement', name: 'public_problem_statement')] + public function problemStatementAction(string $probId): StreamedResponse { return $this->getBinaryFile($probId, function ( - int $probId, + string $probId, Contest $contest, ContestProblem $contestProblem ) { @@ -235,20 +235,20 @@ public function contestProblemsetAction(): StreamedResponse /** * @throws NonUniqueResultException */ - #[Route(path: '/{probId<\d+>}/attachment/{attachmentId<\d+>}', name: 'public_problem_attachment')] - public function attachmentAction(int $probId, int $attachmentId): StreamedResponse + #[Route(path: '/{probId}/attachment/{attachmentId<\d+>}', name: 'public_problem_attachment')] + public function attachmentAction(string $probId, int $attachmentId): StreamedResponse { return $this->getBinaryFile($probId, fn( - int $probId, + string $probId, Contest $contest, ContestProblem $contestProblem ) => $this->dj->getAttachmentStreamedResponse($contestProblem, $attachmentId)); } - #[Route(path: '/{probId<\d+>}/samples.zip', name: 'public_problem_sample_zip')] - public function sampleZipAction(int $probId): StreamedResponse + #[Route(path: '/{probId}/samples.zip', name: 'public_problem_sample_zip')] + public function sampleZipAction(string $probId): StreamedResponse { - return $this->getBinaryFile($probId, function (int $probId, Contest $contest, ContestProblem $contestProblem) { + return $this->getBinaryFile($probId, function (string $probId, Contest $contest, ContestProblem $contestProblem) { return $this->dj->getSamplesZipStreamedResponse($contestProblem); }); } @@ -258,16 +258,13 @@ public function sampleZipAction(int $probId): StreamedResponse * * Shared code between testcases, problem text and attachments. */ - protected function getBinaryFile(int $probId, callable $response): StreamedResponse + protected function getBinaryFile(string $probId, callable $response): StreamedResponse { $contest = $this->dj->getCurrentContest(onlyPublic: true); if (!$contest || !$contest->getFreezeData()->started()) { throw new NotFoundHttpException(sprintf('Problem p%d not found or not available', $probId)); } - $contestProblem = $this->em->getRepository(ContestProblem::class)->find([ - 'problem' => $probId, - 'contest' => $contest, - ]); + $contestProblem = $this->em->getRepository(ContestProblem::class)->findByProblemAndContest($contest, $probId); if (!$contestProblem) { throw new NotFoundHttpException(sprintf('Problem p%d not found or not available', $probId)); } @@ -285,7 +282,7 @@ public function submissionsAction(Request $request, string $teamId, string $prob } /** @var Team|null $team */ - $team = $this->em->getRepository(Team::class)->findOneBy(['externalid' => $teamId]); + $team = $this->em->getRepository(Team::class)->findByExternalId($teamId); if ($team && $team->getCategory() && !$team->getCategory()->getVisible()) { $team = null; } @@ -379,7 +376,7 @@ public function submissionsDataAction(Request $request, ?string $teamId, ?string } $submissionData[$teamKey][$problemKey][] = [ 'time' => $this->twigExtension->printtime($submission->getSubmittime(), contest: $contest), - 'language' => $submission->getLanguageId(), + 'language' => $submission->getLanguage()->getName(), 'verdict' => $this->submissionVerdict($submission, $contest, $verificationRequired), ]; } diff --git a/webapp/src/Controller/Team/ClarificationController.php b/webapp/src/Controller/Team/ClarificationController.php index c6c5079ff1..d1d4771bf3 100644 --- a/webapp/src/Controller/Team/ClarificationController.php +++ b/webapp/src/Controller/Team/ClarificationController.php @@ -45,17 +45,17 @@ public function __construct( parent::__construct($em, $eventLogService, $dj, $kernel); } - #[Route(path: '/clarifications/by-problem/{probId<\d+>}', name: 'team_clarification_by_prob')] - public function viewByProblemAction(Request $request, int $probId): Response + #[Route(path: '/clarifications/by-problem/{probId}', name: 'team_clarification_by_prob')] + public function viewByProblemAction(Request $request, string $probId): Response { $user = $this->dj->getUser(); $team = $user->getTeam(); $teamId = $team->getTeamid(); $contest = $this->dj->getCurrentContest($teamId); - $problem = $this->em->getRepository(Problem::class)->find($probId); + $problem = $this->em->getRepository(Problem::class)->findByExternalId($probId); if ($problem === null) { - throw new NotFoundHttpException(sprintf('Problem %d not found', $probId)); + throw new NotFoundHttpException(sprintf('Problem %s not found', $probId)); } $contestProblem = $problem->getContestProblems(); $foundProblemInContest = false; @@ -66,7 +66,7 @@ public function viewByProblemAction(Request $request, int $probId): Response } } if (!$foundProblemInContest) { - throw new NotFoundHttpException(sprintf('Problem %d not in current contest', $probId)); + throw new NotFoundHttpException(sprintf('Problem %s not in current contest', $probId)); } /** @var Clarification[] $clarifications */ @@ -103,8 +103,8 @@ public function viewByProblemAction(Request $request, int $probId): Response /** * @throws NonUniqueResultException */ - #[Route(path: '/clarifications/{clarId<\d+>}', name: 'team_clarification')] - public function viewAction(Request $request, int $clarId): Response + #[Route(path: '/clarifications/{clarId}', name: 'team_clarification')] + public function viewAction(Request $request, string $clarId): Response { $categories = $this->config->get('clar_categories'); $user = $this->dj->getUser(); @@ -118,7 +118,7 @@ public function viewAction(Request $request, int $clarId): Response ->leftJoin('p.contest_problems', 'cp', Join::WITH, 'cp.contest = :contest') ->select('c, p, co') ->andWhere('c.contest = :contest') - ->andWhere('c.clarid = :clarId') + ->andWhere('c.externalid = :clarId') ->setParameter('contest', $contest) ->setParameter('clarId', $clarId) ->getQuery() @@ -127,9 +127,9 @@ public function viewAction(Request $request, int $clarId): Response $formData = []; if ($clarification) { if ($clarification->getProblem()) { - $formData['subject'] = sprintf('%d-%d', $clarification->getContest()->getCid(), $clarification->getProblem()->getProbid()); + $formData['subject'] = sprintf('%s|%s', $clarification->getContest()->getExternalid(), $clarification->getProblem()->getExternalid()); } else { - $formData['subject'] = sprintf('%d-%s', $clarification->getContest()->getCid(), $clarification->getCategory()); + $formData['subject'] = sprintf('%s#%s', $clarification->getContest()->getExternalid(), $clarification->getCategory()); } $formData['message'] = "> " . str_replace("\n", "\n> ", Utils::wrapUnquoted($clarification->getBody())) . "\n\n"; @@ -149,7 +149,7 @@ public function viewAction(Request $request, int $clarId): Response } if ($clarification === null) { - throw new NotFoundHttpException(sprintf('Clarification %d not found', $clarId)); + throw new NotFoundHttpException(sprintf('Clarification %s not found', $clarId)); } if (!$team->canViewClarification($clarification)) { @@ -185,7 +185,7 @@ public function viewAction(Request $request, int $clarId): Response } } - #[Route(path: '/clarifications/add', name: 'team_clarification_add')] + #[Route(path: '/clarifications/add', name: 'team_clarification_add', priority: 1)] public function addAction(Request $request): Response { $categories = $this->config->get('clar_categories'); @@ -227,15 +227,15 @@ private function newClarificationHelper( Team $team ): void { $formData = $form->getData(); - // First part will always be the contest ID, as Symfony will validate this. - [, $problemId] = explode('-', $formData['subject']); + // First part will always be the contest external ID, as Symfony will validate this. $problem = null; $category = null; $queue = null; - if (!ctype_digit($problemId)) { - $category = $problemId; + if (str_contains($formData['subject'], '#')) { + [, $category] = explode('#', $formData['subject']); } else { - $problem = $this->em->getRepository(Problem::class)->find($problemId); + [, $problemId] = explode('|', $formData['subject']); + $problem = $this->em->getRepository(Problem::class)->findByExternalId($problemId); $queue = $this->config->get('clar_default_problem_queue'); if ($queue === "") { $queue = null; @@ -255,8 +255,8 @@ private function newClarificationHelper( $this->em->persist($newClarification); $this->em->flush(); - $this->dj->auditlog('clarification', $newClarification->getClarid(), 'added', null, null, - $contest->getCid()); + $this->dj->auditlog('clarification', $newClarification->getExternalid(), 'added', null, null, + $contest->getExternalid()); $this->eventLogService->log('clarification', $newClarification->getClarid(), 'create', $contest->getCid()); $this->addFlash('success', 'Clarification sent to the jury'); diff --git a/webapp/src/Controller/Team/MiscController.php b/webapp/src/Controller/Team/MiscController.php index 5b894163d4..437c6831f0 100644 --- a/webapp/src/Controller/Team/MiscController.php +++ b/webapp/src/Controller/Team/MiscController.php @@ -73,7 +73,7 @@ public function homeAction(Request $request): Response ]; if ($contest) { $scoreboard = $this->scoreboardService - ->getTeamScoreboard($contest, $teamId, false); + ->getTeamScoreboard($contest, $team->getExternalid(), false); $data = array_merge( $data, $this->scoreboardService->getScoreboardTwigData( @@ -151,15 +151,15 @@ public function updatesAction(): JsonResponse return $this->json(['unread_clarifications' => $this->dj->getUnreadClarifications()]); } - #[Route(path: '/change-contest/{contestId<-?\d+>}', name: 'team_change_contest')] - public function changeContestAction(Request $request, RouterInterface $router, int $contestId): Response + #[Route(path: '/change-contest/{contestId}', name: 'team_change_contest')] + public function changeContestAction(Request $request, RouterInterface $router, string $contestId): Response { if ($this->isLocalReferer($router, $request)) { $response = new RedirectResponse($request->headers->get('referer')); } else { $response = $this->redirectToRoute('team_index'); } - return $this->dj->setCookie('domjudge_cid', (string)$contestId, 0, null, '', false, false, + return $this->dj->setCookie('domjudge_cid', $contestId, 0, null, '', false, false, $response); } diff --git a/webapp/src/Controller/Team/ProblemController.php b/webapp/src/Controller/Team/ProblemController.php index 9374ee46b4..9a86d50826 100644 --- a/webapp/src/Controller/Team/ProblemController.php +++ b/webapp/src/Controller/Team/ProblemController.php @@ -51,11 +51,11 @@ public function problemsAction(): Response } - #[Route(path: '/problems/{probId<\d+>}/statement', name: 'team_problem_statement')] - public function problemStatementAction(int $probId): StreamedResponse + #[Route(path: '/problems/{probId}/statement', name: 'team_problem_statement')] + public function problemStatementAction(string $probId): StreamedResponse { return $this->getBinaryFile($probId, function ( - int $probId, + string $probId, Contest $contest, ContestProblem $contestProblem ) { @@ -73,20 +73,20 @@ public function problemStatementAction(int $probId): StreamedResponse /** * @throws NonUniqueResultException */ - #[Route(path: '/{probId<\d+>}/attachment/{attachmentId<\d+>}', name: 'team_problem_attachment')] - public function attachmentAction(int $probId, int $attachmentId): StreamedResponse + #[Route(path: '/{probId}/attachment/{attachmentId<\d+>}', name: 'team_problem_attachment')] + public function attachmentAction(string $probId, int $attachmentId): StreamedResponse { return $this->getBinaryFile($probId, fn( - int $probId, + string $probId, Contest $contest, ContestProblem $contestProblem ) => $this->dj->getAttachmentStreamedResponse($contestProblem, $attachmentId)); } - #[Route(path: '/{probId<\d+>}/samples.zip', name: 'team_problem_sample_zip')] - public function sampleZipAction(int $probId): StreamedResponse + #[Route(path: '/{probId}/samples.zip', name: 'team_problem_sample_zip')] + public function sampleZipAction(string $probId): StreamedResponse { - return $this->getBinaryFile($probId, function (int $probId, Contest $contest, ContestProblem $contestProblem) { + return $this->getBinaryFile($probId, function (string $probId, Contest $contest, ContestProblem $contestProblem) { return $this->dj->getSamplesZipStreamedResponse($contestProblem); }); } @@ -96,17 +96,14 @@ public function sampleZipAction(int $probId): StreamedResponse * * Shared code between testcases, problem text and attachments. */ - protected function getBinaryFile(int $probId, callable $response): StreamedResponse + protected function getBinaryFile(string $probId, callable $response): StreamedResponse { $user = $this->dj->getUser(); $contest = $this->dj->getCurrentContest($user->getTeam()->getTeamid()); if (!$contest || !$contest->getFreezeData()->started()) { throw new NotFoundHttpException(sprintf('Problem p%d not found or not available', $probId)); } - $contestProblem = $this->em->getRepository(ContestProblem::class)->find([ - 'problem' => $probId, - 'contest' => $contest, - ]); + $contestProblem = $this->em->getRepository(ContestProblem::class)->findByProblemAndContest($contest, $probId); if (!$contestProblem) { throw new NotFoundHttpException(sprintf('Problem p%d not found or not available', $probId)); } diff --git a/webapp/src/Controller/Team/ScoreboardController.php b/webapp/src/Controller/Team/ScoreboardController.php index 069e5db60c..ca90b607b0 100644 --- a/webapp/src/Controller/Team/ScoreboardController.php +++ b/webapp/src/Controller/Team/ScoreboardController.php @@ -59,16 +59,15 @@ public function scoreboardAction(Request $request): Response return $this->render('team/scoreboard.html.twig', $data, $response); } - #[Route(path: '/team/{teamId<\d+>}', name: 'team_team')] - public function teamAction(Request $request, int $teamId): Response + #[Route(path: '/team/{teamId}', name: 'team_team')] + public function teamAction(Request $request, string $teamId): Response { if (!$this->config->get('enable_ranking')) { throw new BadRequestHttpException('Scoreboard is not available.'); } - /** @var Team|null $team */ - $team = $this->em->getRepository(Team::class)->find($teamId); - if ($team && $team->getCategory() && !$team->getCategory()->getVisible() && $teamId !== $this->dj->getUser()->getTeamId()) { + $team = $this->em->getRepository(Team::class)->findByExternalId($teamId); + if ($team && $team->getCategory() && !$team->getCategory()->getVisible() && $teamId !== $this->dj->getUser()->getTeam()->getExternalid()) { $team = null; } $showFlags = (bool)$this->config->get('show_flags'); diff --git a/webapp/src/DataFixtures/DefaultData/LanguageFixture.php b/webapp/src/DataFixtures/DefaultData/LanguageFixture.php index 6c82704e40..302d9153a4 100644 --- a/webapp/src/DataFixtures/DefaultData/LanguageFixture.php +++ b/webapp/src/DataFixtures/DefaultData/LanguageFixture.php @@ -23,66 +23,65 @@ public function __construct( public function load(ObjectManager $manager): void { $data = [ - // ID external ID name extensions require entry point allow allow time compile compiler version runner version - // entry point description submit judge factor script command command - ['adb', 'ada', 'Ada', ['adb', 'ads'], false, null, false, true, 1, 'adb', 'gnatmake --version', ''], - ['awk', 'awk', 'AWK', ['awk'], false, null, false, true, 1, 'awk', 'awk --version', 'awk --version'], - ['bash', 'bash', 'Bash shell', ['bash'], false, 'Main file', false, true, 1, 'bash', 'bash --version', 'bash --version'], - ['c', 'c', 'C', ['c'], false, null, true, true, 1, 'c', 'gcc --version', ''], - ['cpp', 'cpp', 'C++', ['cpp', 'cc', 'cxx', 'c++'], false, null, true, true, 1, 'cpp', 'g++ --version', ''], - ['csharp', 'csharp', 'C#', ['csharp', 'cs'], false, null, false, true, 1, 'csharp', 'mcs --version', 'mono --version'], - ['f95', 'f95', 'Fortran', ['f95', 'f90'], false, null, false, true, 1, 'f95', 'gfortran --version', ''], - ['hs', 'haskell', 'Haskell', ['hs', 'lhs'], false, null, false, true, 1, 'hs', 'ghc --version', ''], - ['java', 'java', 'Java', ['java'], false, 'Main class', true, true, 1, 'java_javac_detect', 'javac -version', 'java -version'], - ['js', 'javascript', 'JavaScript', ['js', 'mjs'], false, 'Main file', false, true, 1, 'js', 'nodejs --version', 'nodejs --version'], - ['lua', 'lua', 'Lua', ['lua'], false, null, false, true, 1, 'lua', 'luac -v', 'lua -v'], - ['kt', 'kotlin', 'Kotlin', ['kt'], true, 'Main class', false, true, 1, 'kt', 'kotlinc -version', 'kotlin -version'], - ['pas', 'pascal', 'Pascal', ['pas', 'p'], false, 'Main file', false, true, 1, 'pas', 'fpc -iW', ''], - ['pl', 'pl', 'Perl', ['pl'], false, 'Main file', false, true, 1, 'pl', 'perl -v', 'perl -v'], - ['plg', 'prolog', 'Prolog', ['plg'], false, 'Main file', false, true, 1, 'plg', 'swipl --version', ''], - ['py3', 'python3', 'Python 3', ['py'], false, 'Main file', true, true, 1, 'py3', 'pypy3 --version', 'pypy3 --version'], - ['ocaml', 'ocaml', 'OCaml', ['ml'], false, null, false, true, 1, 'ocaml', 'ocamlopt --version', ''], - ['r', 'r', 'R', ['R'], false, 'Main file', false, true, 1, 'r', 'Rscript --version', 'Rscript --version'], - ['rb', 'ruby', 'Ruby', ['rb'], false, 'Main file', false, true, 1, 'rb', 'ruby --version', 'ruby --version'], - ['rs', 'rust', 'Rust', ['rs'], false, null, false, true, 1, 'rs', 'rustc --version', ''], - ['scala', 'scala', 'Scala', ['scala'], false, null, false, true, 1, 'scala', 'scalac -version', 'scala -version'], - ['sh', 'sh', 'POSIX shell', ['sh'], false, 'Main file', false, true, 1, 'sh', 'md5sum /bin/sh', 'md5sum /bin/sh'], - ['swift', 'swift', 'Swift', ['swift'], false, 'Main file', false, true, 1, 'swift', 'swiftc --version', ''], + // external ID name extensions require entry point allow allow time compile compiler version runner version + // entry point description submit judge factor script command command + ['ada', 'Ada', ['adb', 'ads'], false, null, false, true, 1, 'adb', 'gnatmake --version', ''], + ['awk', 'AWK', ['awk'], false, null, false, true, 1, 'awk', 'awk --version', 'awk --version'], + ['bash', 'Bash shell', ['bash'], false, 'Main file', false, true, 1, 'bash', 'bash --version', 'bash --version'], + ['c', 'C', ['c'], false, null, true, true, 1, 'c', 'gcc --version', ''], + ['cpp', 'C++', ['cpp', 'cc', 'cxx', 'c++'], false, null, true, true, 1, 'cpp', 'g++ --version', ''], + ['csharp', 'C#', ['csharp', 'cs'], false, null, false, true, 1, 'csharp', 'mcs --version', 'mono --version'], + ['f95', 'Fortran', ['f95', 'f90'], false, null, false, true, 1, 'f95', 'gfortran --version', ''], + ['haskell', 'Haskell', ['hs', 'lhs'], false, null, false, true, 1, 'hs', 'ghc --version', ''], + ['java', 'Java', ['java'], false, 'Main class', true, true, 1, 'java_javac_detect', 'javac -version', 'java -version'], + ['javascript', 'JavaScript', ['js', 'mjs'], false, 'Main file', false, true, 1, 'js', 'nodejs --version', 'nodejs --version'], + ['lua', 'Lua', ['lua'], false, null, false, true, 1, 'lua', 'luac -v', 'lua -v'], + ['kotlin', 'Kotlin', ['kt'], true, 'Main class', false, true, 1, 'kt', 'kotlinc -version', 'kotlin -version'], + ['pascal', 'Pascal', ['pas', 'p'], false, 'Main file', false, true, 1, 'pas', 'fpc -iW', ''], + ['pl', 'Perl', ['pl'], false, 'Main file', false, true, 1, 'pl', 'perl -v', 'perl -v'], + ['prolog', 'Prolog', ['plg'], false, 'Main file', false, true, 1, 'plg', 'swipl --version', ''], + ['python3', 'Python 3', ['py'], false, 'Main file', true, true, 1, 'py3', 'pypy3 --version', 'pypy3 --version'], + ['ocaml', 'OCaml', ['ml'], false, null, false, true, 1, 'ocaml', 'ocamlopt --version', ''], + ['r', 'R', ['R'], false, 'Main file', false, true, 1, 'r', 'Rscript --version', 'Rscript --version'], + ['ruby', 'Ruby', ['rb'], false, 'Main file', false, true, 1, 'rb', 'ruby --version', 'ruby --version'], + ['rust', 'Rust', ['rs'], false, null, false, true, 1, 'rs', 'rustc --version', ''], + ['scala', 'Scala', ['scala'], false, null, false, true, 1, 'scala', 'scalac -version', 'scala -version'], + ['sh', 'POSIX shell', ['sh'], false, 'Main file', false, true, 1, 'sh', 'md5sum /bin/sh', 'md5sum /bin/sh'], + ['swift', 'Swift', ['swift'], false, 'Main file', false, true, 1, 'swift', 'swiftc --version', ''], ]; foreach ($data as $item) { // Note: we only create the language if it doesn't exist yet. // If it does, we will not update the data - if (!$manager->getRepository(Language::class)->find($item[0])) { + if (!$manager->getRepository(Language::class)->findOneBy(['externalid' => $item[0]])) { $file = sprintf('%s/files/defaultdata/%s.zip', - $this->sqlDir, $item[9] + $this->sqlDir, $item[8] ); - if (!($executable = $manager->getRepository(Executable::class)->find($item[9]))) { + if (!($executable = $manager->getRepository(Executable::class)->find($item[8]))) { $executable = (new Executable()) - ->setExecid($item[9]) - ->setDescription($item[9]) + ->setExecid($item[8]) + ->setDescription($item[8]) ->setType('compile') ->setImmutableExecutable($this->createImmutableExecutable($file)); $manager->persist($executable); } else { - $this->logger->info('Executable %s already exists, not created', [ $item[9] ]); + $this->logger->info('Executable %s already exists, not created', [ $item[8] ]); } $language = (new Language()) - ->setLangid($item[0]) - ->setExternalid($item[1]) - ->setName($item[2]) - ->setExtensions($item[3]) - ->setRequireEntryPoint($item[4]) - ->setEntryPointDescription($item[5]) - ->setAllowSubmit($item[6]) - ->setAllowJudge($item[7]) - ->setTimeFactor($item[8]) + ->setExternalid($item[0]) + ->setName($item[1]) + ->setExtensions($item[2]) + ->setRequireEntryPoint($item[3]) + ->setEntryPointDescription($item[4]) + ->setAllowSubmit($item[5]) + ->setAllowJudge($item[6]) + ->setTimeFactor($item[7]) ->setCompileExecutable($executable); - if (!empty($item[10])) { - $language->setCompilerVersionCommand($item[10]); + if (!empty($item[9])) { + $language->setCompilerVersionCommand($item[9]); } - if (!empty($item[11])) { - $language->setRunnerVersionCommand($item[11]); + if (!empty($item[10])) { + $language->setRunnerVersionCommand($item[10]); } $manager->persist($language); } else { diff --git a/webapp/src/DataFixtures/Test/BalloonCorrectSubmissionFixture.php b/webapp/src/DataFixtures/Test/BalloonCorrectSubmissionFixture.php index beadaa8f99..29ab0b98da 100644 --- a/webapp/src/DataFixtures/Test/BalloonCorrectSubmissionFixture.php +++ b/webapp/src/DataFixtures/Test/BalloonCorrectSubmissionFixture.php @@ -45,7 +45,7 @@ public function load(ObjectManager $manager): void ->setContest($contest) ->setTeam($team) ->setContestProblem($cp) - ->setLanguage($manager->getRepository(Language::class)->find($submissionItem[1])) + ->setLanguage($manager->getRepository(Language::class)->findByExternalId($submissionItem[1])) ->setSubmittime(Utils::now()-2) ->setEntryPoint($submissionItem[3]); $judging = (new Judging()) diff --git a/webapp/src/DataFixtures/Test/EnableJavaEntrypointFixture.php b/webapp/src/DataFixtures/Test/EnableJavaEntrypointFixture.php index 43875e1a1e..285390ad3f 100644 --- a/webapp/src/DataFixtures/Test/EnableJavaEntrypointFixture.php +++ b/webapp/src/DataFixtures/Test/EnableJavaEntrypointFixture.php @@ -9,7 +9,7 @@ class EnableJavaEntrypointFixture extends AbstractTestDataFixture { public function load(ObjectManager $manager): void { - $java = $manager->getRepository(Language::class)->find('java'); + $java = $manager->getRepository(Language::class)->findByExternalId('java'); $java->setRequireEntryPoint(true); $manager->flush(); } diff --git a/webapp/src/DataFixtures/Test/EnableKotlinFixture.php b/webapp/src/DataFixtures/Test/EnableKotlinFixture.php index 02b0b4dcfd..6d05423696 100644 --- a/webapp/src/DataFixtures/Test/EnableKotlinFixture.php +++ b/webapp/src/DataFixtures/Test/EnableKotlinFixture.php @@ -9,7 +9,7 @@ class EnableKotlinFixture extends AbstractTestDataFixture { public function load(ObjectManager $manager): void { - $kotlin = $manager->getRepository(Language::class)->find('kt'); + $kotlin = $manager->getRepository(Language::class)->findByExternalId('kotlin'); $kotlin->setAllowSubmit(true); $manager->flush(); } diff --git a/webapp/src/DataFixtures/Test/RejudgingFirstToSolveFixture.php b/webapp/src/DataFixtures/Test/RejudgingFirstToSolveFixture.php index a5fb6c9f06..9d6a276891 100644 --- a/webapp/src/DataFixtures/Test/RejudgingFirstToSolveFixture.php +++ b/webapp/src/DataFixtures/Test/RejudgingFirstToSolveFixture.php @@ -32,7 +32,7 @@ public function load(ObjectManager $manager): void [$team1, $contest->getStarttime() + 400, 'correct'], ]; - $language = $manager->getRepository(Language::class)->find('cpp'); + $language = $manager->getRepository(Language::class)->findByExternalId('cpp'); $problem = $contest->getProblems()->filter(fn(ContestProblem $problem) => $problem->getShortname() === 'A')->first(); foreach ($submissionData as $submissionItem) { diff --git a/webapp/src/DataFixtures/Test/RejudgingStatesFixture.php b/webapp/src/DataFixtures/Test/RejudgingStatesFixture.php index fe7b893955..941653a555 100644 --- a/webapp/src/DataFixtures/Test/RejudgingStatesFixture.php +++ b/webapp/src/DataFixtures/Test/RejudgingStatesFixture.php @@ -50,7 +50,7 @@ public function load(ObjectManager $manager): void /** @var Team $team */ $team = $manager->getRepository(Team::class)->findOneBy(['name' => 'Example teamname']); /** @var Language $language */ - $language = $manager->getRepository(Language::class)->find('java'); + $language = $manager->getRepository(Language::class)->findByExternalId('java'); // A rejudging has both judgings todo and finished for ($b = 0; $b<=1; $b++) { for ($x = 0; $x < $rejudgingStage[2+$b]; $x++) { diff --git a/webapp/src/DataFixtures/Test/SampleSubmissionsFixture.php b/webapp/src/DataFixtures/Test/SampleSubmissionsFixture.php index 82b53c23b3..0cf902632e 100644 --- a/webapp/src/DataFixtures/Test/SampleSubmissionsFixture.php +++ b/webapp/src/DataFixtures/Test/SampleSubmissionsFixture.php @@ -31,7 +31,7 @@ public function load(ObjectManager $manager): void ->setContest($contest) ->setTeam($manager->getRepository(Team::class)->findOneBy(['name' => $submissionItem[0]])) ->setContestProblem($problem) - ->setLanguage($manager->getRepository(Language::class)->find($submissionItem[2])) + ->setLanguage($manager->getRepository(Language::class)->findByExternalId($submissionItem[2])) ->setSubmittime(Utils::toEpochFloat($submissionItem[3])) ->setEntryPoint($submissionItem[4]); $judging = (new Judging()) diff --git a/webapp/src/DataFixtures/Test/SampleSubmissionsInBucketsFixture.php b/webapp/src/DataFixtures/Test/SampleSubmissionsInBucketsFixture.php index 54314f8dad..0c7a354f5f 100644 --- a/webapp/src/DataFixtures/Test/SampleSubmissionsInBucketsFixture.php +++ b/webapp/src/DataFixtures/Test/SampleSubmissionsInBucketsFixture.php @@ -43,7 +43,7 @@ public function load(ObjectManager $manager): void ->setContest($demoContest) ->setTeam($manager->getRepository(Team::class)->findOneBy(['name' => 'Example teamname'])) ->setContestProblem($problem) - ->setLanguage($manager->getRepository(Language::class)->find('cpp')) + ->setLanguage($manager->getRepository(Language::class)->findByExternalId('cpp')) ->setSubmittime(Utils::toEpochFloat($time)); $judging = (new Judging()) ->setContest($demoContest) diff --git a/webapp/src/DataFixtures/Test/SampleSubmissionsMultipleTriesFixture.php b/webapp/src/DataFixtures/Test/SampleSubmissionsMultipleTriesFixture.php index 987cbe70e5..162aec616d 100644 --- a/webapp/src/DataFixtures/Test/SampleSubmissionsMultipleTriesFixture.php +++ b/webapp/src/DataFixtures/Test/SampleSubmissionsMultipleTriesFixture.php @@ -17,10 +17,10 @@ public function load(ObjectManager $manager): void { $submissionData = [ // team name, problem shortname, language, submittime, entry point, result - ['Example teamname', 'C', 'cpp', '2021-02-01 01:00:56', null, 'timelimit'], - ['Example teamname', 'C', 'c', '2021-02-01 03:15:56', null, 'run-error'], - ['Example teamname', 'C', 'java', '2021-02-01 18:00:00', 'Main', 'wrong-answer'], - ['Example teamname', 'C', 'py3', '2021-02-01 18:00:34', 'main', 'compiler-error'], + ['Example teamname', 'C', 'cpp', '2021-02-01 01:00:56', null, 'timelimit'], + ['Example teamname', 'C', 'c', '2021-02-01 03:15:56', null, 'run-error'], + ['Example teamname', 'C', 'java', '2021-02-01 18:00:00', 'Main', 'wrong-answer'], + ['Example teamname', 'C', 'python3', '2021-02-01 18:00:34', 'main', 'compiler-error'], ]; /** @var Contest $contest */ @@ -33,7 +33,7 @@ public function load(ObjectManager $manager): void ->setContest($contest) ->setTeam($manager->getRepository(Team::class)->findOneBy(['name' => $submissionItem[0]])) ->setContestProblem($problem) - ->setLanguage($manager->getRepository(Language::class)->find($submissionItem[2])) + ->setLanguage($manager->getRepository(Language::class)->findByExternalId($submissionItem[2])) ->setSubmittime(Utils::toEpochFloat($submissionItem[3])) ->setEntryPoint($submissionItem[4]); $judging = (new Judging()) diff --git a/webapp/src/DataFixtures/Test/SampleSubmissionsThreeTriesCorrectFixture.php b/webapp/src/DataFixtures/Test/SampleSubmissionsThreeTriesCorrectFixture.php index 326006a327..26046c489e 100644 --- a/webapp/src/DataFixtures/Test/SampleSubmissionsThreeTriesCorrectFixture.php +++ b/webapp/src/DataFixtures/Test/SampleSubmissionsThreeTriesCorrectFixture.php @@ -32,7 +32,7 @@ public function load(ObjectManager $manager): void ->setContest($contest) ->setTeam($manager->getRepository(Team::class)->findOneBy(['name' => $submissionItem[0]])) ->setContestProblem($problem) - ->setLanguage($manager->getRepository(Language::class)->find($submissionItem[2])) + ->setLanguage($manager->getRepository(Language::class)->findByExternalId($submissionItem[2])) ->setSubmittime(Utils::toEpochFloat($submissionItem[3])) ->setEntryPoint($submissionItem[4]); $judging = (new Judging()) diff --git a/webapp/src/DataFixtures/Test/SampleSubmissionsThreeTriesCorrectSameLanguageFixture.php b/webapp/src/DataFixtures/Test/SampleSubmissionsThreeTriesCorrectSameLanguageFixture.php index 4615861c42..5bc7c0dc5e 100644 --- a/webapp/src/DataFixtures/Test/SampleSubmissionsThreeTriesCorrectSameLanguageFixture.php +++ b/webapp/src/DataFixtures/Test/SampleSubmissionsThreeTriesCorrectSameLanguageFixture.php @@ -32,7 +32,7 @@ public function load(ObjectManager $manager): void ->setContest($contest) ->setTeam($manager->getRepository(Team::class)->findOneBy(['name' => $submissionItem[0]])) ->setContestProblem($problem) - ->setLanguage($manager->getRepository(Language::class)->find($submissionItem[2])) + ->setLanguage($manager->getRepository(Language::class)->findByExternalId($submissionItem[2])) ->setSubmittime(Utils::toEpochFloat($submissionItem[3])) ->setEntryPoint($submissionItem[4]); $judging = (new Judging()) diff --git a/webapp/src/DataTransferObject/Balloon.php b/webapp/src/DataTransferObject/Balloon.php index 8d8721eb7c..914e858e7e 100644 --- a/webapp/src/DataTransferObject/Balloon.php +++ b/webapp/src/DataTransferObject/Balloon.php @@ -16,12 +16,12 @@ public function __construct( public readonly string $problem, public readonly ContestProblem $contestproblem, public readonly string $team, - public readonly int $teamid, + public readonly string $teamid, public readonly ?string $location, public readonly ?string $affiliation, - public readonly ?int $affiliationid, + public readonly ?string $affiliationid, public readonly ?string $category, - public readonly ?int $categoryid, + public readonly ?string $categoryid, #[Serializer\Type('array')] public readonly array $total, public readonly bool $done, diff --git a/webapp/src/DataTransferObject/SubmissionRestriction.php b/webapp/src/DataTransferObject/SubmissionRestriction.php index c23f86bf4f..268348fd99 100644 --- a/webapp/src/DataTransferObject/SubmissionRestriction.php +++ b/webapp/src/DataTransferObject/SubmissionRestriction.php @@ -2,45 +2,51 @@ namespace App\DataTransferObject; +use App\DataTransferObject\Scoreboard\Problem; +use App\Entity\Language; +use App\Entity\Team; +use App\Entity\TeamAffiliation; +use App\Entity\TeamCategory; + class SubmissionRestriction { /** - * @param int|null $rejudgingId ID of a rejudging to filter on - * @param bool|null $verified If true, only return verified submissions - * If false, only return unverified or unjudged submissions - * @param bool|null $judged If true, only return judged submissions - * If false, only return unjudged submissions - * @param bool|null $judging If true, only return submissions currently being judged - * If false, only return submssions which are already judged or still - * need to be judged - * @param bool|null $rejudgingDifference If true, only return judgings that differ from their - * original result in final verdict. Vice versa if false - * @param int|null $teamId ID of a team to filter on - * @param list|null $teamIds ID's of teams to filter on - * @param int|null $categoryId ID of a category to filter on - * @param list|null $categoryIds ID's of categories to filter on - * @param int|null $affiliationId ID of an affiliation to filter on - * @param list|null $affiliationIds ID's of affiliations to filter on - * @param int|null $problemId ID of a problem to filter on - * @param list|null $problemIds ID's of problems to filter on - * @param string|null $languageId ID of a language to filter on - * @param list|null $languageIds ID's of languages to filter on - * @param string|null $judgehost Hostname of a judgehost to filter on - * @param string|null $oldResult Result of old judging to filter on - * @param string|null $result Result of current judging to filter on - * @param list|null $results Results of current judging to filter on - * @param int|null $userId Filter on specific user - * @param bool|null $visible If true, only return submissions from visible teams - * @param bool|null $externalDifference If true, only return results with a difference with an - * external system - * If false, only return results without a difference with an - * external system - * @param string|null $externalResult Result in the external system - * @param bool|null $externallyJudged If true, only return externally judged submissions - * If false, only return externally unjudged submissions - * @param bool|null $externallyVerified If true, only return verified submissions - * If false, only return unverified or unjudged submissions - * @param bool|null $withExternalId If true, only return submissions with an external ID. + * @param int|null $rejudgingId ID of a rejudging to filter on + * @param bool|null $verified If true, only return verified submissions + * If false, only return unverified or unjudged submissions + * @param bool|null $judged If true, only return judged submissions + * If false, only return unjudged submissions + * @param bool|null $judging If true, only return submissions currently being judged + * If false, only return submssions which are already judged or still + * need to be judged + * @param bool|null $rejudgingDifference If true, only return judgings that differ from their + * original result in final verdict. Vice versa if false + * @param int|null $teamId ID of a team to filter on + * @param list|list|null $teamIds ID's of teams to filter on + * @param int|null $categoryId ID of a category to filter on + * @param list|list|null $categoryIds ID's of categories to filter on + * @param int|null $affiliationId ID of an affiliation to filter on + * @param list|list|null $affiliationIds ID's of affiliations to filter on + * @param int|null $problemId ID of a problem to filter on + * @param list|list|null $problemIds ID's of problems to filter on + * @param int|null $languageId ID of a language to filter on + * @param list|list|null $languageIds ID's of languages to filter on + * @param string|null $judgehost Hostname of a judgehost to filter on + * @param string|null $oldResult Result of old judging to filter on + * @param string|null $result Result of current judging to filter on + * @param list|null $results Results of current judging to filter on + * @param int|null $userId Filter on specific user + * @param bool|null $visible If true, only return submissions from visible teams + * @param bool|null $externalDifference If true, only return results with a difference with an + * external system + * If false, only return results without a difference with an + * external system + * @param string|null $externalResult Result in the external system + * @param bool|null $externallyJudged If true, only return externally judged submissions + * If false, only return externally unjudged submissions + * @param bool|null $externallyVerified If true, only return verified submissions + * If false, only return unverified or unjudged submissions + * @param bool|null $withExternalId If true, only return submissions with an external ID. */ public function __construct( public ?int $rejudgingId = null, @@ -56,7 +62,7 @@ public function __construct( public ?array $affiliationIds = [], public ?int $problemId = null, public ?array $problemIds = [], - public ?string $languageId = null, + public ?int $languageId = null, public ?array $languageIds = [], public ?string $judgehost = null, public ?string $oldResult = null, diff --git a/webapp/src/Entity/AuditLog.php b/webapp/src/Entity/AuditLog.php index 416620d9bf..922e556632 100644 --- a/webapp/src/Entity/AuditLog.php +++ b/webapp/src/Entity/AuditLog.php @@ -33,9 +33,9 @@ class AuditLog #[ORM\Column( nullable: true, - options: ['comment' => 'Contest ID associated to this entry', 'unsigned' => true] + options: ['comment' => 'External contest ID associated to this entry', 'unsigned' => true] )] - private ?int $cid = null; + private ?string $cid = null; #[ORM\Column( nullable: true, @@ -52,7 +52,7 @@ class AuditLog #[ORM\Column( length: 64, nullable: true, - options: ['comment' => 'Identifier in reference table'] + options: ['comment' => '(External) identifier in reference table'] )] private ?string $dataid = null; @@ -81,13 +81,13 @@ public function getLogtime(): string|float return $this->logtime; } - public function setCid(?int $cid): AuditLog + public function setCid(?string $cid): AuditLog { $this->cid = $cid; return $this; } - public function getCid(): ?int + public function getCid(): ?string { return $this->cid; } diff --git a/webapp/src/Entity/Clarification.php b/webapp/src/Entity/Clarification.php index 05c4fb0628..23b793cd68 100644 --- a/webapp/src/Entity/Clarification.php +++ b/webapp/src/Entity/Clarification.php @@ -2,6 +2,7 @@ namespace App\Entity; use App\Controller\API\AbstractRestController as ARC; +use App\Repository\ClarificationRepository; use App\Utils\Utils; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -13,7 +14,7 @@ /** * Clarification requests by teams and responses by the jury. */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: ClarificationRepository::class)] #[ORM\Table(options: [ 'collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', @@ -39,8 +40,7 @@ class Clarification extends BaseApiEntity implements #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(options: ['comment' => 'Clarification ID', 'unsigned' => true])] - #[Serializer\SerializedName('clarid')] - #[Serializer\Groups([ARC::GROUP_RESTRICTED_NONSTRICT])] + #[Serializer\Exclude] protected int $clarid; #[ORM\Column( diff --git a/webapp/src/Entity/Contest.php b/webapp/src/Entity/Contest.php index 75dd6c7b25..546842f123 100644 --- a/webapp/src/Entity/Contest.php +++ b/webapp/src/Entity/Contest.php @@ -6,6 +6,7 @@ use App\DataTransferObject\ContestState; use App\DataTransferObject\FileWithName; use App\DataTransferObject\ImageFile; +use App\Repository\ContestRepository; use App\Utils\FreezeData; use App\Utils\Utils; use App\Validator\Constraints\Identifier; @@ -28,7 +29,7 @@ /** * Contests that will be run with this install. */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: ContestRepository::class)] #[ORM\Table(options: [ 'collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', @@ -56,7 +57,7 @@ class Contest extends BaseApiEntity implements #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(options: ['comment' => 'Contest ID', 'unsigned' => true])] - #[Serializer\Groups([ARC::GROUP_NONSTRICT])] + #[Serializer\Exclude] protected ?int $cid = null; #[ORM\Column( diff --git a/webapp/src/Entity/ContestProblem.php b/webapp/src/Entity/ContestProblem.php index 21d02e6bfe..0b6554dc2d 100644 --- a/webapp/src/Entity/ContestProblem.php +++ b/webapp/src/Entity/ContestProblem.php @@ -2,6 +2,7 @@ namespace App\Entity; use App\Controller\API\AbstractRestController as ARC; +use App\Repository\ContestProblemRepository; use App\Service\DOMJudgeService as DJS; use App\Utils\Utils; use Doctrine\Common\Collections\ArrayCollection; @@ -14,7 +15,7 @@ /** * Many-to-Many mapping of contests and problems. */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: ContestProblemRepository::class)] #[ORM\Table( name: 'contestproblem', options: [ @@ -26,11 +27,6 @@ #[ORM\Index(columns: ['cid'], name: 'cid')] #[ORM\Index(columns: ['probid'], name: 'probid')] #[ORM\UniqueConstraint(name: 'shortname', columns: ['cid', 'shortname'], options: ['lengths' => [null, 190]])] -#[Serializer\VirtualProperty( - name: 'probid', - exp: 'object.getProblem().getProbid()', - options: [new Serializer\Groups([ARC::GROUP_NONSTRICT])] -)] #[Serializer\VirtualProperty( name: 'short_name', diff --git a/webapp/src/Entity/JudgeTask.php b/webapp/src/Entity/JudgeTask.php index 513efebee1..07168eee70 100644 --- a/webapp/src/Entity/JudgeTask.php +++ b/webapp/src/Entity/JudgeTask.php @@ -79,9 +79,9 @@ class JudgeTask #[Serializer\VirtualProperty] #[Serializer\SerializedName('submitid')] #[Serializer\Type('string')] - public function getSubmitid(): ?int + public function getSubmitid(): ?string { - return $this->submission?->getSubmitid(); + return $this->submission?->getExternalid(); } diff --git a/webapp/src/Entity/Language.php b/webapp/src/Entity/Language.php index ecaed5e89a..38e0bf1d30 100644 --- a/webapp/src/Entity/Language.php +++ b/webapp/src/Entity/Language.php @@ -3,7 +3,7 @@ use App\Controller\API\AbstractRestController as ARC; use App\DataTransferObject\Command; -use App\Validator\Constraints\Identifier; +use App\Repository\LanguageRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -15,7 +15,7 @@ /** * Programming languages in which teams can submit solutions. */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: LanguageRepository::class)] #[ORM\Table(options: [ 'collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', @@ -30,12 +30,10 @@ class Language extends BaseApiEntity implements ExternalIdFromInternalIdInterface { #[ORM\Id] - #[ORM\Column(length: 32, options: ['comment' => 'Language ID (string)'])] - #[Assert\NotBlank] - #[Assert\NotEqualTo('add')] - #[Identifier] + #[ORM\GeneratedValue] + #[ORM\Column(options: ['comment' => 'Language ID', 'unsigned' => true])] #[Serializer\Exclude] - protected ?string $langid = null; + protected ?int $langid = null; #[ORM\Column(nullable: true, options: ['comment' => 'Language ID to expose in the REST API'])] #[Serializer\SerializedName('id')] @@ -264,13 +262,13 @@ public function getRunnerData(): Command return $ret; } - public function setLangid(string $langid): Language + public function setLangid(int $langid): Language { $this->langid = $langid; return $this; } - public function getLangid(): ?string + public function getLangid(): ?int { return $this->langid; } @@ -420,16 +418,11 @@ public function getSubmissions(): Collection public function getEditorLanguage(): string { - return match ($this->getLangid()) { + return match ($this->getExternalid()) { 'bash' => 'shell', - 'cxx' => 'cpp', - 'kt' => 'kotlin', - 'pas' => 'pascal', 'pl' => 'perl', - 'py2', 'py3' => 'python', - 'rb' => 'ruby', - 'rs' => 'rust', - default => $this->getLangid(), + 'python2', 'python3' => 'python', + default => $this->getExternalid(), }; } diff --git a/webapp/src/Entity/Problem.php b/webapp/src/Entity/Problem.php index a715cd9fdc..1dd8889607 100644 --- a/webapp/src/Entity/Problem.php +++ b/webapp/src/Entity/Problem.php @@ -3,6 +3,7 @@ namespace App\Entity; use App\DataTransferObject\FileWithName; +use App\Repository\ProblemRepository; use App\Utils\Utils; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -18,7 +19,7 @@ /** * Stores testcases per problem. */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: ProblemRepository::class)] #[ORM\Table(options: [ 'collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', diff --git a/webapp/src/Entity/Submission.php b/webapp/src/Entity/Submission.php index 0932fff1bd..1fcee2e8bd 100644 --- a/webapp/src/Entity/Submission.php +++ b/webapp/src/Entity/Submission.php @@ -4,6 +4,7 @@ use App\Controller\API\AbstractRestController as ARC; use App\DataTransferObject\FileWithName; +use App\Repository\SubmissionRepository; use App\Utils\Utils; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -16,7 +17,7 @@ /** * All incoming submissions. */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: SubmissionRepository::class)] #[ORM\Table(options: [ 'collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', @@ -44,8 +45,7 @@ class Submission extends BaseApiEntity implements #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(options: ['comment' => 'Submission ID', 'unsigned' => true])] - #[Serializer\SerializedName('submitid')] - #[Serializer\Groups([ARC::GROUP_NONSTRICT])] + #[Serializer\Exclude] protected int $submitid; #[ORM\Column( diff --git a/webapp/src/Entity/Team.php b/webapp/src/Entity/Team.php index fd9d4e4ef2..b6f73666b8 100644 --- a/webapp/src/Entity/Team.php +++ b/webapp/src/Entity/Team.php @@ -5,6 +5,7 @@ use App\Controller\API\AbstractRestController as ARC; use App\DataTransferObject\ImageFile; use App\DataTransferObject\TeamLocation; +use App\Repository\TeamRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -18,7 +19,7 @@ /** * All teams participating in the contest. */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: TeamRepository::class)] #[ORM\Table(options: ['collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4'])] #[ORM\Index(columns: ['affilid'], name: 'affilid')] #[ORM\Index(columns: ['categoryid'], name: 'categoryid')] @@ -38,8 +39,7 @@ class Team extends BaseApiEntity implements #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(options: ['comment' => 'Team ID', 'unsigned' => true])] - #[Serializer\SerializedName('teamid')] - #[Serializer\Groups([ARC::GROUP_NONSTRICT])] + #[Serializer\Exclude] protected ?int $teamid = null; #[ORM\Column( diff --git a/webapp/src/Entity/TeamAffiliation.php b/webapp/src/Entity/TeamAffiliation.php index d77958d864..ba0ee2d1d7 100644 --- a/webapp/src/Entity/TeamAffiliation.php +++ b/webapp/src/Entity/TeamAffiliation.php @@ -3,6 +3,7 @@ use App\Controller\API\AbstractRestController as ARC; use App\DataTransferObject\ImageFile; +use App\Repository\TeamAffiliationRepository; use App\Validator\Constraints\Country; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -16,7 +17,7 @@ /** * Affilitations for teams (e.g.: university, company). */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: TeamAffiliationRepository::class)] #[ORM\Table(options: [ 'collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', @@ -42,8 +43,7 @@ class TeamAffiliation extends BaseApiEntity implements #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(options: ['comment' => 'Team affiliation ID', 'unsigned' => true])] - #[Serializer\SerializedName('affilid')] - #[Serializer\Groups([ARC::GROUP_NONSTRICT])] + #[Serializer\Exclude] protected ?int $affilid = null; #[ORM\Column( diff --git a/webapp/src/Entity/TeamCategory.php b/webapp/src/Entity/TeamCategory.php index 0ae161ff87..9a5829d150 100644 --- a/webapp/src/Entity/TeamCategory.php +++ b/webapp/src/Entity/TeamCategory.php @@ -2,6 +2,7 @@ namespace App\Entity; use App\Controller\API\AbstractRestController as ARC; +use App\Repository\TeamCategoryRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -14,7 +15,7 @@ /** * Categories for teams (e.g.: participants, observers, ...). */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: TeamCategoryRepository::class)] #[ORM\Table(options: [ 'collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', @@ -37,8 +38,7 @@ class TeamCategory extends BaseApiEntity implements #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(options: ['comment' => 'Team category ID', 'unsigned' => true])] - #[Serializer\SerializedName('categoryid')] - #[Serializer\Groups([ARC::GROUP_NONSTRICT])] + #[Serializer\Exclude] protected ?int $categoryid = null; #[ORM\Column( diff --git a/webapp/src/Entity/User.php b/webapp/src/Entity/User.php index e829805659..01910a8f2c 100644 --- a/webapp/src/Entity/User.php +++ b/webapp/src/Entity/User.php @@ -2,6 +2,7 @@ namespace App\Entity; use App\Controller\API\AbstractRestController as ARC; +use App\Repository\UserRepository; use App\Utils\Utils; use DateTime; use Doctrine\Common\Collections\ArrayCollection; @@ -18,7 +19,7 @@ /** * Users that have access to DOMjudge. */ -#[ORM\Entity] +#[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\Table(options: [ 'collation' => 'utf8mb4_unicode_ci', 'charset' => 'utf8mb4', @@ -38,8 +39,7 @@ class User extends BaseApiEntity implements #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(options: ['comment' => 'User ID', 'unsigned' => true])] - #[Serializer\SerializedName('userid')] - #[Serializer\Groups([ARC::GROUP_NONSTRICT])] + #[Serializer\Exclude] private ?int $userid = null; #[ORM\Column( @@ -214,7 +214,7 @@ public function getName(): ?string public function getShortDescription(): string { - return $this->getName(); + return $this->getName() ?: $this->getUsername(); } public function setEmail(?string $email): User diff --git a/webapp/src/Form/Type/AbstractExternalIdEntityType.php b/webapp/src/Form/Type/AbstractExternalIdEntityType.php index c79a8fbb92..2f934376f1 100644 --- a/webapp/src/Form/Type/AbstractExternalIdEntityType.php +++ b/webapp/src/Form/Type/AbstractExternalIdEntityType.php @@ -24,7 +24,7 @@ public function __construct(protected readonly EventLogService $eventLogService) protected function addExternalIdField(FormBuilderInterface $builder, string $entity): void { $builder->add('externalid', TextType::class, [ - 'label' => 'External ID', + 'label' => 'ID', 'help' => 'Leave empty to generate automatically.', 'required' => false, 'empty_data' => '', diff --git a/webapp/src/Form/Type/ContestExportType.php b/webapp/src/Form/Type/ContestExportType.php index 82fb3afbdb..fbc180fa79 100644 --- a/webapp/src/Form/Type/ContestExportType.php +++ b/webapp/src/Form/Type/ContestExportType.php @@ -14,8 +14,9 @@ public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('contest', EntityType::class, [ 'class' => Contest::class, + 'choice_value' => 'externalid', 'choice_label' => fn(Contest $contest) => sprintf( - 'c%d: %s - %s', $contest->getCid(), $contest->getShortname(), $contest->getName() + '%s: %s - %s', $contest->getExternalid(), $contest->getShortname(), $contest->getName() ), ]); $builder->add('export', SubmitType::class, ['icon' => 'fa-download']); diff --git a/webapp/src/Form/Type/ContestProblemType.php b/webapp/src/Form/Type/ContestProblemType.php index e3c89cdd1d..a191ef2f6e 100644 --- a/webapp/src/Form/Type/ContestProblemType.php +++ b/webapp/src/Form/Type/ContestProblemType.php @@ -21,7 +21,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder->add('problem', EntityType::class, [ 'class' => Problem::class, 'required' => true, - 'choice_label' => fn(Problem $problem) => sprintf('p%d - %s', $problem->getProbid(), $problem->getName()), + 'choice_label' => fn(Problem $problem) => sprintf('%s - %s', $problem->getExternalid(), $problem->getName()), + 'choice_value' => 'externalid', 'query_builder' => fn(EntityRepository $er) => $er ->createQueryBuilder('p') ->orderBy('p.probid'), diff --git a/webapp/src/Form/Type/ContestType.php b/webapp/src/Form/Type/ContestType.php index bcf1769bcc..419f4be719 100644 --- a/webapp/src/Form/Type/ContestType.php +++ b/webapp/src/Form/Type/ContestType.php @@ -124,6 +124,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => TeamCategory::class, 'multiple' => true, 'choice_label' => fn(TeamCategory $category) => $category->getName(), + 'choice_value' => 'externalid', 'help' => 'List of team categories that will receive medals for this contest.', ]); foreach (['gold', 'silver', 'bronze'] as $medalType) { @@ -158,7 +159,8 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'class' => Team::class, 'multiple' => true, - 'choice_label' => fn(Team $team) => sprintf('%s (t%d)', $team->getEffectiveName(), $team->getTeamid()), + 'choice_label' => fn(Team $team) => sprintf('%s (%s)', $team->getEffectiveName(), $team->getExternalid()), + 'choice_value' => 'externalid', 'help' => 'List of teams participating in the contest, in case it is not open to all teams.', ]); $builder->add('teamCategories', EntityType::class, [ @@ -166,6 +168,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => TeamCategory::class, 'multiple' => true, 'choice_label' => fn(TeamCategory $category) => $category->getName(), + 'choice_value' => 'externalid', 'help' => 'List of team categories participating in the contest, in case it is not open to all teams.', ]); $builder->add('enabled', ChoiceType::class, [ @@ -215,6 +218,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => Language::class, 'multiple' => true, 'choice_label' => fn(Language $language) => sprintf('%s (%s)', $language->getName(), $language->getExternalid()), + 'choice_value' => 'externalid', 'help' => 'List of languages that can be used in this contest. Leave empty to allow all languages that are enabled globally.', ]); diff --git a/webapp/src/Form/Type/JuryClarificationType.php b/webapp/src/Form/Type/JuryClarificationType.php index 70b8a69689..6fe3a19633 100644 --- a/webapp/src/Form/Type/JuryClarificationType.php +++ b/webapp/src/Form/Type/JuryClarificationType.php @@ -22,7 +22,7 @@ class JuryClarificationType extends AbstractType { public const RECIPIENT_MUST_SELECT = 'domjudge-must-select'; - /** @var int The clarification entity id if the entity exists in the database */ + /** @var string The clarification entity id if the entity exists in the database */ private $clarid; public function __construct( @@ -41,12 +41,12 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $limitToTeam = $options['limit_to_team'] ?? null; if ($limitToTeam) { - $recipientOptions[$this->getTeamLabel($limitToTeam)] = $limitToTeam->getTeamid(); + $recipientOptions[$this->getTeamLabel($limitToTeam)] = $limitToTeam->getExternalid(); } else { /** @var Team|null $limitToTeam */ $teams = $this->dj->getTeamsForContest($this->dj->getCurrentContest()); foreach ($teams as $team) { - $recipientOptions[$this->getTeamLabel($team)] = $team->getTeamid(); + $recipientOptions[$this->getTeamLabel($team)] = $team->getExternalid(); } } @@ -57,7 +57,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $contest = $this->dj->getCurrentContest(); $hasCurrentContest = $contest !== null; if ($hasCurrentContest) { - $contests = [$contest->getCid() => $contest]; + $contests = [$contest->getExternalid() => $contest]; } else { $contests = $this->dj->getCurrentContests(); } @@ -82,14 +82,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void }; } foreach ($categories as $name => $desc) { - $subjectOptions["$namePrefix $desc"] = "$cid-$name"; + $subjectOptions["$namePrefix $desc"] = "$cid#$name"; } foreach ($contestproblems as $cp) { - if ($cp->getCid() != $cid) { + if ($cp->getContest()->getExternalid() != $cid) { continue; } - $subjectOptions[$namePrefix . $cp->getShortname() . ': ' . $cp->getProblem()->getName()] = "$cid-" . $cp->getProbid(); + $subjectOptions[$namePrefix . $cp->getShortname() . ': ' . $cp->getProblem()->getName()] = "$cid|" . $cp->getProblem()->getExternalid(); } } @@ -141,7 +141,7 @@ public function checkJuryMember(mixed $value, ExecutionContextInterface $context $juryMember = $this->em->createQueryBuilder() ->select('clar.jury_member') ->from(Clarification::class, 'clar') - ->where('clar.clarid = :clarid') + ->where('clar.externalid = :clarid') ->setParameter('clarid', $this->clarid) ->getQuery() ->getSingleResult()['jury_member']; diff --git a/webapp/src/Form/Type/LanguageType.php b/webapp/src/Form/Type/LanguageType.php index 2f0c70864f..55b733dd65 100644 --- a/webapp/src/Form/Type/LanguageType.php +++ b/webapp/src/Form/Type/LanguageType.php @@ -22,9 +22,6 @@ class LanguageType extends AbstractExternalIdEntityType public function buildForm(FormBuilderInterface $builder, array $options): void { $this->addExternalIdField($builder, Language::class); - $builder->add('langid', TextType::class, [ - 'label' => 'Language ID', - ]); $builder->add('name', TextType::class, [ 'empty_data' => '' ]); diff --git a/webapp/src/Form/Type/PrintType.php b/webapp/src/Form/Type/PrintType.php index a9ea10b54b..d8b852f8e2 100644 --- a/webapp/src/Form/Type/PrintType.php +++ b/webapp/src/Form/Type/PrintType.php @@ -21,7 +21,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $languages = $this->em->getRepository(Language::class)->findAll(); $languageChoices = []; foreach ($languages as $language) { - $languageChoices[$language->getName()] = $language->getLangid(); + $languageChoices[$language->getName()] = $language->getExternalid(); } ksort($languageChoices, SORT_NATURAL | SORT_FLAG_CASE); $languageChoices = ['plain text' => ''] + $languageChoices; diff --git a/webapp/src/Form/Type/ProblemType.php b/webapp/src/Form/Type/ProblemType.php index 21f7959c79..ec39744da5 100644 --- a/webapp/src/Form/Type/ProblemType.php +++ b/webapp/src/Form/Type/ProblemType.php @@ -99,6 +99,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'class' => Language::class, 'multiple' => true, + 'choice_value' => 'externalid', 'choice_label' => fn(Language $language) => sprintf('%s (%s)', $language->getName(), $language->getExternalid()), 'help' => 'List of languages that can be used for this problem. Leave empty to allow all languages that are enabled for this contest.', ]); diff --git a/webapp/src/Form/Type/ProblemUploadType.php b/webapp/src/Form/Type/ProblemUploadType.php index d704fea726..18d82ed1d1 100644 --- a/webapp/src/Form/Type/ProblemUploadType.php +++ b/webapp/src/Form/Type/ProblemUploadType.php @@ -17,8 +17,9 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => Contest::class, 'required' => false, 'placeholder' => 'Do not add / update contest data', + 'choice_value' => 'externalid', 'choice_label' => fn(Contest $contest) => sprintf( - 'c%d: %s - %s', $contest->getCid(), $contest->getShortname(), $contest->getName() + '%s: %s - %s', $contest->getExternalid(), $contest->getShortname(), $contest->getName() ), ]); $builder->add('archive', FileType::class, [ diff --git a/webapp/src/Form/Type/ProblemsImportType.php b/webapp/src/Form/Type/ProblemsImportType.php index 48aa01bc96..baedb1c147 100644 --- a/webapp/src/Form/Type/ProblemsImportType.php +++ b/webapp/src/Form/Type/ProblemsImportType.php @@ -16,8 +16,9 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder->add('contest', EntityType::class, [ 'class' => Contest::class, 'required' => true, + 'choice_value' => 'externalid', 'choice_label' => fn(Contest $contest) => sprintf( - 'c%d: %s - %s', $contest->getCid(), $contest->getShortname(), $contest->getName() + '%s: %s - %s', $contest->getExternalid(), $contest->getShortname(), $contest->getName() ), ]); $builder->add('file', FileType::class, [ diff --git a/webapp/src/Form/Type/RejudgingType.php b/webapp/src/Form/Type/RejudgingType.php index 5afcda07c6..96f453f0d8 100644 --- a/webapp/src/Form/Type/RejudgingType.php +++ b/webapp/src/Form/Type/RejudgingType.php @@ -53,6 +53,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'multiple' => true, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'query_builder' => fn(EntityRepository $er) => $er ->createQueryBuilder('c') ->where('c.enabled = 1') @@ -64,6 +65,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => Problem::class, 'required' => false, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'choices' => [], ]); $builder->add('languages', EntityType::class, [ @@ -72,6 +74,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => Language::class, 'required' => false, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'query_builder' => fn(EntityRepository $er) => $er ->createQueryBuilder('l') ->where('l.allowSubmit = 1') @@ -83,6 +86,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => Team::class, 'required' => false, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'choices' => [], ]); $builder->add('users', EntityType::class, [ @@ -91,6 +95,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, 'multiple' => true, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'query_builder' => fn(EntityRepository $er) => $er ->createQueryBuilder('u') ->where('u.enabled = 1') diff --git a/webapp/src/Form/Type/SubmissionsFilterType.php b/webapp/src/Form/Type/SubmissionsFilterType.php index f962bb7501..ebb4ee439a 100644 --- a/webapp/src/Form/Type/SubmissionsFilterType.php +++ b/webapp/src/Form/Type/SubmissionsFilterType.php @@ -45,6 +45,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void "class" => Problem::class, "required" => false, "choice_label" => "name", + "choice_value" => "externalid", "choices" => $problems, "attr" => ["data-filter-field" => "problem-id"], ]); @@ -54,6 +55,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void "class" => Language::class, "required" => false, "choice_label" => "name", + "choice_value" => "externalid", "query_builder" => fn(EntityRepository $er) => $er ->createQueryBuilder("l") ->where("l.allowSubmit = 1") @@ -66,6 +68,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void "class" => TeamCategory::class, "required" => false, "choice_label" => "name", + "choice_value" => "externalid", "query_builder" => fn(EntityRepository $er) => $er ->createQueryBuilder("tc") ->orderBy("tc.name"), @@ -77,6 +80,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void "class" => TeamAffiliation::class, "required" => false, "choice_label" => "name", + "choice_value" => "externalid", "query_builder" => fn(EntityRepository $er) => $er ->createQueryBuilder("ta") ->orderBy("ta.name"), @@ -114,6 +118,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void "class" => Team::class, "required" => false, "choice_label" => "name", + "choice_value" => "externalid", "choices" => $teams, "attr" => ["data-filter-field" => "team-id"], ]); diff --git a/webapp/src/Form/Type/SubmitProblemType.php b/webapp/src/Form/Type/SubmitProblemType.php index 997bb21555..9532866b10 100644 --- a/webapp/src/Form/Type/SubmitProblemType.php +++ b/webapp/src/Form/Type/SubmitProblemType.php @@ -50,6 +50,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'choice_label' => fn(Problem $problem) => sprintf( '%s - %s', $problem->getContestProblems()->first()->getShortName(), $problem->getName() ), + 'choice_value' => 'externalid', 'placeholder' => 'Select a problem', ]; $builder->add('problem', EntityType::class, $problemConfig); @@ -59,6 +60,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => Language::class, 'choices' => $languages, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'placeholder' => 'Select a language', ]); diff --git a/webapp/src/Form/Type/TeamClarificationType.php b/webapp/src/Form/Type/TeamClarificationType.php index 32dfcfe5af..babb3617ef 100644 --- a/webapp/src/Form/Type/TeamClarificationType.php +++ b/webapp/src/Form/Type/TeamClarificationType.php @@ -32,7 +32,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $contest = $this->dj->getCurrentContest($user->getTeam()->getTeamid()); if ($contest) { foreach ($categories as $categoryId => $categoryName) { - $subjects[$categoryName] = sprintf('%d-%s', $contest->getCid(), $categoryId); + $subjects[$categoryName] = sprintf('%s#%s', $contest->getExternalid(), $categoryId); } if ($contest->getFreezeData()->started()) { /** @var ContestProblem $problem */ @@ -40,7 +40,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void if ($problem->getAllowSubmit()) { $problemName = sprintf('%s: %s', $problem->getShortname(), $problem->getProblem()->getName()); - $subjects[$problemName] = sprintf('%d-%d', $contest->getCid(), $problem->getProbid()); + $subjects[$problemName] = sprintf('%s|%s', $contest->getExternalid(), $problem->getExternalId()); } } } diff --git a/webapp/src/Form/Type/TeamType.php b/webapp/src/Form/Type/TeamType.php index 5a5cdde162..7ceb76a7bc 100644 --- a/webapp/src/Form/Type/TeamType.php +++ b/webapp/src/Form/Type/TeamType.php @@ -67,6 +67,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ]); $builder->add('category', EntityType::class, [ 'class' => TeamCategory::class, + 'choice_value' => 'externalid', ]); $builder->add('publicdescription', TextareaType::class, [ 'label' => 'Public description', @@ -76,6 +77,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => TeamAffiliation::class, 'required' => false, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'placeholder' => '-- no affiliation --', 'query_builder' => fn(EntityRepository $er) => $er->createQueryBuilder('a')->orderBy('a.name'), ]); @@ -97,6 +99,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'class' => Contest::class, 'required' => false, 'choice_label' => 'name', + 'choice_value' => 'externalid', 'multiple' => true, 'by_reference' => false, 'query_builder' => fn(EntityRepository $er) => $er @@ -132,6 +135,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => "User", 'required' => true, 'choice_label' => 'name', + 'choice_value' => 'externalid', ]); $builder->add('newUsername', TextType::class, [ 'label' => 'Username', diff --git a/webapp/src/Form/Type/UserType.php b/webapp/src/Form/Type/UserType.php index 12da3e28c2..04acb1dbfa 100644 --- a/webapp/src/Form/Type/UserType.php +++ b/webapp/src/Form/Type/UserType.php @@ -78,6 +78,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'choice_label' => 'effective_name', 'required' => false, 'placeholder' => '-- no team --', + 'choice_value' => 'externalid', 'choices' => $teams, ]); $builder->add('user_roles', EntityType::class, [ diff --git a/webapp/src/Repository/ClarificationRepository.php b/webapp/src/Repository/ClarificationRepository.php new file mode 100644 index 0000000000..880c5b371f --- /dev/null +++ b/webapp/src/Repository/ClarificationRepository.php @@ -0,0 +1,17 @@ + + */ +class ClarificationRepository extends EntityRepository +{ + /** + * @use FindByExternalidTrait + */ + use FindByExternalidTrait; +} diff --git a/webapp/src/Repository/ContestProblemRepository.php b/webapp/src/Repository/ContestProblemRepository.php new file mode 100644 index 0000000000..72611eee1e --- /dev/null +++ b/webapp/src/Repository/ContestProblemRepository.php @@ -0,0 +1,33 @@ + + */ +class ContestProblemRepository extends EntityRepository +{ + public function findByProblemAndContest(Contest|string $contest, Problem|string $problem): ?ContestProblem + { + if ($contest instanceof Contest) { + $contest = $contest->getExternalid(); + } + if ($problem instanceof Problem) { + $problem = $problem->getExternalid(); + } + return $this->createQueryBuilder('cp') + ->innerJoin('cp.contest', 'c') + ->innerJoin('cp.problem', 'p') + ->andWhere('c.externalid = :contestId') + ->andWhere('p.externalid = :problemId') + ->setParameter('contestId', $contest) + ->setParameter('problemId', $problem) + ->getQuery() + ->getOneOrNullResult(); + } +} diff --git a/webapp/src/Repository/ContestRepository.php b/webapp/src/Repository/ContestRepository.php new file mode 100644 index 0000000000..cf564357fe --- /dev/null +++ b/webapp/src/Repository/ContestRepository.php @@ -0,0 +1,17 @@ + + */ +class ContestRepository extends EntityRepository +{ + /** + * @use FindByExternalidTrait + */ + use FindByExternalidTrait; +} diff --git a/webapp/src/Repository/FindByExternalidTrait.php b/webapp/src/Repository/FindByExternalidTrait.php new file mode 100644 index 0000000000..ba4082ede0 --- /dev/null +++ b/webapp/src/Repository/FindByExternalidTrait.php @@ -0,0 +1,19 @@ +findOneBy(['externalid' => $externalId]); + } +} diff --git a/webapp/src/Repository/LanguageRepository.php b/webapp/src/Repository/LanguageRepository.php new file mode 100644 index 0000000000..144a34601c --- /dev/null +++ b/webapp/src/Repository/LanguageRepository.php @@ -0,0 +1,17 @@ + + */ +class LanguageRepository extends EntityRepository +{ + /** + * @use FindByExternalidTrait + */ + use FindByExternalidTrait; +} diff --git a/webapp/src/Repository/ProblemRepository.php b/webapp/src/Repository/ProblemRepository.php new file mode 100644 index 0000000000..0a1d5131d0 --- /dev/null +++ b/webapp/src/Repository/ProblemRepository.php @@ -0,0 +1,17 @@ + + */ +class ProblemRepository extends EntityRepository +{ + /** + * @use FindByExternalidTrait + */ + use FindByExternalidTrait; +} diff --git a/webapp/src/Repository/SubmissionRepository.php b/webapp/src/Repository/SubmissionRepository.php new file mode 100644 index 0000000000..9e5facfef8 --- /dev/null +++ b/webapp/src/Repository/SubmissionRepository.php @@ -0,0 +1,17 @@ + + */ +class SubmissionRepository extends EntityRepository +{ + /** + * @use FindByExternalidTrait + */ + use FindByExternalidTrait; +} diff --git a/webapp/src/Repository/TeamAffiliationRepository.php b/webapp/src/Repository/TeamAffiliationRepository.php new file mode 100644 index 0000000000..14d283d35c --- /dev/null +++ b/webapp/src/Repository/TeamAffiliationRepository.php @@ -0,0 +1,17 @@ + + */ +class TeamAffiliationRepository extends EntityRepository +{ + /** + * @use FindByExternalidTrait + */ + use FindByExternalidTrait; +} diff --git a/webapp/src/Repository/TeamCategoryRepository.php b/webapp/src/Repository/TeamCategoryRepository.php new file mode 100644 index 0000000000..8780ced1fa --- /dev/null +++ b/webapp/src/Repository/TeamCategoryRepository.php @@ -0,0 +1,17 @@ + + */ +class TeamCategoryRepository extends EntityRepository +{ + /** + * @use FindByExternalidTrait + */ + use FindByExternalidTrait; +} diff --git a/webapp/src/Repository/TeamRepository.php b/webapp/src/Repository/TeamRepository.php new file mode 100644 index 0000000000..4c0f46de49 --- /dev/null +++ b/webapp/src/Repository/TeamRepository.php @@ -0,0 +1,17 @@ + + */ +class TeamRepository extends EntityRepository +{ + /** + * @use FindByExternalidTrait + */ + use FindByExternalidTrait; +} diff --git a/webapp/src/Repository/UserRepository.php b/webapp/src/Repository/UserRepository.php new file mode 100644 index 0000000000..5ddde5aefd --- /dev/null +++ b/webapp/src/Repository/UserRepository.php @@ -0,0 +1,17 @@ + + */ +class UserRepository extends EntityRepository +{ + /** + * @use FindByExternalidTrait + */ + use FindByExternalidTrait; +} diff --git a/webapp/src/Security/UserStateUpdater.php b/webapp/src/Security/UserStateUpdater.php index 981e9e079b..89ab53eaa5 100644 --- a/webapp/src/Security/UserStateUpdater.php +++ b/webapp/src/Security/UserStateUpdater.php @@ -47,7 +47,7 @@ public function updateUserState(AuthenticationSuccessEvent $event): void // Otherwise, we would log every API call and we do not want that. if ($firewallName === 'main') { $ip = $this->requestStack->getMainRequest()->getClientIp(); - $this->dj->auditlog('user', $user->getUserid(), 'logged on on ' . $ip, null, $user->getUserName()); + $this->dj->auditlog('user', $user->getExternalid(), 'logged on on ' . $ip, null, $user->getUserName()); } } } diff --git a/webapp/src/Service/BalloonService.php b/webapp/src/Service/BalloonService.php index eb80ffe0f7..e245c0b5c9 100644 --- a/webapp/src/Service/BalloonService.php +++ b/webapp/src/Service/BalloonService.php @@ -85,8 +85,8 @@ public function updateBalloons( /** * @return array, + * team: Team, teamid: string, location: string|null, affiliation: string|null, + * affiliationid: string, category: string, categoryid: string, total: array, * done: bool}}> */ public function collectBalloonTable(Contest $contest, bool $todo = false): array @@ -99,11 +99,11 @@ public function collectBalloonTable(Contest $contest, bool $todo = false): array $query = $em->createQueryBuilder() ->select('b', 's.submittime', 'p.probid', - 't.teamid', 's', 't', 't.location', - 'c.categoryid AS categoryid', 'c.name AS catname', - 'co.cid', 'co.shortname', + 't.externalid as teamid', 's', 't', 't.location', + 'c.externalid AS categoryid', 'c.name AS catname', + 'co.externalid as cid', 'co.shortname', 'cp.shortname AS probshortname', 'cp.color', - 'a.affilid AS affilid', 'a.shortname AS affilshort') + 'a.externalid AS affilid', 'a.shortname AS affilshort') ->from(Balloon::class, 'b') ->leftJoin('b.submission', 's') ->leftJoin('b.problem', 'p') diff --git a/webapp/src/Service/CheckConfigService.php b/webapp/src/Service/CheckConfigService.php index 5f1c76f4b3..e722c0fa1f 100644 --- a/webapp/src/Service/CheckConfigService.php +++ b/webapp/src/Service/CheckConfigService.php @@ -107,8 +107,8 @@ public function runAll(): array $this->stopwatch->stopSection('Teams'); $this->stopwatch->openSection(); - $results['External identifiers'] = $this->checkAllExternalIdentifiers(); - $this->stopwatch->stopSection('External identifiers'); + $results['Identifiers'] = $this->checkAllExternalIdentifiers(); + $this->stopwatch->stopSection('Identifiers'); return $results; } @@ -491,7 +491,7 @@ public function checkContestActive(): ConfigCheckItem $this->stopwatch->stop(__FUNCTION__); $desc = ''; foreach ($contests as $contest) { - $desc .= ' - c' . $contest->getCid() . ' (' . $contest->getShortname() . ")\n"; + $desc .= ' - `' . $contest->getExternalid() . '` (' . $contest->getShortname() . ")\n"; } return new ConfigCheckItem( caption: 'Active contests', @@ -509,7 +509,7 @@ public function checkContestsValidate(): ConfigCheckItem $contesterrors = $cperrors = []; $result = 'O'; foreach ($contests as $contest) { - $cid = $contest->getCid(); + $cid = $contest->getExternalid(); $errors = $this->validator->validate($contest); if (count($errors)) { $result = 'E'; @@ -527,7 +527,7 @@ public function checkContestsValidate(): ConfigCheckItem $desc = ''; foreach ($contesterrors as $cid => $errors) { - $desc .= "Contest: c$cid: "; + $desc .= "Contest: `$cid`: "; if (count($errors) == 0 && empty($cperrors[$cid])) { $desc .= "no errors\n"; } else { @@ -559,7 +559,7 @@ public function checkContestBanners(): ConfigCheckItem foreach ($contests as $contest) { if ($cid = $contest->getExternalid()) { $bannerpath = $this->dj->assetPath($cid, 'contest', true); - $contestName = 'c' . $contest->getCid() . ' (' . $contest->getShortname() . ')'; + $contestName = $contest->getExternalid() . ' (' . $contest->getShortname() . ')'; if ($bannerpath) { if (($filesize = filesize($bannerpath)) > 2 * 1024 * 1024) { $result = 'W'; @@ -598,7 +598,7 @@ public function checkProblemsValidate(): ConfigCheckItem $problemerrors = $moreproblemerrors = []; $result = 'O'; foreach ($problems as $problem) { - $probid = $problem->getProbid(); + $probid = $problem->getExternalid(); $errors = $this->validator->validate($problem); if (count($errors)) { $result = 'E'; @@ -638,20 +638,21 @@ public function checkProblemsValidate(): ConfigCheckItem ->select('tc.testcaseid', 'tc.ranknumber', 'length(tcc.output) as output_size' ) ->from(Testcase::class, 'tc') ->join('tc.content', 'tcc') - ->andWhere('tc.problem = :probid') + ->join('tc.problem', 'p') + ->andWhere('p.externalid = :probid') ->setParameter('probid', $probid) ->getQuery() ->getResult(); if (count($tcs_size) === 0) { $result = 'E'; - $moreproblemerrors[$probid] .= sprintf(" - No testcases for `p%s`\n", $probid); + $moreproblemerrors[$probid] .= sprintf(" - No testcases for `%s`\n", $probid); } else { $problem_output_limit = 1024 * ($problem->getOutputLimit() ?: $output_limit); foreach ($tcs_size as $row) { if ($row['output_size'] > $problem_output_limit) { $result = 'E'; $moreproblemerrors[$probid] .= sprintf( - " - Testcase `%s` for `p%s` exceeds output limit of `%s`\n", + " - Testcase `%s` for `%s` exceeds output limit of `%s`\n", $row['rank'], $probid, $problem_output_limit ); } @@ -662,7 +663,7 @@ public function checkProblemsValidate(): ConfigCheckItem if (!$contestProblem->getAllowJudge()) { $result = 'E'; $moreproblemerrors[$probid] .= sprintf( - " - `p%s` is disabled in contest `%s`\n", + " - `%s` is disabled in contest `%s`\n", $probid, $contestProblem->getContest()->getName() ); } @@ -671,7 +672,7 @@ public function checkProblemsValidate(): ConfigCheckItem $desc = ''; foreach ($problemerrors as $probid => $errors) { - $desc .= " - Problem `p$probid`:\n"; + $desc .= " - Problem `$probid`:\n"; if (count($errors) > 0 || !empty($moreproblemerrors[$probid])) { /* @phpstan-ignore-next-line */ $desc .= (string)$errors . " " . @@ -698,7 +699,7 @@ public function checkLanguagesValidate(): ConfigCheckItem $languageerrors = $morelanguageerrors = []; $result = 'O'; foreach ($languages as $language) { - $langid = $language->getLangid(); + $langid = $language->getExternalid(); $errors = $this->validator->validate($language); if (count($errors)) { $result = 'E'; @@ -969,7 +970,7 @@ protected function checkExternalIdentifiers(string $class): ConfigCheckItem $getter = sprintf('get%s', ucfirst($column)); $routeParams[$param] = $entity->{$getter}(); } - $description .= sprintf(" - [%s %s](%s) does not have an external ID\n", + $description .= sprintf(" - [%s %s](%s) does not have an ID\n", ucfirst(str_replace('_', ' ', $inflector->tableize($entityType))), htmlspecialchars(implode(', ', $metadata->getIdentifierValues($entity))), $this->router->generate($route, $routeParams) diff --git a/webapp/src/Service/DOMJudgeService.php b/webapp/src/Service/DOMJudgeService.php index ab5a9ce060..9c8f4b961c 100644 --- a/webapp/src/Service/DOMJudgeService.php +++ b/webapp/src/Service/DOMJudgeService.php @@ -150,7 +150,7 @@ public function getCurrentContests( } $now = Utils::now(); $qb = $this->em->createQueryBuilder(); - $qb->select('c')->from(Contest::class, 'c', 'c.cid'); + $qb->select('c')->from(Contest::class, 'c', 'c.externalid'); if (isset($onlyOfTeam)) { $qb->leftJoin('c.teams', 'ct') ->leftJoin('c.team_categories', 'tc') @@ -196,7 +196,7 @@ public function getCurrentContest(?int $onlyOfTeam = null, bool $onlyPublic = fa } foreach ($contests as $contest) { - if ($contest->getCid() == $selected_cid) { + if ($contest->getExternalid() == $selected_cid) { return $contest; } } @@ -311,7 +311,7 @@ public function clearCookie( } /** - * @return array + * @return list */ public function getUnreadClarifications(): array { @@ -326,7 +326,7 @@ public function getUnreadClarifications(): array foreach ($clarifications as $clar) { if ($clar->getContest()->getCid() === $contest->getCid()) { $unreadClarifications[] = [ - 'clarid' => $clar->getClarid(), + 'clarid' => $clar->getExternalid(), 'body' => $clar->getBody(), ]; } @@ -360,7 +360,7 @@ public function getUpdates(): array if ($this->checkRole('jury')) { if ($contest) { $clarifications = $this->em->createQueryBuilder() - ->select('clar.clarid', 'clar.body') + ->select('clar.externalid', 'clar.body') ->from(Clarification::class, 'clar') ->andWhere('clar.contest = :contest') ->andWhere('clar.sender is not null') @@ -504,11 +504,11 @@ public function withAllRoles(callable $callable, ?UserInterface $user = null): v */ public function auditlog( string $datatype, - mixed $dataid, + string|null $dataid, string $action, mixed $extraInfo = null, ?string $forceUsername = null, - string|int|null $cid = null + string|null $cid = null ): void { if (!empty($forceUsername)) { $user = $forceUsername; @@ -516,10 +516,6 @@ public function auditlog( $user = $this->getUser() ? $this->getUser()->getUsername() : null; } - if (gettype($cid) == 'string') { - $cid = (int) $cid; - } - $auditLog = new AuditLog(); $auditLog ->setLogtime(Utils::now()) @@ -759,7 +755,7 @@ public function printUserFile( * * @param string $filename The on-disk file to be printed out * @param string $origname The original filename as submitted by the team - * @param string|null $language Langid of the programming language this file is in + * @param string|null $language External ID of the programming language this file is in * @param string $username Username of the print job submitter * @param string|null $teamname Teamname of the team this user belongs to, if any * @param string|null $teamid Teamid of the team this user belongs to, if any @@ -1166,7 +1162,7 @@ public function helperUnblockJudgeTasks(): QueryBuilder ->where('jt.jobid IS NULL'); } - public function unblockJudgeTasksForLanguage(string $langId): void + public function unblockJudgeTasksForLanguage(int $langId): void { // These are all the judgings that don't have associated judgetasks yet. Check whether we unblocked them. $judgings = $this->helperUnblockJudgeTasks() @@ -1201,7 +1197,7 @@ public function unblockJudgeTasksForSubmission(string $submissionId): void // These are all the judgings that don't have associated judgetasks yet. Check whether we unblocked them. $judgings = $this->helperUnblockJudgeTasks() ->join(Submission::class, 's', Join::WITH, 'j.submission = s.submitid') - ->andWhere('j.submission = :submissionid') + ->andWhere('s.externalid = :submissionid') ->setParameter('submissionid', $submissionId) ->getQuery() ->getResult(); diff --git a/webapp/src/Service/ImportExportService.php b/webapp/src/Service/ImportExportService.php index b6e4c21812..89361a26ba 100644 --- a/webapp/src/Service/ImportExportService.php +++ b/webapp/src/Service/ImportExportService.php @@ -551,7 +551,7 @@ public function getResultsData( ->getResult(); $categoryIds = []; foreach ($categories as $category) { - $categoryIds[] = $category->getCategoryid(); + $categoryIds[] = $category->getExternalid(); } $scoreIsInSeconds = (bool)$this->config->get('score_in_seconds'); @@ -894,7 +894,7 @@ protected function importGroupData( foreach ($allCategories as $category) { $this->em->persist($category); $this->em->flush(); - $this->dj->auditlog('team_category', $category->getCategoryid(), 'replaced', + $this->dj->auditlog('team_category', $category->getExternalid(), 'replaced', 'imported from tsv / json'); } @@ -1004,7 +1004,7 @@ protected function importOrganizationData( foreach ($allOrganizations as $organization) { $this->em->persist($organization); $this->em->flush(); - $this->dj->auditlog('team_affiliation', $organization->getAffilid(), 'replaced', + $this->dj->auditlog('team_affiliation', $organization->getExternalid(), 'replaced', 'imported from tsv / json'); } @@ -1370,21 +1370,21 @@ protected function importTeamData(array $teamData, ?string &$message, ?array &$s $this->em->persist($affiliation); $this->em->flush(); $this->dj->auditlog('team_affiliation', - $affiliation->getAffilid(), + $affiliation->getExternalid(), 'added', 'imported from tsv / json'); } foreach ($createdCategories as $category) { $this->em->persist($category); $this->em->flush(); - $this->dj->auditlog('team_category', $category->getCategoryid(), + $this->dj->auditlog('team_category', $category->getExternalid(), 'added', 'imported from tsv'); } foreach ($allTeams as $team) { $this->em->persist($team); $this->em->flush(); - $this->dj->auditlog('team', $team->getTeamid(), 'replaced', 'imported from tsv'); + $this->dj->auditlog('team', $team->getExternalid(), 'replaced', 'imported from tsv'); } if ($contest = $this->dj->getCurrentContest()) { @@ -1481,7 +1481,7 @@ protected function importAccountData( ->setExternalid((string)$teamId) ->setName($teamId . ' - auto-create during import'); $this->em->persist($team); - $this->dj->auditlog('team', $team->getTeamid(), + $this->dj->auditlog('team', $team->getExternalid(), 'added', 'imported from tsv'); } } @@ -1532,14 +1532,15 @@ protected function importAccountData( $this->em->flush(); foreach ($allUsers as $user) { - $this->dj->auditlog('user', $user->getUserid(), 'replaced', 'imported from tsv'); + $this->dj->auditlog('user', $user->getExternalid(), 'replaced', 'imported from tsv'); } if ($contest = $this->dj->getCurrentContest()) { foreach ($newTeams as $newTeam) { + /** @var Team $team */ $team = $newTeam['team']; $action = $newTeam['action']; - $this->dj->auditlog('team', $team->getTeamid(), 'replaced', + $this->dj->auditlog('team', $team->getExternalid(), 'replaced', 'imported from tsv, autocreated for judge'); $this->eventLogService->log('team', $team->getTeamid(), $action, $contest->getCid()); } diff --git a/webapp/src/Service/ImportProblemService.php b/webapp/src/Service/ImportProblemService.php index b69a087500..840759d717 100644 --- a/webapp/src/Service/ImportProblemService.php +++ b/webapp/src/Service/ImportProblemService.php @@ -684,7 +684,7 @@ public function importZippedProblem( $extension = end($parts); foreach ($allowedLanguages as $language) { if (in_array($extension, $language->getExtensions())) { - $languageToUse = $language->getLangid(); + $languageToUse = $language->getExternalid(); break 2; } } @@ -883,7 +883,7 @@ public function importProblemFromRequest(Request $request, ?int $contestId = nul ); $allMessages = array_merge($allMessages, $messages); if ($newProblem) { - $this->dj->auditlog('problem', $newProblem->getProbid(), 'upload zip', $clientName); + $this->dj->auditlog('problem', $newProblem->getExternalid(), 'upload zip', $clientName); $probId = $newProblem->getExternalid(); } else { $errors = array_merge($errors, $messages); diff --git a/webapp/src/Service/RejudgingService.php b/webapp/src/Service/RejudgingService.php index 629dfc3418..5cf79ae67b 100644 --- a/webapp/src/Service/RejudgingService.php +++ b/webapp/src/Service/RejudgingService.php @@ -232,7 +232,7 @@ public function finishRejudging(Rejudging $rejudging, string $action, ?callable ->getQuery() ->getResult(); - $this->dj->auditlog('rejudging', $rejudgingId, $action . 'ing rejudge', '(start)'); + $this->dj->auditlog('rejudging', (string)$rejudgingId, $action . 'ing rejudge', '(start)'); // Add missing state events for all active contests. We do this here // and disable doing it in the loop when calling EventLogService::log, @@ -400,7 +400,7 @@ public function finishRejudging(Rejudging $rejudging, string $action, ?callable ->setValid($action === self::ACTION_APPLY); $this->em->flush(); - $this->dj->auditlog('rejudging', $rejudgingId, $action . 'ing rejudge', '(end)'); + $this->dj->auditlog('rejudging', (string)$rejudgingId, $action . 'ing rejudge', '(end)'); return true; } diff --git a/webapp/src/Service/ScoreboardService.php b/webapp/src/Service/ScoreboardService.php index db9e99ecd7..89d4a11344 100644 --- a/webapp/src/Service/ScoreboardService.php +++ b/webapp/src/Service/ScoreboardService.php @@ -88,11 +88,11 @@ public function getScoreboard( * scorecache table. * * @param Contest $contest The contest to get the scoreboard for. - * @param int $teamId The ID of the team to get the scoreboard for. + * @param string $teamId The ID of the team to get the scoreboard for. * @param bool $showFtsInFreeze If false, the scoreboard will hide first * to solve for submissions after contest freeze. */ - public function getTeamScoreboard(Contest $contest, int $teamId, bool $showFtsInFreeze = true): ?Scoreboard + public function getTeamScoreboard(Contest $contest, string $teamId, bool $showFtsInFreeze = true): ?Scoreboard { $freezeData = new FreezeData($contest); @@ -606,7 +606,7 @@ public function refreshCache(Contest $contest, ?callable $progressReporter = nul { Utils::extendMaxExecutionTime(300); - $this->dj->auditlog('contest', $contest->getCid(), 'refresh scoreboard cache'); + $this->dj->auditlog('contest', $contest->getExternalid(), 'refresh scoreboard cache'); if ($progressReporter === null) { $progressReporter = static function (int $progress, string $log, ?string $message = null) { @@ -657,7 +657,7 @@ public function refreshCache(Contest $contest, ?callable $progressReporter = nul $log .= ', '; } $first = false; - $log .= sprintf('t%d', $team->getTeamid()); + $log .= $team->getExternalid(); $progress = (int)round($index / count($teams) * 100); $progressReporter($progress, $log); @@ -820,7 +820,7 @@ public function getFilterValues(Contest $contest, bool $jury): array /** @var TeamCategory[] $categories */ $categories = $queryBuilder->getQuery()->getResult(); foreach ($categories as $category) { - $filters['categories'][$category->getCategoryid()] = $category->getName(); + $filters['categories'][$category->getExternalid()] = $category->getName(); } // Show only affiliations / countries with visible teams. @@ -845,7 +845,7 @@ public function getFilterValues(Contest $contest, bool $jury): array /** @var TeamAffiliation[] $affiliations */ $affiliations = $queryBuilder->getQuery()->getResult(); foreach ($affiliations as $affiliation) { - $filters['affiliations'][$affiliation->getAffilid()] = $affiliation->getName(); + $filters['affiliations'][$affiliation->getExternalid()] = $affiliation->getName(); if ($showFlags && $affiliation->getCountry() !== null) { $filters['countries'][] = $affiliation->getCountry(); } @@ -983,13 +983,13 @@ protected function getTeamsInOrder(Contest $contest, bool $jury = false, ?Filter if ($filter) { if ($filter->affiliations) { $queryBuilder - ->andWhere('t.affiliation IN (:affiliations)') + ->andWhere('ta.externalid IN (:affiliations)') ->setParameter('affiliations', $filter->affiliations); } if ($filter->categories) { $queryBuilder - ->andWhere('t.category IN (:categories)') + ->andWhere('tc.externalid IN (:categories)') ->setParameter('categories', $filter->categories); } @@ -1001,7 +1001,7 @@ protected function getTeamsInOrder(Contest $contest, bool $jury = false, ?Filter if ($filter->teams) { $queryBuilder - ->andWhere('t.teamid IN (:teams)') + ->andWhere('t.externalid IN (:teams)') ->setParameter('teams', $filter->teams); } } diff --git a/webapp/src/Service/StatisticsService.php b/webapp/src/Service/StatisticsService.php index 7dcf9f6181..0612a389b8 100644 --- a/webapp/src/Service/StatisticsService.php +++ b/webapp/src/Service/StatisticsService.php @@ -96,12 +96,12 @@ public function getTeams(Contest $contest, string $filter): array * problem_num_testcases: int[], team_num_submissions: int[], * team_attempted_n_problems: int[], teams_solved_n_problems: int[], * problem_attempts: int[], problem_solutions: int[], - * problem_stats: array{teams_attempted: array, teams_solved: array}, + * problem_stats: array{teams_attempted: array>, teams_solved: array>}, * submissions: Submission[], misery_index: float, * team_stats: array, * language_stats: array{total_submissions: array, total_solutions: array, - * teams_attempted: array, teams_solved: array}} + * teams_attempted: array>, teams_solved: array>}} */ public function getMiscContestStatistics( Contest $contest, @@ -177,14 +177,14 @@ public function getMiscContestStatistics( $misc['total_submissions']++; $teamStats['total_submitted']++; static::setOrIncrement($misc['problem_attempts'], - $s->getProblem()->getProbId()); + $s->getProblem()->getExternalid()); static::setOrIncrement($teamStats['problems_submitted'], - $s->getProblem()->getProbId()); - $misc['problem_stats']['teams_attempted'][$s->getProblem()->getProbId()][$team->getTeamId()] = $team->getTeamId(); + $s->getProblem()->getExternalid()); + $misc['problem_stats']['teams_attempted'][$s->getProblem()->getExternalid()][$team->getExternalid()] = $team->getExternalid(); static::setOrIncrement($misc['language_stats']['total_submissions'], $s->getLanguage()->getName()); - $misc['language_stats']['teams_attempted'][$s->getLanguage()->getName()][$team->getTeamId()] = $team->getTeamId(); + $misc['language_stats']['teams_attempted'][$s->getLanguage()->getName()][$team->getExternalid()] = $team->getExternalid(); if ($s->getResult() != 'correct') { continue; @@ -192,12 +192,12 @@ public function getMiscContestStatistics( $misc['total_accepted']++; $teamStats['total_accepted']++; static::setOrIncrement($teamStats['problems_accepted'], - $s->getProblem()->getProbId()); + $s->getProblem()->getExternalid()); static::setOrIncrement($misc['problem_solutions'], - $s->getProblem()->getProbId()); - $misc['problem_stats']['teams_solved'][$s->getProblem()->getProbId()][$team->getTeamId()] = $team->getTeamId(); + $s->getProblem()->getExternalid()); + $misc['problem_stats']['teams_solved'][$s->getProblem()->getExternalid()][$team->getExternalid()] = $team->getExternalid(); - $misc['language_stats']['teams_solved'][$s->getLanguage()->getName()][$team->getTeamId()] = $team->getTeamId(); + $misc['language_stats']['teams_solved'][$s->getLanguage()->getName()][$team->getExternalid()] = $team->getExternalid(); static::setOrIncrement($misc['language_stats']['total_solutions'], $s->getLanguage()->getName()); @@ -205,7 +205,7 @@ public function getMiscContestStatistics( $lastSubmission = $s; } } - $misc['team_stats'][$team->getTeamId()] = $teamStats; + $misc['team_stats'][$team->getExternalid()] = $teamStats; static::setOrIncrement($misc['team_attempted_n_problems'], count($teamStats['problems_submitted'])); static::setOrIncrement($misc['teams_solved_n_problems'], @@ -222,7 +222,7 @@ public function getMiscContestStatistics( } $miseryMinutes = ($miserySeconds / 60) * 3; - $misc['team_stats'][$team->getTeamId()]['misery_index'] = $miseryMinutes; + $misc['team_stats'][$team->getExternalid()]['misery_index'] = $miseryMinutes; $totalMiseryMinutes += $miseryMinutes; } $misc['misery_index'] = count($teams) > 0 ? $totalMiseryMinutes / count($teams) : 0; @@ -656,7 +656,7 @@ protected function getNumTestcases(Contest $contest): array // Need to query directly the count, otherwise symfony memory explodes // I think because it tries to load the testdata if you do this the naive way. $results = $this->em->createQueryBuilder() - ->select('p.probid, count(tc.testcaseid) as num_testcases') + ->select('p.externalid, count(tc.testcaseid) as num_testcases') ->from(Contest::class, 'c') ->join('c.problems', 'cp') ->join('cp.problem', 'p') @@ -667,7 +667,7 @@ protected function getNumTestcases(Contest $contest): array ->getQuery()->getResult(); $numTestcases = []; foreach ($results as $r) { - $numTestcases[$r['probid']] = $r['num_testcases']; + $numTestcases[$r['externalid']] = $r['num_testcases']; } return $numTestcases; diff --git a/webapp/src/Service/SubmissionService.php b/webapp/src/Service/SubmissionService.php index 010fd8e59d..5c3622c2c1 100644 --- a/webapp/src/Service/SubmissionService.php +++ b/webapp/src/Service/SubmissionService.php @@ -84,11 +84,12 @@ public function getSubmissionList( $queryBuilder = $this->em->createQueryBuilder() ->from(Submission::class, 's') - ->select('s', 'j', 'cp') + ->select('s', 'j', 'cp', 'l') ->join('s.team', 't') ->join('s.contest_problem', 'cp') + ->join('s.language', 'l') ->andWhere('s.contest IN (:contests)') - ->setParameter('contests', array_keys($contests)) + ->setParameter('contests', $contests) ->orderBy('s.submittime', 'DESC') ->addOrderBy('s.submitid', 'DESC'); @@ -470,7 +471,7 @@ public function submitSolution( ]); } if (!$language instanceof Language) { - $language = $this->em->getRepository(Language::class)->find($language); + $language = $this->em->getRepository(Language::class)->findByExternalId($language); } if ($originalSubmission !== null && !$originalSubmission instanceof Submission) { $originalSubmission = $this->em->getRepository(Submission::class)->find($originalSubmission); @@ -769,8 +770,8 @@ public function submitSolution( $submission->getSubmitid(), $team->getTeamid(), $language->getLangid(), $problem->getProblem()->getProbid())); - $this->dj->auditlog('submission', $submission->getSubmitid(), 'added', - 'via ' . $source->value, null, $contest->getCid()); + $this->dj->auditlog('submission', $submission->getExternalid(), 'added', + 'via ' . $source->value, null, $contest->getExternalid()); if (Utils::difftime((float)$contest->getEndtime(), $submitTime) <= 0) { $this->logger->info( diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index 07fd305a48..b609e2159c 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -935,7 +935,7 @@ public function codeEditor( * source: string, * renamedFrom?: string * }> $files */ - public function showDiff(string $editorId, string $diffId, int $submissionId, string $filename, array $files): string + public function showDiff(string $editorId, string $diffId, string $submissionId, string $filename, array $files): string { $editor = << @@ -943,7 +943,7 @@ public function showDiff(string $editorId, string $diffId, int $submissionId, st $(function() { const editorId = '%s'; const diffId = '%s'; - const submissionId = %d; + const submissionId = '%s'; const models = %s; require(['vs/editor/editor.main'], () => { initDiffEditorTab(editorId, diffId, submissionId, models); @@ -1295,20 +1295,14 @@ public function printWarningContent(ExternalSourceWarning $warning): string /** * Get the entity ID badge to display for the given entity. - * - * When we are in a data source mode that uses external ID's, those will be used and the - * internal ID will be shown in a tooltip. - * - * @param string $idPrefix The prefix to use for the internal ID, if any. */ - public function entityIdBadge(BaseApiEntity $entity, string $idPrefix = ''): string + public function entityIdBadge(BaseApiEntity $entity): string { $propertyAccessor = PropertyAccess::createPropertyAccessor(); $metadata = $this->em->getClassMetadata($entity::class); $primaryKeyColumn = $metadata->getIdentifierColumnNames()[0]; $data = [ - 'idPrefix' => $idPrefix, 'id' => $propertyAccessor->getValue($entity, $primaryKeyColumn), 'externalId' => null, ]; diff --git a/webapp/src/Utils/Scoreboard/Filter.php b/webapp/src/Utils/Scoreboard/Filter.php index 1dee9340b1..0ca2f12332 100644 --- a/webapp/src/Utils/Scoreboard/Filter.php +++ b/webapp/src/Utils/Scoreboard/Filter.php @@ -5,10 +5,10 @@ class Filter { /** - * @param int[] $affiliations + * @param string[] $affiliations * @param string[] $countries - * @param int[] $categories - * @param int[] $teams + * @param string[] $categories + * @param string[] $teams */ public function __construct( public array $affiliations = [], diff --git a/webapp/templates/jury/analysis/contest_overview.html.twig b/webapp/templates/jury/analysis/contest_overview.html.twig index 8b5f628b3d..b3fcd8df3c 100644 --- a/webapp/templates/jury/analysis/contest_overview.html.twig +++ b/webapp/templates/jury/analysis/contest_overview.html.twig @@ -113,7 +113,7 @@ $(function() { {% for p in problems %} - {% set id=p.probid %} + {% set id=p.externalId %} {% set link = path('analysis_problem', {'probid': id, 'view': view}) %} {{ id }} @@ -149,10 +149,10 @@ $(function() { {% for t in teams %} - {% set id=t.teamid %} + {% set id=t.externalid %} {% set link = path('analysis_team', {'team':id}) %} - {{ t | entityIdBadge('t') }} + {{ t | entityIdBadge }} {% if t.affiliation %}{{ t.affiliation.name }}{% else %}-{% endif %} {{ t.effectiveName }} {{ misc.team_stats[id].total_submitted }} / {{ misc.team_stats[id].total_accepted }} @@ -186,7 +186,7 @@ $(function() { {% for j in delayed_judgings.data %} - {% set id=j.submitid %} + {% set id=j.submitexternalid %} {% set link = path('jury_submission', {'submitId': id}) %} {{ id }} @@ -289,7 +289,7 @@ var problem_stats = [ {% for prob in problems %} { "label" : "{{ prob.shortname }}", - "value" : {{ misc.problem_stats.teams_attempted[prob.probid] | default([]) | length }}, + "value" : {{ misc.problem_stats.teams_attempted[prob.externalId] | default([]) | length }}, }, {% endfor %} ] @@ -301,7 +301,7 @@ var problem_stats = [ {% for prob in problems %} { "label" : "{{ prob.shortname }}", - "value" : {{ misc.problem_stats.teams_solved[prob.probid] | default([]) | length }}, + "value" : {{ misc.problem_stats.teams_solved[prob.externalid] | default([]) | length }}, }, {% endfor %} ] diff --git a/webapp/templates/jury/analysis/languages.html.twig b/webapp/templates/jury/analysis/languages.html.twig index 7cb29236af..c299264038 100644 --- a/webapp/templates/jury/analysis/languages.html.twig +++ b/webapp/templates/jury/analysis/languages.html.twig @@ -40,12 +40,12 @@ {% for team in language.teams %} - - {{ team.team | entityIdBadge('t') }} + + {{ team.team | entityIdBadge }} - + {{ team.team.effectiveName }} diff --git a/webapp/templates/jury/analysis/problem.html.twig b/webapp/templates/jury/analysis/problem.html.twig index 52288a12e6..97843ccab8 100644 --- a/webapp/templates/jury/analysis/problem.html.twig +++ b/webapp/templates/jury/analysis/problem.html.twig @@ -1,6 +1,6 @@ {% extends "jury/base.html.twig" %} -{% block title %}Analysis - Problem {{ problem.probid }} {{ current_contest.shortname | default('') }} - {{ parent() }}{% endblock %} +{% block title %}Analysis - Problem {{ problem.externalid }} {{ current_contest.shortname | default('') }} - {{ parent() }}{% endblock %} {% block extrahead %} {{ parent() }} @@ -20,7 +20,7 @@ {% endblock %} {% block content %} -

Problem {{ problem.probid }}: {{ contest_problem | problemBadge }} {{ problem.name }}

+

Problem {{ problem.externalid }}: {{ contest_problem | problemBadge }} {{ problem.name }}

{% include 'jury/partials/analysis_filter.html.twig' %}
diff --git a/webapp/templates/jury/analysis/team.html.twig b/webapp/templates/jury/analysis/team.html.twig index 55b9e97fce..e7a1f569e9 100644 --- a/webapp/templates/jury/analysis/team.html.twig +++ b/webapp/templates/jury/analysis/team.html.twig @@ -25,7 +25,7 @@ {% endblock %} {% block content %} -

Team {{ team.teamid }}: {{ team.effectiveName }}

+

Team {{ team.externalid }}: {{ team.effectiveName }}

diff --git a/webapp/templates/jury/balloons.html.twig b/webapp/templates/jury/balloons.html.twig index 38d87c88fe..32bf0c7a2e 100644 --- a/webapp/templates/jury/balloons.html.twig +++ b/webapp/templates/jury/balloons.html.twig @@ -19,7 +19,7 @@

Balloons - {{ current_contest.name }}

{% if isfrozen %} -
Scoreboard of c{{ current_contest.cid }} ({{ current_contest.shortname }}) is now frozen.
+
Scoreboard of {{ current_contest.externalid }} ({{ current_contest.shortname }}) is now frozen.
{% endif %}
@@ -37,8 +37,8 @@ @@ -54,12 +54,12 @@ + {% endif %} {% endif %}
- + - + {% if run.firstJudgingRun is not null %} - +
-
- {% include 'jury/partials/team_form.html.twig' %} {% endblock %} diff --git a/webapp/templates/jury/user.html.twig b/webapp/templates/jury/user.html.twig index a8a071a8f3..8a50ebf5a2 100644 --- a/webapp/templates/jury/user.html.twig +++ b/webapp/templates/jury/user.html.twig @@ -1,7 +1,7 @@ {% extends "jury/base.html.twig" %} {% import "jury/jury_macros.twig" as macros %} -{% block title %}User {{ user.userid }} - {{ parent() }}{% endblock %} +{% block title %}User {{ user.externalid }} - {{ parent() }}{% endblock %} {% block extrahead %} {{ parent() }} @@ -18,10 +18,6 @@ - - - - @@ -78,8 +74,8 @@ {% if user.team %} {% else %} @@ -137,10 +133,10 @@
{%- if is_granted('ROLE_ADMIN') -%} - {{ button(path('jury_user_edit', {'userId': user.userid}), 'Edit', 'primary', 'edit') }} - {{ button(path('jury_user_delete', {'userId': user.userid}), 'Delete', 'danger', 'trash-alt', true) }} + {{ button(path('jury_user_edit', {'userId': user.externalid}), 'Edit', 'primary', 'edit') }} + {{ button(path('jury_user_delete', {'userId': user.externalid}), 'Delete', 'danger', 'trash-alt', true) }} {% endif %} - {% include 'jury/partials/rejudge_form.html.twig' with {table: 'user', id: user.userid, buttonClass: 'btn-secondary'} %} + {% include 'jury/partials/rejudge_form.html.twig' with {table: 'user', id: user.externalid, buttonClass: 'btn-secondary'} %}

Submissions for {{ user.name }}

diff --git a/webapp/templates/jury/user_edit.html.twig b/webapp/templates/jury/user_edit.html.twig index 45477f7969..39338ebdad 100644 --- a/webapp/templates/jury/user_edit.html.twig +++ b/webapp/templates/jury/user_edit.html.twig @@ -1,7 +1,7 @@ {% extends "jury/base.html.twig" %} {% import "jury/jury_macros.twig" as macros %} -{% block title %}Edit user {{ user.userid }} - {{ parent() }}{% endblock %} +{% block title %}Edit user {{ user.externalid }} - {{ parent() }}{% endblock %} {% block extrahead %} {{ parent() }} @@ -10,15 +10,11 @@ {% block content %} -

Edit user {{ user.userid }}

+

Edit user {{ user.externalid }}

ID{{ user.userid }}
External ID {{ user.externalid }}
Team - - {{ user.team.effectiveName }} {{ user.team | entityIdBadge('t') }} + + {{ user.team.effectiveName }} {{ user.team | entityIdBadge }}
- - - - diff --git a/webapp/templates/jury/versions.html.twig b/webapp/templates/jury/versions.html.twig index 04e636226a..b3b7504c19 100644 --- a/webapp/templates/jury/versions.html.twig +++ b/webapp/templates/jury/versions.html.twig @@ -17,7 +17,7 @@ Language {{ lang.language.langid }} {% if is_granted('ROLE_ADMIN') %} - {{ button(path('jury_language_edit', {'langId': lang.language.langid}), 'Edit version command(s)', 'primary btn-sm', 'edit') }} + {{ button(path('jury_language_edit', {'langId': lang.language.externalid}), 'Edit version command(s)', 'primary btn-sm', 'edit') }} {% endif %} diff --git a/webapp/templates/partials/javascript_language_detect.html.twig b/webapp/templates/partials/javascript_language_detect.html.twig index 78d87c159d..ce36146c2f 100644 --- a/webapp/templates/partials/javascript_language_detect.html.twig +++ b/webapp/templates/partials/javascript_language_detect.html.twig @@ -5,7 +5,7 @@ {% for language in submission_languages %} {% for extension in language.extensions %} case '{{ extension|lower }}': - return '{{ language.langid }}'; + return '{{ language.externalid }}'; {% endfor %} {% endfor %} default: @@ -17,7 +17,7 @@ switch (mainext) { {% for language in submission_languages %} {% if language.requireEntryPoint %} - case '{{ language.langid }}': + case '{{ language.externalid }}': return '{{ language.entryPointDescription | default('Entry point') }}'; {% endif %} {% endfor %} diff --git a/webapp/templates/partials/menu_change_contest.html.twig b/webapp/templates/partials/menu_change_contest.html.twig index 5f6e637201..9ec1f7ec2a 100644 --- a/webapp/templates/partials/menu_change_contest.html.twig +++ b/webapp/templates/partials/menu_change_contest.html.twig @@ -11,7 +11,7 @@ {% endif %} {% for c in contests | filter(c => c != contest) %} {{ c.shortname }} + href="{{ path(change_path, {'contestId': c.externalid}) }}">{{ c.shortname }} {% endfor %} diff --git a/webapp/templates/partials/problem_list.html.twig b/webapp/templates/partials/problem_list.html.twig index e98ba39797..a28812f7ab 100644 --- a/webapp/templates/partials/problem_list.html.twig +++ b/webapp/templates/partials/problem_list.html.twig @@ -145,7 +145,7 @@ {% endif %} {% if clarificationsCount > 0 %} + href="{{ path('team_clarification_by_prob', {'probId': problem.externalId}) }}"> {% if clarificationsCount > 0 %} {% set badgeClass = 'text-bg-info' %} @@ -163,7 +163,7 @@ {% if problem.problem.problemstatementType is not empty %} + href="{{ path(problem_statement_path, {'probId': problem.externalId}) }}"> statement @@ -171,7 +171,7 @@ {% if numsamples > 0 %} + href="{{ path(problem_sample_zip_path, {'probId': problem.externalId}) }}"> samples {% endif %} @@ -183,7 +183,7 @@ {% for attachment in problem.problem.attachments %}
  • + href="{{ path(problem_attachment_path, {'probId': problem.externalId, 'attachmentId': attachment.attachmentid}) }}"> {{ attachment.name }}
  • diff --git a/webapp/templates/partials/scoreboard_summary.html.twig b/webapp/templates/partials/scoreboard_summary.html.twig index 0400016b15..7c98801e2f 100644 --- a/webapp/templates/partials/scoreboard_summary.html.twig +++ b/webapp/templates/partials/scoreboard_summary.html.twig @@ -32,7 +32,7 @@ + {% if enable_ranking %} {% if medalsEnabled %} {% if enable_ranking %} @@ -463,7 +462,7 @@ {% if score.team.affiliation %} {% set link = null %} {% if jury %} - {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.affilid}) %} + {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.externalid}) %} {% endif %} {{ score.team.affiliation.country|countryFlag }} @@ -476,7 +475,7 @@ {% if score.team.affiliation %} {% set link = null %} {% if jury %} - {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.affilid}) %} + {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.externalid}) %} {% endif %} {% set affiliationId = score.team.affiliation.externalid %} @@ -500,15 +499,15 @@ {% set extra = null %} {% if static %} {% set link = '#' %} - {% set extra = 'data-bs-toggle="modal" data-bs-target="#team-modal-' ~ score.team.teamid ~ '"' %} + {% set extra = 'data-bs-toggle="modal" data-bs-target="#team-modal-' ~ score.team.externalid ~ '"' %} {% else %} {% if jury %} - {% set link = path('jury_team', {teamId: score.team.teamid}) %} + {% set link = path('jury_team', {teamId: score.team.externalid}) %} {% elseif public %} - {% set link = path('public_team', {teamId: score.team.teamid}) %} + {% set link = path('public_team', {teamId: score.team.externalid}) %} {% set extra = 'data-ajax-modal' %} {% else %} - {% set link = path('team_team', {teamId: score.team.teamid}) %} + {% set link = path('team_team', {teamId: score.team.externalid}) %} {% set extra = 'data-ajax-modal' %} {% endif %} {% endif %} @@ -573,7 +572,7 @@ {% if static %} {% for score in scores %} - {% embed 'partials/modal.html.twig' with {'modalId': 'team-modal-' ~ score.team.teamid} %} + {% embed 'partials/modal.html.twig' with {'modalId': 'team-modal-' ~ score.team.externalid} %} {% block title %}{{ score.team.effectiveName }}{% endblock %} {% block content %} {% include 'partials/team.html.twig' with {size: 6, team: score.team} %} @@ -605,7 +604,7 @@ diff --git a/webapp/templates/team/partials/clarification.html.twig b/webapp/templates/team/partials/clarification.html.twig index 5004e4c0d4..2d0fba6c9f 100644 --- a/webapp/templates/team/partials/clarification.html.twig +++ b/webapp/templates/team/partials/clarification.html.twig @@ -20,14 +20,14 @@
    From: {% if clarification.sender is not empty %} - {{ clarification.sender.effectiveName }} (t{{ clarification.sender.teamid }}) + {{ clarification.sender.effectiveName }} ({{ clarification.sender.externalid }}) {% else %} Jury {% endif %}
    To: {% if clarification.recipient is not empty %} - {{ clarification.recipient.effectiveName }} (t{{ clarification.recipient.teamid }}) + {{ clarification.recipient.effectiveName }} ({{ clarification.recipient.externalid }}) {% elseif clarification.sender is not null %} Jury {% else %} diff --git a/webapp/templates/team/partials/clarification_list.html.twig b/webapp/templates/team/partials/clarification_list.html.twig index a8afe67896..15e731dc2c 100644 --- a/webapp/templates/team/partials/clarification_list.html.twig +++ b/webapp/templates/team/partials/clarification_list.html.twig @@ -17,7 +17,7 @@
    {%- for clarification in clarifications %} - {%- set link = path('team_clarification', {clarId: clarification.clarid}) %} + {%- set link = path('team_clarification', {clarId: clarification.externalid}) %} diff --git a/webapp/templates/team/partials/submit_scripts.html.twig b/webapp/templates/team/partials/submit_scripts.html.twig index bf0bdb1af6..08182ecdbe 100644 --- a/webapp/templates/team/partials/submit_scripts.html.twig +++ b/webapp/templates/team/partials/submit_scripts.html.twig @@ -4,7 +4,7 @@ return filename.replace(/\.[^\.]*$/, ''); } - function entryPointDetectKt(filename) { + function entryPointDetectKotlin(filename) { var filebase = filename.replace(/\.[^\.]*$/, ''); if (filebase === '') return '_Kt'; @@ -44,8 +44,8 @@ case 'java': $entryPointInput.val(entryPointDetectJava(filename)); break; - case 'kt': - $entryPointInput.val(entryPointDetectKt(filename)); + case 'kotlin': + $entryPointInput.val(entryPointDetectKotlin(filename)); break; default: $entryPointInput.val(filename); diff --git a/webapp/tests/Unit/Controller/API/BalloonsControllerTest.php b/webapp/tests/Unit/Controller/API/BalloonsControllerTest.php index 62314bd84b..19d909c7ac 100644 --- a/webapp/tests/Unit/Controller/API/BalloonsControllerTest.php +++ b/webapp/tests/Unit/Controller/API/BalloonsControllerTest.php @@ -37,7 +37,7 @@ public function testBalloonsNoJudgings(): void public function testMarkAsDone(): void { - $expectedBalloon = ['team'=>'t2: Example teamname', 'problem'=>'U']; + $expectedBalloon = ['team'=>'exteam: Example teamname', 'problem'=>'U']; $contestId = $this->getUnitContestId(); $url = "/contests/$contestId/balloons?todo=1"; $this->loadFixtures([BalloonCorrectSubmissionFixture::class,BalloonUserFixture::class]); diff --git a/webapp/tests/Unit/Controller/API/ConfigControllerTest.php b/webapp/tests/Unit/Controller/API/ConfigControllerTest.php index d596ab58e2..b57ed5bae3 100644 --- a/webapp/tests/Unit/Controller/API/ConfigControllerTest.php +++ b/webapp/tests/Unit/Controller/API/ConfigControllerTest.php @@ -166,7 +166,7 @@ public function testConfigCheckerWorksForAdmin(): void // In the test setup, the config check returns some errors so expected result is 260. $response = $this->verifyApiJsonResponse('GET', $this->endpoint .'/check', 260, 'admin'); - $sections = ['System', 'Configuration', 'Contests', 'Problems and languages', 'Teams', 'External identifiers']; + $sections = ['System', 'Configuration', 'Contests', 'Problems and languages', 'Teams', 'Identifiers']; static::assertIsArray($response); static::assertEquals($sections, array_keys($response)); diff --git a/webapp/tests/Unit/Controller/API/ScoreboardControllerTest.php b/webapp/tests/Unit/Controller/API/ScoreboardControllerTest.php index c9841ca20b..033d64b43d 100644 --- a/webapp/tests/Unit/Controller/API/ScoreboardControllerTest.php +++ b/webapp/tests/Unit/Controller/API/ScoreboardControllerTest.php @@ -66,10 +66,10 @@ public function testFilteredScoreboard(array $filters, int $expectedCount): void public function provideFilters(): Generator { - yield [['category=3'], 1]; - yield [['category=1', 'sortorder=9', 'allteams=true'], 1]; - yield [['category=1', 'sortorder=9'], 0]; - yield [['affiliation=1'], 1]; + yield [['category=participants'], 1]; + yield [['category=system', 'sortorder=9', 'allteams=true'], 1]; + yield [['category=system', 'sortorder=9'], 0]; + yield [['affiliation=utrecht'], 1]; yield [['public=true'], 1]; // Scoreboard is frozen but has no results so those are the same yield [['public=false'], 1]; yield [['sortorder=0'], 1]; @@ -82,6 +82,6 @@ public function provideFilters(): Generator yield [['country=NLD'], 1]; yield [['country=AAA'], 0]; yield [['country=USA'], 0]; - yield [['category=2'], 0]; + yield [['category=self-registered'], 0]; } } diff --git a/webapp/tests/Unit/Controller/API/SubmissionControllerTest.php b/webapp/tests/Unit/Controller/API/SubmissionControllerTest.php index 1bfe4fba36..b8bdeafaa1 100644 --- a/webapp/tests/Unit/Controller/API/SubmissionControllerTest.php +++ b/webapp/tests/Unit/Controller/API/SubmissionControllerTest.php @@ -305,7 +305,7 @@ public function testAddSuccess( static::assertEquals($expectedProblemId, $submission->getProblem()->getProbid(), 'Wrong problem ID'); static::assertEquals($expectedTeamId, $submission->getTeam()->getTeamid(), 'Wrong team ID'); static::assertEquals($expectedUsername, $submission->getUser()->getUserIdentifier(), 'Wrong user'); - static::assertEquals($expectedLanguageId, $submission->getLanguage()->getLangid(), 'Wrong language ID'); + static::assertEquals($expectedLanguageId, $submission->getLanguage()->getExternalid(), 'Wrong language ID'); if ($expectedSubmissionExternalId) { static::assertEquals($expectedSubmissionExternalId, $submission->getExternalid(), 'Wrong external submission ID'); } @@ -390,7 +390,7 @@ public function provideAddSuccess(): Generator 1, 2, 'demo', - 'kt', + 'kotlin', null, null, ['somefile.kt' => file_get_contents(__FILE__)], diff --git a/webapp/tests/Unit/Controller/Jury/ClarificationControllerTest.php b/webapp/tests/Unit/Controller/Jury/ClarificationControllerTest.php index 69ba03609a..4fdf38caff 100644 --- a/webapp/tests/Unit/Controller/Jury/ClarificationControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/ClarificationControllerTest.php @@ -78,7 +78,7 @@ public function testClarificationRequestView(): void trim($clarificationText[1])); $this->verifyLinkToURL('Example teamname', - 'http://localhost/jury/teams/2'); + 'http://localhost/jury/teams/exteam'); } /** @@ -107,7 +107,7 @@ public function testClarificationRequestComposeForm(): void $this->client->submitForm('Send', [ 'jury_clarification[recipient]' => '', - 'jury_clarification[subject]' => '1-tech', + 'jury_clarification[subject]' => 'demo#tech', 'jury_clarification[message]' => 'This is a clarification', ]); diff --git a/webapp/tests/Unit/Controller/Jury/ContestControllerTest.php b/webapp/tests/Unit/Controller/Jury/ContestControllerTest.php index b3ed5e127d..c777e99440 100644 --- a/webapp/tests/Unit/Controller/Jury/ContestControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/ContestControllerTest.php @@ -17,14 +17,14 @@ class ContestControllerTest extends JuryControllerTestCase protected static string $shortTag = 'contest'; protected static array $deleteEntities = ['Demo contest']; protected static string $deleteEntityIdentifier = 'name'; - protected static string $getIDFunc = 'getCid'; + protected static string $getIDFunc = 'getExternalid'; protected static string $className = Contest::class; protected static array $DOM_elements = ['h1' => ['Contests'], 'h3' => ['admin' => ['Current contests', 'All available contests'], 'jury' => []], 'a.btn[title="Import contest"]' => ['admin' => [" Import contest"],'jury'=>[]]]; - protected static ?array $deleteExtra = ['pageurl' => '/jury/contests/1', - 'deleteurl' => '/jury/contests/1/problems/3/delete', + protected static ?array $deleteExtra = ['pageurl' => '/jury/contests/demo', + 'deleteurl' => '/jury/contests/demo/problems/boolfind/delete', 'selector' => 'Boolean switch search', 'fixture' => null]; protected static string $addForm = 'contest['; @@ -48,7 +48,7 @@ class ContestControllerTest extends JuryControllerTestCase 'goldMedals' => '1', 'silverMedals' => '1', 'bronzeMedals' => '1', - 'medalCategories' => ['0' => '2']], + 'medalCategories' => ['0' => 'self-registered']], ['shortname' => 'CLICS_offset_HMM', 'name' => 'No Timezone but only offset', 'activatetimeString' => '2021-07-17 16:08:00+1:11', @@ -246,7 +246,7 @@ class ContestControllerTest extends JuryControllerTestCase 'silverMedals' => '0', 'bronzeMedals' => '0', 'medalsEnabled' => '1', - 'medalCategories' => ['0' => '2']], + 'medalCategories' => ['0' => 'self-registered']], ['shortname' => 'prob', 'problems' => ['0' => ['shortname' => 'boolfind', 'points' => '1', @@ -254,24 +254,24 @@ class ContestControllerTest extends JuryControllerTestCase 'allowJudge' => '1', 'color' => '#ffffff', 'lazyEvalResults' => '0', - 'problem' => '2']]], + 'problem' => 'fltcmp']]], ['shortname' => 'multprob', 'name' => 'Contest with problems', - 'problems' => ['0' => ['problem' => '2', + 'problems' => ['0' => ['problem' => 'fltcmp', 'shortname' => 'fcmp', 'points' => '2', 'allowSubmit' => '1', 'allowJudge' => '1', 'color' => '#000000', 'lazyEvalResults' => '0'], - '1' => ['problem' => '1', + '1' => ['problem' => 'hello', 'shortname' => 'hw', 'points' => '1', 'allowSubmit' => '0', 'allowJudge' => '1', 'color' => '#000000', 'lazyEvalResults' => '0'], - '2' => ['problem' => '3', + '2' => ['problem' => 'boolfind', 'shortname' => 'p3', 'points' => '1', 'allowSubmit' => '1', @@ -280,7 +280,7 @@ class ContestControllerTest extends JuryControllerTestCase 'lazyEvalResults' => '1']]], ['shortname' => 'singleprobspecialchars', 'name' => 'Contest shortname with special chars', - 'problems' => ['0' => ['problem' => '2', + 'problems' => ['0' => ['problem' => 'fltcmp', 'shortname' => '!@#$%^&*()chars<>', 'points' => '2', 'allowSubmit' => '1', @@ -342,7 +342,7 @@ public function testUnlockJudgeTasks(): void // Now, disable the problem. $contest = $em->getRepository(Contest::class)->findOneBy(['shortname' => 'demo']); - $contestId = $contest->getCid(); + $contestId = $contest->getExternalid(); $url = "/jury/contests/$contestId/edit"; $this->verifyPageResponse('GET', $url, 200); @@ -444,7 +444,7 @@ public function testLockedContest(): void $em = static::getContainer()->get(EntityManagerInterface::class); $contest = $em->getRepository(Contest::class)->findOneBy(['shortname' => 'demo']); $contest->setIsLocked(false); - $contestId = $contest->getCid(); + $contestId = $contest->getExternalid(); $editUrl = "/jury/contests/$contestId/edit"; $deleteUrl = "/jury/contests/$contestId/delete"; $contestUrl = "/jury/contests/$contestId"; diff --git a/webapp/tests/Unit/Controller/Jury/ImportExportControllerTest.php b/webapp/tests/Unit/Controller/Jury/ImportExportControllerTest.php index 0083f5e9ab..8c4d8a2ff4 100644 --- a/webapp/tests/Unit/Controller/Jury/ImportExportControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/ImportExportControllerTest.php @@ -116,7 +116,7 @@ public function provideContestYamlContents(): Generator rgb: '#9B630C' HEREDOC; - yield ["1", $yaml]; + yield ["demo", $yaml]; } /** diff --git a/webapp/tests/Unit/Controller/Jury/JuryMiscControllerTest.php b/webapp/tests/Unit/Controller/Jury/JuryMiscControllerTest.php index bda8ae8583..66ecda2099 100644 --- a/webapp/tests/Unit/Controller/Jury/JuryMiscControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/JuryMiscControllerTest.php @@ -110,7 +110,7 @@ public function testBalloonScoreboard(array $fixtures, bool $public, string $con } } } - foreach (range(1, 3) as $id) { + foreach (['hello', 'fltcmp', 'boolfind'] as $id) { $statusCode = in_array($contestStage, ['preActivation','preStart','postDeactivate']) || !$public ? 404 : 200; $this->verifyPageResponse('HEAD', '/public/problems/'.$id.'/statement', $statusCode); } @@ -175,44 +175,44 @@ public function provideJuryAjax(): Generator { foreach ([200 => ['balloon','jury','admin'], 403 => ['team']] as $status => $roles) { foreach ($roles as $role) { - yield ['affiliations', $status, [$role], ['results' => [0 => ['id' => 1, - 'text' => 'Utrecht University (1)'] + yield ['affiliations', $status, [$role], ['results' => [0 => ['id' => 'utrecht', + 'text' => 'Utrecht University (utrecht)'] ]]]; yield ['locations', $status, [$role], ['results' => []]]; } } foreach ([200 => ['jury','admin'], 403 => ['balloon','team']] as $status => $roles) { foreach ($roles as $role) { - yield ['problems', $status, [$role], ['results' => [0 => ['id' => 3, 'text' => 'Boolean switch search (p3)'], - 1 => ['id' => 2, - 'text' => 'Float special compare test (p2)'], - 2 => ['id' => 1, 'text' => 'Hello World (p1)']]]]; - yield ['teams', $status, [$role], ['results' => [0 => ['id' => 1, 'text' => 'DOMjudge (t1)'], - 1 => ['id' => 2, 'text' => 'Example teamname (t2)']]]]; - yield ['languages', $status, [$role], ['results' => [0 => ['id' => 'adb', 'text' => 'Ada (adb)'], + yield ['problems', $status, [$role], ['results' => [0 => ['id' => 'boolfind', 'text' => 'Boolean switch search (boolfind)'], + 1 => ['id' => 'fltcmp', + 'text' => 'Float special compare test (fltcmp)'], + 2 => ['id' => 'hello', 'text' => 'Hello World (hello)']]]]; + yield ['teams', $status, [$role], ['results' => [0 => ['id' => 'domjudge', 'text' => 'DOMjudge (domjudge)'], + 1 => ['id' => 'exteam', 'text' => 'Example teamname (exteam)']]]]; + yield ['languages', $status, [$role], ['results' => [0 => ['id' => 'ada', 'text' => 'Ada (ada)'], 1 => ['id' => 'awk', 'text' => 'AWK (awk)'], 2 => ['id' => 'bash', 'text' => 'Bash shell (bash)'], 3 => ['id' => 'c', 'text' => 'C (c)'], 4 => ['id' => 'csharp', 'text' => 'C# (csharp)'], 5 => ['id' => 'cpp', 'text' => 'C++ (cpp)'], 6 => ['id' => 'f95', 'text' => 'Fortran (f95)'], - 7 => ['id' => 'hs', 'text' => 'Haskell (hs)'], + 7 => ['id' => 'haskell', 'text' => 'Haskell (haskell)'], 8 => ['id' => 'java', 'text' => 'Java (java)'], - 9 => ['id' => 'js', 'text' => 'JavaScript (js)'], - 10 => ['id' => 'kt', 'text' => 'Kotlin (kt)'], + 9 => ['id' => 'javascript', 'text' => 'JavaScript (javascript)'], + 10 => ['id' => 'kotlin', 'text' => 'Kotlin (kotlin)'], 11 => ['id' => 'lua', 'text' => 'Lua (lua)'], 12 => ['id' => 'ocaml', 'text' => 'OCaml (ocaml)'], - 13 => ['id' => 'pas', 'text' => 'Pascal (pas)'], + 13 => ['id' => 'pascal', 'text' => 'Pascal (pascal)'], 14 => ['id' => 'pl', 'text' => 'Perl (pl)'], 15 => ['id' => 'sh', 'text' => 'POSIX shell (sh)'], - 16 => ['id' => 'plg', 'text' => 'Prolog (plg)'], - 17 => ['id' => 'py3', 'text' => 'Python 3 (py3)'], + 16 => ['id' => 'prolog', 'text' => 'Prolog (prolog)'], + 17 => ['id' => 'python3', 'text' => 'Python 3 (python3)'], 18 => ['id' => 'r', 'text' => 'R (r)'], - 19 => ['id' => 'rb', 'text' => 'Ruby (rb)'], - 20 => ['id' => 'rs', 'text' => 'Rust (rs)'], + 19 => ['id' => 'ruby', 'text' => 'Ruby (ruby)'], + 20 => ['id' => 'rust', 'text' => 'Rust (rust)'], 21 => ['id' => 'scala', 'text' => 'Scala (scala)'], 22 => ['id' => 'swift', 'text' => 'Swift (swift)']]]]; - yield ['contests', $status, [$role], ['results' => [0 => ['id' => 1, 'text' => 'Demo contest (demo - c1)'] + yield ['contests', $status, [$role], ['results' => [0 => ['id' => 'demo', 'text' => 'Demo contest (demo - demo)'] ]]]; } } diff --git a/webapp/tests/Unit/Controller/Jury/LanguagesControllerTest.php b/webapp/tests/Unit/Controller/Jury/LanguagesControllerTest.php index 467a90f44f..b8fb40936f 100644 --- a/webapp/tests/Unit/Controller/Jury/LanguagesControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/LanguagesControllerTest.php @@ -17,14 +17,13 @@ class LanguagesControllerTest extends JuryControllerTestCase protected static string $shortTag = 'language'; protected static array $deleteEntities = ['C++','C#','C','Kotlin']; protected static string $deleteEntityIdentifier = 'name'; - protected static string $getIDFunc = 'getLangid'; + protected static string $getIDFunc = 'getExternalid'; protected static string $className = Language::class; protected static array $DOM_elements = ['h1' => ['Enabled languages', 'Disabled languages']]; protected static ?string $addPlus = 'extensions'; protected static string $addForm = 'language['; - protected static array $addEntitiesShown = ['langid', 'externalid', 'name', 'timefactor']; - protected static array $addEntities = [['langid' => 'simple', - 'name' => 'Simple', + protected static array $addEntitiesShown = ['externalid', 'name', 'timefactor']; + protected static array $addEntities = [['name' => 'Simple', 'externalid' => 'extSimple', 'requireEntryPoint' => '0', 'entryPointDescription' => '', @@ -34,53 +33,33 @@ class LanguagesControllerTest extends JuryControllerTestCase 'compileExecutable' => 'java_javac', 'extensions' => ['1' => 'extension'], 'filterCompilerFiles' => '1'], - ['langid' => 'ext', - 'externalid' => 'diffext', + ['externalid' => 'diffext', 'name' => 'External'], - ['langid' => 'lang123_.-', - 'name' => 'langid_expected_chars'], - ['langid' => 'external_expected_chars', - 'externalid' => 'ext123_.-'], - ['langid' => 'name_special_chars', - 'name' => '🕑ড|{}()*'], - ['langid' => 'entry', - 'requireEntryPoint' => '1', + ['name' => 'langid_expected_chars'], + ['externalid' => 'ext123_.-'], + ['name' => '🕑ড|{}()*'], + ['requireEntryPoint' => '1', 'entryPointDescription' => 'shell'], - ['langid' => 'entry_nodesc', - 'requireEntryPoint' => '1', + ['requireEntryPoint' => '1', 'entryPointDescription' => ''], - ['langid' => 'nosub', - 'allowSubmit' => '0'], - ['langid' => 'nojud', - 'allowJudge' => '0'], - ['langid' => 'timef1', - 'timeFactor' => '3'], - ['langid' => 'timef2', - 'timeFactor' => '1.3'], - ['langid' => 'timef3', - 'timeFactor' => '0.5'], - ['langid' => 'comp', - 'compileExecutable' => 'swift'], - ['langid' => 'multex', - 'extensions' => ['0' => 'a', '1' => 'b', + ['allowSubmit' => '0'], + ['allowJudge' => '0'], + ['timeFactor' => '3'], + ['timeFactor' => '1.3'], + ['timeFactor' => '0.5'], + ['compileExecutable' => 'swift'], + ['extensions' => ['0' => 'a', '1' => 'b', '2' => '1', '3' => ',']], - ['langid' => 'extunicode', - 'extensions' => ['0' => '🕑']], - ['langid' => 'nofilt', - 'filterCompilerFiles' => '0'], - ['langid' => 'compVers', - 'compilerVersionCommand' => 'unit -V'], - ['langid' => 'runVers', - 'runnerVersionCommand' => 'run -x |yes|tr "\n" "\`true\`"']]; - protected static array $addEntitiesFailure = ['Only alphanumeric characters and ._- are allowed' => [['langid' => '§$#`"'], - ['langid' => '()*&']], - 'Only letters, numbers, dashes, underscores and dots are allowed.' => [['externalid' => '§$#'], + ['extensions' => ['0' => '🕑']], + ['filterCompilerFiles' => '0'], + ['compilerVersionCommand' => 'unit -V'], + ['runnerVersionCommand' => 'run -x |yes|tr "\n" "\`true\`"']]; + protected static array $addEntitiesFailure = ['Only letters, numbers, dashes, underscores and dots are allowed.' => [['externalid' => '§$#'], ['externalid' => '@#()|']], 'This value should be positive.' => [['timeFactor' => '0'], ['timeFactor' => '-1'], ['timeFactor' => '-.1']], - 'This value should not be blank.' => [['langid' => ''], - ['name' => '']]]; + 'This value should not be blank.' => [['name' => '']]]; public function helperProvideTranslateAddEntity(array $entity, array $expected): array { diff --git a/webapp/tests/Unit/Controller/Jury/ProblemControllerTest.php b/webapp/tests/Unit/Controller/Jury/ProblemControllerTest.php index 410dc4f859..6ddd3587bc 100644 --- a/webapp/tests/Unit/Controller/Jury/ProblemControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/ProblemControllerTest.php @@ -13,11 +13,11 @@ class ProblemControllerTest extends JuryControllerTestCase { protected static string $baseUrl = '/jury/problems'; - protected static array $exampleEntries = ['Hello World', 'default', 5, 3, 2, 1]; + protected static array $exampleEntries = ['Hello World', 'default', 5, 'boolfind', 'fltcmp', 'hello']; protected static string $shortTag = 'problem'; protected static array $deleteEntities = ['Hello World','Float special compare test']; protected static string $deleteEntityIdentifier = 'name'; - protected static string $getIDFunc = 'getProbid'; + protected static string $getIDFunc = 'getExternalid'; protected static string $className = Problem::class; protected static array $DOM_elements = [ 'h1' => ['Problems in contest Demo contest'], @@ -28,7 +28,7 @@ class ProblemControllerTest extends JuryControllerTestCase // Note: we replace the deleteurl in testDeleteExtraEntity below with the actual attachment ID. // This can change when running the tests multiple times. protected static ?array $deleteExtra = [ - 'pageurl' => '/jury/problems/3', + 'pageurl' => '/jury/problems/boolfind', 'deleteurl' => '/jury/problems/attachments/1/delete', 'selector' => 'interactor' ]; @@ -89,7 +89,7 @@ public function testLockedContest(): void $contest->setIsLocked(true); $contestId = $contest->getCid(); $problem = $em->getRepository(Problem::class)->findOneBy(['probid' => 1]); - $probId = $problem->getProbid(); + $probId = $problem->getExternalid(); $editUrl = "/jury/problems/$probId/edit"; $deleteUrl = "/jury/problems/$probId/delete"; $problemUrl = "/jury/problems/$probId"; @@ -158,7 +158,7 @@ public function testMultiDeleteProblems(): void // Get the IDs of the newly created problems foreach ($createdProblems as $problem) { - $problemIds[] = $problem->getProbid(); + $problemIds[] = $problem->getExternalid(); } $problem1Id = $problemIds[0]; diff --git a/webapp/tests/Unit/Controller/Jury/TeamAffiliationControllerTest.php b/webapp/tests/Unit/Controller/Jury/TeamAffiliationControllerTest.php index 0db62b576c..4be9be6a98 100644 --- a/webapp/tests/Unit/Controller/Jury/TeamAffiliationControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/TeamAffiliationControllerTest.php @@ -15,7 +15,7 @@ class TeamAffiliationControllerTest extends JuryControllerTestCase protected static array $deleteEntities = ['UU','FAU']; protected static string $deleteEntityIdentifier = 'shortname'; protected static array $deleteFixtures = [SampleAffiliationsFixture::class]; - protected static string $getIDFunc = 'getAffilid'; + protected static string $getIDFunc = 'getExternalid'; protected static string $className = TeamAffiliation::class; protected static array $DOM_elements = ['h1' => ['Affiliations']]; protected static string $identifyingEditAttribute = 'shortname'; @@ -82,7 +82,7 @@ public function testMultiDeleteTeamAffiliations(): void // Get the IDs of the newly created affiliations foreach ($createdAffiliations as $affiliation) { - $affiliationIds[] = $affiliation->getAffilid(); + $affiliationIds[] = $affiliation->getExternalid(); } $affiliation1Id = $affiliationIds[0]; diff --git a/webapp/tests/Unit/Controller/Jury/TeamCategoryControllerTest.php b/webapp/tests/Unit/Controller/Jury/TeamCategoryControllerTest.php index 95ba0ce4a4..09e6495d8e 100644 --- a/webapp/tests/Unit/Controller/Jury/TeamCategoryControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/TeamCategoryControllerTest.php @@ -14,7 +14,7 @@ class TeamCategoryControllerTest extends JuryControllerTestCase protected static string $shortTag = 'category'; protected static array $deleteEntities = ['System','Observers']; protected static string $deleteEntityIdentifier = 'name'; - protected static string $getIDFunc = 'getCategoryid'; + protected static string $getIDFunc = 'getExternalid'; protected static string $className = TeamCategory::class; protected static array $DOM_elements = ['h1' => ['Categories']]; protected static string $addForm = 'team_category['; @@ -86,7 +86,7 @@ public function testMultiDeleteTeamCategories(): void // Get the IDs of the newly created categories foreach ($createdCategories as $category) { - $categoryIds[] = $category->getCategoryid(); + $categoryIds[] = $category->getExternalid(); } $category1Id = $categoryIds[0]; diff --git a/webapp/tests/Unit/Controller/Jury/TeamControllerTest.php b/webapp/tests/Unit/Controller/Jury/TeamControllerTest.php index 54d6ec5696..a0b5f6191f 100644 --- a/webapp/tests/Unit/Controller/Jury/TeamControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/TeamControllerTest.php @@ -16,17 +16,17 @@ class TeamControllerTest extends JuryControllerTestCase protected static string $shortTag = 'team'; protected static array $deleteEntities = ['DOMjudge','Example teamname']; protected static string $deleteEntityIdentifier = 'name'; - protected static string $getIDFunc = 'getTeamid'; + protected static string $getIDFunc = 'getExternalid'; protected static string $className = Team::class; protected static array $DOM_elements = ['h1' => ['Teams']]; protected static string $addForm = 'team['; protected static array $addEntitiesShown = ['icpcid', 'label', 'displayName', 'location']; - protected static array $overviewSingleNotShown = ['addUserForTeam']; + protected static array $overviewSingleNotShown = ['addUserForTeam', 'category']; protected static array $overviewGeneralNotShown = ['icpcid']; protected static array $addEntitiesCount = ['contests']; protected static array $addEntities = [['name' => 'New Team', 'displayName' => 'New Team Display Name', - 'category' => '3', + 'category' => 'participants', 'publicdescription' => 'Some members', 'penalty' => '0', 'location' => 'The first room', @@ -37,7 +37,7 @@ class TeamControllerTest extends JuryControllerTestCase 'icpcid' => ''], ['name' => 'Another Team', 'displayName' => 'Another Team Display Name', - 'category' => '1', + 'category' => 'system', 'publicdescription' => 'More members', 'penalty' => '20', 'location' => 'Another room', @@ -47,12 +47,12 @@ class TeamControllerTest extends JuryControllerTestCase 'newUsername' => 'linkeduser'], ['name' => 'Team linked to existing user', 'displayName' => 'Third team display name', - 'category' => '1', + 'category' => 'system', 'publicdescription' => 'Members of this team', 'penalty' => '0', 'enabled' => '1', 'addUserForTeam' => Team::ADD_EXISTING_USER, - 'existingUser' => 3], + 'existingUser' => 'demo'], ['name' => 'external_ID', 'label' => 'teamlabel', 'displayName' => 'With External ID'], @@ -124,7 +124,7 @@ public function testMultiDeleteTeams(): void // Get the IDs of the newly created teams foreach ($createdTeams as $team) { - $teamIds[] = $team->getTeamid(); + $teamIds[] = $team->getExternalid(); } $team1Id = $teamIds[0]; diff --git a/webapp/tests/Unit/Controller/Jury/UserControllerTest.php b/webapp/tests/Unit/Controller/Jury/UserControllerTest.php index 1843de2e25..862023d288 100644 --- a/webapp/tests/Unit/Controller/Jury/UserControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/UserControllerTest.php @@ -16,7 +16,7 @@ class UserControllerTest extends JuryControllerTestCase protected static string $shortTag = 'user'; protected static array $deleteEntities = ['demo','judgehost']; protected static string $deleteEntityIdentifier = 'username'; - protected static string $getIDFunc = 'getUserid'; + protected static string $getIDFunc = 'getExternalid'; protected static string $className = User::class; protected static array $DOM_elements = ['h1' => ['Users']]; protected static string $addForm = 'user['; @@ -28,7 +28,7 @@ class UserControllerTest extends JuryControllerTestCase 'plainPassword' => 'plainpassword', 'ipAddress' => '10.0.0.0', 'enabled' => '1', - 'team' => '0', + 'team' => '', 'user_roles' => ['0' => true, '1' => true]], ['username' => 'ABCabc123_-@', 'name' => 'Allowed username'], ['username' => 'nem', 'name' => 'No Email', @@ -124,7 +124,7 @@ public function testMultiDeleteUsers(): void // Get the IDs of the newly created users foreach ($createdUsers as $user) { - $userIds[] = $user->getUserid(); + $userIds[] = $user->getExternalid(); } $user1Id = $userIds[0]; diff --git a/webapp/tests/Unit/Controller/Team/ClarificationControllerTest.php b/webapp/tests/Unit/Controller/Team/ClarificationControllerTest.php index 31a31bb9c0..2ee13710ce 100644 --- a/webapp/tests/Unit/Controller/Team/ClarificationControllerTest.php +++ b/webapp/tests/Unit/Controller/Team/ClarificationControllerTest.php @@ -16,7 +16,7 @@ public function testClarificationRequest(): void $this->client->click($link); $this->client->submitForm('Send', [ - 'team_clarification[subject]' => '1-3', + 'team_clarification[subject]' => 'demo|boolfind', 'team_clarification[message]' => "I don't understand this problem", ]); diff --git a/webapp/tests/Unit/Controller/Team/MiscControllerTest.php b/webapp/tests/Unit/Controller/Team/MiscControllerTest.php index e76b3c6204..ace538518c 100644 --- a/webapp/tests/Unit/Controller/Team/MiscControllerTest.php +++ b/webapp/tests/Unit/Controller/Team/MiscControllerTest.php @@ -103,14 +103,14 @@ function () { $crawler = $this->client->submitForm('Print code', [ 'print[code]' => $code, - 'print[langid]' => 'kt', + 'print[langid]' => 'kotlin', ]); static::assertSelectorTextContains('div.alert.alert-success', 'File has been printed'); $text = trim($crawler->filter('pre')->text(null, false)); - static::assertStringStartsWith('kt', $text); + static::assertStringStartsWith('kotlin', $text); static::assertStringEndsWith( trim(file_get_contents($testFile)), $text); }); diff --git a/webapp/tests/Unit/Controller/Team/ProblemControllerTest.php b/webapp/tests/Unit/Controller/Team/ProblemControllerTest.php index 941169fdbe..ac758971ff 100644 --- a/webapp/tests/Unit/Controller/Team/ProblemControllerTest.php +++ b/webapp/tests/Unit/Controller/Team/ProblemControllerTest.php @@ -135,8 +135,8 @@ public function testSamples(): void // Check the link to download all samples. $link = $cardBodies->eq(1)->filter('a')->eq(2); self::assertSame('samples', $link->text(null, true)); - self::assertSame(sprintf('/team/%d/samples.zip', - $problem->getProbid()), + self::assertSame(sprintf('/team/%s/samples.zip', + $problem->getExternalid()), $link->attr('href')); // Download the sample and make sure the contents are correct. diff --git a/webapp/tests/Unit/Integration/ScoreboardIntegrationTest.php b/webapp/tests/Unit/Integration/ScoreboardIntegrationTest.php index 8ff63433d4..e76341dda9 100644 --- a/webapp/tests/Unit/Integration/ScoreboardIntegrationTest.php +++ b/webapp/tests/Unit/Integration/ScoreboardIntegrationTest.php @@ -288,7 +288,7 @@ public function testTeamScoreboardFreezeFTS(): void $team = $this->teams[1]; - $scoreboard = $this->ss->getTeamScoreboard($this->contest, $team->getTeamid(), false); + $scoreboard = $this->ss->getTeamScoreboard($this->contest, $team->getExternalid(), false); static::assertInstanceOf(SingleTeamScoreboard::class, $scoreboard); static::assertFTSMatch($expected_fts, $scoreboard); @@ -296,7 +296,7 @@ public function testTeamScoreboardFreezeFTS(): void public function testOneSingleFTS(): void { - $lang = $this->em->getRepository(Language::class)->find('c'); + $lang = $this->em->getRepository(Language::class)->findByExternalId('c'); $team = $this->teams[0]; $this->createSubmission($lang, $this->problems[0], $team, 53*60+15.053, 'correct', true) @@ -329,7 +329,7 @@ public function testOneSingleFTS(): void public function testFTSwithVerificationRequired(): void { - $lang = $this->em->getRepository(Language::class)->find('c'); + $lang = $this->em->getRepository(Language::class)->findByExternalId('c'); $team = $this->teams[0]; $this->createSubmission($lang, $this->problems[0], $team, 53*60+15.053, 'correct', true) @@ -365,7 +365,7 @@ public function testFTSwithVerificationRequired(): void public function testFTSwithQueuedRejudging(): void { - $lang = $this->em->getRepository(Language::class)->find('c'); + $lang = $this->em->getRepository(Language::class)->findByExternalId('c'); $team = $this->teams[0]; $this->createSubmission($lang, $this->problems[0], $team, 53 * 60 + 15.053, 'wrong-answer') @@ -445,7 +445,7 @@ protected function recalcScoreCaches(): void protected function createDefaultSubmissions(): void { - $lang = $this->em->getRepository(Language::class)->find('cpp'); + $lang = $this->em->getRepository(Language::class)->findByExternalId('cpp'); $team = $this->teams[0]; $this->createSubmission($lang, $this->problems[0], $team, 53*60+15.053, 'no-output'); diff --git a/webapp/tests/Unit/Service/ImportExportServiceTest.php b/webapp/tests/Unit/Service/ImportExportServiceTest.php index 01453533af..1ce4b5beee 100644 --- a/webapp/tests/Unit/Service/ImportExportServiceTest.php +++ b/webapp/tests/Unit/Service/ImportExportServiceTest.php @@ -1302,7 +1302,7 @@ public function testGetResultsData(bool $full, bool $honors, string $dataSet, st $contestProblemsById[$contestProblem->getExternalid()] = $contestProblem; } - $cpp = $em->getRepository(Language::class)->find('cpp'); + $cpp = $em->getRepository(Language::class)->findByExternalId('cpp'); // We use direct queries here to speed this up $submissionInsertQuery = $em->getConnection()->prepare('INSERT INTO submission (teamid, cid, probid, langid, submittime) VALUES (:teamid, :cid, :probid, :langid, :submittime)'); @@ -1357,7 +1357,7 @@ public function testGetResultsData(bool $full, bool $honors, string $dataSet, st /** @var RequestStack $requestStack */ $requestStack = static::getContainer()->get(RequestStack::class); $request = new Request(); - $request->cookies->set('domjudge_cid', (string)$contest->getCid()); + $request->cookies->set('domjudge_cid', (string)$contest->getExternalid()); $requestStack->push($request); $results = $importExportService->getResultsData(37, $full, $honors); diff --git a/webapp/var/log/.gitkeep b/webapp/var/log/.gitkeep old mode 100755 new mode 100644
    User ID{{ user.userid }}
    Username {{ user.username }} {% set link = null %} {% if jury %} - {% set link = path('jury_problem', {'probId': problem.probid}) %} + {% set link = path('jury_problem', {'probId': problem.externalid}) %} {% endif %} diff --git a/webapp/templates/partials/scoreboard_table.html.twig b/webapp/templates/partials/scoreboard_table.html.twig index 71b074da1d..d5720b9313 100644 --- a/webapp/templates/partials/scoreboard_table.html.twig +++ b/webapp/templates/partials/scoreboard_table.html.twig @@ -94,13 +94,13 @@ {% set target = '_self' %} {% if not static %} {% if jury %} - {% set link = path('jury_problem', {'probId': problem.probid}) %} + {% set link = path('jury_problem', {'probId': problem.externalid}) %} {% elseif problem.problem.problemstatementType is not empty %} {% if public %} - {% set link = path('public_problem_statement', {probId: problem.probid}) %} + {% set link = path('public_problem_statement', {probId: problem.externalid}) %} {% set target = '_blank' %} {% else %} - {% set link = path('team_problem_statement', {probId: problem.probid}) %} + {% set link = path('team_problem_statement', {probId: problem.externalid}) %} {% set target = '_blank' %} {% endif %} {% endif %} @@ -108,7 +108,7 @@ @@ -156,7 +156,7 @@ {% else %} {% set color = score.team.category.color %} {% endif %} -
    @@ -188,7 +188,7 @@ {% if score.team.affiliation %} {% set link = null %} {% if jury %} - {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.affilid}) %} + {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.externalid}) %} {% endif %} {{ score.team.affiliation.country|countryFlag }} @@ -201,7 +201,7 @@ {% if score.team.affiliation %} {% set link = null %} {% if jury %} - {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.affilid}) %} + {% set link = path('jury_team_affiliation', {'affilId': score.team.affiliation.externalid}) %} {% endif %} {% set affiliationId = score.team.affiliation.externalid %} @@ -225,15 +225,15 @@ {% set extra = null %} {% if static %} {% set link = '#' %} - {% set extra = 'data-bs-toggle="modal" data-bs-target="#team-modal-' ~ score.team.teamid ~ '"' %} + {% set extra = 'data-bs-toggle="modal" data-bs-target="#team-modal-' ~ score.team.externalid ~ '"' %} {% else %} {% if jury %} - {% set link = path('jury_team', {teamId: score.team.teamid}) %} + {% set link = path('jury_team', {teamId: score.team.externalid}) %} {% elseif public %} - {% set link = path('public_team', {teamId: score.team.teamid}) %} + {% set link = path('public_team', {teamId: score.team.externalid}) %} {% set extra = 'data-ajax-modal' %} {% else %} - {% set link = path('team_team', {teamId: score.team.teamid}) %} + {% set link = path('team_team', {teamId: score.team.externalid}) %} {% set extra = 'data-ajax-modal' %} {% endif %} {% endif %} @@ -321,8 +321,8 @@ {% set link = null %} {% set extra = null %} {% if jury %} - {% set restrict = {problemId: problem.probid} %} - {% set link = path('jury_team', {teamId: score.team.teamid, restrict: restrict}) %} + {% set restrict = {problemId: problem.externalid} %} + {% set link = path('jury_team', {teamId: score.team.externalid, restrict: restrict}) %} {% elseif static %} {% set link = '#' %} {% set extra = 'data-submissions-url="' ~ path('public_submissions_data') ~ '"' %} @@ -441,8 +441,7 @@ {% endif %}
    {% set link = null %} {% if jury %} - {% set link = path('jury_team_category', {'categoryId': category.categoryid}) %} + {% set link = path('jury_team_category', {'categoryId': category.externalid}) %} {% endif %} {{ category.name }}
    diff --git a/webapp/templates/team/partials/submission.html.twig b/webapp/templates/team/partials/submission.html.twig index c5c3d9445b..6240c7c905 100644 --- a/webapp/templates/team/partials/submission.html.twig +++ b/webapp/templates/team/partials/submission.html.twig @@ -53,7 +53,7 @@ {% if allowDownload %}
    diff --git a/webapp/templates/team/partials/submission_list.html.twig b/webapp/templates/team/partials/submission_list.html.twig index 80c4bd2123..75eaaf45dd 100644 --- a/webapp/templates/team/partials/submission_list.html.twig +++ b/webapp/templates/team/partials/submission_list.html.twig @@ -23,7 +23,7 @@ {%- for submission in submissions %} {% set link = null %} {% if (submission.submittime < current_team_contest.endtime or showTooLateResult) and submission.result and submission.judgings.first is not empty and submission.judgings.first.result is not empty and (not verificationRequired or submission.judgings.first.verified) %} - {% set link = path('team_submission', {submitId: submission.submitid}) %} + {% set link = path('team_submission', {submitId: submission.externalid}) %} {% endif %} {% set classes = '' %} @@ -43,7 +43,7 @@
    - {{ submission.language.langid }} + {{ submission.language.name }} @@ -78,7 +78,7 @@ {% endif %} {% if allowDownload %} - +