1<?php 2 3use 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-2016 XOOPS Project (www.xoops.org) 18 * @license GNU GPL 2 (http://www.gnu.org/licenses/gpl-2.0.html) 19 * @package class 20 * @subpackage CAPTCHA 21 * @since 2.3.0 22 * @author Taiwen Jiang <phppp@users.sourceforge.net> 23 */ 24defined('XOOPS_ROOT_PATH') || exit('Restricted access'); 25 26/** 27 * Class XoopsCaptcha 28 */ 29class XoopsCaptcha 30{ 31 // static $instance; 32 public $active; 33 public $handler; 34 public $path_basic; 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_plugin = XOOPS_ROOT_PATH . '/Frameworks/captcha'; 49 $this->config = $this->loadConfig(); 50 $this->name = $this->config['name']; 51 } 52 53 /** 54 * Get Instance 55 * 56 * @return XoopsCaptcha Instance 57 */ 58 public static function getInstance() 59 { 60 static $instance; 61 if (null === $instance) { 62 $instance = new static(); 63 } 64 65 return $instance; 66 } 67 68 /** 69 * XoopsCaptcha::loadConfig() 70 * 71 * @param mixed $filename 72 * 73 * @return array 74 */ 75 public function loadConfig($filename = null) 76 { 77 $basic_config = array(); 78 $plugin_config = array(); 79 $filename = empty($filename) ? 'config.php' : 'config.' . $filename . '.php'; 80 if (file_exists($file = $this->path_basic . '/' . $filename)) { 81 $basic_config = include $file; 82 } 83 if (file_exists($file = $this->path_plugin . '/' . $filename)) { 84 $plugin_config = include $file; 85 } 86 87 $config = array_merge($basic_config, $plugin_config); 88 foreach ($config as $key => $val) { 89 $config[$key] = $val; 90 } 91 92 return $config; 93 } 94 95 /** 96 * XoopsCaptcha::isActive() 97 * 98 * @return bool 99 */ 100 public function isActive() 101 { 102 if (null !== $this->active) { 103 return $this->active; 104 } 105 if (!empty($this->config['disabled'])) { 106 $this->active = false; 107 108 return $this->active; 109 } 110 if (!empty($this->config['skipmember']) && is_object($GLOBALS['xoopsUser'])) { 111 $this->active = false; 112 113 return $this->active; 114 } 115 if (null === $this->handler) { 116 $this->loadHandler(); 117 } 118 $this->active = isset($this->handler); 119 120 return $this->active; 121 } 122 123 /** 124 * XoopsCaptcha::loadHandler() 125 * 126 * @param mixed $name 127 * @return 128 */ 129 public function loadHandler($name = null) 130 { 131 $name = !empty($name) ? $name : (empty($this->config['mode']) ? 'text' : $this->config['mode']); 132 $class = 'XoopsCaptcha' . ucfirst($name); 133 if (!empty($this->handler) && get_class($this->handler) == $class) { 134 return $this->handler; 135 } 136 $this->handler = null; 137 if (file_exists($file = $this->path_basic . '/' . $name . '.php')) { 138 require_once $file; 139 } else { 140 if (file_exists($file = $this->path_plugin . '/' . $name . '.php')) { 141 require_once $file; 142 } 143 } 144 145 if (!class_exists($class)) { 146 $class = 'XoopsCaptchaText'; 147 require_once $this->path_basic . '/text.php'; 148 } 149 $handler = new $class($this); 150 if ($handler->isActive()) { 151 $this->handler = $handler; 152 $this->handler->loadConfig($name); 153 } 154 155 return $this->handler; 156 } 157 158 /** 159 * XoopsCaptcha::setConfigs() 160 * 161 * @param mixed $configs 162 * @return bool 163 */ 164 public function setConfigs($configs) 165 { 166 foreach ($configs as $key => $val) { 167 $this->setConfig($key, $val); 168 } 169 170 return true; 171 } 172 173 /** 174 * XoopsCaptcha::setConfig() 175 * 176 * @param mixed $name 177 * @param mixed $val 178 * @return bool 179 */ 180 public function setConfig($name, $val) 181 { 182 if (isset($this->$name)) { 183 $this->$name = $val; 184 } else { 185 $this->config[$name] = $val; 186 } 187 188 return true; 189 } 190 191 /** 192 * Verify user submission 193 */ 194 /** 195 * XoopsCaptcha::verify() 196 * 197 * @param mixed $skipMember 198 * @param mixed $name 199 * @return bool 200 */ 201 public function verify($skipMember = null, $name = null) 202 { 203 $sessionName = empty($name) ? $this->name : $name; 204 $skipMember = ($skipMember === null) ? $_SESSION["{$sessionName}_skipmember"] : $skipMember; 205 $maxAttempts = $_SESSION["{$sessionName}_maxattempts"]; 206 $attempt = $_SESSION["{$sessionName}_attempt"]; 207 $is_valid = false; 208 // Skip CAPTCHA verification if disabled 209 if (!$this->isActive()) { 210 $is_valid = true; 211 // Skip CAPTCHA for member if set 212 } elseif (!empty($skipMember) && is_object($GLOBALS['xoopsUser'])) { 213 $is_valid = true; 214 // Kill too many attempts 215 } elseif (!empty($maxAttempts) && $attempt > $maxAttempts) { 216 $this->message[] = _CAPTCHA_TOOMANYATTEMPTS; 217 // Verify the code 218 } else { 219 $is_valid = $this->handler->verify($sessionName); 220 $xoopsPreload = XoopsPreload::getInstance(); 221 $xoopsPreload->triggerEvent('core.behavior.captcha.result', $is_valid); 222 } 223 224 if (!$is_valid) { 225 // Increase the attempt records on failure 226 $_SESSION["{$sessionName}_attempt"]++; 227 // Log the error message 228 $this->message[] = _CAPTCHA_INVALID_CODE; 229 } else { 230 // reset attempt records on success 231 $_SESSION["{$sessionName}_attempt"] = null; 232 } 233 $this->destroyGarbage(true); 234 235 return $is_valid; 236 } 237 238 /** 239 * XoopsCaptcha::getCaption() 240 * 241 * @return mixed|string 242 */ 243 public function getCaption() 244 { 245 return defined('_CAPTCHA_CAPTION') ? constant('_CAPTCHA_CAPTION') : ''; 246 } 247 248 /** 249 * XoopsCaptcha::getMessage() 250 * 251 * @return string 252 */ 253 public function getMessage() 254 { 255 return implode('<br>', $this->message); 256 } 257 258 /** 259 * Destroy historical stuff 260 * @param bool $clearSession 261 * @return bool 262 */ 263 public function destroyGarbage($clearSession = false) 264 { 265 $this->loadHandler(); 266 if (is_callable($this->handler, 'destroyGarbage')) { 267 $this->handler->destroyGarbage(); 268 } 269 if ($clearSession) { 270 $_SESSION[$this->name . '_name'] = null; 271 $_SESSION[$this->name . '_skipmember'] = null; 272 $_SESSION[$this->name . '_code'] = null; 273 $_SESSION[$this->name . '_maxattempts'] = null; 274 } 275 276 return true; 277 } 278 279 /** 280 * XoopsCaptcha::render() 281 * 282 * @return string 283 */ 284 public function render() 285 { 286 $_SESSION[$this->name . '_name'] = $this->name; 287 $_SESSION[$this->name . '_skipmember'] = $this->config['skipmember']; 288 $form = ''; 289 if (!$this->active || empty($this->config['name'])) { 290 return $form; 291 } 292 293 $maxAttempts = $this->config['maxattempts']; 294 $_SESSION[$this->name . '_maxattempts'] = $maxAttempts; 295 $attempt = isset($_SESSION[$this->name . '_attempt']) ? $_SESSION[$this->name . '_attempt'] : 0; 296 $_SESSION[$this->name . '_attempt'] = $attempt; 297 298 // Failure on too many attempts 299 if (!empty($maxAttempts) && $attempt > $maxAttempts) { 300 $form = _CAPTCHA_TOOMANYATTEMPTS; 301 // Load the form element 302 } else { 303 $form = $this->loadForm(); 304 } 305 306 return $form; 307 } 308 309 /** 310 * XoopsCaptcha::renderValidationJS() 311 * 312 * @return string 313 */ 314 public function renderValidationJS() 315 { 316 if (!$this->active || empty($this->config['name'])) { 317 return ''; 318 } 319 320 return $this->handler->renderValidationJS(); 321 } 322 323 /** 324 * XoopsCaptcha::setCode() 325 * 326 * @param mixed $code 327 * @return bool 328 */ 329 public function setCode($code = null) 330 { 331 $code = ($code === null) ? $this->handler->getCode() : $code; 332 if (!empty($code)) { 333 $_SESSION[$this->name . '_code'] = $code; 334 335 return true; 336 } 337 338 return false; 339 } 340 341 /** 342 * XoopsCaptcha::loadForm() 343 * 344 * @return 345 */ 346 public function loadForm() 347 { 348 $form = $this->handler->render(); 349 $this->setCode(); 350 351 return $form; 352 } 353} 354 355/** 356 * Abstract class for CAPTCHA method 357 * 358 * Currently there are two types of CAPTCHA forms, text and image 359 * The default mode is "text", it can be changed in the priority: 360 * 1 If mode is set through XoopsFormCaptcha::setConfig("mode", $mode), take it 361 * 2 Elseif mode is set though captcha/config.php, take it 362 * 3 Else, take "text" 363 */ 364class XoopsCaptchaMethod 365{ 366 public $handler; 367 public $config; 368 public $code; 369 370 /** 371 * XoopsCaptchaMethod::__construct() 372 * 373 * @param mixed $handler 374 */ 375 public function __construct($handler = null) 376 { 377 $this->handler = $handler; 378 } 379 380 /** 381 * XoopsCaptchaMethod::isActive() 382 * 383 * @return bool 384 */ 385 public function isActive() 386 { 387 return true; 388 } 389 390 /** 391 * XoopsCaptchaMethod::loadConfig() 392 * 393 * @param string $name 394 * @return void 395 */ 396 public function loadConfig($name = '') 397 { 398 $this->config = empty($name) ? $this->handler->config : array_merge($this->handler->config, $this->handler->loadConfig($name)); 399 } 400 401 /** 402 * XoopsCaptchaMethod::getCode() 403 * 404 * @return string 405 */ 406 public function getCode() 407 { 408 return (string)$this->code; 409 } 410 411 /** 412 * XoopsCaptchaMethod::render() 413 * 414 * @return void 415 */ 416 public function render() 417 { 418 } 419 420 /** 421 * @return string 422 */ 423 public function renderValidationJS() 424 { 425 return ''; 426 } 427 428 /** 429 * XoopsCaptchaMethod::verify() 430 * 431 * @param mixed $sessionName 432 * @return bool 433 */ 434 public function verify($sessionName = null) 435 { 436 $is_valid = false; 437 if (!empty($_SESSION["{$sessionName}_code"])) { 438 $func = !empty($this->config['casesensitive']) ? 'strcmp' : 'strcasecmp'; 439// $is_valid = !$func(trim(@$_POST[$sessionName]), $_SESSION["{$sessionName}_code"]); 440 $is_valid = !$func(trim(Request::getString($sessionName, '', 'POST')), $_SESSION["{$sessionName}_code"]); 441 } 442 443 return $is_valid; 444 } 445} 446