1: <?php
2:
3: 4: 5:
6: class Quform_Element_Captcha extends Quform_Element_Field
7: {
8: 9: 10:
11: protected $session;
12:
13: 14: 15: 16: 17:
18: public function __construct($id, Quform_Form $form, Quform_Session $session)
19: {
20: parent::__construct($id, $form);
21:
22: $this->session = $session;
23: }
24:
25: 26: 27: 28: 29:
30: protected function getImageAttributes()
31: {
32: $attributes = array(
33: 'class' => 'quform-captcha-image-img',
34: 'src' => $this->generateImageData(),
35: 'alt' => __('CAPTCHA image', 'quform'),
36: 'data-element-id' => $this->getId()
37: );
38:
39: $imageWidth = $this->config('captchaWidth');
40: $imageHeight = $this->config('captchaHeight');
41:
42: if ( ! is_numeric($imageWidth)) {
43: $imageWidth = self::getDefaultConfig('captchaWidth');
44: }
45:
46: if ( ! is_numeric($imageHeight)) {
47: $imageHeight = self::getDefaultConfig('captchaHeight');
48: }
49:
50: $attributes['width'] = $imageWidth;
51: $attributes['height'] = $imageHeight;
52:
53: $attributes = apply_filters('quform_captcha_image_attributes', $attributes, $this, $this->form);
54: $attributes = apply_filters('quform_captcha_image_attributes_' . $this->getIdentifier(), $attributes, $this, $this->form);
55:
56: return $attributes;
57: }
58:
59: 60: 61: 62: 63:
64: protected function getImageHtml()
65: {
66: $output = '<div class="quform-captcha quform-cf">';
67: $output .= sprintf('<div class="quform-captcha-image quform-captcha-image-%s">', $this->getIdentifier());
68: $output .= Quform::getHtmlTag('img', $this->getImageAttributes());
69: $output .= '</div></div>';
70:
71: return $output;
72: }
73:
74: 75: 76: 77: 78: 79:
80: protected function getFieldAttributes(array $context = array())
81: {
82: $attributes = array(
83: 'type' => 'text',
84: 'id' => $this->getUniqueId(),
85: 'name' => $this->getFullyQualifiedName(),
86: 'class' => Quform::sanitizeClass($this->getFieldClasses($context))
87: );
88:
89: if ( ! $this->isEmpty()) {
90: $attributes['value'] = $this->getValue();
91: }
92:
93: $placeholder = $this->form->replaceVariablesPreProcess($this->config('placeholder'));
94: if (Quform::isNonEmptyString($placeholder)) {
95: $attributes['placeholder'] = $placeholder;
96: }
97: $attributes = apply_filters('quform_field_attributes', $attributes, $this, $this->form, $context);
98: $attributes = apply_filters('quform_field_attributes_' . $this->getIdentifier(), $attributes, $this, $this->form, $context);
99:
100: return $attributes;
101: }
102:
103: 104: 105: 106: 107: 108:
109: protected function getFieldClasses(array $context = array())
110: {
111: $classes = array(
112: 'quform-field',
113: 'quform-field-captcha',
114: sprintf('quform-field-%s', $this->getIdentifier())
115: );
116:
117: if ($this->form->config('tooltipsEnabled') && Quform::isNonEmptyString($this->config('tooltip')) && Quform::get($context, 'tooltipType') == 'field') {
118: $classes[] = sprintf('quform-tooltip-%s', Quform::get($context, 'tooltipEvent'));
119: }
120:
121: if (Quform::isNonEmptyString($this->config('customClass'))) {
122: $classes[] = $this->config('customClass');
123: }
124:
125: $classes = apply_filters('quform_field_classes', $classes, $this, $this->form, $context);
126: $classes = apply_filters('quform_field_classes_' . $this->getIdentifier(), $classes, $this, $this->form, $context);
127:
128: return $classes;
129: }
130:
131: 132: 133: 134: 135: 136:
137: protected function getFieldHtml(array $context = array())
138: {
139: return Quform::getHtmlTag('input', $this->getFieldAttributes($context));
140: }
141:
142: 143: 144: 145: 146: 147:
148: protected function getInputHtml(array $context = array())
149: {
150: $output = sprintf('<div class="%s">', Quform::escape(Quform::sanitizeClass($this->getInputClasses($context))));
151: $output .= $this->getFieldHtml($context);
152: $output .= $this->getFieldIconsHtml();
153:
154: if ($this->form->config('tooltipsEnabled') && Quform::isNonEmptyString($this->config('tooltip')) && Quform::get($context, 'tooltipType') == 'field') {
155: $output .= sprintf('<div class="quform-tooltip-content">%s</div>', $this->config('tooltip'));
156: }
157:
158: $output .= '</div>';
159: $output .= $this->getImageHtml();
160:
161: return $output;
162: }
163:
164: 165: 166: 167: 168: 169:
170: protected function generateCode($length)
171: {
172:
173: $characters = '23456789bcdfghjkmnpqrstvwxyz';
174: $code = '';
175:
176: for ($i = 0; $i < $length; $i++) {
177: $code .= substr($characters, mt_rand(0, strlen($characters) - 1), 1);
178: }
179:
180: return $code;
181: }
182:
183: 184: 185: 186: 187:
188: public function generateImageData()
189: {
190: if ($this->supportsDynamicImageGeneration()) {
191: $code = $this->generateCode((int) $this->config('captchaLength'));
192: $data = $this->generateDynamicImage($code);
193: } else {
194: $code = 'catch';
195: $data = $this->getStaticImageData();
196: }
197:
198: $this->session->set($this->form->getSessionKey() . '.captcha.' . $this->getName(), $code);
199:
200: return 'data:image/png;base64,' . $data;
201: }
202:
203: 204: 205: 206: 207: 208: 209: 210:
211: protected function getFontPath($font)
212: {
213: $originalFontPath = QUFORM_LIBRARY_PATH . '/fonts/' . $font;
214: $path = $originalFontPath;
215:
216: if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
217:
218: $tmpDir = Quform::getTempDir('quform/fonts');
219: $cachedFontPath = $tmpDir . '/' . $font;
220:
221: if (file_exists($cachedFontPath)) {
222: $path = $cachedFontPath;
223: } else {
224: if ( ! is_dir($tmpDir)) {
225: wp_mkdir_p($tmpDir);
226: }
227:
228: if (wp_is_writable($tmpDir)) {
229: copy($originalFontPath, $cachedFontPath);
230: $path = $cachedFontPath;
231: }
232:
233: if ( ! file_exists($cachedFontPath)) {
234: $path = $originalFontPath;
235: }
236: }
237: }
238:
239: return $path;
240: }
241:
242: 243: 244:
245: protected function supportsDynamicImageGeneration()
246: {
247: return function_exists('imagecreate') &&
248: function_exists('imagecolorallocate') &&
249: function_exists('imagettftext') &&
250: function_exists('imagepng') &&
251: function_exists('imagedestroy');
252:
253: }
254:
255: 256: 257: 258: 259: 260:
261: protected function generateDynamicImage($code)
262: {
263: $yOffsetAdjustMin = 5;
264: $yOffsetAdjustMax = 10;
265: $width = (int) $this->config('captchaWidth');
266: $height = (int) $this->config('captchaHeight');
267: $minFontSize = (int) $this->config('captchaMinFontSize');
268: $maxFontSize = (int) $this->config('captchaMaxFontSize');
269: $bgAlpha = (int) ((1 - $this->config('captchaBgColorRgba.a', 1)) * 127);
270: $textAlpha = (int) ((1 - $this->config('captchaTextColorRgba.a', 1)) * 127);
271:
272: if ($this->config('captchaRetina')) {
273: $width = $width * 2;
274: $height = $height * 2;
275: $minFontSize = $minFontSize * 2;
276: $maxFontSize = $maxFontSize * 2;
277: $yOffsetAdjustMin = $yOffsetAdjustMin * 2;
278: $yOffsetAdjustMax = $yOffsetAdjustMax * 2;
279: }
280:
281: $image = imagecreate($width, $height);
282:
283: imagecolorallocatealpha(
284: $image,
285: $this->config('captchaBgColorRgba.r'),
286: $this->config('captchaBgColorRgba.g'),
287: $this->config('captchaBgColorRgba.b'),
288: $bgAlpha
289: );
290:
291: $textColor = imagecolorallocatealpha(
292: $image,
293: $this->config('captchaTextColorRgba.r'),
294: $this->config('captchaTextColorRgba.g'),
295: $this->config('captchaTextColorRgba.b'),
296: $textAlpha
297: );
298:
299: for($i = 0; $i < $this->config('captchaLength'); $i++) {
300: $counter = mt_rand(0, 1);
301:
302: if ($counter == 0) {
303: $angle = mt_rand($this->config('captchaMinAngle'), $this->config('captchaMaxAngle'));
304: }
305:
306: if ($counter == 1) {
307: $angle = mt_rand(360 - $this->config('captchaMaxAngle'), 360 - $this->config('captchaMinAngle'));
308: }
309:
310: imagettftext(
311: $image,
312: mt_rand($minFontSize, $maxFontSize),
313: $angle,
314: (($i + 1) * $maxFontSize) - ($maxFontSize / 2),
315: mt_rand($maxFontSize + $yOffsetAdjustMin, $maxFontSize + $yOffsetAdjustMax),
316: $textColor,
317: $this->getFontPath($this->config('captchaFont')),
318: substr($code, $i, 1)
319: );
320: }
321:
322: ob_start();
323: imagepng($image);
324: $data = base64_encode(ob_get_clean());
325: imagedestroy($image);
326:
327: return $data;
328: }
329:
330: 331: 332: 333: 334: 335: 336:
337: protected function getStaticImageData()
338: {
339:
340: if ($this->config('captchaRetina')) {
341: return 'iVBORw0KGgoAAAANSUhEUgAAAJsAAAAoBAMAAAAbEZVkAAAAG1BMVEX///8iIiKQkJBZWVk9PT2srKzj4+PHx8d0dHSp1wYyAAABiElEQVRIie2TQU/CQBCFF0qXHnntIhwhaPRIjSYcKRrkCEnFa6vouQQNV0pC/Nvs7lSSEmLb4Mn0XTqZ7n77tvPKWKlSpf6HetFR43V7Bo2jn25YwBm4+gHXE98aN/KCP8B9AqB7u1+uU5T4QRu4h8k20MXTO4a6F3vAOA+jenefVD5EROakOkx9soi5Lf2uK6YLOw/Opa1q8yMu6ATsntWzIiSnqVvLFjOdPObEfNbQVThmlp3g6IDQ0RxdNhgXOXBGk9XI09JhZjuFkx1m0igq8kgE2TjJMmiONZv5dB8LU7Kk0rbaJMuYdxzuU7jWT8VvXBxwXDmpqZR42rshl8XTbJzlBCtyNwOE0BgDUawgVZm/OiXFkhPpdrJxMmRJaDGacxH2Cafzwb124NPFqzarxw85eG+YBJoLNeZwSDi64kIGkIKyErchsMnGHeRdr2N0CTcHhecSI31WBXCM3UsBGlsC7Zk2YMpAp40Yg6scY02J9waRudOlj8L/+69aF/VSqlSpE9oD+HU3KouM3WcAAAAASUVORK5CYII=';
342: } else {
343: return 'iVBORw0KGgoAAAANSUhEUgAAAJsAAAAoBAMAAAAbEZVkAAAAG1BMVEX///8iIiKQkJBZWVk9PT2srKzj4+PHx8d0dHSp1wYyAAABiElEQVRIie2TQU/CQBCFF0qXHnntIhwhaPRIjSYcKRrkCEnFa6vouQQNV0pC/Nvs7lSSEmLb4Mn0XTqZ7n77tvPKWKlSpf6HetFR43V7Bo2jn25YwBm4+gHXE98aN/KCP8B9AqB7u1+uU5T4QRu4h8k20MXTO4a6F3vAOA+jenefVD5EROakOkx9soi5Lf2uK6YLOw/Opa1q8yMu6ATsntWzIiSnqVvLFjOdPObEfNbQVThmlp3g6IDQ0RxdNhgXOXBGk9XI09JhZjuFkx1m0igq8kgE2TjJMmiONZv5dB8LU7Kk0rbaJMuYdxzuU7jWT8VvXBxwXDmpqZR42rshl8XTbJzlBCtyNwOE0BgDUawgVZm/OiXFkhPpdrJxMmRJaDGacxH2Cafzwb124NPFqzarxw85eG+YBJoLNeZwSDi64kIGkIKyErchsMnGHeRdr2N0CTcHhecSI31WBXCM3UsBGlsC7Zk2YMpAp40Yg6scY02J9waRudOlj8L/+69aF/VSqlSpE9oD+HU3KouM3WcAAAAASUVORK5CYII=';
344: }
345: }
346:
347: 348: 349: 350: 351: 352:
353: protected function renderCss(array $context = array())
354: {
355: $css = parent::renderCss($context);
356:
357: if ($context['fieldWidth'] == 'custom' && Quform::isNonEmptyString($context['fieldWidthCustom'])) {
358: $css .= sprintf('.quform-input-captcha.quform-input-%s { width: %s; }', $this->getIdentifier(), Quform::addCssUnit($context['fieldWidthCustom']));
359: $css .= sprintf('.quform-inner-%s > .quform-error > .quform-error-inner { float: left; min-width: %s; }', $this->getIdentifier(), Quform::addCssUnit($context['fieldWidthCustom']));
360: }
361:
362: return $css;
363: }
364:
365: 366: 367: 368: 369: 370:
371: public static function getDefaultConfig($key = null)
372: {
373: $config = apply_filters('quform_default_config_captcha', array(
374:
375: 'label' => __('Please type the characters', 'quform'),
376: 'description' => __('This helps us prevent spam, thank you.', 'quform'),
377: 'descriptionAbove' => '',
378:
379:
380: 'labelIcon' => '',
381: 'fieldIconLeft' => '',
382: 'fieldIconRight' => '',
383: 'fieldSize' => 'inherit',
384: 'fieldWidth' => 'inherit',
385: 'fieldWidthCustom' => '',
386: 'captchaLength' => '5',
387: 'captchaWidth' => '115',
388: 'captchaHeight' => '40',
389: 'captchaBgColor' => '#FFFFFF',
390: 'captchaBgColorRgba' => array('r' => 255, 'g' => 255, 'b' => 255, 'a' => 1),
391: 'captchaTextColor' => '#222222',
392: 'captchaTextColorRgba' => array('r' => 34, 'g' => 34, 'b' => 34, 'a' => 1),
393: 'captchaFont' => 'Typist.ttf',
394: 'captchaMinFontSize' => '12',
395: 'captchaMaxFontSize' => '19',
396: 'captchaMinAngle' => '0',
397: 'captchaMaxAngle' => '20',
398: 'captchaRetina' => true,
399: 'customClass' => '',
400: 'customElementClass' => '',
401: 'styles' => array(),
402:
403:
404: 'placeholder' => '',
405: 'subLabel' => '',
406: 'subLabelAbove' => '',
407: 'tooltip' => '',
408: 'tooltipType' => 'inherit',
409: 'tooltipEvent' => 'inherit',
410: 'labelPosition' => 'inherit',
411: 'labelWidth' => '',
412:
413:
414: 'logicEnabled' => false,
415: 'logicAction' => true,
416: 'logicMatch' => 'all',
417: 'logicRules' => array(),
418:
419:
420: 'visibility' => '',
421:
422:
423: 'messageRequired' => '',
424: 'messageCaptchaNotMatch' => ''
425: ));
426:
427: $config['type'] = 'captcha';
428:
429: if (Quform::isNonEmptyString($key)) {
430: return Quform::get($config, $key);
431: }
432:
433: return $config;
434: }
435: }
436: