Issues (3083)

htdocs/class/xoopsform/form.php (1 issue)

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 <[email protected]>
19
 */
20
defined('XOOPS_ROOT_PATH') || exit('Restricted access');
21
22
/**
23
 * Abstract base class for forms
24
 *
25
 * @author         Kazumi Ono <[email protected]>
26
 * @author         Taiwen Jiang <[email protected]>
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 = true, $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 | ENT_HTML5) : $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 | ENT_HTML5) : $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 | ENT_HTML5) : $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 | ENT_HTML5) : $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;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
509
        }
510
        $classes = array();
511
        foreach ($this->_class as $class) {
512
            $classes[] = htmlspecialchars($class, ENT_QUOTES | ENT_HTML5);
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
        //  Removed hard-coded legacy pseudo-element - XoopsFormRenderer is now responsible for the legend
638
        foreach ($this->getElements() as $ele) {
639
            ++$i;
640
            if (is_string($ele)) {
641
                $elements[$i]['body'] = $ele;
642
                continue;
643
            }
644
            $ele_name                 = $ele->getName();
645
            $ele_description          = $ele->getDescription();
646
            $n                        = $ele_name ?: $i;
647
            $elements[$n]['name']     = $ele_name;
648
            $elements[$n]['caption']  = $ele->getCaption();
649
            $elements[$n]['body']     = $ele->render();
650
            $elements[$n]['hidden']   = $ele->isHidden();
651
            $elements[$n]['required'] = $ele->isRequired();
652
            if ($ele_description != '') {
653
                $elements[$n]['description'] = $ele_description;
654
            }
655
        }
656
        $js = $this->renderValidationJS();
657
        $tpl->assign($this->getName(), array(
658
            'title'      => $this->getTitle(),
659
            'name'       => $this->getName(),
660
            'action'     => $this->getAction(),
661
            'method'     => $this->getMethod(),
662
            'extra'      => 'onsubmit="return xoopsFormValidate_' . $this->getName() . '();"' . $this->getExtra(),
663
            'javascript' => $js,
664
            'elements'   => $elements,
665
            'rendered'   => $this->render(),
666
        ));
667
    }
668
}
669