Skip to content
Open
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
45 changes: 45 additions & 0 deletions components/ILIAS/Data/src/QR/ErrorCorrectionLevel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*/

declare(strict_types=1);

namespace ILIAS\Data\QR;

/**
* Error correction levels as defined by ISO/IEC 18004.
*
* Each level specifies the percentage of codewords that can be
* restored if the QR code is damaged or partially obscured.
* Please note that increasing the error correction level will
* decrease the data capacity of its payload.
*
* @see https://www.qrcode.com/en/about/error_correction.html
*/
enum ErrorCorrectionLevel: string
{
/** ~7% of codewords can be restored. */
case LOW = 'L';

/** ~15% of codewords can be restored (most fequently used). */
case MEDIUM = 'M';

/** ~25% of codewords can be restored. */
case QUARTILE = 'Q';

/** ~30% of codewords can be restored. */
case HIGH = 'H';
}
45 changes: 45 additions & 0 deletions components/ILIAS/Data/src/QR/SVGCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*/

declare(strict_types=1);

namespace ILIAS\Data\QR;

/**
* Data transfer object that carries the raw SVG data and provides
* funcionality to embed it as data uri.
*/
readonly class SVGCode
{
public function __construct(
protected string $raw_svg_string,
) {
$this->assertStringNotEmpty($this->raw_svg_string);
}

public function toDataUri(): string
{
return "data:image/svg+xml;base64," . base64_encode($this->raw_svg_string);
}

protected function assertStringNotEmpty(string $string): void
{
if (0 >= mb_strlen($string)) {
throw new \InvalidArgumentException("SVG data must not be empty.");
}
}
}
64 changes: 64 additions & 0 deletions components/ILIAS/Data/tests/QRCodeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*/

declare(strict_types=1);

use ILIAS\Data\QR\SVGCode;
use PHPUnit\Framework\Attributes\Depends;
use PHPUnit\Framework\TestCase;

class QRCodeTest extends TestCase
{
public function testConstructorWithEmptyString(): void
{
$this->expectException(\InvalidArgumentException::class);
$code = new SVGCode('');
}

public function testConstructorWithNonEmptyString(): void
{
$this->expectNotToPerformAssertions();
$code = new SVGCode('some svg contents');
}

public function testConstructorWithEmoji(): void
{
$this->expectNotToPerformAssertions();
$code = new SVGCode("\xF0\x9F\x98\x82"); // some emoji
}

#[Depends('testConstructorWithNonEmptyString')]
public function testToDataUri(): void
{
$pseudo_svg_string = 'some svg contents';
$code = new SVGCode($pseudo_svg_string);
$data_uri = $code->toDataUri();

$this->assertStringStartsWith("data:image/svg+xml;base64,", $data_uri); // ensure correct uri format
$this->assertStringEndsWith(base64_encode($pseudo_svg_string), $data_uri); // ensure base64 encoded value
$this->assertSame($data_uri, $code->toDataUri()); // ensure identical output
}

#[Depends('testToDataUri')]
public function testInstancesProcudeSameResult(): void
{
$pseudo_svg_string = 'some svg contents';
$code_one = new SVGCode($pseudo_svg_string);
$code_two = new SVGCode($pseudo_svg_string);
$this->assertSame($code_one->toDataUri(), $code_two->toDataUri()); // ensure identical output
}
}
11 changes: 11 additions & 0 deletions components/ILIAS/Refinery/src/String/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use ILIAS\Refinery\Constraint;
use ILIAS\Refinery\Transformation;
use ILIAS\Refinery\String\Encoding\Group as EncodingGroup;
use ILIAS\Data\QR\ErrorCorrectionLevel;

class Group
{
Expand Down Expand Up @@ -152,4 +153,14 @@ public function encoding(): EncodingGroup
{
return new EncodingGroup();
}

/**
* Creates a transformation to generate an {@see \ILIAS\Data\QR\SVGCode} from string.
*/
public function qrCode(
ErrorCorrectionLevel $error_correction_level = ErrorCorrectionLevel::MEDIUM,
int $size_in_px = 400,
): Transformation {
return new QRCode($error_correction_level, $size_in_px);
}
}
94 changes: 94 additions & 0 deletions components/ILIAS/Refinery/src/String/QRCode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*/

declare(strict_types=1);

namespace ILIAS\Refinery\String;

use ILIAS\Refinery\DeriveApplyToFromTransform;
use ILIAS\Refinery\DeriveInvokeFromTransform;
use ILIAS\Refinery\Transformation;
use ILIAS\Data\QR\ErrorCorrectionLevel;
use ILIAS\Data\QR\SVGCode;
use BaconQrCode as External;

class QRCode implements Transformation
{
use DeriveApplyToFromTransform;
use DeriveInvokeFromTransform;

protected const string SUPPORTED_ENCODING = 'UTF-8';

public function __construct(
protected ErrorCorrectionLevel $error_correction_level,
protected int $size_in_px,
) {
$this->assertIntGreaterThanZero($size_in_px);
}

public function transform(mixed $from): SVGCode
{
$this->assertString($from);
$this->assertStringNotEmpty($from);

$writer = new External\Writer(
new External\Renderer\ImageRenderer(
new External\Renderer\RendererStyle\RendererStyle($this->size_in_px),
new External\Renderer\Image\SvgImageBackEnd(),
),
);

$raw_svg_string = $writer->writeString(
$from,
self::SUPPORTED_ENCODING,
$this->mapErrorCorrectionLevel($this->error_correction_level),
);

return new SVGCode($raw_svg_string);
}

protected function mapErrorCorrectionLevel(ErrorCorrectionLevel $level): External\Common\ErrorCorrectionLevel
{
return match ($level) {
ErrorCorrectionLevel::LOW => External\Common\ErrorCorrectionLevel::L(),
ErrorCorrectionLevel::MEDIUM => External\Common\ErrorCorrectionLevel::M(),
ErrorCorrectionLevel::QUARTILE => External\Common\ErrorCorrectionLevel::Q(),
ErrorCorrectionLevel::HIGH => External\Common\ErrorCorrectionLevel::H(),
};
}

protected function assertString(mixed $value): void
{
if (!is_string($value)) {
throw new \InvalidArgumentException("Argument must be of type string.");
}
}

protected function assertStringNotEmpty(string $string): void
{
if (0 >= mb_strlen($string)) {
throw new \InvalidArgumentException("String must not be empty.");
}
}

protected function assertIntGreaterThanZero(int $number): void
{
if (0 >= $number) {
throw new \InvalidArgumentException("Number must be greater than zero.");
}
}
}
120 changes: 120 additions & 0 deletions components/ILIAS/Refinery/tests/String/QRCodeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*/

declare(strict_types=1);

namespace String;

use ILIAS\Refinery\String\QRCode;
use ILIAS\Data\QR\ErrorCorrectionLevel;
use ILIAS\Data\QR\SVGCode;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Depends;
use PHPUnit\Framework\TestCase;

class QRCodeTest extends TestCase
{
public function testConstructorWithZero(): void
{
$this->expectException(\InvalidArgumentException::class);
$transformation = new QRCode(ErrorCorrectionLevel::LOW, 0);
}

public function testConstructorWithNegativeNumber(): void
{
$this->expectException(\InvalidArgumentException::class);
$transformation = new QRCode(ErrorCorrectionLevel::LOW, -1);
}

public function testConstructorWithPositiveNumber(): void
{
$this->expectNotToPerformAssertions();
$transformation = new QRCode(ErrorCorrectionLevel::LOW, 1);
}

#[Depends('testConstructorWithPositiveNumber')]
public function testTransformWithoutString(): void
{
$transformation = new QRCode(ErrorCorrectionLevel::LOW, 1);
$this->expectException(\InvalidArgumentException::class);
$transformation->transform(1);
}

#[Depends('testConstructorWithPositiveNumber')]
public function testTransformWithEmptyString(): void
{
$transformation = new QRCode(ErrorCorrectionLevel::LOW, 1);
$this->expectException(\InvalidArgumentException::class);
$transformation->transform('');
}

#[Depends('testConstructorWithPositiveNumber')]
public function testTransformWithNonEmptyString(): void
{
$transformation = new QRCode(ErrorCorrectionLevel::LOW, 1);
$this->expectNotToPerformAssertions();
$transformation->transform('some arbitrary data.');
}

#[Depends('testConstructorWithPositiveNumber')]
public function testTransformWithEmoji(): void
{
$transformation = new QRCode(ErrorCorrectionLevel::LOW, 1);
$this->expectNotToPerformAssertions();
$transformation->transform("\xF0\x9F\x98\x82"); // some emoji
}

/** @return array<ErrorCorrectionLevel[]> */
public static function getErrorCorrectionLevels(): array
{
return [
[ErrorCorrectionLevel::LOW],
[ErrorCorrectionLevel::MEDIUM],
[ErrorCorrectionLevel::QUARTILE],
[ErrorCorrectionLevel::HIGH],
];
}

#[Depends('testConstructorWithPositiveNumber')]
#[DataProvider('getErrorCorrectionLevels')]
public function testTransformWithErrorCorrectionLevels(ErrorCorrectionLevel $level): void
{
$transformation = new QRCode($level, 1);
$this->expectNotToPerformAssertions();
$code = $transformation->transform("some arbitrary data.");
}

/** @return array<int[]> */
public static function getSizesInPx(): array
{
return [
[10],
[100],
[400],
[1_000],
];
}

#[Depends('testConstructorWithPositiveNumber')]
#[DataProvider('getSizesInPx')]
public function testTransformWithSizes(int $size_in_px): void
{
$transformation = new QRCode(ErrorCorrectionLevel::LOW, $size_in_px);
$this->expectNotToPerformAssertions();
$code = $transformation->transform("some arbitrary data.");
}
}
Loading
Loading