Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Respect editable configuration for standalone editables in headless document #213

Merged
merged 1 commit into from Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
}
}