<?php
/***************************************************************************
*                            Dolphin Smart Community Builder
*                              -------------------
*     begin                : Mon Mar 23 2006
*     copyright            : (C) 2007 BoonEx Group
*     website              : http://www.boonex.com
* This file is part of Dolphin - Smart Community Builder
*
* Dolphin is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the
* License, or  any later version.
*
* Dolphin 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.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with Dolphin,
* see license.txt file; if not, write to marketing@boonex.com
***************************************************************************/

bx_import('BxDolSession');

define('BX_DOL_FORM_METHOD_GET', 'get');
define('BX_DOL_FORM_METHOD_POST', 'post');

/**
 * Forms base class, consists of
 *  - automatic check routines
 *  - get clear variables from input routines
 *  - database insert/update routines
 *
 *  Init form object examples:  
 *
 *      $aForm = array(
 *            'form_attrs' => array(
 *                'name'     => 'form_my', 
 *                'method'   => 'post',
 *            ),      
 *
 *            'params' => array (
 *                'db' => array(
 *                    'table' => 'table_name', // table name
 *                    'key' => 'ID', // key field name
 *                    'uri' => 'EntryUri', // uri field name
 *                    'uri_title' => 'Title', // title field to generate uri from 
 *                    'submit_name' => 'submit_form', // some filed name with non empty value to determine if the for was submitted, 
 *                                                       in most cases it is submit button name
 *                ),
 *                'csrf' => array(
 *					  'disable' => true, //if it wasn't set or has some other value then CSRF checking is enabled for current form, take a look at sys_security_form_token_enable to disable CSRF checking completely.  
 *                )
 *              ),
 *                  
 *            'inputs' => array(
 *
 *                'Title' => array(
 *                    'type' => 'text',
 *                    'name' => 'Title', // the same as key and database field name 
 *                    'caption' => 'Some caption',
 *                    'required' => true,
 *
 *                    // checker params
 *                    'checker' => array (  
 *                        'func' => 'length', // see BxDolFormCheckerHelper class for all check* functions
 *                        'params' => array(3,100),
 *                        'error' => 'length must be from 3 to 100 characters',
 *                    ),
 *                    // database params
 *                    'db' => array (
 *                        'pass' => 'Xss',  // do XSS clear before getting this value, see BxDolFormCheckerHelper class for all pass* functions
 *                    ),
 *                ),                
 *
 *                'Description' => array(
 *                    'type' => 'textarea',
 *                    'name' => 'Description', // the same as key and database field name 
 *                    'caption' => 'Some caption',
 *                    'required' => true,
 *
 *                    // checker params
 *                    'checker' => array (
 *                        'func' => 'length',
 *                        'error' => 'enter at least 3 characters',
 *                        'params' => array(3,64000),
 *                    ),                    
 *                    'db' => array (
 *                        'pass' => 'XssHtml',  // do XSS clear, but keep HTML before getting this value
 *                    ),                    
 *                ),
 *            );
 *
 *
 *   Example of using:
 *
 *
 *        $oForm = new BxTemplFormView ($aForm);
 *        $oForm->initChecker();
 *
 *        if ($oForm->isSubmittedAndValid ()) {
 *            
 *            // add additional vars to database, in this case creation date field is added
 *            $aValsAdd = array ( 
 *                'Date' => time(),
 *            );
 *            
 *            echo 'insert last id: ' . $oForm->insert ($aValsAdd); // insert validated data to database
 *                         
 *        } else {
 *
 *            echo $oForm->getCode (); // show form
 *
 *        }
 *
 */
class BxDolForm {
    var $_isValid = true;
    var $_sCheckerHelper;

    var $aFormAttrs;
    var $aTableAttrs;
    var $aInputs;
    var $aParams;

    /**
     * Form element id
     * @var string
     */
    var $id;
    
    function BxDolForm ($aInfo) {
        $this->aFormAttrs    = isset($aInfo['form_attrs'])   ? $aInfo['form_attrs']  : array();
        $this->aTableAttrs   = isset($aInfo['table_attrs'])  ? $aInfo['table_attrs'] : array();
        $this->aInputs       = isset($aInfo['inputs'])       ? $aInfo['inputs']      : array();
        $this->aParams       = isset($aInfo['params'])       ? $aInfo['params']      : array();
        
        // get form element id
        $this->id = $this->aFormAttrs['id'] = (!empty($this->aFormAttrs['id']) ? $this->aFormAttrs['id'] : (!empty($this->aFormAttrs['name']) ? $this->aFormAttrs['name'] : 'form_advanced'));
        
        // set default method
        if (!isset($this->aFormAttrs['method']))
            $this->aFormAttrs['method'] = BX_DOL_FORM_METHOD_GET;
        
        // set default action
        if (!isset($this->aFormAttrs['action']))
            $this->aFormAttrs['action'] = '';

        $this->_sCheckerHelper = isset($this->aParams['checker_helper']) ? $this->aParams['checker_helper'] : '';

        BxDolForm::genCsrfToken();

        $aCss = array(
            'forms_adv.css',
            'plugins/jquery/themes/|calendar-blue.css',
            'plugins/jquery/themes/|ui.datepicker.css'
        );
        $aCalendarLangs = array ('en' => 1, 'af' => 1, 'al' => 1, 'bg' => 1, 'br' => 1, 'ca' => 1, 'cs' => 1, 'da' => 1, 'de' => 1, 'du' => 1, 'el' => 1, 'es' => 1, 'fi' => 1, 'fr' => 1, 'he' => 1, 'hr' => 1, 'hu' => 1, 'it' => 1, 'ja' => 1, 'jp' => 1, 'ko' => 1, 'lt' => 1, 'lv' => 1, 'nl' => 1, 'no' => 1, 'pl' => 1, 'pt' => 1, 'ro' => 1, 'ru' => 1, 'si' => 1, 'sk' => 1, 'sp' => 1, 'sv' => 1, 'tr' => 1, 'zh' => 1);
        $aJs = array(
            'jquery.dynDateTime.js',
            'plugins/jquery/langs/|calendar-' . (isset($aCalendarLangs[$GLOBALS['sCurrentLanguage']]) ? $GLOBALS['sCurrentLanguage'] : 'en') . '.js',
            'ui.core.js',
            'ui.slider.js',
            'ui.datepicker.js'
        );
        
        if(isset($GLOBALS['oSysTemplate'])) {
            $GLOBALS['oSysTemplate']->addJs($aJs);
            $GLOBALS['oSysTemplate']->addCss($aCss);
        }
        if(isset($GLOBALS['oAdmTemplate'])) {
            $GLOBALS['oAdmTemplate']->addJs($aJs);
            $GLOBALS['oAdmTemplate']->addCss($aCss);
        }
    }

    function initChecker ($aValues = array ())  {
        if ($this->isSubmitted ()) {
            $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
            $oChecker->setFormMethod($this->aFormAttrs['method']);
            $oChecker->enableFormCsrfChecking(isset($this->aParams['csrf']['disable']) && $this->aParams['csrf']['disable'] === true ? false : true);
            $this->_isValid = $oChecker->check($this->aInputs);
        }
        else
        if ($aValues) {
            $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
            $oChecker->setFormMethod($this->aFormAttrs['method']);
            $oChecker->fillWithValues($this->aInputs, $aValues);
        }        
    }

    function insert ($aValsToAdd = array()) {
        $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
        $oChecker->setFormMethod($this->aFormAttrs['method']);
        $sSql = $oChecker->dbInsert($this->aParams['db'], $this->aInputs, $aValsToAdd);
        if (!$sSql) return false;
        if (db_res ($sSql))
            return db_last_id();
        return false;
    }

    function update ($val, $aValsToAdd = array()) {
        $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
        $oChecker->setFormMethod($this->aFormAttrs['method']);
        $sSql = $oChecker->dbUpdate($val, $this->aParams['db'], $this->aInputs, $aValsToAdd);        
        if (!$sSql) return false;
        return db_res ($sSql);
    }   

    function generateUri ()  {
        $f = &$this->aParams['db'];
        $sUri = $this->getCleanValue ($f['uri_title']);
        return uriGenerate($sUri, $f['table'], $f['uri']);
    }

    function getCleanValue ($sName)  {
        $oChecker = new BxDolFormChecker($this->_sCheckerHelper);
        $oChecker->setFormMethod($this->aFormAttrs['method']);
        $a = $this->aInputs[$sName];
        if ($a)
            return $oChecker->get ($a['name'], $a['db']['pass'], $a['db']['params'] ? $a['db']['params'] : array());
        else
           return $oChecker->get ($sName);
    }    

    function isSubmitted () {
        return BxDolForm::getSubmittedValue($this->aParams['db']['submit_name'], $this->aFormAttrs['method']) ? true : false;
    }

    function isValid () {
        return $this->_isValid;
    }    

    function isSubmittedAndValid () {
        return ($this->isSubmitted() && $this->isValid());
    }
    
    function getSubmittedValue($sKey, $sMethod) {
    	$aData = array();
    	if($sMethod == BX_DOL_FORM_METHOD_GET)
    		$aData = &$_GET;
		else if($sMethod == BX_DOL_FORM_METHOD_POST)
    		$aData = &$_POST;
    		
    	return isset($aData[$sKey]) ? $aData[$sKey] : false;
    }
    
	// Static Methods related to CSRF Tocken
	function genCsrfToken($bReturn = false) {
		if($GLOBALS['MySQL']->getParam('sys_security_form_token_enable') != 'on')
			return;

		$oSession = BxDolSession::getInstance();

		$iCsrfTokenLifetime = (int)$GLOBALS['MySQL']->getParam('sys_security_form_token_lifetime');
		if($oSession->getValue('csrf_token') === false || ($iCsrfTokenLifetime != 0 && time() - (int)$oSession->getValue('csrf_token_time') > $iCsrfTokenLifetime)) {
			$sToken = genRndPwd(20, true);
			$oSession->setValue('csrf_token', $sToken);
			$oSession->setValue('csrf_token_time', time());
		}
		else
			$sToken = $oSession->getValue('csrf_token');

		if($bReturn) 
			return $sToken;
	}
	function getCsrfToken() {
		$oSession = BxDolSession::getInstance();
		return $oSession->getValue('csrf_token');
	}
	function getCsrfTokenTime() {
		$oSession = BxDolSession::getInstance();
		return $oSession->getValue('csrf_token_time');
	}
}

class BxDolFormChecker {
    var $_oChecker;
    var $_sFormMethod;
    var $_bFormCsrfChecking;

    function BxDolFormChecker ($sHelper = '') {
    	$this->_sFormMethod = BX_DOL_FORM_METHOD_GET;
    	$this->_bFormCsrfChecking = true;

        $sCheckerName = !empty($sHelper) ? $sHelper : 'BxDolFormCheckerHelper';
        $this->_oChecker = new $sCheckerName();
    }

    function setFormMethod($sMethod) {
    	$this->_sFormMethod = $sMethod;
    }
    
    function enableFormCsrfChecking($bFormCsrfChecking) {
    	$this->_bFormCsrfChecking = $bFormCsrfChecking;
    }

    // check function
    function check (&$aInputs) {
        $oChecker = $this->_oChecker;
        $iErrors = 0;

        // check CSRF token if it's needed. 
        if($GLOBALS['MySQL']->getParam('sys_security_form_token_enable') == 'on' && $this->_bFormCsrfChecking === true && ($mixedCsrfTokenSys = BxDolForm::getCsrfToken()) !== false) {
			$mixedCsrfTokenUsr = BxDolForm::getSubmittedValue('csrf_token', $this->_sFormMethod);
			unset($aInputs['csrf_token']);        	

			if($mixedCsrfTokenUsr === false || $mixedCsrfTokenSys != $mixedCsrfTokenUsr)
				return false;
        }        

        foreach ($aInputs as $k => $a) {
            $a['name'] = str_replace('[]', '', $a['name']);
            $val = BxDolForm::getSubmittedValue($a['name'], $this->_sFormMethod); 
            if($val === false)
            	$val = isset($_FILES[$a['name']]) ? $_FILES[$a['name']] : '';

            if (!isset ($a['checker']))  {
                if ($a['type'] != 'checkbox')
                    $aInputs[$k]['value'] = $_FILES[$a['name']] ? '' : (get_magic_quotes_gpc() ? stripslashes_adv($val) : $val);
                continue;            
            }

            $sCheckFunction = array($oChecker, 'check'.ucfirst($a['checker']['func']));          
            
            if (is_callable($sCheckFunction))
                $bool = call_user_func_array ($sCheckFunction, $a['checker']['params'] ? array_merge(array($val), $a['checker']['params']) : array ($val));
            else
                $bool = true;
            
            if (is_string($bool)) {
                ++$iErrors;
                $aInputs[$k]['error'] = $bool;
            } elseif (!$bool) {
                ++$iErrors;
                $aInputs[$k]['error'] = $a['checker']['error'];
            }
            $aInputs[$k]['value'] = $_FILES[$a['name']] ? '' : (get_magic_quotes_gpc() ? stripslashes_adv($val) : $val);
        }

        // check for spam
        if (!$iErrors && ('on' == getParam('sys_uridnsbl_enable') || 'on' == getParam('sys_akismet_enable'))) {

            foreach ($aInputs as $k => $a) {

                if ($a['type'] != 'textarea')
                    continue;

                $a['name'] = str_replace('[]', '', $a['name']);
                $val = BxDolForm::getSubmittedValue($a['name'], $this->_sFormMethod); 
                if (!$val)
                    continue;
               
                if ($oChecker->checkNoSpam($val))
                    continue;

                ++$iErrors;
                $aInputs[$k]['error'] = sprintf(_t("_sys_spam_detected"), BX_DOL_URL_ROOT . 'contact.php');
 
            }
        }

        return $iErrors ? false : true;
    }

    // get clean value from GET/POST
    function get ($sName, $sPass = 'Xss', $aParams = array()) {
        if (!$sPass)
            $sPass = 'Xss';
        $this->_oChecker;
        $val = BxDolForm::getSubmittedValue($sName, $this->_sFormMethod);
        return call_user_func_array (array($this->_oChecker, 'pass'.ucfirst($sPass)), $aParams ? array_merge(array($val), $aParams) : array ($val));
    }

    // db functions
    function serializeDbValues (&$aInputs, &$aValsToAdd) {
        $oChecker = $this->_oChecker;
        $s = '';
        foreach ($aInputs as $k => $a) {
            if (!isset ($a['db'])) continue;            
            $valClean = $this->get ($a['name'], $a['db']['pass'], $a['db']['params'] ? $a['db']['params'] : array());
            $s .= "`{$a['name']}` = '$valClean',";
            $aInputs[$k]['db']['value'] = $valClean;
        }
        foreach ($aValsToAdd as $k => $val) {
            $s .= "`{$k}` = '$val',";
        }        
        return $s ? substr ($s, 0, -1) : '';
    }

    function dbInsert (&$aDb, &$aInputs, $aValsToAdd = array()) {
        if (!$aDb['table']) return '';
        $sFields = $this->serializeDbValues ($aInputs, $aValsToAdd);
        if (!$sFields) return '';
        return "INSERT INTO `{$aDb['table']}` SET $sFields";
    }

    function dbUpdate ($val, &$aDb, &$aInputs, $aValsToAdd = array()) {
        if (!$aDb['table'] || !$aDb['key']) return '';
        $sFields = $this->serializeDbValues ($aInputs, $aValsToAdd);
        if (!$sFields) return '';
        return "UPDATE `{$aDb['table']}` SET $sFields WHERE `{$aDb['key']}` = '$val'";
    }    

    function fillWithValues (&$aInputs, &$aValues) {
        foreach ($aInputs as $k => $a) {
            if (!isset($aValues[$k])) continue;
            $sMethod = 'display'.ucfirst($a['db']['pass']);
            if (method_exists($this->_oChecker, $sMethod))                
                $aInputs[$k]['value'] = call_user_func_array (array($this->_oChecker, $sMethod), $a['db']['params'] ? array_merge(array($aValues[$k]), $a['db']['params']) : array ($aValues[$k]));
            else
                $aInputs[$k]['value'] = $aValues[$k];

            if ($a['type'] == 'select_box')
                $aInputs[$k]['value'] = explode (';', $aInputs[$k]['value']);
        }
    }    
}

class BxDolFormCheckerHelper {

    // check functions - check values for limits or patterns

    function checkLength ($s, $iLenMin, $iLenMax) {
        if (is_array($s)) {
            foreach ($s as $k => $v) {
                $iLen = get_mb_len ($v);                
                if ($iLen < $iLenMin || $iLen > $iLenMax)
                    return false;
            }
            return true;
        } 
        $iLen = get_mb_len ($s);
        return $iLen >= $iLenMin && $iLen <= $iLenMax ? true : false;
    }
    function checkDate ($s) {
        return $this->checkPreg ($s, '#^\d+\-\d+\-\d+$#');
    }        
    function checkDateTime ($s) {
        // remove unnecessary opera's input value; 
        $s = str_replace('T', ' ', $s);
        $s = str_replace('Z', ':00', $s);

        return $this->checkPreg ($s, '#^\d+\-\d+\-\d+[\sT]{1}\d+:\d+:\d+$#');
    }            
    function checkPreg ($s, $r) {
        if (is_array($s)) {
            foreach ($s as $k => $v)
                if (!preg_match($r, $v))
                    return false;
            return true;
        }       
        return preg_match($r, $s) ? true : false;        
    }    
    function checkAvail ($s) {
        if (is_array($s)) {
            return !$this->_isEmptyArray($s);
        }        
        return $s ? true : false;
    }        
    function checkEmail($s) {
        return $this->checkPreg ($s, '/^[a-z0-9_\-]+(\.[_a-z0-9\-]+)*@([_a-z0-9\-]+\.)+([a-z]{2}|aero|arpa|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel)$/i');
    }
    function checkCaptcha($s) {
    	bx_import('BxDolSession');
		$oSession = BxDolSession::getInstance();
		$mixedValue = $oSession->getValue('strSec');

        return $mixedValue !== false && $mixedValue === md5($s);
    }
    function checkNoSpam($val) {
        return !bx_is_spam($val);
    }

    // pass functions, prepare values to insert to database
    function passInt ($s) {
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = (int)trim($v);
            }
            return $a;
        }         
        return (int)$s;
    }
    function passFloat ($s) {
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = (float)$v;
            }
            return $a;
        }
        return (float)$s;
    }
    function passDate ($s) {    
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = $this->_passDate ($v);
            }
            return $a;
        }        
        return $this->_passDate ($s);
    }
    function _passDate ($s) {
        list($iYear, $iMonth, $iDay) = explode( '-', $s);
        $iDay   = (int)$iDay;
        $iMonth = (int)$iMonth;
        $iYear  = (int)$iYear;
        $iRet = mktime (0, 0, 0, $iMonth, $iDay, $iYear);
        return $iRet > 0 ? $iRet : 0;
    }
    function passDateTime ($s) {    
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = $this->_passDateTime ($v);
            }
            return $a;
        }
        return $this->_passDateTime ($s);
    }
    function _passDateTime ($s) {
        if (preg_match('#(\d+)\-(\d+)\-(\d+)[\sT]{1}(\d+):(\d+):(\d+)#', $s, $m)) {
            $iDay   = $m[3];
            $iMonth = $m[2];
            $iYear  = $m[1];
            $iH = $m[4];
            $iM = $m[5];
            $iS = $m[6];
            $iRet = mktime ($iH, $iM, $iS, $iMonth, $iDay, $iYear);
            return $iRet > 0 ? $iRet : 0;
        }        
        return $this->passDate ($s);
    }        
    function passXss ($s) {
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = process_db_input ($v, BX_TAGS_STRIP);
            }
            return $a;
        }
        return process_db_input ($s, BX_TAGS_STRIP);
    }
    function passXssHtml ($s) {
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = process_db_input ($v, BX_TAGS_VALIDATE); 
            }
            return $a;
        }        
        return process_db_input ($s, BX_TAGS_VALIDATE); 
    }    

    function passAll ($s) {
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = process_db_input ($v, BX_TAGS_NO_ACTION); 
            }
            return $a;
        }        
        return process_db_input ($s, BX_TAGS_NO_ACTION); 
    }    

    function passPreg ($s, $r) {
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = $this->_passPreg ($v, $r);
            }
            return $a;
        }
        return $this->_passPreg($s, $r);
    }
    function _passPreg ($s, $r) {
        if (preg_match ($r, $s, $m)) {
            return $m[1]; 
        }
        return '';
    }    
    function passTags ($s) {
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = $this->_passTags ($v);
            }
            return $a;
        }        
        return $this->_passTags($s);
    }    
    function _passTags ($s) {
		$sTags = $this->passXss ($s);
		$aTags = explodeTags($sTags);
		return implode(",", $aTags);
    }        
    function passCategories ($aa) {
        if (is_array($aa)) {
            $a = array ();
            foreach ($aa as $k => $v)                
                if ($v)
                    $a[$k] = $this->passXss ($v);
        } else {
            $a = $this->passXss ($aa);
        }
        return is_array($a) ? implode(CATEGORIES_DIVIDER, $a) : $a;

    }
    function passBoolean ($s) {
        if (is_array($s)) {
            $a = array ();
            foreach ($s as $k => $v) {
                $a[$k] = $v == 'on' ? true : false;
            }
            return $a;
        }        
        return $s == 'on' ? true : false;
    }    

    // display functions, prepare values to output to the screen 
    
    function displayDate ($i) {
        return date("Y-m-d", $i);
    }
    function displayDateTime ($i) {
        return date("Y-m-d H:i:s", $i);
    }        

    // for internal usage only

    function _isEmptyArray ($a) {
        if (!is_array($a))
            return true;
        if (empty($a))
            return true;
        foreach ($a as $k => $v)
            if ($v)
                return false;
        return true;
    }
}

?>
