diff --git a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js index 03737dae..aa29e889 100644 --- a/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js +++ b/assets/ckeditor/plugins/PartDBLabel/PartDBLabelUI.js @@ -85,6 +85,9 @@ const PLACEHOLDERS = [ ['[[COMMENT_T]]', 'Comment (plain text)'], ['[[LAST_MODIFIED]]', 'Last modified datetime'], ['[[CREATION_DATE]]', 'Creation datetime'], + ['[[IPN_BARCODE_QR]]', 'IPN as QR code'], + ['[[IPN_BARCODE_C128]]', 'IPN as Code 128 barcode'], + ['[[IPN_BARCODE_C39]]', 'IPN as Code 39 barcode'], ] }, { diff --git a/assets/ckeditor/plugins/PartDBLabel/lang/de.js b/assets/ckeditor/plugins/PartDBLabel/lang/de.js index 53007a0a..2220cc0b 100644 --- a/assets/ckeditor/plugins/PartDBLabel/lang/de.js +++ b/assets/ckeditor/plugins/PartDBLabel/lang/de.js @@ -48,6 +48,9 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, { 'Comment (plain text)': 'Kommentar (Nur-Text)', 'Last modified datetime': 'Zuletzt geändert', 'Creation datetime': 'Erstellt', + 'IPN as QR code': 'IPN als QR Code', + 'IPN as Code 128 barcode': 'IPN als Code 128 Barcode', + 'IPN as Code 39 barcode': 'IPN als Code 39 Barcode', 'Lot ID': 'Lot ID', 'Lot name': 'Lot Name', diff --git a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php index 33743e4f..7ceb30dd 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php @@ -76,11 +76,11 @@ final class BarcodeContentGenerator { $type = $this->classToString(self::URL_MAP, $target); - return $this->urlGenerator->generate('scan_qr', [ - 'type' => $type, + return $this->urlGenerator->generate('scan_qr', [ + 'type' => $type, 'id' => $target->getID() ?? 0, '_locale' => null, -], UrlGeneratorInterface::ABSOLUTE_URL); + ], UrlGeneratorInterface::ABSOLUTE_URL); } /** diff --git a/src/Services/LabelSystem/Barcodes/BarcodeHelper.php b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php new file mode 100644 index 00000000..d13da589 --- /dev/null +++ b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php @@ -0,0 +1,96 @@ +. + */ + +declare(strict_types=1); + + +namespace App\Services\LabelSystem\Barcodes; + +use App\Entity\LabelSystem\BarcodeType; +use Com\Tecnick\Barcode\Barcode; + +/** + * This function is used to generate barcodes of various types using arbitrary (text) content. + */ +class BarcodeHelper +{ + + /** + * Generates a barcode with the given content and type and returns it as SVG string. + * @param string $content + * @param BarcodeType $type + * @return string + */ + public function barcodeAsSVG(string $content, BarcodeType $type): string + { + $barcode = new Barcode(); + + $type_str = match ($type) { + BarcodeType::NONE => throw new \InvalidArgumentException('Barcode type must not be NONE! This would make no sense...'), + BarcodeType::QR => 'QRCODE', + BarcodeType::DATAMATRIX => 'DATAMATRIX', + BarcodeType::CODE39 => 'C39', + BarcodeType::CODE93 => 'C93', + BarcodeType::CODE128 => 'C128A', + }; + + return $barcode->getBarcodeObj($type_str, $content)->getSvgCode(); + } + + /** + * Generates a barcode with the given content and type and returns it as HTML image tag. + * @param string $content + * @param BarcodeType $type + * @param string $width Width of the image tag + * @param string|null $alt_text The alt text of the image tag. If null, the content is used. + * @return string + */ + public function barcodeAsHTML(string $content, BarcodeType $type, string $width = '100%', ?string $alt_text = null): string + { + $svg = $this->barcodeAsSVG($content, $type); + $base64 = $this->dataUri($svg, 'image/svg+xml'); + $alt_text = $alt_text ?? $content; + + return ''.$alt_text.''; + } + + /** + * Creates a data URI (RFC 2397). + * Based on the Twig implementation from HTMLExtension + * + * Length validation is not performed on purpose, validation should + * be done before calling this filter. + * + * @return string The generated data URI + */ + private function dataUri(string $data, string $mime): string + { + $repr = 'data:'; + + $repr .= $mime; + if (str_starts_with($mime, 'text/')) { + $repr .= ','.rawurlencode($data); + } else { + $repr .= ';base64,'.base64_encode($data); + } + + return $repr; + } +} \ No newline at end of file diff --git a/src/Services/LabelSystem/BarcodeGenerator.php b/src/Services/LabelSystem/LabelBarcodeGenerator.php similarity index 64% rename from src/Services/LabelSystem/BarcodeGenerator.php rename to src/Services/LabelSystem/LabelBarcodeGenerator.php index f955955a..ea0d8f44 100644 --- a/src/Services/LabelSystem/BarcodeGenerator.php +++ b/src/Services/LabelSystem/LabelBarcodeGenerator.php @@ -46,67 +46,47 @@ use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\LabelSystem\BarcodeType; use App\Entity\LabelSystem\LabelOptions; use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; +use App\Services\LabelSystem\Barcodes\BarcodeHelper; use Com\Tecnick\Barcode\Barcode; use InvalidArgumentException; /** * @see \App\Tests\Services\LabelSystem\BarcodeGeneratorTest */ -final class BarcodeGenerator +final class LabelBarcodeGenerator { - public function __construct(private readonly BarcodeContentGenerator $barcodeContentGenerator) + public function __construct(private readonly BarcodeContentGenerator $barcodeContentGenerator, private readonly BarcodeHelper $barcodeHelper) { } - public function generateHTMLBarcode(LabelOptions $options, object $target): ?string - { - $svg = $this->generateSVG($options, $target); - $base64 = $this->dataUri($svg, 'image/svg+xml'); - return ''. $this->getContent($options, $target) . ''; - } - - /** - * Creates a data URI (RFC 2397). - * Based on the Twig implementaion from HTMLExtension - * - * Length validation is not performed on purpose, validation should - * be done before calling this filter. - * - * @return string The generated data URI + /** + * Generate the barcode for the given label as HTML image tag. + * @param LabelOptions $options + * @param AbstractDBElement $target + * @return string|null */ - private function dataUri(string $data, string $mime): string + public function generateHTMLBarcode(LabelOptions $options, AbstractDBElement $target): ?string { - $repr = 'data:'; - - $repr .= $mime; - if (str_starts_with($mime, 'text/')) { - $repr .= ','.rawurlencode($data); - } else { - $repr .= ';base64,'.base64_encode($data); - } - - return $repr; - } - - public function generateSVG(LabelOptions $options, object $target): ?string - { - $barcode = new Barcode(); - - $type = match ($options->getBarcodeType()) { - BarcodeType::NONE => null, - BarcodeType::QR => 'QRCODE', - BarcodeType::DATAMATRIX => 'DATAMATRIX', - BarcodeType::CODE39 => 'C39', - BarcodeType::CODE93 => 'C93', - BarcodeType::CODE128 => 'C128A', - }; - - if ($type === null) { + if ($options->getBarcodeType() === BarcodeType::NONE) { return null; } + return $this->barcodeHelper->barcodeAsHTML($this->getContent($options, $target), $options->getBarcodeType()); + } - return $barcode->getBarcodeObj($type, $this->getContent($options, $target))->getSvgCode(); + /** + * Generate the barcode for the given label as SVG string. + * @param LabelOptions $options + * @param AbstractDBElement $target + * @return string|null + */ + public function generateSVG(LabelOptions $options, AbstractDBElement $target): ?string + { + if ($options->getBarcodeType() === BarcodeType::NONE) { + return null; + } + + return $this->barcodeHelper->barcodeAsSVG($this->getContent($options, $target), $options->getBarcodeType()); } public function getContent(LabelOptions $options, AbstractDBElement $target): ?string diff --git a/src/Services/LabelSystem/LabelHTMLGenerator.php b/src/Services/LabelSystem/LabelHTMLGenerator.php index 7b6defa6..b4184646 100644 --- a/src/Services/LabelSystem/LabelHTMLGenerator.php +++ b/src/Services/LabelSystem/LabelHTMLGenerator.php @@ -53,7 +53,7 @@ use Twig\Error\Error; final class LabelHTMLGenerator { - public function __construct(private readonly ElementTypeNameGenerator $elementTypeNameGenerator, private readonly LabelTextReplacer $replacer, private readonly Environment $twig, private readonly BarcodeGenerator $barcodeGenerator, private readonly SandboxedTwigProvider $sandboxedTwigProvider, private readonly Security $security, private readonly string $partdb_title) + public function __construct(private readonly ElementTypeNameGenerator $elementTypeNameGenerator, private readonly LabelTextReplacer $replacer, private readonly Environment $twig, private readonly LabelBarcodeGenerator $barcodeGenerator, private readonly SandboxedTwigProvider $sandboxedTwigProvider, private readonly Security $security, private readonly string $partdb_title) { } diff --git a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php index 11824054..80685e86 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php @@ -24,12 +24,18 @@ namespace App\Services\LabelSystem\PlaceholderProviders; use App\Entity\LabelSystem\BarcodeType; use App\Entity\LabelSystem\LabelOptions; -use App\Services\LabelSystem\BarcodeGenerator; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Services\LabelSystem\Barcodes\BarcodeHelper; +use App\Services\LabelSystem\LabelBarcodeGenerator; use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; +use Com\Tecnick\Barcode\Exception; final class BarcodeProvider implements PlaceholderProviderInterface { - public function __construct(private readonly BarcodeGenerator $barcodeGenerator, private readonly BarcodeContentGenerator $barcodeContentGenerator) + public function __construct(private readonly LabelBarcodeGenerator $barcodeGenerator, + private readonly BarcodeContentGenerator $barcodeContentGenerator, + private readonly BarcodeHelper $barcodeHelper) { } @@ -69,6 +75,36 @@ final class BarcodeProvider implements PlaceholderProviderInterface return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target); } + if ($label_target instanceof Part || $label_target instanceof PartLot) { + if ($label_target instanceof PartLot) { + $label_target = $label_target->getPart(); + } + + if ($label_target === null || $label_target->getIPN() === null || $label_target->getIPN() === '') { + //Replace with empty result, if no IPN is set + return ''; + } + + try { + //Add placeholders for the IPN barcode + if ('[[IPN_BARCODE_C39]]' === $placeholder) { + return $this->barcodeHelper->barcodeAsHTML($label_target->getIPN(), BarcodeType::CODE39); + } + if ('[[IPN_BARCODE_C128]]' === $placeholder) { + return $this->barcodeHelper->barcodeAsHTML($label_target->getIPN(), BarcodeType::CODE128); + } + if ('[[IPN_BARCODE_QR]]' === $placeholder) { + return $this->barcodeHelper->barcodeAsHTML($label_target->getIPN(), BarcodeType::QR); + } + } catch (Exception $e) { + //If an error occurs, output it + return 'IPN Barcode ERROR!: '.$e->getMessage(); + } + } + + + + return null; } } diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeHelperTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeHelperTest.php new file mode 100644 index 00000000..e0639427 --- /dev/null +++ b/tests/Services/LabelSystem/Barcodes/BarcodeHelperTest.php @@ -0,0 +1,68 @@ +. + */ + +namespace App\Tests\Services\LabelSystem\Barcodes; + +use App\Entity\LabelSystem\BarcodeType; +use App\Services\LabelSystem\Barcodes\BarcodeHelper; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +class BarcodeHelperTest extends WebTestCase +{ + + protected ?BarcodeHelper $service = null; + + protected function setUp(): void + { + self::bootKernel(); + $this->service = self::getContainer()->get(BarcodeHelper::class); + } + + public function testBarcodeAsHTML(): void + { + $html = $this->service->barcodeAsHTML('Test', BarcodeType::QR); + $this->assertStringStartsWith('assertStringContainsString('alt="Test"', $html); + } + + public function testBarcodeAsSVG(): void + { + //Test that all barcodes types are supported + foreach (BarcodeType::cases() as $type) { + //Skip NONE type + if (BarcodeType::NONE === $type) { + continue; + } + + $svg = $this->service->barcodeAsSVG('1234', $type); + + $this->assertStringContainsStringIgnoringCase('SVG', $svg); + } + } + + public function testBarcodeAsSVGNoneType(): void + { + //On NONE type, service must throw an exception. + $this->expectException(\InvalidArgumentException::class); + + $this->service->barcodeAsSVG('test', BarcodeType::NONE); + } +} diff --git a/tests/Services/LabelSystem/BarcodeGeneratorTest.php b/tests/Services/LabelSystem/LabelBarcodeGeneratorTest.php similarity index 89% rename from tests/Services/LabelSystem/BarcodeGeneratorTest.php rename to tests/Services/LabelSystem/LabelBarcodeGeneratorTest.php index 0677b48e..4afdd9d2 100644 --- a/tests/Services/LabelSystem/BarcodeGeneratorTest.php +++ b/tests/Services/LabelSystem/LabelBarcodeGeneratorTest.php @@ -44,20 +44,17 @@ namespace App\Tests\Services\LabelSystem; use App\Entity\LabelSystem\BarcodeType; use App\Entity\LabelSystem\LabelOptions; use App\Entity\Parts\Part; -use App\Services\LabelSystem\BarcodeGenerator; +use App\Services\LabelSystem\LabelBarcodeGenerator; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -final class BarcodeGeneratorTest extends WebTestCase +final class LabelBarcodeGeneratorTest extends WebTestCase { - /** - * @var BarcodeGenerator - */ - protected $services; + protected ?LabelBarcodeGenerator $service = null; protected function setUp(): void { self::bootKernel(); - $this->services = self::getContainer()->get(BarcodeGenerator::class); + $this->service = self::getContainer()->get(LabelBarcodeGenerator::class); } public function testGetContent(): void @@ -69,7 +66,7 @@ final class BarcodeGeneratorTest extends WebTestCase foreach (BarcodeType::cases() as $type) { $options = new LabelOptions(); $options->setBarcodeType($type); - $content = $this->services->generateSVG($options, $part); + $content = $this->service->generateSVG($options, $part); //When type is none, service must return null. if (BarcodeType::NONE === $type) { @@ -89,7 +86,7 @@ final class BarcodeGeneratorTest extends WebTestCase foreach (BarcodeType::cases() as $type) { $options = new LabelOptions(); $options->setBarcodeType($type); - $svg = $this->services->generateSVG($options, $part); + $svg = $this->service->generateSVG($options, $part); //When type is none, service must return null. if (BarcodeType::NONE === $type) {