Passed
Push — v5 ( 33c1e2...f092e8 )
by smiley
01:52
created

QRCode::initCustomOutputInterface()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
nc 3
nop 0
dl 0
loc 11
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\{ECICharset, MaskPattern, MaskPatternTester, Mode};
14
use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Kanji, Number, QRData, QRCodeDataException, QRDataModeInterface, QRMatrix};
15
use chillerlan\QRCode\Output\{QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString};
16
use chillerlan\Settings\SettingsContainerInterface;
17
use function class_exists, class_implements, in_array, mb_convert_encoding, mb_detect_encoding;
18
19
/**
20
 * Turns a text string into a Model 2 QR Code
21
 *
22
 * @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
23
 * @see http://www.qrcode.com/en/codes/model12.html
24
 * @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf
25
 * @see https://en.wikipedia.org/wiki/QR_code
26
 * @see http://www.thonky.com/qr-code-tutorial/
27
 */
28
class QRCode{
29
30
	/** @var int */
31
	public const VERSION_AUTO       = -1;
32
	/** @var int */
33
	public const MASK_PATTERN_AUTO  = -1;
34
35
	/** @var string */
36
	public const OUTPUT_MARKUP_HTML = 'html';
37
	/** @var string */
38
	public const OUTPUT_MARKUP_SVG  = 'svg';
39
	/** @var string */
40
	public const OUTPUT_IMAGE_PNG   = 'png';
41
	/** @var string */
42
	public const OUTPUT_IMAGE_JPG   = 'jpg';
43
	/** @var string */
44
	public const OUTPUT_IMAGE_GIF   = 'gif';
45
	/** @var string */
46
	public const OUTPUT_STRING_JSON = 'json';
47
	/** @var string */
48
	public const OUTPUT_STRING_TEXT = 'text';
49
	/** @var string */
50
	public const OUTPUT_IMAGICK     = 'imagick';
51
	/** @var string */
52
	public const OUTPUT_FPDF        = 'fpdf';
53
	/** @var string */
54
	public const OUTPUT_CUSTOM      = 'custom';
55
56
	/**
57
	 * Map of built-in output modules => capabilities
58
	 *
59
	 * @var string[][]
60
	 */
61
	public const OUTPUT_MODES = [
62
		QRMarkup::class => [
63
			self::OUTPUT_MARKUP_SVG,
64
			self::OUTPUT_MARKUP_HTML,
65
		],
66
		QRImage::class => [
67
			self::OUTPUT_IMAGE_PNG,
68
			self::OUTPUT_IMAGE_GIF,
69
			self::OUTPUT_IMAGE_JPG,
70
		],
71
		QRString::class => [
72
			self::OUTPUT_STRING_JSON,
73
			self::OUTPUT_STRING_TEXT,
74
		],
75
		QRImagick::class => [
76
			self::OUTPUT_IMAGICK,
77
		],
78
		QRFpdf::class => [
79
			self::OUTPUT_FPDF,
80
		],
81
	];
82
83
	/**
84
	 * A collection of one or more data segments of [classname, data] to write
85
	 *
86
	 * @see \chillerlan\QRCode\Data\QRDataModeInterface
87
	 *
88
	 * @var \chillerlan\QRCode\Data\QRDataModeInterface[]
89
	 */
90
	protected array $dataSegments = [];
91
92
	/**
93
	 * The settings container
94
	 *
95
	 * @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
96
	 */
97
	protected SettingsContainerInterface $options;
98
99
	/**
100
	 * The selected data interface (Number, AlphaNum, Kanji, Byte)
101
	 */
102
	protected QRData $dataInterface;
103
104
	/**
105
	 * QRCode constructor.
106
	 *
107
	 * Sets the options instance, determines the current mb-encoding and sets it to UTF-8
108
	 */
109
	public function __construct(SettingsContainerInterface $options = null){
110
		$this->options = $options ?? new QROptions;
111
	}
112
113
	/**
114
	 * Renders a QR Code for the given $data and QROptions
115
	 *
116
	 * @return mixed
117
	 */
118
	public function render(string $data = null, string $file = null){
119
120
		if($data !== null){
121
			/** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */
122
			foreach(Mode::DATA_INTERFACES as $dataInterface){
123
124
				if($dataInterface::validateString($data)){
125
					$this->addSegment(new $dataInterface($data));
126
127
					break;
128
				}
129
130
			}
131
132
		}
133
134
		return $this->initOutputInterface()->dump($file);
135
	}
136
137
	/**
138
	 * Returns a QRMatrix object for the given $data and current QROptions
139
	 *
140
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
141
	 */
142
	public function getMatrix():QRMatrix{
143
144
		if(empty($this->dataSegments)){
145
			throw new QRCodeDataException('QRCode::getMatrix() No data given.');
146
		}
147
148
		$this->dataInterface = new QRData($this->options, $this->dataSegments);
149
150
		$maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
151
			? (new MaskPatternTester($this->dataInterface))->getBestMaskPattern()
152
			: new MaskPattern($this->options->maskPattern);
153
154
		$matrix = $this->dataInterface->writeMatrix($maskPattern);
155
156
		if($this->options->addQuietzone){
157
			$matrix->setQuietZone($this->options->quietzoneSize);
158
		}
159
160
		return $matrix;
161
	}
162
163
	/**
164
	 * returns a fresh (built-in) QROutputInterface
165
	 *
166
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
167
	 */
168
	protected function initOutputInterface():QROutputInterface{
169
170
		if($this->options->outputType === $this::OUTPUT_CUSTOM){
171
			return $this->initCustomOutputInterface();
172
		}
173
174
		foreach($this::OUTPUT_MODES as $outputInterface => $modes){
175
176
			if(in_array($this->options->outputType, $modes)){
177
				return new $outputInterface($this->options, $this->getMatrix());
178
			}
179
180
		}
181
182
		throw new QRCodeOutputException('invalid output type');
183
	}
184
185
	/**
186
	 * initializes a custom output module after checking the existence of the class and if it implemnts the required interface
187
	 *
188
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
189
	 */
190
	protected function initCustomOutputInterface():QROutputInterface{
191
192
		if(!class_exists($this->options->outputInterface)){
193
			throw new QRCodeOutputException('invalid custom output module');
194
		}
195
196
		if(!in_array(QROutputInterface::class, class_implements($this->options->outputInterface))){
197
			throw new QRCodeOutputException('custom output module does not implement QROutputInterface');
198
		}
199
200
		return new $this->options->outputInterface($this->options, $this->getMatrix());
201
	}
202
203
	/**
204
	 * checks if a string qualifies as numeric (convenience method)
205
	 */
206
	public function isNumber(string $string):bool{
207
		return Number::validateString($string);
208
	}
209
210
	/**
211
	 * checks if a string qualifies as alphanumeric (convenience method)
212
	 */
213
	public function isAlphaNum(string $string):bool{
214
		return AlphaNum::validateString($string);
215
	}
216
217
	/**
218
	 * checks if a string qualifies as Kanji (convenience method)
219
	 */
220
	public function isKanji(string $string):bool{
221
		return Kanji::validateString($string);
222
	}
223
224
	/**
225
	 * a dummy (convenience method)
226
	 */
227
	public function isByte(string $string):bool{
228
		return Byte::validateString($string);
229
	}
230
231
	/**
232
	 * ISO/IEC 18004:2000 8.3.6 - Mixing modes
233
	 * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length
234
	 */
235
	protected function addSegment(QRDataModeInterface $segment):void{
236
		$this->dataSegments[] = $segment;
237
	}
238
239
	/**
240
	 * ISO/IEC 18004:2000 8.3.2 - Numeric Mode
241
	 */
242
	public function addNumberSegment(string $data):QRCode{
243
		$this->addSegment(new Number($data));
244
245
		return $this;
246
	}
247
248
	/**
249
	 * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode
250
	 */
251
	public function addAlphaNumSegment(string $data):QRCode{
252
		$this->addSegment(new AlphaNum($data));
253
254
		return $this;
255
	}
256
257
	/**
258
	 * ISO/IEC 18004:2000 8.3.5 - Kanji Mode
259
	 */
260
	public function addKanjiSegment(string $data):QRCode{
261
		$this->addSegment(new Kanji($data));
262
263
		return $this;
264
	}
265
266
	/**
267
	 * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode
268
	 */
269
	public function addByteSegment(string $data):QRCode{
270
		$this->addSegment(new Byte($data));
271
272
		return $this;
273
	}
274
275
	/**
276
	 * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode
277
	 */
278
	public function addEciDesignator(int $encoding):QRCode{
279
		$this->addSegment(new ECI($encoding));
280
281
		return $this;
282
	}
283
284
	/**
285
	 * i hate this somehow but i'll leave it for now
286
	 *
287
	 * @throws \chillerlan\QRCode\QRCodeException
288
	 */
289
	public function addEciSegment(int $encoding, string $data):QRCode{
290
		// validate the encoding id
291
		$eciCharset = new ECICharset($encoding);
292
		// get charset name
293
		$eciCharsetName = $eciCharset->getName();
294
		// convert the string to the given charset
295
		if($eciCharsetName !== null){
296
			$data = mb_convert_encoding($data, $eciCharsetName, mb_detect_encoding($data));
297
			// add ECI designator
298
			$this->addSegment(new ECI($eciCharset->getID()));
299
			$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

299
			$this->addSegment(new Byte(/** @scrutinizer ignore-type */ $data));
Loading history...
300
301
			return $this;
302
		}
303
304
		throw new QRCodeException('unable to add ECI segment');
305
	}
306
307
}
308