1 | <?php |
||
32 | class QRCode{ |
||
33 | use ClassLoader; |
||
34 | |||
35 | /** |
||
36 | * API constants |
||
37 | */ |
||
38 | const OUTPUT_MARKUP_HTML = 'html'; |
||
39 | const OUTPUT_MARKUP_SVG = 'svg'; |
||
40 | # const OUTPUT_MARKUP_EPS = 'eps'; |
||
41 | # const OUTPUT_MARKUP_XML = 'xml'; // anyone? |
||
42 | |||
43 | const OUTPUT_IMAGE_PNG = 'png'; |
||
44 | const OUTPUT_IMAGE_JPG = 'jpg'; |
||
45 | const OUTPUT_IMAGE_GIF = 'gif'; |
||
46 | |||
47 | const OUTPUT_STRING_JSON = 'json'; |
||
48 | const OUTPUT_STRING_TEXT = 'text'; |
||
49 | |||
50 | const OUTPUT_CUSTOM = 'custom'; |
||
51 | |||
52 | const VERSION_AUTO = -1; |
||
53 | const MASK_PATTERN_AUTO = -1; |
||
54 | |||
55 | const ECC_L = 0b01; // 7%. |
||
56 | const ECC_M = 0b00; // 15%. |
||
57 | const ECC_Q = 0b11; // 25%. |
||
58 | const ECC_H = 0b10; // 30%. |
||
59 | |||
60 | const DATA_NUMBER = 0b0001; |
||
61 | const DATA_ALPHANUM = 0b0010; |
||
62 | const DATA_BYTE = 0b0100; |
||
63 | const DATA_KANJI = 0b1000; |
||
64 | |||
65 | const ECC_MODES = [ |
||
66 | self::ECC_L => 0, |
||
67 | self::ECC_M => 1, |
||
68 | self::ECC_Q => 2, |
||
69 | self::ECC_H => 3, |
||
70 | ]; |
||
71 | |||
72 | const DATA_MODES = [ |
||
73 | self::DATA_NUMBER => 0, |
||
74 | self::DATA_ALPHANUM => 1, |
||
75 | self::DATA_BYTE => 2, |
||
76 | self::DATA_KANJI => 3, |
||
77 | ]; |
||
78 | |||
79 | const OUTPUT_MODES = [ |
||
80 | QRMarkup::class => [ |
||
81 | self::OUTPUT_MARKUP_SVG, |
||
82 | self::OUTPUT_MARKUP_HTML, |
||
83 | # self::OUTPUT_MARKUP_EPS, |
||
84 | ], |
||
85 | QRImage::class => [ |
||
86 | self::OUTPUT_IMAGE_PNG, |
||
87 | self::OUTPUT_IMAGE_GIF, |
||
88 | self::OUTPUT_IMAGE_JPG, |
||
89 | ], |
||
90 | QRString::class => [ |
||
91 | self::OUTPUT_STRING_JSON, |
||
92 | self::OUTPUT_STRING_TEXT, |
||
93 | ] |
||
94 | ]; |
||
95 | |||
96 | /** |
||
97 | * @var \chillerlan\QRCode\QROptions |
||
98 | */ |
||
99 | protected $options; |
||
100 | |||
101 | /** |
||
102 | * @var \chillerlan\QRCode\Data\QRDataInterface |
||
103 | */ |
||
104 | protected $dataInterface; |
||
105 | |||
106 | /** |
||
107 | * QRCode constructor. |
||
108 | * |
||
109 | * @param \chillerlan\Traits\ContainerInterface|null $options |
||
110 | */ |
||
111 | public function __construct(ContainerInterface $options = null){ |
||
112 | mb_internal_encoding('UTF-8'); |
||
113 | |||
114 | $this->setOptions($options ?? new QROptions); |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Sets the options, called internally by the constructor |
||
119 | * |
||
120 | * @param \chillerlan\Traits\ContainerInterface $options |
||
121 | * |
||
122 | * @return \chillerlan\QRCode\QRCode |
||
123 | * @throws \chillerlan\QRCode\QRCodeException |
||
124 | */ |
||
125 | public function setOptions(ContainerInterface $options):QRCode{ |
||
126 | |||
127 | if(!array_key_exists($options->eccLevel, $this::ECC_MODES)){ |
||
|
|||
128 | throw new QRCodeException('Invalid error correct level: '.$options->eccLevel); |
||
129 | } |
||
130 | |||
131 | if(!is_array($options->imageTransparencyBG) || count($options->imageTransparencyBG) < 3){ |
||
132 | $options->imageTransparencyBG = [255, 255, 255]; |
||
133 | } |
||
134 | |||
135 | $options->version = (int)$options->version; |
||
136 | |||
137 | // clamp min/max version number |
||
138 | $options->versionMin = (int)min($options->versionMin, $options->versionMax); |
||
139 | $options->versionMax = (int)max($options->versionMin, $options->versionMax); |
||
140 | |||
141 | $this->options = $options; |
||
142 | |||
143 | return $this; |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * Renders a QR Code for the given $data and QROptions |
||
148 | * |
||
149 | * @param string $data |
||
150 | * |
||
151 | * @return mixed |
||
152 | */ |
||
153 | public function render(string $data){ |
||
154 | return $this->initOutputInterface($data)->dump(); |
||
155 | } |
||
156 | |||
157 | /** |
||
158 | * Returns a QRMatrix object for the given $data and current QROptions |
||
159 | * |
||
160 | * @param string $data |
||
161 | * |
||
162 | * @return \chillerlan\QRCode\Data\QRMatrix |
||
163 | * @throws \chillerlan\QRCode\Data\QRCodeDataException |
||
164 | */ |
||
165 | public function getMatrix(string $data):QRMatrix { |
||
166 | // https://github.com/chillerlan/php-qrcode/pull/15 |
||
167 | // NOTE: input sanitization should be done outside |
||
168 | // $data = trim($data); |
||
169 | |||
170 | if(empty($data)){ |
||
171 | throw new QRCodeDataException('QRCode::getMatrix() No data given.'); |
||
172 | } |
||
173 | |||
174 | $this->dataInterface = $this->initDataInterface($data); |
||
175 | |||
176 | $maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO |
||
177 | ? $this->getBestMaskPattern() |
||
178 | : min(7, max(0, (int)$this->options->maskPattern)); |
||
179 | |||
180 | $matrix = $this |
||
181 | ->dataInterface |
||
182 | ->initMatrix($maskPattern) |
||
183 | ; |
||
184 | |||
185 | if((bool)$this->options->addQuietzone){ |
||
186 | $matrix->setQuietZone($this->options->quietzoneSize); |
||
187 | } |
||
188 | |||
189 | return $matrix; |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern |
||
194 | * |
||
195 | * @see \chillerlan\QRCode\Data\MaskPatternTester |
||
196 | * |
||
197 | * @return int |
||
198 | */ |
||
199 | protected function getBestMaskPattern():int{ |
||
200 | $penalties = []; |
||
201 | |||
202 | for($testPattern = 0; $testPattern < 8; $testPattern++){ |
||
203 | $matrix = $this |
||
204 | ->dataInterface |
||
205 | ->initMatrix($testPattern, true); |
||
206 | |||
207 | $penalties[$testPattern] = (new MaskPatternTester($matrix))->testPattern(); |
||
208 | } |
||
209 | |||
210 | return array_search(min($penalties), $penalties, true); |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * returns a fresh QRDataInterface for the given $data |
||
215 | * |
||
216 | * @param string $data |
||
217 | * |
||
218 | * @return \chillerlan\QRCode\Data\QRDataInterface |
||
219 | * @throws \chillerlan\QRCode\Data\QRCodeDataException |
||
220 | */ |
||
221 | public function initDataInterface(string $data):QRDataInterface{ |
||
240 | |||
241 | /** |
||
242 | * returns a fresh (built-in) QROutputInterface |
||
243 | * |
||
244 | * @param string $data |
||
245 | * |
||
246 | * @return \chillerlan\QRCode\Output\QROutputInterface |
||
247 | * @throws \chillerlan\QRCode\Output\QRCodeOutputException |
||
248 | */ |
||
249 | protected function initOutputInterface(string $data):QROutputInterface{ |
||
265 | |||
266 | /** |
||
267 | * checks if a string qualifies as numeric |
||
268 | * |
||
269 | * @param string $string |
||
270 | * |
||
271 | * @return bool |
||
272 | */ |
||
273 | public function isNumber(string $string):bool { |
||
276 | |||
277 | /** |
||
278 | * checks if a string qualifies as alphanumeric |
||
279 | * |
||
280 | * @param string $string |
||
281 | * |
||
282 | * @return bool |
||
283 | */ |
||
284 | public function isAlphaNum(string $string):bool { |
||
287 | |||
288 | /** |
||
289 | * checks is a given $string matches the characters of a given $charmap, returns false on the first invalid occurence. |
||
290 | * |
||
291 | * @param string $string |
||
292 | * @param array $charmap |
||
293 | * |
||
294 | * @return bool |
||
295 | */ |
||
296 | protected function checkString(string $string, array $charmap):bool{ |
||
307 | |||
308 | /** |
||
309 | * checks if a string qualifies as Kanji |
||
310 | * |
||
311 | * @param string $string |
||
312 | * |
||
313 | * @return bool |
||
314 | */ |
||
315 | public function isKanji(string $string):bool { |
||
331 | |||
332 | /** |
||
333 | * a dummy |
||
334 | * |
||
335 | * @param $data |
||
336 | * |
||
337 | * @return bool |
||
338 | */ |
||
339 | protected function isByte(string $data):bool{ |
||
342 | |||
343 | } |
||
344 |
If you access a property on an interface, you most likely code against a concrete implementation of the interface.
Available Fixes
Adding an additional type check:
Changing the type hint: