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