1: <?php
2: /**
3: *
4: * @package XCube
5: * @version $Id: XCube_ActionForm.class.php,v 1.4 2008/10/12 04:30:27 minahito Exp $
6: * @copyright Copyright 2005-2007 XOOPS Cube Project <https://github.com/xoopscube/legacy>
7: * @license https://github.com/xoopscube/legacy/blob/master/docs/bsd_licenses.txt Modified BSD license
8: *
9: */
10:
11: if (!defined('XCUBE_CORE_PATH')) define('XCUBE_CORE_PATH', dirname(__FILE__));
12:
13: require_once XCUBE_CORE_PATH . '/XCube_Root.class.php';
14:
15: require_once XCUBE_CORE_PATH . '/XCube_Property.class.php';
16: require_once XCUBE_CORE_PATH . '/XCube_Validator.class.php';
17: require_once XCUBE_CORE_PATH . '/XCube_FormFile.class.php';
18:
19: /**
20: * @public
21: * @brief [Abstract] Fetches input values, valudates fetched values and passes them to some object.
22: *
23: * This class fetches the input value from the request value through the
24: * current context object and validate those values. It separates fetching &
25: * validating from your main logic. Such classes is important in web
26: * program.
27: *
28: * Plus, this action form has features of one time token. It seems one kinds of
29: * validations. The token is registered in templates.
30: *
31: * This is suggestion of a simple action form. We do not force a module
32: * developer to use this. You can learn more full-scale action forms from JAVA
33: * and .NET and other PHP. And, you must use auto-generating tool when you need
34: * to ActionForm that is sub-class of this class.
35: *
36: * XCube_ActionForm contains the one-time token feature for CSRF. But, if the
37: * current HTTP request is from the web service, the token isn't needed.
38: * Therefore, this class decides whether to use the token with the information
39: * of the context.
40: *
41: * @remarks
42: * This class is diable for XCube_Service, because the class uses SESSION
43: * directly. XCube_ActionForm will be changed in the near feature. Developers
44: * need to pay attention to spec change.
45: *
46: * @todo The difference of array and no-array is too big.
47: * @todo Form object should have getValue(), isNull(), toString().
48: * @todo This form is impossible to be used in XCube_Service SOAP mode.
49: */
50: class XCube_ActionForm
51: {
52: /**
53: * @protected
54: * @brief [READ ONLY] XCube_HttpContext
55: *
56: * The context object. Enables to access the HTTP-request information.
57: * Basically, this member property is read only. Initialized in the constructor.
58: */
59: var $mContext = null;
60:
61: /**
62: * @protected
63: * @brief [READ ONLY] XCube_Principal
64: *
65: * The object which has a interface of XCube_Principal. Enables to check
66: * permissions of the current HTTP-request through principal object.
67: * Basically, this member property is read only. Initialized in constructor.
68: */
69: var $mUser = null;
70:
71: /**
72: * @protected
73: * @brief XCube_FormProperty[]
74: */
75: var $mFormProperties = array();
76:
77: /**
78: * @protected
79: * @brief XCube_FieldProperty[]
80: */
81: var $mFieldProperties = array();
82:
83: /**
84: * @protected
85: * @brief bool
86: * @attention
87: * This is temporary until we will decide the method of managing error.
88: */
89: var $mErrorFlag = false;
90:
91: /**
92: * @private
93: * @brief string[]
94: */
95: var $mErrorMessages = array();
96:
97: /**
98: * @protected
99: * @brief string
100: *
101: * Token string as one time token.
102: */
103: var $_mToken = null;
104:
105: /**
106: * @public
107: * @brief Constructor.
108: */
109: function XCube_ActionForm()
110: {
111: $root =& XCube_Root::getSingleton();
112: $this->mContext =& $root->getContext();
113: $this->mUser =& $this->mContext->getUser();
114: }
115:
116: /**
117: * @public
118: * @brief [Abstract] Set up form properties and field properties.
119: */
120: function prepare()
121: {
122: }
123:
124: /**
125: * @public
126: * @brief Gets the token name of this actionform's token.
127: * @return string
128: *
129: * Return token name. If the sub-class doesn't override this member
130: * function, features about one time tokens aren't used.
131: */
132: function getTokenName()
133: {
134: return null;
135: }
136:
137: /**
138: * @public
139: * @brief Gets the token value of this actionform's token.
140: * @return string
141: *
142: * Generate token value, register it to sessions, return it. This member
143: * function should be called in templates. The subclass can override this
144: * to change the logic for generating token value.
145: */
146: function getToken()
147: {
148: if ($this->_mToken == null) {
149: srand(microtime() * 100000);
150: $root=&XCube_Root::getSingleton();
151: $salt = $root->getSiteConfig('Cube', 'Salt');
152: $this->_mToken = md5($salt . uniqid(rand(), true));
153:
154: $_SESSION['XCUBE_TOKEN'][$this->getTokenName()] = $this->_mToken;
155: }
156:
157: return $this->_mToken;
158: }
159:
160: /**
161: * @public
162: * @brief Gets message about the failed validation of token.
163: * @return string
164: */
165: function getTokenErrorMessage()
166: {
167: return _TOKEN_ERROR; //< FIXME
168: }
169:
170: /**
171: * @public
172: * @brief Set raw value as the value of the form property.
173: *
174: * This method is overloaded function.
175: *
176: * \par XCube_ActionForm::set($name, $value)
177: * Set $value to $name property.
178: * \code
179: * $this->set('name', 'Bob'); // Set 'Bob' to 'name'.
180: * \endcode
181: *
182: * \par XCube_ActionForm::set($name, $index, $value)
183: * Set $value to $name array property[$index].
184: * \code
185: * $this->set('names', 0, 'Bob'); // Set 'Bob' to 'names[0]'.
186: * \endcode
187: */
188: function set()
189: {
190: if (isset($this->mFormProperties[func_get_arg(0)])) {
191: if (func_num_args() == 2) {
192: $value = func_get_arg(1);
193: $this->mFormProperties[func_get_arg(0)]->setValue($value);
194: }
195: elseif (func_num_args() == 3) {
196: $index = func_get_arg(1);
197: $value = func_get_arg(2);
198: $this->mFormProperties[func_get_arg(0)]->setValue($index, $value);
199: }
200: }
201: }
202:
203: /**
204: * @deprecated
205: */
206: function setVar()
207: {
208: if (isset($this->mFormProperties[func_get_arg(0)])) {
209: if (func_num_args() == 2) {
210: $this->mFormProperties[func_get_arg(0)]->setValue(func_get_arg(1));
211: }
212: elseif (func_num_args() == 3) {
213: $this->mFormProperties[func_get_arg(0)]->setValue(func_get_arg(1), func_get_arg(2));
214: }
215: }
216: }
217:
218: /**
219: * @public
220: * @brief Gets raw value.
221: * @param $key string Name of form property.
222: * @param $index string Subscript for array.
223: * @return mixed
224: *
225: * @attention
226: * This method returns raw values. Therefore if the value is used in templates,
227: * it must needs escaping.
228: */
229: function get($key, $index=null)
230: {
231: return isset($this->mFormProperties[$key]) ? $this->mFormProperties[$key]->getValue($index) : null;
232: }
233:
234: /**
235: * @deprecated
236: */
237: function getVar($key,$index=null)
238: {
239: return $this->get($key, $index);
240: }
241:
242: /**
243: * @public
244: * @brief Gets form properties of this member property.
245: * @return XCube_AbstractProperty[]
246: * @attention
247: * This method may not be must. So it will be renamed in the near future.
248: * @todo Check whether this method is must.
249: */
250: function &getFormProperties()
251: {
252: return $this->mFormProperties;
253: }
254:
255: /**
256: * @public
257: * @brief Fetches values through the request object.
258: * @return void
259: * @see getFromRequest
260: *
261: * Fetch the input value, set it and form properties. Those values can be
262: * got, through get() method. the sub-class can define own member function
263: * to fetch. Define member functions whose name is "fetch" + "form name".
264: * For example, to fetch "message" define "fetchMessage()" function. Those
265: * function of the sub-class set value to this action form.
266: * \code
267: * function fetchModifytime()
268: * {
269: * $this->set('modifytime', time());
270: * }
271: * \endcode
272: */
273: function fetch()
274: {
275: foreach (array_keys($this->mFormProperties) as $name) {
276: if ($this->mFormProperties[$name]->hasFetchControl()) {
277: $this->mFormProperties[$name]->fetch($this);
278: }
279: else {
280: $value = $this->mContext->mRequest->getRequest($name);
281: $this->mFormProperties[$name]->set($value);
282: }
283: $methodName = "fetch" . ucfirst($name);
284: if (method_exists($this, $methodName)) {
285: // call_user_func(array($this,$methodName));
286: $this->$methodName();
287: }
288: }
289: }
290:
291: /**
292: * @protected
293: * @brief Validates the token.
294: * @return void
295: *
296: * Validates the token. This method is deprecated, because XCube_Action will
297: * be changed for multi-layer. So this method is called by only this class.
298: *
299: * @todo This method has to be remove, because it is using session directly.
300: */
301: function _validateToken()
302: {
303: //
304: // check onetime & transaction token
305: //
306: if ($this->getTokenName() != null) {
307: $key = strtr($this->getTokenName(), '.', '_');
308: $token = isset($_REQUEST[$key]) ? $_REQUEST[$key] : null;
309:
310: if (get_magic_quotes_gpc()) {
311: $token = stripslashes($token);
312: }
313:
314: $flag = true;
315:
316: if (!isset($_SESSION['XCUBE_TOKEN'][$this->getTokenName()])) {
317: $flag = false;
318: }
319: elseif ($_SESSION['XCUBE_TOKEN'][$this->getTokenName()] != $token) {
320: unset($_SESSION['XCUBE_TOKEN'][$this->getTokenName()]);
321: $flag = false;
322: }
323:
324: if (!$flag) {
325: $message = $this->getTokenErrorMessage();
326: if ($message == null) {
327: $this->mErrorFlag = true;
328: }
329: else {
330: $this->addErrorMessage($message);
331: }
332: }
333:
334: //
335: // clear token
336: //
337: unset($_SESSION['XCUBE_TOKEN'][$this->getTokenName()]);
338: }
339: }
340:
341:
342: /**
343: * @public
344: * @brief Validates fetched values.
345: * @return void
346: *
347: * Execute validation, so if a input value is wrong, error messages are
348: * added to error message buffer. The procedure of validation is the
349: * following:
350: *
351: * \li 1. If this object have token name, validate one time tokens.
352: * \li 2. Call the validation member function of all field properties.
353: * \li 3. Call the member function that is defined in the sub-class.
354: *
355: * For a basis, validations are done by functions of each field properties.
356: * But, the sub-class can define own validation logic. Define member
357: * functions whose name is "validate" + "form name". For example, to
358: * validate "message" define "validateMessage()" function.
359: */
360: function validate()
361: {
362: $this->_validateToken();
363:
364: foreach (array_keys($this->mFormProperties) as $name) {
365: if (isset($this->mFieldProperties[$name])) {
366: if ($this->mFormProperties[$name]->isArray()) {
367: foreach (array_keys($this->mFormProperties[$name]->mProperties) as $_name) {
368: $this->mFieldProperties[$name]->validate($this->mFormProperties[$name]->mProperties[$_name]);
369: }
370: }
371: else {
372: $this->mFieldProperties[$name]->validate($this->mFormProperties[$name]);
373: }
374: }
375: }
376:
377: //
378: // If this class has original validation methods, call it.
379: //
380: foreach (array_keys($this->mFormProperties) as $name) {
381: $methodName = "validate" . ucfirst($name);
382: if (method_exists($this, $methodName)) {
383: // call_user_func(array($this,$methodName));
384: $this->$methodName();
385: }
386: }
387: }
388:
389: /**
390: * @public
391: * @brief Gets a value indicating whether this action form keeps error messages or error flag.
392: * @return bool - If the action form is error status, returns true.
393: */
394: function hasError()
395: {
396: return (count($this->mErrorMessages) > 0 || $this->mErrorFlag);
397: }
398:
399: /**
400: * @protected
401: * @brief Adds an message to error message buffer of the form.
402: * @param $message string
403: */
404: function addErrorMessage($message)
405: {
406: $this->mErrorMessages[] = $message;
407: }
408:
409: /**
410: * @public
411: * @brief Gets error messages.
412: * @return string[]
413: */
414: function getErrorMessages()
415: {
416: return $this->mErrorMessages;
417: }
418:
419: /**
420: * @public
421: * @brief [Abstract] Initializes properties' values from an object.
422: * @param $obj mixed
423: * @return void
424: *
425: * Set initial values to this action form from a object. This member
426: * function mediates between the logic and the validation. For example,
427: * developers can use this method to load values from XoopsSimpleObject.
428: *
429: * This member function is abstract. But, the sub-class of this class
430: * doesn't have to implement this.
431: */
432: function load(&$obj)
433: {
434: }
435:
436: /**
437: * @public
438: * @brief [Abstract] Updates an object with properties's values.
439: * @param $obj mixed
440: * @return void
441: *
442: * Set input values to a object from this action form. This member function
443: * mediates between the logic and the result of validations. For example,
444: * developers can use this method to set values to XoopsSimpleObject.
445: *
446: * This member function is abstract. But, the sub-class of this class
447: * doesn't have to implement this.
448: */
449: function update(&$obj)
450: {
451: }
452: }
453:
454: /**
455: * @public
456: * @brief [Abstract] Used for validating member property values of XCube_ActionForm.
457: */
458: class XCube_FieldProperty
459: {
460: /**
461: * @protected
462: * @brief XCube_ActionForm - Parent form contains this field property.
463: */
464: var $mForm;
465:
466: /**
467: * @protected
468: * @brief XCube_Validator[] - std::map<string, XCube_Validator*>
469: */
470: var $mDepends;
471:
472: /**
473: * @protected
474: * @brief Complex Array
475: * @section section1 Complex Array
476: * $mMessages[$name]['message'] - string \n
477: * $mMessages[$name]['args'][] - string
478: *
479: * \code
480: * // Reference Define
481: * typedef std::map<int, string> ArgumentMap;
482: * struct MessageStrage
483: * {
484: * string Message;
485: * ArgumentMap args;
486: * };
487: *
488: * typedef std::map<string, MessageStrage> MessageList;
489: * MessageList mMessages;
490: * \endcode
491: */
492: var $mMessages;
493:
494: /**
495: * @protected
496: * @brief Hash-Map Array - std::map<string, mixed>
497: */
498: var $mVariables;
499:
500: /**
501: * @public
502: * @brief Constructor.
503: * @param $form XCube_ActionForm - Parent form.
504: * @remarks
505: * Only sub-classes of XCube_ActionForm calles this constructor.
506: */
507: function XCube_FieldProperty(&$form)
508: {
509: $this->mForm =& $form;
510: }
511:
512: /**
513: * @public
514: * @brief Initializes the validator list of this field property with the depend rule name list.
515: * @param $dependsArr string[]
516: * @return void
517: */
518: function setDependsByArray($dependsArr)
519: {
520: foreach ($dependsArr as $dependName) {
521: $instance =& XCube_DependClassFactory::factoryClass($dependName);
522: if ($instance !== null) {
523: $this->mDepends[$dependName] =& $instance;
524: }
525:
526: unset($instance);
527: }
528: }
529:
530: /**
531: * @public
532: * @brief Adds an error message which will be used in the case which '$name rule' validation is failed.
533: * @param $name string - Depend rule name.
534: * @param $message string - Error message.
535: * @return void
536: *
537: * It's possible to add 3 or greater parameters.
538: * These additional parameters are used by XCube_Utils::formatString().
539: * \code
540: * $field->addMessage('required', "{0:ucFirst} is requred.", "name");
541: * \endcode
542: * This feature is helpful for automatic ActionForm generators.
543: */
544: function addMessage($name, $message)
545: {
546: if (func_num_args() >= 2) {
547: $args = func_get_args();
548: $this->mMessages[$args[0]]['message'] = $args[1];
549: for ($i = 0; isset($args[$i + 2]); $i++) {
550: $this->mMessages[$args[0]]['args'][$i] = $args[$i + 2];
551: }
552: }
553: }
554:
555: /**
556: * @public
557: * @brief Gets the error message rendered by XCube_Utils::formaString().
558: * @param $name string - Depend rule name
559: * @return string
560: *
561: * Gets the error message registered at addMessage(). If the message setting has some
562: * arguments, messages are rendered by XCube_Utils::formatString().
563: * \code
564: * $field->addMessage('required', "{0:ucFirst} is requred.", "name");
565: *
566: * // Gets "Name is required."
567: * $field->renderMessage('required');
568: * \endcode
569: * This feature is helpful for automatic ActionForm generators.
570: */
571: function renderMessage($name)
572: {
573: if (!isset($this->mMessages[$name]))
574: return null;
575:
576: $message = $this->mMessages[$name]['message'];
577:
578: if (isset($this->mMessages[$name]['args'])) {
579: // Use an unity method.
580: $message = XCube_Utils::formatString($message, $this->mMessages[$name]['args']);
581: }
582:
583: return $message;
584: }
585:
586: /**
587: * @public
588: * @brief Adds a virtual variable used by validators.
589: * @param $name string - A name of the variable.
590: * @param $value mixed - A value of the variable.
591: *
592: * Virtual varialbes are used for validating by validators. For example,
593: * XCube_MinlengthValidator needs a value indicationg a minimum length.
594: * \code
595: * $field->addVar('minlength', 2);
596: * \endcode
597: */
598: function addVar($name, $value)
599: {
600: $this->mVariables[$name] = $value;
601: }
602:
603: /**
604: * @public
605: * @brief Validates form-property with validators which this field property holds.
606: * @attention
607: * Only XCube_ActionForm and its sub-classes should call this method.
608: * @todo This class already has form property instance.
609: */
610: function validate(&$form)
611: {
612: if (is_array($this->mDepends) && count($this->mDepends) > 0) {
613: foreach ($this->mDepends as $name => $depend) {
614: if (!$depend->isValid($form, $this->mVariables)) {
615: // Error
616: // NOTICE: This is temporary until we will decide the method of managing error.
617: $this->mForm->mErrorFlag = true;
618:
619: // TEST!!
620: $this->mForm->addErrorMessage($this->renderMessage($name));
621: }
622: else {
623: // OK
624: }
625: }
626: }
627: }
628: }
629:
630: /**
631: * @internal
632: * @public
633: * @brief Factory for generating validator objects.
634: * @attention
635: * Only 'XCube_ActionForm' class should use this class.
636: */
637: class XCube_DependClassFactory
638: {
639: /**
640: * @public
641: * @internal
642: * @brief [static] Gets a XCube_Validator object by the rule name (depend name).
643: * @param $dependName string
644: * @return XCube_Validator
645: * @attention
646: * Only 'XCube_ActionForm' class should use this class.
647: */
648: function &factoryClass($dependName)
649: {
650: static $_cache;
651:
652: if (!is_array($_cache)) {
653: $_cache = array();
654: }
655:
656: if (!isset($_cache[$dependName])) {
657: // or switch?
658: $class_name = "XCube_" . ucfirst($dependName) . "Validator";
659: if (XC_CLASS_EXISTS($class_name)) {
660: $_cache[$dependName] = new $class_name();
661: }
662: else {
663: // FIXME:: use delegate?
664: die ("This is an error message of Alpha or Beta series. ${dependName} Validator is not found.");
665: }
666: }
667:
668: return $_cache[$dependName];
669: }
670: }
671:
672: ?>
673: