Passed
Push — main ( 9e04d2...06a3ca )
by smiley
02:18
created

QRCode::addHanziSegment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
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
11
namespace chillerlan\QRCode;
12
13
use chillerlan\QRCode\Common\{EccLevel, ECICharset, MaskPattern, Mode, Version};
14
use chillerlan\QRCode\Data\{
15
	AlphaNum, Byte, ECI, Hanzi, Kanji, Number, QRCodeDataException, QRData, QRDataModeInterface, QRMatrix
16
};
17
use chillerlan\QRCode\Decoder\{Decoder, DecoderResult, GDLuminanceSource, IMagickLuminanceSource, LuminanceSourceInterface};
18
use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface};
19
use chillerlan\Settings\SettingsContainerInterface;
20
use function class_exists, class_implements, in_array, mb_convert_encoding, mb_internal_encoding;
21
22
/**
23
 * Turns a text string into a Model 2 QR Code
24
 *
25
 * @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
26
 * @see http://www.qrcode.com/en/codes/model12.html
27
 * @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf
28
 * @see https://en.wikipedia.org/wiki/QR_code
29
 * @see http://www.thonky.com/qr-code-tutorial/
30
 */
31
class QRCode{
32
33
	/**
34
	 * @deprecated 5.0.0 use Version::AUTO instead
35
	 * @see \chillerlan\QRCode\Common\Version::AUTO
36
	 * @var int
37
	 */
38
	public const VERSION_AUTO      = Version::AUTO;
39
40
	/**
41
	 * @deprecated 5.0.0 use MaskPattern::AUTO instead
42
	 * @see \chillerlan\QRCode\Common\MaskPattern::AUTO
43
	 * @var int
44
	 */
45
	public const MASK_PATTERN_AUTO = MaskPattern::AUTO;
46
47
	/**
48
	 * @deprecated 5.0.0 use EccLevel::L instead
49
	 * @see \chillerlan\QRCode\Common\EccLevel::L
50
	 * @var int
51
	 */
52
	public const ECC_L = EccLevel::L;
53
54
	/**
55
	 * @deprecated 5.0.0 use EccLevel::M instead
56
	 * @see \chillerlan\QRCode\Common\EccLevel::M
57
	 * @var int
58
	 */
59
	public const ECC_M = EccLevel::M;
60
61
	/**
62
	 * @deprecated 5.0.0 use EccLevel::Q instead
63
	 * @see \chillerlan\QRCode\Common\EccLevel::Q
64
	 * @var int
65
	 */
66
	public const ECC_Q = EccLevel::Q;
67
68
	/**
69
	 * @deprecated 5.0.0 use EccLevel::H instead
70
	 * @see \chillerlan\QRCode\Common\EccLevel::H
71
	 * @var int
72
	 */
73
	public const ECC_H = EccLevel::H;
74
75
	/**
76
	 * @deprecated 5.0.0 use QROutputInterface::MARKUP_HTML instead
77
	 * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_HTML
78
	 * @var string
79
	 */
80
	public const OUTPUT_MARKUP_HTML = QROutputInterface::MARKUP_HTML;
81
82
	/**
83
	 * @deprecated 5.0.0 use QROutputInterface::MARKUP_SVG instead
84
	 * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_SVG
85
	 * @var string
86
	 */
87
	public const OUTPUT_MARKUP_SVG  = QROutputInterface::MARKUP_SVG;
88
89
	/**
90
	 * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_PNG instead
91
	 * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_PNG
92
	 * @var string
93
	 */
94
	public const OUTPUT_IMAGE_PNG   = QROutputInterface::GDIMAGE_PNG;
95
96
	/**
97
	 * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_JPG instead
98
	 * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_JPG
99
	 * @var string
100
	 */
101
	public const OUTPUT_IMAGE_JPG   = QROutputInterface::GDIMAGE_JPG;
102
103
	/**
104
	 * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_GIF instead
105
	 * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_GIF
106
	 * @var string
107
	 */
108
	public const OUTPUT_IMAGE_GIF   = QROutputInterface::GDIMAGE_GIF;
109
110
	/**
111
	 * @deprecated 5.0.0 use QROutputInterface::STRING_JSON instead
112
	 * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_JSON
113
	 * @var string
114
	 */
115
	public const OUTPUT_STRING_JSON = QROutputInterface::STRING_JSON;
116
117
	/**
118
	 * @deprecated 5.0.0 use QROutputInterface::STRING_TEXT instead
119
	 * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_TEXT
120
	 * @var string
121
	 */
122
	public const OUTPUT_STRING_TEXT = QROutputInterface::STRING_TEXT;
123
124
	/**
125
	 * @deprecated 5.0.0 use QROutputInterface::IMAGICK instead
126
	 * @see \chillerlan\QRCode\Output\QROutputInterface::IMAGICK
127
	 * @var string
128
	 */
129
	public const OUTPUT_IMAGICK     = QROutputInterface::IMAGICK;
130
131
	/**
132
	 * @deprecated 5.0.0 use QROutputInterface::FPDF instead
133
	 * @see \chillerlan\QRCode\Output\QROutputInterface::FPDF
134
	 * @var string
135
	 */
136
	public const OUTPUT_FPDF        = QROutputInterface::FPDF;
137
138
	/**
139
	 * @deprecated 5.0.0 use QROutputInterface::EPS instead
140
	 * @see \chillerlan\QRCode\Output\QROutputInterface::EPS
141
	 * @var string
142
	 */
143
	public const OUTPUT_EPS         = QROutputInterface::EPS;
144
145
	/**
146
	 * @deprecated 5.0.0 use QROutputInterface::CUSTOM instead
147
	 * @see \chillerlan\QRCode\Output\QROutputInterface::CUSTOM
148
	 * @var string
149
	 */
150
	public const OUTPUT_CUSTOM      = QROutputInterface::CUSTOM;
151
152
	/**
153
	 * @deprecated 5.0.0 use QROutputInterface::MODES instead
154
	 * @see \chillerlan\QRCode\Output\QROutputInterface::MODES
155
	 * @var string[]
156
	 */
157
	public const OUTPUT_MODES       = QROutputInterface::MODES;
158
159
	/**
160
	 * The settings container
161
	 *
162
	 * @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
163
	 */
164
	protected SettingsContainerInterface $options;
165
166
	/**
167
	 * A collection of one or more data segments of [classname, data] to write
168
	 *
169
	 * @see \chillerlan\QRCode\Data\QRDataModeInterface
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
	 * Sets the options instance
184
	 */
185
	public function __construct(SettingsContainerInterface $options = null){
186
		$this->options = $options ?? new QROptions;
187
188
		// i hate this less
189
		if($this->options->readerUseImagickIfAvailable){
190
			$this->luminanceSourceFQN = IMagickLuminanceSource::class;
191
		}
192
	}
193
194
	/**
195
	 * Renders a QR Code for the given $data and QROptions, saves $file optionally
196
	 *
197
	 * @return mixed
198
	 */
199
	public function render(string $data = null, string $file = null){
200
201
		if($data !== null){
202
			/** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */
203
			foreach(Mode::INTERFACES as $dataInterface){
204
205
				if($dataInterface::validateString($data)){
206
					$this->addSegment(new $dataInterface($data));
207
208
					break;
209
				}
210
			}
211
		}
212
213
		return $this->initOutputInterface()->dump($file);
214
	}
215
216
	/**
217
	 * Returns a QRMatrix object for the given $data and current QROptions
218
	 *
219
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
220
	 */
221
	public function getMatrix():QRMatrix{
222
223
		if(empty($this->dataSegments)){
224
			throw new QRCodeDataException('QRCode::getMatrix() No data given.');
225
		}
226
227
		$dataInterface = new QRData($this->options, $this->dataSegments);
228
		$maskPattern   = $this->options->maskPattern === MaskPattern::AUTO
229
			? MaskPattern::getBestPattern($dataInterface)
230
			: 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

230
			: new MaskPattern(/** @scrutinizer ignore-type */ $this->options->maskPattern);
Loading history...
231
232
		$matrix = $dataInterface->writeMatrix($maskPattern);
233
234
		// add matrix modifications after mask pattern evaluation and before handing over to output
235
		if($this->options->addLogoSpace){
236
			$logoSpaceWidth  = $this->options->logoSpaceWidth;
237
			$logoSpaceHeight = $this->options->logoSpaceHeight;
238
239
			// check whether one of the dimensions was omitted
240
			if($logoSpaceWidth === null || $logoSpaceHeight === null){
241
				$logoSpaceWidth  = $logoSpaceWidth ?? $logoSpaceHeight ?? 0;
242
				$logoSpaceHeight = null;
243
			}
244
245
			$matrix->setLogoSpace(
246
				$logoSpaceWidth,
247
				$logoSpaceHeight,
248
				$this->options->logoSpaceStartX,
249
				$this->options->logoSpaceStartY
250
			);
251
		}
252
253
		if($this->options->addQuietzone){
254
			$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

254
			$matrix->setQuietZone(/** @scrutinizer ignore-type */ $this->options->quietzoneSize);
Loading history...
255
		}
256
257
		return $matrix;
258
	}
259
260
	/**
261
	 * returns a fresh (built-in) QROutputInterface
262
	 *
263
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
264
	 */
265
	protected function initOutputInterface():QROutputInterface{
266
267
		if($this->options->outputType === QROutputInterface::CUSTOM){
268
			return $this->initCustomOutputInterface();
269
		}
270
271
		$outputInterface = QROutputInterface::MODES[$this->options->outputType] ?? false;
272
273
		if($outputInterface){
274
			return new $outputInterface($this->options, $this->getMatrix());
275
		}
276
277
		throw new QRCodeOutputException('invalid output type');
278
	}
279
280
	/**
281
	 * initializes a custom output module after checking the existence of the class and if it implemnts the required interface
282
	 *
283
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
284
	 */
285
	protected function initCustomOutputInterface():QROutputInterface{
286
287
		if(!class_exists($this->options->outputInterface)){
0 ignored issues
show
Bug introduced by
It seems like $this->options->outputInterface can also be of type null; however, parameter $class of class_exists() 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

287
		if(!class_exists(/** @scrutinizer ignore-type */ $this->options->outputInterface)){
Loading history...
288
			throw new QRCodeOutputException('invalid custom output module');
289
		}
290
291
		if(!in_array(QROutputInterface::class, class_implements($this->options->outputInterface))){
292
			throw new QRCodeOutputException('custom output module does not implement QROutputInterface');
293
		}
294
295
		/** @phan-suppress-next-line PhanTypeExpectedObjectOrClassName */
296
		return new $this->options->outputInterface($this->options, $this->getMatrix());
297
	}
298
299
	/**
300
	 * checks if a string qualifies as numeric (convenience method)
301
	 *
302
	 * @deprecated 5.0.0 use Number::validateString() instead
303
	 * @see \chillerlan\QRCode\Data\Number::validateString()
304
	 * @codeCoverageIgnore
305
	 */
306
	public function isNumber(string $string):bool{
307
		return Number::validateString($string);
308
	}
309
310
	/**
311
	 * checks if a string qualifies as alphanumeric (convenience method)
312
	 *
313
	 * @deprecated 5.0.0 use AlphaNum::validateString() instead
314
	 * @see \chillerlan\QRCode\Data\AlphaNum::validateString()
315
	 * @codeCoverageIgnore
316
	 */
317
	public function isAlphaNum(string $string):bool{
318
		return AlphaNum::validateString($string);
319
	}
320
321
	/**
322
	 * checks if a string qualifies as Kanji (convenience method)
323
	 *
324
	 * @deprecated 5.0.0 use Kanji::validateString() instead
325
	 * @see \chillerlan\QRCode\Data\Kanji::validateString()
326
	 * @codeCoverageIgnore
327
	 */
328
	public function isKanji(string $string):bool{
329
		return Kanji::validateString($string);
330
	}
331
332
	/**
333
	 * a dummy (convenience method)
334
	 *
335
	 * @deprecated 5.0.0 use Byte::validateString() instead
336
	 * @see \chillerlan\QRCode\Data\Byte::validateString()
337
	 * @codeCoverageIgnore
338
	 */
339
	public function isByte(string $string):bool{
340
		return Byte::validateString($string);
341
	}
342
343
	/**
344
	 * Adds a data segment
345
	 *
346
	 * ISO/IEC 18004:2000 8.3.6 - Mixing modes
347
	 * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length
348
	 */
349
	public function addSegment(QRDataModeInterface $segment):void{
350
		$this->dataSegments[] = $segment;
351
	}
352
353
	/**
354
	 * Clears the data segments array
355
	 */
356
	public function clearSegments():self{
357
		$this->dataSegments = [];
358
359
		return $this;
360
	}
361
362
	/**
363
	 * Adds a numeric data segment
364
	 *
365
	 * ISO/IEC 18004:2000 8.3.2 - Numeric Mode
366
	 */
367
	public function addNumericSegment(string $data):self{
368
		$this->addSegment(new Number($data));
369
370
		return $this;
371
	}
372
373
	/**
374
	 * Adds an alphanumeric data segment
375
	 *
376
	 * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode
377
	 */
378
	public function addAlphaNumSegment(string $data):self{
379
		$this->addSegment(new AlphaNum($data));
380
381
		return $this;
382
	}
383
384
	/**
385
	 * Adds a Kanji data segment (Japanese double-byte characters, Shift-JIS)
386
	 *
387
	 * ISO/IEC 18004:2000 8.3.5 - Kanji Mode
388
	 */
389
	public function addKanjiSegment(string $data):self{
390
		$this->addSegment(new Kanji($data));
391
392
		return $this;
393
	}
394
395
	/**
396
	 * Adds a Hanzi data segment (simplified Chinese double-byte characters, GB2312/GB18030)
397
	 *
398
	 * GBT18284-2000 Hanzi Mode
399
	 */
400
	public function addHanziSegment(string $data):self{
401
		$this->addSegment(new Hanzi($data));
402
403
		return $this;
404
	}
405
406
	/**
407
	 * Adds an 8-bit byte data segment
408
	 *
409
	 * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode
410
	 */
411
	public function addByteSegment(string $data):self{
412
		$this->addSegment(new Byte($data));
413
414
		return $this;
415
	}
416
417
	/**
418
	 * Adds a standalone ECI designator
419
	 *
420
	 * The ECI designator must be followed by a Byte segment that contains the string encoded according to the given ECI charset
421
	 *
422
	 * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode
423
	 */
424
	public function addEciDesignator(int $encoding):self{
425
		$this->addSegment(new ECI($encoding));
426
427
		return $this;
428
	}
429
430
	/**
431
	 * Adds an ECI data segment (including designator)
432
	 *
433
	 * The given string will be encoded from mb_internal_encoding() to the given ECI character set
434
	 *
435
	 * i hate this somehow but i'll leave it for now
436
	 *
437
	 * @throws \chillerlan\QRCode\QRCodeException
438
	 */
439
	public function addEciSegment(int $encoding, string $data):self{
440
		// validate the encoding id
441
		$eciCharset = new ECICharset($encoding);
442
		// get charset name
443
		$eciCharsetName = $eciCharset->getName();
444
		// convert the string to the given charset
445
		if($eciCharsetName !== null){
446
			$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

446
			$data = mb_convert_encoding($data, $eciCharsetName, /** @scrutinizer ignore-type */ mb_internal_encoding());
Loading history...
447
			// add ECI designator
448
			$this->addSegment(new ECI($eciCharset->getID()));
449
			$this->addSegment(new Byte($data));
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type array; however, parameter $data of chillerlan\QRCode\Data\Byte::__construct() 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

449
			$this->addSegment(new Byte(/** @scrutinizer ignore-type */ $data));
Loading history...
450
451
			return $this;
452
		}
453
454
		throw new QRCodeException('unable to add ECI segment');
455
	}
456
457
	/**
458
	 * Reads a QR Code from a given file
459
	 *
460
	 * @noinspection PhpUndefinedMethodInspection
461
	 */
462
	public function readFromFile(string $path):DecoderResult{
463
		return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options));
464
	}
465
466
	/**
467
	 * Reads a QR Code from the given data blob
468
	 *
469
	 *  @noinspection PhpUndefinedMethodInspection
470
	 */
471
	public function readFromBlob(string $blob):DecoderResult{
472
		return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options));
473
	}
474
475
	/**
476
	 * Reads a QR Code from the given luminance source
477
	 */
478
	public function readFromSource(LuminanceSourceInterface $source):DecoderResult{
479
		return (new Decoder)->decode($source);
480
	}
481
482
}
483