|
1
|
|
|
<?php |
|
|
|
|
|
|
2
|
|
|
/** |
|
3
|
|
|
* CAPTCHA configurations for Image mode |
|
4
|
|
|
* |
|
5
|
|
|
* Based on DuGris' SecurityImage |
|
6
|
|
|
* |
|
7
|
|
|
* You may not change or alter any portion of this comment or credits |
|
8
|
|
|
* of supporting developers from this source code or any supporting source code |
|
9
|
|
|
* which is considered copyrighted (c) material of the original comment or credit authors. |
|
10
|
|
|
* This program is distributed in the hope that it will be useful, |
|
11
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|
13
|
|
|
* |
|
14
|
|
|
* @copyright (c) 2000-2016 XOOPS Project (www.xoops.org) |
|
15
|
|
|
* @license GNU GPL 2 (http://www.gnu.org/licenses/gpl-2.0.html) |
|
16
|
|
|
* @package class |
|
17
|
|
|
* @subpackage CAPTCHA |
|
18
|
|
|
* @since 2.3.0 |
|
19
|
|
|
* @author Taiwen Jiang <[email protected]> |
|
20
|
|
|
*/ |
|
21
|
|
|
defined('XOOPS_ROOT_PATH') || exit('Restricted access'); |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* Class XoopsCaptcha |
|
25
|
|
|
*/ |
|
26
|
|
|
class XoopsCaptcha |
|
27
|
|
|
{ |
|
28
|
|
|
// static $instance; |
|
29
|
|
|
public $active; |
|
30
|
|
|
public $handler; |
|
31
|
|
|
public $path_basic; |
|
32
|
|
|
public $path_plugin; |
|
33
|
|
|
public $name; |
|
34
|
|
|
public $config = array(); |
|
35
|
|
|
public $message = array(); // Logging error messages |
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* construct |
|
39
|
|
|
*/ |
|
40
|
|
|
public function __construct() |
|
41
|
|
|
{ |
|
42
|
|
|
xoops_loadLanguage('captcha'); |
|
43
|
|
|
// Load static configurations |
|
44
|
|
|
$this->path_basic = XOOPS_ROOT_PATH . '/class/captcha'; |
|
45
|
|
|
$this->path_plugin = XOOPS_ROOT_PATH . '/Frameworks/captcha'; |
|
46
|
|
|
$this->config = $this->loadConfig(); |
|
47
|
|
|
$this->name = $this->config['name']; |
|
48
|
|
|
} |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* Deprecated, use getInstance() instead |
|
52
|
|
|
*/ |
|
53
|
|
|
public static function instance() |
|
54
|
|
|
{ |
|
55
|
|
|
return self::getInstance(); |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* Get Instance |
|
60
|
|
|
* |
|
61
|
|
|
* @return Instance |
|
62
|
|
|
*/ |
|
63
|
|
|
public static function getInstance() |
|
64
|
|
|
{ |
|
65
|
|
|
static $instance; |
|
66
|
|
|
if (!isset($instance)) { |
|
67
|
|
|
$class = __CLASS__; |
|
68
|
|
|
$instance = new $class(); |
|
69
|
|
|
} |
|
70
|
|
|
|
|
71
|
|
|
return $instance; |
|
72
|
|
|
} |
|
73
|
|
|
|
|
74
|
|
|
/** |
|
75
|
|
|
* XoopsCaptcha::loadConfig() |
|
76
|
|
|
* |
|
77
|
|
|
* @param mixed $filename |
|
78
|
|
|
* |
|
79
|
|
|
* @return array |
|
80
|
|
|
*/ |
|
81
|
|
|
public function loadConfig($filename = null) |
|
82
|
|
|
{ |
|
83
|
|
|
$basic_config = array(); |
|
84
|
|
|
$plugin_config = array(); |
|
85
|
|
|
$filename = empty($filename) ? 'config.php' : 'config.' . $filename . '.php'; |
|
86
|
|
|
if (file_exists($file = $this->path_basic . '/' . $filename)) { |
|
87
|
|
|
$basic_config = include $file; |
|
88
|
|
|
} |
|
89
|
|
|
if (file_exists($file = $this->path_plugin . '/' . $filename)) { |
|
90
|
|
|
$plugin_config = include $file; |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
$config = array_merge($basic_config, $plugin_config); |
|
94
|
|
|
foreach ($config as $key => $val) { |
|
95
|
|
|
$config[$key] = $val; |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
return $config; |
|
99
|
|
|
} |
|
100
|
|
|
|
|
101
|
|
|
/** |
|
102
|
|
|
* XoopsCaptcha::isActive() |
|
103
|
|
|
* |
|
104
|
|
|
* @return bool |
|
105
|
|
|
*/ |
|
106
|
|
|
public function isActive() |
|
107
|
|
|
{ |
|
108
|
|
|
if (isset($this->active)) { |
|
109
|
|
|
return $this->active; |
|
110
|
|
|
} |
|
111
|
|
|
if (!empty($this->config['disabled'])) { |
|
112
|
|
|
$this->active = false; |
|
113
|
|
|
|
|
114
|
|
|
return $this->active; |
|
115
|
|
|
} |
|
116
|
|
|
if (!empty($this->config['skipmember']) && is_object($GLOBALS['xoopsUser'])) { |
|
117
|
|
|
$this->active = false; |
|
118
|
|
|
|
|
119
|
|
|
return $this->active; |
|
120
|
|
|
} |
|
121
|
|
|
if (!isset($this->handler)) { |
|
122
|
|
|
$this->loadHandler(); |
|
123
|
|
|
} |
|
124
|
|
|
$this->active = isset($this->handler); |
|
125
|
|
|
|
|
126
|
|
|
return $this->active; |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* XoopsCaptcha::loadHandler() |
|
131
|
|
|
* |
|
132
|
|
|
* @param mixed $name |
|
133
|
|
|
* @return |
|
134
|
|
|
*/ |
|
135
|
|
|
public function loadHandler($name = null) |
|
|
|
|
|
|
136
|
|
|
{ |
|
137
|
|
|
$name = !empty($name) ? $name : (empty($this->config['mode']) ? 'text' : $this->config['mode']); |
|
138
|
|
|
$class = 'XoopsCaptcha' . ucfirst($name); |
|
139
|
|
|
if (!empty($this->handler) && get_class($this->handler) == $class) { |
|
140
|
|
|
return $this->handler; |
|
141
|
|
|
} |
|
142
|
|
|
$this->handler = null; |
|
143
|
|
|
if (file_exists($file = $this->path_basic . '/' . $name . '.php')) { |
|
144
|
|
|
require_once $file; |
|
145
|
|
View Code Duplication |
} else { |
|
146
|
|
|
if (file_exists($file = $this->path_plugin . '/' . $name . '.php')) { |
|
147
|
|
|
require_once $file; |
|
148
|
|
|
} |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
|
|
if (!class_exists($class)) { |
|
152
|
|
|
$class = 'XoopsCaptchaText'; |
|
153
|
|
|
require_once $this->path_basic . '/text.php'; |
|
154
|
|
|
} |
|
155
|
|
|
$handler = new $class($this); |
|
156
|
|
|
if ($handler->isActive()) { |
|
157
|
|
|
$this->handler = $handler; |
|
158
|
|
|
$this->handler->loadConfig($name); |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
return $this->handler; |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
/** |
|
165
|
|
|
* XoopsCaptcha::setConfigs() |
|
166
|
|
|
* |
|
167
|
|
|
* @param mixed $configs |
|
168
|
|
|
* @return bool |
|
169
|
|
|
*/ |
|
170
|
|
|
public function setConfigs($configs) |
|
171
|
|
|
{ |
|
172
|
|
|
foreach ($configs as $key => $val) { |
|
173
|
|
|
$this->setConfig($key, $val); |
|
174
|
|
|
} |
|
175
|
|
|
|
|
176
|
|
|
return true; |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* XoopsCaptcha::setConfig() |
|
181
|
|
|
* |
|
182
|
|
|
* @param mixed $name |
|
183
|
|
|
* @param mixed $val |
|
184
|
|
|
* @return bool |
|
185
|
|
|
*/ |
|
186
|
|
|
public function setConfig($name, $val) |
|
187
|
|
|
{ |
|
188
|
|
|
if (isset($this->$name)) { |
|
189
|
|
|
$this->$name = $val; |
|
190
|
|
|
} else { |
|
191
|
|
|
$this->config[$name] = $val; |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
return true; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* Verify user submission |
|
199
|
|
|
*/ |
|
200
|
|
|
/** |
|
201
|
|
|
* XoopsCaptcha::verify() |
|
202
|
|
|
* |
|
203
|
|
|
* @param mixed $skipMember |
|
204
|
|
|
* @param mixed $name |
|
205
|
|
|
* @return bool |
|
206
|
|
|
*/ |
|
207
|
|
|
public function verify($skipMember = null, $name = null) |
|
208
|
|
|
{ |
|
209
|
|
|
$sessionName = empty($name) ? $this->name : $name; |
|
210
|
|
|
$skipMember = ($skipMember === null) ? $_SESSION["{$sessionName}_skipmember"] : $skipMember; |
|
211
|
|
|
$maxAttempts = $_SESSION["{$sessionName}_maxattempts"]; |
|
212
|
|
|
$attempt = $_SESSION["{$sessionName}_attempt"]; |
|
213
|
|
|
$is_valid = false; |
|
214
|
|
|
// Skip CAPTCHA verification if disabled |
|
215
|
|
|
if (!$this->isActive()) { |
|
216
|
|
|
$is_valid = true; |
|
217
|
|
|
// Skip CAPTCHA for member if set |
|
218
|
|
|
} elseif (is_object($GLOBALS['xoopsUser']) && !empty($skipMember)) { |
|
219
|
|
|
$is_valid = true; |
|
220
|
|
|
// Kill too many attempts |
|
221
|
|
|
} elseif (!empty($maxAttempts) && $attempt > $maxAttempts) { |
|
222
|
|
|
$this->message[] = _CAPTCHA_TOOMANYATTEMPTS; |
|
223
|
|
|
// Verify the code |
|
224
|
|
|
} else { |
|
225
|
|
|
$is_valid = $this->handler->verify($sessionName); |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
if (!$is_valid) { |
|
229
|
|
|
// Increase the attempt records on failure |
|
230
|
|
|
$_SESSION["{$sessionName}_attempt"]++; |
|
231
|
|
|
// Log the error message |
|
232
|
|
|
$this->message[] = _CAPTCHA_INVALID_CODE; |
|
233
|
|
|
} else { |
|
234
|
|
|
// reset attempt records on success |
|
235
|
|
|
$_SESSION["{$sessionName}_attempt"] = null; |
|
236
|
|
|
} |
|
237
|
|
|
$this->destroyGarbage(true); |
|
238
|
|
|
|
|
239
|
|
|
return $is_valid; |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
/** |
|
243
|
|
|
* XoopsCaptcha::getCaption() |
|
244
|
|
|
* |
|
245
|
|
|
* @return mixed|string |
|
246
|
|
|
*/ |
|
247
|
|
|
public function getCaption() |
|
248
|
|
|
{ |
|
249
|
|
|
return defined('_CAPTCHA_CAPTION') ? constant('_CAPTCHA_CAPTION') : ''; |
|
250
|
|
|
} |
|
251
|
|
|
|
|
252
|
|
|
/** |
|
253
|
|
|
* XoopsCaptcha::getMessage() |
|
254
|
|
|
* |
|
255
|
|
|
* @return string |
|
256
|
|
|
*/ |
|
257
|
|
|
public function getMessage() |
|
258
|
|
|
{ |
|
259
|
|
|
return implode('<br />', $this->message); |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
/** |
|
263
|
|
|
* Destroy historical stuff |
|
264
|
|
|
* @param bool $clearSession |
|
265
|
|
|
* @return bool |
|
266
|
|
|
*/ |
|
267
|
|
|
public function destroyGarbage($clearSession = false) |
|
268
|
|
|
{ |
|
269
|
|
|
$this->loadHandler(); |
|
270
|
|
|
if (is_callable($this->handler, 'destroyGarbage')) { |
|
271
|
|
|
$this->handler->destroyGarbage(); |
|
272
|
|
|
} |
|
273
|
|
|
if ($clearSession) { |
|
274
|
|
|
$_SESSION[$this->name . '_name'] = null; |
|
275
|
|
|
$_SESSION[$this->name . '_skipmember'] = null; |
|
276
|
|
|
$_SESSION[$this->name . '_code'] = null; |
|
277
|
|
|
$_SESSION[$this->name . '_maxattempts'] = null; |
|
278
|
|
|
} |
|
279
|
|
|
|
|
280
|
|
|
return true; |
|
281
|
|
|
} |
|
282
|
|
|
|
|
283
|
|
|
/** |
|
284
|
|
|
* XoopsCaptcha::render() |
|
285
|
|
|
* |
|
286
|
|
|
* @return string |
|
287
|
|
|
*/ |
|
288
|
|
|
public function render() |
|
289
|
|
|
{ |
|
290
|
|
|
$_SESSION[$this->name . '_name'] = $this->name; |
|
291
|
|
|
$_SESSION[$this->name . '_skipmember'] = $this->config['skipmember']; |
|
292
|
|
|
$form = ''; |
|
293
|
|
|
if (!$this->active || empty($this->config['name'])) { |
|
294
|
|
|
return $form; |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
|
|
$maxAttempts = $this->config['maxattempts']; |
|
298
|
|
|
$_SESSION[$this->name . '_maxattempts'] = $maxAttempts; |
|
299
|
|
|
$attempt = isset($_SESSION[$this->name . '_attempt']) ? $_SESSION[$this->name . '_attempt'] : 0; |
|
300
|
|
|
$_SESSION[$this->name . '_attempt'] = $attempt; |
|
301
|
|
|
|
|
302
|
|
|
// Failure on too many attempts |
|
303
|
|
|
if (!empty($maxAttempts) && $attempt > $maxAttempts) { |
|
304
|
|
|
$form = _CAPTCHA_TOOMANYATTEMPTS; |
|
305
|
|
|
// Load the form element |
|
306
|
|
|
} else { |
|
307
|
|
|
$form = $this->loadForm(); |
|
308
|
|
|
} |
|
309
|
|
|
|
|
310
|
|
|
return $form; |
|
311
|
|
|
} |
|
312
|
|
|
|
|
313
|
|
|
/** |
|
314
|
|
|
* XoopsCaptcha::renderValidationJS() |
|
315
|
|
|
* |
|
316
|
|
|
* @return string |
|
317
|
|
|
*/ |
|
318
|
|
|
public function renderValidationJS() |
|
319
|
|
|
{ |
|
320
|
|
|
if (!$this->active || empty($this->config['name'])) { |
|
321
|
|
|
return ''; |
|
322
|
|
|
} |
|
323
|
|
|
|
|
324
|
|
|
return $this->handler->renderValidationJS(); |
|
325
|
|
|
} |
|
326
|
|
|
|
|
327
|
|
|
/** |
|
328
|
|
|
* XoopsCaptcha::setCode() |
|
329
|
|
|
* |
|
330
|
|
|
* @param mixed $code |
|
331
|
|
|
* @return bool |
|
332
|
|
|
*/ |
|
333
|
|
|
public function setCode($code = null) |
|
334
|
|
|
{ |
|
335
|
|
|
$code = ($code === null) ? $this->handler->getCode() : $code; |
|
336
|
|
|
if (!empty($code)) { |
|
337
|
|
|
$_SESSION[$this->name . '_code'] = $code; |
|
338
|
|
|
|
|
339
|
|
|
return true; |
|
340
|
|
|
} |
|
341
|
|
|
|
|
342
|
|
|
return false; |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
/** |
|
346
|
|
|
* XoopsCaptcha::loadForm() |
|
347
|
|
|
* |
|
348
|
|
|
* @return |
|
349
|
|
|
*/ |
|
350
|
|
|
public function loadForm() |
|
|
|
|
|
|
351
|
|
|
{ |
|
352
|
|
|
$form = $this->handler->render(); |
|
353
|
|
|
$this->setCode(); |
|
354
|
|
|
|
|
355
|
|
|
return $form; |
|
356
|
|
|
} |
|
357
|
|
|
} |
|
358
|
|
|
|
|
359
|
|
|
/** |
|
360
|
|
|
* Abstract class for CAPTCHA method |
|
361
|
|
|
* |
|
362
|
|
|
* Currently there are two types of CAPTCHA forms, text and image |
|
363
|
|
|
* The default mode is "text", it can be changed in the priority: |
|
364
|
|
|
* 1 If mode is set through XoopsFormCaptcha::setConfig("mode", $mode), take it |
|
365
|
|
|
* 2 Elseif mode is set though captcha/config.php, take it |
|
366
|
|
|
* 3 Else, take "text" |
|
367
|
|
|
*/ |
|
368
|
|
|
class XoopsCaptchaMethod |
|
|
|
|
|
|
369
|
|
|
{ |
|
370
|
|
|
public $handler; |
|
371
|
|
|
public $config; |
|
372
|
|
|
public $code; |
|
373
|
|
|
|
|
374
|
|
|
/** |
|
375
|
|
|
* XoopsCaptchaMethod::__construct() |
|
376
|
|
|
* |
|
377
|
|
|
* @param mixed $handler |
|
378
|
|
|
*/ |
|
379
|
|
|
public function __construct($handler = null) |
|
380
|
|
|
{ |
|
381
|
|
|
$this->handler = $handler; |
|
382
|
|
|
} |
|
383
|
|
|
|
|
384
|
|
|
/** |
|
385
|
|
|
* XoopsCaptchaMethod::isActive() |
|
386
|
|
|
* |
|
387
|
|
|
* @return bool |
|
388
|
|
|
*/ |
|
389
|
|
|
public function isActive() |
|
390
|
|
|
{ |
|
391
|
|
|
return true; |
|
392
|
|
|
} |
|
393
|
|
|
|
|
394
|
|
|
/** |
|
395
|
|
|
* XoopsCaptchaMethod::loadConfig() |
|
396
|
|
|
* |
|
397
|
|
|
* @param string $name |
|
398
|
|
|
* @return void |
|
399
|
|
|
*/ |
|
400
|
|
|
public function loadConfig($name = '') |
|
401
|
|
|
{ |
|
402
|
|
|
$this->config = empty($name) ? $this->handler->config : array_merge($this->handler->config, $this->handler->loadConfig($name)); |
|
403
|
|
|
} |
|
404
|
|
|
|
|
405
|
|
|
/** |
|
406
|
|
|
* XoopsCaptchaMethod::getCode() |
|
407
|
|
|
* |
|
408
|
|
|
* @return string |
|
409
|
|
|
*/ |
|
410
|
|
|
public function getCode() |
|
411
|
|
|
{ |
|
412
|
|
|
return (string)$this->code; |
|
413
|
|
|
} |
|
414
|
|
|
|
|
415
|
|
|
/** |
|
416
|
|
|
* XoopsCaptchaMethod::render() |
|
417
|
|
|
* |
|
418
|
|
|
* @return void |
|
419
|
|
|
*/ |
|
420
|
|
|
public function render() |
|
421
|
|
|
{ |
|
422
|
|
|
} |
|
423
|
|
|
|
|
424
|
|
|
/** |
|
425
|
|
|
* @return string |
|
426
|
|
|
*/ |
|
427
|
|
|
public function renderValidationJS() |
|
428
|
|
|
{ |
|
429
|
|
|
return ''; |
|
430
|
|
|
} |
|
431
|
|
|
|
|
432
|
|
|
/** |
|
433
|
|
|
* XoopsCaptchaMethod::verify() |
|
434
|
|
|
* |
|
435
|
|
|
* @param mixed $sessionName |
|
436
|
|
|
* @return bool |
|
437
|
|
|
*/ |
|
438
|
|
|
public function verify($sessionName = null) |
|
439
|
|
|
{ |
|
440
|
|
|
$is_valid = false; |
|
441
|
|
|
if (!empty($_SESSION["{$sessionName}_code"])) { |
|
442
|
|
|
$func = !empty($this->config['casesensitive']) ? 'strcmp' : 'strcasecmp'; |
|
443
|
|
|
$is_valid = !$func(trim(@$_POST[$sessionName]), $_SESSION["{$sessionName}_code"]); |
|
444
|
|
|
} |
|
445
|
|
|
|
|
446
|
|
|
return $is_valid; |
|
447
|
|
|
} |
|
448
|
|
|
} |
|
449
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.