Skip to content

Commit 60e2052

Browse files
authored
Respect editable configuration for standalone editables in headless document (#213)
1 parent 68c4dc9 commit 60e2052

File tree

9 files changed

+335
-234
lines changed

9 files changed

+335
-234
lines changed

UPGRADE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Upgrade Notes
22

3+
## 5.0.3
4+
- Respect editable configuration for standalone editables in headless document
5+
36
## 5.0.2
47
- Fix element config load priority to allow config overwrites
58

config/services/editable.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ services:
1717
ToolboxBundle\Document\Editable\EditableWorker:
1818
public: true
1919

20+
ToolboxBundle\Document\Editable\ConfigParser: ~
2021
ToolboxBundle\Document\Editable\HeadlessEditableRenderer: ~
2122

2223
ToolboxBundle\Factory\HeadlessEditableInfoFactory: ~

public/css/admin.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,10 @@ body > .single-teaser img {
595595
padding: 3px 0 0 0;
596596
}
597597

598+
.toolbox-element-edit-button.not-configurable:before {
599+
top: -18px;
600+
}
601+
598602
.toolbox-element-edit-button.no-interaction {
599603
height: 26px;
600604
}
@@ -711,6 +715,11 @@ body > .single-teaser img {
711715
[TOOLBOX HEADLESS] Inline Editables - Config Node
712716
*/
713717

718+
.toolbox-headless .toolbox-container {
719+
border: none;
720+
padding: 0;
721+
}
722+
714723
.toolbox-headless .inline-config-area {
715724
border: 1px dashed #d6d6d6;
716725
padding: 10px;
@@ -770,5 +779,5 @@ body > .single-teaser img {
770779
.toolbox-headless .inline-config-area .pimcore_block_entry {
771780
margin: 0 0 20px 0;
772781
border: 1px dotted #6428b44a;
773-
padding: 5px 10px;
782+
padding: 10px;
774783
}

src/Builder/AbstractConfigBuilder.php

Lines changed: 7 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,19 @@
33
namespace ToolboxBundle\Builder;
44

55
use Pimcore\Model\Document\Editable\Area\Info;
6-
use Pimcore\Model\Document\Editable\Checkbox;
76
use Pimcore\Templating\Renderer\EditableRenderer;
87
use Pimcore\Translation\Translator;
8+
use ToolboxBundle\Document\Editable\ConfigParser;
99
use ToolboxBundle\Manager\AreaManagerInterface;
10-
use ToolboxBundle\Registry\StoreProviderRegistryInterface;
1110
use Twig\Environment;
1211

1312
abstract class AbstractConfigBuilder
1413
{
1514
public function __construct(
1615
protected Translator $translator,
1716
protected Environment $templating,
18-
protected StoreProviderRegistryInterface $storeProvider,
1917
protected AreaManagerInterface $areaManager,
18+
protected ConfigParser $configParser,
2019
protected EditableRenderer $editableRenderer
2120
) {
2221
}
@@ -47,8 +46,7 @@ protected function parseConfigElements(
4746

4847
$editableNode = $this->parseConfigElement($info, $configElementName, $elementData, $acStoreProcessed, $brickId, $themeOptions);
4948

50-
//if element need's a store and store is empty: skip field
51-
if ($this->needStore($elementData['type']) && $this->hasValidStore($editableNode['config']) === false) {
49+
if ($editableNode === null) {
5250
continue;
5351
}
5452

@@ -99,34 +97,14 @@ protected function parseConfigElements(
9997
return $editableNodes;
10098
}
10199

102-
private function parseConfigElement(?Info $info, string $elementName, array $elementData, bool $acStoreProcessed, string $brickId, array $themeOptions): array
100+
private function parseConfigElement(?Info $info, string $elementName, array $elementData, bool $acStoreProcessed, string $brickId, array $themeOptions): ?array
103101
{
104-
$editableConfig = $elementData['config'];
105102
$editableType = $elementData['type'];
106103

107-
//set element config data
108-
$parsedNode = $this->parseElementNode($elementName, $elementData, $acStoreProcessed);
104+
$parsedNode = $this->configParser->parseConfigElement($info, $elementName, $elementData, $acStoreProcessed);
109105

110-
//set width
111-
if ($this->canHaveDynamicWidth($editableType)) {
112-
$parsedNode['width'] = $parsedNode['width'] ?? '100%';
113-
} else {
114-
unset($parsedNode['width']);
115-
}
116-
117-
//set height
118-
if ($this->canHaveDynamicHeight($editableType)) {
119-
$parsedNode['height'] = $parsedNode['height'] ?? 200;
120-
} else {
121-
unset($parsedNode['height']);
122-
}
123-
124-
// set default
125-
$parsedNode['config']['defaultValue'] = $this->getSelectedValue($info, $parsedNode, $editableConfig['default'] ?? null);
126-
127-
// check store
128-
if ($this->needStore($editableType) && $this->hasValidStore($editableConfig)) {
129-
$parsedNode['config']['store'] = $this->buildStore($editableType, $editableConfig);
106+
if ($parsedNode === null) {
107+
return null;
130108
}
131109

132110
if ($editableType === 'block' && array_key_exists('children', $elementData) && is_array($elementData['children']) && count($elementData['children']) > 0) {
@@ -136,105 +114,6 @@ private function parseConfigElement(?Info $info, string $elementName, array $ele
136114
return $parsedNode;
137115
}
138116

139-
private function parseElementNode(string $configElementName, array $elementData, bool $acStoreProcessed): array
140-
{
141-
$elementNode = [
142-
'type' => $elementData['type'],
143-
'name' => $configElementName,
144-
'tab' => $elementData['tab'],
145-
'label' => !empty($elementData['title']) ? $elementData['title'] : null,
146-
'description' => !empty($elementData['description']) ? $elementData['description'] : null,
147-
'config' => $elementData['config'] ?? [],
148-
'additional_classes_element' => false,
149-
];
150-
151-
if ($elementData['type'] === 'additionalClasses') {
152-
if ($acStoreProcessed === true) {
153-
throw new \Exception(
154-
sprintf(
155-
'A element of type "additionalClasses" in element "%s" already has been defined. You can only add one field of type "%s" per area. Use "%s" instead.',
156-
$configElementName,
157-
'additionalClasses',
158-
'additionalClassesChained'
159-
)
160-
);
161-
}
162-
163-
$elementNode['type'] = 'select';
164-
$elementNode['label'] = !empty($elementData['title']) ? $elementData['title'] : 'Additional';
165-
$elementNode['additional_classes_element'] = true;
166-
$elementNode['name'] = 'add_classes';
167-
} elseif ($elementData['type'] === 'additionalClassesChained') {
168-
if ($acStoreProcessed === false) {
169-
throw new \Exception(
170-
sprintf(
171-
'You need to add a element of type "%s" before adding a "%s" element.',
172-
'additionalClasses',
173-
'additionalClassesChained'
174-
)
175-
);
176-
}
177-
178-
if (!str_starts_with($configElementName, 'additional_classes_chain_')) {
179-
throw new \Exception(
180-
sprintf(
181-
'Chained AC element name needs to start with "%s" followed by a numeric. "%s" given.',
182-
'additional_classes_chain_',
183-
$configElementName
184-
)
185-
);
186-
}
187-
188-
$chainedElementName = explode('_', $configElementName);
189-
$chainedIncrementor = end($chainedElementName);
190-
if (!is_numeric($chainedIncrementor)) {
191-
throw new \Exception('Chained AC element name must end with an numeric. "' . $chainedIncrementor . '" given.');
192-
}
193-
194-
$elementNode['type'] = 'select';
195-
$elementNode['label'] = !empty($elementData['title']) ? $elementData['title'] : 'Additional';
196-
$elementNode['additional_classes_element'] = true;
197-
$elementNode['name'] = 'add_cclasses_' . $chainedIncrementor;
198-
}
199-
200-
// translate title
201-
if (!empty($elementNode['label'])) {
202-
$elementNode['label'] = $this->translator->trans($elementNode['label'], [], 'admin');
203-
}
204-
205-
// translate description
206-
if (!empty($elementNode['description'])) {
207-
$elementNode['description'] = $this->translator->trans($elementNode['description'], [], 'admin');
208-
}
209-
210-
return $elementNode;
211-
}
212-
213-
private function getSelectedValue(?Info $info, array $config, mixed $defaultConfigValue): mixed
214-
{
215-
if (!$info instanceof Info) {
216-
return $this->castPimcoreEditableValue($config['type'], $defaultConfigValue);
217-
}
218-
219-
$el = $info->getDocumentElement($config['name'], $config['type']);
220-
221-
if ($el === null) {
222-
return $this->castPimcoreEditableValue($config['type'], $defaultConfigValue);
223-
}
224-
225-
// force default (only if it returns false)
226-
// checkboxes may return an empty string and are impossible to track into default mode
227-
if (!empty($defaultConfigValue) && (method_exists($el, 'isEmpty') && $el->isEmpty() === true)) {
228-
$el->setDataFromResource($defaultConfigValue);
229-
}
230-
231-
$value = $el instanceof Checkbox ? $el->isChecked() : $el->getData();
232-
233-
$fallbackAwareValue = !empty($value) ? $value : $defaultConfigValue;
234-
235-
return $this->castPimcoreEditableValue($config['type'], $fallbackAwareValue);
236-
}
237-
238117
private function checkColumnAdjusterField(string $brickId, ?string $tab, array $themeOptions, string $configElementName, array $editableNodes): array
239118
{
240119
if ($brickId !== 'columns') {
@@ -261,107 +140,4 @@ private function checkColumnAdjusterField(string $brickId, ?string $tab, array $
261140
return $editableNodes;
262141
}
263142

264-
private function buildStore($type, $config): array
265-
{
266-
$storeValues = [];
267-
if (isset($config['store'])) {
268-
$storeValues = $config['store'];
269-
} elseif (isset($config['store_provider'])) {
270-
$storeProvider = $this->storeProvider->get($config['store_provider']);
271-
$storeValues = $storeProvider->getValues();
272-
}
273-
274-
if (count($storeValues) === 0) {
275-
throw new \Exception($type . ' has no valid configured store');
276-
}
277-
278-
$store = [];
279-
foreach ($storeValues as $k => $v) {
280-
if (is_array($v)) {
281-
$v = $v['name'];
282-
}
283-
$store[] = [$k, $this->translator->trans($v, [], 'admin')];
284-
}
285-
286-
return $store;
287-
}
288-
289-
private function hasValidStore($parsedConfig): bool
290-
{
291-
if (isset($parsedConfig['store']) && is_array($parsedConfig['store']) && count($parsedConfig['store']) > 0) {
292-
return true;
293-
}
294-
295-
if (isset($parsedConfig['store_provider']) && $this->storeProvider->has($parsedConfig['store_provider'])) {
296-
return true;
297-
}
298-
299-
return false;
300-
}
301-
302-
private function needStore($type): bool
303-
{
304-
return in_array($type, ['select', 'multiselect', 'additionalClasses', 'additionalClassesChained']);
305-
}
306-
307-
private function canHaveDynamicWidth($type): bool
308-
{
309-
return in_array(
310-
$type,
311-
[
312-
'multihref',
313-
'relations',
314-
'href',
315-
'relation',
316-
'image',
317-
'input',
318-
'multiselect',
319-
'numeric',
320-
'embed',
321-
'pdf',
322-
'renderlet',
323-
'select',
324-
'snippet',
325-
'table',
326-
'textarea',
327-
'video',
328-
'wysiwyg',
329-
'parallaximage',
330-
'additionalClasses',
331-
'additionalClassesChained'
332-
]
333-
);
334-
}
335-
336-
private function canHaveDynamicHeight($type): bool
337-
{
338-
return in_array($type, [
339-
'multihref',
340-
'relations',
341-
'image',
342-
'multiselect',
343-
'embed',
344-
'pdf',
345-
'renderlet',
346-
'snippet',
347-
'textarea',
348-
'video',
349-
'wysiwyg',
350-
'parallaximage'
351-
]);
352-
}
353-
354-
private function castPimcoreEditableValue(string $type, mixed $value): mixed
355-
{
356-
// pimcore numeric editable requires string type
357-
if ($type === 'numeric') {
358-
return $value === null ? null : (string) $value;
359-
}
360-
361-
if ($type === 'areablock') {
362-
return $value === null ? null : (is_string($value) ? $value : serialize($value));
363-
}
364-
365-
return $value;
366-
}
367143
}

0 commit comments

Comments
 (0)