* @license http://xoopscube.sourceforge.net/license/bsd_licenses.txt Modified BSD license * */ if (!defined('XCUBE_CORE_PATH')) define('XCUBE_CORE_PATH', dirname(__FILE__)); require_once XCUBE_CORE_PATH . '/XCube_Root.class.php'; require_once XCUBE_CORE_PATH . '/XCube_Property.class.php'; require_once XCUBE_CORE_PATH . '/XCube_Validator.class.php'; require_once XCUBE_CORE_PATH . '/XCube_FormFile.class.php'; /** * @public * @brief [Abstract] Fetches input values, valudates fetched values and passes them to some object. * * This class fetches the input value from the request value through the * current context object and validate those values. It separates fetching & * validating from your main logic. Such classes is important in web * program. * * Plus, this action form has features of one time token. It seems one kinds of * validations. The token is registered in templates. * * This is suggestion of a simple action form. We do not force a module * developer to use this. You can learn more full-scale action forms from JAVA * and .NET and other PHP. And, you must use auto-generating tool when you need * to ActionForm that is sub-class of this class. * * XCube_ActionForm contains the one-time token feature for CSRF. But, if the * current HTTP request is from the web service, the token isn't needed. * Therefore, this class decides whether to use the token with the information * of the context. * * @remarks * This class is diable for XCube_Service, because the class uses SESSION * directly. XCube_ActionForm will be changed in the near feature. Developers * need to pay attention to spec change. * * @todo The difference of array and no-array is too big. * @todo Form object should have getValue(), isNull(), toString(). * @todo This form is impossible to be used in XCube_Service SOAP mode. */ class XCube_ActionForm { /** * @protected * @brief [READ ONLY] XCube_HttpContext * * The context object. Enables to access the HTTP-request information. * Basically, this member property is read only. Initialized in the constructor. */ var $mContext = null; /** * @protected * @brief [READ ONLY] XCube_Principal * * The object which has a interface of XCube_Principal. Enables to check * permissions of the current HTTP-request through principal object. * Basically, this member property is read only. Initialized in constructor. */ var $mUser = null; /** * @protected * @brief XCube_FormProperty[] */ var $mFormProperties = array(); /** * @protected * @brief XCube_FieldProperty[] */ var $mFieldProperties = array(); /** * @protected * @brief bool * @attention * This is temporary until we will decide the method of managing error. */ var $mErrorFlag = false; /** * @private * @brief string[] */ var $mErrorMessages = array(); /** * @protected * @brief string * * Token string as one time token. */ var $_mToken = null; /** * @public * @brief Constructor. */ function XCube_ActionForm() { $root =& XCube_Root::getSingleton(); $this->mContext =& $root->getContext(); $this->mUser =& $this->mContext->getUser(); } /** * @public * @brief [Abstract] Set up form properties and field properties. */ function prepare() { } /** * @public * @brief Gets the token name of this actionform's token. * @return string * * Return token name. If the sub-class doesn't override this member * function, features about one time tokens aren't used. */ function getTokenName() { return null; } /** * @public * @brief Gets the token value of this actionform's token. * @return string * * Generate token value, register it to sessions, return it. This member * function should be called in templates. The subclass can override this * to change the logic for generating token value. */ function getToken() { if ($this->_mToken == null) { srand(microtime() * 100000); $root=&XCube_Root::getSingleton(); $salt = $root->getSiteConfig('Cube', 'Salt'); $this->_mToken = md5($salt . uniqid(rand(), true)); $_SESSION['XCUBE_TOKEN'][$this->getTokenName()] = $this->_mToken; } return $this->_mToken; } /** * @public * @brief Gets message about the failed validation of token. * @return string */ function getTokenErrorMessage() { return _TOKEN_ERROR; //< FIXME } /** * @public * @brief Set raw value as the value of the form property. * * This method is overloaded function. * * \par XCube_ActionForm::set($name, $value) * Set $value to $name property. * \code * $this->set('name', 'Bob'); // Set 'Bob' to 'name'. * \endcode * * \par XCube_ActionForm::set($name, $index, $value) * Set $value to $name array property[$index]. * \code * $this->set('names', 0, 'Bob'); // Set 'Bob' to 'names[0]'. * \endcode */ function set() { if (isset($this->mFormProperties[func_get_arg(0)])) { if (func_num_args() == 2) { $value = func_get_arg(1); $this->mFormProperties[func_get_arg(0)]->setValue($value); } elseif (func_num_args() == 3) { $index = func_get_arg(1); $value = func_get_arg(2); $this->mFormProperties[func_get_arg(0)]->setValue($index, $value); } } } /** * @deprecated */ function setVar() { if (isset($this->mFormProperties[func_get_arg(0)])) { if (func_num_args() == 2) { $this->mFormProperties[func_get_arg(0)]->setValue(func_get_arg(1)); } elseif (func_num_args() == 3) { $this->mFormProperties[func_get_arg(0)]->setValue(func_get_arg(1), func_get_arg(2)); } } } /** * @public * @brief Gets raw value. * @param $key string Name of form property. * @param $index string Subscript for array. * @return mixed * * @attention * This method returns raw values. Therefore if the value is used in templates, * it must needs escaping. */ function get($key, $index=null) { return isset($this->mFormProperties[$key]) ? $this->mFormProperties[$key]->getValue($index) : null; } /** * @deprecated */ function getVar($key,$index=null) { return $this->get($key, $index); } /** * @public * @brief Gets form properties of this member property. * @return XCube_AbstractProperty[] * @attention * This method may not be must. So it will be renamed in the near future. * @todo Check whether this method is must. */ function &getFormProperties() { return $this->mFormProperties; } /** * @public * @brief Fetches values through the request object. * @return void * @see getFromRequest * * Fetch the input value, set it and form properties. Those values can be * got, through get() method. the sub-class can define own member function * to fetch. Define member functions whose name is "fetch" + "form name". * For example, to fetch "message" define "fetchMessage()" function. Those * function of the sub-class set value to this action form. * \code * function fetchModifytime() * { * $this->set('modifytime', time()); * } * \endcode */ function fetch() { foreach (array_keys($this->mFormProperties) as $name) { if ($this->mFormProperties[$name]->hasFetchControl()) { $this->mFormProperties[$name]->fetch($this); } else { $value = $this->mContext->mRequest->getRequest($name); $this->mFormProperties[$name]->set($value); } $methodName = "fetch" . ucfirst($name); if (method_exists($this, $methodName)) { // call_user_func(array($this,$methodName)); $this->$methodName(); } } } /** * @protected * @brief Validates the token. * @return void * * Validates the token. This method is deprecated, because XCube_Action will * be changed for multi-layer. So this method is called by only this class. * * @todo This method has to be remove, because it is using session directly. */ function _validateToken() { // // check onetime & transaction token // if ($this->getTokenName() != null) { $key = strtr($this->getTokenName(), '.', '_'); $token = isset($_REQUEST[$key]) ? $_REQUEST[$key] : null; if (get_magic_quotes_gpc()) { $token = stripslashes($token); } $flag = true; if (!isset($_SESSION['XCUBE_TOKEN'][$this->getTokenName()])) { $flag = false; } elseif ($_SESSION['XCUBE_TOKEN'][$this->getTokenName()] != $token) { unset($_SESSION['XCUBE_TOKEN'][$this->getTokenName()]); $flag = false; } if (!$flag) { $message = $this->getTokenErrorMessage(); if ($message == null) { $this->mErrorFlag = true; } else { $this->addErrorMessage($message); } } // // clear token // unset($_SESSION['XCUBE_TOKEN'][$this->getTokenName()]); } } /** * @public * @brief Validates fetched values. * @return void * * Execute validation, so if a input value is wrong, error messages are * added to error message buffer. The procedure of validation is the * following: * * \li 1. If this object have token name, validate one time tokens. * \li 2. Call the validation member function of all field properties. * \li 3. Call the member function that is defined in the sub-class. * * For a basis, validations are done by functions of each field properties. * But, the sub-class can define own validation logic. Define member * functions whose name is "validate" + "form name". For example, to * validate "message" define "validateMessage()" function. */ function validate() { $this->_validateToken(); foreach (array_keys($this->mFormProperties) as $name) { if (isset($this->mFieldProperties[$name])) { if ($this->mFormProperties[$name]->isArray()) { foreach (array_keys($this->mFormProperties[$name]->mProperties) as $_name) { $this->mFieldProperties[$name]->validate($this->mFormProperties[$name]->mProperties[$_name]); } } else { $this->mFieldProperties[$name]->validate($this->mFormProperties[$name]); } } } // // If this class has original validation methods, call it. // foreach (array_keys($this->mFormProperties) as $name) { $methodName = "validate" . ucfirst($name); if (method_exists($this, $methodName)) { // call_user_func(array($this,$methodName)); $this->$methodName(); } } } /** * @public * @brief Gets a value indicating whether this action form keeps error messages or error flag. * @return bool - If the action form is error status, returns true. */ function hasError() { return (count($this->mErrorMessages) > 0 || $this->mErrorFlag); } /** * @protected * @brief Adds an message to error message buffer of the form. * @param $message string */ function addErrorMessage($message) { $this->mErrorMessages[] = $message; } /** * @public * @brief Gets error messages. * @return string[] */ function getErrorMessages() { return $this->mErrorMessages; } /** * @public * @brief [Abstract] Initializes properties' values from an object. * @param $obj mixed * @return void * * Set initial values to this action form from a object. This member * function mediates between the logic and the validation. For example, * developers can use this method to load values from XoopsSimpleObject. * * This member function is abstract. But, the sub-class of this class * doesn't have to implement this. */ function load(&$obj) { } /** * @public * @brief [Abstract] Updates an object with properties's values. * @param $obj mixed * @return void * * Set input values to a object from this action form. This member function * mediates between the logic and the result of validations. For example, * developers can use this method to set values to XoopsSimpleObject. * * This member function is abstract. But, the sub-class of this class * doesn't have to implement this. */ function update(&$obj) { } } /** * @public * @brief [Abstract] Used for validating member property values of XCube_ActionForm. */ class XCube_FieldProperty { /** * @protected * @brief XCube_ActionForm - Parent form contains this field property. */ var $mForm; /** * @protected * @brief XCube_Validator[] - std::map */ var $mDepends; /** * @protected * @brief Complex Array * @section section1 Complex Array * $mMessages[$name]['message'] - string \n * $mMessages[$name]['args'][] - string * * \code * // Reference Define * typedef std::map ArgumentMap; * struct MessageStrage * { * string Message; * ArgumentMap args; * }; * * typedef std::map MessageList; * MessageList mMessages; * \endcode */ var $mMessages; /** * @protected * @brief Hash-Map Array - std::map */ var $mVariables; /** * @public * @brief Constructor. * @param $form XCube_ActionForm - Parent form. * @remarks * Only sub-classes of XCube_ActionForm calles this constructor. */ function XCube_FieldProperty(&$form) { $this->mForm =& $form; } /** * @public * @brief Initializes the validator list of this field property with the depend rule name list. * @param $dependsArr string[] * @return void */ function setDependsByArray($dependsArr) { foreach ($dependsArr as $dependName) { $instance =& XCube_DependClassFactory::factoryClass($dependName); if ($instance !== null) { $this->mDepends[$dependName] =& $instance; } unset($instance); } } /** * @public * @brief Adds an error message which will be used in the case which '$name rule' validation is failed. * @param $name string - Depend rule name. * @param $message string - Error message. * @return void * * It's possible to add 3 or greater parameters. * These additional parameters are used by XCube_Utils::formatString(). * \code * $field->addMessage('required', "{0:ucFirst} is requred.", "name"); * \endcode * This feature is helpful for automatic ActionForm generators. */ function addMessage($name, $message) { if (func_num_args() >= 2) { $args = func_get_args(); $this->mMessages[$args[0]]['message'] = $args[1]; for ($i = 0; isset($args[$i + 2]); $i++) { $this->mMessages[$args[0]]['args'][$i] = $args[$i + 2]; } } } /** * @public * @brief Gets the error message rendered by XCube_Utils::formaString(). * @param $name string - Depend rule name * @return string * * Gets the error message registered at addMessage(). If the message setting has some * arguments, messages are rendered by XCube_Utils::formatString(). * \code * $field->addMessage('required', "{0:ucFirst} is requred.", "name"); * * // Gets "Name is required." * $field->renderMessage('required'); * \endcode * This feature is helpful for automatic ActionForm generators. */ function renderMessage($name) { if (!isset($this->mMessages[$name])) return null; $message = $this->mMessages[$name]['message']; if (isset($this->mMessages[$name]['args'])) { // Use an unity method. $message = XCube_Utils::formatString($message, $this->mMessages[$name]['args']); } return $message; } /** * @public * @brief Adds a virtual variable used by validators. * @param $name string - A name of the variable. * @param $value mixed - A value of the variable. * * Virtual varialbes are used for validating by validators. For example, * XCube_MinlengthValidator needs a value indicationg a minimum length. * \code * $field->addVar('minlength', 2); * \endcode */ function addVar($name, $value) { $this->mVariables[$name] = $value; } /** * @public * @brief Validates form-property with validators which this field property holds. * @attention * Only XCube_ActionForm and its sub-classes should call this method. * @todo This class already has form property instance. */ function validate(&$form) { if (is_array($this->mDepends) && count($this->mDepends) > 0) { foreach ($this->mDepends as $name => $depend) { if (!$depend->isValid($form, $this->mVariables)) { // Error // NOTICE: This is temporary until we will decide the method of managing error. $this->mForm->mErrorFlag = true; // TEST!! $this->mForm->addErrorMessage($this->renderMessage($name)); } else { // OK } } } } } /** * @internal * @public * @brief Factory for generating validator objects. * @attention * Only 'XCube_ActionForm' class should use this class. */ class XCube_DependClassFactory { /** * @public * @internal * @brief [static] Gets a XCube_Validator object by the rule name (depend name). * @param $dependName string * @return XCube_Validator * @attention * Only 'XCube_ActionForm' class should use this class. */ function &factoryClass($dependName) { static $_cache; if (!is_array($_cache)) { $_cache = array(); } if (!isset($_cache[$dependName])) { // or switch? $class_name = "XCube_" . ucfirst($dependName) . "Validator"; if (XC_CLASS_EXISTS($class_name)) { $_cache[$dependName] =& new $class_name(); } else { // FIXME:: use delegate? die ("This is an error message of Alpha or Beta series. ${dependName} Validator is not found."); } } return $_cache[$dependName]; } } ?>