<?php
/**
 * CAPTCHA configurations for Image mode
 *
 * Based on DuGris' SecurityImage
 *
 * You may not change or alter any portion of this comment or credits
 * of supporting developers from this source code or any supporting source code
 * which is considered copyrighted (c) material of the original comment or credit authors.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @copyright       The XOOPS project http://sourceforge.net/projects/xoops/
 * @license         GNU GPL 2 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.html)
 * @package         class
 * @subpackage      CAPTCHA
 * @since           2.3.0
 * @author          Taiwen Jiang <phppp@users.sourceforge.net>
 * @version         $Id: xoopscaptcha.php 8066 2011-11-06 05:09:33Z beckmi $
 */
defined('XOOPS_ROOT_PATH') or die('Restricted access');

class XoopsCaptcha
{
    // static $instance;
    var $active;
    var $handler;
    var $path_basic;
    var $path_plugin;
    var $name;
    var $config = array();
    var $message = array(); // Logging error messages

    /**
     * construct
     */
    function __construct()
    {
        xoops_loadLanguage('captcha');
        // Load static configurations
        $this->path_basic = XOOPS_ROOT_PATH . '/class/captcha';
        $this->path_plugin = XOOPS_ROOT_PATH . '/Frameworks/captcha';
        $this->config = $this->loadConfig();
        $this->name = $this->config['name'];
    }

    /**
     * Xoops Captcha Construct
     *
     * @return XoopsCaptcha
     */
    function XoopsCaptcha()
    {
        $this->__construct();
    }

    /**
     * Get Instance
     *
     * @return Instance
     */
    function &getInstance()
    {
        static $instance;
        if (!isset($instance)) {
            $class = __CLASS__;
            $instance = new $class();
        }
        return $instance;
    }

    /**
     * XoopsCaptcha::loadConfig()
     *
     * @param mixed $filename
     * @return
     */
    function loadConfig($filename = null)
    {
        $basic_config = array();
        $plugin_config = array();
        $filename = empty($filename) ? 'config.php' : 'config.' . $filename . '.php';
        if (file_exists($file = $this->path_basic . '/' . $filename)) {
            $basic_config = include $file;
        }
        if (file_exists($file = $this->path_plugin . '/' . $filename)) {
            $plugin_config = include $file;
        }

        $config = array_merge($basic_config, $plugin_config);
        foreach ($config as $key => $val) {
            $config[$key] = $val;
        }
        return $config;
    }

    /**
     * XoopsCaptcha::isActive()
     *
     * @return
     */
    function isActive()
    {
        if (isset($this->active)) {
            return $this->active;
        }
        if (!empty($this->config['disabled'])) {
            $this->active = false;
            return $this->active;
        }
        if (!empty($this->config['skipmember']) && is_object($GLOBALS['xoopsUser'])) {
            $this->active = false;
            return $this->active;
        }
        if (!isset($this->handler)) {
            $this->loadHandler();
        }
        $this->active = isset($this->handler);
        return $this->active;
    }

    /**
     * XoopsCaptcha::loadHandler()
     *
     * @param mixed $name
     * @return
     */
    function loadHandler($name = null)
    {
        $name = !empty($name) ? $name : (empty($this->config['mode']) ? 'text' : $this->config['mode']);
        $class = 'XoopsCaptcha' . ucfirst($name);
        if (!empty($this->handler) && get_class($this->handler) == $class) {
            return $this->handler;
        }
        $this->handler = null;
        if (file_exists($file = $this->path_basic . '/' . $name . '.php')) {
            require_once $file;
        } else {
            if (file_exists($file = $this->path_plugin . '/' . $name . '.php')) {
                require_once $file;
            }
        }

        if (!class_exists($class)) {
            $class = 'XoopsCaptchaText';
            require_once $this->path_basic . '/text.php';
        }
        $handler = new $class($this);
        if ($handler->isActive()) {
            $this->handler = $handler;
            $this->handler->loadConfig($name);
        }
        return $this->handler;
    }

    /**
     * XoopsCaptcha::setConfigs()
     *
     * @param mixed $configs
     * @return
     */
    function setConfigs($configs)
    {
        foreach ($configs as $key => $val) {
            $this->setConfig($key, $val);
        }
        return true;
    }

    /**
     * XoopsCaptcha::setConfig()
     *
     * @param mixed $name
     * @param mixed $val
     * @return
     */
    function setConfig($name, $val)
    {
        if (isset($this->$name)) {
            $this->$name = $val;
        } else {
            $this->config[$name] = $val;
        }
        return true;
    }

    /**
     * Verify user submission
     */
    /**
     * XoopsCaptcha::verify()
     *
     * @param mixed $skipMember
     * @param mixed $name
     * @return
     */
    function verify($skipMember = null, $name = null)
    {
        $sessionName = empty($name) ? $this->name : $name;
        $skipMember = ($skipMember === null) ? $_SESSION["{$sessionName}_skipmember"] : $skipMember;
        $maxAttempts =  $_SESSION["{$sessionName}_maxattempts"];
        $attempt = $_SESSION["{$sessionName}_attempt"];
        $is_valid = false;
        // Skip CAPTCHA verification if disabled
        if (!$this->isActive()) {
            $is_valid = true;
            // Skip CAPTCHA for member if set
        } else if (is_object($GLOBALS['xoopsUser']) && ! empty($skipMember)) {
            $is_valid = true;
            // Kill too many attempts
        } else if (!empty($maxAttempts) && $attempt > $maxAttempts) {
            $this->message[] = _CAPTCHA_TOOMANYATTEMPTS;
            // Verify the code
        } else {
            $is_valid = $this->handler->verify($sessionName);
        }

        if (!$is_valid) {
            // Increase the attempt records on failure
            $_SESSION["{$sessionName}_attempt"]++;
            // Log the error message
            $this->message[] = _CAPTCHA_INVALID_CODE;
        } else {
            // reset attempt records on success
            $_SESSION["{$sessionName}_attempt"] = null;
        }
        $this->destroyGarbage(true);
        return $is_valid;
    }

    /**
     * XoopsCaptcha::getCaption()
     *
     * @return
     */
    function getCaption()
    {
        return defined('_CAPTCHA_CAPTION') ? constant('_CAPTCHA_CAPTION') : '';
    }

    /**
     * XoopsCaptcha::getMessage()
     *
     * @return
     */
    function getMessage()
    {
        return implode('<br />', $this->message);
    }

    /**
     * Destory historical stuff
     */
    function destroyGarbage($clearSession = false)
    {
        $this->loadHandler();
        if (is_callable($this->handler, 'destroyGarbage')) {
            $this->handler->destroyGarbage();
        }
        if ($clearSession) {
            $_SESSION[$this->name . '_name'] = null;
            $_SESSION[$this->name . '_skipmember'] = null;
            $_SESSION[$this->name . '_code'] = null;
            $_SESSION[$this->name . '_maxattempts'] = null;
        }

        return true;
    }

    /**
     * XoopsCaptcha::render()
     *
     * @return
     */
    function render()
    {
        $_SESSION[$this->name . '_name'] = $this->name;
        $_SESSION[$this->name . '_skipmember'] = $this->config['skipmember'];
        $form = '';
        if (!$this->active || empty($this->config['name'])) {
            return $form;
        }

        $maxAttempts = $this->config['maxattempts'];
        $_SESSION[$this->name . '_maxattempts'] = $maxAttempts;
        $attempt = isset($_SESSION[$this->name . '_attempt']) ? $_SESSION[$this->name . '_attempt'] : 0;
        $_SESSION[$this->name . '_attempt'] = $attempt;

        // Failure on too many attempts
        if (!empty($maxAttempts) && $attempt > $maxAttempts) {
            $form = _CAPTCHA_TOOMANYATTEMPTS;
            // Load the form element
        } else {
            $form = $this->loadForm();
        }
        return $form;
    }

    /**
     * XoopsCaptcha::renderValidationJS()
     *
     * @return string
     */
    function renderValidationJS()
    {
        if (!$this->active || empty($this->config['name'])) {
            return '';
        }
        return $this->handler->renderValidationJS();
    }

    /**
     * XoopsCaptcha::setCode()
     *
     * @param mixed $code
     * @return
     */
    function setCode($code = null)
    {
        $code = ($code === null) ? $this->handler->getCode() : $code;
        if (!empty($code)) {
            $_SESSION[$this->name . '_code'] = $code;
            return true;
        }
        return false;
    }

    /**
     * XoopsCaptcha::loadForm()
     *
     * @return
     */
    function loadForm()
    {
        $form = $this->handler->render();
        $this->setCode();
        return $form;
    }
}

/**
 * Abstract class for CAPTCHA method
 *
 * Currently there are two types of CAPTCHA forms, text and image
 * The default mode is "text", it can be changed in the priority:
 * 1 If mode is set through XoopsFormCaptcha::setConfig("mode", $mode), take it
 * 2 Elseif mode is set though captcha/config.php, take it
 * 3 Else, take "text"
 */
class XoopsCaptchaMethod
{
    var $handler;
    var $config;
    var $code;

    /**
     * XoopsCaptchaMethod::__construct()
     *
     * @param mixed $handler
     */
    function __construct($handler = null)
    {
        $this->handler = $handler;
    }

    /**
     * XoopsCaptchaMethod::XoopsCaptchaMethod()
     *
     * @param mixed $handler
     */
    function XoopsCaptchaMethod($handler = null)
    {
        $this->__construct($handler);
    }

    /**
     * XoopsCaptchaMethod::isActive()
     *
     * @return
     */
    function isActive()
    {
        return true;
    }

    /**
     * XoopsCaptchaMethod::loadConfig()
     *
     * @param string $name
     * @return
     */
    function loadConfig($name = '')
    {
        $this->config = empty($name) ? $this->handler->config : array_merge($this->handler->config, $this->handler->loadConfig($name));
    }

    /**
     * XoopsCaptchaMethod::getCode()
     *
     * @return
     */
    function getCode()
    {
        return strval($this->code);
    }

    /**
     * XoopsCaptchaMethod::render()
     *
     * @return
     */
    function render()
    {
    }

    function renderValidationJS()
    {
        return '';
    }

    /**
     * XoopsCaptchaMethod::verify()
     *
     * @param mixed $sessionName
     * @return
     */
    function verify($sessionName = null)
    {
        $is_valid = false;
        if (!empty($_SESSION["{$sessionName}_code"])) {
            $func = !empty($this->config['casesensitive']) ? 'strcmp' : 'strcasecmp';
            $is_valid = !$func(trim(@$_POST[$sessionName]), $_SESSION["{$sessionName}_code"]);
        }
        return $is_valid;
    }

}

?>