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 (http://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: * extra information for the <form> tag
82: *
83: * @var array
84: */
85: public $_extra = array();
86:
87: /**
88: * required elements
89: *
90: * @var array
91: */
92: public $_required = array();
93:
94: /**
95: * additional serialised object checksum (ERM Analysis - Requirement)
96: * @deprecated
97: * @access private
98: */
99: public $_objid = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
100:
101: /**
102: * *#@-
103: */
104:
105: /**
106: * constructor
107: *
108: * @param string $title title of the form
109: * @param string $name "name" attribute for the <form> tag
110: * @param string $action "action" attribute for the <form> tag
111: * @param string $method "method" attribute for the <form> tag
112: * @param bool $addtoken whether to add a security token to the form
113: * @param string $summary
114: */
115: public function __construct($title, $name, $action, $method = 'post', $addtoken = false, $summary = '')
116: {
117: $this->_title = $title;
118: $this->_name = $name;
119: $this->_action = $action;
120: $this->_method = $method;
121: $this->_summary = $summary;
122: if ($addtoken != false) {
123: $this->addElement(new XoopsFormHiddenToken());
124: }
125: }
126: /**
127: * PHP 4 style constructor compatibility shim
128: * @deprecated all callers should be using parent::__construct()
129: */
130: public function XoopsForm()
131: {
132: $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);
133: trigger_error("Should call parent::__construct in {$trace[0]['file']} line {$trace[0]['line']},");
134: self::__construct();
135: }
136: /**
137: * *#@+
138: * retrieves object serialisation/identification id (sha1 used)
139: *
140: * each object has serialisation<br>
141: * - legal requirement of enterprise relational management (ERM)
142: *
143: * @deprecated
144: * @access public
145: * @param $object
146: * @param string $hashinfo
147: * @return string
148: */
149: public function getObjectID($object, $hashinfo = 'sha1')
150: {
151: if (!is_object($object)) {
152: $object = $this;
153: }
154:
155: switch ($hashinfo) {
156: case 'md5':
157:
158: @$var['name'] = md5(get_class($object));
159:
160: foreach (get_object_vars($object) as $key => $value) {
161: if ($key !== '_objid') {
162: @$var['value'] = $this->getArrayID($value, $key, $var['value'], $hashinfo);
163: }
164: }
165:
166: foreach (get_class_methods($object) as $key => $value) {
167: @$var['func'] = $this->getArrayID($value, $key, $var['func'], $hashinfo);
168: }
169:
170: @$this->_objid = md5($var['name'] . ':' . $var['func'] . ':' . $var['value']);
171:
172: return $this->_objid;
173: break;
174:
175: default:
176:
177: @$var['name'] = sha1(get_class($object));
178:
179: foreach (get_object_vars($object) as $key => $value) {
180: if ($key !== '_objid') {
181: @$var['value'] = $this->getArrayID($value, $key, $var['value'], $hashinfo);
182: }
183: }
184:
185: foreach (get_class_methods($object) as $key => $value) {
186: @$var['func'] = $this->getArrayID($value, $key, $var['func'], $hashinfo);
187: }
188:
189: @$this->_objid = sha1($var['name'] . ':' . $var['func'] . ':' . $var['value']);
190:
191: return $this->_objid;
192:
193: }
194: }
195:
196: /**
197: * @param $value
198: * @param $key
199: * @param $ret
200: * @param string $hashinfo
201: *
202: * @return string
203: */
204: public function getArrayID($value, $key, $ret, $hashinfo = 'sha1')
205: {
206: switch ($hashinfo) {
207: case 'md5':
208: if (is_array($value)) {
209: foreach ($value as $keyb => $valueb) {
210: @$ret = md5($ret . ':' . $this->getArrayID($valueb, $keyb, $ret, $hashinfo));
211: }
212: } else {
213: @$ret = md5($ret . ':' . $key . ':' . $value);
214: }
215:
216: return $ret;
217: break;
218: default:
219: if (is_array($value)) {
220: foreach ($value as $keyb => $valueb) {
221: @$ret = sha1($ret . ':' . $this->getArrayID($valueb, $keyb, $ret, $hashinfo));
222: }
223: } else {
224: @$ret = sha1($ret . ':' . $key . ':' . $value);
225: }
226:
227: return $ret;
228: break;
229: }
230: }
231:
232: /**
233: * return the summary of the form
234: *
235: * @param bool $encode To sanitizer the text?
236: * @return string
237: */
238: public function getSummary($encode = false)
239: {
240: return $encode ? htmlspecialchars($this->_summary, ENT_QUOTES) : $this->_summary;
241: }
242:
243: /**
244: * return the title of the form
245: *
246: * @param bool $encode To sanitizer the text?
247: * @return string
248: */
249: public function getTitle($encode = false)
250: {
251: return $encode ? htmlspecialchars($this->_title, ENT_QUOTES) : $this->_title;
252: }
253:
254: /**
255: * get the "name" attribute for the <form> tag
256: *
257: * Deprecated, to be refactored
258: *
259: * @param bool $encode To sanitizer the text?
260: * @return string
261: */
262: public function getName($encode = true)
263: {
264: return $encode ? htmlspecialchars($this->_name, ENT_QUOTES) : $this->_name;
265: }
266:
267: /**
268: * get the "action" attribute for the <form> tag
269: *
270: * @param bool $encode To sanitizer the text?
271: * @return string
272: */
273: public function getAction($encode = true)
274: {
275: // Convert & to & for backward compatibility
276: return $encode ? htmlspecialchars(str_replace('&', '&', $this->_action), ENT_QUOTES) : $this->_action;
277: }
278:
279: /**
280: * get the "method" attribute for the <form> tag
281: *
282: * @return string
283: */
284: public function getMethod()
285: {
286: return (strtolower($this->_method) === 'get') ? 'get' : 'post';
287: }
288:
289: /**
290: * Add an element to the form
291: *
292: * @param string|XoopsFormElement $formElement reference to a {@link XoopsFormElement}
293: * @param bool $required is this a "required" element?
294: *
295: */
296: public function addElement($formElement, $required = false)
297: {
298: if (is_string($formElement)) {
299: $this->_elements[] = $formElement;
300: } elseif (is_subclass_of($formElement, 'xoopsformelement')) {
301: $this->_elements[] = &$formElement;
302: if (!$formElement->isContainer()) {
303: if ($required) {
304: $formElement->_required = true;
305: $this->_required[] = &$formElement;
306: }
307: } else {
308: $required_elements = &$formElement->getRequired();
309: $count = count($required_elements);
310: for ($i = 0; $i < $count; ++$i) {
311: $this->_required[] = &$required_elements[$i];
312: }
313: }
314: }
315: }
316:
317: /**
318: * get an array of forms elements
319: *
320: * @param bool $recurse get elements recursively?
321: *
322: * @return XoopsFormElement[] array of {@link XoopsFormElement}s
323: */
324: public function &getElements($recurse = false)
325: {
326: if (!$recurse) {
327: return $this->_elements;
328: } else {
329: $ret = array();
330: $count = count($this->_elements);
331: for ($i = 0; $i < $count; ++$i) {
332: if (is_object($this->_elements[$i])) {
333: if (!$this->_elements[$i]->isContainer()) {
334: $ret[] = &$this->_elements[$i];
335: } else {
336: $elements = &$this->_elements[$i]->getElements(true);
337: $count2 = count($elements);
338: for ($j = 0; $j < $count2; ++$j) {
339: $ret[] = &$elements[$j];
340: }
341: unset($elements);
342: }
343: }
344: }
345:
346: return $ret;
347: }
348: }
349:
350: /**
351: * get an array of "name" attributes of form elements
352: *
353: * @return array array of form element names
354: */
355: public function getElementNames()
356: {
357: $ret = array();
358: $elements = &$this->getElements(true);
359: $count = count($elements);
360: for ($i = 0; $i < $count; ++$i) {
361: $ret[] = $elements[$i]->getName();
362: }
363:
364: return $ret;
365: }
366:
367: /**
368: * get a reference to a {@link XoopsFormElement} object by its "name"
369: *
370: * @param string $name "name" attribute assigned to a {@link XoopsFormElement}
371: * @return object reference to a {@link XoopsFormElement}, false if not found
372: */
373: public function &getElementByName($name)
374: {
375: $elements =& $this->getElements(true);
376: $count = count($elements);
377: for ($i = 0; $i < $count; ++$i) {
378: if ($name == $elements[$i]->getName(false)) {
379: return $elements[$i];
380: }
381: }
382: $elt = null;
383:
384: return $elt;
385: }
386:
387: /**
388: * Sets the "value" attribute of a form element
389: *
390: * @param string $name the "name" attribute of a form element
391: * @param string $value the "value" attribute of a form element
392: */
393: public function setElementValue($name, $value)
394: {
395: $ele = &$this->getElementByName($name);
396: if (is_object($ele) && method_exists($ele, 'setValue')) {
397: $ele->setValue($value);
398: }
399: }
400:
401: /**
402: * Sets the "value" attribute of form elements in a batch
403: *
404: * @param array $values array of name/value pairs to be assigned to form elements
405: */
406: public function setElementValues($values)
407: {
408: if (is_array($values) && !empty($values)) {
409: // will not use getElementByName() for performance..
410: $elements = &$this->getElements(true);
411: $count = count($elements);
412: for ($i = 0; $i < $count; ++$i) {
413: $name = $elements[$i]->getName(false);
414: if ($name && isset($values[$name]) && method_exists($elements[$i], 'setValue')) {
415: $elements[$i]->setValue($values[$name]);
416: }
417: }
418: }
419: }
420:
421: /**
422: * Gets the "value" attribute of a form element
423: *
424: * @param string $name the "name" attribute of a form element
425: * @param bool $encode To sanitizer the text?
426: * @return string the "value" attribute assigned to a form element, null if not set
427: */
428: public function getElementValue($name, $encode = false)
429: {
430: $ele = &$this->getElementByName($name);
431: if (is_object($ele) && method_exists($ele, 'getValue')) {
432: return $ele->getValue($encode);
433: }
434:
435: return null;
436: }
437:
438: /**
439: * gets the "value" attribute of all form elements
440: *
441: * @param bool $encode To sanitizer the text?
442: * @return array array of name/value pairs assigned to form elements
443: */
444: public function getElementValues($encode = false)
445: {
446: // will not use getElementByName() for performance..
447: $elements = &$this->getElements(true);
448: $count = count($elements);
449: $values = array();
450: for ($i = 0; $i < $count; ++$i) {
451: $name = $elements[$i]->getName(false);
452: if ($name && method_exists($elements[$i], 'getValue')) {
453: $values[$name] = &$elements[$i]->getValue($encode);
454: }
455: }
456:
457: return $values;
458: }
459:
460: /**
461: * set the extra attributes for the <form> tag
462: *
463: * @param string $extra extra attributes for the <form> tag
464: */
465: public function setExtra($extra)
466: {
467: if (!empty($extra)) {
468: $this->_extra[] = $extra;
469: }
470: }
471:
472: /**
473: * set the summary tag for the <form> tag
474: *
475: * @param string $summary
476: */
477: public function setSummary($summary)
478: {
479: if (!empty($summary)) {
480: $this->summary = strip_tags($summary);
481: }
482: }
483:
484: /**
485: * get the extra attributes for the <form> tag
486: *
487: * @return string
488: */
489: public function &getExtra()
490: {
491: $extra = empty($this->_extra) ? '' : ' ' . implode(' ', $this->_extra);
492:
493: return $extra;
494: }
495:
496: /**
497: * make an element "required"
498: *
499: * @param XoopsFormElement $formElement reference to a {@link XoopsFormElement}
500: */
501: public function setRequired(XoopsFormElement $formElement)
502: {
503: $this->_required[] = &$formElement;
504: }
505:
506: /**
507: * get an array of "required" form elements
508: *
509: * @return array array of {@link XoopsFormElement}s
510: */
511: public function &getRequired()
512: {
513: return $this->_required;
514: }
515:
516: /**
517: * insert a break in the form
518: *
519: * This method is abstract. It must be overwritten in the child classes.
520: *
521: * @param string $extra extra information for the break
522: * @abstract
523: */
524: public function insertBreak($extra = null)
525: {
526: }
527:
528: /**
529: * returns renderered form
530: *
531: * This method is abstract. It must be overwritten in the child classes.
532: *
533: * @abstract
534: */
535: public function render()
536: {
537: }
538:
539: /**
540: * displays rendered form
541: */
542: public function display()
543: {
544: echo $this->render();
545: }
546:
547: /**
548: * Renders the Javascript function needed for client-side for validation
549: *
550: * Form elements that have been declared "required" and not set will prevent the form from being
551: * submitted. Additionally, each element class may provide its own "renderValidationJS" method
552: * that is supposed to return custom validation code for the element.
553: *
554: * The element validation code can assume that the JS "myform" variable points to the form, and must
555: * execute <i>return false</i> if validation fails.
556: *
557: * A basic element validation method may contain something like this:
558: * <code>
559: * function renderValidationJS() {
560: * $name = $this->getName();
561: * return "if (myform.{$name}.value != 'valid') { " .
562: * "myform.{$name}.focus(); window.alert( '$name is invalid' ); return false;" .
563: * " }";
564: * }
565: * </code>
566: *
567: * @param boolean $withtags Include the < javascript > tags in the returned string
568: *
569: * @return string
570: */
571: public function renderValidationJS($withtags = true)
572: {
573: $js = '';
574: if ($withtags) {
575: $js .= "\n<!-- Start Form Validation JavaScript //-->\n<script type='text/javascript'>\n<!--//\n";
576: }
577: $formname = $this->getName();
578: $js .= "function xoopsFormValidate_{$formname}() { var myform = window.document.{$formname}; ";
579: $elements =& $this->getElements(true);
580: foreach ($elements as $elt) {
581: if (method_exists($elt, 'renderValidationJS')) {
582: $js .= $elt->renderValidationJS();
583: }
584: }
585: $js .= "return true;\n}\n";
586: if ($withtags) {
587: $js .= "//--></script>\n<!-- End Form Validation JavaScript //-->\n";
588: }
589:
590: return $js;
591: }
592:
593: /**
594: * assign to smarty form template instead of displaying directly
595: *
596: * @param XoopsTpl $tpl reference to a {@link Smarty} object object
597: * @see Smarty
598: */
599: public function assign(XoopsTpl $tpl)
600: {
601: $i = -1;
602: $elements = array();
603: if (count($this->getRequired()) > 0) {
604: $this->_elements[] = "<tr class='foot'><td colspan='2'>* = " . _REQUIRED . '</td></tr>';
605: }
606: foreach ($this->getElements() as $ele) {
607: ++$i;
608: if (is_string($ele)) {
609: $elements[$i]['body'] = $ele;
610: continue;
611: }
612: $ele_name = $ele->getName();
613: $ele_description = $ele->getDescription();
614: $n = $ele_name ?: $i;
615: $elements[$n]['name'] = $ele_name;
616: $elements[$n]['caption'] = $ele->getCaption();
617: $elements[$n]['body'] = $ele->render();
618: $elements[$n]['hidden'] = $ele->isHidden();
619: $elements[$n]['required'] = $ele->isRequired();
620: if ($ele_description != '') {
621: $elements[$n]['description'] = $ele_description;
622: }
623: }
624: $js = $this->renderValidationJS();
625: $tpl->assign($this->getName(), array(
626: 'title' => $this->getTitle(),
627: 'name' => $this->getName(),
628: 'action' => $this->getAction(),
629: 'method' => $this->getMethod(),
630: 'extra' => 'onsubmit="return xoopsFormValidate_' . $this->getName() . '();"' . $this->getExtra(),
631: 'javascript' => $js,
632: 'elements' => $elements,
633: 'rendered' => $this->render(),
634: ));
635: }
636: }
637: