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