Skip to content
Merged
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
2 changes: 1 addition & 1 deletion webapp/src/Controller/Jury/SubmissionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1240,7 +1240,7 @@ protected function determineFileChanged(array $files, array $oldFiles): array
}
}
}
if($isNewFile) {
if ($isNewFile) {
$result['addedfiles'][] = $newfile;
}
}
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/Entity/ContestProblem.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public function validate(ExecutionContextInterface $context): void
{
if ($this->getColor() && Utils::convertToHex($this->getColor()) === null) {
$context
->buildViolation('This is not a valid color')
->buildViolation('This is not a valid color.')
->atPath('color')
->addViolation();
}
Expand Down
4 changes: 3 additions & 1 deletion webapp/src/Service/EventLogService.php
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,9 @@
$awards = [];
$this->dj->withAllRoles(function () use ($url, &$awards) {
$response = $this->dj->internalApiRequest($url);
if ( !empty($response) ) $awards = Utils::jsonDecode($response);
if (!empty($response)) {
$awards = Utils::jsonDecode($response);

Check failure on line 491 in webapp/src/Service/EventLogService.php

View workflow job for this annotation

GitHub Actions / phpcs

Line indented incorrectly; expected at least 24 spaces, found 22
}
});
foreach ($awards as $award) {
$this->insertEvent($contest, 'awards', $award['id'], $award);
Expand Down
7 changes: 6 additions & 1 deletion webapp/src/Service/ExternalContestSourceService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,12 @@ protected function validateAndUpdateProblem(Event $event, EventData $data): void
);
$contestProblem->setShortname($data->label);
}
if ($contestProblem->getColor() !== ($data->rgb)) {
if (preg_match('/^#[[:xdigit:]]{3}(?:[[:xdigit:]]{3}){0,2}$/', $data->rgb)) {
$this->logger->warning(
'Contest problem color does not match between feed (%s) and local (%s), but feed is invalid.',
[$data->rgb, $contestProblem->getColor()]
);
} elseif ($contestProblem->getColor() !== ($data->rgb)) {
$this->logger->warning(
'Contest problem color does not match between feed (%s) and local (%s), updating',
[$data->rgb, $contestProblem->getColor()]
Expand Down
54 changes: 2 additions & 52 deletions webapp/src/Twig/TwigExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -1179,64 +1179,14 @@ public function fileTypeIcon(string $type): string
return 'fas fa-file-' . $iconName;
}

private function relativeLuminance(string $rgb): float
{
// See https://en.wikipedia.org/wiki/Relative_luminance
[$r, $g, $b] = Utils::parseHexColor($rgb);

[$lr, $lg, $lb] = [
pow($r / 255, 2.4),
pow($g / 255, 2.4),
pow($b / 255, 2.4),
];

return 0.2126 * $lr + 0.7152 * $lg + 0.0722 * $lb;
}

private function apcaContrast(string $fgColor, string $bgColor): float
{
// Based on WCAG 3.x (https://www.w3.org/TR/wcag-3.0/)
$luminanceForeground = $this->relativeLuminance($fgColor);
$luminanceBackground = $this->relativeLuminance($bgColor);

$contrast = ($luminanceBackground > $luminanceForeground)
? (pow($luminanceBackground, 0.56) - pow($luminanceForeground, 0.57)) * 1.14
: (pow($luminanceBackground, 0.65) - pow($luminanceForeground, 0.62)) * 1.14;

return round($contrast * 100, 2);
}

/**
* @return array{string, string}
*/
private function hexToForegroundAndBorder(string $rgb): array
{
$background = Utils::parseHexColor($rgb);

// Pick a border that's a bit darker.
$darker = $background;
$darker[0] = max($darker[0] - 64, 0);
$darker[1] = max($darker[1] - 64, 0);
$darker[2] = max($darker[2] - 64, 0);
$border = Utils::rgbToHex($darker);

// Pick the text color with the biggest absolute contrast.
$contrastWithWhite = $this->apcaContrast('#ffffff', $rgb);
$contrastWithBlack = $this->apcaContrast('#000000', $rgb);

$foreground = (abs($contrastWithBlack) > abs($contrastWithWhite)) ? '#000000' : '#ffffff';

return [$foreground, $border];
}

public function problemBadge(ContestProblem $problem, bool $grayedOut = false): string
{
$rgb = Utils::convertToHex($problem->getColor() ?? '#ffffff');
if ($grayedOut || empty($rgb)) {
$rgb = Utils::convertToHex('whitesmoke');
}

[$foreground, $border] = $this->hexToForegroundAndBorder($rgb);
[$foreground, $border] = Utils::hexToForegroundAndBorder($rgb);

if ($grayedOut) {
$foreground = 'silver';
Expand All @@ -1262,7 +1212,7 @@ public function problemBadgeMaybe(
$rgb = Utils::convertToHex('whitesmoke');
}

[$foreground, $border] = $this->hexToForegroundAndBorder($rgb);
[$foreground, $border] = Utils::hexToForegroundAndBorder($rgb);

if (!$matrixItem->isCorrect) {
$foreground = 'silver';
Expand Down
59 changes: 57 additions & 2 deletions webapp/src/Utils/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@
*/
public static function convertToHex(string $color): ?string
{
if (preg_match('/^#[[:xdigit:]]{3,6}$/', $color)) {
if (preg_match('/^#[[:xdigit:]]{3}(?:[[:xdigit:]]{3}){0,2}$/', $color)) {
return $color;
}

Expand Down Expand Up @@ -375,7 +375,62 @@
*/
public static function rgbToHex(array $color): string
{
return "#" . static::componentToHex($color[0]) . static::componentToHex($color[1]) . static::componentToHex($color[2]);
$result = "#";
for ($i=0; $i<count($color); $i++) {
$result .= static::componentToHex($color[$i]);
}
return $result;
}

public static function relativeLuminance(string $rgb): float
{
// See https://en.wikipedia.org/wiki/Relative_luminance
[$r, $g, $b] = static::parseHexColor($rgb);

[$lr, $lg, $lb] = [
pow($r / 255, 2.4),
pow($g / 255, 2.4),
pow($b / 255, 2.4),
];

return 0.2126 * $lr + 0.7152 * $lg + 0.0722 * $lb;
}

public static function apcaContrast(string $fgColor, string $bgColor): float
{
// Based on WCAG 3.x (https://www.w3.org/TR/wcag-3.0/)
$luminanceForeground = static::relativeLuminance($fgColor);
$luminanceBackground = static::relativeLuminance($bgColor);

$contrast = ($luminanceBackground > $luminanceForeground)
? (pow($luminanceBackground, 0.56) - pow($luminanceForeground, 0.57)) * 1.14
: (pow($luminanceBackground, 0.65) - pow($luminanceForeground, 0.62)) * 1.14;

return round($contrast * 100, 2);
}

/**
* @return array{string, string}
*/
public static function hexToForegroundAndBorder(string $rgb): array
{
$background = Utils::parseHexColor($rgb);

// Pick a border that's a bit darker.
// We explicit keep the alpha channel as-is.
$darker = $background;
$darker[0] = max($darker[0] - 64, 0);
$darker[1] = max($darker[1] - 64, 0);
$darker[2] = max($darker[2] - 64, 0);
$border = Utils::rgbToHex($darker);

// Pick the text color with the biggest absolute contrast.
$contrastWithWhite = static::apcaContrast('#ffffff', $rgb);
$contrastWithBlack = static::apcaContrast('#000000', $rgb);

$foreground = (abs($contrastWithBlack) > abs($contrastWithWhite)) ? '#000000' : '#ffffff';

return [$foreground, $border];
}

/**
Expand Down Expand Up @@ -1027,10 +1082,10 @@
/**
* Call ob_flush() unless the top-level output buffer does not allow it.
*/
public static function ob_flush_if_possible(): bool

Check failure on line 1085 in webapp/src/Utils/Utils.php

View workflow job for this annotation

GitHub Actions / phpcs

Method name &quot;Utils::ob_flush_if_possible&quot; is not in camel caps format
{
$status = ob_get_status();
if ( empty($status) || ($status['flags'] & PHP_OUTPUT_HANDLER_CLEANABLE) ) {

Check failure on line 1088 in webapp/src/Utils/Utils.php

View workflow job for this annotation

GitHub Actions / phpcs

Expected 0 spaces before closing bracket; 1 found

Check failure on line 1088 in webapp/src/Utils/Utils.php

View workflow job for this annotation

GitHub Actions / phpcs

Expected 0 spaces after opening bracket; 1 found
return ob_flush();
}
return false;
Expand Down
41 changes: 38 additions & 3 deletions webapp/tests/Unit/Utils/UtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,17 @@ public function testConvertToHexConvert(): void
self::assertEquals('#00BFFF', Utils::convertToHex('deep sky blue'));
self::assertEquals('#FFD700', Utils::convertToHex('GOLD'));
self::assertEquals('#B8860B', Utils::convertToHex('darkgoldenrod '));
self::assertEquals('#aabbcc', Utils::convertToHex('#aabbcc'));
self::assertEquals('#aaabbbccc', Utils::convertToHex('#aaabbbccc'));
self::assertEquals('#def', Utils::convertToHex('#def'));
self::assertEquals('#f1A', Utils::convertToHex('#f1A'));
}

public function testParseHexColor(): void
{
self::assertEquals([255, 255, 255], Utils::parseHexColor('#ffffff'));
self::assertEquals([0, 0, 0], Utils::parseHexColor('#000000'));
self::assertEquals([0, 0, 0], Utils::parseHexColor('#000'));
self::assertEquals([171, 205, 239], Utils::parseHexColor('#abcdef'));
self::assertEquals([254, 220, 186], Utils::parseHexColor('#FEDCBA'));
}
Expand All @@ -343,6 +348,34 @@ public function testRgbToHex(): void
self::assertEquals('#fedcba', Utils::rgbToHex([254, 220, 186]));
}

public function testRelativeLuminance(): void
{
self::assertEquals(0.0, Utils::relativeLuminance("#000000"));
self::assertEquals(1.0, Utils::relativeLuminance("#FFFfff"));
self::assertEquals(0.00751604342389449, Utils::relativeLuminance("#123"));
}

/**
* Test that the APCA contrast function returns the correct data
*/
public function testApcaContrast(): void
{
self::assertEquals(-114.0, Utils::apcaContrast("#fff", "#000000"));
self::assertEquals(-114.0, Utils::apcaContrast("#ffffff", "#000000"));
self::assertEquals(-114.0, Utils::apcaContrast("#fffffffff", "#000"));
self::assertEquals(114.0, Utils::apcaContrast("#000000", "#ffffff"));
self::assertEquals(0.0, Utils::apcaContrast("#fffFFF", "#FFFfff"));
self::assertEquals(-0.36, Utils::apcaContrast("#111", "#111"));
}

public function testHexToForegroundAndBorder(): void
{
self::assertEquals(["#000000", "#bfbd9d"], Utils::hexToForegroundAndBorder("#fffDDD"));
self::assertEquals(["#ffffff", "#000000"], Utils::hexToForegroundAndBorder("#000000"));
self::assertEquals(["#000000", "#6a7b8c"], Utils::hexToForegroundAndBorder("#ABC"));
self::assertEquals(["#000000", "#6a7b8c"], Utils::hexToForegroundAndBorder("#AAABBBCCC"));
}

/**
* Test function that converts colour name to hex notation.
* Returns null for unknown values.
Expand All @@ -351,7 +384,9 @@ public function testConvertToHexUnknown(): void
{
self::assertNull(Utils::convertToHex('doesnotexist'));
self::assertNull(Utils::convertToHex('#aabbccdd'));
self::assertNull(Utils::convertToHex('#12346h'));
self::assertNull(Utils::convertToHex('#12345h'));
self::assertNull(Utils::convertToHex('#1234'));
self::assertNull(Utils::convertToHex('#12'));
}

/**
Expand Down Expand Up @@ -770,7 +805,7 @@ public function testSanitizeSvg(): void
<a xlink:href="javascript:alert(2)">test 2</a>
<a href="#test3">test 3</a>
<a xlink:href="#test">test 4</a>

<a href="data:data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E">test 5</a>
<a xlink:href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' onload='alert(88)'%3E%3C/svg%3E">test 6</a>
<use xlink:href="defs.svg#icon-1"/>
Expand All @@ -783,7 +818,7 @@ public function testSanitizeSvg(): void
<this>shouldn't be here</this>
<script>alert(1);</script>
<line fill="none" stroke="#000000" stroke-miterlimit="10" x1="541.54" y1="299.573" x2="543.179" y2="536.458"/>

</svg>
EOF;
$clean = Utils::sanitizeSvg($dirty);
Expand Down
Loading