QrCodeHelper.php 7.4 KB

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