1: <?php
2: /**
3: * PHP version 5
4: *
5: * @package generalDriver
6: * @author Christian Schiffler <c.schiffler@cyberspectrum.de>
7: * @author Stefan Heimes <stefan_heimes@hotmail.com>
8: * @author Tristan Lins <tristan.lins@bit3.de>
9: * @copyright The MetaModels team.
10: * @license LGPL.
11: * @filesource
12: */
13:
14: namespace DcGeneral\Contao\Dca\Palette;
15:
16: use DcGeneral\DataDefinition\Palette\Condition\Palette\DefaultPaletteCondition;
17: use DcGeneral\DataDefinition\Palette\Condition\Palette\PaletteConditionChain;
18: use DcGeneral\DataDefinition\Palette\Condition\Palette\PaletteConditionInterface;
19: use DcGeneral\DataDefinition\Palette\Condition\Palette\PropertyValueCondition as PalettePropertyValueCondition;
20: use DcGeneral\DataDefinition\Palette\Condition\Palette\PropertyTrueCondition as PalettePropertyTrueCondition;
21: use DcGeneral\DataDefinition\Palette\Condition\Property\PropertyTrueCondition;
22: use DcGeneral\DataDefinition\Palette\Condition\Property\PropertyValueCondition;
23: use DcGeneral\DataDefinition\Palette\Legend;
24: use DcGeneral\DataDefinition\Palette\Palette;
25: use DcGeneral\DataDefinition\Palette\PaletteCollection;
26: use DcGeneral\DataDefinition\Palette\PaletteCollectionInterface;
27: use DcGeneral\DataDefinition\Palette\PaletteInterface;
28: use DcGeneral\DataDefinition\Palette\Property;
29: use DcGeneral\DataDefinition\Palette\PropertyInterface;
30:
31: /**
32: * Class LegacyPalettesParser.
33: *
34: * This class parses the palettes from a legacy DCA into the palette collection definitions being used in DcGeneral.
35: *
36: * @package DcGeneral\Contao\Dca\Palette
37: */
38: class LegacyPalettesParser
39: {
40: /**
41: * Parse the palette and sub palette array and create a complete palette collection.
42: *
43: * @param array(string => string) $palettes The palettes from the DCA.
44: *
45: * @param array(string => string) $subpalettes The sub palettes from the DCA [optional].
46: *
47: * @param PaletteCollectionInterface|null $collection The palette collection to populate [optional].
48: *
49: * @return PaletteCollectionInterface
50: */
51: public function parse(array $palettes, array $subpalettes = array(), PaletteCollectionInterface $collection = null)
52: {
53: if (isset($palettes['__selector__']))
54: {
55: $selectorFieldNames = $palettes['__selector__'];
56: unset($palettes['__selector__']);
57: }
58: else
59: {
60: $selectorFieldNames = array();
61: }
62:
63: $subPaletteProperties = $this->parseSubpalettes($subpalettes, $selectorFieldNames);
64:
65: return $this->parsePalettes(
66: $palettes,
67: $subPaletteProperties,
68: $selectorFieldNames,
69: $collection
70: );
71: }
72:
73: /**
74: * Parse the given palettes.
75: *
76: * @param array(string => string) $palettes The array of palettes, e.g.
77: * <code>array('default' => '{title_legend},title')</code>.
78: *
79: * @param array(string => string) $subPaletteProperties Mapped array from subpalette [optional].
80: *
81: * @param array $selectorFieldNames List of names of the properties to be used as selector
82: * [optional].
83: *
84: * @param PaletteCollectionInterface $collection The palette collection to populate [optional].
85: *
86: * @return PaletteCollectionInterface
87: */
88: public function parsePalettes(
89: array $palettes,
90: array $subPaletteProperties = array(),
91: array $selectorFieldNames = array(),
92: PaletteCollectionInterface $collection = null
93: )
94: {
95: if (!$collection)
96: {
97: $collection = new PaletteCollection();
98: }
99:
100: if (isset($palettes['__selector__']))
101: {
102: $selectorFieldNames = array_merge($selectorFieldNames, $palettes['__selector__']);
103: $selectorFieldNames = array_unique($selectorFieldNames);
104: unset($palettes['__selector__']);
105: }
106:
107: foreach ($palettes as $selector => $fields)
108: {
109: // Fields list must be a string.
110: if (!is_string($fields))
111: {
112: continue;
113: }
114:
115: if ($collection->hasPaletteByName($selector))
116: {
117: $palette = $collection->getPaletteByName($selector);
118: $this->parsePalette(
119: $selector,
120: $fields,
121: $subPaletteProperties,
122: $selectorFieldNames,
123: $palette
124: );
125: }
126: else {
127: $palette = $this->parsePalette(
128: $selector,
129: $fields,
130: $subPaletteProperties,
131: $selectorFieldNames
132: );
133: $collection->addPalette($palette);
134: }
135: }
136:
137: return $collection;
138: }
139:
140: /**
141: * Parse a single palette.
142: *
143: * @param string $paletteSelector The selector for the palette.
144: *
145: * @param string $fields The fields contained within the palette.
146: *
147: * @param array(string => PropertyInterface) $subPaletteProperties The sub palette properties [optional].
148: *
149: * @param array(string) $selectorFieldNames The names of all properties being used as
150: * selectors [optional].
151: *
152: * @param PaletteInterface $palette The palette to be populated [optional].
153: *
154: * @return Palette
155: */
156: public function parsePalette(
157: $paletteSelector,
158: $fields,
159: array $subPaletteProperties = array(),
160: array $selectorFieldNames = array(),
161: PaletteInterface $palette = null
162: )
163: {
164: if (!$palette)
165: {
166: $palette = new Palette();
167: $palette->setName($paletteSelector);
168: }
169:
170: $condition = $this->createPaletteCondition($paletteSelector, $selectorFieldNames);
171: $palette->setCondition($condition);
172:
173: // We ignore the difference between field set (separated by ";") and fields (separated by ",").
174: $fields = preg_split('~[;,]~', $fields);
175: $fields = array_map('trim', $fields);
176: $fields = array_filter($fields);
177:
178: $legend = null;
179:
180: foreach ($fields as $field)
181: {
182: // TODO what about :hide? this is currently not supported by LegendInterface.
183: if (preg_match('~^\{(.*?)(_legend)?(:hide)?\}$~', $field, $matches))
184: {
185: $name = $matches[1];
186: if ($palette->hasLegend($name))
187: {
188: $legend = $palette->getLegend($name);
189: }
190: else
191: {
192: $legend = new Legend($matches[1]);
193: $palette->addLegend($legend);
194: }
195: }
196: else {
197: // Fallback for incomplete palettes without legend,
198: // Create an empty legend.
199: if (!$legend)
200: {
201: $name = 'unnamed';
202: if ($palette->hasLegend($name))
203: {
204: $legend = $palette->getLegend($name);
205: }
206: else
207: {
208: $legend = new Legend($matches[1]);
209: $palette->addLegend($legend);
210: }
211: }
212:
213: // Add the current field to the legend.
214: $property = new Property($field);
215: $legend->addProperty($property);
216:
217: // Add sub palette fields to the legend.
218: if (isset($subPaletteProperties[$field]))
219: {
220: foreach ($subPaletteProperties[$field] as $property)
221: {
222: $legend->addProperty(clone $property);
223: }
224: }
225: }
226: }
227:
228: return $palette;
229: }
230:
231: /**
232: * Parse the palette selector and create the corresponding condition.
233: *
234: * @param string $paletteSelector Create the condition for the selector.
235: *
236: * @param array $selectorFieldNames The property names to be used as selectors.
237: *
238: * @return PaletteConditionInterface
239: */
240: public function createPaletteCondition($paletteSelector, array $selectorFieldNames)
241: {
242: if ($paletteSelector == 'default')
243: {
244: return new DefaultPaletteCondition();
245: }
246:
247: // Legacy fallback, try to split on $selectors with optimistic suggestion of values.
248: if (strpos($paletteSelector, '|') === false)
249: {
250: foreach ($selectorFieldNames as $selectorFieldName)
251: {
252: $paletteSelector = str_replace(
253: $selectorFieldName,
254: '|' . $selectorFieldName . '|',
255: $paletteSelector
256: );
257: }
258: }
259:
260: // Extended mode, split selectors and values with "|".
261: $paletteSelectorParts = explode('|', $paletteSelector);
262: $paletteSelectorParts = array_map('trim', $paletteSelectorParts);
263: $paletteSelectorParts = array_filter($paletteSelectorParts);
264:
265: $condition = new PaletteConditionChain();
266:
267: foreach ($paletteSelectorParts as $paletteSelectorPart)
268: {
269: // The part is a property name (checkbox like selector).
270: if (in_array($paletteSelectorPart, $selectorFieldNames))
271: {
272: $condition->addCondition(
273: new PalettePropertyTrueCondition($paletteSelectorPart)
274: );
275: }
276: // The part is a value (but which?) (select box like selector).
277: else
278: {
279: $orCondition = new PaletteConditionChain(array(), PaletteConditionChain::OR_CONJUNCTION);
280:
281: foreach ($selectorFieldNames as $selectorFieldName)
282: {
283: $orCondition->addCondition(
284: new PalettePropertyValueCondition(
285: $selectorFieldName,
286: $paletteSelectorPart
287: )
288: );
289: }
290:
291: $condition->addCondition($orCondition);
292: }
293: }
294:
295: return $condition;
296: }
297:
298: /**
299: * Parse the sub palettes and return the properties for each selector property.
300: *
301: * @param array $subpalettes The sub palettes to parse.
302: *
303: * @param array $selectorFieldNames Names of the selector properties [optional].
304: *
305: * @return array(string => PropertyInterface[])
306: */
307: public function parseSubpalettes(array $subpalettes, array $selectorFieldNames = array())
308: {
309: $properties = array();
310:
311: foreach ($subpalettes as $subPaletteSelector => $childFields)
312: {
313: // Child fields list must be a string.
314: if (!is_string($childFields))
315: {
316: continue;
317: }
318:
319: $selectorFieldName = $this->createSubpaletteSelectorFieldName($subPaletteSelector, $selectorFieldNames);
320:
321: $properties[$selectorFieldName] = $this->parseSubpalette(
322: $subPaletteSelector,
323: $childFields,
324: $selectorFieldNames
325: );
326: }
327:
328: return $properties;
329: }
330:
331: /**
332: * Parse the list of sub palette fields into an array of properties.
333: *
334: * @param string $subPaletteSelector The selector in use.
335: *
336: * @param string $childFields List of the properties for the sub palette.
337: *
338: * @param array $selectorFieldNames List of the selector properties [optional].
339: *
340: * @return PropertyInterface[]
341: */
342: public function parseSubpalette($subPaletteSelector, $childFields, array $selectorFieldNames = array())
343: {
344: $childFields = explode(',', $childFields);
345: $childFields = array_map('trim', $childFields);
346:
347: $condition = $this->createSubpaletteCondition($subPaletteSelector, $selectorFieldNames);
348:
349: $properties = array();
350:
351: foreach ($childFields as $childField)
352: {
353: $property = new Property($childField);
354: $property->setVisibleCondition(clone $condition);
355: $properties[] = $property;
356: }
357:
358: return $properties;
359: }
360:
361: /**
362: * Translate a sub palette selector into the real name of a property.
363: *
364: * This method supports the following cases for the sub palette selector:
365: *
366: * Case 1: the sub palette selector contain a combination of "property name" + '_' + value
367: * in which we require that the "property name" is contained within $selectorFieldNames.
368: * In this cases a select/radio sub palette is in place.
369: *
370: * Case 2: the sub palette selector is only a "property name", the value is then implicated to be true.
371: * In this cases a checkbox sub palette is in place.
372: *
373: * @param string $subPaletteSelector The selector being evaluated.
374: *
375: * @param array $selectorFieldNames The names of the properties to be used as selectors [optional].
376: *
377: * @return string
378: */
379: public function createSubpaletteSelectorFieldName($subPaletteSelector, array $selectorFieldNames = array())
380: {
381: $selectorValues = explode('_', $subPaletteSelector);
382: $selectorFieldName = array_shift($selectorValues);
383: $selectorValueCount = count($selectorValues);
384: while ($selectorValueCount)
385: {
386: if (in_array($selectorFieldName, $selectorFieldNames))
387: {
388: break;
389: }
390: $selectorFieldName .= '_' . array_shift($selectorValues);
391: }
392:
393: return $selectorFieldName;
394: }
395:
396: /**
397: * Parse the sub palette selector and create the corresponding condition.
398: *
399: * This method supports the following cases for the sub palette selector:
400: *
401: * Case 1: the sub palette selector contain a combination of "property name" + '_' + value
402: * in which we require that the "property name" is contained within $selectorFieldNames.
403: * In this cases a select/radio sub palette is in place.
404: *
405: * Case 2: the sub palette selector is only a "property name", the value is then implicated to be true.
406: * In this cases a checkbox sub palette is in place.
407: *
408: * @param string $subPaletteSelector The selector being evaluated.
409: *
410: * @param array $selectorFieldNames The names of the properties to be used as selectors [optional].
411: *
412: * @return PropertyTrueCondition|PropertyValueCondition|null
413: */
414: public function createSubpaletteCondition($subPaletteSelector, array $selectorFieldNames = array())
415: {
416: $condition = null;
417:
418: // Try to find a select value first (case 1).
419: $selectorValues = explode('_', $subPaletteSelector);
420: $selectorFieldName = array_shift($selectorValues);
421: $selectorValueCount = count($selectorValues);
422: while ($selectorValueCount)
423: {
424: if (in_array($selectorFieldName, $selectorFieldNames))
425: {
426: $condition = new PropertyValueCondition($selectorFieldName, implode('_', $selectorValues));
427: break;
428: }
429: $selectorFieldName .= '_' . array_shift($selectorValues);
430: }
431:
432: // If case 1 was not successful, try implicitly case 2 must apply.
433: if (!$condition)
434: {
435: $condition = new PropertyTrueCondition($subPaletteSelector);
436: }
437:
438: return $condition;
439: }
440: }
441: