1: <?php
2: /*
3: You may not change or alter any portion of this comment or credits
4: of supporting developers from this source code or any supporting source code
5: which is considered copyrighted (c) material of the original comment or credit authors.
6:
7: This program is distributed in the hope that it will be useful,
8: but WITHOUT ANY WARRANTY; without even the implied warranty of
9: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12:
13: namespace Xoops\Form;
14:
15: use Xoops\Html\Attributes;
16:
17: /**
18: * Element - Abstract base class for form elements
19: *
20: * @category Xoops\Form\Element
21: * @package Xoops\Form
22: * @author trabis <lusopoemas@gmail.com>
23: * @copyright 2012-2015 XOOPS Project (http://xoops.org)
24: * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
25: * @link http://xoops.org
26: */
27: abstract class Element extends Attributes
28: {
29: /**
30: * @var string[] list of attributes to NOT render
31: */
32: protected $suppressList = ['caption', 'datalist', 'description', 'option'];
33:
34: /**
35: * Javascript performing additional validation of this element data
36: *
37: * This property contains a list of Javascript snippets that will be sent to
38: * \Xoops\Form\Form::renderValidationJS().
39: * NB: All elements are added to the output one after the other, so don't forget
40: * to add a ";" after each to ensure no Javascript syntax error is generated.
41: *
42: * @var array ()
43: */
44: public $customValidationCode = array();
45:
46: /**
47: * extra attributes to go in the tag
48: *
49: * @var array
50: */
51: protected $extra = array();
52:
53: /**
54: * __construct
55: *
56: * @param array $attributes array of attribute name => value pairs
57: * Control attributes:
58: * ElementFactory::FORM_KEY optional form or tray to hold this element
59: */
60: public function __construct($attributes = array())
61: {
62: parent::__construct($attributes);
63: if ($this->has(ElementFactory::FORM_KEY)
64: && $this->get(ElementFactory::FORM_KEY) instanceof ContainerInterface) {
65: $this->get(ElementFactory::FORM_KEY)->addElement($this);
66: }
67: }
68:
69: /**
70: * render - Generates output for the element.
71: *
72: * This method is abstract and must be overwritten by the child classes.
73: *
74: * @return string
75: */
76: abstract public function render();
77:
78: /**
79: * render attributes as a string to include in HTML output
80: *
81: * @return string
82: */
83: public function renderAttributeString()
84: {
85: $this->suppressRender($this->suppressList);
86:
87: // title attribute needs to be generated if not already set
88: if (!$this->has('title')) {
89: $this->set('title', $this->getTitle());
90: }
91: // generate id from name if not already set
92: if (!$this->has('id')) {
93: $id = $this->get('name');
94: if (substr($id, -2) === '[]') {
95: $id = substr($id, 0, strlen($id)-2);
96: }
97: $this->set('id', $id);
98: }
99: return parent::renderAttributeString();
100: }
101:
102: /**
103: * getValue - Get an array of pre-selected values
104: *
105: * @param boolean $encode True to encode special characters
106: *
107: * @return mixed
108: */
109: public function getValue($encode = false)
110: {
111: $values = $this->get('value', '');
112: if (is_array($values)) {
113: $ret = array();
114: foreach ($values as $value) {
115: $ret[] = $encode ? htmlspecialchars($value, ENT_QUOTES) : $value;
116: }
117: return $ret;
118: }
119: return $encode ? htmlspecialchars($values, ENT_QUOTES) : $values;
120: }
121:
122: /**
123: * setValue - Set pre-selected values
124: *
125: * @param mixed $value value to assign to this element
126: *
127: * @return void
128: */
129: public function setValue($value)
130: {
131: $this->set('value', $value);
132: }
133:
134: /**
135: * setName - set the "name" attribute for the element
136: *
137: * @param string $name "name" attribute for the element
138: *
139: * @return void
140: */
141: public function setName($name)
142: {
143: $this->set('name', $name);
144: }
145:
146: /**
147: * getName - get the "name" attribute for the element
148: *
149: * @return string
150: */
151: public function getName()
152: {
153: return (string) $this->get('name');
154: }
155:
156: /**
157: * setAccessKey - set the access key attribute for the element
158: *
159: * @param string $key "accesskey" attribute for the element
160: *
161: * @return void
162: */
163: public function setAccessKey($key)
164: {
165: $this->set('accesskey', $key);
166: }
167:
168: /**
169: * getAccessKey - get the access key attribute for the element
170: *
171: * @return string "accesskey" attribute value
172: */
173: public function getAccessKey()
174: {
175: return (string) $this->get('accesskey');
176: }
177:
178: /**
179: * getAccessString - If the access key is found in the specified string, underline it
180: *
181: * @param string $str string to add access key highlight to
182: *
183: * @return string Enhanced string with the 1st occurrence of "accesskey underlined
184: */
185: public function getAccessString($str)
186: {
187: $access = $this->getAccessKey();
188: if (!empty($access) && (false !== ($pos = strpos($str, $access)))) {
189: return htmlspecialchars(substr($str, 0, $pos), ENT_QUOTES)
190: . '<span style="text-decoration: underline;">'
191: . htmlspecialchars(substr($str, $pos, 1), ENT_QUOTES) . '</span>'
192: . htmlspecialchars(substr($str, $pos + 1), ENT_QUOTES);
193: }
194: return htmlspecialchars($str, ENT_QUOTES);
195: }
196:
197: /**
198: * setClass - set the "class" attribute for the element
199: *
200: * @param string $class class attribute for the element
201: *
202: * @return void
203: */
204: public function setClass($class)
205: {
206: $this->add('class', (string) $class);
207: }
208:
209: /**
210: * getClass - get the "class" attribute for the element
211: *
212: * @return string "class" attribute value
213: */
214: public function getClass()
215: {
216: $class = $this->get('class', false);
217: if ($class === false) {
218: return false;
219: }
220: return htmlspecialchars(implode(' ', $class), ENT_QUOTES);
221: }
222:
223: /**
224: * setPattern - set the "pattern" attribute for the element
225: *
226: * @param string $pattern pattern attribute for the element
227: * @param string $patternDescription pattern description
228: *
229: * @return void
230: */
231: public function setPattern($pattern, $patternDescription = '')
232: {
233: $this->set('pattern', $pattern);
234: $this->set(':pattern_description', $patternDescription);
235: }
236:
237: /**
238: * getPattern - get the "pattern" attribute for the element
239: *
240: * @return string "pattern"
241: */
242: public function getPattern()
243: {
244: return (string) $this->get('pattern', '');
245: }
246:
247: /**
248: * getPatternDescription - get the "pattern_description"
249: *
250: * @return string "pattern_description"
251: */
252: public function getPatternDescription()
253: {
254: return (string) $this->get(':pattern_description', '');
255: }
256:
257: /**
258: * setDatalist - set the datalist attribute for the element
259: *
260: * @param string[]|string $datalist datalist attribute for the element
261: *
262: * @return void
263: */
264: public function setDatalist($datalist)
265: {
266: $this->add('datalist', $datalist);
267: }
268:
269: /**
270: * renderDatalist - get the datalist attribute for the element
271: *
272: * @return string "datalist" attribute value
273: */
274: public function renderDatalist()
275: {
276: if (!$this->isDatalist()) {
277: return '';
278: }
279: $ret = "\n" . '<datalist id="list_' . $this->getName() . '">' . "\n";
280: foreach ($this->get('datalist') as $datalist) {
281: $ret .= '<option value="' . htmlspecialchars($datalist, ENT_QUOTES) . '">' . "\n";
282: }
283: $ret .= '</datalist>' . "\n";
284: return $ret;
285: }
286:
287: /**
288: * isDatalist - is there a datalist for the element?
289: *
290: * @return boolean true if element has a non-empty datalist
291: */
292: public function isDatalist()
293: {
294: return $this->has('datalist');
295: }
296:
297: /**
298: * setCaption - set the caption for the element
299: *
300: * @param string $caption caption for element
301: *
302: * @return void
303: */
304: public function setCaption($caption)
305: {
306: $this->set('caption', $caption);
307: }
308:
309: /**
310: * getCaption - get the caption for the element
311: *
312: * @return string
313: */
314: public function getCaption()
315: {
316: return $this->get('caption', '');
317: //return htmlspecialchars($this->caption, ENT_QUOTES);
318: }
319:
320: /**
321: * setTitle - set the title for the element
322: *
323: * @param string $title title for element
324: *
325: * @return void
326: */
327: public function setTitle($title)
328: {
329: $this->set('title', $title);
330: }
331:
332: /**
333: * getTitle - get the title for the element
334: *
335: * @return string
336: */
337: public function getTitle()
338: {
339: if ($this->has('title')) {
340: return $this->get('title');
341: } else {
342: if ($this->has(':pattern_description')) {
343: return htmlspecialchars(
344: strip_tags($this->get('caption') . ' - ' . $this->get(':pattern_description')),
345: ENT_QUOTES
346: );
347: } else {
348: return htmlspecialchars(strip_tags($this->get('caption')), ENT_QUOTES);
349: }
350: }
351: }
352:
353: /**
354: * setDescription - set the element's description
355: *
356: * @param string $description description
357: *
358: * @return void
359: */
360: public function setDescription($description)
361: {
362: $this->set('description', $description);
363: }
364:
365: /**
366: * getDescription - get the element's description
367: *
368: * @param boolean $encode True to encode special characters
369: *
370: * @return string
371: */
372: public function getDescription($encode = false)
373: {
374: $description = $this->get('description', '');
375: return $encode ? htmlspecialchars($description, ENT_QUOTES) : $description;
376: }
377:
378: /**
379: * setHidden - flag the element as "hidden"
380: *
381: * @return void
382: */
383: public function setHidden()
384: {
385: $this->set('hidden');
386: }
387:
388: /**
389: * isHidden - is this a hidden element?
390: *
391: * @return boolean true if hidden
392: */
393: public function isHidden()
394: {
395: return $this->has('hidden');
396: }
397:
398: /**
399: * setRequired - set entry required
400: *
401: * @param boolean $bool true to set required entry for this element
402: *
403: * @return void
404: */
405: public function setRequired($bool = true)
406: {
407: if ($bool) {
408: $this->set('required');
409: }
410: }
411:
412: /**
413: * isRequired - is entry required for this element?
414: *
415: * @return boolean true if entry is required
416: */
417: public function isRequired()
418: {
419: return $this->has('required');
420: }
421:
422: /**
423: * setExtra - Add extra attributes to the element.
424: *
425: * This string will be inserted verbatim and unvalidated in the
426: * element's tag. Know what you are doing!
427: *
428: * @param string $extra extra raw text to insert into form
429: * @param boolean $replace If true, passed string will replace current
430: * content, otherwise it will be appended to it
431: *
432: * @return string[] New content of the extra string
433: *
434: * @deprecated please use attributes for event scripting
435: */
436: public function setExtra($extra, $replace = false)
437: {
438: if ($replace) {
439: $this->extra = array(trim($extra));
440: } else {
441: $this->extra[] = trim($extra);
442: }
443: return $this->extra;
444: }
445:
446: /**
447: * getExtra - Get the extra attributes for the element
448: *
449: * @param boolean $encode True to encode special characters
450: *
451: * @return string
452: *
453: * @see setExtra() this is going to disappear
454: */
455: public function getExtra($encode = false)
456: {
457: if (!$encode) {
458: return implode(' ', $this->extra);
459: }
460: $value = array();
461: foreach ($this->extra as $val) {
462: $value[] = str_replace('>', '>', str_replace('<', '<', $val));
463: }
464: return empty($value) ? '' : implode(' ', $value);
465: }
466:
467: /**
468: * addCustomValidationCode - Add custom validation javascript
469: *
470: * This string will be inserted verbatim and unvalidated in the page.
471: * Know what you are doing!
472: *
473: * @param string $code javascript code to insert into form
474: * @param boolean $replace If true, passed string will replace current code,
475: * otherwise it will be appended to it
476: *
477: * @return void
478: */
479: public function addCustomValidationCode($code, $replace = false)
480: {
481: if ($replace) {
482: $this->customValidationCode = [$code];
483: } else {
484: $this->customValidationCode[] = $code;
485: }
486: }
487:
488: /**
489: * renderValidationJS - Render custom javascript validation code
490: *
491: * @return string|false
492: */
493: public function renderValidationJS()
494: {
495: // render custom validation code if any
496: if (!empty($this->customValidationCode)) {
497: return implode("\n", $this->customValidationCode);
498: // generate validation code if required
499: } else {
500: if ($this->isRequired() && $eltname = $this->getName()) {
501: // $eltname = $this->getName();
502: $eltcaption = $this->getCaption();
503: $eltmsg = empty($eltcaption)
504: ? sprintf(\XoopsLocale::F_ENTER, $eltname)
505: : sprintf(\XoopsLocale::F_ENTER, $eltcaption);
506: $eltmsg = str_replace(array(':', '?', '%'), '', $eltmsg);
507: $eltmsg = str_replace('"', '\"', stripslashes($eltmsg));
508: $eltmsg = strip_tags($eltmsg);
509: return "\n"
510: . "if ( myform.{$eltname}.value == \"\" ) { window.alert(\"{$eltmsg}\");"
511: . " myform.{$eltname}.focus(); return false; }\n";
512: }
513: }
514: return false;
515: }
516:
517: /**
518: * Test if a class that starts with the pattern string is set
519: *
520: * @param string $pattern 'starts with' to match
521: *
522: * @return integer|false false if no match, or index of matching class
523: */
524: public function hasClassLike($pattern)
525: {
526: $class = $this->get('class');
527: if ($class) {
528: $length = strlen($pattern);
529: foreach ((array) $class as $i => $value) {
530: if (0 === strncmp($value, $pattern, $length)) {
531: return $i;
532: }
533: }
534: }
535: return false;
536: }
537:
538: /**
539: * themeDecorateElement - add theme decoration to element
540: *
541: * @return void
542: *
543: * @todo this should ask the theme
544: */
545: public function themeDecorateElement()
546: {
547: if ($this instanceof Button) {
548: $class = 'btn';
549: } elseif (false !== $this->hasClassLike('span')) {
550: return;
551: } elseif ($this instanceof TextArea) {
552: $class = 'span5';
553: } elseif ($this instanceof OptionElement) {
554: $class = 'span2';
555: $options = $this->get('option', []);
556: foreach ($options as $value) {
557: if (is_array($value)) { // optgroup
558: foreach ($value as $subvalue) {
559: if (strlen($subvalue) > 20) {
560: $class = 'span3';
561: break 2;
562: }
563: }
564: } elseif (strlen($value) > 20) {
565: $class = 'span3';
566: break;
567: }
568: }
569: } else {
570: $size = $this->get('size', 0);
571: if ($size < 20) {
572: $class = 'span2';
573: } elseif ($size < 30) {
574: $class = 'span3';
575: } else {
576: $class = 'span4';
577: }
578: }
579: $this->add('class', $class);
580: }
581:
582: /**
583: * Convenience method to assist with setting attributes when using BC Element syntax
584: *
585: * Set attribute $name to $value, replacing $value with $default if $value is empty, or if the
586: * value is not one of the values specified in the (optional) $enum array
587: *
588: * @param string $name attribute name
589: * @param mixed $value attribute value
590: * @param mixed $default default value
591: * @param array $enum optional list of valid values
592: *
593: * @return void
594: */
595: public function setWithDefaults($name, $value, $default = null, $enum = null)
596: {
597: if (empty($value)) {
598: $value = $default;
599: } elseif (null !== $enum && !in_array($value, $enum)) {
600: $value = $default;
601: }
602: $this->set($name, $value);
603: }
604:
605: /**
606: * Convenience method to assist with setting attributes when using BC Element syntax
607: *
608: * Set attribute $name to $value, replacing $value with $default if $value is empty, or if the
609: * value is not one of the values specified in the (optional) $enum array
610: *
611: * @param string $name attribute name
612: * @param mixed $value attribute value
613: *
614: * @return void
615: */
616: public function setIfNotEmpty($name, $value)
617: {
618: // don't overwrite
619: if (!$this->has($name) && !empty($value)) {
620: $this->set($name, $value);
621: }
622: }
623:
624: /**
625: * Convenience method to assist with setting attributes
626: *
627: * Set attribute $name to $value, replacing $value with $default if $value is empty, or if the
628: * value is not one of the values specified in the (optional) $enum array
629: *
630: * @param string $name attribute name
631: * @param mixed $value attribute value
632: *
633: * @return void
634: */
635: public function setIfNotSet($name, $value)
636: {
637: // don't overwrite
638: if (!$this->has($name)) {
639: $this->set($name, $value);
640: }
641: }
642: }
643: