Passed
Push — main ( 1f6ae3...de77be )
by smiley
01:54
created

QRCode::addSegment()   A

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

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

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

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

432
			$this->addSegment(new Byte(/** @scrutinizer ignore-type */ $data));
Loading history...
433
434
			return $this;
435
		}
436
437
		throw new QRCodeException('unable to add ECI segment');
438
	}
439
440
	/**
441
	 * Reads a QR Code from a given file
442
	 *
443
	 * @noinspection PhpUndefinedMethodInspection
444
	 */
445
	public function readFromFile(string $path):DecoderResult{
446
		return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options));
447
	}
448
449
	/**
450
	 * Reads a QR Code from the given data blob
451
	 *
452
	 *  @noinspection PhpUndefinedMethodInspection
453
	 */
454
	public function readFromBlob(string $blob):DecoderResult{
455
		return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options));
456
	}
457
458
	/**
459
	 * Reads a QR Code from the given luminance source
460
	 */
461
	public function readFromSource(LuminanceSourceInterface $source):DecoderResult{
462
		return (new Decoder)->decode($source);
463
	}
464
465
}
466