Passed
Push — v5 ( abf6c2...81dcab )
by smiley
01:49
created

QRCode::addEciSegment()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

285
			$this->addSegment(new Byte(/** @scrutinizer ignore-type */ $data));
Loading history...
286
287
			return $this;
288
		}
289
290
		throw new QRCodeException('unable to add ECI segment');
291
	}
292
293
}
294