Passed
Push — master ( 552d15...d4c1db )
by smiley
02:51
created

QRCode::initOutputInterface()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 15
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 6
nc 4
nop 1
dl 0
loc 15
rs 9.2222
c 1
b 0
f 0
1
<?php
2
/**
3
 * Class QRCode
4
 *
5
 * @filesource   QRCode.php
6
 * @created      26.11.2015
7
 * @package      chillerlan\QRCode
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2015 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\QRCode;
14
15
use chillerlan\QRCode\Data\{
16
	AlphaNum, Byte, Kanji, MaskPatternTester, Number, QRCodeDataException, QRDataInterface, QRMatrix
0 ignored issues
show
Bug introduced by
The type chillerlan\QRCode\Data\QRMatrix was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
};
18
use chillerlan\QRCode\Output\{
19
	QRCodeOutputException, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString
20
};
21
use chillerlan\Settings\SettingsContainerInterface;
22
23
use function call_user_func_array, class_exists, in_array, mb_internal_encoding, ord, strlen, strtolower, str_split;
24
25
/**
26
 * Turns a text string into a Model 2 QR Code
27
 *
28
 * @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
29
 * @see http://www.qrcode.com/en/codes/model12.html
30
 * @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf
31
 * @see https://en.wikipedia.org/wiki/QR_code
32
 * @see http://www.thonky.com/qr-code-tutorial/
33
 */
34
class QRCode{
35
36
	/** @var int */
37
	public const VERSION_AUTO       = -1;
38
	/** @var int */
39
	public const MASK_PATTERN_AUTO  = -1;
40
41
	// ISO/IEC 18004:2000 Table 2
42
43
	/** @var int */
44
	public const DATA_NUMBER   = 0b0001;
45
	/** @var int */
46
	public const DATA_ALPHANUM = 0b0010;
47
	/** @var int */
48
	public const DATA_BYTE     = 0b0100;
49
	/** @var int */
50
	public const DATA_KANJI    = 0b1000;
51
52
	/**
53
	 * References to the keys of the following tables:
54
	 *
55
	 * @see \chillerlan\QRCode\Data\QRDataInterface::MAX_LENGTH
56
	 *
57
	 * @var int[]
58
	 */
59
	public const DATA_MODES = [
60
		self::DATA_NUMBER   => 0,
61
		self::DATA_ALPHANUM => 1,
62
		self::DATA_BYTE     => 2,
63
		self::DATA_KANJI    => 3,
64
	];
65
66
	// ISO/IEC 18004:2000 Tables 12, 25
67
68
	/** @var int */
69
	public const ECC_L = 0b01; // 7%.
70
	/** @var int */
71
	public const ECC_M = 0b00; // 15%.
72
	/** @var int */
73
	public const ECC_Q = 0b11; // 25%.
74
	/** @var int */
75
	public const ECC_H = 0b10; // 30%.
76
77
	/**
78
	 * References to the keys of the following tables:
79
	 *
80
	 * @see \chillerlan\QRCode\Data\QRDataInterface::MAX_BITS
81
	 * @see \chillerlan\QRCode\Data\QRDataInterface::RSBLOCKS
82
	 * @see \chillerlan\QRCode\Data\QRMatrix::formatPattern
83
	 *
84
	 * @var int[]
85
	 */
86
	public const ECC_MODES = [
87
		self::ECC_L => 0,
88
		self::ECC_M => 1,
89
		self::ECC_Q => 2,
90
		self::ECC_H => 3,
91
	];
92
93
	/** @var string */
94
	public const OUTPUT_MARKUP_HTML = 'html';
95
	/** @var string */
96
	public const OUTPUT_MARKUP_SVG  = 'svg';
97
	/** @var string */
98
	public const OUTPUT_IMAGE_PNG   = 'png';
99
	/** @var string */
100
	public const OUTPUT_IMAGE_JPG   = 'jpg';
101
	/** @var string */
102
	public const OUTPUT_IMAGE_GIF   = 'gif';
103
	/** @var string */
104
	public const OUTPUT_STRING_JSON = 'json';
105
	/** @var string */
106
	public const OUTPUT_STRING_TEXT = 'text';
107
	/** @var string */
108
	public const OUTPUT_IMAGICK     = 'imagick';
109
	/** @var string */
110
	public const OUTPUT_CUSTOM      = 'custom';
111
112
	/**
113
	 * Map of built-in output modules => capabilities
114
	 *
115
	 * @var string[][]
116
	 */
117
	public const OUTPUT_MODES = [
118
		QRMarkup::class => [
119
			self::OUTPUT_MARKUP_SVG,
120
			self::OUTPUT_MARKUP_HTML,
121
		],
122
		QRImage::class => [
123
			self::OUTPUT_IMAGE_PNG,
124
			self::OUTPUT_IMAGE_GIF,
125
			self::OUTPUT_IMAGE_JPG,
126
		],
127
		QRString::class => [
128
			self::OUTPUT_STRING_JSON,
129
			self::OUTPUT_STRING_TEXT,
130
		],
131
		QRImagick::class => [
132
			self::OUTPUT_IMAGICK,
133
		],
134
	];
135
136
	/**
137
	 * Map of data mode => interface
138
	 *
139
	 * @var string[]
140
	 */
141
	protected const DATA_INTERFACES = [
142
		'number'   => Number::class,
143
		'alphanum' => AlphaNum::class,
144
		'kanji'    => Kanji::class,
145
		'byte'     => Byte::class,
146
	];
147
148
	/**
149
	 * The settings container
150
	 *
151
	 * @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
152
	 */
153
	protected SettingsContainerInterface $options;
154
155
	/**
156
	 * The selected data interface (Number, AlphaNum, Kanji, Byte)
157
	 */
158
	protected QRDataInterface $dataInterface;
159
160
	/**
161
	 * The current encoding (before the QRCode instance was invoked)
162
	 *
163
	 * @see http://php.net/manual/function.mb-internal-encoding.php mb_internal_encoding()
164
	 */
165
	protected string $mbCurrentEncoding;
166
167
	/**
168
	 * QRCode constructor.
169
	 *
170
	 * Sets the options instance, determines the current mb-encoding and sets it to UTF-8
171
	 */
172
	public function __construct(SettingsContainerInterface $options = null){
173
		// save the current mb-encoding (in case it differs from UTF-8)
174
		$this->mbCurrentEncoding = mb_internal_encoding();
175
		// use UTF-8 from here on
176
		mb_internal_encoding('UTF-8');
177
178
		$this->options = $options ?? new QROptions;
179
	}
180
181
	/**
182
	 * Restores the previous mb-encoding setting, so that we don't mess up the rest of the script
183
	 *
184
	 * @return void
185
	 */
186
	public function __destruct(){
187
		mb_internal_encoding($this->mbCurrentEncoding);
188
	}
189
190
	/**
191
	 * Renders a QR Code for the given $data and QROptions
192
	 *
193
	 * @return mixed
194
	 */
195
	public function render(string $data, string $file = null){
196
		return $this->initOutputInterface($data)->dump($file);
197
	}
198
199
	/**
200
	 * Returns a QRMatrix object for the given $data and current QROptions
201
	 *
202
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
203
	 */
204
	public function getMatrix(string $data):QRMatrix{
205
206
		if(empty($data)){
207
			throw new QRCodeDataException('QRCode::getMatrix() No data given.');
208
		}
209
210
		$this->dataInterface = $this->initDataInterface($data);
211
212
		$maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
213
			? (new MaskPatternTester($this->dataInterface))->getBestMaskPattern()
214
			: $this->options->maskPattern;
215
216
		$matrix = $this->dataInterface->initMatrix($maskPattern);
217
218
		if((bool)$this->options->addQuietzone){
219
			$matrix->setQuietZone($this->options->quietzoneSize);
220
		}
221
222
		return $matrix;
223
	}
224
225
	/**
226
	 * returns a fresh QRDataInterface for the given $data
227
	 *
228
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
229
	 */
230
	public function initDataInterface(string $data):QRDataInterface{
231
232
		// allow forcing the data mode
233
		// see https://github.com/chillerlan/php-qrcode/issues/39
234
		$interface = $this::DATA_INTERFACES[strtolower($this->options->dataModeOverride)] ?? null;
235
236
		if($interface !== null){
237
			return new $interface($this->options, $data);
238
		}
239
240
		foreach($this::DATA_INTERFACES as $mode => $dataInterface){
241
242
			if(call_user_func_array([$this, 'is'.$mode], [$data])){
243
				return new $dataInterface($this->options, $data);
244
			}
245
246
		}
247
248
		throw new QRCodeDataException('invalid data type'); // @codeCoverageIgnore
249
	}
250
251
	/**
252
	 * returns a fresh (built-in) QROutputInterface
253
	 *
254
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
255
	 */
256
	protected function initOutputInterface(string $data):QROutputInterface{
257
258
		if($this->options->outputType === $this::OUTPUT_CUSTOM && class_exists($this->options->outputInterface)){
259
			return new $this->options->outputInterface($this->options, $this->getMatrix($data));
260
		}
261
262
		foreach($this::OUTPUT_MODES as $outputInterface => $modes){
263
264
			if(in_array($this->options->outputType, $modes, true) && class_exists($outputInterface)){
265
				return new $outputInterface($this->options, $this->getMatrix($data));
266
			}
267
268
		}
269
270
		throw new QRCodeOutputException('invalid output type');
271
	}
272
273
	/**
274
	 * checks if a string qualifies as numeric
275
	 */
276
	public function isNumber(string $string):bool{
277
		return $this->checkString($string, QRDataInterface::CHAR_MAP_NUMBER);
278
	}
279
280
	/**
281
	 * checks if a string qualifies as alphanumeric
282
	 */
283
	public function isAlphaNum(string $string):bool{
284
		return $this->checkString($string, QRDataInterface::CHAR_MAP_ALPHANUM);
285
	}
286
287
	/**
288
	 * checks is a given $string matches the characters of a given $charmap, returns false on the first invalid occurence.
289
	 */
290
	protected function checkString(string $string, array $charmap):bool{
291
292
		foreach(str_split($string) as $chr){
293
			if(!isset($charmap[$chr])){
294
				return false;
295
			}
296
		}
297
298
		return true;
299
	}
300
301
	/**
302
	 * checks if a string qualifies as Kanji
303
	 */
304
	public function isKanji(string $string):bool{
305
		$i   = 0;
306
		$len = strlen($string);
307
308
		while($i + 1 < $len){
309
			$c = ((0xff & ord($string[$i])) << 8) | (0xff & ord($string[$i + 1]));
310
311
			if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){
312
				return false;
313
			}
314
315
			$i += 2;
316
		}
317
318
		return $i >= $len;
319
	}
320
321
	/**
322
	 * a dummy
323
	 */
324
	public function isByte(string $data):bool{
325
		return !empty($data);
326
	}
327
328
}
329