QRCode::isAlphaNum()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
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
It seems like $this->options->maskPattern can also be of type null; however, parameter $maskPattern of chillerlan\QRCode\Common...kPattern::__construct() 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 ignore-type  annotation

243
			: new MaskPattern(/** @scrutinizer ignore-type */ $this->options->maskPattern);
Loading history...
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
Bug introduced by
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 ignore-type  annotation

274
			$matrix->setQuietZone(/** @scrutinizer ignore-type */ $this->options->quietzoneSize);
Loading history...
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
Bug introduced by
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 ignore-type  annotation

451
			$data = mb_convert_encoding($data, $eciCharsetName, /** @scrutinizer ignore-type */ mb_internal_encoding());
Loading history...
452
453
			return $this
454
				->addEciDesignator($eciCharset->getID())
455
				->addByteSegment($data)
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

455
				->addByteSegment(/** @scrutinizer ignore-type */ $data)
Loading history...
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