Skip to content

Commit e5d4847

Browse files
Fix reuploading problemset.
Also fix uploading using PUT and some small bugs in the contest import script wrt problemset upload.
1 parent 48c3616 commit e5d4847

File tree

7 files changed

+126
-8
lines changed

7 files changed

+126
-8
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"phpstan/phpdoc-parser": "^1.25",
7676
"promphp/prometheus_client_php": "^2.6",
7777
"ramsey/uuid": "^4.2",
78+
"riverline/multipart-parser": "^2.1",
7879
"select2/select2": "4.*",
7980
"sentry/sentry-symfony": "^4.5",
8081
"symfony/asset": "6.4.*",

composer.lock

Lines changed: 57 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

misc-tools/import-contest.in

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@ def import_contest_problemset_document(cid: str):
127127

128128
if text_file:
129129
if dj_utils.confirm(f'Import {text_file} for contest?', False):
130-
dj_utils.upload_file(f'contests/{cid}/problemset', 'text', text_file)
131-
print('Contest text imported.')
130+
dj_utils.upload_file(f'contests/{cid}/problemset', 'problemset', text_file)
131+
print('Contest problemset imported.')
132132
else:
133-
print('Skipping contest text import.')
133+
print('Skipping contest problemset import.')
134134

135135
if len(sys.argv) == 1:
136136
dj_utils.domjudge_webapp_folder_or_api_url = webappdir

webapp/src/Controller/API/ContestController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ public function deleteProblemsetAction(Request $request, string $cid): Response
297297
required: ['problemset'],
298298
properties: [
299299
new OA\Property(
300-
property: 'text',
300+
property: 'problemset',
301301
description: 'The problemset document to use, as either text/html, text/plain or application/pdf.',
302302
type: 'string',
303303
format: 'binary'

webapp/src/Entity/Contest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1390,7 +1390,7 @@ public function addExternalContestSource(ExternalContestSource $externalContestS
13901390

13911391
public function setContestProblemsetContent(?ContestProblemsetContent $content): self
13921392
{
1393-
$this->contestProblemsetContent->clear();
1393+
$this->contestProblemsetContent = new ArrayCollection();
13941394
if ($content) {
13951395
$this->contestProblemsetContent->add($content);
13961396
$content->setContest($this);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\EventListener;
4+
5+
use Riverline\MultiPartParser\Converters\HttpFoundation;
6+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
7+
use Symfony\Component\HttpFoundation\File\UploadedFile;
8+
use Symfony\Component\HttpKernel\Event\ControllerEvent;
9+
10+
#[AsEventListener(priority: 1)]
11+
class ParseFilesForPutListener
12+
{
13+
public function __invoke(ControllerEvent $event): void
14+
{
15+
// When we have a PUT request, the files on the request will not be filled, since PHP doesn't
16+
// fill the $_FILES array. We can manually parse the request to fill it using a library.
17+
// This code is based on https://github.com/symfony/symfony/issues/36409#issuecomment-612442260.
18+
$request = $event->getRequest();
19+
if ($request->isMethod('PUT')) {
20+
$document = HttpFoundation::convert($request);
21+
// Not all PUT requests are multipart, for example JSON PUT requests aren't. So check
22+
// to see if this is a multipart request and otherwise do nothing.
23+
if (!$document->isMultiPart()) {
24+
return;
25+
}
26+
27+
foreach ($document->getParts() as $part) {
28+
if (!$part->isFile()) {
29+
continue;
30+
}
31+
32+
$filename = tempnam(sys_get_temp_dir(), 'dj-put');
33+
file_put_contents($filename, $part->getBody());
34+
$uploadedFile = new UploadedFile(
35+
$filename,
36+
$part->getFileName(),
37+
$part->getMimeType(),
38+
test: true // Since it is not a real uploaded file, mark it as test
39+
);
40+
$request->files->set($part->getName(), $uploadedFile);
41+
}
42+
}
43+
}
44+
}

webapp/tests/Unit/Controller/API/ContestControllerAdminTest.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,12 @@ public function testProblemsetManagement(): void
176176
$object = $this->verifyApiJsonResponse('GET', $url, 200, $this->apiUser);
177177
self::assertArrayNotHasKey('problemset', $object);
178178

179-
// Now upload a banner
179+
// Now upload a problemset
180180
$problemsetFile = __DIR__ . '/../../../../../webapp/public/doc/logos/DOMjudgelogo.pdf';
181181
$problemset = new UploadedFile($problemsetFile, 'DOMjudgelogo.pdf');
182182
$this->verifyApiJsonResponse('POST', $url . '/problemset', 204, $this->apiUser, null, ['problemset' => $problemset]);
183183

184-
// Verify we do have a banner now
184+
// Verify we do have a problemset now
185185
$object = $this->verifyApiJsonResponse('GET', $url, 200, $this->apiUser);
186186
$problemsetConfig = [
187187
[
@@ -200,6 +200,23 @@ public function testProblemsetManagement(): void
200200
$callbackData = ob_get_clean();
201201
self::assertEquals(file_get_contents($problemsetFile), $callbackData);
202202

203+
// Upload the problemset again, this time using PUT to also test that
204+
$problemsetFile = __DIR__ . '/../../../../../webapp/public/doc/logos/DOMjudgelogo.pdf';
205+
$problemset = new UploadedFile($problemsetFile, 'DOMjudgelogo.pdf');
206+
$this->verifyApiJsonResponse('PUT', $url . '/problemset', 204, $this->apiUser, null, ['problemset' => $problemset]);
207+
208+
// Verify we still have a problemset
209+
$object = $this->verifyApiJsonResponse('GET', $url, 200, $this->apiUser);;
210+
self::assertSame($problemsetConfig, $object['problemset']);
211+
212+
$this->client->request('GET', '/api' . $url . '/problemset');
213+
/** @var StreamedResponse $response */
214+
$response = $this->client->getResponse();
215+
ob_start();
216+
$response->getCallback()();
217+
$callbackData = ob_get_clean();
218+
self::assertEquals(file_get_contents($problemsetFile), $callbackData);
219+
203220
// Delete the problemset again
204221
$this->verifyApiJsonResponse('DELETE', $url . '/problemset', 204, $this->apiUser);
205222

0 commit comments

Comments
 (0)