diff --git a/webapp/migrations/Version20240224115108.php b/webapp/migrations/Version20240224115108.php new file mode 100644 index 0000000000..be1c9f610c --- /dev/null +++ b/webapp/migrations/Version20240224115108.php @@ -0,0 +1,36 @@ +addSql('ALTER TABLE configuration CHANGE name name VARCHAR(64) NOT NULL COMMENT \'Name of the configuration variable\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE configuration CHANGE name name VARCHAR(32) NOT NULL COMMENT \'Name of the configuration variable\''); + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/webapp/src/DataTransferObject/Shadowing/ProblemEvent.php b/webapp/src/DataTransferObject/Shadowing/ProblemEvent.php index 2aa47e6e27..5294759608 100644 --- a/webapp/src/DataTransferObject/Shadowing/ProblemEvent.php +++ b/webapp/src/DataTransferObject/Shadowing/ProblemEvent.php @@ -7,7 +7,7 @@ class ProblemEvent implements EventData public function __construct( public readonly string $id, public readonly string $name, - public readonly int $timeLimit, + public readonly ?int $timeLimit, public readonly ?string $label, public readonly ?string $rgb, ) {} diff --git a/webapp/src/Entity/Configuration.php b/webapp/src/Entity/Configuration.php index dc41cbed87..32c41e1f3a 100644 --- a/webapp/src/Entity/Configuration.php +++ b/webapp/src/Entity/Configuration.php @@ -20,7 +20,7 @@ class Configuration #[ORM\Column(options: ['comment' => 'Configuration ID', 'unsigned' => true])] private int $configid; - #[ORM\Column(length: 32, options: ['comment' => 'Name of the configuration variable'])] + #[ORM\Column(length: 64, options: ['comment' => 'Name of the configuration variable'])] private string $name; #[ORM\Column( diff --git a/webapp/src/Service/ExternalContestSourceService.php b/webapp/src/Service/ExternalContestSourceService.php index 31922a1b2f..3ca2257292 100644 --- a/webapp/src/Service/ExternalContestSourceService.php +++ b/webapp/src/Service/ExternalContestSourceService.php @@ -522,7 +522,8 @@ protected function loadContest(): void case ExternalContestSource::TYPE_CCS_API: try { // The base URL is the URL of the CCS API root. - if (preg_match('/^(.*\/)contests\/.*/', + // Proper is '^(.*\/)contests\/.*/', but PC^2 doesn't expose this (yet). + if (preg_match('/^(.*\/)contest(s\/.*)?/', $this->source->getSource(), $matches) === 0) { $this->loadingError = 'Cannot determine base URL. Did you pass a CCS API contest URL?'; $this->cachedContestData = null; @@ -701,11 +702,11 @@ protected function validateAndUpdateContest(Event $event, EventData $data): void $freezeHourModifier = $freezeNegative ? -1 : 1; $freezeInSeconds = $freezeHourModifier * (int)$freezeData[2] * 3600 + 60 * (int)$freezeData[3] - + (double)sprintf('%d.%03d', $freezeData[4], $freezeData[5]); + + (double)sprintf('%d.%03d', $freezeData[4], $freezeData[5] ?? 0); $durationHourModifier = $durationNegative ? -1 : 1; $durationInSeconds = $durationHourModifier * (int)$durationData[2] * 3600 + 60 * (int)$durationData[3] - + (double)sprintf('%d.%03d', $durationData[4], $durationData[5]); + + (double)sprintf('%d.%03d', $durationData[4], $durationData[5] ?? 0); $freezeStartSeconds = $durationInSeconds - $freezeInSeconds; $freezeHour = floor($freezeStartSeconds / 3600); $freezeMinutes = floor(($freezeStartSeconds % 3600) / 60); @@ -1023,17 +1024,22 @@ protected function validateAndUpdateProblem(Event $event, EventData $data): void $this->removeWarning($event->type, $data->id, ExternalSourceWarning::TYPE_ENTITY_NOT_FOUND); $toCheckProblem = [ - 'name' => $data->name, - 'timelimit' => $data->timeLimit, + 'name' => $data->name, ]; + if ($data->timeLimit !== null) { + $toCheckProblem['timelimit'] = $data->timeLimit; + } + + /* Disable as PC2 can have 2 problems with the same label if ($contestProblem->getShortname() !== $data->label) { + if ($contestProblem->getShortname() !== $data['label']) { $this->logger->warning( 'Contest problem short name does not match between feed (%s) and local (%s), updating', [$data->label, $contestProblem->getShortname()] ); $contestProblem->setShortname($data->label); - } + } */ if ($contestProblem->getColor() !== ($data->rgb)) { $this->logger->warning( 'Contest problem color does not match between feed (%s) and local (%s), updating', @@ -1435,7 +1441,7 @@ protected function importSubmission(Event $event, EventData $data): void 'message' => 'No source files in event', ]); $submissionDownloadSucceeded = false; - } elseif (($data->files[0]->mime ?? null) !== 'application/zip') { + } elseif ($data->files[0]->mime !== null && $data->files[0]->mime !== 'application/zip') { $this->addOrUpdateWarning($event, $data->id, ExternalSourceWarning::TYPE_SUBMISSION_ERROR, [ 'message' => 'Non-ZIP source files in event', ]); @@ -1444,6 +1450,10 @@ protected function importSubmission(Event $event, EventData $data): void $zipUrl = $data->files[0]->href; if (preg_match('/^https?:\/\//', $zipUrl) === 0) { // Relative URL, prepend the base URL. + // If both the base path ends with a / and the zip URL starts with one, drop one of them + if (str_ends_with($this->basePath, '/') && str_starts_with($zipUrl, '/')) { + $zipUrl = substr($zipUrl, 1); + } $zipUrl = ($this->basePath ?? '') . $zipUrl; } diff --git a/webapp/tests/Unit/Controller/Jury/ConfigControllerTest.php b/webapp/tests/Unit/Controller/Jury/ConfigControllerTest.php index fe47776fa9..5375266c92 100644 --- a/webapp/tests/Unit/Controller/Jury/ConfigControllerTest.php +++ b/webapp/tests/Unit/Controller/Jury/ConfigControllerTest.php @@ -54,6 +54,18 @@ function ($errors) { }); } + /** + * Test that we can change a longer config value. + */ + public function testChangedLongConfigName(): void + { + $this->withChangedConfiguration('config_external_contest_sources_allow_untrusted_certificates', 'on', + function ($errors) { + static::assertEmpty($errors); + $this->verifyPageResponse('GET', '/jury/config', 200); + }); + } + /** * Test that an invalid penalty time produces an error */