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 = strip_tags($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 = strip_tags($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 = conHtmlEntities($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 = conHtmlEntities($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 = "if (typeof jQuery == \"function\") {
816: jQuery(function(){ jQuery('$sel').datepicker({
817: altFormat: 'yy-mm-dd',
818: altField: '$sel-hidden'
819: });});
820: }";
821: break;
822: // case self::CAPTCHA:
823: // $sel = '#pifa-field-' . $idfield . ' label';
824: // $newCaptchaCode = mi18n("NEW_CAPTCHA_CODE");
825: // $script = "jQuery(function(){\n";
826: // // implement captcha reload on click
827: // $script .= "jQuery('$sel').click(function (e) {\n";
828: // $script .= "e.preventDefault();\n";
829: // $script .= "var url = 'front_content.php?securimage&' +
830: // Math.random();\n";
831: // $script .= "jQuery(this).parent().find('img').attr('src',
832: // url);\n";
833: // $script .= "});\n";
834: // // append 'New Captcha Code' to label
835: // $script .= "jQuery('$sel').append('<br/><br/><span
836: // style=\"cursor:pointer\">$newCaptchaCode</span>');";
837: // $script .= "});\n";
838: // break;
839: default:
840: $script = '';
841: }
842:
843: $elemScript = NULL;
844: if (0 < strlen($script)) {
845: $elemScript = new cHTMLScript();
846: $elemScript->setContent($script);
847: }
848:
849: return $elemScript;
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: * The order of field types in this array influences the order of icons
865: * displayed in the backend for selection!
866: *
867: * @return array
868: */
869: public static function getFieldTypeNames() {
870: return array(
871: self::INPUTTEXT => Pifa::i18n('INPUTTEXT'),
872: self::TEXTAREA => Pifa::i18n('TEXTAREA'),
873: self::INPUTPASSWORD => Pifa::i18n('INPUTPASSWORD'),
874: self::INPUTRADIO => Pifa::i18n('INPUTRADIO'),
875: self::INPUTCHECKBOX => Pifa::i18n('INPUTCHECKBOX'),
876: self::SELECT => Pifa::i18n('SELECT'),
877: self::SELECTMULTI => Pifa::i18n('SELECTMULTI'),
878: self::DATEPICKER => Pifa::i18n('DATEPICKER'),
879: self::INPUTFILE => Pifa::i18n('INPUTFILE'),
880: self::PROCESSBAR => Pifa::i18n('PROCESSBAR'),
881: self::SLIDER => Pifa::i18n('SLIDER'),
882: // self::CAPTCHA => Pifa::i18n('CAPTCHA'),
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: self::BUTTONSUBMIT => Pifa::i18n('BUTTONSUBMIT'),
889: self::BUTTONRESET => Pifa::i18n('BUTTONRESET'),
890: self::BUTTON => Pifa::i18n('BUTTON'),
891: self::BUTTONIMAGE => Pifa::i18n('BUTTONIMAGE')
892: );
893: }
894:
895: /**
896: * Return the field type name for the given field type id.
897: *
898: * @param int $fieldType
899: * @return string
900: */
901: public static function getFieldTypeName($fieldTypeId) {
902: $fieldTypeId = cSecurity::toInteger($fieldTypeId);
903: $fieldTypeNames = self::getFieldTypeNames();
904:
905: if (array_key_exists($fieldTypeId, $fieldTypeNames)) {
906: $fieldTypeName = $fieldTypeNames[$fieldTypeId];
907: } else {
908: $fieldTypeName = Pifa::i18n('UNKNOWN');
909: }
910:
911: return $fieldTypeName;
912: }
913:
914: /**
915: * Return this fields type name.
916: *
917: * @param int $fieldType
918: * @return string
919: */
920: public function getMyFieldTypeName() {
921: return self::getFieldTypeName($this->get('field_type'));
922: }
923:
924: /**
925: * Returns a string describing this fields database data type as used in
926: * MySQL CREATE and ALTER TABLE statements.
927: *
928: * @throws PifaException if field is not loaded
929: * @throws PifaException if field type is not implemented
930: */
931: public function getDbDataType() {
932: if (!$this->isLoaded()) {
933: $msg = Pifa::i18n('FIELD_LOAD_ERROR');
934: throw new PifaException($msg);
935: }
936:
937: $fieldType = cSecurity::toInteger($this->get('field_type'));
938:
939: switch ($fieldType) {
940:
941: // Text and password input fields can store a string of
942: // arbitrary length. Cause they are single lined it does not
943: // make sense to enable them storing more than 1023 characters
944: // though.
945: case self::INPUTTEXT:
946: case self::INPUTPASSWORD:
947: case self::INPUTHIDDEN:
948: case self::INPUTFILE:
949:
950: return 'VARCHAR(' . self::VARCHAR_SIZE . ')';
951:
952: // A group of checkboxes can store multiple values. So this has
953: // to be implemented as a CSV string of max. 1023 characters.
954: case self::INPUTCHECKBOX:
955:
956: return 'VARCHAR(' . self::VARCHAR_SIZE . ')';
957:
958: // Textareas are designed to store longer texts so I chose the
959: // TEXT data type to enable them storing up to 65535 characters.
960: case self::TEXTAREA:
961:
962: return 'TEXT';
963:
964: // A group of radiobuttons or a select box can store a single
965: // value of a given set of options. This can be implemented
966: // as an enumeration.
967: case self::INPUTRADIO:
968: case self::SELECT:
969: case self::SELECTMULTI:
970:
971: // $optionValues = $this->get('option_values');
972: // $optionValues = explode(',', $optionValues);
973: // array_map(function ($val) {
974: // return "'$val'";
975: // }, $optionValues);
976: // $optionValues = join(',', $optionValues);
977:
978: // return "ENUM($optionValues)";
979:
980: // as long as the GUI does not offer an entry to define option
981: // values when creating a new field assume VARCHAR as data type
982: return 'VARCHAR(' . self::VARCHAR_SIZE . ')';
983:
984: // The datepicker can store a date having an optional time
985: // portion. I chose DATETIME as data type over TIMESTAMP due to
986: // its limitation of storing dates before 1970-01-01 although
987: // even DATETIME can't store dates before 1000-01-01.
988: case self::DATEPICKER:
989:
990: return 'DATETIME';
991:
992: // Buttons don't have any values that should be stored.
993: case self::BUTTONSUBMIT:
994: case self::BUTTONRESET:
995: case self::BUTTON:
996: case self::BUTTONIMAGE:
997:
998: return NULL;
999:
1000: // TODO For some filed types I havn't yet decided which data
1001: // type to use.
1002: case self::PROCESSBAR:
1003: case self::SLIDER:
1004: // case self::CAPTCHA:
1005: case self::MATRIX:
1006: case self::PARA:
1007: case self::FIELDSET_BEGIN:
1008: case self::FIELDSET_END:
1009:
1010: return NULL;
1011:
1012: default:
1013: $msg = Pifa::i18n('NOT_IMPLEMENTED_FIELDTYPE');
1014: $msg = sprintf($msg, $fieldType);
1015: // Util::logDump($this->values);
1016: throw new PifaException($msg);
1017: }
1018: }
1019:
1020: /**
1021: * Deletes this form with all its fields and stored data.
1022: * The forms data table is also dropped.
1023: */
1024: public function delete() {
1025: $cfg = cRegistry::getConfig();
1026: $db = cRegistry::getDb();
1027:
1028: if (!$this->isLoaded()) {
1029: $msg = Pifa::i18n('FIELD_LOAD_ERROR');
1030: throw new PifaException($msg);
1031: }
1032:
1033: // update ranks of younger siblings
1034: $sql = "-- PifaField->delete()
1035: UPDATE
1036: " . cRegistry::getDbTableName('pifa_field') . "
1037: SET
1038: field_rank = field_rank - 1
1039: WHERE
1040: idform = " . cSecurity::toInteger($this->get('idform')) . "
1041: AND field_rank > " . cSecurity::toInteger($this->get('field_rank')) . "
1042: ;";
1043: if (false === $db->query($sql)) {
1044: // false is returned if no fields were updated
1045: // but that doesn't matter ...
1046: }
1047:
1048: // delete field
1049: $sql = "-- PifaField->delete()
1050: DELETE FROM
1051: " . cRegistry::getDbTableName('pifa_field') . "
1052: WHERE
1053: idfield = " . cSecurity::toInteger($this->get('idfield')) . "
1054: ;";
1055: if (false === $db->query($sql)) {
1056: $msg = Pifa::i18n('FIELD_DELETE_ERROR');
1057: throw new PifaException($msg);
1058: }
1059:
1060: // drop column of data table
1061: if (0 < strlen(trim($this->get('column_name')))) {
1062: $pifaForm = new PifaForm($this->get('idform'));
1063:
1064: if (0 < strlen(trim($pifaForm->get('data_table')))) {
1065: $sql = "-- PifaField->delete()
1066: ALTER TABLE
1067: `" . cSecurity::toString($pifaForm->get('data_table')) . "`
1068: DROP COLUMN
1069: `" . cSecurity::toString($this->get('column_name')) . "`
1070: ;";
1071: if (false === $db->query($sql)) {
1072: $msg = Pifa::i18n('COLUMN_DROP_ERROR');
1073: throw new PifaException($msg);
1074: }
1075: }
1076: }
1077: }
1078:
1079: /**
1080: * Determines for which form field types which data should be editable in
1081: * backend.
1082: *
1083: * @param string $columnName for data to edit
1084: */
1085: public function showField($columnName) {
1086: $fieldType = $this->get('field_type');
1087: $fieldType = cSecurity::toInteger($fieldType);
1088:
1089: switch ($columnName) {
1090:
1091: case 'idfield':
1092: case 'idform':
1093: case 'field_rank':
1094: case 'field_type':
1095: // will never be editable directly
1096: return false;
1097:
1098: case 'column_name':
1099: return in_array($fieldType, array(
1100: self::INPUTTEXT,
1101: self::TEXTAREA,
1102: self::INPUTPASSWORD,
1103: self::INPUTRADIO,
1104: self::INPUTCHECKBOX,
1105: self::SELECT,
1106: self::SELECTMULTI,
1107: self::DATEPICKER,
1108: self::INPUTFILE,
1109: self::PROCESSBAR,
1110: self::SLIDER,
1111: // self::CAPTCHA,
1112: // self::BUTTONSUBMIT,
1113: // self::BUTTONRESET,
1114: // self::BUTTON,
1115: // self::BUTTONIMAGE,
1116: self::MATRIX,
1117: self::INPUTHIDDEN
1118: /*
1119: self::FIELDSET_BEGIN,
1120: self::FIELDSET_END
1121: */
1122: ));
1123:
1124: case 'label':
1125: case 'display_label':
1126: return in_array($fieldType, array(
1127: self::INPUTTEXT,
1128: self::TEXTAREA,
1129: self::INPUTPASSWORD,
1130: self::INPUTRADIO,
1131: self::INPUTCHECKBOX,
1132: self::SELECT,
1133: self::SELECTMULTI,
1134: self::DATEPICKER,
1135: self::INPUTFILE,
1136: self::PROCESSBAR,
1137: self::SLIDER,
1138: // self::CAPTCHA,
1139: self::BUTTONSUBMIT,
1140: self::BUTTONRESET,
1141: self::BUTTON,
1142: // self::BUTTONIMAGE,
1143: self::MATRIX,
1144: self::PARA,
1145: // self::INPUTHIDDEN,
1146: self::FIELDSET_BEGIN
1147: /*
1148: self::FIELDSET_END
1149: */
1150: ));
1151:
1152: case 'default_value':
1153: return in_array($fieldType, array(
1154: self::INPUTTEXT,
1155: self::TEXTAREA,
1156: self::INPUTPASSWORD,
1157: self::INPUTRADIO,
1158: self::INPUTCHECKBOX,
1159: self::SELECT,
1160: self::SELECTMULTI,
1161: self::DATEPICKER,
1162: self::INPUTFILE,
1163: self::PROCESSBAR,
1164: self::SLIDER,
1165: // self::CAPTCHA,
1166: self::BUTTONSUBMIT,
1167: self::BUTTONRESET,
1168: self::BUTTON,
1169: // self::BUTTONIMAGE,
1170: self::MATRIX,
1171: self::INPUTHIDDEN
1172: ));
1173:
1174: case 'option_labels':
1175: case 'option_values':
1176: case 'option_class':
1177: return in_array($fieldType, array(
1178: self::INPUTRADIO,
1179: self::INPUTCHECKBOX,
1180: self::SELECT,
1181: self::SELECTMULTI
1182: ));
1183:
1184: case 'help_text':
1185: return in_array($fieldType, array(
1186: self::INPUTTEXT,
1187: self::TEXTAREA,
1188: self::INPUTPASSWORD,
1189: self::INPUTRADIO,
1190: self::INPUTCHECKBOX,
1191: self::SELECT,
1192: self::SELECTMULTI,
1193: self::DATEPICKER,
1194: self::INPUTFILE,
1195: self::PROCESSBAR,
1196: self::SLIDER,
1197: // self::CAPTCHA,
1198: self::BUTTONSUBMIT,
1199: self::BUTTONRESET,
1200: self::BUTTON,
1201: self::BUTTONIMAGE,
1202: self::MATRIX,
1203: self::PARA,
1204: self::FIELDSET_BEGIN
1205: ));
1206:
1207: case 'obligatory':
1208: return in_array($fieldType, array(
1209: self::INPUTTEXT,
1210: self::TEXTAREA,
1211: self::INPUTPASSWORD,
1212: self::INPUTRADIO,
1213: self::INPUTCHECKBOX,
1214: self::SELECT,
1215: self::SELECTMULTI,
1216: self::DATEPICKER,
1217: self::INPUTFILE,
1218: self::PROCESSBAR,
1219: self::SLIDER,
1220: // self::CAPTCHA,
1221: // self::BUTTONSUBMIT,
1222: // self::BUTTONRESET,
1223: // self::BUTTON,
1224: // self::BUTTONIMAGE,
1225: self::MATRIX,
1226: self::INPUTHIDDEN
1227: ));
1228:
1229: case 'rule':
1230: return in_array($fieldType, array(
1231: self::INPUTTEXT,
1232: self::TEXTAREA,
1233: self::INPUTPASSWORD,
1234: self::INPUTRADIO,
1235: self::INPUTCHECKBOX,
1236: self::SELECT,
1237: self::SELECTMULTI,
1238: self::DATEPICKER,
1239: self::INPUTFILE,
1240: self::PROCESSBAR,
1241: self::SLIDER,
1242: // self::CAPTCHA,
1243: // self::BUTTONSUBMIT,
1244: // self::BUTTONRESET,
1245: // self::BUTTON,
1246: // self::BUTTONIMAGE,
1247: self::MATRIX,
1248: self::INPUTHIDDEN
1249: ));
1250:
1251: case 'error_message':
1252: return in_array($fieldType, array(
1253: self::INPUTTEXT,
1254: self::TEXTAREA,
1255: self::INPUTPASSWORD,
1256: self::INPUTRADIO,
1257: self::INPUTCHECKBOX,
1258: self::SELECT,
1259: self::SELECTMULTI,
1260: self::DATEPICKER,
1261: self::INPUTFILE,
1262: self::PROCESSBAR,
1263: self::SLIDER,
1264: // self::CAPTCHA,
1265: // self::BUTTONSUBMIT,
1266: // self::BUTTONRESET,
1267: // self::BUTTON,
1268: // self::BUTTONIMAGE,
1269: self::MATRIX,
1270: self::INPUTHIDDEN
1271: ));
1272:
1273: case 'css_class':
1274: return in_array($fieldType, array(
1275: self::INPUTTEXT,
1276: self::TEXTAREA,
1277: self::INPUTPASSWORD,
1278: self::INPUTRADIO,
1279: self::INPUTCHECKBOX,
1280: self::SELECT,
1281: self::SELECTMULTI,
1282: self::DATEPICKER,
1283: self::INPUTFILE,
1284: self::PROCESSBAR,
1285: self::SLIDER,
1286: // self::CAPTCHA,
1287: self::BUTTONSUBMIT,
1288: self::BUTTONRESET,
1289: self::BUTTON,
1290: self::BUTTONIMAGE,
1291: self::MATRIX,
1292: self::PARA,
1293: self::FIELDSET_BEGIN
1294: /*
1295: self::INPUTHIDDEN
1296: */
1297: ));
1298:
1299: case 'uri':
1300: return in_array($fieldType, array(
1301: /*
1302: self::INPUTTEXT,
1303: self::TEXTAREA,
1304: self::INPUTPASSWORD,
1305: self::INPUTRADIO,
1306: self::INPUTCHECKBOX,
1307: self::SELECT,
1308: self::SELECTMULTI,
1309: self::DATEPICKER,
1310: self::INPUTFILE,
1311: self::PROCESSBAR,
1312: self::SLIDER,
1313: // self::CAPTCHA,
1314: self::BUTTONSUBMIT,
1315: self::BUTTONRESET,
1316: self::BUTTON,
1317: */
1318: self::BUTTONIMAGE,
1319: /*
1320: self::MATRIX,
1321: self::PARA,
1322: self::FIELDSET_BEGIN
1323: self::INPUTHIDDEN
1324: */
1325: ));
1326:
1327: default:
1328: $msg = Pifa::i18n('NOT_IMPLEMENTED_FIELDPROP');
1329: $msg = sprintf($msg, $columnName);
1330: throw new PifaException($msg);
1331: }
1332: }
1333:
1334: /**
1335: *
1336: * @return array
1337: */
1338: public function getOptions() {
1339: $option_labels = $this->get('option_labels');
1340: $option_values = $this->get('option_values');
1341:
1342: $out = array();
1343: if (0 < strlen($option_labels . $option_values)) {
1344: $option_labels = explode(',', $option_labels);
1345: $option_values = explode(',', $option_values);
1346:
1347: // str_getcsv requires PHP 5.3 :(
1348: // $option_labels = str_getcsv($option_labels);
1349: // $option_values = str_getcsv($option_values);
1350:
1351: // instead replace commas stored as entities by real commas
1352: $func = create_function('$v', 'return str_replace(\',\', \',\', $v);');
1353: $option_labels = array_map($func, $option_labels);
1354: $option_values = array_map($func, $option_values);
1355:
1356: foreach (array_keys($option_labels) as $key) {
1357: $out[] = array(
1358: 'label' => $option_labels[$key],
1359: 'value' => $option_values[$key]
1360: );
1361: }
1362: }
1363:
1364: return $out;
1365: }
1366: }
1367:
1368: ?>
1369: