1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13: namespace DcGeneral\Contao\View\Contao2BackendView;
14:
15: use ContaoCommunityAlliance\Contao\Bindings\ContaoEvents;
16: use ContaoCommunityAlliance\Contao\Bindings\Events\Backend\AddToUrlEvent;
17: use ContaoCommunityAlliance\Contao\Bindings\Events\Image\GenerateHtmlEvent;
18: use DcGeneral\Contao\Compatibility\DcCompat;
19: use DcGeneral\Contao\View\Contao2BackendView\Event\BuildWidgetEvent;
20: use DcGeneral\Contao\View\Contao2BackendView\Event\DecodePropertyValueForWidgetEvent;
21: use DcGeneral\Contao\View\Contao2BackendView\Event\EncodePropertyValueFromWidgetEvent;
22: use DcGeneral\Contao\View\Contao2BackendView\Event\GetPropertyOptionsEvent;
23: use DcGeneral\Contao\View\Contao2BackendView\Event\ManipulateWidgetEvent;
24: use DcGeneral\Contao\View\Contao2BackendView\Event\ResolveWidgetErrorMessageEvent;
25: use DcGeneral\Data\ModelInterface;
26: use DcGeneral\Data\PropertyValueBag;
27: use DcGeneral\DataDefinition\Definition\Properties\PropertyInterface;
28: use DcGeneral\EnvironmentInterface;
29: use DcGeneral\Exception\DcGeneralInvalidArgumentException;
30: use DcGeneral\Exception\DcGeneralRuntimeException;
31:
32: 33: 34: 35: 36: 37: 38:
39: class ContaoWidgetManager
40: {
41: 42: 43: 44: 45:
46: protected $environment;
47:
48: 49: 50: 51: 52:
53: protected $model;
54:
55: 56: 57: 58: 59: 60: 61:
62: public function __construct(EnvironmentInterface $environment, ModelInterface $model)
63: {
64: $this->environment = $environment;
65: $this->model = $model;
66:
67: $this->preLoadRichTextEditor();
68: }
69:
70: 71: 72: 73: 74: 75: 76: 77: 78:
79: public function encodeValue($property, $value)
80: {
81: $environment = $this->getEnvironment();
82:
83: $event = new EncodePropertyValueFromWidgetEvent($environment, $this->model);
84: $event
85: ->setProperty($property)
86: ->setValue($value);
87:
88: $environment->getEventPropagator()->propagate(
89: $event::NAME,
90: $event,
91: array(
92: $environment->getDataDefinition()->getName(),
93: $property
94: )
95: );
96:
97: return $event->getValue();
98: }
99:
100: 101: 102: 103: 104: 105: 106: 107: 108:
109: public function decodeValue($property, $value)
110: {
111: $environment = $this->getEnvironment();
112:
113: $event = new DecodePropertyValueForWidgetEvent($environment, $this->model);
114: $event
115: ->setProperty($property)
116: ->setValue($value);
117:
118: $environment->getEventPropagator()->propagate(
119: $event::NAME,
120: $event,
121: array(
122: $environment->getDataDefinition()->getName(),
123: $property
124: )
125: );
126:
127: return $event->getValue();
128: }
129:
130: 131: 132:
133: public function getEnvironment()
134: {
135: return $this->environment;
136: }
137:
138: 139: 140:
141: public function hasWidget($property)
142: {
143: try
144: {
145: return $this->getWidget($property) !== null;
146: }
147: catch(\Exception $e)
148: {
149:
150: }
151: return false;
152: }
153:
154: 155: 156: 157: 158: 159: 160:
161: protected function getXLabel($propInfo)
162: {
163: $strXLabel = '';
164: $environment = $this->getEnvironment();
165: $defName = $environment->getDataDefinition()->getName();
166: $translator = $environment->getTranslator();
167:
168:
169: if ($propInfo->getWidgetType() === 'textarea' && !array_key_exists('rte', $propInfo->getExtra()))
170: {
171: $event = new GenerateHtmlEvent(
172: 'wrap.gif',
173: $translator->translate('wordWrap', 'MSC'),
174: sprintf(
175: 'title="%s" class="toggleWrap" onclick="Backend.toggleWrap(\'ctrl_%s\');"',
176: specialchars($translator->translate('wordWrap', 'MSC')),
177: $propInfo->getName()
178: )
179: );
180:
181: $environment->getEventPropagator()->propagate(ContaoEvents::IMAGE_GET_HTML, $event);
182:
183: $strXLabel .= ' ' . $event->getHtml();
184: }
185:
186:
187: if ($propInfo->getExtra() && array_key_exists('helpwizard', $propInfo->getExtra()))
188: {
189: $event = new GenerateHtmlEvent(
190: 'about.gif',
191: $translator->translate('helpWizard', 'MSC'),
192: 'style="vertical-align:text-bottom;"'
193: );
194:
195: $environment->getEventPropagator()->propagate(ContaoEvents::IMAGE_GET_HTML, $event);
196:
197: $strXLabel .= sprintf(
198: ' <a href="contao/help.php?table=%s&field=%s"
199: title="%s"
200: onclick="Backend.openWindow(this, 600, 500); return false;">%s</a>',
201: $defName,
202: $propInfo->getName(),
203: specialchars($translator->translate('helpWizard', 'MSC')),
204: $event->getHtml()
205: );
206: }
207:
208:
209: if ($propInfo->getWidgetType() === 'fileTree')
210: {
211:
212: if (version_compare(VERSION, '3.0', '<'))
213: {
214: $event = new GenerateHtmlEvent(
215: 'filemanager.gif',
216: $translator->translate('fileManager', 'MSC'),
217: 'style="vertical-align:text-bottom;"'
218: );
219:
220: $environment->getEventPropagator()->propagate(ContaoEvents::IMAGE_GET_HTML, $event);
221:
222: $strXLabel .= sprintf(
223: ' <a href="contao/files.php"
224: title="%s"
225: onclick="Backend.getScrollOffset(); Backend.openWindow(this, 750, 500); return false;">%s</a>',
226: specialchars($translator->translate('fileManager', 'MSC')),
227: $event->getHtml()
228: );
229: }
230: }
231:
232: elseif ($propInfo->getWidgetType() === 'tableWizard')
233: {
234: $urlEvent = new AddToUrlEvent('key=table');
235:
236: $importTableEvent = new GenerateHtmlEvent(
237: 'tablewizard.gif',
238: $translator->translate('importTable.0', $defName),
239: 'style="vertical-align:text-bottom;"'
240: );
241:
242: $shrinkEvent = new GenerateHtmlEvent(
243: 'demagnify.gif',
244: $translator->translate('shrink.0', $defName),
245: sprintf(
246: 'title="%s" style="vertical-align:text-bottom; cursor:pointer;" onclick="Backend.tableWizardResize(0.9);"',
247: specialchars($translator->translate('shrink.1', $defName))
248: )
249: );
250:
251: $expandEvent = new GenerateHtmlEvent(
252: 'magnify.gif',
253: $translator->translate('expand.0', $defName),
254: sprintf(
255: 'title="%s" style="vertical-align:text-bottom; cursor:pointer;" onclick="Backend.tableWizardResize(1.1);"',
256: specialchars($translator->translate('expand.1', $defName))
257: )
258: );
259:
260: $environment->getEventPropagator()->propagate(ContaoEvents::BACKEND_ADD_TO_URL, $urlEvent);
261:
262: $environment->getEventPropagator()->propagate(ContaoEvents::IMAGE_GET_HTML, $importTableEvent);
263: $environment->getEventPropagator()->propagate(ContaoEvents::IMAGE_GET_HTML, $shrinkEvent);
264: $environment->getEventPropagator()->propagate(ContaoEvents::IMAGE_GET_HTML, $expandEvent);
265:
266: $strXLabel .= sprintf(
267: ' <a href="%s" title="%s" onclick="Backend.getScrollOffset();">%s</a> %s%s',
268: ampersand($urlEvent->getUrl()),
269: specialchars($translator->translate('importTable.1', $defName)),
270: $importTableEvent->getHtml(),
271: $shrinkEvent->getHtml(),
272: $expandEvent->getHtml()
273: );
274: }
275:
276: elseif ($propInfo->getWidgetType() === 'listWizard')
277: {
278: $urlEvent = new AddToUrlEvent('key=list');
279:
280: $importListEvent = new GenerateHtmlEvent(
281: 'tablewizard.gif',
282: $translator->translate('importList.0', $defName),
283: 'style="vertical-align:text-bottom;"'
284: );
285:
286: $environment->getEventPropagator()->propagate(ContaoEvents::BACKEND_ADD_TO_URL, $urlEvent);
287: $environment->getEventPropagator()->propagate(ContaoEvents::IMAGE_GET_HTML, $importListEvent);
288:
289: $strXLabel .= sprintf(
290: ' <a href="%s" title="%s" onclick="Backend.getScrollOffset();">%s</a>',
291: ampersand($urlEvent->getUrl()),
292: specialchars($translator->translate('importList.1', $defName)),
293: $importListEvent->getHtml()
294: );
295: }
296:
297: return $strXLabel;
298: }
299:
300: 301: 302: 303: 304:
305: public function preLoadRichTextEditor()
306: {
307: foreach ($this->getEnvironment()->getDataDefinition()->getPropertiesDefinition()->getProperties() as $property)
308: {
309:
310: $extra = $property->getExtra();
311:
312: if (!isset($extra['eval']['rte']))
313: {
314: continue;
315: }
316:
317: if (strncmp($extra['eval']['rte'], 'tiny', 4) !== 0)
318: {
319: continue;
320: }
321:
322: list($file, $type) = explode('|', $extra['eval']['rte']);
323:
324: $propertyId = 'ctrl_' . $property->getName();
325:
326: $GLOBALS['TL_RTE'][$file][$propertyId] = array(
327: 'id' => $propertyId,
328: 'file' => $file,
329: 'type' => $type
330: );
331: }
332: }
333:
334: 335: 336: 337: 338: 339: 340: 341: 342:
343: public function getWidget($property)
344: {
345: $environment = $this->getEnvironment();
346: $defName = $environment->getDataDefinition()->getName();
347: $propertyDefinitions = $environment->getDataDefinition()->getPropertiesDefinition();
348:
349: if (!$propertyDefinitions->hasProperty($property))
350: {
351: throw new DcGeneralInvalidArgumentException('Property ' . $property . ' is not defined in propertyDefinitions.');
352: }
353:
354: $event = new BuildWidgetEvent($environment, $this->model, $propertyDefinitions->getProperty($property));
355:
356: $environment->getEventPropagator()->propagate(
357: $event::NAME,
358: $event,
359: array(
360: $defName,
361: $property
362: )
363: );
364:
365: if ($event->getWidget())
366: {
367: return $event->getWidget();
368: }
369:
370: $propInfo = $propertyDefinitions->getProperty($property);
371: $propExtra = $propInfo->getExtra();
372: $varValue = $this->decodeValue($property, $this->model->getProperty($property));
373: $xLabel = $this->getXLabel($propInfo);
374:
375: $strClass = $GLOBALS['BE_FFL'][$propInfo->getWidgetType()];
376: if (!class_exists($strClass))
377: {
378: return null;
379: }
380:
381:
382: if (in_array($propExtra['rgxp'], array('date', 'time', 'datim'))
383: && !$propExtra['mandatory']
384: && is_numeric($varValue) && $varValue == 0)
385: {
386: $varValue = '';
387: }
388:
389:
390:
391:
392: $propExtra['required'] = ($varValue == '') && $propExtra['mandatory'];
393:
394: $options = $propInfo->getOptions();
395: $event = new GetPropertyOptionsEvent($environment, $this->model);
396: $event->setPropertyName($property);
397: $event->setOptions($options);
398: $environment->getEventPropagator()->propagate(
399: $event::NAME,
400: $event,
401: $environment->getDataDefinition()->getName(),
402: $property
403: );
404:
405: if ($event->getOptions() !== $options)
406: {
407: $options = $event->getOptions();
408: }
409:
410: $arrConfig = array(
411: 'inputType' => $propInfo->getWidgetType(),
412: 'label' => array(
413: $propInfo->getLabel(),
414: $propInfo->getDescription()
415: ),
416: 'options' => $options,
417: 'eval' => $propExtra,
418:
419:
420:
421:
422: );
423:
424: if (version_compare(VERSION, '3.0', '>='))
425: {
426: $arrPrepared = \Widget::getAttributesFromDca(
427: $arrConfig,
428: $propInfo->getName(),
429: $varValue,
430: $property,
431: $defName,
432: new DcCompat($environment, $this->model, $property)
433: );
434: }
435: else
436: {
437: $arrPrepared = BackendBindings::prepareForWidget($arrConfig, $propInfo->getName(), $varValue, $property, $defName);
438: }
439:
440:
441:
442:
443: if ($arrConfig['inputType'] == 'checkbox'
444: && is_array($GLOBALS['TL_DCA'][$defName]['subpalettes'])
445: && in_array($property, array_keys($GLOBALS['TL_DCA'][$defName]['subpalettes']))
446: && $arrConfig['eval']['submitOnChange']
447: )
448: {
449: $arrPrepared['onclick'] = $arrConfig['eval']['submitOnChange'] ? "Backend.autoSubmit('".$defName."')" : '';
450: }
451:
452: $objWidget = new $strClass($arrPrepared);
453:
454: $objWidget->currentRecord = $this->model->getId();
455:
456: $objWidget->wizard .= $xLabel;
457:
458: $event = new ManipulateWidgetEvent($environment, $this->model, $propInfo, $objWidget);
459: $environment->getEventPropagator()->propagate(
460: $event::NAME,
461: $event,
462: array(
463: $defName,
464: $property
465: )
466: );
467:
468: return $objWidget;
469: }
470:
471: 472: 473: 474: 475: 476: 477:
478: protected function buildDatePicker($objWidget)
479: {
480: $translator = $this->getEnvironment()->getTranslator();
481:
482: $strFormat = $GLOBALS['TL_CONFIG'][$objWidget->rgxp . 'Format'];
483:
484: $arrConfig = array(
485: 'allowEmpty' => true,
486: 'toggleElements' => '#toggle_' . $objWidget->id,
487: 'pickerClass' => 'datepicker_dashboard',
488: 'format' => $strFormat,
489: 'inputOutputFormat' => $strFormat,
490: 'positionOffset' => array(
491: 'x' => 130,
492: 'y' => -185
493: ),
494: 'startDay' => $translator->translate('weekOffset', 'MSC'),
495: 'days' => array_values((array)$translator->translate('DAYS', 'MSC')),
496: 'dayShort' => $translator->translate('dayShortLength', 'MSC'),
497: 'months' => array_values((array)$translator->translate('MONTHS', 'MSC')),
498: 'monthShort' => $translator->translate('monthShortLength', 'MSC')
499: );
500:
501: switch ($objWidget->rgxp)
502: {
503: case 'datim':
504: $arrConfig['timePicker'] = true;
505:
506: $time = ",\n timePicker:true";
507: break;
508:
509: case 'time':
510: $arrConfig['timePickerOnly'] = true;
511:
512: $time = ",\n pickOnly:\"time\"";
513: break;
514: default:
515: $time = '';
516: }
517:
518: if (version_compare(DATEPICKER, '2.1', '>'))
519: {
520: return 'new Picker.Date($$("#ctrl_' . $objWidget->id . '"), {
521: draggable:false,
522: toggle:$$("#toggle_' . $objWidget->id . '"),
523: format:"' . \Date::formatToJs($strFormat) . '",
524: positionOffset:{x:-197,y:-182}' . $time . ',
525: pickerClass:"datepicker_dashboard",
526: useFadeInOut:!Browser.ie,
527: startDay:' . $translator->translate('weekOffset', 'MSC') . ',
528: titleFormat:"' . $translator->translate('titleFormat', 'MSC') . '"
529: });';
530: }
531:
532: return 'new DatePicker(' . json_encode('#ctrl_' . $objWidget->id) . ', ' . json_encode($arrConfig) . ');';
533: }
534:
535: 536: 537: 538: 539: 540: 541:
542: protected function generateHelpText($property)
543: {
544: $environment = $this->getEnvironment();
545: $propInfo = $environment->getDataDefinition()->getPropertiesDefinition()->getProperty($property);
546: $label = $propInfo->getDescription();
547: $widgetType = $propInfo->getWidgetType();
548:
549:
550: if (!$GLOBALS['TL_CONFIG']['showHelp'] || $widgetType == 'password' || !strlen($label))
551: {
552: return '';
553: }
554:
555: return '<p class="tl_help tl_tip">' . $label . '</p>';
556: }
557:
558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568:
569: public function renderWidget($property, $ignoreErrors = false)
570: {
571: $environment = $this->getEnvironment();
572: $definition = $environment->getDataDefinition();
573: $propertyDefinitions = $definition->getPropertiesDefinition();
574: $propInfo = $propertyDefinitions->getProperty($property);
575: $propExtra = $propInfo->getExtra();
576: $widget = $this->getWidget($property);
577:
578:
579: if (!$widget)
580: {
581: throw new DcGeneralRuntimeException('No widget for property ' . $property);
582: }
583:
584: if ($ignoreErrors)
585: {
586:
587: $reflection = new \ReflectionProperty(get_class($widget), 'arrErrors');
588: $reflection->setAccessible(true);
589: $reflection->setValue($widget, array());
590: $reflection = new \ReflectionProperty(get_class($widget), 'strClass');
591: $reflection->setAccessible(true);
592: $reflection->setValue($widget, str_replace('error', '', $reflection->getValue($widget)));
593: }
594:
595: $strDatePicker = '';
596: if (isset($propExtra['datepicker']))
597: {
598: $strDatePicker = $this->buildDatePicker($widget);
599: }
600:
601: $objTemplateFoo = new ContaoBackendViewTemplate('dcbe_general_field');
602: $objTemplateFoo->setData(array(
603: 'strName' => $property,
604: 'strClass' => $propExtra['tl_class'],
605: 'widget' => $widget->parse(),
606: 'hasErrors' => $widget->hasErrors(),
607: 'strDatepicker' => $strDatePicker,
608:
609: 'blnUpdate' => false,
610: 'strHelp' => $this->generateHelpText($property)
611: ));
612:
613: return $objTemplateFoo->parse();
614: }
615:
616: 617: 618:
619: public function processInput(PropertyValueBag $propertyValues)
620: {
621: foreach (array_keys($propertyValues->getArrayCopy()) as $property)
622: {
623: $widget = $this->getWidget($property);
624:
625:
626:
627: $widget->value = $propertyValues->getPropertyValue($property);
628: $widget->validate();
629:
630: if ($widget->hasErrors())
631: {
632: foreach ($widget->getErrors() as $error)
633: {
634: $propertyValues->markPropertyValueAsInvalid($property, $error);
635: }
636: }
637: else
638: {
639: $propertyValues->setPropertyValue($property, $this->encodeValue($property, $widget->value));
640: }
641: }
642: }
643:
644: 645: 646:
647: public function processErrors(PropertyValueBag $propertyValues)
648: {
649: $propertyErrors = $propertyValues->getInvalidPropertyErrors();
650: $definitionName = $this->getEnvironment()->getDataDefinition()->getName();
651:
652: if ($propertyErrors)
653: {
654: $propagator = $this->getEnvironment()->getEventPropagator();
655:
656: foreach ($propertyErrors as $property => $errors)
657: {
658: $widget = $this->getWidget($property);
659:
660: foreach ($errors as $error)
661: {
662: $event = new ResolveWidgetErrorMessageEvent($this->getEnvironment(), $error);
663: $propagator->propagate(
664: $event::NAME,
665: $event,
666: array(
667: $definitionName,
668: $property
669: )
670: );
671:
672: $widget->addError($event->getError());
673: }
674: }
675: }
676: }
677: }
678: