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 (isset($errors[$this->get('idfield')])) {
412: $error = $errors[$this->get('idfield')];
413: if (0 < strlen($error)) {
414: $content[] = new cHTMLParagraph($error, 'pifa-error-message');
415: }
416: }
417: } catch (PifaNotImplementedException $e) {
418: return NULL; // PASS // warning?
419: }
420:
421: $content = array_filter($content);
422: if (empty($content)) {
423: return NULL; // PASS // warning?
424: }
425:
426: // CSS class for surrounding division
427: $class = 'pifa-field-' . $this->get('field_type');
428: // optional class for field
429: if (0 < strlen(trim($this->get('css_class')))) {
430: $class .= ' ' . implode(' ', explode(',', $this->get('css_class')));
431: }
432: // optional class for obligatory field
433: if (true === (bool) $this->get('obligatory')) {
434: $class .= ' pifa-obligatory';
435: }
436: // optional error class for field
437: if (NULL !== $error) {
438: $class .= ' pifa-error';
439: }
440:
441: // ID for surrounding division
442: $id = 'pifa-field-' . $this->get('idfield');
443:
444: // surrounding division
445: $div = new cHTMLDiv($content, $class, $id);
446:
447: return "\n\t" . $div->render();
448: break;
449: }
450: }
451:
452: /**
453: *
454: * @return cHTMLLabel
455: * @todo should be private, right?
456: */
457: public function _getElemLabel() {
458: if (1 !== cSecurity::toInteger($this->get('display_label'))) {
459: return '';
460: }
461:
462: // get field data
463: $idfield = cSecurity::toInteger($this->get('idfield'));
464: $fieldType = cSecurity::toInteger($this->get('field_type'));
465: $label = $this->get('label');
466:
467: if (NULL === $label) {
468: return NULL;
469: }
470:
471: // buttons have no external label
472: // the label is instead used as element value (see _getElemField())
473: if (in_array($fieldType, array(
474: self::BUTTONSUBMIT,
475: self::BUTTONRESET,
476: self::BUTTON,
477: self::BUTTONIMAGE
478: ))) {
479: return NULL;
480: }
481:
482: // obligatory fields have an additional ' *'
483: if (true === (bool) $this->get('obligatory')) {
484: $label .= ' *';
485: }
486:
487: // add span to request new captcha code
488: // if (self::CAPTCHA===$fieldType) {
489: // $label .= '<br><br><span style="cursor: pointer;">New Captcha
490: // Code</span>';
491: // }
492:
493: $elemLabel = new cHTMLLabel($label, 'pifa-field-elm-' . $idfield, 'pifa-field-lbl');
494: if (self::INPUTRADIO === $fieldType) {
495: $elemLabel->removeAttribute('for');
496: }
497: // set ID (workaround: remove ID first!)
498: $elemLabel->removeAttribute('id');
499:
500: return $elemLabel;
501: }
502:
503: /**
504: * Creates the HTML for a form field.
505: *
506: * The displayed value of a form field can be set via a GET param whose name
507: * has to be the fields column name which is set if the form is displayed
508: * for the first time and user hasn't entered another value.
509: *
510: * @param bool $error if field elem should be displayed as erroneous
511: * @throws PifaNotImplementedException if field type is not implemented
512: * @return cHTMLTextbox cHTMLTextarea cHTMLPasswordbox cHTMLSpan
513: * cHTMLSelectElement NULL cHTMLButton
514: * @todo should be private, right?
515: */
516: public function _getElemField() {
517:
518: // get field data
519: $idfield = cSecurity::toInteger($this->get('idfield'));
520:
521: $fieldType = cSecurity::toInteger($this->get('field_type'));
522:
523: $columnName = $this->get('column_name');
524:
525: $label = $this->get('label');
526:
527: $uri = $this->get('uri');
528:
529: // get option labels & values
530: // either from field or from external data source class
531: $optionClass = $this->get('option_class');
532: if (0 === strlen(trim($optionClass))) {
533: $optionLabels = $this->get('option_labels');
534: if (NULL !== $optionLabels) {
535: $optionLabels = explode(',', $optionLabels);
536: }
537: $optionValues = $this->get('option_values');
538: if (NULL !== $optionValues) {
539: $optionValues = explode(',', $optionValues);
540: }
541: } else {
542: $filename = Pifa::fromCamelCase($optionClass);
543: $filename = "extensions/class.pifa.$filename.php";
544: if (false === file_exists(Pifa::getPath() . $filename)) {
545: $msg = Pifa::i18n('MISSING_EOD_FILE');
546: $msg = sprintf($msg, $filename);
547: throw new PifaException($msg);
548: }
549: plugin_include(Pifa::getName(), $filename);
550: if (false === class_exists($optionClass)) {
551: $msg = Pifa::i18n('MISSING_EOD_CLASS');
552: $msg = sprintf($msg, $optionClass);
553: throw new PifaException($msg);
554: }
555: $dataSource = new $optionClass();
556: $optionLabels = $dataSource->getOptionLabels();
557: $optionValues = $dataSource->getOptionValues();
558: }
559:
560: // ID for field & FOR for label
561: $id = 'pifa-field-elm-' . $idfield;
562:
563: // get current value
564: $value = $this->getValue();
565:
566: // if no current value is given
567: if (NULL === $value) {
568: // the fields default value is used
569: $value = $this->get('default_value');
570: // which could be overwritten by a GET param
571: if (array_key_exists($columnName, $_GET)) {
572: $value = $_GET[$columnName];
573: // try to prevent XSS ... the lazy way ...
574: $value = htmlentities($value, ENT_COMPAT | ENT_HTML401, 'UTF-8');
575: }
576: }
577:
578: switch ($fieldType) {
579:
580: case self::INPUTTEXT:
581:
582: $elemField = new cHTMLTextbox($columnName);
583: // set ID (workaround: remove ID first!)
584: $elemField->removeAttribute('id')->setID($id);
585: // due to a bug setting NULL as title leads to title="title"
586: if (!is_null($this->get('default_value'))) {
587: $elemField->setAttribute('title', $this->get('default_value'));
588: }
589: if (!is_null($value)) {
590: $elemField->setValue($value);
591: }
592: break;
593:
594: case self::TEXTAREA:
595:
596: $elemField = new cHTMLTextarea($columnName);
597: // set ID (workaround: remove ID first!)
598: $elemField->removeAttribute('id')->setID($id);
599: if (!is_null($this->get('default_value'))) {
600: $elemField->setAttribute('title', $this->get('default_value'));
601: }
602: if (!is_null($value)) {
603: $elemField->setValue($value);
604: }
605: break;
606:
607: case self::INPUTPASSWORD:
608:
609: $elemField = new cHTMLPasswordbox($columnName);
610: // set ID (workaround: remove ID first!)
611: $elemField->removeAttribute('id')->setID($id);
612: if (!is_null($this->get('default_value'))) {
613: $elemField->setAttribute('title', $this->get('default_value'));
614: }
615: if (!is_null($value)) {
616: $elemField->setValue($value);
617: }
618: break;
619:
620: case self::INPUTRADIO:
621: case self::INPUTCHECKBOX:
622:
623: $count = min(array(
624: count($optionLabels),
625: count($optionValues)
626: ));
627: $tmpHtml = '';
628: for ($i = 0; $i < $count; $i++) {
629: if (self::INPUTRADIO === $fieldType) {
630: $elemField = new cHTMLRadiobutton($columnName, $optionValues[$i]);
631: } else if (self::INPUTCHECKBOX === $fieldType) {
632: $elemField = new cHTMLCheckbox($columnName . '[]', $optionValues[$i]);
633: }
634: if (!is_array($value)) {
635: $value = explode(',', $value);
636: }
637: $elemField->setChecked(in_array($optionValues[$i], $value));
638: $elemField->setLabelText($optionLabels[$i]);
639: $tmpHtml .= $elemField->render();
640: }
641: $elemField = new cHTMLSpan($tmpHtml);
642: break;
643:
644: case self::SELECT:
645: case self::SELECTMULTI:
646:
647: $elemField = new cHTMLSelectElement($columnName);
648:
649: // set ID (workaround: remove ID first!)
650: $elemField->removeAttribute('id')->setID($id);
651: $autofill = array();
652: $count = min(array(
653: count($optionLabels),
654: count($optionValues)
655: ));
656:
657: for ($i = 0; $i < $count; $i++) {
658: $autofill[$optionValues[$i]] = $optionLabels[$i];
659: }
660:
661: $elemField->autoFill($autofill);
662: $elemField->setDefault($value);
663:
664: break;
665:
666: case self::DATEPICKER:
667:
668: // hidden field to post date in generic date format
669: $hiddenField = new cHTMLHiddenField($columnName);
670: $hiddenField->removeAttribute('id')->setID($id . '-hidden');
671: if (!is_null($value)) {
672: $hiddenField->setValue($value);
673: }
674:
675: // textbox to display date in localized date format
676: $textbox = new cHTMLTextbox($columnName . '_visible');
677: // set ID (workaround: remove ID first!)
678: $textbox->removeAttribute('id')->setID($id);
679:
680: // surrounding div
681: $elemField = new cHTMLDiv(array(
682: $hiddenField,
683: $textbox
684: ));
685:
686: break;
687:
688: case self::INPUTFILE:
689:
690: $elemField = new cHTMLUpload($columnName);
691: // set ID (workaround: remove ID first!)
692: $elemField->removeAttribute('id')->setID($id);
693: break;
694:
695: case self::PROCESSBAR:
696:
697: $elemField = NULL;
698: // TODO PROCESSBAR is NYI
699: // $elemField = new cHTML();
700: break;
701:
702: case self::SLIDER:
703:
704: $elemField = NULL;
705: // TODO SLIDER is NYI
706: // $elemField = new cHTML();
707: break;
708:
709: // case self::CAPTCHA:
710:
711: // // input
712: // $elemField = new cHTMLTextbox($columnName);
713: // // set ID (workaround: remove ID first!)
714: // $elemField->removeAttribute('id')->setID($id);
715: // if (NULL !== $value) {
716: // $elemField->setValue($value);
717: // }
718:
719: // // surrounding div
720: // // img src (front_content.php?securimage)
721: // // will be caught by Pifa::afterLoadPlugins
722: // $img = new cHTMLImage('front_content.php?securimage');
723: // $img->setAttribute('alt', 'captcha');
724: // $elemField = new cHTMLDiv(array($img, $elemField));
725:
726: // break;
727:
728: case self::BUTTONSUBMIT:
729: case self::BUTTONRESET:
730: case self::BUTTON:
731: case self::BUTTONIMAGE:
732: $modes = array(
733: self::BUTTONSUBMIT => 'submit',
734: self::BUTTONRESET => 'reset',
735: self::BUTTON => 'button',
736: self::BUTTONIMAGE => 'image'
737: );
738: $mode = $modes[$fieldType];
739: $elemField = new cHTMLButton($columnName);
740: // set ID (workaround: remove ID first!)
741: $elemField->removeAttribute('id')->setID($id);
742: $elemField->setMode($mode);
743: if ('image' === $mode) {
744: $elemField->setAttribute('src', $uri);
745: $elemField->removeAttribute('value');
746: } else {
747: // set label or mode as value
748: $elemField->setTitle($label? $label : $mode);
749: }
750: break;
751:
752: case self::MATRIX:
753:
754: $elemField = NULL;
755: // TODO MATRIX is NYI
756: // $elemField = new cHTML();
757: break;
758:
759: case self::PARA:
760: $elemField = NULL;
761: // TODO PARA is NYI
762: // $elemField = new cHTML();
763: break;
764:
765: case self::INPUTHIDDEN:
766: $elemField = new cHTMLHiddenField($columnName);
767: // set ID (workaround: remove ID first!)
768: $elemField->removeAttribute('id')->setID($id);
769: if (NULL !== $value) {
770: $elemField->setValue($value);
771: }
772: break;
773:
774: default:
775: $msg = Pifa::i18n('NOT_IMPLEMENTED_FIELDTYPE');
776: $msg = sprintf($msg, $fieldType);
777: throw new PifaNotImplementedException($msg);
778: }
779:
780: return $elemField;
781: }
782:
783: /**
784: *
785: * @return Ambigous <NULL, cHTMLParagraph>
786: * @todo should be private, right?
787: */
788: public function _getElemHelp() {
789: $helpText = $this->get('help_text');
790:
791: $p = NULL;
792: if (0 < strlen($helpText)) {
793: $p = new cHTMLParagraph($helpText, 'pifa-field-help');
794: }
795:
796: return $p;
797: }
798:
799: /**
800: *
801: * @return Ambigous <NULL, cHTMLScript>
802: * @todo should be private, right?
803: */
804: public function _getElemScript() {
805:
806: // ID for field & FOR for label
807: $idfield = cSecurity::toInteger($this->get('idfield'));
808: $fieldType = cSecurity::toInteger($this->get('field_type'));
809:
810: switch ($fieldType) {
811: case self::DATEPICKER:
812: $sel = '#pifa-field-elm-' . $idfield;
813: // dateFormat: 'yy-mm-dd', // could be different
814: // altFormat as ISO_8601
815: $script = "jQuery(function(){ jQuery('$sel').datepicker({
816: altFormat: 'yy-mm-dd',
817: altField: '$sel-hidden'
818: });});";
819: break;
820: // case self::CAPTCHA:
821: // $sel = '#pifa-field-' . $idfield . ' label';
822: // $newCaptchaCode = mi18n("NEW_CAPTCHA_CODE");
823: // $script = "jQuery(function(){\n";
824: // // implement captcha reload on click
825: // $script .= "jQuery('$sel').click(function (e) {\n";
826: // $script .= "e.preventDefault();\n";
827: // $script .= "var url = 'front_content.php?securimage&' +
828: // Math.random();\n";
829: // $script .= "jQuery(this).parent().find('img').attr('src',
830: // url);\n";
831: // $script .= "});\n";
832: // // append 'New Captcha Code' to label
833: // $script .= "jQuery('$sel').append('<br/><br/><span
834: // style=\"cursor:pointer\">$newCaptchaCode</span>');";
835: // $script .= "});\n";
836: // break;
837: default:
838: $script = '';
839: }
840:
841: $elemScript = NULL;
842: if (0 < strlen($script)) {
843: $elemScript = new cHTMLScript();
844: $elemScript->setContent($script);
845: }
846:
847: return $elemScript;
848: }
849:
850: /**
851: * Returns an array containing all field type ids.
852: *
853: * @return array
854: */
855: public static function getFieldTypeIds() {
856: return array_keys(self::getFieldTypeNames());
857: }
858:
859: /**
860: * Returns an array containing all field type ids mapped to their names.
861: *
862: * The order of field types in this array influences the order of icons
863: * displayed in the backend for selection!
864: *
865: * @return array
866: */
867: public static function getFieldTypeNames() {
868: return array(
869: self::INPUTTEXT => Pifa::i18n('INPUTTEXT'),
870: self::TEXTAREA => Pifa::i18n('TEXTAREA'),
871: self::INPUTPASSWORD => Pifa::i18n('INPUTPASSWORD'),
872: self::INPUTRADIO => Pifa::i18n('INPUTRADIO'),
873: self::INPUTCHECKBOX => Pifa::i18n('INPUTCHECKBOX'),
874: self::SELECT => Pifa::i18n('SELECT'),
875: self::SELECTMULTI => Pifa::i18n('SELECTMULTI'),
876: self::DATEPICKER => Pifa::i18n('DATEPICKER'),
877: self::INPUTFILE => Pifa::i18n('INPUTFILE'),
878: self::PROCESSBAR => Pifa::i18n('PROCESSBAR'),
879: self::SLIDER => Pifa::i18n('SLIDER'),
880: // self::CAPTCHA => Pifa::i18n('CAPTCHA'),
881: // self::MATRIX => Pifa::i18n('MATRIX'),
882: self::PARA => Pifa::i18n('PARAGRAPH'),
883: self::INPUTHIDDEN => Pifa::i18n('INPUTHIDDEN'),
884: self::FIELDSET_BEGIN => Pifa::i18n('FIELDSET_BEGIN'),
885: self::FIELDSET_END => Pifa::i18n('FIELDSET_END'),
886: self::BUTTONSUBMIT => Pifa::i18n('BUTTONSUBMIT'),
887: self::BUTTONRESET => Pifa::i18n('BUTTONRESET'),
888: self::BUTTON => Pifa::i18n('BUTTON'),
889: self::BUTTONIMAGE => Pifa::i18n('BUTTONIMAGE')
890: );
891: }
892:
893: /**
894: * Return the field type name for the given field type id.
895: *
896: * @param int $fieldType
897: * @return string
898: */
899: public static function getFieldTypeName($fieldTypeId) {
900: $fieldTypeId = cSecurity::toInteger($fieldTypeId);
901: $fieldTypeNames = self::getFieldTypeNames();
902:
903: if (array_key_exists($fieldTypeId, $fieldTypeNames)) {
904: $fieldTypeName = $fieldTypeNames[$fieldTypeId];
905: } else {
906: $fieldTypeName = Pifa::i18n('UNKNOWN');
907: }
908:
909: return $fieldTypeName;
910: }
911:
912: /**
913: * Return this fields type name.
914: *
915: * @param int $fieldType
916: * @return string
917: */
918: public function getMyFieldTypeName() {
919: return self::getFieldTypeName($this->get('field_type'));
920: }
921:
922: /**
923: * Returns a string describing this fields database data type as used in
924: * MySQL CREATE and ALTER TABLE statements.
925: *
926: * @throws PifaException if field is not loaded
927: * @throws PifaException if field type is not implemented
928: */
929: public function getDbDataType() {
930: if (!$this->isLoaded()) {
931: $msg = Pifa::i18n('FIELD_LOAD_ERROR');
932: throw new PifaException($msg);
933: }
934:
935: $fieldType = cSecurity::toInteger($this->get('field_type'));
936:
937: switch ($fieldType) {
938:
939: // Text and password input fields can store a string of
940: // arbitrary length. Cause they are single lined it does not
941: // make sense to enable them storing more than 1023 characters
942: // though.
943: case self::INPUTTEXT:
944: case self::INPUTPASSWORD:
945: case self::INPUTHIDDEN:
946: case self::INPUTFILE:
947:
948: return 'VARCHAR(' . self::VARCHAR_SIZE . ')';
949:
950: // A group of checkboxes can store multiple values. So this has
951: // to be implemented as a CSV string of max. 1023 characters.
952: case self::INPUTCHECKBOX:
953:
954: return 'VARCHAR(' . self::VARCHAR_SIZE . ')';
955:
956: // Textareas are designed to store longer texts so I chose the
957: // TEXT data type to enable them storing up to 65535 characters.
958: case self::TEXTAREA:
959:
960: return 'TEXT';
961:
962: // A group of radiobuttons or a select box can store a single
963: // value of a given set of options. This can be implemented
964: // as an enumeration.
965: case self::INPUTRADIO:
966: case self::SELECT:
967: case self::SELECTMULTI:
968:
969: // $optionValues = $this->get('option_values');
970: // $optionValues = explode(',', $optionValues);
971: // array_map(function ($val) {
972: // return "'$val'";
973: // }, $optionValues);
974: // $optionValues = join(',', $optionValues);
975:
976: // return "ENUM($optionValues)";
977:
978: // as long as the GUI does not offer an entry to define option
979: // values when creating a new field assume VARCHAR as data type
980: return 'VARCHAR(' . self::VARCHAR_SIZE . ')';
981:
982: // The datepicker can store a date having an optional time
983: // portion. I chose DATETIME as data type over TIMESTAMP due to
984: // its limitation of storing dates before 1970-01-01 although
985: // even DATETIME can't store dates before 1000-01-01.
986: case self::DATEPICKER:
987:
988: return 'DATETIME';
989:
990: // Buttons don't have any values that should be stored.
991: case self::BUTTONSUBMIT:
992: case self::BUTTONRESET:
993: case self::BUTTON:
994: case self::BUTTONIMAGE:
995:
996: return NULL;
997:
998: // TODO For some filed types I havn't yet decided which data
999: // type to use.
1000: case self::PROCESSBAR:
1001: case self::SLIDER:
1002: // case self::CAPTCHA:
1003: case self::MATRIX:
1004: case self::PARA:
1005: case self::FIELDSET_BEGIN:
1006: case self::FIELDSET_END:
1007:
1008: return NULL;
1009:
1010: default:
1011: $msg = Pifa::i18n('NOT_IMPLEMENTED_FIELDTYPE');
1012: $msg = sprintf($msg, $fieldType);
1013: // Util::logDump($this->values);
1014: throw new PifaException($msg);
1015: }
1016: }
1017:
1018: /**
1019: * Deletes this form with all its fields and stored data.
1020: * The forms data table is also dropped.
1021: */
1022: public function delete() {
1023: $cfg = cRegistry::getConfig();
1024: $db = cRegistry::getDb();
1025:
1026: if (!$this->isLoaded()) {
1027: $msg = Pifa::i18n('FIELD_LOAD_ERROR');
1028: throw new PifaException($msg);
1029: }
1030:
1031: // update ranks of younger siblings
1032: $sql = "-- PifaField->delete()
1033: UPDATE
1034: " . cRegistry::getDbTableName('pifa_field') . "
1035: SET
1036: field_rank = field_rank - 1
1037: WHERE
1038: idform = " . cSecurity::toInteger($this->get('idform')) . "
1039: AND field_rank > " . cSecurity::toInteger($this->get('field_rank')) . "
1040: ;";
1041: if (false === $db->query($sql)) {
1042: // false is returned if no fields were updated
1043: // but that doesn't matter ...
1044: }
1045:
1046: // delete field
1047: $sql = "-- PifaField->delete()
1048: DELETE FROM
1049: " . cRegistry::getDbTableName('pifa_field') . "
1050: WHERE
1051: idfield = " . cSecurity::toInteger($this->get('idfield')) . "
1052: ;";
1053: if (false === $db->query($sql)) {
1054: $msg = Pifa::i18n('FIELD_DELETE_ERROR');
1055: throw new PifaException($msg);
1056: }
1057:
1058: // drop column of data table
1059: if (0 < strlen(trim($this->get('column_name')))) {
1060: $pifaForm = new PifaForm($this->get('idform'));
1061:
1062: if (0 < strlen(trim($pifaForm->get('data_table')))) {
1063: $sql = "-- PifaField->delete()
1064: ALTER TABLE
1065: `" . cSecurity::toString($pifaForm->get('data_table')) . "`
1066: DROP COLUMN
1067: `" . cSecurity::toString($this->get('column_name')) . "`
1068: ;";
1069: if (false === $db->query($sql)) {
1070: $msg = Pifa::i18n('COLUMN_DROP_ERROR');
1071: throw new PifaException($msg);
1072: }
1073: }
1074: }
1075: }
1076:
1077: /**
1078: * Determines for which form field types which data should be editable in
1079: * backend.
1080: *
1081: * @param string $columnName for data to edit
1082: */
1083: public function showField($columnName) {
1084: $fieldType = $this->get('field_type');
1085: $fieldType = cSecurity::toInteger($fieldType);
1086:
1087: switch ($columnName) {
1088:
1089: case 'idfield':
1090: case 'idform':
1091: case 'field_rank':
1092: case 'field_type':
1093: // will never be editable directly
1094: return false;
1095:
1096: case 'column_name':
1097: return in_array($fieldType, array(
1098: self::INPUTTEXT,
1099: self::TEXTAREA,
1100: self::INPUTPASSWORD,
1101: self::INPUTRADIO,
1102: self::INPUTCHECKBOX,
1103: self::SELECT,
1104: self::SELECTMULTI,
1105: self::DATEPICKER,
1106: self::INPUTFILE,
1107: self::PROCESSBAR,
1108: self::SLIDER,
1109: // self::CAPTCHA,
1110: // self::BUTTONSUBMIT,
1111: // self::BUTTONRESET,
1112: // self::BUTTON,
1113: // self::BUTTONIMAGE,
1114: self::MATRIX,
1115: self::INPUTHIDDEN
1116: /*
1117: self::FIELDSET_BEGIN,
1118: self::FIELDSET_END
1119: */
1120: ));
1121:
1122: case 'label':
1123: case 'display_label':
1124: return in_array($fieldType, array(
1125: self::INPUTTEXT,
1126: self::TEXTAREA,
1127: self::INPUTPASSWORD,
1128: self::INPUTRADIO,
1129: self::INPUTCHECKBOX,
1130: self::SELECT,
1131: self::SELECTMULTI,
1132: self::DATEPICKER,
1133: self::INPUTFILE,
1134: self::PROCESSBAR,
1135: self::SLIDER,
1136: // self::CAPTCHA,
1137: self::BUTTONSUBMIT,
1138: self::BUTTONRESET,
1139: self::BUTTON,
1140: // self::BUTTONIMAGE,
1141: self::MATRIX,
1142: self::PARA,
1143: // self::INPUTHIDDEN,
1144: self::FIELDSET_BEGIN
1145: /*
1146: self::FIELDSET_END
1147: */
1148: ));
1149:
1150: case 'default_value':
1151: return in_array($fieldType, array(
1152: self::INPUTTEXT,
1153: self::TEXTAREA,
1154: self::INPUTPASSWORD,
1155: self::INPUTRADIO,
1156: self::INPUTCHECKBOX,
1157: self::SELECT,
1158: self::SELECTMULTI,
1159: self::DATEPICKER,
1160: self::INPUTFILE,
1161: self::PROCESSBAR,
1162: self::SLIDER,
1163: // self::CAPTCHA,
1164: self::BUTTONSUBMIT,
1165: self::BUTTONRESET,
1166: self::BUTTON,
1167: // self::BUTTONIMAGE,
1168: self::MATRIX,
1169: self::INPUTHIDDEN
1170: ));
1171:
1172: case 'option_labels':
1173: case 'option_values':
1174: case 'option_class':
1175: return in_array($fieldType, array(
1176: self::INPUTRADIO,
1177: self::INPUTCHECKBOX,
1178: self::SELECT,
1179: self::SELECTMULTI
1180: ));
1181:
1182: case 'help_text':
1183: return in_array($fieldType, array(
1184: self::INPUTTEXT,
1185: self::TEXTAREA,
1186: self::INPUTPASSWORD,
1187: self::INPUTRADIO,
1188: self::INPUTCHECKBOX,
1189: self::SELECT,
1190: self::SELECTMULTI,
1191: self::DATEPICKER,
1192: self::INPUTFILE,
1193: self::PROCESSBAR,
1194: self::SLIDER,
1195: // self::CAPTCHA,
1196: self::BUTTONSUBMIT,
1197: self::BUTTONRESET,
1198: self::BUTTON,
1199: self::BUTTONIMAGE,
1200: self::MATRIX,
1201: self::PARA,
1202: self::FIELDSET_BEGIN
1203: ));
1204:
1205: case 'obligatory':
1206: return in_array($fieldType, array(
1207: self::INPUTTEXT,
1208: self::TEXTAREA,
1209: self::INPUTPASSWORD,
1210: self::INPUTRADIO,
1211: self::INPUTCHECKBOX,
1212: self::SELECT,
1213: self::SELECTMULTI,
1214: self::DATEPICKER,
1215: self::INPUTFILE,
1216: self::PROCESSBAR,
1217: self::SLIDER,
1218: // self::CAPTCHA,
1219: // self::BUTTONSUBMIT,
1220: // self::BUTTONRESET,
1221: // self::BUTTON,
1222: // self::BUTTONIMAGE,
1223: self::MATRIX,
1224: self::INPUTHIDDEN
1225: ));
1226:
1227: case 'rule':
1228: return in_array($fieldType, array(
1229: self::INPUTTEXT,
1230: self::TEXTAREA,
1231: self::INPUTPASSWORD,
1232: self::INPUTRADIO,
1233: self::INPUTCHECKBOX,
1234: self::SELECT,
1235: self::SELECTMULTI,
1236: self::DATEPICKER,
1237: self::INPUTFILE,
1238: self::PROCESSBAR,
1239: self::SLIDER,
1240: // self::CAPTCHA,
1241: // self::BUTTONSUBMIT,
1242: // self::BUTTONRESET,
1243: // self::BUTTON,
1244: // self::BUTTONIMAGE,
1245: self::MATRIX,
1246: self::INPUTHIDDEN
1247: ));
1248:
1249: case 'error_message':
1250: return in_array($fieldType, array(
1251: self::INPUTTEXT,
1252: self::TEXTAREA,
1253: self::INPUTPASSWORD,
1254: self::INPUTRADIO,
1255: self::INPUTCHECKBOX,
1256: self::SELECT,
1257: self::SELECTMULTI,
1258: self::DATEPICKER,
1259: self::INPUTFILE,
1260: self::PROCESSBAR,
1261: self::SLIDER,
1262: // self::CAPTCHA,
1263: // self::BUTTONSUBMIT,
1264: // self::BUTTONRESET,
1265: // self::BUTTON,
1266: // self::BUTTONIMAGE,
1267: self::MATRIX,
1268: self::INPUTHIDDEN
1269: ));
1270:
1271: case 'css_class':
1272: return in_array($fieldType, array(
1273: self::INPUTTEXT,
1274: self::TEXTAREA,
1275: self::INPUTPASSWORD,
1276: self::INPUTRADIO,
1277: self::INPUTCHECKBOX,
1278: self::SELECT,
1279: self::SELECTMULTI,
1280: self::DATEPICKER,
1281: self::INPUTFILE,
1282: self::PROCESSBAR,
1283: self::SLIDER,
1284: // self::CAPTCHA,
1285: self::BUTTONSUBMIT,
1286: self::BUTTONRESET,
1287: self::BUTTON,
1288: self::BUTTONIMAGE,
1289: self::MATRIX,
1290: self::PARA,
1291: self::FIELDSET_BEGIN
1292: /*
1293: self::INPUTHIDDEN
1294: */
1295: ));
1296:
1297: case 'uri':
1298: return in_array($fieldType, array(
1299: /*
1300: self::INPUTTEXT,
1301: self::TEXTAREA,
1302: self::INPUTPASSWORD,
1303: self::INPUTRADIO,
1304: self::INPUTCHECKBOX,
1305: self::SELECT,
1306: self::SELECTMULTI,
1307: self::DATEPICKER,
1308: self::INPUTFILE,
1309: self::PROCESSBAR,
1310: self::SLIDER,
1311: // self::CAPTCHA,
1312: self::BUTTONSUBMIT,
1313: self::BUTTONRESET,
1314: self::BUTTON,
1315: */
1316: self::BUTTONIMAGE,
1317: /*
1318: self::MATRIX,
1319: self::PARA,
1320: self::FIELDSET_BEGIN
1321: self::INPUTHIDDEN
1322: */
1323: ));
1324:
1325: default:
1326: $msg = Pifa::i18n('NOT_IMPLEMENTED_FIELDPROP');
1327: $msg = sprintf($msg, $columnName);
1328: throw new PifaException($msg);
1329: }
1330: }
1331:
1332: /**
1333: *
1334: * @return array
1335: */
1336: public function getOptions() {
1337: $option_labels = $this->get('option_labels');
1338: $option_values = $this->get('option_values');
1339:
1340: $out = array();
1341: if (0 < strlen($option_labels . $option_values)) {
1342: $option_labels = explode(',', $option_labels);
1343: $option_values = explode(',', $option_values);
1344:
1345: // str_getcsv requires PHP 5.3 :(
1346: // $option_labels = str_getcsv($option_labels);
1347: // $option_values = str_getcsv($option_values);
1348:
1349: // instead replace commas stored as entities by real commas
1350: $func = create_function('$v', 'return str_replace(\',\', \',\', $v);');
1351: $option_labels = array_map($func, $option_labels);
1352: $option_values = array_map($func, $option_values);
1353:
1354: foreach (array_keys($option_labels) as $key) {
1355: $out[] = array(
1356: 'label' => $option_labels[$key],
1357: 'value' => $option_values[$key]
1358: );
1359: }
1360: }
1361:
1362: return $out;
1363: }
1364: }
1365:
1366: ?>
1367: