Test Failed
Push — master ( a17451...322104 )
by smiley
17:04
created

QRCode::__destruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
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
17
};
18
use chillerlan\QRCode\Output\{
19
	QRCodeOutputException, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString
20
};
21
use chillerlan\Settings\SettingsContainerInterface;
22
23
/**
24
 * Turns a text string into a Model 2 QR Code
25
 *
26
 * @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
27
 * @link http://www.qrcode.com/en/codes/model12.html
28
 * @link http://www.thonky.com/qr-code-tutorial/
29
 */
30
class QRCode{
31
32
	/**
33
	 * API constants
34
	 */
35
	public const OUTPUT_MARKUP_HTML = 'html';
36
	public const OUTPUT_MARKUP_SVG  = 'svg';
37
	public const OUTPUT_IMAGE_PNG   = 'png';
38
	public const OUTPUT_IMAGE_JPG   = 'jpg';
39
	public const OUTPUT_IMAGE_GIF   = 'gif';
40
	public const OUTPUT_STRING_JSON = 'json';
41
	public const OUTPUT_STRING_TEXT = 'text';
42
	public const OUTPUT_IMAGICK     = 'imagick';
43
	public const OUTPUT_CUSTOM      = 'custom';
44
45
	public const VERSION_AUTO       = -1;
46
	public const MASK_PATTERN_AUTO  = -1;
47
48
	public const ECC_L         = 0b01; // 7%.
49
	public const ECC_M         = 0b00; // 15%.
50
	public const ECC_Q         = 0b11; // 25%.
51
	public const ECC_H         = 0b10; // 30%.
52
53
	public const DATA_NUMBER   = 0b0001;
54
	public const DATA_ALPHANUM = 0b0010;
55
	public const DATA_BYTE     = 0b0100;
56
	public const DATA_KANJI    = 0b1000;
57
58
	public const ECC_MODES = [
59
		self::ECC_L => 0,
60
		self::ECC_M => 1,
61
		self::ECC_Q => 2,
62
		self::ECC_H => 3,
63
	];
64
65
	public const DATA_MODES = [
66
		self::DATA_NUMBER   => 0,
67
		self::DATA_ALPHANUM => 1,
68
		self::DATA_BYTE     => 2,
69
		self::DATA_KANJI    => 3,
70
	];
71
72
	public const OUTPUT_MODES = [
73
		QRMarkup::class => [
74
			self::OUTPUT_MARKUP_SVG,
75
			self::OUTPUT_MARKUP_HTML,
76
		],
77
		QRImage::class => [
78
			self::OUTPUT_IMAGE_PNG,
79
			self::OUTPUT_IMAGE_GIF,
80
			self::OUTPUT_IMAGE_JPG,
81
		],
82
		QRString::class => [
83
			self::OUTPUT_STRING_JSON,
84
			self::OUTPUT_STRING_TEXT,
85
		],
86
		QRImagick::class => [
87
			self::OUTPUT_IMAGICK,
88
		],
89
	];
90
91
	/**
92
	 * @var \chillerlan\QRCode\QROptions
93
	 */
94
	protected $options;
95
96
	/**
97
	 * @var \chillerlan\QRCode\Data\QRDataInterface
98
	 */
99
	protected $dataInterface;
100
101
	/**
102
	 * @see http://php.net/manual/function.mb-internal-encoding.php
103
	 * @var string
104
	 */
105
	protected $mbCurrentEncoding;
106
107
	/**
108
	 * QRCode constructor.
109
	 *
110
	 * @param \chillerlan\Settings\SettingsContainerInterface|null $options
111
	 */
112
	public function __construct(SettingsContainerInterface $options = null){
113
		// save the current mb encoding (in case it differs from UTF-8)
114
		$this->mbCurrentEncoding = mb_internal_encoding();
115
		// use UTF-8 from here on
116
		mb_internal_encoding('UTF-8');
117
118
		$this->options = $options ?? new QROptions;
0 ignored issues
show
Documentation Bug introduced by
$options ?? new \chillerlan\QRCode\QROptions() is of type object<chillerlan\Settin...ingsContainerInterface>, but the property $options was declared to be of type object<chillerlan\QRCode\QROptions>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
119
	}
120
121
	/**
122
	 * @return void
123
	 */
124
	public function __destruct(){
125
		// restore the previous mb_internal_encoding, so that we don't mess up the rest of the script
126
		mb_internal_encoding($this->mbCurrentEncoding);
127
	}
128
129
	/**
130
	 * Renders a QR Code for the given $data and QROptions
131
	 *
132
	 * @param string      $data
133
	 * @param string|null $file
134
	 *
135
	 * @return mixed
136
	 */
137
	public function render(string $data, string $file = null){
138
		return $this->initOutputInterface($data)->dump($file);
139
	}
140
141
	/**
142
	 * Returns a QRMatrix object for the given $data and current QROptions
143
	 *
144
	 * @param string $data
145
	 *
146
	 * @return \chillerlan\QRCode\Data\QRMatrix
147
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
148
	 */
149
	public function getMatrix(string $data):QRMatrix{
150
151
		if(empty($data)){
152
			throw new QRCodeDataException('QRCode::getMatrix() No data given.');
153
		}
154
155
		$this->dataInterface = $this->initDataInterface($data);
156
157
		$maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
158
			? $this->getBestMaskPattern()
159
			: $this->options->maskPattern;
160
161
		$matrix = $this->dataInterface->initMatrix($maskPattern);
162
163
		if((bool)$this->options->addQuietzone){
164
			$matrix->setQuietZone($this->options->quietzoneSize);
0 ignored issues
show
Bug introduced by
It seems like $this->options->quietzoneSize can also be of type boolean; however, chillerlan\QRCode\Data\QRMatrix::setQuietZone() does only seem to accept null|integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
165
		}
166
167
		return $matrix;
168
	}
169
170
	/**
171
	 * shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern
172
	 *
173
	 * @see \chillerlan\QRCode\Data\MaskPatternTester
174
	 *
175
	 * @return int
176
	 */
177
	protected function getBestMaskPattern():int{
178
		$penalties = [];
179
180
		for($pattern = 0; $pattern < 8; $pattern++){
181
			$tester = new MaskPatternTester($this->dataInterface->initMatrix($pattern, true));
182
183
			$penalties[$pattern] = $tester->testPattern();
184
		}
185
186
		return array_search(min($penalties), $penalties, true);
187
	}
188
189
	/**
190
	 * returns a fresh QRDataInterface for the given $data
191
	 *
192
	 * @param string                       $data
193
	 *
194
	 * @return \chillerlan\QRCode\Data\QRDataInterface
195
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
196
	 */
197
	public function initDataInterface(string $data):QRDataInterface{
198
199
		foreach(['Number', 'AlphaNum', 'Kanji', 'Byte'] as $mode){
200
			$dataInterface = __NAMESPACE__.'\\Data\\'.$mode;
201
202
			if(call_user_func_array([$this, 'is'.$mode], [$data]) && class_exists($dataInterface)){
203
				return new $dataInterface($this->options, $data);
204
			}
205
206
		}
207
208
		throw new QRCodeDataException('invalid data type'); // @codeCoverageIgnore
209
	}
210
211
	/**
212
	 * returns a fresh (built-in) QROutputInterface
213
	 *
214
	 * @param string $data
215
	 *
216
	 * @return \chillerlan\QRCode\Output\QROutputInterface
217
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
218
	 */
219
	protected function initOutputInterface(string $data):QROutputInterface{
220
221
		if($this->options->outputType === $this::OUTPUT_CUSTOM && class_exists($this->options->outputInterface)){
222
			return new $this->options->outputInterface($this->options, $this->getMatrix($data));
223
		}
224
225
		foreach($this::OUTPUT_MODES as $outputInterface => $modes){
226
227
			if(in_array($this->options->outputType, $modes, true) && class_exists($outputInterface)){
228
				return new $outputInterface($this->options, $this->getMatrix($data));
229
			}
230
231
		}
232
233
		throw new QRCodeOutputException('invalid output type');
234
	}
235
236
	/**
237
	 * checks if a string qualifies as numeric
238
	 *
239
	 * @param string $string
240
	 *
241
	 * @return bool
242
	 */
243
	public function isNumber(string $string):bool{
244
		return $this->checkString($string, QRDataInterface::NUMBER_CHAR_MAP);
245
	}
246
247
	/**
248
	 * checks if a string qualifies as alphanumeric
249
	 *
250
	 * @param string $string
251
	 *
252
	 * @return bool
253
	 */
254
	public function isAlphaNum(string $string):bool{
255
		return $this->checkString($string, QRDataInterface::ALPHANUM_CHAR_MAP);
256
	}
257
258
	/**
259
	 * checks is a given $string matches the characters of a given $charmap, returns false on the first invalid occurence.
260
	 *
261
	 * @param string $string
262
	 * @param array  $charmap
263
	 *
264
	 * @return bool
265
	 */
266
	protected function checkString(string $string, array $charmap):bool{
267
		$len = strlen($string);
268
269
		for($i = 0; $i < $len; $i++){
270
			if(!in_array($string[$i], $charmap, true)){
271
				return false;
272
			}
273
		}
274
275
		return true;
276
	}
277
278
	/**
279
	 * checks if a string qualifies as Kanji
280
	 *
281
	 * @param string $string
282
	 *
283
	 * @return bool
284
	 */
285
	public function isKanji(string $string):bool{
286
		$i   = 0;
287
		$len = strlen($string);
288
289
		while($i + 1 < $len){
290
			$c = ((0xff&ord($string[$i])) << 8)|(0xff&ord($string[$i + 1]));
291
292
			if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){
293
				return false;
294
			}
295
296
			$i += 2;
297
		}
298
299
		return $i >= $len;
300
	}
301
302
	/**
303
	 * a dummy
304
	 *
305
	 * @param $data
306
	 *
307
	 * @return bool
308
	 */
309
	protected function isByte(string $data):bool{
310
		return !empty($data);
311
	}
312
313
}
314