QrCodeHelper.php 7.2 KB

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