1: <?php
2:
3: /**
4: *
5: * @package Plugin
6: * @subpackage FormAssistant
7: * @version SVN Revision $Rev:$
8: * @author marcus.gnass
9: * @copyright four for business AG
10: * @link http://www.4fb.de
11: */
12:
13: // assert CONTENIDO framework
14: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
15:
16: /**
17: * PIFA field item collection class.
18: * It's a kind of a model.
19: *
20: * @author marcus.gnass
21: */
22: class PifaFieldCollection extends ItemCollection {
23:
24: /**
25: * Create an instance.
26: *
27: * @param mixed $where clause to be used to load items or false
28: */
29: public function __construct($where = false) {
30: parent::__construct(cRegistry::getDbTableName('pifa_field'), 'idfield');
31: $this->_setItemClass('PifaField');
32: if (false !== $where) {
33: $this->select($where);
34: }
35: }
36:
37: /**
38: * Reorders a forms fields according to the given.
39: *
40: * @param int $idform
41: * @param string $idfields containing a CSV list of idfield as integer
42: */
43: public static function reorder($idform, $idfields) {
44: $sql = "-- PifaFieldCollection::reorder()
45: UPDATE
46: " . cRegistry::getDbTableName('pifa_field') . "
47: SET
48: field_rank = FIND_IN_SET(idfield, '$idfields')
49: WHERE
50: idform = $idform
51: ;";
52:
53: cRegistry::getDb()->query($sql);
54: }
55: }
56:
57: /**
58: * PIFA field item class.
59: * It's a kind of a model.
60: *
61: * @author marcus.gnass
62: */
63: class PifaField extends Item {
64:
65: /**
66: * Size to use for VARCHAR fields.
67: * Remember: the maximum row size for the used table type, not counting
68: * BLOBs, is 65535.
69: *
70: * @var int
71: * @todo PIFA should be able to calculate the size for one record by the
72: * size of its fields and handle it accordingly.
73: */
74: const VARCHAR_SIZE = 255;
75:
76: /**
77: * Input field for single-line text.
78: *
79: * @var int
80: */
81: const INPUTTEXT = 1;
82:
83: /**
84: * Input field for multi-line text.
85: *
86: * @var int
87: */
88: const TEXTAREA = 2;
89:
90: /**
91: * Input field for single-line password.
92: *
93: * @var int
94: */
95: const INPUTPASSWORD = 3;
96:
97: /**
98: * Radiobox.
99: *
100: * @var int
101: */
102: const INPUTRADIO = 4;
103:
104: /**
105: * Checkbox
106: *
107: * @var int
108: */
109: const INPUTCHECKBOX = 5;
110:
111: /**
112: * Selectbox allowing for selection of a single option.
113: *
114: * @var int
115: */
116: const SELECT = 6;
117:
118: /**
119: * Selectbox allowing for selection of multiple options.
120: *
121: * @var int
122: */
123: const SELECTMULTI = 7;
124:
125: /**
126: * Input field for date selection.
127: *
128: * @var int
129: */
130: const DATEPICKER = 8;
131:
132: /**
133: * Input field for file selection.
134: *
135: * @var int
136: */
137: const INPUTFILE = 9;
138:
139: /**
140: * Processbar.
141: *
142: * @var int
143: */
144: const PROCESSBAR = 10;
145:
146: /**
147: * Slider.
148: *
149: * @var int
150: */
151: const SLIDER = 11;
152:
153: // /**
154: // * Captcha.
155: // *
156: // * @var int
157: // */
158: // const CAPTCHA = 12;
159:
160: /**
161: * Button of type submit.
162: *
163: * @var int
164: */
165: const BUTTONSUBMIT = 13;
166:
167: /**
168: * Button of type reset.
169: *
170: * @var int
171: */
172: const BUTTONRESET = 14;
173:
174: /**
175: * General button.
176: *
177: * @deprecated use PifaField::BUTTON instead
178: * @var int
179: */
180: const BUTTONBACK = 15;
181:
182: /**
183: * General button.
184: *
185: * @var int
186: */
187: const BUTTON = 15;
188:
189: /**
190: *
191: * @var int
192: */
193: const MATRIX = 16;
194:
195: /**
196: * A text to be displayed between form fields.
197: * It's no form field on its own but should be handled like one.
198: *
199: * @var int
200: */
201: const PARA = 17;
202:
203: /**
204: * A hidden input field.
205: *
206: * @var int
207: */
208: const INPUTHIDDEN = 18;
209:
210: /**
211: * Begin of a fieldset.
212: *
213: * @var int
214: */
215: const FIELDSET_BEGIN = 19;
216:
217: /**
218: * End of a fieldset.
219: *
220: * @var int
221: */
222: const FIELDSET_END = 20;
223:
224: /**
225: * Button of type image (which is in fact a submit button).
226: *
227: * @var int
228: */
229: const BUTTONIMAGE = 21;
230:
231: /**
232: * The form fields value.
233: *
234: * @var mixed
235: */
236: private $_value = NULL;
237:
238: /**
239: * The file that was transmitted in case of INPUTFILE.
240: *
241: * @var array
242: */
243: private $_file = NULL;
244:
245: /**
246: * Create an instance.
247: *
248: * @param mixed $id ID of item to be loaded or false
249: */
250: public function __construct($id = false) {
251: parent::__construct(cRegistry::getDbTableName('pifa_field'), 'idfield');
252: $this->setFilters(array(), array());
253: if (false !== $id) {
254: $this->loadByPrimaryKey($id);
255: }
256: }
257:
258: /**
259: * Rule has to be stripslashed to allow regular expressions with
260: * backslashes.
261: *
262: * @see Item::getField()
263: */
264: function getField($field, $bSafe = true) {
265: $value = parent::getField($field, $bSafe);
266: if ('rule' === $field) {
267: $value = stripslashes($value);
268: }
269: return $value;
270: }
271:
272: /**
273: * Getter for protected prop.
274: */
275: public function getLastError() {
276: return $this->lasterror;
277: }
278:
279: /**
280: * Returns this fields stored value.
281: *
282: * @return mixed the $_value
283: */
284: public function getValue() {
285: return $this->_value;
286: }
287:
288: /**
289: *
290: * @param mixed $_value
291: */
292: public function setValue($value) {
293: $this->_value = $value;
294: }
295:
296: /**
297: * keys: name, tmp_name
298: *
299: * @return array the $_file
300: */
301: public function getFile() {
302: return $this->_file;
303: }
304:
305: /**
306: *
307: * @param array $_file
308: */
309: public function setFile(array $_file) {
310: $this->_file = $_file;
311: }
312:
313: /**
314: *
315: * @throws PifaValidationException if an error occured
316: */
317: public function validate() {
318:
319: // get value
320: $values = $this->getValue();
321: if (NULL === $values) {
322: $values = $this->get('default_value');
323: }
324:
325: // value could be an array (for form fields with suffix '[]')
326: // so make it an array anyway ...
327: if (!is_array($values)) {
328: $values = array(
329: $values
330: );
331: }
332:
333: foreach ($values as $value) {
334:
335: // if (self::CAPTCHA == $this->get('field_type')) {
336: // // check for captcha
337: // $securimage = new Securimage(array(
338: // 'session_name' => cRegistry::getClientId() . 'frontend'
339: // ));
340: // $isValid = $securimage->check($value);
341: // } else
342:
343: if (1 === cSecurity::toInteger($this->get('obligatory')) && 0 === strlen($value)) {
344: // check for obligatory & rule
345: $isValid = false;
346: } else if (0 < strlen($this->get('rule')) && in_array(preg_match($this->get('rule'), $value), array(
347: false,
348: 0
349: ))) {
350: // check for rule
351: $isValid = false;
352: } else {
353: $isValid = true;
354: }
355:
356: // throw error
357: if (true !== $isValid) {
358: $error_message = $this->get('error_message');
359: if (NULL === $error_message) {
360: // $error_message = 'invalid data';
361: $error_message = '';
362: }
363: throw new PifaValidationException(array(
364: $this->get('idfield') => $error_message
365: ));
366: }
367: }
368: }
369:
370: /**
371: * Returns HTML for this form that should be displayed in frontend.
372: *
373: * @param array $errors to be displayed for form field
374: * @return string
375: */
376: public function toHtml(array $errors = NULL) {
377: $out = '';
378: switch (cSecurity::toInteger($this->get('field_type'))) {
379:
380: case self::FIELDSET_BEGIN:
381:
382: // optional class for field
383: if (0 < strlen(trim($this->get('css_class')))) {
384: $class = ' class="' . implode(' ', explode(',', $this->get('css_class'))) . '"';
385: }
386: $out .= "\n\t<fieldset$class>";
387: // add optional legend/description
388: if (1 === cSecurity::toInteger($this->get('display_label'))) {
389: $label = $this->get('label');
390: $out .= "\n\t<legend>$label</legend>";
391: }
392:
393: return $out;
394: break;
395:
396: case self::FIELDSET_END:
397:
398: return "\n\t</fieldset>";
399: break;
400:
401: default:
402:
403: // build HTML content
404: $content = array();
405: try {
406: $content[] = $this->_getElemLabel();
407: $content[] = $this->_getElemField();
408: $content[] = $this->_getElemHelp();
409: $content[] = $this->_getElemScript();
410: // / add this fields error message
411: if (array_key_exists($this->get('idfield'), $errors)) {
412: $error = $errors[$this->get('idfield')];
413: $content[] = new cHTMLParagraph($error, 'pifa-error-message');
414: }
415: } catch (PifaNotImplementedException $e) {
416: return NULL; // PASS // warning?
417: }
418:
419: $content = array_filter($content);
420: if (empty($content)) {
421: return NULL; // PASS // warning?
422: }
423:
424: // CSS class for surrounding division
425: $class = 'pifa-field-' . $this->get('field_type');
426: // optional class for field
427: if (0 < strlen(trim($this->get('css_class')))) {
428: $class .= ' ' . implode(' ', explode(',', $this->get('css_class')));
429: }
430: // optional class for obligatory field
431: if (true === (bool) $this->get('obligatory')) {
432: $class .= ' pifa-obligatory';
433: }
434: // optional error class for field
435: if (NULL !== $error) {
436: $class .= ' pifa-error';
437: }
438:
439: // ID for surrounding division
440: $id = 'pifa-field-' . $this->get('idfield');
441:
442: // surrounding division
443: $div = new cHTMLDiv($content, $class, $id);
444:
445: return "\n\t" . $div->render();
446: break;
447: }
448: }
449:
450: /**
451: *
452: * @return cHTMLLabel
453: * @todo should be private, right?
454: */
455: public function _getElemLabel() {
456: if (1 !== cSecurity::toInteger($this->get('display_label'))) {
457: return '';
458: }
459:
460: // get field data
461: $idfield = cSecurity::toInteger($this->get('idfield'));
462: $fieldType = cSecurity::toInteger($this->get('field_type'));
463: $label = $this->get('label');
464:
465: if (NULL === $label) {
466: return NULL;
467: }
468:
469: // buttons have no external label
470: // the label is instead used as element value (see _getElemField())
471: if (in_array($fieldType, array(
472: self::BUTTONSUBMIT,
473: self::BUTTONRESET,
474: self::BUTTON,
475: self::BUTTONIMAGE
476: ))) {
477: return NULL;
478: }
479:
480: // obligatory fields have an additional ' *'
481: if (true === (bool) $this->get('obligatory')) {
482: $label .= ' *';
483: }
484:
485: // add span to request new captcha code
486: // if (self::CAPTCHA===$fieldType) {
487: // $label .= '<br><br><span style="cursor: pointer;">New Captcha
488: // Code</span>';
489: // }
490:
491: $elemLabel = new cHTMLLabel($label, 'pifa-field-elm-' . $idfield, 'pifa-field-lbl');
492: // set ID (workaround: remove ID first!)
493: $elemLabel->removeAttribute('id');
494:
495: return $elemLabel;
496: }
497:
498: /**
499: * Creates the HTML for a form field.
500: *
501: * The displayed value of a form field can be set via a GET param whose name
502: * has to be the fields column name which is set if the form is displayed
503: * for the first time and user hasn't entered another value.
504: *
505: * @param bool $error if field elem should be displayed as erroneous
506: * @throws PifaNotImplementedException if field type is not implemented
507: * @return cHTMLTextbox cHTMLTextarea cHTMLPasswordbox cHTMLSpan
508: * cHTMLSelectElement NULL cHTMLButton
509: * @todo should be private, right?
510: */
511: public function _getElemField() {
512:
513: // get field data
514: $idfield = cSecurity::toInteger($this->get('idfield'));
515:
516: $fieldType = cSecurity::toInteger($this->get('field_type'));
517:
518: $columnName = $this->get('column_name');
519:
520: $label = $this->get('label');
521:
522: $uri = $this->get('uri');
523:
524: // get option labels & values
525: // either from field or from external data source class
526: $optionClass = $this->get('option_class');
527: if (0 === strlen(trim($optionClass))) {
528: $optionLabels = $this->get('option_labels');
529: if (NULL !== $optionLabels) {
530: $optionLabels = explode(',', $optionLabels);
531: }
532: $optionValues = $this->get('option_values');
533: if (NULL !== $optionValues) {
534: $optionValues = explode(',', $optionValues);
535: }
536: } else {
537: $filename = Pifa::fromCamelCase($optionClass);
538: $filename = "extensions/class.pifa.$filename.php";
539: if (false === file_exists(Pifa::getPath() . $filename)) {
540: $msg = Pifa::i18n('MISSING_EOD_FILE');
541: $msg = sprintf($msg, $filename);
542: throw new PifaException($msg);
543: }
544: plugin_include(Pifa::getName(), $filename);
545: if (false === class_exists($optionClass)) {
546: $msg = Pifa::i18n('MISSING_EOD_CLASS');
547: $msg = sprintf($msg, $optionClass);
548: throw new PifaException($msg);
549: }
550: $dataSource = new $optionClass();
551: $optionLabels = $dataSource->getOptionLabels();
552: $optionValues = $dataSource->getOptionValues();
553: }
554:
555: // ID for field & FOR for label
556: $id = 'pifa-field-elm-' . $idfield;
557:
558: // get current value
559: $value = $this->getValue();
560:
561: // if no current value is given
562: if (NULL === $value) {
563: // the fields default value is used
564: $value = $this->get('default_value');
565: // which could be overwritten by a GET param
566: if (array_key_exists($columnName, $_GET)) {
567: $value = $_GET[$columnName];
568: // try to prevent XSS ... the lazy way ...
569: $value = htmlentities($value, ENT_COMPAT | ENT_HTML401, 'UTF-8');
570: }
571: }
572:
573: switch ($fieldType) {
574:
575: case self::INPUTTEXT:
576:
577: $elemField = new cHTMLTextbox($columnName);
578: // set ID (workaround: remove ID first!)
579: $elemField->removeAttribute('id')->setID($id);
580: // due to a bug setting NULL as title leads to title="title"
581: if (!is_null($this->get('default_value'))) {
582: $elemField->setAttribute('title', $this->get('default_value'));
583: }
584: if (!is_null($value)) {
585: $elemField->setValue($value);
586: }
587: break;
588:
589: case self::TEXTAREA:
590:
591: $elemField = new cHTMLTextarea($columnName);
592: // set ID (workaround: remove ID first!)
593: $elemField->removeAttribute('id')->setID($id);
594: if (!is_null($this->get('default_value'))) {
595: $elemField->setAttribute('title', $this->get('default_value'));
596: }
597: if (!is_null($value)) {
598: $elemField->setValue($value);
599: }
600: break;
601:
602: case self::INPUTPASSWORD:
603:
604: $elemField = new cHTMLPasswordbox($columnName);
605: // set ID (workaround: remove ID first!)
606: $elemField->removeAttribute('id')->setID($id);
607: if (!is_null($this->get('default_value'))) {
608: $elemField->setAttribute('title', $this->get('default_value'));
609: }
610: if (!is_null($value)) {
611: $elemField->setValue($value);
612: }
613: break;
614:
615: case self::INPUTRADIO:
616: case self::INPUTCHECKBOX:
617:
618: $count = min(array(
619: count($optionLabels),
620: count($optionValues)
621: ));
622: $tmpHtml = '';
623: for ($i = 0; $i < $count; $i++) {
624: if (self::INPUTRADIO === $fieldType) {
625: $elemField = new cHTMLRadiobutton($columnName, $optionValues[$i]);
626: } else if (self::INPUTCHECKBOX === $fieldType) {
627: $elemField = new cHTMLCheckbox($columnName . '[]', $optionValues[$i]);
628: }
629: if (!is_array($value)) {
630: $value = explode(',', $value);
631: }
632: $elemField->setChecked(in_array($optionValues[$i], $value));
633: $elemField->setLabelText($optionLabels[$i]);
634: $tmpHtml .= $elemField->render();
635: }
636: $elemField = new cHTMLSpan($tmpHtml);
637: break;
638:
639: case self::SELECT:
640: case self::SELECTMULTI:
641:
642: $elemField = new cHTMLSelectElement($columnName);
643:
644: // set ID (workaround: remove ID first!)
645: $elemField->removeAttribute('id')->setID($id);
646: $autofill = array();
647: $count = min(array(
648: count($optionLabels),
649: count($optionValues)
650: ));
651:
652: for ($i = 0; $i < $count; $i++) {
653: $autofill[$optionValues[$i]] = $optionLabels[$i];
654: }
655:
656: $elemField->autoFill($autofill);
657: $elemField->setDefault($value);
658:
659: break;
660:
661: case self::DATEPICKER:
662:
663: // hidden field to post date in generic date format
664: $hiddenField = new cHTMLHiddenField($columnName);
665: $hiddenField->removeAttribute('id')->setID($id . '-hidden');
666: if (!is_null($value)) {
667: $hiddenField->setValue($value);
668: }
669:
670: // textbox to display date in localized date format
671: $textbox = new cHTMLTextbox($columnName . '_visible');
672: // set ID (workaround: remove ID first!)
673: $textbox->removeAttribute('id')->setID($id);
674:
675: // surrounding div
676: $elemField = new cHTMLDiv(array(
677: $hiddenField,
678: $textbox
679: ));
680:
681: break;
682:
683: case self::INPUTFILE:
684:
685: $elemField = new cHTMLUpload($columnName);
686: // set ID (workaround: remove ID first!)
687: $elemField->removeAttribute('id')->setID($id);
688: break;
689:
690: case self::PROCESSBAR:
691:
692: $elemField = NULL;
693: // TODO PROCESSBAR is NYI
694: // $elemField = new cHTML();
695: break;
696:
697: case self::SLIDER:
698:
699: $elemField = NULL;
700: // TODO SLIDER is NYI
701: // $elemField = new cHTML();
702: break;
703:
704: // case self::CAPTCHA:
705:
706: // // input
707: // $elemField = new cHTMLTextbox($columnName);
708: // // set ID (workaround: remove ID first!)
709: // $elemField->removeAttribute('id')->setID($id);
710: // if (NULL !== $value) {
711: // $elemField->setValue($value);
712: // }
713:
714: // // surrounding div
715: // // img src (front_content.php?securimage) will be caught by
716: // // Pifa::afterLoadPlugins
717: // $elemField = new cHTMLDiv(array(
718: // new cHTMLImage('front_content.php?securimage'),
719: // $elemField
720: // ));
721:
722: // break;
723:
724: case self::BUTTONSUBMIT:
725: case self::BUTTONRESET:
726: case self::BUTTON:
727: case self::BUTTONIMAGE:
728: $modes = array(
729: self::BUTTONSUBMIT => 'submit',
730: self::BUTTONRESET => 'reset',
731: self::BUTTON => 'button',
732: self::BUTTONIMAGE => 'image'
733: );
734: $mode = $modes[$fieldType];
735: $elemField = new cHTMLButton($columnName);
736: // set ID (workaround: remove ID first!)
737: $elemField->removeAttribute('id')->setID($id);
738: $elemField->setMode($mode);
739: if ('image' === $mode) {
740: $elemField->setAttribute('src', $uri);
741: $elemField->removeAttribute('value');
742: } else {
743: // set label or mode as value
744: $elemField->setTitle($label? $label : $mode);
745: }
746: break;
747:
748: case self::MATRIX:
749:
750: $elemField = NULL;
751: // TODO MATRIX is NYI
752: // $elemField = new cHTML();
753: break;
754:
755: case self::PARA:
756: $elemField = NULL;
757: // TODO PARA is NYI
758: // $elemField = new cHTML();
759: break;
760:
761: case self::INPUTHIDDEN:
762: $elemField = new cHTMLHiddenField($columnName);
763: // set ID (workaround: remove ID first!)
764: $elemField->removeAttribute('id')->setID($id);
765: if (NULL !== $value) {
766: $elemField->setValue($value);
767: }
768: break;
769:
770: default:
771: $msg = Pifa::i18n('NOT_IMPLEMENTED_FIELDTYPE');
772: $msg = sprintf($msg, $fieldType);
773: throw new PifaNotImplementedException($msg);
774: }
775:
776: return $elemField;
777: }
778:
779: /**
780: *
781: * @return Ambigous <NULL, cHTMLParagraph>
782: * @todo should be private, right?
783: */
784: public function _getElemHelp() {
785: $helpText = $this->get('help_text');
786:
787: $p = NULL;
788: if (0 < strlen($helpText)) {
789: $p = new cHTMLParagraph($helpText, 'pifa-field-help');
790: }
791:
792: return $p;
793: }
794:
795: /**
796: *
797: * @return Ambigous <NULL, cHTMLScript>
798: * @todo should be private, right?
799: */
800: public function _getElemScript() {
801:
802: // ID for field & FOR for label
803: $idfield = cSecurity::toInteger($this->get('idfield'));
804: $fieldType = cSecurity::toInteger($this->get('field_type'));
805:
806: switch ($fieldType) {
807: case self::DATEPICKER:
808: $sel = '#pifa-field-elm-' . $idfield;
809: // dateFormat: 'yy-mm-dd', // could be different
810: // altFormat as ISO_8601
811: $script = "jQuery(function(){ jQuery('$sel').datepicker({
812: altFormat: 'yy-mm-dd',
813: altField: '$sel-hidden'
814: });});";
815: break;
816: // case self::CAPTCHA:
817: // $sel = '#pifa-field-' . $idfield . ' label';
818: // $newCaptchaCode = mi18n("NEW_CAPTCHA_CODE");
819: // $script = "jQuery(function(){\n";
820: // // implement captcha reload on click
821: // $script .= "jQuery('$sel').click(function (e) {\n";
822: // $script .= "e.preventDefault();\n";
823: // $script .= "var url = 'front_content.php?securimage&' +
824: // Math.random();\n";
825: // $script .= "jQuery(this).parent().find('img').attr('src',
826: // url);\n";
827: // $script .= "});\n";
828: // // append 'New Captcha Code' to label
829: // $script .= "jQuery('$sel').append('<br/><br/><span
830: // style=\"cursor:pointer\">$newCaptchaCode</span>');";
831: // $script .= "});\n";
832: // break;
833: default:
834: $script = '';
835: }
836:
837: $elemScript = NULL;
838: if (0 < strlen($script)) {
839: $elemScript = new cHTMLScript();
840: $elemScript->setContent($script);
841: }
842:
843: return $elemScript;
844: }
845:
846: /**
847: * Returns an array containing all field type ids.
848: *
849: * @return array
850: */
851: public static function getFieldTypeIds() {
852: return array_keys(self::getFieldTypeNames());
853: }
854:
855: /**
856: * Returns an array containing all field type ids mapped to their names.
857: *
858: * The order of field types in this array influences the order of icons
859: * displayed in the backend for selection!
860: *
861: * @return array
862: */
863: public static function getFieldTypeNames() {
864: return array(
865: self::INPUTTEXT => Pifa::i18n('INPUTTEXT'),
866: self::TEXTAREA => Pifa::i18n('TEXTAREA'),
867: self::INPUTPASSWORD => Pifa::i18n('INPUTPASSWORD'),
868: self::INPUTRADIO => Pifa::i18n('INPUTRADIO'),
869: self::INPUTCHECKBOX => Pifa::i18n('INPUTCHECKBOX'),
870: self::SELECT => Pifa::i18n('SELECT'),
871: self::SELECTMULTI => Pifa::i18n('SELECTMULTI'),
872: self::DATEPICKER => Pifa::i18n('DATEPICKER'),
873: self::INPUTFILE => Pifa::i18n('INPUTFILE'),
874: self::PROCESSBAR => Pifa::i18n('PROCESSBAR'),
875: self::SLIDER => Pifa::i18n('SLIDER'),
876: // self::CAPTCHA => Pifa::i18n('CAPTCHA'),
877: // self::MATRIX => Pifa::i18n('MATRIX'),
878: self::PARA => Pifa::i18n('PARAGRAPH'),
879: self::INPUTHIDDEN => Pifa::i18n('INPUTHIDDEN'),
880: self::FIELDSET_BEGIN => Pifa::i18n('FIELDSET_BEGIN'),
881: self::FIELDSET_END => Pifa::i18n('FIELDSET_END'),
882: self::BUTTONSUBMIT => Pifa::i18n('BUTTONSUBMIT'),
883: self::BUTTONRESET => Pifa::i18n('BUTTONRESET'),
884: self::BUTTON => Pifa::i18n('BUTTON'),
885: self::BUTTONIMAGE => Pifa::i18n('BUTTONIMAGE')
886: );
887: }
888:
889: /**
890: * Return the field type name for the given field type id.
891: *
892: * @param int $fieldType
893: * @return string
894: */
895: public static function getFieldTypeName($fieldTypeId) {
896: $fieldTypeId = cSecurity::toInteger($fieldTypeId);
897: $fieldTypeNames = self::getFieldTypeNames();
898:
899: if (array_key_exists($fieldTypeId, $fieldTypeNames)) {
900: $fieldTypeName = $fieldTypeNames[$fieldTypeId];
901: } else {
902: $fieldTypeName = Pifa::i18n('UNKNOWN');
903: }
904:
905: return $fieldTypeName;
906: }
907:
908: /**
909: * Return this fields type name.
910: *
911: * @param int $fieldType
912: * @return string
913: */
914: public function getMyFieldTypeName() {
915: return self::getFieldTypeName($this->get('field_type'));
916: }
917:
918: /**
919: * Returns a string describing this fields database data type as used in
920: * MySQL CREATE and ALTER TABLE statements.
921: *
922: * @throws PifaException if field is not loaded
923: * @throws PifaException if field type is not implemented
924: */
925: public function getDbDataType() {
926: if (!$this->isLoaded()) {
927: $msg = Pifa::i18n('FIELD_LOAD_ERROR');
928: throw new PifaException($msg);
929: }
930:
931: $fieldType = cSecurity::toInteger($this->get('field_type'));
932:
933: switch ($fieldType) {
934:
935: // Text and password input fields can store a string of
936: // arbitrary length. Cause they are single lined it does not
937: // make sense to enable them storing more than 1023 characters
938: // though.
939: case self::INPUTTEXT:
940: case self::INPUTPASSWORD:
941: case self::INPUTHIDDEN:
942: case self::INPUTFILE:
943:
944: return 'VARCHAR(' . self::VARCHAR_SIZE . ')';
945:
946: // A group of checkboxes can store multiple values. So this has
947: // to be implemented as a CSV string of max. 1023 characters.
948: case self::INPUTCHECKBOX:
949:
950: return 'VARCHAR(' . self::VARCHAR_SIZE . ')';
951:
952: // Textareas are designed to store longer texts so I chose the
953: // TEXT data type to enable them storing up to 65535 characters.
954: case self::TEXTAREA:
955:
956: return 'TEXT';
957:
958: // A group of radiobuttons or a select box can store a single
959: // value of a given set of options. This can be implemented
960: // as an enumeration.
961: case self::INPUTRADIO:
962: case self::SELECT:
963: case self::SELECTMULTI:
964:
965: // $optionValues = $this->get('option_values');
966: // $optionValues = explode(',', $optionValues);
967: // array_map(function ($val) {
968: // return "'$val'";
969: // }, $optionValues);
970: // $optionValues = join(',', $optionValues);
971:
972: // return "ENUM($optionValues)";
973:
974: // as long as the GUI does not offer an entry to define option
975: // values when creating a new field assume VARCHAR as data type
976: return 'VARCHAR(' . self::VARCHAR_SIZE . ')';
977:
978: // The datepicker can store a date having an optional time
979: // portion. I chose DATETIME as data type over TIMESTAMP due to
980: // its limitation of storing dates before 1970-01-01 although
981: // even DATETIME can't store dates before 1000-01-01.
982: case self::DATEPICKER:
983:
984: return 'DATETIME';
985:
986: // Buttons don't have any values that should be stored.
987: case self::BUTTONSUBMIT:
988: case self::BUTTONRESET:
989: case self::BUTTON:
990: case self::BUTTONIMAGE:
991:
992: return NULL;
993:
994: // TODO For some filed types I havn't yet decided which data
995: // type to use.
996: case self::PROCESSBAR:
997: case self::SLIDER:
998: // case self::CAPTCHA:
999: case self::MATRIX:
1000: case self::PARA:
1001: case self::FIELDSET_BEGIN:
1002: case self::FIELDSET_END:
1003:
1004: return NULL;
1005:
1006: default:
1007: $msg = Pifa::i18n('NOT_IMPLEMENTED_FIELDTYPE');
1008: $msg = sprintf($msg, $fieldType);
1009: // Util::logDump($this->values);
1010: throw new PifaException($msg);
1011: }
1012: }
1013:
1014: /**
1015: * Deletes this form with all its fields and stored data.
1016: * The forms data table is also dropped.
1017: */
1018: public function delete() {
1019: $cfg = cRegistry::getConfig();
1020: $db = cRegistry::getDb();
1021:
1022: if (!$this->isLoaded()) {
1023: $msg = Pifa::i18n('FIELD_LOAD_ERROR');
1024: throw new PifaException($msg);
1025: }
1026:
1027: // update ranks of younger siblings
1028: $sql = "-- PifaField->delete()
1029: UPDATE
1030: " . cRegistry::getDbTableName('pifa_field') . "
1031: SET
1032: field_rank = field_rank - 1
1033: WHERE
1034: idform = " . cSecurity::toInteger($this->get('idform')) . "
1035: AND field_rank > " . cSecurity::toInteger($this->get('field_rank')) . "
1036: ;";
1037: if (false === $db->query($sql)) {
1038: // false is returned if no fields were updated
1039: // but that doesn't matter ...
1040: }
1041:
1042: // delete field
1043: $sql = "-- PifaField->delete()
1044: DELETE FROM
1045: " . cRegistry::getDbTableName('pifa_field') . "
1046: WHERE
1047: idfield = " . cSecurity::toInteger($this->get('idfield')) . "
1048: ;";
1049: if (false === $db->query($sql)) {
1050: $msg = Pifa::i18n('FIELD_DELETE_ERROR');
1051: throw new PifaException($msg);
1052: }
1053:
1054: // drop column of data table
1055: if (0 < strlen(trim($this->get('column_name')))) {
1056: $pifaForm = new PifaForm($this->get('idform'));
1057:
1058: if (0 < strlen(trim($pifaForm->get('data_table')))) {
1059: $sql = "-- PifaField->delete()
1060: ALTER TABLE
1061: `" . cSecurity::toString($pifaForm->get('data_table')) . "`
1062: DROP COLUMN
1063: `" . cSecurity::toString($this->get('column_name')) . "`
1064: ;";
1065: if (false === $db->query($sql)) {
1066: $msg = Pifa::i18n('COLUMN_DROP_ERROR');
1067: throw new PifaException($msg);
1068: }
1069: }
1070: }
1071: }
1072:
1073: /**
1074: * Determines for which form field types which data should be editable in
1075: * backend.
1076: *
1077: * @param string $columnName for data to edit
1078: */
1079: public function showField($columnName) {
1080: $fieldType = $this->get('field_type');
1081: $fieldType = cSecurity::toInteger($fieldType);
1082:
1083: switch ($columnName) {
1084:
1085: case 'idfield':
1086: case 'idform':
1087: case 'field_rank':
1088: case 'field_type':
1089: // will never be editable directly
1090: return false;
1091:
1092: case 'column_name':
1093: return in_array($fieldType, array(
1094: self::INPUTTEXT,
1095: self::TEXTAREA,
1096: self::INPUTPASSWORD,
1097: self::INPUTRADIO,
1098: self::INPUTCHECKBOX,
1099: self::SELECT,
1100: self::SELECTMULTI,
1101: self::DATEPICKER,
1102: self::INPUTFILE,
1103: self::PROCESSBAR,
1104: self::SLIDER,
1105: // self::CAPTCHA,
1106: // self::BUTTONSUBMIT,
1107: // self::BUTTONRESET,
1108: // self::BUTTON,
1109: // self::BUTTONIMAGE,
1110: self::MATRIX,
1111: self::INPUTHIDDEN
1112: /*
1113: self::FIELDSET_BEGIN,
1114: self::FIELDSET_END
1115: */
1116: ));
1117:
1118: case 'label':
1119: case 'display_label':
1120: return in_array($fieldType, array(
1121: self::INPUTTEXT,
1122: self::TEXTAREA,
1123: self::INPUTPASSWORD,
1124: self::INPUTRADIO,
1125: self::INPUTCHECKBOX,
1126: self::SELECT,
1127: self::SELECTMULTI,
1128: self::DATEPICKER,
1129: self::INPUTFILE,
1130: self::PROCESSBAR,
1131: self::SLIDER,
1132: // self::CAPTCHA,
1133: self::BUTTONSUBMIT,
1134: self::BUTTONRESET,
1135: self::BUTTON,
1136: // self::BUTTONIMAGE,
1137: self::MATRIX,
1138: self::PARA,
1139: // self::INPUTHIDDEN,
1140: self::FIELDSET_BEGIN
1141: /*
1142: self::FIELDSET_END
1143: */
1144: ));
1145:
1146: case 'default_value':
1147: return in_array($fieldType, array(
1148: self::INPUTTEXT,
1149: self::TEXTAREA,
1150: self::INPUTPASSWORD,
1151: self::INPUTRADIO,
1152: self::INPUTCHECKBOX,
1153: self::SELECT,
1154: self::SELECTMULTI,
1155: self::DATEPICKER,
1156: self::INPUTFILE,
1157: self::PROCESSBAR,
1158: self::SLIDER,
1159: // self::CAPTCHA,
1160: self::BUTTONSUBMIT,
1161: self::BUTTONRESET,
1162: self::BUTTON,
1163: // self::BUTTONIMAGE,
1164: self::MATRIX,
1165: self::INPUTHIDDEN
1166: ));
1167:
1168: case 'option_labels':
1169: case 'option_values':
1170: case 'option_class':
1171: return in_array($fieldType, array(
1172: self::INPUTRADIO,
1173: self::INPUTCHECKBOX,
1174: self::SELECT,
1175: self::SELECTMULTI
1176: ));
1177:
1178: case 'help_text':
1179: return in_array($fieldType, array(
1180: self::INPUTTEXT,
1181: self::TEXTAREA,
1182: self::INPUTPASSWORD,
1183: self::INPUTRADIO,
1184: self::INPUTCHECKBOX,
1185: self::SELECT,
1186: self::SELECTMULTI,
1187: self::DATEPICKER,
1188: self::INPUTFILE,
1189: self::PROCESSBAR,
1190: self::SLIDER,
1191: // self::CAPTCHA,
1192: self::BUTTONSUBMIT,
1193: self::BUTTONRESET,
1194: self::BUTTON,
1195: self::BUTTONIMAGE,
1196: self::MATRIX,
1197: self::PARA,
1198: self::FIELDSET_BEGIN
1199: ));
1200:
1201: case 'obligatory':
1202: return in_array($fieldType, array(
1203: self::INPUTTEXT,
1204: self::TEXTAREA,
1205: self::INPUTPASSWORD,
1206: self::INPUTRADIO,
1207: self::INPUTCHECKBOX,
1208: self::SELECT,
1209: self::SELECTMULTI,
1210: self::DATEPICKER,
1211: self::INPUTFILE,
1212: self::PROCESSBAR,
1213: self::SLIDER,
1214: // self::CAPTCHA,
1215: // self::BUTTONSUBMIT,
1216: // self::BUTTONRESET,
1217: // self::BUTTON,
1218: // self::BUTTONIMAGE,
1219: self::MATRIX,
1220: self::INPUTHIDDEN
1221: ));
1222:
1223: case 'rule':
1224: return in_array($fieldType, array(
1225: self::INPUTTEXT,
1226: self::TEXTAREA,
1227: self::INPUTPASSWORD,
1228: self::INPUTRADIO,
1229: self::INPUTCHECKBOX,
1230: self::SELECT,
1231: self::SELECTMULTI,
1232: self::DATEPICKER,
1233: self::INPUTFILE,
1234: self::PROCESSBAR,
1235: self::SLIDER,
1236: // self::CAPTCHA,
1237: // self::BUTTONSUBMIT,
1238: // self::BUTTONRESET,
1239: // self::BUTTON,
1240: // self::BUTTONIMAGE,
1241: self::MATRIX,
1242: self::INPUTHIDDEN
1243: ));
1244:
1245: case 'error_message':
1246: return in_array($fieldType, array(
1247: self::INPUTTEXT,
1248: self::TEXTAREA,
1249: self::INPUTPASSWORD,
1250: self::INPUTRADIO,
1251: self::INPUTCHECKBOX,
1252: self::SELECT,
1253: self::SELECTMULTI,
1254: self::DATEPICKER,
1255: self::INPUTFILE,
1256: self::PROCESSBAR,
1257: self::SLIDER,
1258: // self::CAPTCHA,
1259: // self::BUTTONSUBMIT,
1260: // self::BUTTONRESET,
1261: // self::BUTTON,
1262: // self::BUTTONIMAGE,
1263: self::MATRIX,
1264: self::INPUTHIDDEN
1265: ));
1266:
1267: case 'css_class':
1268: return in_array($fieldType, array(
1269: self::INPUTTEXT,
1270: self::TEXTAREA,
1271: self::INPUTPASSWORD,
1272: self::INPUTRADIO,
1273: self::INPUTCHECKBOX,
1274: self::SELECT,
1275: self::SELECTMULTI,
1276: self::DATEPICKER,
1277: self::INPUTFILE,
1278: self::PROCESSBAR,
1279: self::SLIDER,
1280: // self::CAPTCHA,
1281: self::BUTTONSUBMIT,
1282: self::BUTTONRESET,
1283: self::BUTTON,
1284: self::BUTTONIMAGE,
1285: self::MATRIX,
1286: self::PARA,
1287: self::FIELDSET_BEGIN
1288: /*
1289: self::INPUTHIDDEN
1290: */
1291: ));
1292:
1293: case 'uri':
1294: return in_array($fieldType, array(
1295: /*
1296: self::INPUTTEXT,
1297: self::TEXTAREA,
1298: self::INPUTPASSWORD,
1299: self::INPUTRADIO,
1300: self::INPUTCHECKBOX,
1301: self::SELECT,
1302: self::SELECTMULTI,
1303: self::DATEPICKER,
1304: self::INPUTFILE,
1305: self::PROCESSBAR,
1306: self::SLIDER,
1307: // self::CAPTCHA,
1308: self::BUTTONSUBMIT,
1309: self::BUTTONRESET,
1310: self::BUTTON,
1311: */
1312: self::BUTTONIMAGE,
1313: /*
1314: self::MATRIX,
1315: self::PARA,
1316: self::FIELDSET_BEGIN
1317: self::INPUTHIDDEN
1318: */
1319: ));
1320:
1321: default:
1322: $msg = Pifa::i18n('NOT_IMPLEMENTED_FIELDPROP');
1323: $msg = sprintf($msg, $columnName);
1324: throw new PifaException($msg);
1325: }
1326: }
1327:
1328: /**
1329: *
1330: * @return array
1331: */
1332: public function getOptions() {
1333: $option_labels = $this->get('option_labels');
1334: $option_values = $this->get('option_values');
1335:
1336: $out = array();
1337: if (0 < strlen($option_labels . $option_values)) {
1338: $option_labels = explode(',', $option_labels);
1339: $option_values = explode(',', $option_values);
1340:
1341: // str_getcsv requires PHP 5.3 :(
1342: // $option_labels = str_getcsv($option_labels);
1343: // $option_values = str_getcsv($option_values);
1344:
1345: // instead replace commas stored as entities by real commas
1346: $func = create_function('$v', 'return str_replace(\',\', \',\', $v);');
1347: $option_labels = array_map($func, $option_labels);
1348: $option_values = array_map($func, $option_values);
1349:
1350: foreach (array_keys($option_labels) as $key) {
1351: $out[] = array(
1352: 'label' => $option_labels[$key],
1353: 'value' => $option_values[$key]
1354: );
1355: }
1356: }
1357:
1358: return $out;
1359: }
1360: }
1361:
1362: ?>
1363: