Skip to content

Commit

Permalink
Respect editable configuration for standalone editables in headless d…
Browse files Browse the repository at this point in the history
…ocument (#213)
  • Loading branch information
solverat committed Jan 3, 2024
1 parent 68c4dc9 commit 60e2052
Show file tree
Hide file tree
Showing 9 changed files with 335 additions and 234 deletions.
3 changes: 3 additions & 0 deletions UPGRADE.md
@@ -1,5 +1,8 @@
# Upgrade Notes

## 5.0.3
- Respect editable configuration for standalone editables in headless document

## 5.0.2
- Fix element config load priority to allow config overwrites

Expand Down
1 change: 1 addition & 0 deletions config/services/editable.yaml
Expand Up @@ -17,6 +17,7 @@ services:
ToolboxBundle\Document\Editable\EditableWorker:
public: true

ToolboxBundle\Document\Editable\ConfigParser: ~
ToolboxBundle\Document\Editable\HeadlessEditableRenderer: ~

ToolboxBundle\Factory\HeadlessEditableInfoFactory: ~
Expand Down
11 changes: 10 additions & 1 deletion public/css/admin.css
Expand Up @@ -595,6 +595,10 @@ body > .single-teaser img {
padding: 3px 0 0 0;
}

.toolbox-element-edit-button.not-configurable:before {
top: -18px;
}

.toolbox-element-edit-button.no-interaction {
height: 26px;
}
Expand Down Expand Up @@ -711,6 +715,11 @@ body > .single-teaser img {
[TOOLBOX HEADLESS] Inline Editables - Config Node
*/

.toolbox-headless .toolbox-container {
border: none;
padding: 0;
}

.toolbox-headless .inline-config-area {
border: 1px dashed #d6d6d6;
padding: 10px;
Expand Down Expand Up @@ -770,5 +779,5 @@ body > .single-teaser img {
.toolbox-headless .inline-config-area .pimcore_block_entry {
margin: 0 0 20px 0;
border: 1px dotted #6428b44a;
padding: 5px 10px;
padding: 10px;
}
238 changes: 7 additions & 231 deletions src/Builder/AbstractConfigBuilder.php
Expand Up @@ -3,20 +3,19 @@
namespace ToolboxBundle\Builder;

use Pimcore\Model\Document\Editable\Area\Info;
use Pimcore\Model\Document\Editable\Checkbox;
use Pimcore\Templating\Renderer\EditableRenderer;
use Pimcore\Translation\Translator;
use ToolboxBundle\Document\Editable\ConfigParser;
use ToolboxBundle\Manager\AreaManagerInterface;
use ToolboxBundle\Registry\StoreProviderRegistryInterface;
use Twig\Environment;

abstract class AbstractConfigBuilder
{
public function __construct(
protected Translator $translator,
protected Environment $templating,
protected StoreProviderRegistryInterface $storeProvider,
protected AreaManagerInterface $areaManager,
protected ConfigParser $configParser,
protected EditableRenderer $editableRenderer
) {
}
Expand Down Expand Up @@ -47,8 +46,7 @@ protected function parseConfigElements(

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

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

Expand Down Expand Up @@ -99,34 +97,14 @@ protected function parseConfigElements(
return $editableNodes;
}

private function parseConfigElement(?Info $info, string $elementName, array $elementData, bool $acStoreProcessed, string $brickId, array $themeOptions): array
private function parseConfigElement(?Info $info, string $elementName, array $elementData, bool $acStoreProcessed, string $brickId, array $themeOptions): ?array
{
$editableConfig = $elementData['config'];
$editableType = $elementData['type'];

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

//set width
if ($this->canHaveDynamicWidth($editableType)) {
$parsedNode['width'] = $parsedNode['width'] ?? '100%';
} else {
unset($parsedNode['width']);
}

//set height
if ($this->canHaveDynamicHeight($editableType)) {
$parsedNode['height'] = $parsedNode['height'] ?? 200;
} else {
unset($parsedNode['height']);
}

// set default
$parsedNode['config']['defaultValue'] = $this->getSelectedValue($info, $parsedNode, $editableConfig['default'] ?? null);

// check store
if ($this->needStore($editableType) && $this->hasValidStore($editableConfig)) {
$parsedNode['config']['store'] = $this->buildStore($editableType, $editableConfig);
if ($parsedNode === null) {
return null;
}

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

private function parseElementNode(string $configElementName, array $elementData, bool $acStoreProcessed): array
{
$elementNode = [
'type' => $elementData['type'],
'name' => $configElementName,
'tab' => $elementData['tab'],
'label' => !empty($elementData['title']) ? $elementData['title'] : null,
'description' => !empty($elementData['description']) ? $elementData['description'] : null,
'config' => $elementData['config'] ?? [],
'additional_classes_element' => false,
];

if ($elementData['type'] === 'additionalClasses') {
if ($acStoreProcessed === true) {
throw new \Exception(
sprintf(
'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.',
$configElementName,
'additionalClasses',
'additionalClassesChained'
)
);
}

$elementNode['type'] = 'select';
$elementNode['label'] = !empty($elementData['title']) ? $elementData['title'] : 'Additional';
$elementNode['additional_classes_element'] = true;
$elementNode['name'] = 'add_classes';
} elseif ($elementData['type'] === 'additionalClassesChained') {
if ($acStoreProcessed === false) {
throw new \Exception(
sprintf(
'You need to add a element of type "%s" before adding a "%s" element.',
'additionalClasses',
'additionalClassesChained'
)
);
}

if (!str_starts_with($configElementName, 'additional_classes_chain_')) {
throw new \Exception(
sprintf(
'Chained AC element name needs to start with "%s" followed by a numeric. "%s" given.',
'additional_classes_chain_',
$configElementName
)
);
}

$chainedElementName = explode('_', $configElementName);
$chainedIncrementor = end($chainedElementName);
if (!is_numeric($chainedIncrementor)) {
throw new \Exception('Chained AC element name must end with an numeric. "' . $chainedIncrementor . '" given.');
}

$elementNode['type'] = 'select';
$elementNode['label'] = !empty($elementData['title']) ? $elementData['title'] : 'Additional';
$elementNode['additional_classes_element'] = true;
$elementNode['name'] = 'add_cclasses_' . $chainedIncrementor;
}

// translate title
if (!empty($elementNode['label'])) {
$elementNode['label'] = $this->translator->trans($elementNode['label'], [], 'admin');
}

// translate description
if (!empty($elementNode['description'])) {
$elementNode['description'] = $this->translator->trans($elementNode['description'], [], 'admin');
}

return $elementNode;
}

private function getSelectedValue(?Info $info, array $config, mixed $defaultConfigValue): mixed
{
if (!$info instanceof Info) {
return $this->castPimcoreEditableValue($config['type'], $defaultConfigValue);
}

$el = $info->getDocumentElement($config['name'], $config['type']);

if ($el === null) {
return $this->castPimcoreEditableValue($config['type'], $defaultConfigValue);
}

// force default (only if it returns false)
// checkboxes may return an empty string and are impossible to track into default mode
if (!empty($defaultConfigValue) && (method_exists($el, 'isEmpty') && $el->isEmpty() === true)) {
$el->setDataFromResource($defaultConfigValue);
}

$value = $el instanceof Checkbox ? $el->isChecked() : $el->getData();

$fallbackAwareValue = !empty($value) ? $value : $defaultConfigValue;

return $this->castPimcoreEditableValue($config['type'], $fallbackAwareValue);
}

private function checkColumnAdjusterField(string $brickId, ?string $tab, array $themeOptions, string $configElementName, array $editableNodes): array
{
if ($brickId !== 'columns') {
Expand All @@ -261,107 +140,4 @@ private function checkColumnAdjusterField(string $brickId, ?string $tab, array $
return $editableNodes;
}

private function buildStore($type, $config): array
{
$storeValues = [];
if (isset($config['store'])) {
$storeValues = $config['store'];
} elseif (isset($config['store_provider'])) {
$storeProvider = $this->storeProvider->get($config['store_provider']);
$storeValues = $storeProvider->getValues();
}

if (count($storeValues) === 0) {
throw new \Exception($type . ' has no valid configured store');
}

$store = [];
foreach ($storeValues as $k => $v) {
if (is_array($v)) {
$v = $v['name'];
}
$store[] = [$k, $this->translator->trans($v, [], 'admin')];
}

return $store;
}

private function hasValidStore($parsedConfig): bool
{
if (isset($parsedConfig['store']) && is_array($parsedConfig['store']) && count($parsedConfig['store']) > 0) {
return true;
}

if (isset($parsedConfig['store_provider']) && $this->storeProvider->has($parsedConfig['store_provider'])) {
return true;
}

return false;
}

private function needStore($type): bool
{
return in_array($type, ['select', 'multiselect', 'additionalClasses', 'additionalClassesChained']);
}

private function canHaveDynamicWidth($type): bool
{
return in_array(
$type,
[
'multihref',
'relations',
'href',
'relation',
'image',
'input',
'multiselect',
'numeric',
'embed',
'pdf',
'renderlet',
'select',
'snippet',
'table',
'textarea',
'video',
'wysiwyg',
'parallaximage',
'additionalClasses',
'additionalClassesChained'
]
);
}

private function canHaveDynamicHeight($type): bool
{
return in_array($type, [
'multihref',
'relations',
'image',
'multiselect',
'embed',
'pdf',
'renderlet',
'snippet',
'textarea',
'video',
'wysiwyg',
'parallaximage'
]);
}

private function castPimcoreEditableValue(string $type, mixed $value): mixed
{
// pimcore numeric editable requires string type
if ($type === 'numeric') {
return $value === null ? null : (string) $value;
}

if ($type === 'areablock') {
return $value === null ? null : (is_string($value) ? $value : serialize($value));
}

return $value;
}
}

0 comments on commit 60e2052

Please sign in to comment.