Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/8.3.1.1_paste_code' into paste_code
Browse files Browse the repository at this point in the history
  • Loading branch information
as6325400 committed Sep 23, 2024
2 parents 6a70aa0 + bb8e1e5 commit 6dcfb05
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/autoconf-check-different-distro.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
image: ${{ matrix.os }}:${{ matrix.version }}
steps:
- name: Install git so we get the .github directory
run: dnf install -y git
run: dnf install -y git composer
- uses: actions/checkout@v4
- name: Setup image and run bats tests
run: .github/jobs/configure-checks/setup_configure_image.sh
2 changes: 1 addition & 1 deletion .github/workflows/autoconf-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
image: ${{ matrix.os }}:${{ matrix.version }}
steps:
- name: Install git so we get the .github directory
run: apt-get update; apt-get install -y git
run: apt-get update; apt-get install -y git composer
- uses: actions/checkout@v4
- name: Setup image and run bats tests
run: .github/jobs/configure-checks/setup_configure_image.sh
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
2 changes: 2 additions & 0 deletions lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ install-domserver:
install-judgehost:
$(INSTALL_DATA) -t $(DESTDIR)$(judgehost_libdir) *.php *.sh
$(INSTALL_PROG) -t $(DESTDIR)$(judgehost_libdir) alert

domserver: SUBDIRS=vendor
12 changes: 12 additions & 0 deletions lib/vendor/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ifndef TOPDIR
TOPDIR=../..
endif
include $(TOPDIR)/Makefile.global

clean-l:
rm -f autoload_runtime.php

autoload_runtime.php:
composer $(subst 1,-q,$(QUIET)) dump-autoload -o -a -d $(TOPDIR)

domserver: autoload_runtime.php
103 changes: 93 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\EventLogService;
Expand Down Expand Up @@ -60,32 +61,48 @@ 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) {
Expand All @@ -96,11 +113,77 @@ public function createAction(Request $request, ?Problem $problem = null): Respon
} 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 = $tempFileName;
$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',
'Submission done! Watch for the verdict in the list below.'
);
} 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
81 changes: 81 additions & 0 deletions webapp/src/Form/Type/SubmitProblemPasteType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?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' => '']
]);
$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>

0 comments on commit 6dcfb05

Please sign in to comment.