Passed
Push — main ( cc58c4...8e9887 )
by smiley
01:59
created

QRCode::readFromFile()   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
nc 1
nop 1
dl 0
loc 2
rs 10
c 1
b 0
f 0
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, 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
	 * @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
	public function __construct(SettingsContainerInterface $options = null){
182
		$this->setOptions($options ?? new QROptions);
183
	}
184
185
	/**
186
	 * Sets an options instance
187
	 */
188
	public function setOptions(SettingsContainerInterface $options):self{
189
		$this->options = $options;
190
191
		if($this->options->readerUseImagickIfAvailable){
192
			$this->luminanceSourceFQN = IMagickLuminanceSource::class;
193
		}
194
195
		return $this;
196
	}
197
198
	/**
199
	 * Renders a QR Code for the given $data and QROptions, saves $file optionally
200
	 *
201
	 * @return mixed
202
	 */
203
	public function render(string $data = null, string $file = null){
204
205
		if($data !== null){
206
			/** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */
207
			foreach(Mode::INTERFACES as $dataInterface){
208
209
				if($dataInterface::validateString($data)){
210
					$this->addSegment(new $dataInterface($data));
211
212
					break;
213
				}
214
			}
215
		}
216
217
		return $this->renderMatrix($this->getMatrix(), $file);
218
	}
219
220
	/**
221
	 * Renders a QR Code for the given QRMatrix and QROptions, saves $file optionally
222
	 *
223
	 * @return mixed
224
	 */
225
	public function renderMatrix(QRMatrix $matrix, string $file = null){
226
		return $this->initOutputInterface($matrix)->dump($file);
227
	}
228
229
	/**
230
	 * Returns a QRMatrix object for the given $data and current QROptions
231
	 *
232
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
233
	 */
234
	public function getMatrix():QRMatrix{
235
		$dataInterface = new QRData($this->options, $this->dataSegments);
236
		$maskPattern   = $this->options->maskPattern === MaskPattern::AUTO
237
			? MaskPattern::getBestPattern($dataInterface)
238
			: 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

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

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

439
			$data = mb_convert_encoding($data, $eciCharsetName, /** @scrutinizer ignore-type */ mb_internal_encoding());
Loading history...
440
			$this
441
				->addEciDesignator($eciCharset->getID())
442
				->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

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