Add new fields to items: id_code, image, location; implement QR code generation and printing; update translations and UI

This commit is contained in:
2025-11-11 17:59:23 +01:00
parent 921a74bbe2
commit a15c976106
61 changed files with 5514 additions and 83 deletions

View File

@@ -0,0 +1,17 @@
<?php
/**
* Class QRCodeOutputException
*
* @filesource QRCodeOutputException.php
* @created 09.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\QRCodeException;
class QRCodeOutputException extends QRCodeException{}

View File

@@ -0,0 +1,113 @@
<?php
/**
* Class QRFpdf
*
* https://github.com/chillerlan/php-qrcode/pull/49
*
* @filesource QRFpdf.php
* @created 03.06.2020
* @package chillerlan\QRCode\Output
* @author Maximilian Kresse
*
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\QRCodeException;
use chillerlan\Settings\SettingsContainerInterface;
use FPDF;
use function array_values, class_exists, count, is_array;
/**
* QRFpdf output module (requires fpdf)
*
* @see https://github.com/Setasign/FPDF
* @see http://www.fpdf.org/
*/
class QRFpdf extends QROutputAbstract{
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
if(!class_exists(FPDF::class)){
// @codeCoverageIgnoreStart
throw new QRCodeException(
'The QRFpdf output requires FPDF as dependency but the class "\FPDF" couldn\'t be found.'
);
// @codeCoverageIgnoreEnd
}
parent::__construct($options, $matrix);
}
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_array($v) || count($v) < 3){
$this->moduleValues[$M_TYPE] = $defaultValue
? [0, 0, 0]
: [255, 255, 255];
}
else{
$this->moduleValues[$M_TYPE] = array_values($v);
}
}
}
/**
* @inheritDoc
*
* @return string|\FPDF
*/
public function dump(?string $file = null){
$file ??= $this->options->cachefile;
$fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, [$this->length, $this->length]);
$fpdf->AddPage();
$prevColor = null;
foreach($this->matrix->matrix() as $y => $row){
foreach($row as $x => $M_TYPE){
/** @var int $M_TYPE */
$color = $this->moduleValues[$M_TYPE];
if($prevColor === null || $prevColor !== $color){
/** @phan-suppress-next-line PhanParamTooFewUnpack */
$fpdf->SetFillColor(...$color);
$prevColor = $color;
}
$fpdf->Rect($x * $this->scale, $y * $this->scale, 1 * $this->scale, 1 * $this->scale, 'F');
}
}
if($this->options->returnResource){
return $fpdf;
}
$pdfData = $fpdf->Output('S');
if($file !== null){
$this->saveToFile($pdfData, $file);
}
if($this->options->imageBase64){
$pdfData = sprintf('data:application/pdf;base64,%s', base64_encode($pdfData));
}
return $pdfData;
}
}

View File

@@ -0,0 +1,217 @@
<?php
/**
* Class QRImage
*
* @filesource QRImage.php
* @created 05.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\{QRCode, QRCodeException};
use chillerlan\Settings\SettingsContainerInterface;
use Exception;
use function array_values, base64_encode, call_user_func, count, extension_loaded, imagecolorallocate, imagecolortransparent,
imagecreatetruecolor, imagedestroy, imagefilledrectangle, imagegif, imagejpeg, imagepng, in_array,
is_array, ob_end_clean, ob_get_contents, ob_start, range, sprintf;
/**
* Converts the matrix into GD images, raw or base64 output (requires ext-gd)
*
* @see http://php.net/manual/book.image.php
*/
class QRImage extends QROutputAbstract{
/**
* GD image types that support transparency
*
* @var string[]
*/
protected const TRANSPARENCY_TYPES = [
QRCode::OUTPUT_IMAGE_PNG,
QRCode::OUTPUT_IMAGE_GIF,
];
protected string $defaultMode = QRCode::OUTPUT_IMAGE_PNG;
/**
* The GD image resource
*
* @see imagecreatetruecolor()
* @var resource|\GdImage
*
* @phan-suppress PhanUndeclaredTypeProperty
*/
protected $image;
/**
* @inheritDoc
*
* @throws \chillerlan\QRCode\QRCodeException
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
if(!extension_loaded('gd')){
throw new QRCodeException('ext-gd not loaded'); // @codeCoverageIgnore
}
parent::__construct($options, $matrix);
}
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_array($v) || count($v) < 3){
$this->moduleValues[$M_TYPE] = $defaultValue
? [0, 0, 0]
: [255, 255, 255];
}
else{
$this->moduleValues[$M_TYPE] = array_values($v);
}
}
}
/**
* @inheritDoc
*
* @return string|resource|\GdImage
*
* @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn
*/
public function dump(?string $file = null){
$file ??= $this->options->cachefile;
$this->image = imagecreatetruecolor($this->length, $this->length);
// avoid: Indirect modification of overloaded property $imageTransparencyBG has no effect
// https://stackoverflow.com/a/10455217
$tbg = $this->options->imageTransparencyBG;
/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
$background = imagecolorallocate($this->image, ...$tbg);
if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
imagecolortransparent($this->image, $background);
}
imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background);
foreach($this->matrix->matrix() as $y => $row){
foreach($row as $x => $M_TYPE){
$this->setPixel($x, $y, $this->moduleValues[$M_TYPE]);
}
}
if($this->options->returnResource){
return $this->image;
}
$imageData = $this->dumpImage();
if($file !== null){
$this->saveToFile($imageData, $file);
}
if($this->options->imageBase64){
$imageData = sprintf('data:image/%s;base64,%s', $this->options->outputType, base64_encode($imageData));
}
return $imageData;
}
/**
* Creates a single QR pixel with the given settings
*/
protected function setPixel(int $x, int $y, array $rgb):void{
imagefilledrectangle(
$this->image,
$x * $this->scale,
$y * $this->scale,
($x + 1) * $this->scale,
($y + 1) * $this->scale,
/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
imagecolorallocate($this->image, ...$rgb)
);
}
/**
* Creates the final image by calling the desired GD output function
*
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
protected function dumpImage():string{
ob_start();
try{
call_user_func([$this, $this->outputMode ?? $this->defaultMode]);
}
// not going to cover edge cases
// @codeCoverageIgnoreStart
catch(Exception $e){
throw new QRCodeOutputException($e->getMessage());
}
// @codeCoverageIgnoreEnd
$imageData = ob_get_contents();
imagedestroy($this->image);
ob_end_clean();
return $imageData;
}
/**
* PNG output
*
* @return void
*/
protected function png():void{
imagepng(
$this->image,
null,
in_array($this->options->pngCompression, range(-1, 9), true)
? $this->options->pngCompression
: -1
);
}
/**
* Jiff - like... JitHub!
*
* @return void
*/
protected function gif():void{
imagegif($this->image);
}
/**
* JPG output
*
* @return void
*/
protected function jpg():void{
imagejpeg(
$this->image,
null,
in_array($this->options->jpegQuality, range(0, 100), true)
? $this->options->jpegQuality
: 85
);
}
}

View File

@@ -0,0 +1,119 @@
<?php
/**
* Class QRImagick
*
* @filesource QRImagick.php
* @created 04.07.2018
* @package chillerlan\QRCode\Output
* @author smiley <smiley@chillerlan.net>
* @copyright 2018 smiley
* @license MIT
*
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\QRCodeException;
use chillerlan\Settings\SettingsContainerInterface;
use Imagick, ImagickDraw, ImagickPixel;
use function extension_loaded, is_string;
/**
* ImageMagick output module (requires ext-imagick)
*
* @see http://php.net/manual/book.imagick.php
* @see http://phpimagick.com
*/
class QRImagick extends QROutputAbstract{
protected Imagick $imagick;
/**
* @inheritDoc
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
if(!extension_loaded('imagick')){
throw new QRCodeException('ext-imagick not loaded'); // @codeCoverageIgnore
}
parent::__construct($options, $matrix);
}
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $type => $defaultValue){
$v = $this->options->moduleValues[$type] ?? null;
if(!is_string($v)){
$this->moduleValues[$type] = $defaultValue
? new ImagickPixel($this->options->markupDark)
: new ImagickPixel($this->options->markupLight);
}
else{
$this->moduleValues[$type] = new ImagickPixel($v);
}
}
}
/**
* @inheritDoc
*
* @return string|\Imagick
*/
public function dump(?string $file = null){
$file ??= $this->options->cachefile;
$this->imagick = new Imagick;
$this->imagick->newImage(
$this->length,
$this->length,
new ImagickPixel($this->options->imagickBG ?? 'transparent'),
$this->options->imagickFormat
);
$this->drawImage();
if($this->options->returnResource){
return $this->imagick;
}
$imageData = $this->imagick->getImageBlob();
if($file !== null){
$this->saveToFile($imageData, $file);
}
return $imageData;
}
/**
* Creates the QR image via ImagickDraw
*/
protected function drawImage():void{
$draw = new ImagickDraw;
foreach($this->matrix->matrix() as $y => $row){
foreach($row as $x => $M_TYPE){
$draw->setStrokeColor($this->moduleValues[$M_TYPE]);
$draw->setFillColor($this->moduleValues[$M_TYPE]);
$draw->rectangle(
$x * $this->scale,
$y * $this->scale,
($x + 1) * $this->scale,
($y + 1) * $this->scale
);
}
}
$this->imagick->drawImage($draw);
}
}

View File

@@ -0,0 +1,162 @@
<?php
/**
* Class QRMarkup
*
* @filesource QRMarkup.php
* @created 17.12.2016
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2016 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\QRCode;
use function is_string, sprintf, strip_tags, trim;
/**
* Converts the matrix into markup types: HTML, SVG, ...
*/
class QRMarkup extends QROutputAbstract{
protected string $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
/**
* @see \sprintf()
*/
protected string $svgHeader = '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" '.
'style="width: 100%%; height: auto;" viewBox="0 0 %2$d %2$d">';
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_string($v)){
$this->moduleValues[$M_TYPE] = $defaultValue
? $this->options->markupDark
: $this->options->markupLight;
}
else{
$this->moduleValues[$M_TYPE] = trim(strip_tags($v), '\'"');
}
}
}
/**
* HTML output
*/
protected function html(?string $file = null):string{
$html = empty($this->options->cssClass)
? '<div>'
: '<div class="'.$this->options->cssClass.'">';
$html .= $this->options->eol;
foreach($this->matrix->matrix() as $row){
$html .= '<div>';
foreach($row as $M_TYPE){
$html .= '<span style="background: '.$this->moduleValues[$M_TYPE].';"></span>';
}
$html .= '</div>'.$this->options->eol;
}
$html .= '</div>'.$this->options->eol;
if($file !== null){
/** @noinspection HtmlRequiredLangAttribute */
return sprintf(
'<!DOCTYPE html><html><head><meta charset="UTF-8"><title>QR Code</title></head><body>%s</body></html>',
$this->options->eol.$html
);
}
return $html;
}
/**
* SVG output
*
* @see https://github.com/codemasher/php-qrcode/pull/5
*/
protected function svg(?string $file = null):string{
$matrix = $this->matrix->matrix();
$svg = sprintf($this->svgHeader, $this->options->cssClass, $this->options->svgViewBoxSize ?? $this->moduleCount)
.$this->options->eol
.'<defs>'.$this->options->svgDefs.'</defs>'
.$this->options->eol;
foreach($this->moduleValues as $M_TYPE => $value){
$path = '';
foreach($matrix as $y => $row){
//we'll combine active blocks within a single row as a lightweight compression technique
$start = null;
$count = 0;
foreach($row as $x => $module){
if($module === $M_TYPE){
$count++;
if($start === null){
$start = $x;
}
if(isset($row[$x + 1])){
continue;
}
}
if($count > 0){
$len = $count;
$start ??= 0; // avoid type coercion in sprintf() - phan happy
$path .= sprintf('M%s %s h%s v1 h-%sZ ', $start, $y, $len, $len);
// reset count
$count = 0;
$start = null;
}
}
}
if(!empty($path)){
$svg .= sprintf(
'<path class="qr-%s %s" stroke="transparent" fill="%s" fill-opacity="%s" d="%s" />',
$M_TYPE, $this->options->cssClass, $value, $this->options->svgOpacity, $path
);
}
}
// close svg
$svg .= '</svg>'.$this->options->eol;
// if saving to file, append the correct headers
if($file !== null){
return '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'.
$this->options->eol.$svg;
}
if($this->options->imageBase64){
$svg = sprintf('data:image/svg+xml;base64,%s', base64_encode($svg));
}
return $svg;
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* Class QROutputAbstract
*
* @filesource QROutputAbstract.php
* @created 09.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\Settings\SettingsContainerInterface;
use function call_user_func_array, dirname, file_put_contents, get_called_class, in_array, is_writable, sprintf;
/**
* common output abstract
*/
abstract class QROutputAbstract implements QROutputInterface{
/**
* the current size of the QR matrix
*
* @see \chillerlan\QRCode\Data\QRMatrix::size()
*/
protected int $moduleCount;
/**
* the current output mode
*
* @see \chillerlan\QRCode\QROptions::$outputType
*/
protected string $outputMode;
/**
* the default output mode of the current output module
*/
protected string $defaultMode;
/**
* the current scaling for a QR pixel
*
* @see \chillerlan\QRCode\QROptions::$scale
*/
protected int $scale;
/**
* the side length of the QR image (modules * scale)
*/
protected int $length;
/**
* an (optional) array of color values for the several QR matrix parts
*/
protected array $moduleValues;
/**
* the (filled) data matrix object
*/
protected QRMatrix $matrix;
/**
* @var \chillerlan\Settings\SettingsContainerInterface|\chillerlan\QRCode\QROptions
*/
protected SettingsContainerInterface $options;
/**
* QROutputAbstract constructor.
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
$this->options = $options;
$this->matrix = $matrix;
$this->moduleCount = $this->matrix->size();
$this->scale = $this->options->scale;
$this->length = $this->moduleCount * $this->scale;
$class = get_called_class();
if(isset(QRCode::OUTPUT_MODES[$class]) && in_array($this->options->outputType, QRCode::OUTPUT_MODES[$class])){
$this->outputMode = $this->options->outputType;
}
$this->setModuleValues();
}
/**
* Sets the initial module values (clean-up & defaults)
*/
abstract protected function setModuleValues():void;
/**
* saves the qr data to a file
*
* @see file_put_contents()
* @see \chillerlan\QRCode\QROptions::cachefile
*
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
protected function saveToFile(string $data, string $file):bool{
if(!is_writable(dirname($file))){
throw new QRCodeOutputException(sprintf('Could not write data to cache file: %s', $file));
}
return (bool)file_put_contents($file, $data);
}
/**
* @inheritDoc
*/
public function dump(?string $file = null){
$file ??= $this->options->cachefile;
// call the built-in output method with the optional file path as parameter
// to make the called method aware if a cache file was given
$data = call_user_func_array([$this, $this->outputMode ?? $this->defaultMode], [$file]);
if($file !== null){
$this->saveToFile($data, $file);
}
return $data;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* Interface QROutputInterface,
*
* @filesource QROutputInterface.php
* @created 02.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
/**
* Converts the data matrix into readable output
*/
interface QROutputInterface{
public const DEFAULT_MODULE_VALUES = [
// light
QRMatrix::M_NULL => false,
QRMatrix::M_DARKMODULE_LIGHT => false,
QRMatrix::M_DATA => false,
QRMatrix::M_FINDER => false,
QRMatrix::M_SEPARATOR => false,
QRMatrix::M_ALIGNMENT => false,
QRMatrix::M_TIMING => false,
QRMatrix::M_FORMAT => false,
QRMatrix::M_VERSION => false,
QRMatrix::M_QUIETZONE => false,
QRMatrix::M_LOGO => false,
QRMatrix::M_FINDER_DOT_LIGHT => false,
// dark
QRMatrix::M_DARKMODULE => true,
QRMatrix::M_DATA_DARK => true,
QRMatrix::M_FINDER_DARK => true,
QRMatrix::M_SEPARATOR_DARK => true,
QRMatrix::M_ALIGNMENT_DARK => true,
QRMatrix::M_TIMING_DARK => true,
QRMatrix::M_FORMAT_DARK => true,
QRMatrix::M_VERSION_DARK => true,
QRMatrix::M_QUIETZONE_DARK => true,
QRMatrix::M_LOGO_DARK => true,
QRMatrix::M_FINDER_DOT => true,
];
/**
* generates the output, optionally dumps it to a file, and returns it
*
* @return mixed
*/
public function dump(?string $file = null);
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* Class QRString
*
* @filesource QRString.php
* @created 05.12.2015
* @package chillerlan\QRCode\Output
* @author Smiley <smiley@chillerlan.net>
* @copyright 2015 Smiley
* @license MIT
*
* @noinspection PhpUnusedParameterInspection
* @noinspection PhpComposerExtensionStubsInspection
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\QRCode;
use function implode, is_string, json_encode;
/**
* Converts the matrix data into string types
*/
class QRString extends QROutputAbstract{
protected string $defaultMode = QRCode::OUTPUT_STRING_TEXT;
/**
* @inheritDoc
*/
protected function setModuleValues():void{
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
$v = $this->options->moduleValues[$M_TYPE] ?? null;
if(!is_string($v)){
$this->moduleValues[$M_TYPE] = $defaultValue
? $this->options->textDark
: $this->options->textLight;
}
else{
$this->moduleValues[$M_TYPE] = $v;
}
}
}
/**
* string output
*/
protected function text(?string $file = null):string{
$str = [];
foreach($this->matrix->matrix() as $row){
$r = [];
foreach($row as $M_TYPE){
$r[] = $this->moduleValues[$M_TYPE];
}
$str[] = implode('', $r);
}
return implode($this->options->eol, $str);
}
/**
* JSON output
*/
protected function json(?string $file = null):string{
return json_encode($this->matrix->matrix());
}
}