1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* CAPTCHA class For XOOPS |
5
|
|
|
* |
6
|
|
|
* Currently there are two types of CAPTCHA forms, text and image |
7
|
|
|
* The default mode is "text", it can be changed in the priority: |
8
|
|
|
* 1 If mode is set through XoopsFormCaptcha::setConfig("mode", $mode), take it |
9
|
|
|
* 2 Elseif mode is set though captcha/config.php, take it |
10
|
|
|
* 3 Else, take "text" |
11
|
|
|
* |
12
|
|
|
* D.J. |
13
|
|
|
*/ |
14
|
|
|
class XoopsCaptcha |
15
|
|
|
{ |
16
|
|
|
public $active = true; |
17
|
|
|
public $mode = 'text'; // potential values: image, text |
18
|
|
|
public $config = []; |
19
|
|
|
|
20
|
|
|
public $message = []; // Logging error messages |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* XoopsCaptcha constructor. |
24
|
|
|
*/ |
25
|
|
|
public function __construct() |
26
|
|
|
{ |
27
|
|
|
// Loading default preferences |
28
|
|
|
$this->config = @include __DIR__ . '/config.php'; |
29
|
|
|
|
30
|
|
|
$this->setMode($this->config['mode']); |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* @return XoopsCaptcha |
35
|
|
|
*/ |
36
|
|
|
public static function getInstance() |
37
|
|
|
{ |
38
|
|
|
static $instance; |
39
|
|
|
if (null === $instance) { |
40
|
|
|
$instance = new static(); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
return $instance; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @param $name |
48
|
|
|
* @param $val |
49
|
|
|
* @return bool |
50
|
|
|
*/ |
51
|
|
|
public function setConfig($name, $val) |
52
|
|
|
{ |
53
|
|
|
if ('mode' === $name) { |
54
|
|
|
$this->setMode($val); |
55
|
|
|
} elseif (isset($this->$name)) { |
56
|
|
|
$this->$name = $val; |
57
|
|
|
} else { |
58
|
|
|
$this->config[$name] = $val; |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
return true; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Set CAPTCHA mode |
66
|
|
|
* |
67
|
|
|
* For future possible modes, right now force to use text or image |
68
|
|
|
* |
69
|
|
|
* @param string $mode if no mode is set, just verify current mode |
70
|
|
|
*/ |
71
|
|
|
public function setMode($mode = null) |
72
|
|
|
{ |
73
|
|
|
if (!empty($mode) && in_array($mode, ['text', 'image'])) { |
74
|
|
|
$this->mode = $mode; |
75
|
|
|
|
76
|
|
|
if ('image' !== $this->mode) { |
77
|
|
|
return; |
78
|
|
|
} |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
// Disable image mode |
82
|
|
View Code Duplication |
if (!extension_loaded('gd')) { |
|
|
|
|
83
|
|
|
$this->mode = 'text'; |
84
|
|
|
} else { |
85
|
|
|
$required_functions = [ |
86
|
|
|
'imagecreatetruecolor', |
87
|
|
|
'imagecolorallocate', |
88
|
|
|
'imagefilledrectangle', |
89
|
|
|
'imagejpeg', |
90
|
|
|
'imagedestroy', |
91
|
|
|
'imageftbbox' |
92
|
|
|
]; |
93
|
|
|
foreach ($required_functions as $func) { |
94
|
|
|
if (!function_exists($func)) { |
95
|
|
|
$this->mode = 'text'; |
96
|
|
|
break; |
97
|
|
|
} |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Initializing the CAPTCHA class |
104
|
|
|
* @param string $name |
105
|
|
|
* @param null $skipmember |
106
|
|
|
* @param null $num_chars |
107
|
|
|
* @param null $fontsize_min |
108
|
|
|
* @param null $fontsize_max |
109
|
|
|
* @param null $background_type |
110
|
|
|
* @param null $background_num |
111
|
|
|
*/ |
112
|
|
|
public function init( |
113
|
|
|
$name = 'xoopscaptcha', |
114
|
|
|
$skipmember = null, |
115
|
|
|
$num_chars = null, |
116
|
|
|
$fontsize_min = null, |
117
|
|
|
$fontsize_max = null, |
118
|
|
|
$background_type = null, |
119
|
|
|
$background_num = null |
120
|
|
|
) { |
121
|
|
|
// Loading RUN-TIME settings |
122
|
|
|
foreach (array_keys($this->config) as $key) { |
123
|
|
|
if (isset(${$key}) && null !== ${$key}) { |
124
|
|
|
$this->config[$key] = ${$key}; |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
$this->config['name'] = $name; |
128
|
|
|
|
129
|
|
|
// Skip CAPTCHA for member if set |
130
|
|
|
if ($this->config['skipmember'] && is_object($GLOBALS['xoopsUser'])) { |
131
|
|
|
$this->active = false; |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Verify user submission |
137
|
|
|
* @param null $skipMember |
138
|
|
|
* @return bool |
139
|
|
|
*/ |
140
|
|
|
public function verify($skipMember = null) |
141
|
|
|
{ |
142
|
|
|
$sessionName = @$_SESSION['XoopsCaptcha_name']; |
143
|
|
|
$skipMember = (null === $skipMember) ? @$_SESSION['XoopsCaptcha_skipmember'] : $skipMember; |
144
|
|
|
$maxAttempts = (int)(@$_SESSION['XoopsCaptcha_maxattempts']); |
145
|
|
|
|
146
|
|
|
$is_valid = false; |
147
|
|
|
|
148
|
|
|
// Skip CAPTCHA for member if set |
149
|
|
|
if (is_object($GLOBALS['xoopsUser']) && !empty($skipMember)) { |
150
|
|
|
$is_valid = true; |
151
|
|
|
// Kill too many attempts |
152
|
|
|
} elseif (!empty($maxAttempts) && $_SESSION['XoopsCaptcha_attempt_' . $sessionName] > $maxAttempts) { |
153
|
|
|
$this->message[] = XOOPS_CAPTCHA_TOOMANYATTEMPTS; |
154
|
|
|
|
155
|
|
|
// Verify the code |
156
|
|
|
} elseif (!empty($_SESSION['XoopsCaptcha_sessioncode'])) { |
157
|
|
|
$func = $this->config['casesensitive'] ? 'strcmp' : 'strcasecmp'; |
158
|
|
|
$is_valid = !$func(trim(@$_POST[$sessionName]), $_SESSION['XoopsCaptcha_sessioncode']); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
if (!empty($maxAttempts)) { |
162
|
|
|
if (!$is_valid) { |
163
|
|
|
// Increase the attempt records on failure |
164
|
|
|
$_SESSION['XoopsCaptcha_attempt_' . $sessionName]++; |
165
|
|
|
// Log the error message |
166
|
|
|
$this->message[] = XOOPS_CAPTCHA_INVALID_CODE; |
167
|
|
|
} else { |
168
|
|
|
|
169
|
|
|
// reset attempt records on success |
170
|
|
|
$_SESSION['XoopsCaptcha_attempt_' . $sessionName] = null; |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
$this->destroyGarbage(true); |
174
|
|
|
|
175
|
|
|
return $is_valid; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* @return mixed|string |
180
|
|
|
*/ |
181
|
|
|
public function getCaption() |
182
|
|
|
{ |
183
|
|
|
return defined('XOOPS_CAPTCHA_CAPTION') ? constant('XOOPS_CAPTCHA_CAPTION') : ''; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* @return string |
188
|
|
|
*/ |
189
|
|
|
public function getMessage() |
190
|
|
|
{ |
191
|
|
|
return implode('<br>', $this->message); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Destory historical stuff |
196
|
|
|
* @param bool $clearSession |
197
|
|
|
* @return bool |
198
|
|
|
*/ |
199
|
|
|
public function destroyGarbage($clearSession = false) |
200
|
|
|
{ |
201
|
|
|
require_once __DIR__ . '/' . $this->mode . '.php'; |
202
|
|
|
$class = 'XoopsCaptcha' . ucfirst($this->mode); |
203
|
|
|
$captchaHandler = new $class(); |
204
|
|
|
if (method_exists($captchaHandler, 'destroyGarbage')) { |
205
|
|
|
$captchaHandler->loadConfig($this->config); |
206
|
|
|
$captchaHandler->destroyGarbage(); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
if ($clearSession) { |
210
|
|
|
$_SESSION['XoopsCaptcha_name'] = null; |
211
|
|
|
$_SESSION['XoopsCaptcha_skipmember'] = null; |
212
|
|
|
$_SESSION['XoopsCaptcha_sessioncode'] = null; |
213
|
|
|
$_SESSION['XoopsCaptcha_maxattempts'] = null; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
return true; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* @return mixed|string |
221
|
|
|
*/ |
222
|
|
|
public function render() |
223
|
|
|
{ |
224
|
|
|
$form = ''; |
225
|
|
|
|
226
|
|
|
if (!$this->active || empty($this->config['name'])) { |
227
|
|
|
return $form; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
$_SESSION['XoopsCaptcha_name'] = $this->config['name']; |
231
|
|
|
$_SESSION['XoopsCaptcha_skipmember'] = $this->config['skipmember']; |
232
|
|
|
$maxAttempts = $this->config['maxattempt']; |
233
|
|
|
$_SESSION['XoopsCaptcha_maxattempts'] = $maxAttempts; |
234
|
|
|
/* |
235
|
|
|
if (!empty($maxAttempts)) { |
236
|
|
|
$_SESSION['XoopsCaptcha_maxattempts_'.$_SESSION['XoopsCaptcha_name']] = $maxAttempts; |
237
|
|
|
} |
238
|
|
|
*/ |
239
|
|
|
|
240
|
|
|
// Fail on too many attempts |
241
|
|
|
if (!empty($maxAttempts) && @$_SESSION['XoopsCaptcha_attempt_' . $this->config['name']] > $maxAttempts) { |
242
|
|
|
$form = XOOPS_CAPTCHA_TOOMANYATTEMPTS; |
243
|
|
|
// Load the form element |
244
|
|
|
} else { |
245
|
|
|
$form = $this->loadForm(); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
return $form; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* @return mixed |
253
|
|
|
*/ |
254
|
|
|
public function loadForm() |
255
|
|
|
{ |
256
|
|
|
require_once __DIR__ . '/' . $this->mode . '.php'; |
257
|
|
|
$class = 'XoopsCaptcha' . ucfirst($this->mode); |
258
|
|
|
$captchaHandler = new $class(); |
259
|
|
|
$captchaHandler->loadConfig($this->config); |
260
|
|
|
|
261
|
|
|
$form = $captchaHandler->render(); |
262
|
|
|
|
263
|
|
|
return $form; |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.