Skip to content

add Paste code to submit feature #2711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions etc/db-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@
- category: Display
description: Options related to the DOMjudge user interface.
items:
- name: default_submission_code_mode
type: int
default_value: 0
public: true
description: Select the default submission method for the team
options:
0: Paste
1: Upload
- name: output_display_limit
type: int
default_value: 2000
Expand Down
104 changes: 94 additions & 10 deletions webapp/src/Controller/Team/SubmissionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use App\Entity\Submission;
use App\Entity\Testcase;
use App\Form\Type\SubmitProblemType;
use App\Form\Type\SubmitProblemPasteType;
use App\Service\ConfigurationService;
use App\Service\DOMJudgeService;
use App\Service\SubmissionService;
Expand Down Expand Up @@ -54,34 +55,111 @@ public function createAction(Request $request, ?Problem $problem = null): Respon
if ($problem !== null) {
$data['problem'] = $problem;
}
$form = $this->formFactory
$formUpload = $this->formFactory
->createBuilder(SubmitProblemType::class, $data)
->setAction($this->generateUrl('team_submit'))
->getForm();

$form->handleRequest($request);
$formPaste = $this->formFactory
->createBuilder(SubmitProblemPasteType::class, $data)
->setAction($this->generateUrl('team_submit'))
->getForm();

if ($form->isSubmitted() && $form->isValid()) {
$formUpload->handleRequest($request);
$formPaste->handleRequest($request);

if ($formUpload->isSubmitted() && $formUpload->isValid()) {
if ($contest === null) {
$this->addFlash('danger', 'No active contest');
} elseif (!$this->dj->checkrole('jury') && !$contest->getFreezeData()->started()) {
$this->addFlash('danger', 'Contest has not yet started');
} else {
/** @var Problem $problem */
$problem = $form->get('problem')->getData();
$problem = $formUpload->get('problem')->getData();
/** @var Language $language */
$language = $form->get('language')->getData();
$language = $formUpload->get('language')->getData();
/** @var UploadedFile[] $files */
$files = $form->get('code')->getData();
$files = $formUpload->get('code')->getData();
if (!is_array($files)) {
$files = [$files];
}
$entryPoint = $form->get('entry_point')->getData() ?: null;
$entryPoint = $formUpload->get('entry_point')->getData() ?: null;
$submission = $this->submissionService->submitSolution(
$team, $this->dj->getUser(), $problem->getProbid(), $contest, $language, $files, 'team page', null,
null, $entryPoint, null, null, $message
$team,
$this->dj->getUser(),
$problem->getProbid(),
$contest,
$language,
$files,
'team page',
null,
null,
$entryPoint,
null,
null,
$message
);

if ($submission) {
$this->addFlash(
'success',
'Submission done! Watch for the verdict in the list below.'
);
} else {
$this->addFlash('danger', $message);
}
return $this->redirectToRoute('team_index');
}
} elseif ($formPaste->isSubmitted() && $formPaste->isValid()) {
if ($contest === null) {
$this->addFlash('danger', 'No active contest');
} elseif (!$this->dj->checkrole('jury') && !$contest->getFreezeData()->started()) {
$this->addFlash('danger', 'Contest has not yet started');
} else {
$problem = $formPaste->get('problem')->getData();
$language = $formPaste->get('language')->getData();
$codeContent = $formPaste->get('code_content')->getData();
if($codeContent == null || empty(trim($codeContent))) {
$this->addFlash('danger','No code content provided.');
return $this->redirectToRoute('team_index');
}
$tempDir = sys_get_temp_dir();
$tempFileName = sprintf(
'submission_%s_%s_%s.%s',
$user->getUsername(),
$problem->getName(),
date('Y-m-d_H-i-s'),
$language->getExtensions()[0]
);
$tempFileName = preg_replace('/[^a-zA-Z0-9_.-]/', '_', $tempFileName);
$tempFilePath = $tempDir . DIRECTORY_SEPARATOR . $tempFileName;
file_put_contents($tempFilePath, $codeContent);

$uploadedFile = new UploadedFile(
$tempFilePath,
$tempFileName,
'application/octet-stream',
null,
true
);

$files = [$uploadedFile];
$entryPoint = $formPaste->get('entry_point')->getData() ?: null;
$submission = $this->submissionService->submitSolution(
$team,
$this->dj->getUser(),
$problem,
$contest,
$language,
$files,
'team page',
null,
null,
$entryPoint,
null,
null,
$message
);
if ($submission) {
$this->addFlash(
'success',
Expand All @@ -90,11 +168,17 @@ public function createAction(Request $request, ?Problem $problem = null): Respon
} else {
$this->addFlash('danger', $message);
}

return $this->redirectToRoute('team_index');
}
}

$data = ['form' => $form->createView(), 'problem' => $problem];
$data = [
'formupload' => $formUpload->createView(),
'formpaste' => $formPaste->createView(),
'problem' => $problem,
'defaultSubmissionCodeMode' => (bool) $this->config->get('default_submission_code_mode'),
];
$data['validFilenameRegex'] = SubmissionService::FILENAME_REGEX;

if ($request->isXmlHttpRequest()) {
Expand Down
99 changes: 99 additions & 0 deletions webapp/src/Form/Type/SubmitProblemPasteType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php declare(strict_types=1);

namespace App\Form\Type;

use App\Entity\Language;
use App\Entity\Problem;
use App\Service\ConfigurationService;
use App\Service\DOMJudgeService;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;

class SubmitProblemPasteType extends AbstractType
{
public function __construct(
protected readonly DOMJudgeService $dj,
protected readonly ConfigurationService $config,
protected readonly EntityManagerInterface $em
) {
}

public function buildForm(FormBuilderInterface $builder, array $options): void
{
$user = $this->dj->getUser();
$contest = $this->dj->getCurrentContest($user->getTeam()->getTeamid());

$builder->add('code_content', HiddenType::class, [
'required' => true,
]);
$problemConfig = [
'class' => Problem::class,
'query_builder' => fn(EntityRepository $er) => $er->createQueryBuilder('p')
->join('p.contest_problems', 'cp', Join::WITH, 'cp.contest = :contest')
->select('p', 'cp')
->andWhere('cp.allowSubmit = 1')
->setParameter('contest', $contest)
->addOrderBy('cp.shortname'),
'choice_label' => fn(Problem $problem) => sprintf(
'%s - %s',
$problem->getContestProblems()->first()->getShortName(),
$problem->getName()
),
'placeholder' => 'Select a problem',
];
$builder->add('problem', EntityType::class, $problemConfig);

$builder->add('language', EntityType::class, [
'class' => Language::class,
'query_builder' => fn(EntityRepository $er) => $er
->createQueryBuilder('l')
->andWhere('l.allowSubmit = 1'),
'choice_label' => 'name',
'placeholder' => 'Select a language',
]);

$builder->add('entry_point', TextType::class, [
'label' => 'Entry point',
'required' => false,
'help' => 'The entry point for your code.',
'row_attr' => ['data-entry-point' => ''],
'constraints' => [
new Callback(function ($value, ExecutionContextInterface $context) {
/** @var Form $form */
$form = $context->getRoot();
/** @var Language $language */
$language = $form->get('language')->getData();
if ($language && $language->getRequireEntryPoint() && empty($value)) {
$message = sprintf(
'%s required, but not specified',
$language->getEntryPointDescription() ?: 'Entry point'
);
$context
->buildViolation($message)
->atPath('entry_point')
->addViolation();
}
}),
]
]);
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($problemConfig) {
$data = $event->getData();
if (isset($data['problem'])) {
$problemConfig += ['row_attr' => ['class' => 'd-none']];
$event->getForm()->add('problem', EntityType::class, $problemConfig);
}
});
}
}
2 changes: 2 additions & 0 deletions webapp/templates/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<script src="{{ asset("js/bootstrap.bundle.min.js") }}"></script>

<script src="{{ asset("js/domjudge.js") }}"></script>
<script src="{{ asset('js/ace/ace.js') }}"></script>

{% for file in customAssetFiles('js') %}
<script src="{{ asset('js/custom/' ~ file) }}"></script>
{% endfor %}
Expand Down
96 changes: 83 additions & 13 deletions webapp/templates/team/submit_modal.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,64 @@
{% include 'partials/alert.html.twig' with {'type': 'danger', 'message': 'Submissions (temporarily) disabled.'} %}
</div>
{% else %}
{{ form_start(form) }}
{% set active_tab = defaultSubmissionCodeMode == 0 ? 'paste' : 'upload' %}

<!-- Bootstrap Nav Tabs for Switching -->
<div class="modal-body">
{{ form_row(form.code) }}
<div class="alert d-none" id="files_selected"></div>
{{ form_row(form.problem) }}
{{ form_row(form.language) }}
{{ form_row(form.entry_point) }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn-success btn">
<i class="fas fa-cloud-upload-alt"></i> Submit
</button>
<ul class="nav nav-tabs container text-center" id="submissionTabs" role="tablist" style="width: 100%">
<li class="nav-item" role="presentation">
<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>
</li>
<li class="nav-item text-center" role="presentation">
<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>
</li>
</ul>
<div class="tab-content" id="submissionTabsContent" style="margin-top: 20px;">
<!-- File Upload Tab -->
<div class="tab-pane fade {% if active_tab == 'upload' %}show active{% endif %}" id="upload" role="tabpanel" aria-labelledby="upload-tab">
{{ form_start(formupload) }}
{{ form_row(formupload.code) }}
<div class="alert d-none" id="files_selected"></div>
{{ form_row(formupload.problem) }}
{{ form_row(formupload.language) }}
{{ form_row(formupload.entry_point) }}
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-success">
<i class="fas fa-cloud-upload-alt"></i> Submit Upload
</button>
</div>
{{ form_end(formupload) }}
</div>

<!-- Paste Code Tab -->
<div class="tab-pane fade {% if active_tab == 'paste' %}show active{% endif %}" id="paste" role="tabpanel" aria-labelledby="paste-tab">
{{ form_start(formpaste) }}
{{ form_widget(formpaste.code_content) }}
<label for="codeInput">Paste your code here:</label>
<div class="editor-container">
{{ "" | codeEditor(
"_team_submission_code",
"c_cpp",
true,
formpaste.code_content.vars.id,
null,
formpaste.language.vars.value
) }}
</div>
{{ form_row(formpaste.problem) }}
{{ form_row(formpaste.language) }}
{{ form_row(formpaste.entry_point) }}
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-paste"></i> Submit Paste
</button>
</div>
{{ form_end(formpaste) }}
</div>
</div>
</div>
{{ form_end(form) }}
{% endif %}
</div>
</div>
Expand Down Expand Up @@ -80,4 +123,31 @@
filesSelected.removeClass('d-none');
});
</script>
<style>
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
}

.text-center {
text-align: center;
}

.editor-container {
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 10px;
margin-top: 10px;
margin-bottom: 10px;
background-color: #fafafa;
max-height: 400px;
overflow: auto;
}

.editor-container:hover {
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
</style>
</div>