aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorArthur <arthur@hoa.ro>2015-11-07 16:52:53 +0100
committerArthur <arthur@hoa.ro>2015-11-07 16:52:53 +0100
commitc536c98ae134191108171d266b73326d6b851f93 (patch)
treeb6f43bc921f1a1f7b08ecfd368fe6aa9e3ffc748
parent1b4ea59f93386594a6e6f35e581244b65c2db57f (diff)
parentabb3ff38f5aea6231f981fd2e6d417f09de7a6e6 (diff)
downloadShaarli-c536c98ae134191108171d266b73326d6b851f93.tar.gz
Shaarli-c536c98ae134191108171d266b73326d6b851f93.tar.zst
Shaarli-c536c98ae134191108171d266b73326d6b851f93.zip
Merge pull request #285 from ArthurHoaro/plugin-qrcode
PLUGIN QRCode
-rw-r--r--plugins/qrcode/qr-1.1.3.js1215
-rw-r--r--plugins/qrcode/qr-1.1.3.min.js5
-rw-r--r--plugins/qrcode/qrcode.html5
-rw-r--r--plugins/qrcode/qrcode.php41
-rw-r--r--plugins/qrcode/qrcode.pngbin0 -> 321 bytes
-rw-r--r--plugins/qrcode/shaarli-qrcode.js56
-rw-r--r--tests/plugins/PlugQrcodeTest.php67
7 files changed, 1389 insertions, 0 deletions
diff --git a/plugins/qrcode/qr-1.1.3.js b/plugins/qrcode/qr-1.1.3.js
new file mode 100644
index 00000000..4f127e6e
--- /dev/null
+++ b/plugins/qrcode/qr-1.1.3.js
@@ -0,0 +1,1215 @@
1// [qr.js](http://neocotic.com/qr.js)
2// (c) 2014 Alasdair Mercer
3// Licensed under the GPL Version 3 license.
4// Based on [jsqrencode](http://code.google.com/p/jsqrencode/)
5// (c) 2010 tz@execpc.com
6// Licensed under the GPL Version 3 license.
7// For all details and documentation:
8// <http://neocotic.com/qr.js>
9
10(function (root) {
11
12 'use strict';
13
14 // Private constants
15 // -----------------
16
17 // Alignment pattern.
18 var ALIGNMENT_DELTA = [
19 0, 11, 15, 19, 23, 27, 31,
20 16, 18, 20, 22, 24, 26, 28, 20, 22, 24, 24, 26, 28, 28, 22, 24, 24,
21 26, 26, 28, 28, 24, 24, 26, 26, 26, 28, 28, 24, 26, 26, 26, 28, 28
22 ];
23 // Default MIME type.
24 var DEFAULT_MIME = 'image/png';
25 // MIME used to initiate a browser download prompt when `qr.save` is called.
26 var DOWNLOAD_MIME = 'image/octet-stream';
27 // There are four elements per version. The first two indicate the number of blocks, then the
28 // data width, and finally the ECC width.
29 var ECC_BLOCKS = [
30 1, 0, 19, 7, 1, 0, 16, 10, 1, 0, 13, 13, 1, 0, 9, 17,
31 1, 0, 34, 10, 1, 0, 28, 16, 1, 0, 22, 22, 1, 0, 16, 28,
32 1, 0, 55, 15, 1, 0, 44, 26, 2, 0, 17, 18, 2, 0, 13, 22,
33 1, 0, 80, 20, 2, 0, 32, 18, 2, 0, 24, 26, 4, 0, 9, 16,
34 1, 0, 108, 26, 2, 0, 43, 24, 2, 2, 15, 18, 2, 2, 11, 22,
35 2, 0, 68, 18, 4, 0, 27, 16, 4, 0, 19, 24, 4, 0, 15, 28,
36 2, 0, 78, 20, 4, 0, 31, 18, 2, 4, 14, 18, 4, 1, 13, 26,
37 2, 0, 97, 24, 2, 2, 38, 22, 4, 2, 18, 22, 4, 2, 14, 26,
38 2, 0, 116, 30, 3, 2, 36, 22, 4, 4, 16, 20, 4, 4, 12, 24,
39 2, 2, 68, 18, 4, 1, 43, 26, 6, 2, 19, 24, 6, 2, 15, 28,
40 4, 0, 81, 20, 1, 4, 50, 30, 4, 4, 22, 28, 3, 8, 12, 24,
41 2, 2, 92, 24, 6, 2, 36, 22, 4, 6, 20, 26, 7, 4, 14, 28,
42 4, 0, 107, 26, 8, 1, 37, 22, 8, 4, 20, 24, 12, 4, 11, 22,
43 3, 1, 115, 30, 4, 5, 40, 24, 11, 5, 16, 20, 11, 5, 12, 24,
44 5, 1, 87, 22, 5, 5, 41, 24, 5, 7, 24, 30, 11, 7, 12, 24,
45 5, 1, 98, 24, 7, 3, 45, 28, 15, 2, 19, 24, 3, 13, 15, 30,
46 1, 5, 107, 28, 10, 1, 46, 28, 1, 15, 22, 28, 2, 17, 14, 28,
47 5, 1, 120, 30, 9, 4, 43, 26, 17, 1, 22, 28, 2, 19, 14, 28,
48 3, 4, 113, 28, 3, 11, 44, 26, 17, 4, 21, 26, 9, 16, 13, 26,
49 3, 5, 107, 28, 3, 13, 41, 26, 15, 5, 24, 30, 15, 10, 15, 28,
50 4, 4, 116, 28, 17, 0, 42, 26, 17, 6, 22, 28, 19, 6, 16, 30,
51 2, 7, 111, 28, 17, 0, 46, 28, 7, 16, 24, 30, 34, 0, 13, 24,
52 4, 5, 121, 30, 4, 14, 47, 28, 11, 14, 24, 30, 16, 14, 15, 30,
53 6, 4, 117, 30, 6, 14, 45, 28, 11, 16, 24, 30, 30, 2, 16, 30,
54 8, 4, 106, 26, 8, 13, 47, 28, 7, 22, 24, 30, 22, 13, 15, 30,
55 10, 2, 114, 28, 19, 4, 46, 28, 28, 6, 22, 28, 33, 4, 16, 30,
56 8, 4, 122, 30, 22, 3, 45, 28, 8, 26, 23, 30, 12, 28, 15, 30,
57 3, 10, 117, 30, 3, 23, 45, 28, 4, 31, 24, 30, 11, 31, 15, 30,
58 7, 7, 116, 30, 21, 7, 45, 28, 1, 37, 23, 30, 19, 26, 15, 30,
59 5, 10, 115, 30, 19, 10, 47, 28, 15, 25, 24, 30, 23, 25, 15, 30,
60 13, 3, 115, 30, 2, 29, 46, 28, 42, 1, 24, 30, 23, 28, 15, 30,
61 17, 0, 115, 30, 10, 23, 46, 28, 10, 35, 24, 30, 19, 35, 15, 30,
62 17, 1, 115, 30, 14, 21, 46, 28, 29, 19, 24, 30, 11, 46, 15, 30,
63 13, 6, 115, 30, 14, 23, 46, 28, 44, 7, 24, 30, 59, 1, 16, 30,
64 12, 7, 121, 30, 12, 26, 47, 28, 39, 14, 24, 30, 22, 41, 15, 30,
65 6, 14, 121, 30, 6, 34, 47, 28, 46, 10, 24, 30, 2, 64, 15, 30,
66 17, 4, 122, 30, 29, 14, 46, 28, 49, 10, 24, 30, 24, 46, 15, 30,
67 4, 18, 122, 30, 13, 32, 46, 28, 48, 14, 24, 30, 42, 32, 15, 30,
68 20, 4, 117, 30, 40, 7, 47, 28, 43, 22, 24, 30, 10, 67, 15, 30,
69 19, 6, 118, 30, 18, 31, 47, 28, 34, 34, 24, 30, 20, 61, 15, 30
70 ];
71 // Map of human-readable ECC levels.
72 var ECC_LEVELS = {
73 L: 1,
74 M: 2,
75 Q: 3,
76 H: 4
77 };
78 // Final format bits with mask (level << 3 | mask).
79 var FINAL_FORMAT = [
80 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, /* L */
81 0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, /* M */
82 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed, /* Q */
83 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b /* H */
84 ];
85 // Galois field exponent table.
86 var GALOIS_EXPONENT = [
87 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26,
88 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0,
89 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23,
90 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1,
91 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0,
92 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2,
93 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce,
94 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc,
95 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54,
96 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73,
97 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff,
98 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41,
99 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6,
100 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09,
101 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16,
102 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x00
103 ];
104 // Galois field log table.
105 var GALOIS_LOG = [
106 0xff, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b,
107 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71,
108 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45,
109 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6,
110 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88,
111 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40,
112 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d,
113 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57,
114 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18,
115 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e,
116 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61,
117 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2,
118 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6,
119 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a,
120 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7,
121 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf
122 ];
123 // *Badness* coefficients.
124 var N1 = 3;
125 var N2 = 3;
126 var N3 = 40;
127 var N4 = 10;
128 // Version pattern.
129 var VERSION_BLOCK = [
130 0xc94, 0x5bc, 0xa99, 0x4d3, 0xbf6, 0x762, 0x847, 0x60d, 0x928, 0xb78, 0x45d, 0xa17, 0x532,
131 0x9a6, 0x683, 0x8c9, 0x7ec, 0xec4, 0x1e1, 0xfab, 0x08e, 0xc1a, 0x33f, 0xd75, 0x250, 0x9d5,
132 0x6f0, 0x8ba, 0x79f, 0xb0b, 0x42e, 0xa64, 0x541, 0xc69
133 ];
134 // Mode for node.js file system file writes.
135 var WRITE_MODE = parseInt('0666', 8);
136
137 // Private variables
138 // -----------------
139
140 // Run lengths for badness.
141 var badBuffer = [];
142 // Constructor for `canvas` elements in the node.js environment.
143 var Canvas;
144 // Data block.
145 var dataBlock;
146 // ECC data blocks and tables.
147 var eccBlock, neccBlock1, neccBlock2;
148 // ECC buffer.
149 var eccBuffer = [];
150 // ECC level (defaults to **L**).
151 var eccLevel = 1;
152 // Image buffer.
153 var frameBuffer = [];
154 // Fixed part of the image.
155 var frameMask = [];
156 // File system within the node.js environment.
157 var fs;
158 // Constructor for `img` elements in the node.js environment.
159 var Image;
160 // Indicates whether or not this script is running in node.js.
161 var inNode = false;
162 // Generator polynomial.
163 var polynomial = [];
164 // Save the previous value of the `qr` variable.
165 var previousQr = root.qr;
166 // Data input buffer.
167 var stringBuffer = [];
168 // Version for the data.
169 var version;
170 // Data width is based on `version`.
171 var width;
172
173 // Private functions
174 // -----------------
175
176 // Create a new canvas using `document.createElement` unless script is running in node.js, in
177 // which case the `canvas` module is used.
178 function createCanvas() {
179 return inNode ? new Canvas() : root.document.createElement('canvas');
180 }
181
182 // Create a new image using `document.createElement` unless script is running in node.js, in
183 // which case the `canvas` module is used.
184 function createImage() {
185 return inNode ? new Image() : root.document.createElement('img');
186 }
187
188 // Force the canvas image to be downloaded in the browser.
189 // Optionally, a `callback` function can be specified which will be called upon completed. Since
190 // this is not an asynchronous operation, this is merely convenient and helps simplify the
191 // calling code.
192 function download(cvs, data, callback) {
193 var mime = data.mime || DEFAULT_MIME;
194
195 root.location.href = cvs.toDataURL(mime).replace(mime, DOWNLOAD_MIME);
196
197 if (typeof callback === 'function') callback();
198 }
199
200 // Normalize the `data` that is provided to the main API.
201 function normalizeData(data) {
202 if (typeof data === 'string') data = { value: data };
203 return data || {};
204 }
205
206 // Override the `qr` API methods that require HTML5 canvas support to throw a relevant error.
207 function overrideAPI(qr) {
208 var methods = [ 'canvas', 'image', 'save', 'saveSync', 'toDataURL' ];
209 var i;
210
211 function overrideMethod(name) {
212 qr[name] = function () {
213 throw new Error(name + ' requires HTML5 canvas element support');
214 };
215 }
216
217 for (i = 0; i < methods.length; i++) {
218 overrideMethod(methods[i]);
219 }
220 }
221
222 // Asynchronously write the data of the rendered canvas to a given file path.
223 function writeFile(cvs, data, callback) {
224 if (typeof data.path !== 'string') {
225 return callback(new TypeError('Invalid path type: ' + typeof data.path));
226 }
227
228 var fd, buff;
229
230 // Write the buffer to the open file stream once both prerequisites are met.
231 function writeBuffer() {
232 fs.write(fd, buff, 0, buff.length, 0, function (error) {
233 fs.close(fd);
234
235 callback(error);
236 });
237 }
238
239 // Create a buffer of the canvas' data.
240 cvs.toBuffer(function (error, _buff) {
241 if (error) return callback(error);
242
243 buff = _buff;
244 if (fd) {
245 writeBuffer();
246 }
247 });
248
249 // Open a stream for the file to be written.
250 fs.open(data.path, 'w', WRITE_MODE, function (error, _fd) {
251 if (error) return callback(error);
252
253 fd = _fd;
254 if (buff) {
255 writeBuffer();
256 }
257 });
258 }
259
260 // Write the data of the rendered canvas to a given file path.
261 function writeFileSync(cvs, data) {
262 if (typeof data.path !== 'string') {
263 throw new TypeError('Invalid path type: ' + typeof data.path);
264 }
265
266 var buff = cvs.toBuffer();
267 var fd = fs.openSync(data.path, 'w', WRITE_MODE);
268
269 try {
270 fs.writeSync(fd, buff, 0, buff.length, 0);
271 } finally {
272 fs.closeSync(fd);
273 }
274 }
275
276 // Set bit to indicate cell in frame is immutable (symmetric around diagonal).
277 function setMask(x, y) {
278 var bit;
279
280 if (x > y) {
281 bit = x;
282 x = y;
283 y = bit;
284 }
285
286 bit = y;
287 bit *= y;
288 bit += y;
289 bit >>= 1;
290 bit += x;
291
292 frameMask[bit] = 1;
293 }
294
295 // Enter alignment pattern. Foreground colour to frame, background to mask. Frame will be merged
296 // with mask later.
297 function addAlignment(x, y) {
298 var i;
299
300 frameBuffer[x + width * y] = 1;
301
302 for (i = -2; i < 2; i++) {
303 frameBuffer[(x + i) + width * (y - 2)] = 1;
304 frameBuffer[(x - 2) + width * (y + i + 1)] = 1;
305 frameBuffer[(x + 2) + width * (y + i)] = 1;
306 frameBuffer[(x + i + 1) + width * (y + 2)] = 1;
307 }
308
309 for (i = 0; i < 2; i++) {
310 setMask(x - 1, y + i);
311 setMask(x + 1, y - i);
312 setMask(x - i, y - 1);
313 setMask(x + i, y + 1);
314 }
315 }
316
317 // Exponentiation mod N.
318 function modN(x) {
319 while (x >= 255) {
320 x -= 255;
321 x = (x >> 8) + (x & 255);
322 }
323
324 return x;
325 }
326
327 // Calculate and append `ecc` data to the `data` block. If block is in the string buffer the
328 // indices to buffers are used.
329 function appendData(data, dataLength, ecc, eccLength) {
330 var bit, i, j;
331
332 for (i = 0; i < eccLength; i++) {
333 stringBuffer[ecc + i] = 0;
334 }
335
336 for (i = 0; i < dataLength; i++) {
337 bit = GALOIS_LOG[stringBuffer[data + i] ^ stringBuffer[ecc]];
338
339 if (bit !== 255) {
340 for (j = 1; j < eccLength; j++) {
341 stringBuffer[ecc + j - 1] = stringBuffer[ecc + j] ^
342 GALOIS_EXPONENT[modN(bit + polynomial[eccLength - j])];
343 }
344 } else {
345 for (j = ecc; j < ecc + eccLength; j++) {
346 stringBuffer[j] = stringBuffer[j + 1];
347 }
348 }
349
350 stringBuffer[ecc + eccLength - 1] = bit === 255 ? 0 :
351 GALOIS_EXPONENT[modN(bit + polynomial[0])];
352 }
353 }
354
355 // Check mask since symmetricals use half.
356 function isMasked(x, y) {
357 var bit;
358
359 if (x > y) {
360 bit = x;
361 x = y;
362 y = bit;
363 }
364
365 bit = y;
366 bit += y * y;
367 bit >>= 1;
368 bit += x;
369
370 return frameMask[bit] === 1;
371 }
372
373 // Apply the selected mask out of the 8 options.
374 function applyMask(mask) {
375 var x, y, r3x, r3y;
376
377 switch (mask) {
378 case 0:
379 for (y = 0; y < width; y++) {
380 for (x = 0; x < width; x++) {
381 if (!((x + y) & 1) && !isMasked(x, y)) {
382 frameBuffer[x + y * width] ^= 1;
383 }
384 }
385 }
386
387 break;
388 case 1:
389 for (y = 0; y < width; y++) {
390 for (x = 0; x < width; x++) {
391 if (!(y & 1) && !isMasked(x, y)) {
392 frameBuffer[x + y * width] ^= 1;
393 }
394 }
395 }
396
397 break;
398 case 2:
399 for (y = 0; y < width; y++) {
400 for (r3x = 0, x = 0; x < width; x++, r3x++) {
401 if (r3x === 3) r3x = 0;
402
403 if (!r3x && !isMasked(x, y)) {
404 frameBuffer[x + y * width] ^= 1;
405 }
406 }
407 }
408
409 break;
410 case 3:
411 for (r3y = 0, y = 0; y < width; y++, r3y++) {
412 if (r3y === 3) r3y = 0;
413
414 for (r3x = r3y, x = 0; x < width; x++, r3x++) {
415 if (r3x === 3) r3x = 0;
416
417 if (!r3x && !isMasked(x, y)) {
418 frameBuffer[x + y * width] ^= 1;
419 }
420 }
421 }
422
423 break;
424 case 4:
425 for (y = 0; y < width; y++) {
426 for (r3x = 0, r3y = ((y >> 1) & 1), x = 0; x < width; x++, r3x++) {
427 if (r3x === 3) {
428 r3x = 0;
429 r3y = !r3y;
430 }
431
432 if (!r3y && !isMasked(x, y)) {
433 frameBuffer[x + y * width] ^= 1;
434 }
435 }
436 }
437
438 break;
439 case 5:
440 for (r3y = 0, y = 0; y < width; y++, r3y++) {
441 if (r3y === 3) r3y = 0;
442
443 for (r3x = 0, x = 0; x < width; x++, r3x++) {
444 if (r3x === 3) r3x = 0;
445
446 if (!((x & y & 1) + !(!r3x | !r3y)) && !isMasked(x, y)) {
447 frameBuffer[x + y * width] ^= 1;
448 }
449 }
450 }
451
452 break;
453 case 6:
454 for (r3y = 0, y = 0; y < width; y++, r3y++) {
455 if (r3y === 3) r3y = 0;
456
457 for (r3x = 0, x = 0; x < width; x++, r3x++) {
458 if (r3x === 3) r3x = 0;
459
460 if (!(((x & y & 1) + (r3x && (r3x === r3y))) & 1) && !isMasked(x, y)) {
461 frameBuffer[x + y * width] ^= 1;
462 }
463 }
464 }
465
466 break;
467 case 7:
468 for (r3y = 0, y = 0; y < width; y++, r3y++) {
469 if (r3y === 3) r3y = 0;
470
471 for (r3x = 0, x = 0; x < width; x++, r3x++) {
472 if (r3x === 3) r3x = 0;
473
474 if (!(((r3x && (r3x === r3y)) + ((x + y) & 1)) & 1) && !isMasked(x, y)) {
475 frameBuffer[x + y * width] ^= 1;
476 }
477 }
478 }
479
480 break;
481 }
482 }
483
484 // Using the table for the length of each run, calculate the amount of bad image. Long runs or
485 // those that look like finders are called twice; once for X and Y.
486 function getBadRuns(length) {
487 var badRuns = 0;
488 var i;
489
490 for (i = 0; i <= length; i++) {
491 if (badBuffer[i] >= 5) {
492 badRuns += N1 + badBuffer[i] - 5;
493 }
494 }
495
496 // FBFFFBF as in finder.
497 for (i = 3; i < length - 1; i += 2) {
498 if (badBuffer[i - 2] === badBuffer[i + 2] &&
499 badBuffer[i + 2] === badBuffer[i - 1] &&
500 badBuffer[i - 1] === badBuffer[i + 1] &&
501 badBuffer[i - 1] * 3 === badBuffer[i] &&
502 // Background around the foreground pattern? Not part of the specs.
503 (badBuffer[i - 3] === 0 || i + 3 > length ||
504 badBuffer[i - 3] * 3 >= badBuffer[i] * 4 ||
505 badBuffer[i + 3] * 3 >= badBuffer[i] * 4)) {
506 badRuns += N3;
507 }
508 }
509
510 return badRuns;
511 }
512
513 // Calculate how bad the masked image is (e.g. blocks, imbalance, runs, or finders).
514 function checkBadness() {
515 var b, b1, bad, big, bw, count, h, x, y;
516 bad = bw = count = 0;
517
518 // Blocks of same colour.
519 for (y = 0; y < width - 1; y++) {
520 for (x = 0; x < width - 1; x++) {
521 // All foreground colour.
522 if ((frameBuffer[x + width * y] &&
523 frameBuffer[(x + 1) + width * y] &&
524 frameBuffer[x + width * (y + 1)] &&
525 frameBuffer[(x + 1) + width * (y + 1)]) ||
526 // All background colour.
527 !(frameBuffer[x + width * y] ||
528 frameBuffer[(x + 1) + width * y] ||
529 frameBuffer[x + width * (y + 1)] ||
530 frameBuffer[(x + 1) + width * (y + 1)])) {
531 bad += N2;
532 }
533 }
534 }
535
536 // X runs.
537 for (y = 0; y < width; y++) {
538 badBuffer[0] = 0;
539
540 for (h = b = x = 0; x < width; x++) {
541 if ((b1 = frameBuffer[x + width * y]) === b) {
542 badBuffer[h]++;
543 } else {
544 badBuffer[++h] = 1;
545 }
546
547 b = b1;
548 bw += b ? 1 : -1;
549 }
550
551 bad += getBadRuns(h);
552 }
553
554 if (bw < 0) bw = -bw;
555
556 big = bw;
557 big += big << 2;
558 big <<= 1;
559
560 while (big > width * width) {
561 big -= width * width;
562 count++;
563 }
564
565 bad += count * N4;
566
567 // Y runs.
568 for (x = 0; x < width; x++) {
569 badBuffer[0] = 0;
570
571 for (h = b = y = 0; y < width; y++) {
572 if ((b1 = frameBuffer[x + width * y]) === b) {
573 badBuffer[h]++;
574 } else {
575 badBuffer[++h] = 1;
576 }
577
578 b = b1;
579 }
580
581 bad += getBadRuns(h);
582 }
583
584 return bad;
585 }
586
587 // Generate the encoded QR image for the string provided.
588 function generateFrame(str) {
589 var i, j, k, m, t, v, x, y;
590
591 // Find the smallest version that fits the string.
592 t = str.length;
593
594 version = 0;
595
596 do {
597 version++;
598
599 k = (eccLevel - 1) * 4 + (version - 1) * 16;
600
601 neccBlock1 = ECC_BLOCKS[k++];
602 neccBlock2 = ECC_BLOCKS[k++];
603 dataBlock = ECC_BLOCKS[k++];
604 eccBlock = ECC_BLOCKS[k];
605
606 k = dataBlock * (neccBlock1 + neccBlock2) + neccBlock2 - 3 + (version <= 9);
607
608 if (t <= k) break;
609 } while (version < 40);
610
611 // FIXME: Ensure that it fits insted of being truncated.
612 width = 17 + 4 * version;
613
614 // Allocate, clear and setup data structures.
615 v = dataBlock + (dataBlock + eccBlock) * (neccBlock1 + neccBlock2) + neccBlock2;
616
617 for (t = 0; t < v; t++) {
618 eccBuffer[t] = 0;
619 }
620
621 stringBuffer = str.slice(0);
622
623 for (t = 0; t < width * width; t++) {
624 frameBuffer[t] = 0;
625 }
626
627 for (t = 0; t < (width * (width + 1) + 1) / 2; t++) {
628 frameMask[t] = 0;
629 }
630
631 // Insert finders: Foreground colour to frame and background to mask.
632 for (t = 0; t < 3; t++) {
633 k = y = 0;
634
635 if (t === 1) k = (width - 7);
636 if (t === 2) y = (width - 7);
637
638 frameBuffer[(y + 3) + width * (k + 3)] = 1;
639
640 for (x = 0; x < 6; x++) {
641 frameBuffer[(y + x) + width * k] = 1;
642 frameBuffer[y + width * (k + x + 1)] = 1;
643 frameBuffer[(y + 6) + width * (k + x)] = 1;
644 frameBuffer[(y + x + 1) + width * (k + 6)] = 1;
645 }
646
647 for (x = 1; x < 5; x++) {
648 setMask(y + x, k + 1);
649 setMask(y + 1, k + x + 1);
650 setMask(y + 5, k + x);
651 setMask(y + x + 1, k + 5);
652 }
653
654 for (x = 2; x < 4; x++) {
655 frameBuffer[(y + x) + width * (k + 2)] = 1;
656 frameBuffer[(y + 2) + width * (k + x + 1)] = 1;
657 frameBuffer[(y + 4) + width * (k + x)] = 1;
658 frameBuffer[(y + x + 1) + width * (k + 4)] = 1;
659 }
660 }
661
662 // Alignment blocks.
663 if (version > 1) {
664 t = ALIGNMENT_DELTA[version];
665 y = width - 7;
666
667 for (;;) {
668 x = width - 7;
669
670 while (x > t - 3) {
671 addAlignment(x, y);
672
673 if (x < t) break;
674
675 x -= t;
676 }
677
678 if (y <= t + 9) break;
679
680 y -= t;
681
682 addAlignment(6, y);
683 addAlignment(y, 6);
684 }
685 }
686
687 // Single foreground cell.
688 frameBuffer[8 + width * (width - 8)] = 1;
689
690 // Timing gap (mask only).
691 for (y = 0; y < 7; y++) {
692 setMask(7, y);
693 setMask(width - 8, y);
694 setMask(7, y + width - 7);
695 }
696
697 for (x = 0; x < 8; x++) {
698 setMask(x, 7);
699 setMask(x + width - 8, 7);
700 setMask(x, width - 8);
701 }
702
703 // Reserve mask, format area.
704 for (x = 0; x < 9; x++) {
705 setMask(x, 8);
706 }
707
708 for (x = 0; x < 8; x++) {
709 setMask(x + width - 8, 8);
710 setMask(8, x);
711 }
712
713 for (y = 0; y < 7; y++) {
714 setMask(8, y + width - 7);
715 }
716
717 // Timing row/column.
718 for (x = 0; x < width - 14; x++) {
719 if (x & 1) {
720 setMask(8 + x, 6);
721 setMask(6, 8 + x);
722 } else {
723 frameBuffer[(8 + x) + width * 6] = 1;
724 frameBuffer[6 + width * (8 + x)] = 1;
725 }
726 }
727
728 // Version block.
729 if (version > 6) {
730 t = VERSION_BLOCK[version - 7];
731 k = 17;
732
733 for (x = 0; x < 6; x++) {
734 for (y = 0; y < 3; y++, k--) {
735 if (1 & (k > 11 ? version >> (k - 12) : t >> k)) {
736 frameBuffer[(5 - x) + width * (2 - y + width - 11)] = 1;
737 frameBuffer[(2 - y + width - 11) + width * (5 - x)] = 1;
738 } else {
739 setMask(5 - x, 2 - y + width - 11);
740 setMask(2 - y + width - 11, 5 - x);
741 }
742 }
743 }
744 }
745
746 // Sync mask bits. Only set above for background cells, so now add the foreground.
747 for (y = 0; y < width; y++) {
748 for (x = 0; x <= y; x++) {
749 if (frameBuffer[x + width * y]) {
750 setMask(x, y);
751 }
752 }
753 }
754
755 // Convert string to bit stream. 8-bit data to QR-coded 8-bit data (numeric, alphanum, or kanji
756 // not supported).
757 v = stringBuffer.length;
758
759 // String to array.
760 for (i = 0; i < v; i++) {
761 eccBuffer[i] = stringBuffer.charCodeAt(i);
762 }
763
764 stringBuffer = eccBuffer.slice(0);
765
766 // Calculate max string length.
767 x = dataBlock * (neccBlock1 + neccBlock2) + neccBlock2;
768
769 if (v >= x - 2) {
770 v = x - 2;
771
772 if (version > 9) v--;
773 }
774
775 // Shift and re-pack to insert length prefix.
776 i = v;
777
778 if (version > 9) {
779 stringBuffer[i + 2] = 0;
780 stringBuffer[i + 3] = 0;
781
782 while (i--) {
783 t = stringBuffer[i];
784
785 stringBuffer[i + 3] |= 255 & (t << 4);
786 stringBuffer[i + 2] = t >> 4;
787 }
788
789 stringBuffer[2] |= 255 & (v << 4);
790 stringBuffer[1] = v >> 4;
791 stringBuffer[0] = 0x40 | (v >> 12);
792 } else {
793 stringBuffer[i + 1] = 0;
794 stringBuffer[i + 2] = 0;
795
796 while (i--) {
797 t = stringBuffer[i];
798
799 stringBuffer[i + 2] |= 255 & (t << 4);
800 stringBuffer[i + 1] = t >> 4;
801 }
802
803 stringBuffer[1] |= 255 & (v << 4);
804 stringBuffer[0] = 0x40 | (v >> 4);
805 }
806
807 // Fill to end with pad pattern.
808 i = v + 3 - (version < 10);
809
810 while (i < x) {
811 stringBuffer[i++] = 0xec;
812 stringBuffer[i++] = 0x11;
813 }
814
815 // Calculate generator polynomial.
816 polynomial[0] = 1;
817
818 for (i = 0; i < eccBlock; i++) {
819 polynomial[i + 1] = 1;
820
821 for (j = i; j > 0; j--) {
822 polynomial[j] = polynomial[j] ? polynomial[j - 1] ^
823 GALOIS_EXPONENT[modN(GALOIS_LOG[polynomial[j]] + i)] : polynomial[j - 1];
824 }
825
826 polynomial[0] = GALOIS_EXPONENT[modN(GALOIS_LOG[polynomial[0]] + i)];
827 }
828
829 // Use logs for generator polynomial to save calculation step.
830 for (i = 0; i <= eccBlock; i++) {
831 polynomial[i] = GALOIS_LOG[polynomial[i]];
832 }
833
834 // Append ECC to data buffer.
835 k = x;
836 y = 0;
837
838 for (i = 0; i < neccBlock1; i++) {
839 appendData(y, dataBlock, k, eccBlock);
840
841 y += dataBlock;
842 k += eccBlock;
843 }
844
845 for (i = 0; i < neccBlock2; i++) {
846 appendData(y, dataBlock + 1, k, eccBlock);
847
848 y += dataBlock + 1;
849 k += eccBlock;
850 }
851
852 // Interleave blocks.
853 y = 0;
854
855 for (i = 0; i < dataBlock; i++) {
856 for (j = 0; j < neccBlock1; j++) {
857 eccBuffer[y++] = stringBuffer[i + j * dataBlock];
858 }
859
860 for (j = 0; j < neccBlock2; j++) {
861 eccBuffer[y++] = stringBuffer[(neccBlock1 * dataBlock) + i + (j * (dataBlock + 1))];
862 }
863 }
864
865 for (j = 0; j < neccBlock2; j++) {
866 eccBuffer[y++] = stringBuffer[(neccBlock1 * dataBlock) + i + (j * (dataBlock + 1))];
867 }
868
869 for (i = 0; i < eccBlock; i++) {
870 for (j = 0; j < neccBlock1 + neccBlock2; j++) {
871 eccBuffer[y++] = stringBuffer[x + i + j * eccBlock];
872 }
873 }
874
875 stringBuffer = eccBuffer;
876
877 // Pack bits into frame avoiding masked area.
878 x = y = width - 1;
879 k = v = 1;
880
881 // inteleaved data and ECC codes.
882 m = (dataBlock + eccBlock) * (neccBlock1 + neccBlock2) + neccBlock2;
883
884 for (i = 0; i < m; i++) {
885 t = stringBuffer[i];
886
887 for (j = 0; j < 8; j++, t <<= 1) {
888 if (0x80 & t) {
889 frameBuffer[x + width * y] = 1;
890 }
891
892 // Find next fill position.
893 do {
894 if (v) {
895 x--;
896 } else {
897 x++;
898
899 if (k) {
900 if (y !== 0) {
901 y--;
902 } else {
903 x -= 2;
904 k = !k;
905
906 if (x === 6) {
907 x--;
908 y = 9;
909 }
910 }
911 } else {
912 if (y !== width - 1) {
913 y++;
914 } else {
915 x -= 2;
916 k = !k;
917
918 if (x === 6) {
919 x--;
920 y -= 8;
921 }
922 }
923 }
924 }
925
926 v = !v;
927 } while (isMasked(x, y));
928 }
929 }
930
931 // Save pre-mask copy of frame.
932 stringBuffer = frameBuffer.slice(0);
933
934 t = 0;
935 y = 30000;
936
937 // Using `for` instead of `while` since in original Arduino code if an early mask was *good
938 // enough* it wouldn't try for a better one since they get more complex and take longer.
939 for (k = 0; k < 8; k++) {
940 // Returns foreground-background imbalance.
941 applyMask(k);
942
943 x = checkBadness();
944
945 // Is current mask better than previous best?
946 if (x < y) {
947 y = x;
948 t = k;
949 }
950
951 // Don't increment `i` to a void redoing mask.
952 if (t === 7) break;
953
954 // Reset for next pass.
955 frameBuffer = stringBuffer.slice(0);
956 }
957
958 // Redo best mask as none were *good enough* (i.e. last wasn't `t`).
959 if (t !== k) {
960 applyMask(t);
961 }
962
963 // Add in final mask/ECC level bytes.
964 y = FINAL_FORMAT[t + ((eccLevel - 1) << 3)];
965
966 // Low byte.
967 for (k = 0; k < 8; k++, y >>= 1) {
968 if (y & 1) {
969 frameBuffer[(width - 1 - k) + width * 8] = 1;
970
971 if (k < 6) {
972 frameBuffer[8 + width * k] = 1;
973 } else {
974 frameBuffer[8 + width * (k + 1)] = 1;
975 }
976 }
977 }
978
979 // High byte.
980 for (k = 0; k < 7; k++, y >>= 1) {
981 if (y & 1) {
982 frameBuffer[8 + width * (width - 7 + k)] = 1;
983
984 if (k) {
985 frameBuffer[(6 - k) + width * 8] = 1;
986 } else {
987 frameBuffer[7 + width * 8] = 1;
988 }
989 }
990 }
991
992 // Finally, return the image data.
993 return frameBuffer;
994 }
995
996 // qr.js setup
997 // -----------
998
999 // Build the publicly exposed API.
1000 var qr = {
1001
1002 // Constants
1003 // ---------
1004
1005 // Current version of `qr`.
1006 VERSION: '1.1.3',
1007
1008 // QR functions
1009 // ------------
1010
1011 // Generate the QR code using the data provided and render it on to a `<canvas>` element.
1012 // If no `<canvas>` element is specified in the argument provided a new one will be created and
1013 // used.
1014 // ECC (error correction capacity) determines how many intential errors are contained in the QR
1015 // code.
1016 canvas: function(data) {
1017 data = normalizeData(data);
1018
1019 // Module size of the generated QR code (i.e. 1-10).
1020 var size = data.size >= 1 && data.size <= 10 ? data.size : 4;
1021 // Actual size of the QR code symbol and is scaled to 25 pixels (e.g. 1 = 25px, 3 = 75px).
1022 size *= 25;
1023
1024 // `<canvas>` element used to render the QR code.
1025 var cvs = data.canvas || createCanvas();
1026 // Retreive the 2D context of the canvas.
1027 var c2d = cvs.getContext('2d');
1028 // Ensure the canvas has the correct dimensions.
1029 c2d.canvas.width = size;
1030 c2d.canvas.height = size;
1031 // Fill the canvas with the correct background colour.
1032 c2d.fillStyle = data.background || '#fff';
1033 c2d.fillRect(0, 0, size, size);
1034
1035 // Determine the ECC level to be applied.
1036 eccLevel = ECC_LEVELS[(data.level && data.level.toUpperCase()) || 'L'];
1037
1038 // Generate the image frame for the given `value`.
1039 var frame = generateFrame(data.value || '');
1040
1041 c2d.lineWidth = 1;
1042
1043 // Determine the *pixel* size.
1044 var px = size;
1045 px /= width;
1046 px = Math.floor(px);
1047
1048 // Draw the QR code.
1049 c2d.clearRect(0, 0, size, size);
1050 c2d.fillStyle = data.background || '#fff';
1051 c2d.fillRect(0, 0, px * (width + 8), px * (width + 8));
1052 c2d.fillStyle = data.foreground || '#000';
1053
1054 var i, j;
1055
1056 for (i = 0; i < width; i++) {
1057 for (j = 0; j < width; j++) {
1058 if (frame[j * width + i]) {
1059 c2d.fillRect(px * i, px * j, px, px);
1060 }
1061 }
1062 }
1063
1064 return cvs;
1065 },
1066
1067 // Generate the QR code using the data provided and render it on to a `<img>` element.
1068 // If no `<img>` element is specified in the argument provided a new one will be created and
1069 // used.
1070 // ECC (error correction capacity) determines how many intential errors are contained in the QR
1071 // code.
1072 image: function(data) {
1073 data = normalizeData(data);
1074
1075 // `<canvas>` element only which the QR code is rendered.
1076 var cvs = this.canvas(data);
1077 // `<img>` element used to display the QR code.
1078 var img = data.image || createImage();
1079
1080 // Apply the QR code to `img`.
1081 img.src = cvs.toDataURL(data.mime || DEFAULT_MIME);
1082 img.height = cvs.height;
1083 img.width = cvs.width;
1084
1085 return img;
1086 },
1087
1088 // Generate the QR code using the data provided and render it on to a `<canvas>` element and
1089 // save it as an image file.
1090 // If no `<canvas>` element is specified in the argument provided a new one will be created and
1091 // used.
1092 // ECC (error correction capacity) determines how many intential errors are contained in the QR
1093 // code.
1094 // If called in a browser the `path` property/argument is ignored and will simply prompt the
1095 // user to choose a location and file name. However, if called within node.js the file will be
1096 // saved to specified path.
1097 // A `callback` function must be provided which will be called once the saving process has
1098 // started. If an error occurs it will be passed as the first argument to this function,
1099 // otherwise this argument will be `null`.
1100 save: function(data, path, callback) {
1101 data = normalizeData(data);
1102
1103 switch (typeof path) {
1104 case 'function':
1105 callback = path;
1106 path = null;
1107 break;
1108 case 'string':
1109 data.path = path;
1110 break;
1111 }
1112
1113 // Callback function is required.
1114 if (typeof callback !== 'function') {
1115 throw new TypeError('Invalid callback type: ' + typeof callback);
1116 }
1117
1118 var completed = false;
1119 // `<canvas>` element only which the QR code is rendered.
1120 var cvs = this.canvas(data);
1121
1122 // Simple function to try and ensure that the `callback` function is only called once.
1123 function done(error) {
1124 if (!completed) {
1125 completed = true;
1126
1127 callback(error);
1128 }
1129 }
1130
1131 if (inNode) {
1132 writeFile(cvs, data, done);
1133 } else {
1134 download(cvs, data, done);
1135 }
1136 },
1137
1138 // Generate the QR code using the data provided and render it on to a `<canvas>` element and
1139 // save it as an image file.
1140 // If no `<canvas>` element is specified in the argument provided a new one will be created and
1141 // used.
1142 // ECC (error correction capacity) determines how many intential errors are contained in the QR
1143 // code.
1144 // If called in a browser the `path` property/argument is ignored and will simply prompt the
1145 // user to choose a location and file name. However, if called within node.js the file will be
1146 // saved to specified path.
1147 saveSync: function(data, path) {
1148 data = normalizeData(data);
1149
1150 if (typeof path === 'string') data.path = path;
1151
1152 // `<canvas>` element only which the QR code is rendered.
1153 var cvs = this.canvas(data);
1154
1155 if (inNode) {
1156 writeFileSync(cvs, data);
1157 } else {
1158 download(cvs, data);
1159 }
1160 },
1161
1162 // Generate the QR code using the data provided and render it on to a `<canvas>` element before
1163 // returning its data URI.
1164 // If no `<canvas>` element is specified in the argument provided a new one will be created and
1165 // used.
1166 // ECC (error correction capacity) determines how many intential errors are contained in the QR
1167 // code.
1168 toDataURL: function(data) {
1169 data = normalizeData(data);
1170
1171 return this.canvas(data).toDataURL(data.mime || DEFAULT_MIME);
1172 },
1173
1174 // Utility functions
1175 // -----------------
1176
1177 // Run qr.js in *noConflict* mode, returning the `qr` variable to its previous owner.
1178 // Returns a reference to `qr`.
1179 noConflict: function() {
1180 root.qr = previousQr;
1181 return this;
1182 }
1183
1184 };
1185
1186 // Support
1187 // -------
1188
1189 // Export `qr` for node.js and CommonJS.
1190 if (typeof exports !== 'undefined') {
1191 inNode = true;
1192
1193 if (typeof module !== 'undefined' && module.exports) {
1194 exports = module.exports = qr;
1195 }
1196 exports.qr = qr;
1197
1198 // Import required node.js modules.
1199 Canvas = require('canvas');
1200 Image = Canvas.Image;
1201 fs = require('fs');
1202 } else if (typeof define === 'function' && define.amd) {
1203 define(function () {
1204 return qr;
1205 });
1206 } else {
1207 // In non-HTML5 browser so strip base functionality.
1208 if (!root.HTMLCanvasElement) {
1209 overrideAPI(qr);
1210 }
1211
1212 root.qr = qr;
1213 }
1214
1215})(this);
diff --git a/plugins/qrcode/qr-1.1.3.min.js b/plugins/qrcode/qr-1.1.3.min.js
new file mode 100644
index 00000000..19d704e1
--- /dev/null
+++ b/plugins/qrcode/qr-1.1.3.min.js
@@ -0,0 +1,5 @@
1/*! qr-js v1.1.3 | (c) 2014 Alasdair Mercer | GPL v3 License
2jsqrencode | (c) 2010 tz@execpc.com | GPL v3 License
3*/
4!function(a){"use strict";function b(){return T?new r:a.document.createElement("canvas")}function c(){return T?new x:a.document.createElement("img")}function d(b,c,d){var e=c.mime||B;a.location.href=b.toDataURL(e).replace(e,C),"function"==typeof d&&d()}function e(a){return"string"==typeof a&&(a={value:a}),a||{}}function f(a){function b(b){a[b]=function(){throw new Error(b+" requires HTML5 canvas element support")}}var c,d=["canvas","image","save","saveSync","toDataURL"];for(c=0;c<d.length;c++)b(d[c])}function g(a,b,c){function d(){w.write(e,f,0,f.length,0,function(a){w.close(e),c(a)})}if("string"!=typeof b.path)return c(new TypeError("Invalid path type: "+typeof b.path));var e,f;a.toBuffer(function(a,b){return a?c(a):(f=b,void(e&&d()))}),w.open(b.path,"w",N,function(a,b){return a?c(a):(e=b,void(f&&d()))})}function h(a,b){if("string"!=typeof b.path)throw new TypeError("Invalid path type: "+typeof b.path);var c=a.toBuffer(),d=w.openSync(b.path,"w",N);try{w.writeSync(d,c,0,c.length,0)}finally{w.closeSync(d)}}function i(a,b){var c;a>b&&(c=a,a=b,b=c),c=b,c*=b,c+=b,c>>=1,c+=a,S[c]=1}function j(a,b){var c;for(R[a+z*b]=1,c=-2;2>c;c++)R[a+c+z*(b-2)]=1,R[a-2+z*(b+c+1)]=1,R[a+2+z*(b+c)]=1,R[a+c+1+z*(b+2)]=1;for(c=0;2>c;c++)i(a-1,b+c),i(a+1,b-c),i(a-c,b-1),i(a+c,b+1)}function k(a){for(;a>=255;)a-=255,a=(a>>8)+(255&a);return a}function l(a,b,c,d){var e,f,g;for(f=0;d>f;f++)W[c+f]=0;for(f=0;b>f;f++){if(e=H[W[a+f]^W[c]],255!==e)for(g=1;d>g;g++)W[c+g-1]=W[c+g]^G[k(e+U[d-g])];else for(g=c;c+d>g;g++)W[g]=W[g+1];W[c+d-1]=255===e?0:G[k(e+U[0])]}}function m(a,b){var c;return a>b&&(c=a,a=b,b=c),c=b,c+=b*b,c>>=1,c+=a,1===S[c]}function n(a){var b,c,d,e;switch(a){case 0:for(c=0;z>c;c++)for(b=0;z>b;b++)b+c&1||m(b,c)||(R[b+c*z]^=1);break;case 1:for(c=0;z>c;c++)for(b=0;z>b;b++)1&c||m(b,c)||(R[b+c*z]^=1);break;case 2:for(c=0;z>c;c++)for(d=0,b=0;z>b;b++,d++)3===d&&(d=0),d||m(b,c)||(R[b+c*z]^=1);break;case 3:for(e=0,c=0;z>c;c++,e++)for(3===e&&(e=0),d=e,b=0;z>b;b++,d++)3===d&&(d=0),d||m(b,c)||(R[b+c*z]^=1);break;case 4:for(c=0;z>c;c++)for(d=0,e=c>>1&1,b=0;z>b;b++,d++)3===d&&(d=0,e=!e),e||m(b,c)||(R[b+c*z]^=1);break;case 5:for(e=0,c=0;z>c;c++,e++)for(3===e&&(e=0),d=0,b=0;z>b;b++,d++)3===d&&(d=0),(b&c&1)+!(!d|!e)||m(b,c)||(R[b+c*z]^=1);break;case 6:for(e=0,c=0;z>c;c++,e++)for(3===e&&(e=0),d=0,b=0;z>b;b++,d++)3===d&&(d=0),(b&c&1)+(d&&d===e)&1||m(b,c)||(R[b+c*z]^=1);break;case 7:for(e=0,c=0;z>c;c++,e++)for(3===e&&(e=0),d=0,b=0;z>b;b++,d++)3===d&&(d=0),(d&&d===e)+(b+c&1)&1||m(b,c)||(R[b+c*z]^=1)}}function o(a){var b,c=0;for(b=0;a>=b;b++)O[b]>=5&&(c+=I+O[b]-5);for(b=3;a-1>b;b+=2)O[b-2]===O[b+2]&&O[b+2]===O[b-1]&&O[b-1]===O[b+1]&&3*O[b-1]===O[b]&&(0===O[b-3]||b+3>a||3*O[b-3]>=4*O[b]||3*O[b+3]>=4*O[b])&&(c+=K);return c}function p(){var a,b,c,d,e,f,g,h,i;for(c=e=f=0,i=0;z-1>i;i++)for(h=0;z-1>h;h++)(R[h+z*i]&&R[h+1+z*i]&&R[h+z*(i+1)]&&R[h+1+z*(i+1)]||!(R[h+z*i]||R[h+1+z*i]||R[h+z*(i+1)]||R[h+1+z*(i+1)]))&&(c+=J);for(i=0;z>i;i++){for(O[0]=0,g=a=h=0;z>h;h++)(b=R[h+z*i])===a?O[g]++:O[++g]=1,a=b,e+=a?1:-1;c+=o(g)}for(0>e&&(e=-e),d=e,d+=d<<2,d<<=1;d>z*z;)d-=z*z,f++;for(c+=f*L,h=0;z>h;h++){for(O[0]=0,g=a=i=0;z>i;i++)(b=R[h+z*i])===a?O[g]++:O[++g]=1,a=b;c+=o(g)}return c}function q(a){var b,c,d,e,f,g,h,o;f=a.length,y=0;do if(y++,d=4*(Q-1)+16*(y-1),u=D[d++],v=D[d++],s=D[d++],t=D[d],d=s*(u+v)+v-3+(9>=y),d>=f)break;while(40>y);for(z=17+4*y,g=s+(s+t)*(u+v)+v,f=0;g>f;f++)P[f]=0;for(W=a.slice(0),f=0;z*z>f;f++)R[f]=0;for(f=0;(z*(z+1)+1)/2>f;f++)S[f]=0;for(f=0;3>f;f++){for(d=o=0,1===f&&(d=z-7),2===f&&(o=z-7),R[o+3+z*(d+3)]=1,h=0;6>h;h++)R[o+h+z*d]=1,R[o+z*(d+h+1)]=1,R[o+6+z*(d+h)]=1,R[o+h+1+z*(d+6)]=1;for(h=1;5>h;h++)i(o+h,d+1),i(o+1,d+h+1),i(o+5,d+h),i(o+h+1,d+5);for(h=2;4>h;h++)R[o+h+z*(d+2)]=1,R[o+2+z*(d+h+1)]=1,R[o+4+z*(d+h)]=1,R[o+h+1+z*(d+4)]=1}if(y>1)for(f=A[y],o=z-7;;){for(h=z-7;h>f-3&&(j(h,o),!(f>h));)h-=f;if(f+9>=o)break;o-=f,j(6,o),j(o,6)}for(R[8+z*(z-8)]=1,o=0;7>o;o++)i(7,o),i(z-8,o),i(7,o+z-7);for(h=0;8>h;h++)i(h,7),i(h+z-8,7),i(h,z-8);for(h=0;9>h;h++)i(h,8);for(h=0;8>h;h++)i(h+z-8,8),i(8,h);for(o=0;7>o;o++)i(8,o+z-7);for(h=0;z-14>h;h++)1&h?(i(8+h,6),i(6,8+h)):(R[8+h+6*z]=1,R[6+z*(8+h)]=1);if(y>6)for(f=M[y-7],d=17,h=0;6>h;h++)for(o=0;3>o;o++,d--)1&(d>11?y>>d-12:f>>d)?(R[5-h+z*(2-o+z-11)]=1,R[2-o+z-11+z*(5-h)]=1):(i(5-h,2-o+z-11),i(2-o+z-11,5-h));for(o=0;z>o;o++)for(h=0;o>=h;h++)R[h+z*o]&&i(h,o);for(g=W.length,b=0;g>b;b++)P[b]=W.charCodeAt(b);if(W=P.slice(0),h=s*(u+v)+v,g>=h-2&&(g=h-2,y>9&&g--),b=g,y>9){for(W[b+2]=0,W[b+3]=0;b--;)f=W[b],W[b+3]|=255&f<<4,W[b+2]=f>>4;W[2]|=255&g<<4,W[1]=g>>4,W[0]=64|g>>12}else{for(W[b+1]=0,W[b+2]=0;b--;)f=W[b],W[b+2]|=255&f<<4,W[b+1]=f>>4;W[1]|=255&g<<4,W[0]=64|g>>4}for(b=g+3-(10>y);h>b;)W[b++]=236,W[b++]=17;for(U[0]=1,b=0;t>b;b++){for(U[b+1]=1,c=b;c>0;c--)U[c]=U[c]?U[c-1]^G[k(H[U[c]]+b)]:U[c-1];U[0]=G[k(H[U[0]]+b)]}for(b=0;t>=b;b++)U[b]=H[U[b]];for(d=h,o=0,b=0;u>b;b++)l(o,s,d,t),o+=s,d+=t;for(b=0;v>b;b++)l(o,s+1,d,t),o+=s+1,d+=t;for(o=0,b=0;s>b;b++){for(c=0;u>c;c++)P[o++]=W[b+c*s];for(c=0;v>c;c++)P[o++]=W[u*s+b+c*(s+1)]}for(c=0;v>c;c++)P[o++]=W[u*s+b+c*(s+1)];for(b=0;t>b;b++)for(c=0;u+v>c;c++)P[o++]=W[h+b+c*t];for(W=P,h=o=z-1,d=g=1,e=(s+t)*(u+v)+v,b=0;e>b;b++)for(f=W[b],c=0;8>c;c++,f<<=1){128&f&&(R[h+z*o]=1);do g?h--:(h++,d?0!==o?o--:(h-=2,d=!d,6===h&&(h--,o=9)):o!==z-1?o++:(h-=2,d=!d,6===h&&(h--,o-=8))),g=!g;while(m(h,o))}for(W=R.slice(0),f=0,o=3e4,d=0;8>d&&(n(d),h=p(),o>h&&(o=h,f=d),7!==f);d++)R=W.slice(0);for(f!==d&&n(f),o=F[f+(Q-1<<3)],d=0;8>d;d++,o>>=1)1&o&&(R[z-1-d+8*z]=1,6>d?R[8+z*d]=1:R[8+z*(d+1)]=1);for(d=0;7>d;d++,o>>=1)1&o&&(R[8+z*(z-7+d)]=1,d?R[6-d+8*z]=1:R[7+8*z]=1);return R}var r,s,t,u,v,w,x,y,z,A=[0,11,15,19,23,27,31,16,18,20,22,24,26,28,20,22,24,24,26,28,28,22,24,24,26,26,28,28,24,24,26,26,26,28,28,24,26,26,26,28,28],B="image/png",C="image/octet-stream",D=[1,0,19,7,1,0,16,10,1,0,13,13,1,0,9,17,1,0,34,10,1,0,28,16,1,0,22,22,1,0,16,28,1,0,55,15,1,0,44,26,2,0,17,18,2,0,13,22,1,0,80,20,2,0,32,18,2,0,24,26,4,0,9,16,1,0,108,26,2,0,43,24,2,2,15,18,2,2,11,22,2,0,68,18,4,0,27,16,4,0,19,24,4,0,15,28,2,0,78,20,4,0,31,18,2,4,14,18,4,1,13,26,2,0,97,24,2,2,38,22,4,2,18,22,4,2,14,26,2,0,116,30,3,2,36,22,4,4,16,20,4,4,12,24,2,2,68,18,4,1,43,26,6,2,19,24,6,2,15,28,4,0,81,20,1,4,50,30,4,4,22,28,3,8,12,24,2,2,92,24,6,2,36,22,4,6,20,26,7,4,14,28,4,0,107,26,8,1,37,22,8,4,20,24,12,4,11,22,3,1,115,30,4,5,40,24,11,5,16,20,11,5,12,24,5,1,87,22,5,5,41,24,5,7,24,30,11,7,12,24,5,1,98,24,7,3,45,28,15,2,19,24,3,13,15,30,1,5,107,28,10,1,46,28,1,15,22,28,2,17,14,28,5,1,120,30,9,4,43,26,17,1,22,28,2,19,14,28,3,4,113,28,3,11,44,26,17,4,21,26,9,16,13,26,3,5,107,28,3,13,41,26,15,5,24,30,15,10,15,28,4,4,116,28,17,0,42,26,17,6,22,28,19,6,16,30,2,7,111,28,17,0,46,28,7,16,24,30,34,0,13,24,4,5,121,30,4,14,47,28,11,14,24,30,16,14,15,30,6,4,117,30,6,14,45,28,11,16,24,30,30,2,16,30,8,4,106,26,8,13,47,28,7,22,24,30,22,13,15,30,10,2,114,28,19,4,46,28,28,6,22,28,33,4,16,30,8,4,122,30,22,3,45,28,8,26,23,30,12,28,15,30,3,10,117,30,3,23,45,28,4,31,24,30,11,31,15,30,7,7,116,30,21,7,45,28,1,37,23,30,19,26,15,30,5,10,115,30,19,10,47,28,15,25,24,30,23,25,15,30,13,3,115,30,2,29,46,28,42,1,24,30,23,28,15,30,17,0,115,30,10,23,46,28,10,35,24,30,19,35,15,30,17,1,115,30,14,21,46,28,29,19,24,30,11,46,15,30,13,6,115,30,14,23,46,28,44,7,24,30,59,1,16,30,12,7,121,30,12,26,47,28,39,14,24,30,22,41,15,30,6,14,121,30,6,34,47,28,46,10,24,30,2,64,15,30,17,4,122,30,29,14,46,28,49,10,24,30,24,46,15,30,4,18,122,30,13,32,46,28,48,14,24,30,42,32,15,30,20,4,117,30,40,7,47,28,43,22,24,30,10,67,15,30,19,6,118,30,18,31,47,28,34,34,24,30,20,61,15,30],E={L:1,M:2,Q:3,H:4},F=[30660,29427,32170,30877,26159,25368,27713,26998,21522,20773,24188,23371,17913,16590,20375,19104,13663,12392,16177,14854,9396,8579,11994,11245,5769,5054,7399,6608,1890,597,3340,2107],G=[1,2,4,8,16,32,64,128,29,58,116,232,205,135,19,38,76,152,45,90,180,117,234,201,143,3,6,12,24,48,96,192,157,39,78,156,37,74,148,53,106,212,181,119,238,193,159,35,70,140,5,10,20,40,80,160,93,186,105,210,185,111,222,161,95,190,97,194,153,47,94,188,101,202,137,15,30,60,120,240,253,231,211,187,107,214,177,127,254,225,223,163,91,182,113,226,217,175,67,134,17,34,68,136,13,26,52,104,208,189,103,206,129,31,62,124,248,237,199,147,59,118,236,197,151,51,102,204,133,23,46,92,184,109,218,169,79,158,33,66,132,21,42,84,168,77,154,41,82,164,85,170,73,146,57,114,228,213,183,115,230,209,191,99,198,145,63,126,252,229,215,179,123,246,241,255,227,219,171,75,150,49,98,196,149,55,110,220,165,87,174,65,130,25,50,100,200,141,7,14,28,56,112,224,221,167,83,166,81,162,89,178,121,242,249,239,195,155,43,86,172,69,138,9,18,36,72,144,61,122,244,245,247,243,251,235,203,139,11,22,44,88,176,125,250,233,207,131,27,54,108,216,173,71,142,0],H=[255,0,1,25,2,50,26,198,3,223,51,238,27,104,199,75,4,100,224,14,52,141,239,129,28,193,105,248,200,8,76,113,5,138,101,47,225,36,15,33,53,147,142,218,240,18,130,69,29,181,194,125,106,39,249,185,201,154,9,120,77,228,114,166,6,191,139,98,102,221,48,253,226,152,37,179,16,145,34,136,54,208,148,206,143,150,219,189,241,210,19,92,131,56,70,64,30,66,182,163,195,72,126,110,107,58,40,84,250,133,186,61,202,94,155,159,10,21,121,43,78,212,229,172,115,243,167,87,7,112,192,247,140,128,99,13,103,74,222,237,49,197,254,24,227,165,153,119,38,184,180,124,17,68,146,217,35,32,137,46,55,63,209,91,149,188,207,205,144,135,151,178,220,252,190,97,242,86,211,171,20,42,93,158,132,60,57,83,71,109,65,162,31,45,67,216,183,123,164,118,196,23,73,236,127,12,111,246,108,161,59,82,41,157,85,170,251,96,134,177,187,204,62,90,203,89,95,176,156,169,160,81,11,245,22,235,122,117,44,215,79,174,213,233,230,231,173,232,116,214,244,234,168,80,88,175],I=3,J=3,K=40,L=10,M=[3220,1468,2713,1235,3062,1890,2119,1549,2344,2936,1117,2583,1330,2470,1667,2249,2028,3780,481,4011,142,3098,831,3445,592,2517,1776,2234,1951,2827,1070,2660,1345,3177],N=parseInt("0666",8),O=[],P=[],Q=1,R=[],S=[],T=!1,U=[],V=a.qr,W=[],X={VERSION:"1.1.3",canvas:function(a){a=e(a);var c=a.size>=1&&a.size<=10?a.size:4;c*=25;var d=a.canvas||b(),f=d.getContext("2d");f.canvas.width=c,f.canvas.height=c,f.fillStyle=a.background||"#fff",f.fillRect(0,0,c,c),Q=E[a.level&&a.level.toUpperCase()||"L"];var g=q(a.value||"");f.lineWidth=1;var h=c;h/=z,h=Math.floor(h),f.clearRect(0,0,c,c),f.fillStyle=a.background||"#fff",f.fillRect(0,0,h*(z+8),h*(z+8)),f.fillStyle=a.foreground||"#000";var i,j;for(i=0;z>i;i++)for(j=0;z>j;j++)g[j*z+i]&&f.fillRect(h*i,h*j,h,h);return d},image:function(a){a=e(a);var b=this.canvas(a),d=a.image||c();return d.src=b.toDataURL(a.mime||B),d.height=b.height,d.width=b.width,d},save:function(a,b,c){function f(a){h||(h=!0,c(a))}switch(a=e(a),typeof b){case"function":c=b,b=null;break;case"string":a.path=b}if("function"!=typeof c)throw new TypeError("Invalid callback type: "+typeof c);var h=!1,i=this.canvas(a);T?g(i,a,f):d(i,a,f)},saveSync:function(a,b){a=e(a),"string"==typeof b&&(a.path=b);var c=this.canvas(a);T?h(c,a):d(c,a)},toDataURL:function(a){return a=e(a),this.canvas(a).toDataURL(a.mime||B)},noConflict:function(){return a.qr=V,this}};"undefined"!=typeof exports?(T=!0,"undefined"!=typeof module&&module.exports&&(exports=module.exports=X),exports.qr=X,r=require("canvas"),x=r.Image,w=require("fs")):"function"==typeof define&&define.amd?define(function(){return X}):(a.HTMLCanvasElement||f(X),a.qr=X)}(this);
5//# sourceMappingURL=qr.min.map \ No newline at end of file
diff --git a/plugins/qrcode/qrcode.html b/plugins/qrcode/qrcode.html
new file mode 100644
index 00000000..ffdaf3b8
--- /dev/null
+++ b/plugins/qrcode/qrcode.html
@@ -0,0 +1,5 @@
1<div class="linkqrcode">
2 <a href="http://qrfree.kaywa.com/?l=1&amp;s=8&amp;d=%s" onclick="showQrCode(this); return false;" class="qrcode" data-permalink="%s">
3 <img src="%s/qrcode/qrcode.png" width="13" height="13" title="QR-Code">
4 </a>
5</div>
diff --git a/plugins/qrcode/qrcode.php b/plugins/qrcode/qrcode.php
new file mode 100644
index 00000000..1080c964
--- /dev/null
+++ b/plugins/qrcode/qrcode.php
@@ -0,0 +1,41 @@
1<?php
2/**
3 * Plugin qrcode
4 * Add QRCode containing URL for each links.
5 * Display a QRCode icon in link list.
6 */
7
8/**
9 * Add qrcode icon to link_plugin when rendering linklist.
10 *
11 * @param array $data - linklist data.
12 *
13 * @return mixed - linklist data with qrcode plugin.
14 */
15function hook_qrcode_render_linklist($data)
16{
17 $qrcode_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.html');
18
19 foreach ($data['links'] as &$value) {
20 $qrcode = sprintf($qrcode_html, $value['url'], $value['url'], PluginManager::$PLUGINS_PATH);
21 $value['link_plugin'][] = $qrcode;
22 }
23
24 return $data;
25}
26
27/**
28 * When linklist is displayed, include qrcode JS files.
29 *
30 * @param array $data - footer data.
31 *
32 * @return mixed - footer data with qrcode JS files added.
33 */
34function hook_qrcode_render_footer($data)
35{
36 if ($data['_PAGE_'] == Router::$PAGE_LINKLIST) {
37 $data['js_files'][] = PluginManager::$PLUGINS_PATH . '/qrcode/shaarli-qrcode.js';
38 }
39
40 return $data;
41}
diff --git a/plugins/qrcode/qrcode.png b/plugins/qrcode/qrcode.png
new file mode 100644
index 00000000..c2cfa476
--- /dev/null
+++ b/plugins/qrcode/qrcode.png
Binary files differ
diff --git a/plugins/qrcode/shaarli-qrcode.js b/plugins/qrcode/shaarli-qrcode.js
new file mode 100644
index 00000000..0a8de21d
--- /dev/null
+++ b/plugins/qrcode/shaarli-qrcode.js
@@ -0,0 +1,56 @@
1// Show the QR-Code of a permalink (when the QR-Code icon is clicked).
2function showQrCode(caller,loading)
3{
4 // Dynamic javascript lib loading: We only load qr.js if the QR code icon is clicked:
5 if (typeof(qr) == 'undefined') // Load qr.js only if not present.
6 {
7 if (!loading) // If javascript lib is still loading, do not append script to body.
8 {
9 var element = document.createElement("script");
10 element.src = "plugins/qrcode/qr-1.1.3.min.js";
11 document.body.appendChild(element);
12 }
13 setTimeout(function() { showQrCode(caller,true);}, 200); // Retry in 200 milliseconds.
14 return false;
15 }
16
17 // Remove previous qrcode if present.
18 removeQrcode();
19
20 // Build the div which contains the QR-Code:
21 var element = document.createElement('div');
22 element.id="permalinkQrcode";
23
24 // Make QR-Code div commit sepuku when clicked:
25 if ( element.attachEvent ){
26 element.attachEvent('onclick', 'this.parentNode.removeChild(this);' );
27
28 } else {
29 // Damn IE
30 element.setAttribute('onclick', 'this.parentNode.removeChild(this);' );
31 }
32
33 // Build the QR-Code:
34 var image = qr.image({size: 8,value: caller.dataset.permalink});
35 if (image)
36 {
37 element.appendChild(image);
38 element.innerHTML += "<br>Click to close";
39 caller.parentNode.appendChild(element);
40 }
41 else
42 {
43 element.innerHTML = "Your browser does not seem to be HTML5 compatible.";
44 }
45 return false;
46}
47
48// Remove any displayed QR-Code
49function removeQrcode()
50{
51 var elem = document.getElementById("permalinkQrcode");
52 if (elem) {
53 elem.parentNode.removeChild(elem);
54 }
55 return false;
56} \ No newline at end of file
diff --git a/tests/plugins/PlugQrcodeTest.php b/tests/plugins/PlugQrcodeTest.php
new file mode 100644
index 00000000..86dc7f29
--- /dev/null
+++ b/tests/plugins/PlugQrcodeTest.php
@@ -0,0 +1,67 @@
1<?php
2
3/**
4 * PlugQrcodeTest.php
5 */
6
7require_once 'plugins/qrcode/qrcode.php';
8require_once 'application/Router.php';
9
10/**
11 * Class PlugQrcodeTest
12 * Unit test for the QR-Code plugin
13 */
14class PlugQrcodeTest extends PHPUnit_Framework_TestCase
15{
16 /**
17 * Reset plugin path
18 */
19 function setUp() {
20 PluginManager::$PLUGINS_PATH = 'plugins';
21 }
22
23 /**
24 * Test render_linklist hook.
25 */
26 function testQrcodeLinklist()
27 {
28 $str = 'http://randomstr.com/test';
29 $data = array(
30 'title' => $str,
31 'links' => array(
32 array(
33 'url' => $str,
34 )
35 )
36 );
37
38 $data = hook_qrcode_render_linklist($data);
39 $link = $data['links'][0];
40 // data shouldn't be altered
41 $this->assertEquals($str, $data['title']);
42 $this->assertEquals($str, $link['url']);
43
44 // plugin data
45 $this->assertEquals(1, count($link['link_plugin']));
46 $this->assertNotFalse(strpos($link['link_plugin'][0], $str));
47 }
48
49 /**
50 * Test render_footer hook.
51 */
52 function testQrcodeFooter()
53 {
54 $str = 'stuff';
55 $data = array($str => $str);
56 $data['_PAGE_'] = Router::$PAGE_LINKLIST;
57
58 $data = hook_qrcode_render_footer($data);
59 $this->assertEquals($str, $data[$str]);
60 $this->assertEquals(1, count($data['js_files']));
61
62 $data = array($str => $str);
63 $data['_PAGE_'] = $str;
64 $this->assertEquals($str, $data[$str]);
65 $this->assertArrayNotHasKey('js_files', $data);
66 }
67}