From bbe9787d02c2fc41d1d907f4d8d5780d2fb26163 Mon Sep 17 00:00:00 2001 From: Nicky Gerritsen Date: Sun, 7 May 2023 18:35:12 +0200 Subject: [PATCH] Auto calculate num actions for jury tables. --- .../src/Controller/Jury/ContestController.php | 1 - .../Controller/Jury/ExecutableController.php | 99 ++---- .../Controller/Jury/JudgehostController.php | 1 - .../Controller/Jury/LanguageController.php | 1 - .../src/Controller/Jury/ProblemController.php | 1 - .../Jury/TeamAffiliationController.php | 1 - .../Jury/TeamCategoryController.php | 1 - webapp/src/Controller/Jury/TeamController.php | 1 - webapp/src/Controller/Jury/UserController.php | 1 - webapp/src/Twig/TwigExtension.php | 294 ++++++++---------- webapp/templates/jury/contests.html.twig | 2 +- webapp/templates/jury/executables.html.twig | 2 +- webapp/templates/jury/jury_macros.twig | 3 +- webapp/templates/jury/languages.html.twig | 4 +- .../jury/partials/judgehost_list.html.twig | 2 +- webapp/templates/jury/problems.html.twig | 2 +- .../jury/team_affiliations.html.twig | 2 +- .../templates/jury/team_categories.html.twig | 2 +- webapp/templates/jury/teams.html.twig | 2 +- webapp/templates/jury/users.html.twig | 2 +- 20 files changed, 171 insertions(+), 253 deletions(-) diff --git a/webapp/src/Controller/Jury/ContestController.php b/webapp/src/Controller/Jury/ContestController.php index 0bfe46e49b8..987d05e18e3 100644 --- a/webapp/src/Controller/Jury/ContestController.php +++ b/webapp/src/Controller/Jury/ContestController.php @@ -379,7 +379,6 @@ public function indexAction(Request $request): Response 'upcoming_contest' => $upcomingContest, 'contests_table' => $contests_table, 'table_fields' => $table_fields, - 'num_actions' => $this->isGranted('ROLE_ADMIN') && !$contest->isLocked() ? 2 : 0, ]); } diff --git a/webapp/src/Controller/Jury/ExecutableController.php b/webapp/src/Controller/Jury/ExecutableController.php index f80f732a655..6c6261a6ca9 100644 --- a/webapp/src/Controller/Jury/ExecutableController.php +++ b/webapp/src/Controller/Jury/ExecutableController.php @@ -6,16 +6,13 @@ use App\Entity\Executable; use App\Entity\ExecutableFile; use App\Entity\ImmutableExecutable; -use App\Entity\Role; -use App\Form\Type\ExecutableType; use App\Form\Type\ExecutableUploadType; use App\Service\ConfigurationService; use App\Service\DOMJudgeService; use App\Service\EventLogService; use App\Utils\Utils; use Doctrine\ORM\EntityManagerInterface; -use FOS\RestBundle\Controller\Annotations as Rest; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; +use Symfony\Component\Security\Http\Attribute\IsGranted; use Symfony\Component\Form\Exception\InvalidArgumentException; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -29,35 +26,19 @@ use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route("/jury/executables") - * @IsGranted("ROLE_JURY") - */ +#[IsGranted('ROLE_JURY')] +#[Route(path: '/jury/executables')] class ExecutableController extends BaseController { - protected EntityManagerInterface $em; - protected DOMJudgeService $dj; - protected ConfigurationService $config; - protected KernelInterface $kernel; - protected EventLogService $eventLogService; - public function __construct( - EntityManagerInterface $em, - DOMJudgeService $dj, - ConfigurationService $config, - KernelInterface $kernel, - EventLogService $eventLogService - ) { - $this->em = $em; - $this->dj = $dj; - $this->config = $config; - $this->kernel = $kernel; - $this->eventLogService = $eventLogService; - } - - /** - * @Route("", name="jury_executables") - */ + protected readonly EntityManagerInterface $em, + protected readonly DOMJudgeService $dj, + protected readonly ConfigurationService $config, + protected readonly KernelInterface $kernel, + protected readonly EventLogService $eventLogService + ) {} + + #[Route(path: '', name: 'jury_executables')] public function indexAction(Request $request): Response { $data = []; @@ -116,15 +97,12 @@ public function indexAction(Request $request): Response return $this->render('jury/executables.html.twig', [ 'executables' => $executables_table, 'table_fields' => $table_fields, - 'num_actions' => count($execactions), - 'form' => $form->createView(), + 'form' => $form, ]); } - /** - * @Route("/add", name="jury_executable_add") - * @IsGranted("ROLE_ADMIN") - */ + #[IsGranted('ROLE_ADMIN')] + #[Route(path: '/add', name: 'jury_executable_add')] public function addAction(Request $request): Response { $data = []; @@ -141,7 +119,7 @@ public function addAction(Request $request): Response $zip = $this->dj->openZipFile($archive->getRealPath()); $filename = $archive->getClientOriginalName(); $id = substr($filename, 0, strlen($filename) - strlen(".zip")); - if (! preg_match ('#^[a-z0-9_-]+$#i', $id)) { + if (! preg_match('#^[a-z0-9_-]+$#i', $id)) { throw new InvalidArgumentException(sprintf("File base name '%s' must contain only alphanumerics", $id)); } $description = $id; @@ -183,13 +161,11 @@ public function addAction(Request $request): Response } return $this->render('jury/executable_add.html.twig', [ - 'form' => $form->createView(), + 'form' => $form, ]); } - /** - * @Route("/{execId}", name="jury_executable") - */ + #[Route(path: '/{execId}', name: 'jury_executable')] public function viewAction(Request $request, string $execId): Response { /** @var Executable $executable */ @@ -227,7 +203,7 @@ public function viewAction(Request $request, string $execId): Response $files = []; foreach ($editorData['filenames'] as $idx => $filename) { $newContent = str_replace("\r\n", "\n", $submittedData['source' . $idx]); - if (substr($newContent, -1) != "\n") { + if (!str_ends_with($newContent, "\n")) { // Ace swallows the newline at the end of file. Let's re-add it like most editors do. $newContent .= "\n"; } @@ -241,18 +217,6 @@ public function viewAction(Request $request, string $execId): Response $this->em->persist($executableFile); $files[] = $executableFile; } - $offset = count($files); - foreach ($editorData['skippedBinary'] as $idx => $skippedBinaryData) { - $origExecutableFile = $this->em->getRepository(ExecutableFile::class)->find($skippedBinaryData['execfileid']); - $executableFile = new ExecutableFile(); - $executableFile - ->setRank($idx + $offset) - ->setIsExecutable($origExecutableFile->isExecutable()) - ->setFilename($origExecutableFile->getFilename()) - ->setFileContent($origExecutableFile->getFileContent()); - $this->em->persist($executableFile); - $files[] = $executableFile; - } $immutableExecutable = new ImmutableExecutable($files); $this->em->persist($immutableExecutable); @@ -301,9 +265,7 @@ public function viewAction(Request $request, string $execId): Response ])); } - /** - * @Route("/{execId}/download", name="jury_executable_download") - */ + #[Route(path: '/{execId}/download', name: 'jury_executable_download')] public function downloadAction(string $execId): Response { /** @var Executable $executable */ @@ -318,10 +280,8 @@ public function downloadAction(string $execId): Response return Utils::streamAsBinaryFile($zipFileContent, $filename, 'zip'); } - /** - * @Route("/{execId}/delete/{rankToDelete}", name="jury_executable_delete_single") - * @IsGranted("ROLE_ADMIN") - */ + #[IsGranted('ROLE_ADMIN')] + #[Route(path: '/{execId}/delete/{rankToDelete}', name: 'jury_executable_delete_single')] public function deleteSingleAction(Request $request, string $execId, int $rankToDelete): Response { /** @var Executable $executable */ @@ -388,9 +348,7 @@ public function deleteSingleAction(Request $request, string $execId, int $rankTo } } - /** - * @Route("/{execId}/download/{rank}", name="jury_executable_download_single") - */ + #[Route(path: '/{execId}/download/{rank}', name: 'jury_executable_download_single')] public function downloadSingleAction(string $execId, int $rank): Response { /** @var Executable $executable */ @@ -410,10 +368,8 @@ public function downloadSingleAction(string $execId, int $rank): Response throw new NotFoundHttpException(sprintf('No file with rank %d found.', $rank)); } - /** - * @Route("/{execId}/delete", name="jury_executable_delete") - * @IsGranted("ROLE_ADMIN") - */ + #[IsGranted('ROLE_ADMIN')] + #[Route(path: '/{execId}/delete', name: 'jury_executable_delete')] public function deleteAction(Request $request, string $execId): Response { /** @var Executable $executable */ @@ -448,10 +404,7 @@ protected function dataForEditor(Executable $executable): array $content = $file->getFileContent(); $rank = $file->getRank(); if (!mb_detect_encoding($content, null, true)) { - $skippedBinary[] = [ - 'filename' => $filename, - 'execfileid' => $file->getExecFileId(), - ]; + $skippedBinary[] = $filename; continue; // Skip binary files. } $filenames[] = $filename; @@ -474,7 +427,7 @@ protected function dataForEditor(Executable $executable): array private function getAceFilename(string $filename, string $content): string { - if (strpos($filename, '.') === false) { + if (!str_contains($filename, '.')) { // If the file does not contain a dot, see if we have a shebang which we can use as filename. // We do this to hint the ACE editor to use a specific language. [$firstLine] = explode("\n", $content, 2); diff --git a/webapp/src/Controller/Jury/JudgehostController.php b/webapp/src/Controller/Jury/JudgehostController.php index d4b0cdc807c..afd7a024bc4 100644 --- a/webapp/src/Controller/Jury/JudgehostController.php +++ b/webapp/src/Controller/Jury/JudgehostController.php @@ -184,7 +184,6 @@ public function indexAction(Request $request): Response $data = [ 'judgehosts' => $judgehosts_table, 'table_fields' => $table_fields, - 'num_actions' => $this->isGranted('ROLE_ADMIN') ? 2 : 0, 'all_checked_in_recently' => $all_checked_in_recently, 'refresh' => [ 'after' => 5, diff --git a/webapp/src/Controller/Jury/LanguageController.php b/webapp/src/Controller/Jury/LanguageController.php index f5f6c1683ce..fb3c5e0020e 100644 --- a/webapp/src/Controller/Jury/LanguageController.php +++ b/webapp/src/Controller/Jury/LanguageController.php @@ -146,7 +146,6 @@ public function indexAction(): Response 'enabled_languages' => $enabled_languages, 'disabled_languages' => $disabled_languages, 'table_fields' => $table_fields, - 'num_actions' => $this->isGranted('ROLE_ADMIN') ? 2 : 0, ]); } diff --git a/webapp/src/Controller/Jury/ProblemController.php b/webapp/src/Controller/Jury/ProblemController.php index 536c6fb928c..62502bd74b3 100644 --- a/webapp/src/Controller/Jury/ProblemController.php +++ b/webapp/src/Controller/Jury/ProblemController.php @@ -214,7 +214,6 @@ public function indexAction(): Response $data = [ 'problems' => $problems_table, 'table_fields' => $table_fields, - 'num_actions' => $this->isGranted('ROLE_ADMIN') ? 4 : 2, ]; return $this->render('jury/problems.html.twig', $data); diff --git a/webapp/src/Controller/Jury/TeamAffiliationController.php b/webapp/src/Controller/Jury/TeamAffiliationController.php index 62c12c57501..02436c76d5f 100644 --- a/webapp/src/Controller/Jury/TeamAffiliationController.php +++ b/webapp/src/Controller/Jury/TeamAffiliationController.php @@ -143,7 +143,6 @@ public function indexAction(string $projectDir): Response return $this->render('jury/team_affiliations.html.twig', [ 'team_affiliations' => $team_affiliations_table, 'table_fields' => $table_fields, - 'num_actions' => $this->isGranted('ROLE_ADMIN') ? 2 : 0, ]); } diff --git a/webapp/src/Controller/Jury/TeamCategoryController.php b/webapp/src/Controller/Jury/TeamCategoryController.php index 9c0eecd9e6c..789c12db823 100644 --- a/webapp/src/Controller/Jury/TeamCategoryController.php +++ b/webapp/src/Controller/Jury/TeamCategoryController.php @@ -127,7 +127,6 @@ public function indexAction(): Response return $this->render('jury/team_categories.html.twig', [ 'team_categories' => $team_categories_table, 'table_fields' => $table_fields, - 'num_actions' => $this->isGranted('ROLE_ADMIN') ? 2 : 0, ]); } diff --git a/webapp/src/Controller/Jury/TeamController.php b/webapp/src/Controller/Jury/TeamController.php index f4056c460d0..500badb58e6 100644 --- a/webapp/src/Controller/Jury/TeamController.php +++ b/webapp/src/Controller/Jury/TeamController.php @@ -239,7 +239,6 @@ public function indexAction(): Response return $this->render('jury/teams.html.twig', [ 'teams' => $teams_table, 'table_fields' => $table_fields, - 'num_actions' => $this->isGranted('ROLE_ADMIN') ? 3 : 1, ]); } diff --git a/webapp/src/Controller/Jury/UserController.php b/webapp/src/Controller/Jury/UserController.php index 7aed63c520f..f69387ea696 100644 --- a/webapp/src/Controller/Jury/UserController.php +++ b/webapp/src/Controller/Jury/UserController.php @@ -184,7 +184,6 @@ public function indexAction(): Response return $this->render('jury/users.html.twig', [ 'users' => $users_table, 'table_fields' => $table_fields, - 'num_actions' => $this->isGranted('ROLE_ADMIN') ? 2 : 0, ]); } diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index 217b555ab4b..1225c2148c6 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -12,6 +12,8 @@ use App\Entity\Language; use App\Entity\Submission; use App\Entity\SubmissionFile; +use App\Entity\TeamCategory; +use App\Entity\Team; use App\Entity\Testcase; use App\Service\AwardService; use App\Service\ConfigurationService; @@ -22,6 +24,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use SebastianBergmann\Diff\Differ; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Intl\Countries; use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\PropertyAccess\PropertyAccess; @@ -35,99 +38,79 @@ class TwigExtension extends AbstractExtension implements GlobalsInterface { - protected DOMJudgeService $dj; - protected ConfigurationService $config; - protected Environment $twig; - protected EntityManagerInterface $em; - protected SubmissionService $submissionService; - protected EventLogService $eventLogService; - protected AwardService $awards; - protected TokenStorageInterface $tokenStorage; - protected AuthorizationCheckerInterface $authorizationChecker; - protected string $projectDir; - public function __construct( - DOMJudgeService $dj, - ConfigurationService $config, - Environment $twig, - EntityManagerInterface $em, - SubmissionService $submissionService, - EventLogService $eventLogService, - AwardService $awards, - TokenStorageInterface $tokenStorage, - AuthorizationCheckerInterface $authorizationChecker, - string $projectDir - ) { - $this->dj = $dj; - $this->config = $config; - $this->twig = $twig; - $this->em = $em; - $this->submissionService = $submissionService; - $this->eventLogService = $eventLogService; - $this->awards = $awards; - $this->tokenStorage = $tokenStorage; - $this->authorizationChecker = $authorizationChecker; - $this->projectDir = $projectDir; - } + protected readonly DOMJudgeService $dj, + protected readonly ConfigurationService $config, + protected readonly Environment $twig, + protected readonly EntityManagerInterface $em, + protected readonly SubmissionService $submissionService, + protected readonly EventLogService $eventLogService, + protected readonly AwardService $awards, + protected readonly TokenStorageInterface $tokenStorage, + protected readonly AuthorizationCheckerInterface $authorizationChecker, + #[Autowire('%kernel.project_dir%')] + protected readonly string $projectDir + ) {} public function getFunctions(): array { return [ - new TwigFunction('button', [$this, 'button'], ['is_safe' => ['html']]), - new TwigFunction('calculatePenaltyTime', [$this, 'calculatePenaltyTime']), - new TwigFunction('showExternalId', [$this, 'showExternalId']), - new TwigFunction('customAssetFiles', [$this, 'customAssetFiles']), - new TwigFunction('globalBannerAssetPath', [$this->dj, 'globalBannerAssetPath']), + new TwigFunction('button', $this->button(...), ['is_safe' => ['html']]), + new TwigFunction('calculatePenaltyTime', $this->calculatePenaltyTime(...)), + new TwigFunction('showExternalId', $this->showExternalId(...)), + new TwigFunction('customAssetFiles', $this->customAssetFiles(...)), + new TwigFunction('globalBannerAssetPath', $this->dj->globalBannerAssetPath(...)), ]; } public function getFilters(): array { return [ - new TwigFilter('printtimediff', [$this, 'printtimediff']), - new TwigFilter('printremainingminutes', [$this, 'printremainingminutes']), - new TwigFilter('printtime', [$this, 'printtime']), - new TwigFilter('printtimeHover', [$this, 'printtimeHover'], ['is_safe' => ['html']]), - new TwigFilter('printResult', [$this, 'printResult'], ['is_safe' => ['html']]), - new TwigFilter('printValidJuryResult', [$this, 'printValidJuryResult'], ['is_safe' => ['html']]), - new TwigFilter('printValidJurySubmissionResult', [$this, 'printValidJurySubmissionResult'], + new TwigFilter('printtimediff', $this->printtimediff(...)), + new TwigFilter('printremainingminutes', $this->printremainingminutes(...)), + new TwigFilter('printtime', $this->printtime(...)), + new TwigFilter('printtimeHover', $this->printtimeHover(...), ['is_safe' => ['html']]), + new TwigFilter('printResult', $this->printResult(...), ['is_safe' => ['html']]), + new TwigFilter('printValidJuryResult', $this->printValidJuryResult(...), ['is_safe' => ['html']]), + new TwigFilter('printValidJurySubmissionResult', $this->printValidJurySubmissionResult(...), ['is_safe' => ['html']]), - new TwigFilter('printHost', [$this, 'printHost'], ['is_safe' => ['html']]), - new TwigFilter('printHosts', [$this, 'printHosts'], ['is_safe' => ['html']]), - new TwigFilter('printFiles', [$this, 'printFiles'], ['is_safe' => ['html']]), - new TwigFilter('printLazyMode', [$this, 'printLazyMode']), - new TwigFilter('printYesNo', [$this, 'printYesNo']), - new TwigFilter('printSize', [Utils::class, 'printSize'], ['is_safe' => ['html']]), - new TwigFilter('testcaseResults', [$this, 'testcaseResults'], ['is_safe' => ['html']]), - new TwigFilter('displayTestcaseResults', [$this, 'displayTestcaseResults'], + new TwigFilter('printHost', $this->printHost(...), ['is_safe' => ['html']]), + new TwigFilter('printHosts', $this->printHosts(...), ['is_safe' => ['html']]), + new TwigFilter('printFiles', $this->printFiles(...), ['is_safe' => ['html']]), + new TwigFilter('printLazyMode', $this->printLazyMode(...)), + new TwigFilter('printYesNo', $this->printYesNo(...)), + new TwigFilter('printSize', Utils::printSize(...), ['is_safe' => ['html']]), + new TwigFilter('testcaseResults', $this->testcaseResults(...), ['is_safe' => ['html']]), + new TwigFilter('displayTestcaseResults', $this->displayTestcaseResults(...), ['is_safe' => ['html']]), - new TwigFilter('externalCcsUrl', [$this, 'externalCcsUrl']), - new TwigFilter('lineCount', [$this, 'lineCount']), + new TwigFilter('externalCcsUrl', $this->externalCcsUrl(...)), + new TwigFilter('lineCount', $this->lineCount(...)), new TwigFilter('base64', 'base64_encode'), new TwigFilter('base64_decode', 'base64_decode'), - new TwigFilter('parseRunDiff', [$this, 'parseRunDiff'], ['is_safe' => ['html']]), - new TwigFilter('runDiff', [$this, 'runDiff'], ['is_safe' => ['html']]), - new TwigFilter('interactiveLog', [$this, 'interactiveLog'], ['is_safe' => ['html']]), - new TwigFilter('codeEditor', [$this, 'codeEditor'], ['is_safe' => ['html']]), - new TwigFilter('showDiff', [$this, 'showDiff'], ['is_safe' => ['html']]), - new TwigFilter('printContestStart', [$this, 'printContestStart']), - new TwigFilter('assetPath', [$this->dj, 'assetPath']), - new TwigFilter('printTimeRelative', [$this, 'printTimeRelative']), - new TwigFilter('scoreTime', [$this, 'scoreTime']), - new TwigFilter('statusClass', [$this, 'statusClass']), - new TwigFilter('statusIcon', [$this, 'statusIcon'], ['is_safe' => ['html']]), - new TwigFilter('countryFlag', [$this, 'countryFlag'], ['is_safe' => ['html']]), - new TwigFilter('affiliationLogo', [$this, 'affiliationLogo'], ['is_safe' => ['html']]), - new TwigFilter('descriptionExpand', [$this, 'descriptionExpand'], ['is_safe' => ['html']]), - new TwigFilter('wrapUnquoted', [$this, 'wrapUnquoted']), - new TwigFilter('hexColorToRGBA', [$this, 'hexColorToRGBA']), - new TwigFilter('tsvField', [$this, 'toTsvField']), - new TwigFilter('fileTypeIcon', [$this, 'fileTypeIcon']), - new TwigFilter('problemBadge', [$this, 'problemBadge'], ['is_safe' => ['html']]), - new TwigFilter('printMetadata', [$this, 'printMetadata'], ['is_safe' => ['html']]), - new TwigFilter('printWarningContent', [$this, 'printWarningContent'], ['is_safe' => ['html']]), - new TwigFilter('entityIdBadge', [$this, 'entityIdBadge'], ['is_safe' => ['html']]), - new TwigFilter('medalType', [$this->awards, 'medalType']), + new TwigFilter('parseRunDiff', $this->parseRunDiff(...), ['is_safe' => ['html']]), + new TwigFilter('runDiff', $this->runDiff(...), ['is_safe' => ['html']]), + new TwigFilter('interactiveLog', $this->interactiveLog(...), ['is_safe' => ['html']]), + new TwigFilter('codeEditor', $this->codeEditor(...), ['is_safe' => ['html']]), + new TwigFilter('showDiff', $this->showDiff(...), ['is_safe' => ['html']]), + new TwigFilter('printContestStart', $this->printContestStart(...)), + new TwigFilter('assetPath', $this->dj->assetPath(...)), + new TwigFilter('printTimeRelative', $this->printTimeRelative(...)), + new TwigFilter('scoreTime', $this->scoreTime(...)), + new TwigFilter('statusClass', $this->statusClass(...)), + new TwigFilter('statusIcon', $this->statusIcon(...), ['is_safe' => ['html']]), + new TwigFilter('countryFlag', $this->countryFlag(...), ['is_safe' => ['html']]), + new TwigFilter('affiliationLogo', $this->affiliationLogo(...), ['is_safe' => ['html']]), + new TwigFilter('descriptionExpand', $this->descriptionExpand(...), ['is_safe' => ['html']]), + new TwigFilter('wrapUnquoted', $this->wrapUnquoted(...)), + new TwigFilter('hexColorToRGBA', $this->hexColorToRGBA(...)), + new TwigFilter('tsvField', $this->toTsvField(...)), + new TwigFilter('fileTypeIcon', $this->fileTypeIcon(...)), + new TwigFilter('problemBadge', $this->problemBadge(...), ['is_safe' => ['html']]), + new TwigFilter('printMetadata', $this->printMetadata(...), ['is_safe' => ['html']]), + new TwigFilter('printWarningContent', $this->printWarningContent(...), ['is_safe' => ['html']]), + new TwigFilter('entityIdBadge', $this->entityIdBadge(...), ['is_safe' => ['html']]), + new TwigFilter('medalType', $this->awards->medalType(...)), + new TwigFilter('numTableActions', $this->numTableActions(...)), ]; } @@ -137,15 +120,16 @@ public function getGlobals(): array $refresh_flag = ($refresh_cookie == null || (bool)$refresh_cookie); $user = $this->dj->getUser(); - $team = $user ? $user->getTeam() : null; + $team = $user?->getTeam(); + $selfRegistrationCategoriesCount = $this->em->getRepository(TeamCategory::class)->count(['allow_self_registration' => 1]); // These variables mostly exist for the header template. return [ 'current_contest_id' => $this->dj->getCurrentContestCookie(), 'current_contest' => $this->dj->getCurrentContest(), 'current_contests' => $this->dj->getCurrentContests(), - 'current_public_contest' => $this->dj->getCurrentContest(-1), - 'current_public_contests' => $this->dj->getCurrentContests(-1), + 'current_public_contest' => $this->dj->getCurrentContest(onlyPublic: true), + 'current_public_contests' => $this->dj->getCurrentContests(onlyPublic: true), 'have_printing' => $this->config->get('print_command'), 'refresh_flag' => $refresh_flag, 'icat_url' => $this->config->get('icat_url'), @@ -167,6 +151,7 @@ public function getGlobals(): array $this->authorizationChecker->isGranted('ROLE_ADMIN') && $this->config->get('data_source') === DOMJudgeService::DATA_SOURCE_CONFIGURATION_AND_LIVE_EXTERNAL, 'doc_links' => $this->dj->getDocLinks(), + 'allow_registration' => $selfRegistrationCategoriesCount !== 0, ]; } @@ -189,10 +174,9 @@ public function printremainingminutes(float $start, float $end): string /** * Print a time formatted as specified. The format is according to date(). - * @param string|float $datetime * @param Contest|null $contest If given, print time relative to that contest start. */ - public function printtime($datetime, ?string $format = null, ?Contest $contest = null): string + public function printtime(string|float|null $datetime, ?string $format = null, ?Contest $contest = null): string { if ($datetime === null) { $datetime = Utils::now(); @@ -230,10 +214,9 @@ public function printtime($datetime, ?string $format = null, ?Contest $contest = * Helper function to print a time in the default/configured format, * and a hover title attribute with the full datetime string. * - * @param string|float $datetime * @param Contest|null $contest If given, print time relative to that contest start. */ - public function printtimeHover($datetime, ?Contest $contest = null): string + public function printtimeHover(string|float $datetime, ?Contest $contest = null): string { if ($datetime === null) { $datetime = Utils::now(); @@ -286,17 +269,13 @@ public function button( public static function statusClass(string $status): string { - switch ($status) { - case 'noconn': - return 'text-muted'; - case 'crit': - return 'text-danger'; - case 'warn': - return 'text-warning'; - case 'ok': - return 'text-success'; - } - return ''; + return match ($status) { + 'noconn' => 'text-muted', + 'crit' => 'text-danger', + 'warn' => 'text-warning', + 'ok' => 'text-success', + default => '', + }; } public static function statusIcon(string $status): string @@ -329,7 +308,7 @@ public function countryFlag(?string $alpha3CountryCode, bool $showFullname = fal try { $countryAlpha2 = strtolower(Countries::getAlpha2Code($alpha3CountryCode)); - } catch (MissingResourceException $e) { + } catch (MissingResourceException) { return ''; } $assetFunction = $this->twig->getFunction('asset')->getCallable(); @@ -351,7 +330,7 @@ public function affiliationLogo(string $affiliationId, string $shortName): strin $assetFunction = $this->twig->getFunction('asset')->getCallable(); $assetUrl = call_user_func($assetFunction, $asset); return sprintf('', - Utils::specialchars($assetUrl), Utils::specialchars($shortName)); + htmlspecialchars($assetUrl), htmlspecialchars($shortName)); } return ''; @@ -362,8 +341,8 @@ public function testcaseResults(Submission $submission, ?bool $showExternal = fa // We use a direct SQL query here for performance reasons if ($showExternal) { /** @var ExternalJudgement|null $externalJudgement */ - $externalJudgement = $submission->getExternalJudgements()->first(); - $externalJudgementId = $externalJudgement ? $externalJudgement->getExtjudgementid() : null; + $externalJudgement = $submission->getExternalJudgements()->first() ?: null; + $externalJudgementId = $externalJudgement?->getExtjudgementid(); $probId = $submission->getProblem()->getProbid(); $testcases = $this->em->getConnection()->fetchAllAssociative( 'SELECT er.result as runresult, t.ranknumber, t.description @@ -375,7 +354,7 @@ public function testcaseResults(Submission $submission, ?bool $showExternal = fa $submissionDone = $externalJudgement && !empty($externalJudgement->getEndtime()); } else { - /** @var Judging|null $judging */ + /** @var Judging|bool $judging */ $judging = $submission->getJudgings()->first(); $judgingId = $judging ? $judging->getJudgingid() : null; $probId = $submission->getProblem()->getProbid(); @@ -413,12 +392,12 @@ public function testcaseResults(Submission $submission, ?bool $showExternal = fa if (!empty($testcase['description'])) { $title = sprintf('Run %d: %s', $key + 1, - Utils::specialchars($testcase['description'])); + htmlspecialchars($testcase['description'])); } else { $title = sprintf('Run %d', $key + 1); } - $results .= sprintf('%s', $class, $title, + $results .= sprintf('%s', $class, $title, $text); } @@ -443,9 +422,9 @@ public function displayTestcaseResults(array $testcases, bool $submissionDone, b /** @var JudgingRun $run */ $run = $isExternal ? $testcase->getFirstExternalRun() : $testcase->getFirstJudgingRun(); if ($isExternal) { - $runResult = $run ? $run->getResult() : null; + $runResult = $run?->getResult(); } else { - $runResult = $run ? $run->getRunresult() : null; + $runResult = $run?->getRunresult(); } if ($run) { @@ -477,7 +456,7 @@ public function displayTestcaseResults(array $testcases, bool $submissionDone, b $titleElements[] = sprintf('runtime: %ss', $run->getRuntime()); $titleElements[] = sprintf('result: %s', $runResult); } - $icon = sprintf('%s', $class, $text); + $icon = sprintf('%s', $class, $text); $results .= sprintf('%s', join(', ', $titleElements), $testcase->getRank(), $isCorrect ? 'onclick="display_correctruns(true);"' : '', $icon); @@ -591,10 +570,10 @@ public function printFiles(Collection $files): string } $firstFile = $files[0]->getFilename(); if (count($files) == 1) { - return sprintf('%s', Utils::specialchars($firstFile)); + return sprintf('%s', htmlspecialchars($firstFile)); } return sprintf('%s (and %d more)', - Utils::specialchars($firstFile), + htmlspecialchars($firstFile), count($files) - 1 ); } @@ -614,7 +593,7 @@ public function printHost(?string $hostname, bool $full = false): string $hostname = array_shift($expl); } - return sprintf('%s', Utils::specialchars($hostname)); + return sprintf('%s', htmlspecialchars($hostname)); } /** @@ -680,7 +659,7 @@ public function printHosts(array $hostnames): string if (empty($common_prefix) && empty($common_suffix)) { // No common prefix nor suffix: list all the names without "{}". - return implode(", ", array_map([$this, 'printHost'], $hostnames)); + return implode(", ", array_map($this->printHost(...), $hostnames)); } else { $hosts = $common_prefix . "{" . implode(",", $middle_parts) . "}" . $common_suffix; return $this->printHost($hosts, true); @@ -696,7 +675,7 @@ public function parseRunDiff(string $difftext): string { $line = strtok($difftext, "\n"); //first line if ($line === false || sscanf($line, "### DIFFERENCES FROM LINE %d ###\n", $firstdiff) != 1) { - return Utils::specialchars($difftext); + return htmlspecialchars($difftext); } $return = $line . "\n"; @@ -714,23 +693,13 @@ public function parseRunDiff(string $difftext): string $linenostr = mb_substr($line, 0, $linenowidth); $diffline = mb_substr($line, $linenowidth + 1); $mid = mb_substr($diffline, $midloc - 1, 3); - switch ($mid) { - case ' = ': - $formdiffline = "" . Utils::specialchars($diffline) . ""; - break; - case ' ! ': - $formdiffline = "" . Utils::specialchars($diffline) . ""; - break; - case ' $ ': - $formdiffline = "" . Utils::specialchars($diffline) . ""; - break; - case ' > ': - case ' < ': - $formdiffline = "" . Utils::specialchars($diffline) . ""; - break; - default: - $formdiffline = Utils::specialchars($diffline); - } + $formdiffline = match ($mid) { + ' = ' => "" . htmlspecialchars($diffline) . "", + ' ! ' => "" . htmlspecialchars($diffline) . "", + ' $ ' => "" . htmlspecialchars($diffline) . "", + ' > ', ' < ' => "" . htmlspecialchars($diffline) . "", + default => htmlspecialchars($diffline), + }; $return = $return . $linenostr . " " . $formdiffline . "\n"; $line = strtok("\n"); } @@ -776,7 +745,7 @@ public function interactiveLog(string $log, bool $forTeam = false): string if (empty($content)) { break; } - $content = Utils::specialchars($content); + $content = htmlspecialchars($content); $content = '' . str_replace("\n", "\u{21B5}
", $content) . ''; @@ -860,7 +829,7 @@ public function codeEditor( HTML; $rank = $index; $id = sprintf('editor%s', $rank); - $code = Utils::specialchars($code); + $code = htmlspecialchars($code); if ($elementToUpdate) { $extraForEdit = <<"; - break; - case '+': - $formdiffline = "" . Utils::specialchars($line) . ""; - break; - default: - $formdiffline = Utils::specialchars($line); - } + $formdiffline = match (substr($line, 0, 1)) { + '-' => "" . htmlspecialchars($line) . "", + '+' => "" . htmlspecialchars($line) . "", + default => htmlspecialchars($line), + }; $return .= $formdiffline . "\n"; $line = strtok("\n"); } @@ -979,9 +943,8 @@ public function printTimeRelative(float $relativeTime, bool $useMicroseconds = f /** * Display the scoretime for the given time. - * @param string|float $time */ - public function scoreTime($time): int + public function scoreTime(string|float $time): int { return Utils::scoretime($time, (bool)$this->config->get('score_in_seconds')); } @@ -1005,8 +968,8 @@ public function descriptionExpand(?string $description = null): string return implode('
', $descriptionLines); } else { $default = implode('
', array_slice($descriptionLines, 0, 3)); - $defaultEscaped = Utils::specialchars($default); - $expandedEscaped = Utils::specialchars(implode('
', $descriptionLines)); + $defaultEscaped = htmlspecialchars($default); + $expandedEscaped = htmlspecialchars(implode('
', $descriptionLines)); return << @@ -1067,17 +1030,11 @@ public function toTsvField(string $field): string public function fileTypeIcon(string $type): string { - switch ($type) { - case 'pdf': - $iconName = 'pdf'; - break; - case 'txt': - $iconName = 'alt'; - break; - default: - $iconName = 'code'; - break; - } + $iconName = match ($type) { + 'pdf' => 'pdf', + 'txt' => 'alt', + default => 'code', + }; return 'fas fa-file-' . $iconName; } @@ -1137,11 +1094,11 @@ public function printWarningContent(ExternalSourceWarning $warning): string $tdField = "$field"; $tdUs = sprintf( '%s', - $diff['us'] === null ? $null : $diff['us'] + $diff['us'] ?? $null ); $tdExternal = sprintf( '%s', - $diff['external'] === null ? $null : $diff['external'] + $diff['external'] ?? $null ); $rows[] = "{$tdField}{$tdUs}{$tdExternal}"; } @@ -1197,14 +1154,29 @@ public function printWarningContent(ExternalSourceWarning $warning): string public function entityIdBadge(BaseApiEntity $entity, string $idPrefix = ''): string { $propertyAccessor = PropertyAccess::createPropertyAccessor(); - $metadata = $this->em->getClassMetadata(get_class($entity)); + $metadata = $this->em->getClassMetadata($entity::class); $primaryKeyColumn = $metadata->getIdentifierColumnNames()[0]; $externalIdField = $this->eventLogService->externalIdFieldForEntity($entity); - return $this->twig->render('jury/entity_id_badge.html.twig', [ + $data = [ 'idPrefix' => $idPrefix, 'id' => $propertyAccessor->getValue($entity, $primaryKeyColumn), 'externalId' => $externalIdField ? $propertyAccessor->getValue($entity, $externalIdField) : null, - ]); + ]; + + if ($entity instanceof Team) { + $data['label'] = $entity->getLabel(); + } + + return $this->twig->render('jury/entity_id_badge.html.twig', $data); + } + + protected function numTableActions(array $tableData): int + { + $maxNumActions = 0; + foreach ($tableData as $item) { + $maxNumActions = max($maxNumActions, count($item['actions'] ?? [])); + } + return $maxNumActions; } } diff --git a/webapp/templates/jury/contests.html.twig b/webapp/templates/jury/contests.html.twig index a9af41e1a41..04cc260fc86 100644 --- a/webapp/templates/jury/contests.html.twig +++ b/webapp/templates/jury/contests.html.twig @@ -87,7 +87,7 @@

All available contests

- {{ macros.table(contests_table, table_fields, num_actions) }} + {{ macros.table(contests_table, table_fields) }} {% if is_granted('ROLE_ADMIN') %}

diff --git a/webapp/templates/jury/executables.html.twig b/webapp/templates/jury/executables.html.twig index 94af4cae5b9..cd5276371e7 100644 --- a/webapp/templates/jury/executables.html.twig +++ b/webapp/templates/jury/executables.html.twig @@ -12,7 +12,7 @@

Executables

- {{ macros.table(executables, table_fields, num_actions, {'ordering': 'false'}) }} + {{ macros.table(executables, table_fields, {'ordering': 'false'}) }} {% if is_granted('ROLE_ADMIN') %}

diff --git a/webapp/templates/jury/jury_macros.twig b/webapp/templates/jury/jury_macros.twig index f86c3e08acf..ddb70eb30a1 100644 --- a/webapp/templates/jury/jury_macros.twig +++ b/webapp/templates/jury/jury_macros.twig @@ -85,12 +85,13 @@ {% endmacro %} -{% macro table(data, fields, num_actions, options) %} +{% macro table(data, fields, options) %}

+ {%- set num_actions = data | numTableActions %} {%- set default_sort = 0 %} {%- set default_sort_order = 'asc' %} {%- for key,column in fields %} diff --git a/webapp/templates/jury/languages.html.twig b/webapp/templates/jury/languages.html.twig index 3a68e483b75..0df29e4c15a 100644 --- a/webapp/templates/jury/languages.html.twig +++ b/webapp/templates/jury/languages.html.twig @@ -12,14 +12,14 @@

Enabled languages

- {{ macros.table(enabled_languages, table_fields, num_actions) }} + {{ macros.table(enabled_languages, table_fields) }}

Disabled languages

- {{ macros.table(disabled_languages, table_fields, num_actions) }} + {{ macros.table(disabled_languages, table_fields) }} {% if is_granted('ROLE_ADMIN') %}

diff --git a/webapp/templates/jury/partials/judgehost_list.html.twig b/webapp/templates/jury/partials/judgehost_list.html.twig index aa31d1bb6dc..a3177345549 100644 --- a/webapp/templates/jury/partials/judgehost_list.html.twig +++ b/webapp/templates/jury/partials/judgehost_list.html.twig @@ -1,2 +1,2 @@ {% import "jury/jury_macros.twig" as macros %} -{{ macros.table(judgehosts, table_fields, num_actions, {ordering: 'false'}) }} +{{ macros.table(judgehosts, table_fields, {ordering: 'false'}) }} diff --git a/webapp/templates/jury/problems.html.twig b/webapp/templates/jury/problems.html.twig index 727b515ab29..511fcc5cbb6 100644 --- a/webapp/templates/jury/problems.html.twig +++ b/webapp/templates/jury/problems.html.twig @@ -12,7 +12,7 @@

Problems

- {{ macros.table(problems, table_fields, num_actions) }} + {{ macros.table(problems, table_fields) }} {% if is_granted('ROLE_ADMIN') %}

diff --git a/webapp/templates/jury/team_affiliations.html.twig b/webapp/templates/jury/team_affiliations.html.twig index 4c5503003ad..0241c263b01 100644 --- a/webapp/templates/jury/team_affiliations.html.twig +++ b/webapp/templates/jury/team_affiliations.html.twig @@ -12,7 +12,7 @@

Affiliations

- {{ macros.table(team_affiliations, table_fields, num_actions) }} + {{ macros.table(team_affiliations, table_fields) }} {%- if is_granted('ROLE_ADMIN') %} diff --git a/webapp/templates/jury/team_categories.html.twig b/webapp/templates/jury/team_categories.html.twig index 9024a8bc795..91e7a95b96a 100644 --- a/webapp/templates/jury/team_categories.html.twig +++ b/webapp/templates/jury/team_categories.html.twig @@ -12,7 +12,7 @@

Categories

- {{ macros.table(team_categories, table_fields, num_actions) }} + {{ macros.table(team_categories, table_fields) }} {% if is_granted('ROLE_ADMIN') %}

diff --git a/webapp/templates/jury/teams.html.twig b/webapp/templates/jury/teams.html.twig index 4d1eeb11bee..b2501cf750a 100644 --- a/webapp/templates/jury/teams.html.twig +++ b/webapp/templates/jury/teams.html.twig @@ -12,7 +12,7 @@

Teams

- {{ macros.table(teams, table_fields, num_actions) }} + {{ macros.table(teams, table_fields) }} {%- if is_granted('ROLE_ADMIN') %} diff --git a/webapp/templates/jury/users.html.twig b/webapp/templates/jury/users.html.twig index e629db218fc..e5e291843db 100644 --- a/webapp/templates/jury/users.html.twig +++ b/webapp/templates/jury/users.html.twig @@ -12,7 +12,7 @@

Users

- {{ macros.table(users, table_fields, num_actions) }} + {{ macros.table(users, table_fields) }} {% if is_granted('ROLE_ADMIN') %}