Skip to content

Commit 6dcfb05

Browse files
committed
Merge remote-tracking branch 'origin/8.3.1.1_paste_code' into paste_code
2 parents 6a70aa0 + bb8e1e5 commit 6dcfb05

File tree

9 files changed

+283
-25
lines changed

9 files changed

+283
-25
lines changed

.github/workflows/autoconf-check-different-distro.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
image: ${{ matrix.os }}:${{ matrix.version }}
2121
steps:
2222
- name: Install git so we get the .github directory
23-
run: dnf install -y git
23+
run: dnf install -y git composer
2424
- uses: actions/checkout@v4
2525
- name: Setup image and run bats tests
2626
run: .github/jobs/configure-checks/setup_configure_image.sh

.github/workflows/autoconf-check.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
image: ${{ matrix.os }}:${{ matrix.version }}
3333
steps:
3434
- name: Install git so we get the .github directory
35-
run: apt-get update; apt-get install -y git
35+
run: apt-get update; apt-get install -y git composer
3636
- uses: actions/checkout@v4
3737
- name: Setup image and run bats tests
3838
run: .github/jobs/configure-checks/setup_configure_image.sh

etc/db-config.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,14 @@
201201
- category: Display
202202
description: Options related to the DOMjudge user interface.
203203
items:
204+
- name: default_submission_code_mode
205+
type: int
206+
default_value: 0
207+
public: true
208+
description: Select the default submission method for the team
209+
options:
210+
0: Paste
211+
1: Upload
204212
- name: output_display_limit
205213
type: int
206214
default_value: 2000

lib/Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ install-domserver:
2020
install-judgehost:
2121
$(INSTALL_DATA) -t $(DESTDIR)$(judgehost_libdir) *.php *.sh
2222
$(INSTALL_PROG) -t $(DESTDIR)$(judgehost_libdir) alert
23+
24+
domserver: SUBDIRS=vendor

lib/vendor/Makefile

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
ifndef TOPDIR
2+
TOPDIR=../..
3+
endif
4+
include $(TOPDIR)/Makefile.global
5+
6+
clean-l:
7+
rm -f autoload_runtime.php
8+
9+
autoload_runtime.php:
10+
composer $(subst 1,-q,$(QUIET)) dump-autoload -o -a -d $(TOPDIR)
11+
12+
domserver: autoload_runtime.php

webapp/src/Controller/Team/SubmissionController.php

+93-10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Entity\Submission;
1010
use App\Entity\Testcase;
1111
use App\Form\Type\SubmitProblemType;
12+
use App\Form\Type\SubmitProblemPasteType;
1213
use App\Service\ConfigurationService;
1314
use App\Service\DOMJudgeService;
1415
use App\Service\EventLogService;
@@ -60,32 +61,48 @@ public function createAction(Request $request, ?Problem $problem = null): Respon
6061
if ($problem !== null) {
6162
$data['problem'] = $problem;
6263
}
63-
$form = $this->formFactory
64+
$formUpload = $this->formFactory
6465
->createBuilder(SubmitProblemType::class, $data)
6566
->setAction($this->generateUrl('team_submit'))
6667
->getForm();
6768

68-
$form->handleRequest($request);
69+
$formPaste = $this->formFactory
70+
->createBuilder(SubmitProblemPasteType::class, $data)
71+
->setAction($this->generateUrl('team_submit'))
72+
->getForm();
6973

70-
if ($form->isSubmitted() && $form->isValid()) {
74+
$formUpload->handleRequest($request);
75+
$formPaste->handleRequest($request);
76+
if ($formUpload->isSubmitted() && $formUpload->isValid()) {
7177
if ($contest === null) {
7278
$this->addFlash('danger', 'No active contest');
7379
} elseif (!$this->dj->checkrole('jury') && !$contest->getFreezeData()->started()) {
7480
$this->addFlash('danger', 'Contest has not yet started');
7581
} else {
7682
/** @var Problem $problem */
77-
$problem = $form->get('problem')->getData();
83+
$problem = $formUpload->get('problem')->getData();
7884
/** @var Language $language */
79-
$language = $form->get('language')->getData();
85+
$language = $formUpload->get('language')->getData();
8086
/** @var UploadedFile[] $files */
81-
$files = $form->get('code')->getData();
87+
$files = $formUpload->get('code')->getData();
8288
if (!is_array($files)) {
8389
$files = [$files];
8490
}
85-
$entryPoint = $form->get('entry_point')->getData() ?: null;
91+
$entryPoint = $formUpload->get('entry_point')->getData() ?: null;
8692
$submission = $this->submissionService->submitSolution(
87-
$team, $this->dj->getUser(), $problem->getProbid(), $contest, $language, $files, 'team page', null,
88-
null, $entryPoint, null, null, $message
93+
$team,
94+
$this->dj->getUser(),
95+
$problem->getProbid(),
96+
$contest,
97+
$language,
98+
$files,
99+
'team page',
100+
null,
101+
null,
102+
$entryPoint,
103+
null,
104+
null,
105+
$message
89106
);
90107

91108
if ($submission) {
@@ -96,11 +113,77 @@ public function createAction(Request $request, ?Problem $problem = null): Respon
96113
} else {
97114
$this->addFlash('danger', $message);
98115
}
116+
return $this->redirectToRoute('team_index');
117+
}
118+
} elseif ($formPaste->isSubmitted() && $formPaste->isValid()) {
119+
if ($contest === null) {
120+
$this->addFlash('danger', 'No active contest');
121+
} elseif (!$this->dj->checkrole('jury') && !$contest->getFreezeData()->started()) {
122+
$this->addFlash('danger', 'Contest has not yet started');
123+
} else {
124+
$problem = $formPaste->get('problem')->getData();
125+
$language = $formPaste->get('language')->getData();
126+
$codeContent = $formPaste->get('code_content')->getData();
127+
if($codeContent == null || empty(trim($codeContent))) {
128+
$this->addFlash('danger','No code content provided.');
129+
return $this->redirectToRoute('team_index');
130+
}
131+
$tempDir = sys_get_temp_dir();
132+
$tempFileName = sprintf(
133+
'submission_%s_%s_%s.%s',
134+
$user->getUsername(),
135+
$problem->getName(),
136+
date('Y-m-d_H-i-s'),
137+
$language->getExtensions()[0]
138+
);
139+
$tempFileName = preg_replace('/[^a-zA-Z0-9_.-]/', '_', $tempFileName);
140+
$tempFilePath = $tempDir . DIRECTORY_SEPARATOR . $tempFileName;
141+
file_put_contents($tempFilePath, $codeContent);
142+
143+
$uploadedFile = new UploadedFile(
144+
$tempFilePath,
145+
$tempFileName,
146+
'application/octet-stream',
147+
null,
148+
true
149+
);
150+
151+
$files = [$uploadedFile];
152+
$entryPoint = $tempFileName;
153+
$submission = $this->submissionService->submitSolution(
154+
$team,
155+
$this->dj->getUser(),
156+
$problem,
157+
$contest,
158+
$language,
159+
$files,
160+
'team page',
161+
null,
162+
null,
163+
$entryPoint,
164+
null,
165+
null,
166+
$message
167+
);
168+
if ($submission) {
169+
$this->addFlash(
170+
'success',
171+
'Submission done! Watch for the verdict in the list below.'
172+
);
173+
} else {
174+
$this->addFlash('danger', $message);
175+
}
176+
99177
return $this->redirectToRoute('team_index');
100178
}
101179
}
102180

103-
$data = ['form' => $form->createView(), 'problem' => $problem];
181+
$data = [
182+
'formupload' => $formUpload->createView(),
183+
'formpaste' => $formPaste->createView(),
184+
'problem' => $problem,
185+
'defaultSubmissionCodeMode' => (bool) $this->config->get('default_submission_code_mode'),
186+
];
104187
$data['validFilenameRegex'] = SubmissionService::FILENAME_REGEX;
105188

106189
if ($request->isXmlHttpRequest()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\Form\Type;
4+
5+
use App\Entity\Language;
6+
use App\Entity\Problem;
7+
use App\Service\ConfigurationService;
8+
use App\Service\DOMJudgeService;
9+
use Doctrine\ORM\EntityManagerInterface;
10+
use Doctrine\ORM\EntityRepository;
11+
use Doctrine\ORM\Query\Expr\Join;
12+
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
13+
use Symfony\Component\Form\AbstractType;
14+
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
15+
use Symfony\Component\Form\Extension\Core\Type\TextType;
16+
use Symfony\Component\Form\Form;
17+
use Symfony\Component\Form\FormBuilderInterface;
18+
use Symfony\Component\Form\FormEvent;
19+
use Symfony\Component\Form\FormEvents;
20+
use Symfony\Component\Validator\Constraints\Callback;
21+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
22+
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
23+
24+
class SubmitProblemPasteType extends AbstractType
25+
{
26+
public function __construct(
27+
protected readonly DOMJudgeService $dj,
28+
protected readonly ConfigurationService $config,
29+
protected readonly EntityManagerInterface $em
30+
) {
31+
}
32+
33+
public function buildForm(FormBuilderInterface $builder, array $options): void
34+
{
35+
$user = $this->dj->getUser();
36+
$contest = $this->dj->getCurrentContest($user->getTeam()->getTeamid());
37+
38+
$builder->add('code_content', HiddenType::class, [
39+
'required' => true,
40+
]);
41+
$problemConfig = [
42+
'class' => Problem::class,
43+
'query_builder' => fn(EntityRepository $er) => $er->createQueryBuilder('p')
44+
->join('p.contest_problems', 'cp', Join::WITH, 'cp.contest = :contest')
45+
->select('p', 'cp')
46+
->andWhere('cp.allowSubmit = 1')
47+
->setParameter('contest', $contest)
48+
->addOrderBy('cp.shortname'),
49+
'choice_label' => fn(Problem $problem) => sprintf(
50+
'%s - %s',
51+
$problem->getContestProblems()->first()->getShortName(),
52+
$problem->getName()
53+
),
54+
'placeholder' => 'Select a problem',
55+
];
56+
$builder->add('problem', EntityType::class, $problemConfig);
57+
58+
$builder->add('language', EntityType::class, [
59+
'class' => Language::class,
60+
'query_builder' => fn(EntityRepository $er) => $er
61+
->createQueryBuilder('l')
62+
->andWhere('l.allowSubmit = 1'),
63+
'choice_label' => 'name',
64+
'placeholder' => 'Select a language',
65+
]);
66+
67+
$builder->add('entry_point', TextType::class, [
68+
'label' => 'Entry point',
69+
'required' => false,
70+
'help' => 'The entry point for your code.',
71+
'row_attr' => ['data-entry-point' => '']
72+
]);
73+
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($problemConfig) {
74+
$data = $event->getData();
75+
if (isset($data['problem'])) {
76+
$problemConfig += ['row_attr' => ['class' => 'd-none']];
77+
$event->getForm()->add('problem', EntityType::class, $problemConfig);
78+
}
79+
});
80+
}
81+
}

webapp/templates/base.html.twig

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
<script src="{{ asset("js/bootstrap.bundle.min.js") }}"></script>
1515

1616
<script src="{{ asset("js/domjudge.js") }}"></script>
17+
<script src="{{ asset('js/ace/ace.js') }}"></script>
18+
1719
{% for file in customAssetFiles('js') %}
1820
<script src="{{ asset('js/custom/' ~ file) }}"></script>
1921
{% endfor %}

webapp/templates/team/submit_modal.html.twig

+83-13
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,64 @@
1919
{% include 'partials/alert.html.twig' with {'type': 'danger', 'message': 'Submissions (temporarily) disabled.'} %}
2020
</div>
2121
{% else %}
22-
{{ form_start(form) }}
22+
{% set active_tab = defaultSubmissionCodeMode == 0 ? 'paste' : 'upload' %}
23+
24+
<!-- Bootstrap Nav Tabs for Switching -->
2325
<div class="modal-body">
24-
{{ form_row(form.code) }}
25-
<div class="alert d-none" id="files_selected"></div>
26-
{{ form_row(form.problem) }}
27-
{{ form_row(form.language) }}
28-
{{ form_row(form.entry_point) }}
29-
</div>
30-
<div class="modal-footer">
31-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
32-
<button type="submit" class="btn-success btn">
33-
<i class="fas fa-cloud-upload-alt"></i> Submit
34-
</button>
26+
<ul class="nav nav-tabs container text-center" id="submissionTabs" role="tablist" style="width: 100%">
27+
<li class="nav-item" role="presentation">
28+
<a class="nav-link {% if active_tab == 'upload' %}active{% endif %}" id="upload-tab" data-bs-toggle="tab" href="#upload" role="tab" aria-controls="upload" aria-selected="{% if active_tab == 'upload' %}true{% else %}false{% endif %}">Upload File</a>
29+
</li>
30+
<li class="nav-item text-center" role="presentation">
31+
<a class="nav-link {% if active_tab == 'paste' %}active{% endif %}" id="paste-tab" data-bs-toggle="tab" href="#paste" role="tab" aria-controls="paste" aria-selected="{% if active_tab == 'paste' %}true{% else %}false{% endif %}">Paste Code</a>
32+
</li>
33+
</ul>
34+
<div class="tab-content" id="submissionTabsContent" style="margin-top: 20px;">
35+
<!-- File Upload Tab -->
36+
<div class="tab-pane fade {% if active_tab == 'upload' %}show active{% endif %}" id="upload" role="tabpanel" aria-labelledby="upload-tab">
37+
{{ form_start(formupload) }}
38+
{{ form_row(formupload.code) }}
39+
<div class="alert d-none" id="files_selected"></div>
40+
{{ form_row(formupload.problem) }}
41+
{{ form_row(formupload.language) }}
42+
{{ form_row(formupload.entry_point) }}
43+
<div class="modal-footer">
44+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
45+
<button type="submit" class="btn btn-success">
46+
<i class="fas fa-cloud-upload-alt"></i> Submit Upload
47+
</button>
48+
</div>
49+
{{ form_end(formupload) }}
50+
</div>
51+
52+
<!-- Paste Code Tab -->
53+
<div class="tab-pane fade {% if active_tab == 'paste' %}show active{% endif %}" id="paste" role="tabpanel" aria-labelledby="paste-tab">
54+
{{ form_start(formpaste) }}
55+
{{ form_widget(formpaste.code_content) }}
56+
<label for="codeInput">Paste your code here:</label>
57+
<div class="editor-container">
58+
{{ "" | codeEditor(
59+
"_team_submission_code",
60+
"c_cpp",
61+
true,
62+
formpaste.code_content.vars.id,
63+
null,
64+
formpaste.language.vars.value
65+
) }}
66+
</div>
67+
{{ form_row(formpaste.problem) }}
68+
{{ form_row(formpaste.language) }}
69+
{{ form_row(formpaste.entry_point) }}
70+
<div class="modal-footer">
71+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
72+
<button type="submit" class="btn btn-primary">
73+
<i class="fas fa-paste"></i> Submit Paste
74+
</button>
75+
</div>
76+
{{ form_end(formpaste) }}
77+
</div>
78+
</div>
3579
</div>
36-
{{ form_end(form) }}
3780
{% endif %}
3881
</div>
3982
</div>
@@ -80,4 +123,31 @@
80123
filesSelected.removeClass('d-none');
81124
});
82125
</script>
126+
<style>
127+
.container {
128+
display: grid;
129+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
130+
gap: 10px;
131+
}
132+
133+
.text-center {
134+
text-align: center;
135+
}
136+
137+
.editor-container {
138+
border: 1px solid #ddd;
139+
border-radius: 4px;
140+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
141+
padding: 10px;
142+
margin-top: 10px;
143+
margin-bottom: 10px;
144+
background-color: #fafafa;
145+
max-height: 400px;
146+
overflow: auto;
147+
}
148+
149+
.editor-container:hover {
150+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
151+
}
152+
</style>
83153
</div>

0 commit comments

Comments
 (0)