1: <?php
2: /**
3: * XOOPS Form Class
4: *
5: * You may not change or alter any portion of this comment or credits
6: * of supporting developers from this source code or any supporting source code
7: * which is considered copyrighted (c) material of the original comment or credit authors.
8: * This program is distributed in the hope that it will be useful,
9: * but WITHOUT ANY WARRANTY; without even the implied warranty of
10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11: *
12: * @copyright (c) 2000-2016 XOOPS Project (www.xoops.org)
13: * @license GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
14: * @package kernel
15: * @subpackage form
16: * @since 2.0.0
17: * @author Kazumi Ono (AKA onokazu) http://www.myweb.ne.jp/, http://jp.xoops.org/
18: * @author Taiwen Jiang <phppp@users.sourceforge.net>
19: */
20: defined('XOOPS_ROOT_PATH') || exit('Restricted access');
21:
22: /**
23: * Abstract base class for forms
24: *
25: * @author Kazumi Ono <onokazu@xoops.org>
26: * @author Taiwen Jiang <phppp@users.sourceforge.net>
27: * @package kernel
28: * @subpackage form
29: * @access public
30: */
31: class XoopsForm
32: {
33: /**
34: * *#@+
35: *
36: * @access private
37: */
38: /**
39: * "action" attribute for the html form
40: *
41: * @var string
42: */
43: public $_action;
44:
45: /**
46: * "method" attribute for the form.
47: *
48: * @var string
49: */
50: public $_method;
51:
52: /**
53: * "name" attribute of the form
54: *
55: * @var string
56: */
57: public $_name;
58:
59: /**
60: * title for the form
61: *
62: * @var string
63: */
64: public $_title;
65:
66: /**
67: * summary for the form (WGAC2 Requirement)
68: *
69: * @var string
70: */
71: public $_summary = '';
72:
73: /**
74: * array of {@link XoopsFormElement} objects
75: *
76: * @var array
77: */
78: public $_elements = array();
79:
80: /**
81: * HTML classes for the <form> tag
82: *
83: * @var array
84: */
85: public $_class = array();
86:
87: /**
88: * extra information for the <form> tag
89: *
90: * @var array
91: */
92: public $_extra = array();
93:
94: /**
95: * required elements
96: *
97: * @var array
98: */
99: public $_required = array();
100:
101: /**
102: * additional serialized object checksum (ERM Analysis - Requirement)
103: * @deprecated
104: * @access private
105: */
106: public $_objid = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
107:
108: /**
109: * *#@-
110: */
111:
112: /**
113: * constructor
114: *
115: * @param string $title title of the form
116: * @param string $name "name" attribute for the <form> tag
117: * @param string $action "action" attribute for the <form> tag
118: * @param string $method "method" attribute for the <form> tag
119: * @param bool $addtoken whether to add a security token to the form
120: * @param string $summary
121: */
122: public function __construct($title, $name, $action, $method = 'post', $addtoken = false, $summary = '')
123: {
124: $this->_title = $title;
125: $this->_name = $name;
126: $this->_action = $action;
127: $this->_method = $method;
128: $this->_summary = $summary;
129: if (false != $addtoken) {
130: $this->addElement(new XoopsFormHiddenToken());
131: }
132: }
133: /**
134: * PHP 4 style constructor compatibility shim
135: * @deprecated all callers should be using parent::__construct()
136: */
137: public function XoopsForm()
138: {
139: $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
140: trigger_error("Should call parent::__construct in {$trace[0]['file']} line {$trace[0]['line']},", E_USER_DEPRECATED);
141: self::__construct();
142: }
143: /**
144: * *#@+
145: * retrieves object serialization/identification id (sha1 used)
146: *
147: * each object has serialization<br>
148: * - legal requirement of enterprise relational management (ERM)
149: *
150: * @deprecated
151: * @access public
152: * @param mixed $object The object or value to serialize
153: * @param string $hashinfo Hashing algorithm to use (default 'sha1')
154: * @return string The serialization ID
155: */
156: public function getObjectID($object, $hashinfo = 'sha1')
157: {
158: // Initialize $var
159: $var = array(
160: 'name' => '',
161: 'value' => '',
162: 'func' => ''
163: );
164:
165: // Check if $object is an object; if not, use $this
166: if (!is_object($object)) {
167: $object = $this;
168: }
169:
170: // Switch hash method based on $hashinfo
171: $hashMethod = ('md5' === $hashinfo) ? 'md5' : 'sha1';
172:
173: // Hash the class name
174: $var['name'] = $hashMethod(get_class($object));
175:
176: // Hash the object variables
177: foreach (get_object_vars($object) as $key => $value) {
178: if ($key !== '_objid') {
179: $var['value'] = $this->getArrayID($value, $key, $var['value'], $hashinfo);
180: }
181: }
182:
183: // Hash the class methods
184: foreach (get_class_methods($object) as $key => $value) {
185: $var['func'] = $this->getArrayID($value, $key, $var['func'], $hashinfo);
186: }
187:
188: // Generate the final hash
189: $this->_objid = $hashMethod(implode(':', $var));
190:
191: return $this->_objid;
192: }
193:
194:
195: /**
196: * @param mixed $value
197: * @param mixed $key
198: * @param string $ret
199: * @param string $hashinfo
200: *
201: * @return string
202: */
203: public function getArrayID($value, $key, $ret, $hashinfo = 'sha1')
204: {
205: switch ($hashinfo) {
206: case 'md5':
207: if (!isset($ret)) {
208: $ret = '';
209: }
210: if (is_array($value)) {
211: foreach ($value as $keyb => $valueb) {
212: $ret = md5($ret . ':' . $this->getArrayID($valueb, $keyb, $ret, $hashinfo));
213: }
214: } else {
215: $ret = md5($ret . ':' . $key . ':' . $value);
216: }
217:
218: return $ret;
219: default:
220: if (!isset($ret)) {
221: $ret = '';
222: }
223: if (is_array($value)) {
224: foreach ($value as $keyb => $valueb) {
225: $ret = sha1($ret . ':' . $this->getArrayID($valueb, $keyb, $ret, $hashinfo));
226: }
227: } else {
228: $ret = sha1($ret . ':' . $key . ':' . $value);
229: }
230:
231: return $ret;
232: }
233: }
234:
235: /**
236: * return the summary of the form
237: *
238: * @param bool $encode To sanitizer the text?
239: * @return string
240: */
241: public function getSummary($encode = false)
242: {
243: return $encode ? htmlspecialchars($this->_summary, ENT_QUOTES) : $this->_summary;
244: }
245:
246: /**
247: * return the title of the form
248: *
249: * @param bool $encode To sanitizer the text?
250: * @return string
251: */
252: public function getTitle($encode = false)
253: {
254: return $encode ? htmlspecialchars($this->_title, ENT_QUOTES) : $this->_title;
255: }
256:
257: /**
258: * get the "name" attribute for the <form> tag
259: *
260: * Deprecated, to be refactored
261: *
262: * @param bool $encode To sanitizer the text?
263: * @return string
264: */
265: public function getName($encode = true)
266: {
267: return $encode ? htmlspecialchars($this->_name, ENT_QUOTES) : $this->_name;
268: }
269:
270: /**
271: * get the "action" attribute for the <form> tag
272: *
273: * @param bool $encode To sanitizer the text?
274: * @return string
275: */
276: public function getAction($encode = true)
277: {
278: // Convert &amp; to & for backward compatibility
279: return $encode ? htmlspecialchars(str_replace('&amp;', '&', $this->_action), ENT_QUOTES) : $this->_action;
280: }
281:
282: /**
283: * get the "method" attribute for the <form> tag
284: *
285: * @return string
286: */
287: public function getMethod()
288: {
289: return (strtolower($this->_method) === 'get') ? 'get' : 'post';
290: }
291:
292: /**
293: * Add an element to the form
294: *
295: * @param string|XoopsFormElement $formElement reference to a {@link XoopsFormElement}
296: * @param bool $required is this a "required" element?
297: *
298: */
299: public function addElement($formElement, $required = false)
300: {
301: if (is_string($formElement)) {
302: $this->_elements[] = $formElement;
303: } elseif (is_subclass_of($formElement, 'XoopsFormElement')) {
304: $this->_elements[] = &$formElement;
305: if (!$formElement->isContainer()) {
306: if ($required) {
307: $formElement->_required = true;
308: $this->_required[] = &$formElement;
309: }
310: } else {
311: $required_elements = &$formElement->getRequired();
312: $count = count($required_elements);
313: for ($i = 0; $i < $count; ++$i) {
314: $this->_required[] = &$required_elements[$i];
315: }
316: }
317: }
318: }
319:
320: /**
321: * get an array of forms elements
322: *
323: * @param bool $recurse get elements recursively?
324: *
325: * @return XoopsFormElement[] array of {@link XoopsFormElement}s
326: */
327: public function &getElements($recurse = false)
328: {
329: if (!$recurse) {
330: return $this->_elements;
331: } else {
332: $ret = array();
333: $count = count($this->_elements);
334: for ($i = 0; $i < $count; ++$i) {
335: if (is_object($this->_elements[$i])) {
336: if (!$this->_elements[$i]->isContainer()) {
337: $ret[] = &$this->_elements[$i];
338: } else {
339: $elements = &$this->_elements[$i]->getElements(true);
340: $count2 = count($elements);
341: for ($j = 0; $j < $count2; ++$j) {
342: $ret[] = &$elements[$j];
343: }
344: unset($elements);
345: }
346: }
347: }
348:
349: return $ret;
350: }
351: }
352:
353: /**
354: * get an array of "name" attributes of form elements
355: *
356: * @return array array of form element names
357: */
358: public function getElementNames()
359: {
360: $ret = array();
361: $elements = &$this->getElements(true);
362: $count = count($elements);
363: for ($i = 0; $i < $count; ++$i) {
364: $ret[] = $elements[$i]->getName();
365: }
366:
367: return $ret;
368: }
369:
370: /**
371: * get a reference to a {@link XoopsFormElement} object by its "name"
372: *
373: * @param string $name "name" attribute assigned to a {@link XoopsFormElement}
374: * @return object reference to a {@link XoopsFormElement}, false if not found
375: */
376: public function &getElementByName($name)
377: {
378: $elements =& $this->getElements(true);
379: $count = count($elements);
380: for ($i = 0; $i < $count; ++$i) {
381: if ($name == $elements[$i]->getName(false)) {
382: return $elements[$i];
383: }
384: }
385: $elt = null;
386:
387: return $elt;
388: }
389:
390: /**
391: * Sets the "value" attribute of a form element
392: *
393: * @param string $name the "name" attribute of a form element
394: * @param string $value the "value" attribute of a form element
395: */
396: public function setElementValue($name, $value)
397: {
398: $ele = &$this->getElementByName($name);
399: if (is_object($ele) && method_exists($ele, 'setValue')) {
400: $ele->setValue($value);
401: }
402: }
403:
404: /**
405: * Sets the "value" attribute of form elements in a batch
406: *
407: * @param array $values array of name/value pairs to be assigned to form elements
408: */
409: public function setElementValues($values)
410: {
411: if (!empty($values) && \is_array($values)) {
412: // will not use getElementByName() for performance..
413: $elements = &$this->getElements(true);
414: $count = count($elements);
415: for ($i = 0; $i < $count; ++$i) {
416: $name = $elements[$i]->getName(false);
417: if ($name && isset($values[$name]) && method_exists($elements[$i], 'setValue')) {
418: $elements[$i]->setValue($values[$name]);
419: }
420: }
421: }
422: }
423:
424: /**
425: * Gets the "value" attribute of a form element
426: *
427: * @param string $name the "name" attribute of a form element
428: * @param bool $encode To sanitizer the text?
429: * @return string the "value" attribute assigned to a form element, null if not set
430: */
431: public function getElementValue($name, $encode = false)
432: {
433: $ele = &$this->getElementByName($name);
434: if (is_object($ele) && method_exists($ele, 'getValue')) {
435: return $ele->getValue($encode);
436: }
437:
438: return null;
439: }
440:
441: /**
442: * gets the "value" attribute of all form elements
443: *
444: * @param bool $encode To sanitizer the text?
445: * @return array array of name/value pairs assigned to form elements
446: */
447: public function getElementValues($encode = false)
448: {
449: // will not use getElementByName() for performance..
450: $elements = &$this->getElements(true);
451: $count = count($elements);
452: $values = array();
453: for ($i = 0; $i < $count; ++$i) {
454: $name = $elements[$i]->getName(false);
455: if ($name && method_exists($elements[$i], 'getValue')) {
456: $values[$name] = &$elements[$i]->getValue($encode);
457: }
458: }
459:
460: return $values;
461: }
462:
463: /**
464: * set the "class" attribute for the <form> tag
465: *
466: * @param string $class
467: */
468: public function setClass($class)
469: {
470: $class = trim($class);
471: if (!empty($class)) {
472: $this->_class[] = $class;
473: }
474: }
475:
476: /**
477: * set the extra attributes for the <form> tag
478: *
479: * @param string $extra extra attributes for the <form> tag
480: */
481: public function setExtra($extra)
482: {
483: if (!empty($extra)) {
484: $this->_extra[] = $extra;
485: }
486: }
487:
488: /**
489: * set the summary tag for the <form> tag
490: *
491: * @param string $summary
492: */
493: public function setSummary($summary)
494: {
495: if (!empty($summary)) {
496: $this->summary = strip_tags($summary);
497: }
498: }
499:
500: /**
501: * get the "class" attribute for the <form> tag
502: *
503: * @return string "class" attribute value
504: */
505: public function &getClass()
506: {
507: if (empty($this->_class)) {
508: return false;
509: }
510: $classes = array();
511: foreach ($this->_class as $class) {
512: $classes[] = htmlspecialchars($class, ENT_QUOTES);
513: }
514:
515: return implode(' ', $classes);
516: }
517:
518: /**
519: * get the extra attributes for the <form> tag
520: *
521: * @return string
522: */
523: public function &getExtra()
524: {
525: $extra = empty($this->_extra) ? '' : ' ' . implode(' ', $this->_extra);
526:
527: return $extra;
528: }
529:
530: /**
531: * make an element "required"
532: *
533: * @param XoopsFormElement $formElement reference to a {@link XoopsFormElement}
534: */
535: public function setRequired(XoopsFormElement $formElement)
536: {
537: $this->_required[] = &$formElement;
538: }
539:
540: /**
541: * get an array of "required" form elements
542: *
543: * @return array array of {@link XoopsFormElement}s
544: */
545: public function &getRequired()
546: {
547: return $this->_required;
548: }
549:
550: /**
551: * insert a break in the form
552: *
553: * This method is abstract. It must be overwritten in the child classes.
554: *
555: * @param string $extra extra information for the break
556: * @abstract
557: */
558: public function insertBreak($extra = null)
559: {
560: }
561:
562: /**
563: * returns renderered form
564: *
565: * This method is abstract. It must be overwritten in the child classes.
566: *
567: * @abstract
568: */
569: public function render()
570: {
571: }
572:
573: /**
574: * displays rendered form
575: */
576: public function display()
577: {
578: echo $this->render();
579: }
580:
581: /**
582: * Renders the Javascript function needed for client-side for validation
583: *
584: * Form elements that have been declared "required" and not set will prevent the form from being
585: * submitted. Additionally, each element class may provide its own "renderValidationJS" method
586: * that is supposed to return custom validation code for the element.
587: *
588: * The element validation code can assume that the JS "myform" variable points to the form, and must
589: * execute <i>return false</i> if validation fails.
590: *
591: * A basic element validation method may contain something like this:
592: * <code>
593: * function renderValidationJS() {
594: * $name = $this->getName();
595: * return "if (myform.{$name}.value != 'valid') { " .
596: * "myform.{$name}.focus(); window.alert( '$name is invalid' ); return false;" .
597: * " }";
598: * }
599: * </code>
600: *
601: * @param boolean $withtags Include the < javascript > tags in the returned string
602: *
603: * @return string
604: */
605: public function renderValidationJS($withtags = true)
606: {
607: $js = '';
608: if ($withtags) {
609: $js .= "\n<!-- Start Form Validation JavaScript //-->\n<script type='text/javascript'>\n<!--//\n";
610: }
611: $formname = $this->getName();
612: $js .= "function xoopsFormValidate_{$formname}() { var myform = window.document.{$formname}; ";
613: $elements =& $this->getElements(true);
614: foreach ($elements as $elt) {
615: if (method_exists($elt, 'renderValidationJS')) {
616: $js .= $elt->renderValidationJS();
617: }
618: }
619: $js .= "return true;\n}\n";
620: if ($withtags) {
621: $js .= "//--></script>\n<!-- End Form Validation JavaScript //-->\n";
622: }
623:
624: return $js;
625: }
626:
627: /**
628: * assign to smarty form template instead of displaying directly
629: *
630: * @param XoopsTpl $tpl reference to a {@link Smarty} object object
631: * @see Smarty
632: */
633: public function assign(XoopsTpl $tpl)
634: {
635: $i = -1;
636: $elements = array();
637: if (count($this->getRequired()) > 0) {
638: $this->_elements[] = "<tr class='foot'><td colspan='2'>* = " . _REQUIRED . '</td></tr>';
639: }
640: foreach ($this->getElements() as $ele) {
641: ++$i;
642: if (is_string($ele)) {
643: $elements[$i]['body'] = $ele;
644: continue;
645: }
646: $ele_name = $ele->getName();
647: $ele_description = $ele->getDescription();
648: $n = $ele_name ?: $i;
649: $elements[$n]['name'] = $ele_name;
650: $elements[$n]['caption'] = $ele->getCaption();
651: $elements[$n]['body'] = $ele->render();
652: $elements[$n]['hidden'] = $ele->isHidden();
653: $elements[$n]['required'] = $ele->isRequired();
654: if ($ele_description != '') {
655: $elements[$n]['description'] = $ele_description;
656: }
657: }
658: $js = $this->renderValidationJS();
659: $tpl->assign($this->getName(), array(
660: 'title' => $this->getTitle(),
661: 'name' => $this->getName(),
662: 'action' => $this->getAction(),
663: 'method' => $this->getMethod(),
664: 'extra' => 'onsubmit="return xoopsFormValidate_' . $this->getName() . '();"' . $this->getExtra(),
665: 'javascript' => $js,
666: 'elements' => $elements,
667: 'rendered' => $this->render(),
668: ));
669: }
670: }
671: