Passed
Push — master ( 164e41...836fbe )
by Michael
05:36 queued 01:25
created

XoopsCaptcha::loadConfig()   B

Complexity

Conditions 8
Paths 64

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 16
c 1
b 0
f 0
nc 64
nop 1
dl 0
loc 25
rs 8.4444
1
<?php
2
3
use Xmf\Request;
4
5
/**
6
 * CAPTCHA configurations for Image mode
7
 *
8
 * Based on DuGris' SecurityImage
9
 *
10
 * You may not change or alter any portion of this comment or credits
11
 * of supporting developers from this source code or any supporting source code
12
 * which is considered copyrighted (c) material of the original comment or credit authors.
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
 *
17
 * @copyright       (c) 2000-2021 XOOPS Project (https://xoops.org)
18
 * @license             GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
19
 * @package             class
20
 * @subpackage          CAPTCHA
21
 * @since               2.3.0
22
 * @author              Taiwen Jiang <[email protected]>
23
 */
24
defined('XOOPS_ROOT_PATH') || exit('Restricted access');
25
26
/**
27
 * Class XoopsCaptcha
28
 */
29
class XoopsCaptcha
30
{
31
    public $active;
32
    public $handler;
33
    public $path_basic;
34
    public $path_config;
35
    public $path_plugin;
36
    public $name;
37
    public $config  = array();
38
    public $message = array(); // Logging error messages
39
40
    /**
41
     * construct
42
     */
43
    protected function __construct()
44
    {
45
        xoops_loadLanguage('captcha');
46
        // Load static configurations
47
        $this->path_basic  = XOOPS_ROOT_PATH . '/class/captcha';
48
        $this->path_config = XOOPS_VAR_PATH . '/configs/captcha';
49
        $this->path_plugin = XOOPS_ROOT_PATH . '/Frameworks/captcha';
50
        $this->config      = $this->loadConfig();
51
        $this->name        = $this->config['name'];
52
    }
53
54
    /**
55
     * Get Instance
56
     *
57
     * @return XoopsCaptcha Instance
58
     */
59
    public static function getInstance()
60
    {
61
        static $instance;
62
        if (null === $instance) {
63
            $instance = new static();
64
        }
65
66
        return $instance;
67
    }
68
69
    /**
70
     * XoopsCaptcha::loadConfig()
71
     *
72
     * @param string|null $methodname the captcha method, i.e. text, image, recaptcha2, etc
73
     *
74
     * @return array
75
     */
76
    public function loadConfig($methodname = null)
77
    {
78
        $basic_config  = array();
79
        $plugin_config = array();
80
        $filename      = empty($methodname) ? 'config.php' : 'config.' . $methodname . '.php';
81
        $distfilename  = empty($methodname) ? 'config.dist.php' : 'config.' . $methodname . '.dist.php';
82
        if (file_exists($file = $this->path_config . '/' . $filename)) {
83
            $basic_config = include $file;
84
        } elseif (file_exists($distfile = $this->path_basic . '/' . $distfilename)) {
85
            $basic_config = include $distfile;
86
            if (false===copy($distfile, $file)) {
87
                trigger_error('Could not create captcha config file ' . $filename);
88
            }
89
        }
90
        // use of the path_plugin
91
        if (file_exists($file = $this->path_plugin . '/' . $filename)) {
92
            $plugin_config = include $file;
93
        }
94
95
        $config = array_merge($basic_config, $plugin_config);
96
        foreach ($config as $key => $val) {
97
            $this->config[$key] = $val;
98
        }
99
100
        return $config;
101
    }
102
103
    /**
104
     * XoopsCaptcha::isActive()
105
     *
106
     * @return bool
107
     */
108
    public function isActive()
109
    {
110
        if (null !== $this->active) {
111
            return $this->active;
112
        }
113
        if (!empty($this->config['disabled'])) {
114
            $this->active = false;
115
116
            return $this->active;
117
        }
118
        if (!empty($this->config['skipmember']) && is_object($GLOBALS['xoopsUser'])) {
119
            $this->active = false;
120
121
            return $this->active;
122
        }
123
        if (null === $this->handler) {
124
            $this->loadHandler();
125
        }
126
        $this->active = isset($this->handler);
127
128
        return $this->active;
129
    }
130
131
    /**
132
     * XoopsCaptcha::loadHandler()
133
     *
134
     * @param mixed $name
135
     * @return
136
     */
137
    public function loadHandler($name = null)
138
    {
139
        $name  = !empty($name) ? $name : (empty($this->config['mode']) ? 'text' : $this->config['mode']);
140
        $class = 'XoopsCaptcha' . ucfirst($name);
141
        if (!empty($this->handler) && get_class($this->handler) == $class) {
142
            return $this->handler;
143
        }
144
        $this->handler = null;
145
        if (file_exists($file = $this->path_basic . '/' . $name . '.php')) {
146
            require_once $file;
147
        } else {
148
            if (file_exists($file = $this->path_plugin . '/' . $name . '.php')) {
149
                require_once $file;
150
            }
151
        }
152
153
        if (!class_exists($class)) {
154
            $class = 'XoopsCaptchaText';
155
            require_once $this->path_basic . '/text.php';
156
        }
157
        $handler = new $class($this);
158
        if ($handler->isActive()) {
159
            $this->handler = $handler;
160
            $this->handler->loadConfig($name);
161
        }
162
163
        return $this->handler;
164
    }
165
166
    /**
167
     * XoopsCaptcha::setConfigs()
168
     *
169
     * @param  mixed $configs
170
     * @return bool
171
     */
172
    public function setConfigs($configs)
173
    {
174
        foreach ($configs as $key => $val) {
175
            $this->setConfig($key, $val);
176
        }
177
178
        return true;
179
    }
180
181
    /**
182
     * XoopsCaptcha::setConfig()
183
     *
184
     * @param  mixed $name
185
     * @param  mixed $val
186
     * @return bool
187
     */
188
    public function setConfig($name, $val)
189
    {
190
        if (isset($this->$name)) {
191
            $this->$name = $val;
192
        } else {
193
            $this->config[$name] = $val;
194
        }
195
196
        return true;
197
    }
198
199
    /**
200
     * Verify user submission
201
     */
202
    /**
203
     * XoopsCaptcha::verify()
204
     *
205
     * @param  mixed $skipMember
206
     * @param  mixed $name
207
     * @return bool
208
     */
209
    public function verify($skipMember = null, $name = null)
210
    {
211
        $sessionName = empty($name) ? $this->name : $name;
212
        $skipMember  = ($skipMember === null) ? $_SESSION["{$sessionName}_skipmember"] : $skipMember;
213
        $maxAttempts = $_SESSION["{$sessionName}_maxattempts"];
214
        $attempt     = $_SESSION["{$sessionName}_attempt"];
215
        $is_valid    = false;
216
        // Skip CAPTCHA verification if disabled
217
        if (!$this->isActive()) {
218
            $is_valid = true;
219
            // Skip CAPTCHA for member if set
220
        } elseif (!empty($skipMember) && is_object($GLOBALS['xoopsUser'])) {
221
            $is_valid = true;
222
            // Kill too many attempts
223
        } elseif (!empty($maxAttempts) && $attempt > $maxAttempts) {
224
            $this->message[] = _CAPTCHA_TOOMANYATTEMPTS;
225
            // Verify the code
226
        } else {
227
            $is_valid = $this->handler->verify($sessionName);
228
            $xoopsPreload = XoopsPreload::getInstance();
229
            $xoopsPreload->triggerEvent('core.behavior.captcha.result', $is_valid);
230
        }
231
232
        if (!$is_valid) {
233
            // Increase the attempt records on failure
234
            $_SESSION["{$sessionName}_attempt"]++;
235
            // Log the error message
236
            $this->message[] = _CAPTCHA_INVALID_CODE;
237
        } else {
238
            // reset attempt records on success
239
            $_SESSION["{$sessionName}_attempt"] = null;
240
        }
241
        $this->destroyGarbage(true);
242
243
        return $is_valid;
244
    }
245
246
    /**
247
     * XoopsCaptcha::getCaption()
248
     *
249
     * @return mixed|string
250
     */
251
    public function getCaption()
252
    {
253
        return defined('_CAPTCHA_CAPTION') ? constant('_CAPTCHA_CAPTION') : '';
254
    }
255
256
    /**
257
     * XoopsCaptcha::getMessage()
258
     *
259
     * @return string
260
     */
261
    public function getMessage()
262
    {
263
        return implode('<br>', $this->message);
264
    }
265
266
    /**
267
     * Destroy historical stuff
268
     * @param bool $clearSession
269
     * @return bool
270
     */
271
    public function destroyGarbage($clearSession = false)
272
    {
273
        $this->loadHandler();
274
        if (is_callable($this->handler, 'destroyGarbage')) {
0 ignored issues
show
Bug introduced by
'destroyGarbage' of type string is incompatible with the type boolean expected by parameter $syntax_only of is_callable(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

274
        if (is_callable($this->handler, /** @scrutinizer ignore-type */ 'destroyGarbage')) {
Loading history...
275
            $this->handler->destroyGarbage();
276
        }
277
        if ($clearSession) {
278
            $_SESSION[$this->name . '_name']        = null;
279
            $_SESSION[$this->name . '_skipmember']  = null;
280
            $_SESSION[$this->name . '_code']        = null;
281
            $_SESSION[$this->name . '_maxattempts'] = null;
282
        }
283
284
        return true;
285
    }
286
287
    /**
288
     * XoopsCaptcha::render()
289
     *
290
     * @return string
291
     */
292
    public function render()
293
    {
294
        $_SESSION[$this->name . '_name']       = $this->name;
295
        $_SESSION[$this->name . '_skipmember'] = $this->config['skipmember'];
296
        $form                                  = '';
297
        if (!$this->active || empty($this->config['name'])) {
298
            return $form;
299
        }
300
301
        $maxAttempts                            = $this->config['maxattempts'];
302
        $_SESSION[$this->name . '_maxattempts'] = $maxAttempts;
303
        $attempt                                = isset($_SESSION[$this->name . '_attempt']) ? $_SESSION[$this->name . '_attempt'] : 0;
304
        $_SESSION[$this->name . '_attempt']     = $attempt;
305
306
        // Failure on too many attempts
307
        if (!empty($maxAttempts) && $attempt > $maxAttempts) {
308
            $form = _CAPTCHA_TOOMANYATTEMPTS;
309
            // Load the form element
310
        } else {
311
            $form = $this->loadForm();
312
        }
313
314
        return $form;
315
    }
316
317
    /**
318
     * XoopsCaptcha::renderValidationJS()
319
     *
320
     * @return string
321
     */
322
    public function renderValidationJS()
323
    {
324
        if (!$this->active || empty($this->config['name'])) {
325
            return '';
326
        }
327
328
        return $this->handler->renderValidationJS();
329
    }
330
331
    /**
332
     * XoopsCaptcha::setCode()
333
     *
334
     * @param  mixed $code
335
     * @return bool
336
     */
337
    public function setCode($code = null)
338
    {
339
        $code = ($code === null) ? $this->handler->getCode() : $code;
340
        if (!empty($code)) {
341
            $_SESSION[$this->name . '_code'] = $code;
342
343
            return true;
344
        }
345
346
        return false;
347
    }
348
349
    /**
350
     * XoopsCaptcha::loadForm()
351
     *
352
     * @return
353
     */
354
    public function loadForm()
355
    {
356
        $form = $this->handler->render();
357
        $this->setCode();
358
359
        return $form;
360
    }
361
}
362
363
/**
364
 * Abstract class for CAPTCHA method
365
 *
366
 * Currently there are two types of CAPTCHA forms, text and image
367
 * The default mode is "text", it can be changed in the priority:
368
 * 1 If mode is set through XoopsFormCaptcha::setConfig("mode", $mode), take it
369
 * 2 Elseif mode is set though captcha/config.php, take it
370
 * 3 Else, take "text"
371
 */
372
class XoopsCaptchaMethod
373
{
374
    public $handler;
375
    public $config;
376
    public $code;
377
378
    /**
379
     * XoopsCaptchaMethod::__construct()
380
     *
381
     * @param mixed $handler
382
     */
383
    public function __construct($handler = null)
384
    {
385
        $this->handler = $handler;
386
    }
387
388
    /**
389
     * XoopsCaptchaMethod::isActive()
390
     *
391
     * @return bool
392
     */
393
    public function isActive()
394
    {
395
        return true;
396
    }
397
398
    /**
399
     * XoopsCaptchaMethod::loadConfig()
400
     *
401
     * @param  string $name
402
     * @return void
403
     */
404
    public function loadConfig($name = '')
405
    {
406
        $this->config = empty($name) ? $this->handler->config : array_merge($this->handler->config, $this->handler->loadConfig($name));
0 ignored issues
show
Bug introduced by
The method loadConfig() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

406
        $this->config = empty($name) ? $this->handler->config : array_merge($this->handler->config, $this->handler->/** @scrutinizer ignore-call */ loadConfig($name));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
407
    }
408
409
    /**
410
     * XoopsCaptchaMethod::getCode()
411
     *
412
     * @return string
413
     */
414
    public function getCode()
415
    {
416
        return (string)$this->code;
417
    }
418
419
    /**
420
     * XoopsCaptchaMethod::render()
421
     *
422
     * @return void
423
     */
424
    public function render()
425
    {
426
    }
427
428
    /**
429
     * @return string
430
     */
431
    public function renderValidationJS()
432
    {
433
        return '';
434
    }
435
436
    /**
437
     * XoopsCaptchaMethod::verify()
438
     *
439
     * @param  mixed $sessionName
440
     * @return bool
441
     */
442
    public function verify($sessionName = null)
443
    {
444
        $is_valid = false;
445
        if (!empty($_SESSION["{$sessionName}_code"])) {
446
            $func     = !empty($this->config['casesensitive']) ? 'strcmp' : 'strcasecmp';
447
            $is_valid = !$func(trim(Request::getString($sessionName, '', 'POST')), $_SESSION["{$sessionName}_code"]);
448
        }
449
450
        return $is_valid;
451
    }
452
}
453