1 | <?php |
||||
2 | /** |
||||
3 | * Class QRCode |
||||
4 | * |
||||
5 | * @created 26.11.2015 |
||||
6 | * @author Smiley <[email protected]> |
||||
7 | * @copyright 2015 Smiley |
||||
8 | * @license MIT |
||||
9 | * |
||||
10 | * @SuppressWarnings(PHPMD.CouplingBetweenObjects) |
||||
11 | */ |
||||
12 | |||||
13 | namespace chillerlan\QRCode; |
||||
14 | |||||
15 | use chillerlan\QRCode\Common\{EccLevel, ECICharset, MaskPattern, Mode, Version}; |
||||
16 | use chillerlan\QRCode\Data\{ |
||||
17 | AlphaNum, Byte, ECI, Hanzi, Kanji, Number, QRData, QRDataModeInterface, QRMatrix |
||||
18 | }; |
||||
19 | use chillerlan\QRCode\Decoder\{Decoder, DecoderResult, GDLuminanceSource, IMagickLuminanceSource, LuminanceSourceInterface}; |
||||
20 | use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface}; |
||||
21 | use chillerlan\Settings\SettingsContainerInterface; |
||||
22 | use function class_exists, class_implements, in_array, mb_convert_encoding, mb_internal_encoding; |
||||
23 | |||||
24 | /** |
||||
25 | * Turns a text string into a Model 2 QR Code |
||||
26 | * |
||||
27 | * @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php |
||||
28 | * @see https://www.qrcode.com/en/codes/model12.html |
||||
29 | * @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf |
||||
30 | * @see https://en.wikipedia.org/wiki/QR_code |
||||
31 | * @see https://www.thonky.com/qr-code-tutorial/ |
||||
32 | */ |
||||
33 | class QRCode{ |
||||
34 | |||||
35 | /** |
||||
36 | * @deprecated 5.0.0 use Version::AUTO instead |
||||
37 | * @see \chillerlan\QRCode\Common\Version::AUTO |
||||
38 | * @var int |
||||
39 | */ |
||||
40 | public const VERSION_AUTO = Version::AUTO; |
||||
41 | |||||
42 | /** |
||||
43 | * @deprecated 5.0.0 use MaskPattern::AUTO instead |
||||
44 | * @see \chillerlan\QRCode\Common\MaskPattern::AUTO |
||||
45 | * @var int |
||||
46 | */ |
||||
47 | public const MASK_PATTERN_AUTO = MaskPattern::AUTO; |
||||
48 | |||||
49 | /** |
||||
50 | * @deprecated 5.0.0 use EccLevel::L instead |
||||
51 | * @see \chillerlan\QRCode\Common\EccLevel::L |
||||
52 | * @var int |
||||
53 | */ |
||||
54 | public const ECC_L = EccLevel::L; |
||||
55 | |||||
56 | /** |
||||
57 | * @deprecated 5.0.0 use EccLevel::M instead |
||||
58 | * @see \chillerlan\QRCode\Common\EccLevel::M |
||||
59 | * @var int |
||||
60 | */ |
||||
61 | public const ECC_M = EccLevel::M; |
||||
62 | |||||
63 | /** |
||||
64 | * @deprecated 5.0.0 use EccLevel::Q instead |
||||
65 | * @see \chillerlan\QRCode\Common\EccLevel::Q |
||||
66 | * @var int |
||||
67 | */ |
||||
68 | public const ECC_Q = EccLevel::Q; |
||||
69 | |||||
70 | /** |
||||
71 | * @deprecated 5.0.0 use EccLevel::H instead |
||||
72 | * @see \chillerlan\QRCode\Common\EccLevel::H |
||||
73 | * @var int |
||||
74 | */ |
||||
75 | public const ECC_H = EccLevel::H; |
||||
76 | |||||
77 | /** |
||||
78 | * @deprecated 5.0.0 use QROutputInterface::MARKUP_HTML instead |
||||
79 | * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_HTML |
||||
80 | * @var string |
||||
81 | */ |
||||
82 | public const OUTPUT_MARKUP_HTML = QROutputInterface::MARKUP_HTML; |
||||
83 | |||||
84 | /** |
||||
85 | * @deprecated 5.0.0 use QROutputInterface::MARKUP_SVG instead |
||||
86 | * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_SVG |
||||
87 | * @var string |
||||
88 | */ |
||||
89 | public const OUTPUT_MARKUP_SVG = QROutputInterface::MARKUP_SVG; |
||||
90 | |||||
91 | /** |
||||
92 | * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_PNG instead |
||||
93 | * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_PNG |
||||
94 | * @var string |
||||
95 | */ |
||||
96 | public const OUTPUT_IMAGE_PNG = QROutputInterface::GDIMAGE_PNG; |
||||
97 | |||||
98 | /** |
||||
99 | * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_JPG instead |
||||
100 | * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_JPG |
||||
101 | * @var string |
||||
102 | */ |
||||
103 | public const OUTPUT_IMAGE_JPG = QROutputInterface::GDIMAGE_JPG; |
||||
104 | |||||
105 | /** |
||||
106 | * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_GIF instead |
||||
107 | * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_GIF |
||||
108 | * @var string |
||||
109 | */ |
||||
110 | public const OUTPUT_IMAGE_GIF = QROutputInterface::GDIMAGE_GIF; |
||||
111 | |||||
112 | /** |
||||
113 | * @deprecated 5.0.0 use QROutputInterface::STRING_JSON instead |
||||
114 | * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_JSON |
||||
115 | * @var string |
||||
116 | */ |
||||
117 | public const OUTPUT_STRING_JSON = QROutputInterface::STRING_JSON; |
||||
118 | |||||
119 | /** |
||||
120 | * @deprecated 5.0.0 use QROutputInterface::STRING_TEXT instead |
||||
121 | * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_TEXT |
||||
122 | * @var string |
||||
123 | */ |
||||
124 | public const OUTPUT_STRING_TEXT = QROutputInterface::STRING_TEXT; |
||||
125 | |||||
126 | /** |
||||
127 | * @deprecated 5.0.0 use QROutputInterface::IMAGICK instead |
||||
128 | * @see \chillerlan\QRCode\Output\QROutputInterface::IMAGICK |
||||
129 | * @var string |
||||
130 | */ |
||||
131 | public const OUTPUT_IMAGICK = QROutputInterface::IMAGICK; |
||||
132 | |||||
133 | /** |
||||
134 | * @deprecated 5.0.0 use QROutputInterface::FPDF instead |
||||
135 | * @see \chillerlan\QRCode\Output\QROutputInterface::FPDF |
||||
136 | * @var string |
||||
137 | */ |
||||
138 | public const OUTPUT_FPDF = QROutputInterface::FPDF; |
||||
139 | |||||
140 | /** |
||||
141 | * @deprecated 5.0.0 use QROutputInterface::EPS instead |
||||
142 | * @see \chillerlan\QRCode\Output\QROutputInterface::EPS |
||||
143 | * @var string |
||||
144 | */ |
||||
145 | public const OUTPUT_EPS = QROutputInterface::EPS; |
||||
146 | |||||
147 | /** |
||||
148 | * @deprecated 5.0.0 use QROutputInterface::CUSTOM instead |
||||
149 | * @see \chillerlan\QRCode\Output\QROutputInterface::CUSTOM |
||||
150 | * @var string |
||||
151 | */ |
||||
152 | public const OUTPUT_CUSTOM = QROutputInterface::CUSTOM; |
||||
153 | |||||
154 | /** |
||||
155 | * @deprecated 5.0.0 use QROutputInterface::MODES instead |
||||
156 | * @see \chillerlan\QRCode\Output\QROutputInterface::MODES |
||||
157 | * @var string[] |
||||
158 | */ |
||||
159 | public const OUTPUT_MODES = QROutputInterface::MODES; |
||||
160 | |||||
161 | /** |
||||
162 | * The settings container |
||||
163 | * |
||||
164 | * @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface |
||||
165 | */ |
||||
166 | protected SettingsContainerInterface $options; |
||||
167 | |||||
168 | /** |
||||
169 | * A collection of one or more data segments of QRDataModeInterface instances to write |
||||
170 | * |
||||
171 | * @var \chillerlan\QRCode\Data\QRDataModeInterface[] |
||||
172 | */ |
||||
173 | protected array $dataSegments = []; |
||||
174 | |||||
175 | /** |
||||
176 | * The luminance source for the reader |
||||
177 | */ |
||||
178 | protected string $luminanceSourceFQN = GDLuminanceSource::class; |
||||
179 | |||||
180 | /** |
||||
181 | * QRCode constructor. |
||||
182 | * |
||||
183 | * PHP8: accept iterable |
||||
184 | */ |
||||
185 | public function __construct(SettingsContainerInterface $options = null){ |
||||
186 | $this->setOptions(($options ?? new QROptions)); |
||||
187 | } |
||||
188 | |||||
189 | /** |
||||
190 | * Sets an options instance |
||||
191 | */ |
||||
192 | public function setOptions(SettingsContainerInterface $options):self{ |
||||
193 | $this->options = $options; |
||||
194 | |||||
195 | if($this->options->readerUseImagickIfAvailable){ |
||||
196 | $this->luminanceSourceFQN = IMagickLuminanceSource::class; |
||||
197 | } |
||||
198 | |||||
199 | return $this; |
||||
200 | } |
||||
201 | |||||
202 | /** |
||||
203 | * Renders a QR Code for the given $data and QROptions, saves $file optionally |
||||
204 | * |
||||
205 | * @return mixed |
||||
206 | */ |
||||
207 | public function render(string $data = null, string $file = null){ |
||||
208 | |||||
209 | if($data !== null){ |
||||
210 | /** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */ |
||||
211 | foreach(Mode::INTERFACES as $dataInterface){ |
||||
212 | |||||
213 | if($dataInterface::validateString($data)){ |
||||
214 | $this->addSegment(new $dataInterface($data)); |
||||
215 | |||||
216 | break; |
||||
217 | } |
||||
218 | } |
||||
219 | } |
||||
220 | |||||
221 | return $this->renderMatrix($this->getQRMatrix(), $file); |
||||
222 | } |
||||
223 | |||||
224 | /** |
||||
225 | * Renders a QR Code for the given QRMatrix and QROptions, saves $file optionally |
||||
226 | * |
||||
227 | * @return mixed |
||||
228 | */ |
||||
229 | public function renderMatrix(QRMatrix $matrix, string $file = null){ |
||||
230 | return $this->initOutputInterface($matrix)->dump($file ?? $this->options->cachefile); |
||||
231 | } |
||||
232 | |||||
233 | /** |
||||
234 | * Returns a QRMatrix object for the given $data and current QROptions |
||||
235 | * |
||||
236 | * @throws \chillerlan\QRCode\Data\QRCodeDataException |
||||
237 | */ |
||||
238 | public function getQRMatrix():QRMatrix{ |
||||
239 | $matrix = (new QRData($this->options, $this->dataSegments))->writeMatrix(); |
||||
240 | |||||
241 | $maskPattern = $this->options->maskPattern === MaskPattern::AUTO |
||||
242 | ? MaskPattern::getBestPattern($matrix) |
||||
243 | : new MaskPattern($this->options->maskPattern); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
244 | |||||
245 | $matrix->setFormatInfo($maskPattern)->mask($maskPattern); |
||||
246 | |||||
247 | return $this->addMatrixModifications($matrix); |
||||
248 | } |
||||
249 | |||||
250 | /** |
||||
251 | * add matrix modifications after mask pattern evaluation and before handing over to output |
||||
252 | */ |
||||
253 | protected function addMatrixModifications(QRMatrix $matrix):QRMatrix{ |
||||
254 | |||||
255 | if($this->options->addLogoSpace){ |
||||
256 | $logoSpaceWidth = $this->options->logoSpaceWidth; |
||||
257 | $logoSpaceHeight = $this->options->logoSpaceHeight; |
||||
258 | |||||
259 | // check whether one of the dimensions was omitted |
||||
260 | if($logoSpaceWidth === null || $logoSpaceHeight === null){ |
||||
261 | $logoSpaceWidth = ($logoSpaceWidth ?? $logoSpaceHeight ?? 0); |
||||
262 | $logoSpaceHeight = null; |
||||
263 | } |
||||
264 | |||||
265 | $matrix->setLogoSpace( |
||||
266 | $logoSpaceWidth, |
||||
267 | $logoSpaceHeight, |
||||
268 | $this->options->logoSpaceStartX, |
||||
269 | $this->options->logoSpaceStartY |
||||
270 | ); |
||||
271 | } |
||||
272 | |||||
273 | if($this->options->addQuietzone){ |
||||
274 | $matrix->setQuietZone($this->options->quietzoneSize); |
||||
0 ignored issues
–
show
It seems like
$this->options->quietzoneSize can also be of type null ; however, parameter $quietZoneSize of chillerlan\QRCode\Data\QRMatrix::setQuietZone() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
275 | } |
||||
276 | |||||
277 | return $matrix; |
||||
278 | } |
||||
279 | |||||
280 | /** |
||||
281 | * @deprecated 5.0.0 use QRCode::getQRMatrix() instead |
||||
282 | * @see \chillerlan\QRCode\QRCode::getQRMatrix() |
||||
283 | * @codeCoverageIgnore |
||||
284 | */ |
||||
285 | public function getMatrix():QRMatrix{ |
||||
286 | return $this->getQRMatrix(); |
||||
287 | } |
||||
288 | |||||
289 | /** |
||||
290 | * initializes a fresh built-in or custom QROutputInterface |
||||
291 | * |
||||
292 | * @throws \chillerlan\QRCode\Output\QRCodeOutputException |
||||
293 | */ |
||||
294 | protected function initOutputInterface(QRMatrix $matrix):QROutputInterface{ |
||||
295 | $outputInterface =( QROutputInterface::MODES[$this->options->outputType] ?? null); |
||||
296 | |||||
297 | if($this->options->outputType === QROutputInterface::CUSTOM){ |
||||
298 | $outputInterface = $this->options->outputInterface; |
||||
299 | } |
||||
300 | |||||
301 | if(!$outputInterface || !class_exists($outputInterface)){ |
||||
302 | throw new QRCodeOutputException('invalid output module'); |
||||
303 | } |
||||
304 | |||||
305 | if(!in_array(QROutputInterface::class, class_implements($outputInterface))){ |
||||
306 | throw new QRCodeOutputException('output module does not implement QROutputInterface'); |
||||
307 | } |
||||
308 | |||||
309 | return new $outputInterface($this->options, $matrix); |
||||
310 | } |
||||
311 | |||||
312 | /** |
||||
313 | * checks if a string qualifies as numeric (convenience method) |
||||
314 | * |
||||
315 | * @deprecated 5.0.0 use Number::validateString() instead |
||||
316 | * @see \chillerlan\QRCode\Data\Number::validateString() |
||||
317 | * @codeCoverageIgnore |
||||
318 | */ |
||||
319 | public function isNumber(string $string):bool{ |
||||
320 | return Number::validateString($string); |
||||
321 | } |
||||
322 | |||||
323 | /** |
||||
324 | * checks if a string qualifies as alphanumeric (convenience method) |
||||
325 | * |
||||
326 | * @deprecated 5.0.0 use AlphaNum::validateString() instead |
||||
327 | * @see \chillerlan\QRCode\Data\AlphaNum::validateString() |
||||
328 | * @codeCoverageIgnore |
||||
329 | */ |
||||
330 | public function isAlphaNum(string $string):bool{ |
||||
331 | return AlphaNum::validateString($string); |
||||
332 | } |
||||
333 | |||||
334 | /** |
||||
335 | * checks if a string qualifies as Kanji (convenience method) |
||||
336 | * |
||||
337 | * @deprecated 5.0.0 use Kanji::validateString() instead |
||||
338 | * @see \chillerlan\QRCode\Data\Kanji::validateString() |
||||
339 | * @codeCoverageIgnore |
||||
340 | */ |
||||
341 | public function isKanji(string $string):bool{ |
||||
342 | return Kanji::validateString($string); |
||||
343 | } |
||||
344 | |||||
345 | /** |
||||
346 | * a dummy (convenience method) |
||||
347 | * |
||||
348 | * @deprecated 5.0.0 use Byte::validateString() instead |
||||
349 | * @see \chillerlan\QRCode\Data\Byte::validateString() |
||||
350 | * @codeCoverageIgnore |
||||
351 | */ |
||||
352 | public function isByte(string $string):bool{ |
||||
353 | return Byte::validateString($string); |
||||
354 | } |
||||
355 | |||||
356 | /** |
||||
357 | * Adds a data segment |
||||
358 | * |
||||
359 | * ISO/IEC 18004:2000 8.3.6 - Mixing modes |
||||
360 | * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length |
||||
361 | */ |
||||
362 | public function addSegment(QRDataModeInterface $segment):self{ |
||||
363 | $this->dataSegments[] = $segment; |
||||
364 | |||||
365 | return $this; |
||||
366 | } |
||||
367 | |||||
368 | /** |
||||
369 | * Clears the data segments array |
||||
370 | * |
||||
371 | * @codeCoverageIgnore |
||||
372 | */ |
||||
373 | public function clearSegments():self{ |
||||
374 | $this->dataSegments = []; |
||||
375 | |||||
376 | return $this; |
||||
377 | } |
||||
378 | |||||
379 | /** |
||||
380 | * Adds a numeric data segment |
||||
381 | * |
||||
382 | * ISO/IEC 18004:2000 8.3.2 - Numeric Mode |
||||
383 | */ |
||||
384 | public function addNumericSegment(string $data):self{ |
||||
385 | return $this->addSegment(new Number($data)); |
||||
386 | } |
||||
387 | |||||
388 | /** |
||||
389 | * Adds an alphanumeric data segment |
||||
390 | * |
||||
391 | * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode |
||||
392 | */ |
||||
393 | public function addAlphaNumSegment(string $data):self{ |
||||
394 | return $this->addSegment(new AlphaNum($data)); |
||||
395 | } |
||||
396 | |||||
397 | /** |
||||
398 | * Adds a Kanji data segment (Japanese 13-bit double-byte characters, Shift-JIS) |
||||
399 | * |
||||
400 | * ISO/IEC 18004:2000 8.3.5 - Kanji Mode |
||||
401 | */ |
||||
402 | public function addKanjiSegment(string $data):self{ |
||||
403 | return $this->addSegment(new Kanji($data)); |
||||
404 | } |
||||
405 | |||||
406 | /** |
||||
407 | * Adds a Hanzi data segment (simplified Chinese 13-bit double-byte characters, GB2312/GB18030) |
||||
408 | * |
||||
409 | * GBT18284-2000 Hanzi Mode |
||||
410 | */ |
||||
411 | public function addHanziSegment(string $data):self{ |
||||
412 | return $this->addSegment(new Hanzi($data)); |
||||
413 | } |
||||
414 | |||||
415 | /** |
||||
416 | * Adds an 8-bit byte data segment |
||||
417 | * |
||||
418 | * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode |
||||
419 | */ |
||||
420 | public function addByteSegment(string $data):self{ |
||||
421 | return $this->addSegment(new Byte($data)); |
||||
422 | } |
||||
423 | |||||
424 | /** |
||||
425 | * Adds a standalone ECI designator |
||||
426 | * |
||||
427 | * The ECI designator must be followed by a Byte segment that contains the string encoded according to the given ECI charset |
||||
428 | * |
||||
429 | * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode |
||||
430 | */ |
||||
431 | public function addEciDesignator(int $encoding):self{ |
||||
432 | return $this->addSegment(new ECI($encoding)); |
||||
433 | } |
||||
434 | |||||
435 | /** |
||||
436 | * Adds an ECI data segment (including designator) |
||||
437 | * |
||||
438 | * The given string will be encoded from mb_internal_encoding() to the given ECI character set |
||||
439 | * |
||||
440 | * I hate this somehow, but I'll leave it for now |
||||
441 | * |
||||
442 | * @throws \chillerlan\QRCode\QRCodeException |
||||
443 | */ |
||||
444 | public function addEciSegment(int $encoding, string $data):self{ |
||||
445 | // validate the encoding id |
||||
446 | $eciCharset = new ECICharset($encoding); |
||||
447 | // get charset name |
||||
448 | $eciCharsetName = $eciCharset->getName(); |
||||
449 | // convert the string to the given charset |
||||
450 | if($eciCharsetName !== null){ |
||||
451 | $data = mb_convert_encoding($data, $eciCharsetName, mb_internal_encoding()); |
||||
0 ignored issues
–
show
It seems like
mb_internal_encoding() can also be of type true ; however, parameter $from_encoding of mb_convert_encoding() does only seem to accept array|null|string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
452 | |||||
453 | return $this |
||||
454 | ->addEciDesignator($eciCharset->getID()) |
||||
455 | ->addByteSegment($data) |
||||
0 ignored issues
–
show
It seems like
$data can also be of type array ; however, parameter $data of chillerlan\QRCode\QRCode::addByteSegment() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
456 | ; |
||||
457 | } |
||||
458 | |||||
459 | throw new QRCodeException('unable to add ECI segment'); |
||||
460 | } |
||||
461 | |||||
462 | /** |
||||
463 | * Reads a QR Code from a given file |
||||
464 | * |
||||
465 | * @noinspection PhpUndefinedMethodInspection |
||||
466 | */ |
||||
467 | public function readFromFile(string $path):DecoderResult{ |
||||
468 | return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options)); |
||||
469 | } |
||||
470 | |||||
471 | /** |
||||
472 | * Reads a QR Code from the given data blob |
||||
473 | * |
||||
474 | * @noinspection PhpUndefinedMethodInspection |
||||
475 | */ |
||||
476 | public function readFromBlob(string $blob):DecoderResult{ |
||||
477 | return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options)); |
||||
478 | } |
||||
479 | |||||
480 | /** |
||||
481 | * Reads a QR Code from the given luminance source |
||||
482 | */ |
||||
483 | public function readFromSource(LuminanceSourceInterface $source):DecoderResult{ |
||||
484 | return (new Decoder)->decode($source); |
||||
485 | } |
||||
486 | |||||
487 | } |
||||
488 |