QrCodeHelper.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <?php
  2. App::uses('AppHelper', 'View/Helper');
  3. /**
  4. * Example Url:
  5. * http://chart.apis.google.com/chart?cht=qr&chs=400x400&chl=SomeText
  6. */
  7. /*
  8. if (!defined('QS_CODE_MIN_SIZE')) {
  9. define('QS_CODE_MIN_SIZE', 58);
  10. }
  11. if (!defined('QS_CODE_MAX_SIZE')) {
  12. define('QS_CODE_MAX_SIZE', 540);
  13. }
  14. if (!defined('QS_CODE_DEFAULT_SIZE')) {
  15. define('QS_CODE_DEFAULT_SIZE', 74);
  16. }
  17. if (!defined('QS_CODE_DEFAULT_LEVEL')) {
  18. define('QS_CODE_DEFAULT_LEVEL', 'L');
  19. }
  20. */
  21. /**
  22. * QR Code Helper
  23. * based on google chart api
  24. * @see http://code.google.com/intl/de-DE/apis/chart/types.html#qrcodes
  25. *
  26. * alternative service api / engine: http://goqr.me/api-description/ (not available right now)
  27. * or: http://qrcode.kaywa.com/img.php
  28. *
  29. * NOTE: urls have a 2k limit - for the total amount of 4296 chars (7089 for numeric values only) you will need to send it via post
  30. *
  31. * TODO: set size according to text length automatically
  32. */
  33. class QrCodeHelper extends AppHelper {
  34. public $helpers = ['Html'];
  35. const MIN_SIZE = 58; # not readable anymore below this value
  36. const MAX_SIZE = 540; # max of 300000 pixels
  37. const DEFAULT_SIZE = 74; # 2x size
  38. const DEFAULT_LEVEL = 'L'; # highest correction level
  39. const SIZE_L = 58;
  40. const SIZE_M = 66;
  41. const SIZE_Q = 66;
  42. const SIZE_H = 74;
  43. public $engine = 'google';
  44. public $url = 'http://chart.apis.google.com/chart?';
  45. /**
  46. * necessary
  47. * - chl: string $text
  48. * - choe: string $outputEncoding
  49. * - chs: size (...x...)
  50. */
  51. public $options = ['cht' => 'qr', 'chl' => '', 'choe' => '', 'chs' => ''];
  52. public $ecLevels = ['H', 'Q', 'M', 'L']; # 30%..7%
  53. public $formattingTypes = ['url' => 'http', 'tel' => 'tel', 'sms' => 'smsto', 'card' => 'mecard'];
  54. public function __construct(View $View, $settings = []) {
  55. parent::__construct($View, $settings);
  56. $this->reset();
  57. }
  58. /**
  59. * Main barcode display function
  60. * @param string $text (utf8 encoded)
  61. * @param array $imageOptions
  62. * NOTE: set size or level manually prior to calling this method
  63. */
  64. public function image($text, $options = []) {
  65. return $this->Html->image($this->uri($text), $options);
  66. }
  67. /**
  68. * Just the url - without image tag
  69. * Note: cannot be url() due to AppHelper conflicts
  70. *
  71. * @param string $text
  72. * @return string Url
  73. */
  74. public function uri($text) {
  75. $params = [];
  76. $params['chl'] = rawurlencode($text);
  77. return $this->_uri($params);
  78. }
  79. /**
  80. * @return string Url
  81. */
  82. protected function _uri($params = []) {
  83. $params += $this->options;
  84. $pieces = [];
  85. foreach ($params as $key => $value) {
  86. $pieces[] = $key . '=' . $value;
  87. }
  88. return $this->url . implode('&', $pieces);
  89. }
  90. /**
  91. * Format a text in a specific format
  92. * - url, sms, tel, email, market, geo
  93. * @return string formattedText
  94. */
  95. public function formatText($text, $type = null) {
  96. switch ($type) {
  97. case 'text':
  98. break;
  99. case 'url':
  100. $text = $this->Html->url($text, true);
  101. break;
  102. case 'sms':
  103. $text = 'smsto:' . implode(':', (array)$text);
  104. break;
  105. case 'tel':
  106. $text = 'tel:' . $text;
  107. break;
  108. case 'email':
  109. $text = 'mailto:' . $text;
  110. break;
  111. case 'geo':
  112. $text = 'geo:' . implode(',', (array)$text); #like 77.1,11.8
  113. break;
  114. case 'market':
  115. $text = 'market://search?q=pname:' . $text;
  116. }
  117. return $text;
  118. }
  119. /**
  120. * Generate mecard string
  121. * 1: name, nickname, note, birthday, sound
  122. * 1..n (as array or string): address, tel, url, email
  123. * for details on cards see:
  124. * http://www.nttdocomo.co.jp/english/service/imode/make/content/barcode/function/application/addressbook/index.html
  125. * example: MECARD: N:Docomo,Taro; SOUND:docomotaro; TEL:03XXXXXXXX; EMAIL:d@e.de;
  126. * @return string mecard
  127. */
  128. public function formatCard($data) {
  129. $data = (array)$data;
  130. $res = [];
  131. foreach ($data as $key => $val) {
  132. switch ($key) {
  133. case 'name':
  134. $res[] = 'N:' . $val; # //TODO: support array
  135. break;
  136. case 'nickname':
  137. $res[] = 'NICKNAME:' . $val;
  138. break;
  139. case 'sound':
  140. $res[] = 'SOUND:' . $val;
  141. break;
  142. case 'note':
  143. $val = str_replace(';', ',', $val);
  144. $res[] = 'NOTE:' . $val; //TODO: remove other invalid characters
  145. break;
  146. case 'birthday':
  147. if (strlen($val) !== 8) {
  148. $val = substr($val, 0, 4) . substr($val, 6, 2) . substr($val, 10, 2);
  149. }
  150. $res[] = 'BDAY:' . $val;
  151. break;
  152. case 'tel':
  153. $val = (array)$val;
  154. foreach ($val as $v) {
  155. $res[] = 'TEL:' . $v;
  156. }
  157. break;
  158. case 'video':
  159. $val = (array)$val;
  160. foreach ($val as $v) {
  161. $res[] = 'TEL-AV:' . $v;
  162. }
  163. break;
  164. case 'address':
  165. $val = (array)$val;
  166. foreach ($val as $v) {
  167. $res[] = 'ADR:' . $v; //TODO: reformat (array etc)
  168. }
  169. break;
  170. case 'org':
  171. $val = (array)$val;
  172. foreach ($val as $v) {
  173. $res[] = 'ORG:' . $v;
  174. }
  175. break;
  176. case 'role':
  177. $val = (array)$val;
  178. foreach ($val as $v) {
  179. $res[] = 'ROLE:' . $v;
  180. }
  181. break;
  182. case 'email':
  183. $val = (array)$val;
  184. foreach ($val as $v) {
  185. $res[] = 'EMAIL:' . $v;
  186. }
  187. break;
  188. case 'url':
  189. $val = (array)$val;
  190. foreach ($val as $v) {
  191. $res[] = 'URL:' . $this->Html->url($v, true);
  192. }
  193. break;
  194. }
  195. }
  196. return 'MECARD:' . implode(';', $res) . ';';
  197. }
  198. /**
  199. * //TODO
  200. * calendar event
  201. * e.g.: BEGIN:VEVENT SUMMARY:dfdfd DTSTART:20100226T092900Z DTEND:20100226T102900Z END:VEVENT
  202. * @see http://zxing.appspot.com/generator/
  203. */
  204. public function formatEvent() {
  205. }
  206. /**
  207. * QrCodeHelper::format()
  208. *
  209. * @param mixed $protocol
  210. * @param mixed $string
  211. * @return string
  212. */
  213. public function format($protocol, $string) {
  214. return $protocol . ':' . $string;
  215. }
  216. /**
  217. * Change size
  218. * result format: chs=<size>x<size>
  219. * @return bool Success
  220. *
  221. * //TODO: automatic detection
  222. * //default is 2x size (plus margin) of typical QR version for the error correction level (L=V.2, M/Q=V.3, H=V.4)
  223. * //$ecCodes = array('L' => 58, 'M' => 66, 'Q' => 66, 'H' => 74);
  224. */
  225. public function setSize($value) {
  226. if ($value === 'auto') {
  227. //TODO
  228. }
  229. $value = (int)$value;
  230. if ($value >= static::MIN_SIZE && $value <= static::MAX_SIZE) {
  231. $this->options['chs'] = $value . 'x' . $value;
  232. return true;
  233. }
  234. return false;
  235. }
  236. /**
  237. * Change level and margin - optional
  238. * result format: chld=<EC level>|<margin>
  239. * @return bool Success
  240. */
  241. public function setLevel($level, $margin = null) {
  242. if (in_array($level, $this->ecLevels)) {
  243. if ($margin === null) {
  244. $margin = 4; # minimum
  245. }
  246. $this->options['chld'] = strtoupper($level) . '|' . $margin;
  247. return true;
  248. }
  249. return false;
  250. }
  251. public function setEncoding() {
  252. //TODO
  253. }
  254. /**
  255. * QrCodeHelper::reset()
  256. *
  257. * @return void
  258. */
  259. public function reset() {
  260. $this->setSize(static::DEFAULT_SIZE);
  261. //$this->setLevel(QS_CODE_DEFAULT_LEVEL);
  262. $this->options['chld'] = '';
  263. $this->options['choe'] = Configure::read('App.encoding');
  264. }
  265. /**
  266. * Show current options - for debugging only
  267. */
  268. public function debug() {
  269. return $this->options;
  270. }
  271. /**
  272. * 25 => 21x21 (L)
  273. * ...
  274. * 4000 => 547x547 (L)
  275. * @param int $length
  276. * @return int size
  277. */
  278. protected function _findSuitableSize() {
  279. }
  280. }