Compare commits

..

No commits in common. "master" and "v1.25.1" have entirely different histories.

53 changed files with 334 additions and 1391 deletions

View file

@ -1,41 +1,5 @@
## [Unreleased]
### Added
* feat(repository): add RepositoryQuery::addCaseInsensitiveFilters()
* feat(repository): add RepositoryQuery::addForcedFilterHandler()
### Fixed
* fix(crud/index): fix row attribute value render
* fix(crud/navigation_setting): fix form action
* fix(crud/template): use default route params
## [v1.27.0] - 2025-12-22
### Fixed
* fix(crud): use route params to redirect after a delation
### Added
* add option `removeItemButton: true` when applying choices.js
* feat(builder): allow to define `allowed_widgets` in form options
* feat(collection): add delete_attr, add_attr options
* feat(builder): allow to add block between children
* feat(builder): improve UI to add new block
* feat(settings): allow to edit a setting in plain page
* feat(crud/index): allow to define row attributes
## [v1.26.0] - 2025-03-17
### Added
* FileUploadHandler: allow to upload multiple files
* CrudController: allow to add callables after creation, update and delation
### Fixed
* fix(crud): use context variable to retrieve the form and the form options
* fix(node): use `false` instead of `O` in query
### Changed
* CrudConfiguration::setShowActions: add context when setting this parameter
## [v1.25.2] - 2025-02-07
### Added
* allow to set `data-*` attributes on modal
## [v1.25.1] - 2024-05-13
### Added
* add drag&drop in the block builder

View file

@ -5,7 +5,7 @@ namespace App\Core\BuilderBlock\Block\Bootstrap;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
abstract class BootstrapBlock extends BuilderBlock
class BootstrapBlock extends BuilderBlock
{
public function configure()
{

View file

@ -17,7 +17,7 @@ class ColumnBlock extends BootstrapBlock
->setLabel('Column')
->setIsContainer(true)
->setOrder(3)
->setClass('col-12 col-lg-3 pr-md-1')
->setClass('col-12 col-lg-2 pr-md-1')
->setTemplate('@Core/builder_block/bootstrap/column.html.twig')
->setIcon('<i class="fas fa-columns"></i>')
->addSetting(name: 'size', label: 'Extra small', type: 'number', attributes: ['min' => 0, 'max' => 12])
@ -25,7 +25,6 @@ class ColumnBlock extends BootstrapBlock
->addSetting(name: 'sizeMd', label: 'Medium', type: 'number', attributes: ['min' => 0, 'max' => 12])
->addSetting(name: 'sizeLg', label: 'Large', type: 'number', attributes: ['min' => 0, 'max' => 12])
->addSetting(name: 'sizeXl', label: 'Extra large', type: 'number', attributes: ['min' => 0, 'max' => 12])
->addSetting(name: 'customClass', label: 'Custom class', type: 'text')
;
}
}

View file

@ -20,7 +20,6 @@ class ContainerBlock extends BootstrapBlock
->setTemplate('@Core/builder_block/bootstrap/container.html.twig')
->setIcon('<i class="fas fa-th"></i>')
->addSetting(name: 'isFluid', label: 'Fluid', type: 'checkbox')
->addSetting(name: 'customClass', label: 'Custom class', type: 'text')
;
}
}

View file

@ -20,7 +20,6 @@ class RowBlock extends BootstrapBlock
->setIcon('<i class="fas fa-align-justify"></i>')
->setTemplate('@Core/builder_block/bootstrap/row.html.twig')
->addWidget('bsColumn')
->addSetting(name: 'customClass', label: 'Custom class', type: 'text')
;
}
}

View file

@ -5,10 +5,10 @@ namespace App\Core\BuilderBlock\Block\Editor;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
abstract class EditorBlock extends BuilderBlock
class EditorBlock extends BuilderBlock
{
public function configure()
{
$this->setCategory('Editors');
$this->setCategory('Editor');
}
}

View file

@ -21,7 +21,6 @@ class TextareaBlock extends EditorBlock
->addSetting(name: 'nl2br', label: 'Insert line breaks', type: 'checkbox', default: true)
->addSetting(name: 'allowHtml', label: 'Allow HTML', type: 'checkbox', default: false)
->addSetting(name: 'value', type: 'textarea')
->setPreview('value')
;
}
}

View file

@ -19,7 +19,6 @@ class TinymceBlock extends EditorBlock
->setIcon('<i class="fas fa-pencil-alt"></i>')
->setTemplate('@Core/builder_block/editor/tinymce.html.twig')
->addSetting(name: 'value', type: 'textarea', attributes: ['data-tinymce' => ''])
->setPreview('value')
;
}
}

View file

@ -12,7 +12,6 @@ abstract class BuilderBlock
protected array $widgets = [];
protected array $vars = [];
protected string $template = '';
protected ?string $preview = null;
protected bool $isContainer = false;
protected ?string $icon = null;
protected int $order = 1;
@ -136,6 +135,19 @@ abstract class BuilderBlock
return $this->template;
}
public function toArray(): array
{
return [
'label' => $this->getLabel(),
'category' => $this->getCategory(),
'isContainer' => $this->getIsContainer(),
'widgets' => $this->getWidgets(),
'settings' => $this->getSettings(),
'class' => $this->getClass(),
'icon' => $this->getIcon(),
];
}
public function setClass(?string $class): self
{
$this->class = $class;
@ -172,19 +184,7 @@ abstract class BuilderBlock
return $this->order;
}
public function setPreview(?string $preview): self
{
$this->preview = $preview;
return $this;
}
public function getPreview(): ?string
{
return $this->preview;
}
public function buildVars(array $data, array $context)
public function buildVars(array $data)
{
}
@ -192,18 +192,4 @@ abstract class BuilderBlock
{
return $this->vars;
}
public function toArray(): array
{
return [
'label' => $this->getLabel(),
'category' => $this->getCategory(),
'isContainer' => $this->getIsContainer(),
'widgets' => $this->getWidgets(),
'settings' => $this->getSettings(),
'class' => $this->getClass(),
'icon' => $this->getIcon(),
'preview' => $this->getPreview(),
];
}
}

View file

@ -6,20 +6,11 @@ class BuilderBlockContainer
{
protected array $widgets = [];
public function addWidget(BuilderBlock $widget): self
public function addWidget(BuilderBlock $widget): void
{
$widget->configure();
$this->widgets[$widget->getName()] = $widget;
return $this;
}
public function removeWidget(string $name)
{
unset($this->widgets[$name]);
return $this;
}
public function getWidgets(): array
@ -29,11 +20,6 @@ class BuilderBlockContainer
return $this->widgets;
}
public function hasWidget(string $name)
{
return isset($this->widgets[$name]);
}
public function getWidget(string $name): BuilderBlock
{
return $this->widgets[$name];

View file

@ -26,13 +26,7 @@ abstract class CrudController extends AdminController
abstract protected function getConfiguration(): CrudConfiguration;
protected function doIndex(
int $page,
RepositoryQuery $query,
Request $request,
Session $session,
string $context = 'index'
): Response
protected function doIndex(int $page, RepositoryQuery $query, Request $request, Session $session, string $context = 'index'): Response
{
$configuration = $this->getConfiguration();
@ -56,20 +50,13 @@ abstract class CrudController extends AdminController
]);
}
protected function doNew(
EntityInterface $entity,
EntityManager $entityManager,
Request $request,
callable $beforeCreate = null,
callable $afterCreate = null,
string $context = 'new'
): Response
protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeCreate = null, string $context = 'new'): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm($context), $entity, $configuration->getFormOptions($context));
$form = $this->createForm($configuration->getForm('new'), $entity, $configuration->getFormOptions($context));
if ($request->isMethod('POST')) {
$form->handleRequest($request);
@ -80,11 +67,6 @@ abstract class CrudController extends AdminController
}
$entityManager->create($entity);
if (null !== $afterCreate) {
call_user_func_array($afterCreate, [$entity, $form, $request]);
}
$this->addFlash('success', 'The data has been saved.');
return $this->redirectToRoute($configuration->getPageRoute('edit'), array_merge(
@ -114,20 +96,13 @@ abstract class CrudController extends AdminController
]);
}
protected function doEdit(
EntityInterface $entity,
EntityManager $entityManager,
Request $request,
callable $beforeUpdate = null,
callable $afterUpdate = null,
string $context = 'edit'
): Response
protected function doEdit(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null, string $context = 'edit'): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm($context), $entity, $configuration->getFormOptions($context));
$form = $this->createForm($configuration->getForm('edit'), $entity, $configuration->getFormOptions($context));
if ($request->isMethod('POST')) {
$form->handleRequest($request);
@ -138,11 +113,6 @@ abstract class CrudController extends AdminController
}
$entityManager->update($entity);
if (null !== $afterUpdate) {
call_user_func_array($afterUpdate, [$entity, $form, $request]);
}
$this->addFlash('success', 'The data has been saved.');
return $this->redirectToRoute($configuration->getPageRoute($context), array_merge(
@ -161,15 +131,7 @@ abstract class CrudController extends AdminController
]);
}
protected function doInlineEdit(
string $context,
string $label,
EntityInterface $entity,
EntityManager $entityManager,
Request $request,
callable $beforeUpdate = null,
callable $afterUpdate = null
): Response
protected function doInlineEdit(string $context, string $label, EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null): Response
{
$configuration = $this->getConfiguration();
@ -230,11 +192,6 @@ abstract class CrudController extends AdminController
$session->remove($lastRequestId);
$entityManager->update($entity);
if (null !== $afterUpdate) {
call_user_func_array($afterUpdate, [$entity, $form, $request]);
}
$this->addFlash('success', 'The data has been saved.');
return $this->redirect($redirectTo);
@ -260,13 +217,7 @@ abstract class CrudController extends AdminController
]);
}
protected function doSort(
int $page,
RepositoryQuery $query,
EntityManager $entityManager,
Request $request,
Session $session
): Response
protected function doSort(int $page, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
$context = $request->query->get('context', 'index');
@ -304,13 +255,7 @@ abstract class CrudController extends AdminController
return $this->json([]);
}
protected function doBatch(
int $page,
RepositoryQuery $query,
EntityManager $entityManager,
Request $request,
Session $session
): Response
protected function doBatch(int $page, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
$datas = $request->request->get('batch', []);
@ -381,14 +326,7 @@ abstract class CrudController extends AdminController
return $this->json([]);
}
protected function doDelete(
EntityInterface $entity,
EntityManager $entityManager,
Request $request,
callable $beforeDelete = null,
callable $afterDelete = null,
string $route = 'index'
): Response
protected function doDelete(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeDelete = null, string $route = 'index'): Response
{
$configuration = $this->getConfiguration();
@ -399,26 +337,22 @@ abstract class CrudController extends AdminController
$entityManager->delete($entity);
if (null !== $afterDelete) {
call_user_func($afterDelete, $entity);
}
$this->addFlash('success', 'The data has been removed.');
}
return $this->redirectToRoute($configuration->getPageRoute($route), $configuration->getPageRouteParams($route));
return $this->redirectToRoute($configuration->getPageRoute($route));
}
protected function doFilter(Session $session, string $context = 'filter'): Response
{
$configuration = $this->getConfiguration();
$type = $configuration->getForm($context);
$type = $configuration->getForm('filter');
if (null === $type) {
throw $this->createNotFoundException();
}
$form = $this->createForm($type, null, $configuration->getFormOptions($context));
$form = $this->createForm($type, null, $configuration->getFormOptions('filter'));
$form->submit($session->get($form->getName(), []));
return $this->render($configuration->getView($context), [

View file

@ -30,7 +30,6 @@ class BuilderBlockController extends AbstractController
protected function translate(array $data)
{
$data['label'] = $this->translator->trans($data['label']);
$data['category'] = $this->translator->trans($data['category']);
foreach ($data['settings'] as $key => $value) {
$data['settings'][$key]['label'] = $this->translator->trans($data['settings'][$key]['label']);

View file

@ -35,8 +35,6 @@ class NavigationSettingAdminController extends AdminController
$session = $request->getSession();
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId);
$options = $entity->getOptions();
$optionView = $options['view'] ?? 'modal';
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
@ -66,19 +64,17 @@ class NavigationSettingAdminController extends AdminController
$session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.');
if ($optionView === 'modal') {
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->render('@Core/setting/navigation_setting_admin/edit.html.twig', [
'form' => $form->createView(),
'entity' => $entity,
'options' => $options,
'options' => $event->getData()['options'],
'redirectTo' => $redirectTo,
]);
}

View file

@ -55,8 +55,6 @@ class SettingAdminController extends AdminController
$session = $request->getSession();
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId);
$options = $entity->getOptions();
$optionView = $options['view'] ?? 'modal';
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
@ -84,13 +82,11 @@ class SettingAdminController extends AdminController
$session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.');
if ($optionView === 'modal') {
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->render('@Core/setting/setting_admin/edit.html.twig', [

View file

@ -2,8 +2,6 @@
namespace App\Core\Crud;
use App\Core\Entity\EntityInterface;
/**
* class CrudConfiguration.
*
@ -29,8 +27,7 @@ class CrudConfiguration
protected array $isSortableCollection = [];
protected string $sortableCollectionProperty = 'sortOrder';
protected ?string $defaultLocale = null;
protected array $showActions = [];
protected array $listRowAttributes = [];
protected bool $showActions = true;
protected static $self;
@ -250,26 +247,6 @@ class CrudConfiguration
return $this->viewDatas[$context][$name] ?? $defaultValue;
}
public function setListRowAttributes(string $context, array $attributes): self
{
$this->listRowAttributes[$context] = $attributes;
return $this;
}
public function getListRowAttributes(string $context, EntityInterface $entity): array
{
$attributes = $this->listRowAttributes[$context] ?? [];
foreach ($attributes as $key => $attribute) {
if (is_callable($attribute)) {
$attributes[$key] = $attribute($entity);
}
}
return $attributes;
}
// --
public function setField(string $context, string $label, string $field, array $options): self
@ -392,15 +369,15 @@ class CrudConfiguration
return $this->sortableCollectionProperty;
}
public function setShowActions(string $page, bool $showActions): self
public function setShowActions(bool $showActions): self
{
$this->showActions[$page] = $showActions;
$this->showActions = $showActions;
return $this;
}
public function getShowActions(string $page): bool
public function getShowActions(): bool
{
return $this->showActions[$page] ?? true;
return $this->showActions;
}
}

View file

@ -26,9 +26,6 @@ class NavigationSetting implements EntityInterface
#[ORM\Column(type: 'text', nullable: true)]
protected $value;
#[ORM\Column(type: 'text', nullable: true)]
protected $options;
#[ORM\ManyToOne(targetEntity: Navigation::class, inversedBy: 'navigationSettings')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
protected $navigation;
@ -97,16 +94,4 @@ class NavigationSetting implements EntityInterface
return $this;
}
public function getOptions()
{
return json_decode($this->options, true) ?? [];
}
public function setOptions(?array $options): self
{
$this->options = json_encode($options ?? []);
return $this;
}
}

View file

@ -25,9 +25,6 @@ class Setting implements EntityInterface
#[ORM\Column(type: 'text', nullable: true)]
protected $value;
#[ORM\Column(type: 'text', nullable: true)]
protected $options;
public function getId(): ?int
{
return $this->id;
@ -80,16 +77,4 @@ class Setting implements EntityInterface
return $this;
}
public function getOptions()
{
return json_decode($this->options, true) ?? [];
}
public function setOptions(?array $options): self
{
$this->options = json_encode($options ?? []);
return $this;
}
}

View file

@ -233,7 +233,7 @@ class FsFileManager
$directory .= '/'.trim(dirname($fullPaths[$key]), '/');
}
$this->uploadHandler->handleForm($file, $directory, keepOriginalFilename: true);
$this->uploadHandler->handleForm($file, $directory, null, true);
}
}

View file

@ -20,53 +20,27 @@ class FileUploadHandler
return $this;
}
public function handleForm(
null|array|UploadedFile $uploadedFile,
string $path,
?callable $afterUploadCallback = null,
?callable $afterUploadsCallback = null,
bool $keepOriginalFilename = false
): null|array|string
public function handleForm(?UploadedFile $uploadedFile, string $path, ?callable $afterUploadCallback = null, bool $keepOriginalFilename = false): void
{
if (null === $uploadedFile) {
return null;
return;
}
if (is_array($uploadedFile)) {
$filenames = [];
$originalFilename = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
foreach ($uploadedFile as $file) {
$filename = $this->handleForm($file, $path, $afterUploadCallback, null, $keepOriginalFilename);
if ($filename !== null) {
$filenames[] = $filename;
}
}
if (!empty($filenames) && $afterUploadsCallback) {
$afterUploadsCallback($filenames);
}
return $filenames;
if ($keepOriginalFilename) {
$filename = $originalFilename.'.'.$uploadedFile->guessExtension();
} elseif (!is_callable($this->filenameGenerator)) {
$safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
$filename = date('Ymd-his').$safeFilename.'.'.$uploadedFile->guessExtension();
} else {
$originalFilename = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
$filename = call_user_func($this->filenameGenerator, $uploadedFile);
}
if ($keepOriginalFilename) {
$filename = $originalFilename.'.'.$uploadedFile->guessExtension();
} elseif (!is_callable($this->filenameGenerator)) {
$safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
$filename = date('Ymd-his').$safeFilename.'.'.$uploadedFile->guessExtension();
} else {
$filename = call_user_func($this->filenameGenerator, $uploadedFile);
}
$uploadedFile->move($path, $filename);
$uploadedFile->move($path, $filename);
if ($afterUploadCallback) {
$afterUploadCallback($filename);
}
return $filename;
if ($afterUploadCallback) {
$afterUploadCallback($filename);
}
}
}

View file

@ -19,7 +19,6 @@ class BuilderType extends AbstractType
parent::buildView($view, $form, $options);
$view->vars = array_replace($view->vars, [
'allowed_widgets' => $options['allowed_widgets'],
]);
}
@ -29,10 +28,7 @@ class BuilderType extends AbstractType
$resolver->setDefaults([
'compound' => false,
'allowed_widgets' => [],
]);
$resolver->setAllowedTypes('allowed_widgets', 'array');
}
public function getBlockPrefix()

View file

@ -16,23 +16,12 @@ class CollectionType extends BaseCollectionType
{
parent::buildView($view, $form, $options);
$classes = [
'add_attr' => 'collection-add',
'delete_attr' => 'text-right',
];
foreach ($classes as $key => $class) {
$options[$key]['class'] = $class.' '.($options[$key]['class'] ?? '');
}
$view->vars = array_replace($view->vars, [
'collection_name' => $options['collection_name'],
'label_add' => $options['label_add'],
'label_delete' => $options['label_delete'],
'allow_add' => $options['allow_add'],
'allow_delete' => $options['allow_delete'],
'add_attr' => $options['add_attr'],
'delete_attr' => $options['delete_attr'],
'template_before_item' => $options['template_before_item'],
'template_after_item' => $options['template_after_item'],
]);
@ -48,8 +37,6 @@ class CollectionType extends BaseCollectionType
'label_delete' => 'Delete',
'template_before_item' => null,
'template_after_item' => null,
'add_attr' => [],
'delete_attr' => [],
]);
}

View file

@ -1,97 +0,0 @@
<?php
namespace App\Core\Maker;
use Doctrine\Common\Annotations\Annotation;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Filesystem\Filesystem;
class MakeBuilderBlock extends AbstractMaker
{
public static function getCommandName(): string
{
return 'make:builder-block';
}
public static function getCommandDescription(): string
{
return 'Creates a new builder block class';
}
public function configureCommand(Command $command, InputConfiguration $inputConf)
{
$command
->addArgument(
'builder-block-class',
InputArgument::OPTIONAL,
'Choose a name for your block class (e.g. <fg=yellow>ExampleBlock</>)'
)
->setHelp('')
;
}
public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
{
$blockClassNameDetails = $generator->createClassNameDetails(
$input->getArgument('builder-block-class'),
'BuilderBlock\\',
'Block'
);
$templatePath = sprintf(
'builder_block/%s.html.twig',
Str::asSnakeCase(preg_replace('/Block$/', '', $blockClassNameDetails->getShortName()))
);
$options = [
'entity' => $blockClassNameDetails->getFullName(),
'template' => $templatePath,
'label' => Str::asHumanWords($blockClassNameDetails->getShortName())
];
$blockPath = $generator->generateController(
$blockClassNameDetails->getFullName(),
__DIR__.'/../Resources/maker/builder/Block.tpl.php',
$options
);
$generator->writeChanges();
$realTemplatePath = 'templates/'.$templatePath;
$filesystem = new Filesystem();
if (!$filesystem->exists($templatePath)) {
$filesystem->mkdir(dirname($realTemplatePath));
$filesystem->dumpFile($realTemplatePath, $this->getTemplate());
$io->comment(sprintf('<fg=blue>created</>: %s', $realTemplatePath));
}
$this->writeSuccessMessage($io);
}
protected function getTemplate(): string
{
return <<< EOF
<div id="{{ id }}">
{% for item in children %}
{{ item|block_to_html(context) }}
{% endfor %}
</div>
EOF;
}
public function configureDependencies(DependencyBuilder $dependencies)
{
}
}

View file

@ -149,7 +149,6 @@ EOF
'textarea' => null,
'choice' => 'BlockEntity\\ChoiceBlock::class',
'collection' => 'BlockEntity\\CollectionBlock::class',
'builder' => 'BlockEntity\\BuilderBlock::class',
'editor_js_textarea' => null,
'file' => 'BlockEntity\\FileBlock::class',
'file_picker' => null,

View file

@ -3,7 +3,7 @@
namespace App\Core;
if (!defined('MURPH_VERSION')) {
define('MURPH_VERSION', 'v1.27.0');
define('MURPH_VERSION', 'v1.25.1');
}
/**

View file

@ -18,7 +18,6 @@ abstract class RepositoryQuery
protected PaginatorInterface $paginator;
protected string $id;
protected array $forcedFilterHandlers = [];
protected array $caseInsensitiveFilters = [];
public function __construct(ServiceEntityRepository $repository, string $id, PaginatorInterface $paginator = null)
{
@ -89,11 +88,7 @@ abstract class RepositoryQuery
$this->andWhere('.'.$name.' = :'.$name);
$this->setParameter(':'.$name, $value);
} elseif (is_string($value)) {
if (in_array($name, $this->caseInsensitiveFilters)) {
$this->andWhere(sprintf('LOWER ( .%1$s) LIKE LOWER(:%1$s)', $name));
} else {
$this->andWhere('.'.$name.' LIKE :'.$name);
}
$this->andWhere('.'.$name.' LIKE :'.$name);
$this->setParameter(':'.$name, '%'.$value.'%');
} else {
$this->filterHandler($name, $value);
@ -147,18 +142,4 @@ abstract class RepositoryQuery
protected function filterHandler(string $name, $value)
{
}
protected function addCaseInsensitiveFilters(string $name): self
{
$this->caseInsensitiveFilters[] = $name;
return $this;
}
protected function addForcedFilterHandlers(string $name): self
{
$this->forcedFilterHandlers[] = $name;
return $this;
}
}

View file

@ -18,7 +18,7 @@ class NodeRepository extends NestedTreeRepository
$query = $this->createQueryBuilder('n')
->join('n.menu', 'm')
->where('n.url = :url')
->andWhere('n.disableUrl = false')
->andWhere('n.disableUrl = 0')
->andWhere('n.aliasNode is null')
->andWhere('m.navigation = :navigation')
->setParameter(':url', $url)

View file

@ -761,25 +761,23 @@ label.required::after {
}
.builder-widget {
.container {
max-width: 100%;
}
.block {
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;
padding: 15px;
border: 1px solid rgba(map-get($theme-colors, 'dark-blue'), 0.3);
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
background: rgba(map-get($theme-colors, 'dark-blue'), 0.02);
}
> .block {
box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 4px;
border: 1px solid map-get($theme-colors, 'dark-blue');
}
.block-header {
.block-header-item {
font-size: 12px;
display: inline-block;
margin-bottom: 10px;
padding: 2px 6px;
border-radius: 4px;
margin-right: 2px;
@ -787,55 +785,21 @@ label.required::after {
}
}
.block .block-icon {
> * {
display: inline-block;
margin-right: 3px;
}
.block-label {
background: map-get($theme-colors, 'dark-blue');
border: 1px solid map-get($theme-colors, 'dark-blue');
color: lighten(map-get($theme-colors, 'dark-blue'), 100%);
}
.builder-add {
&-top {
margin-top: 7px;
}
&-button {
cursor: pointer;
background: rgba(map-get($theme-colors, 'dark-blue'), 0.1);
text-align: center;
padding-bottom: 5px;
margin: 8px 0;
border-radius: 4px;
&:hover {
background: rgba(map-get($theme-colors, 'dark-blue'), 0.2);
}
.btn {
font-size: 12px;
line-height: 14px;
padding: 3px 5px;
}
}
}
.block-root {
border: 1px solid map-get($theme-colors, 'dark-blue');
box-shadow: none;
}
.block-root > .container .builder-add {
margin-top: 0;
.block-settings-inverse {
background: none;
border: 1px solid map-get($theme-colors, 'dark-blue');
color: map-get($theme-colors, 'dark-blue');
}
.block-settings {
padding: 4px;
margin-top: 10px;
margin-bottom: 5px;
.form-control {
margin-top: 0.5rem !important;
}
}
.block-id {
@ -848,52 +812,4 @@ label.required::after {
min-height: 40px;
}
}
.block-preview {
white-space: nowrap;
max-width: 30%;
text-overflow: ellipsis;
overflow: hidden;
}
.builder-code-editor {
border: 0;
padding: 0;
background: none;
position: fixed;
top: 15px;
textarea {
font-family: Monospace;
min-height: 50vh;
}
}
.dragger {
cursor: pointer;
color: #6c757d;
border-color: #6c757d;
text-align: center;
vertical-align: middle;
}
$block-colors: #E183F5 #E3F7C6 #82DDF5 #F5BA82 #A088A6;
$block-colors-length: length($block-colors);
@for $i from 1 through 100 {
$block-color-index: ($block-colors-length + $i) % $block-colors-length + 1;
.block-depth-#{$i} {
.block-label {
background: nth($block-colors, $block-color-index);
border: 1px solid darken(nth($block-colors, $block-color-index), 50%);
color: darken(nth($block-colors, $block-color-index), 50%);
}
.builder-add-button:hover {
background: nth($block-colors, $block-color-index);
color: darken(nth($block-colors, $block-color-index), 50%);
}
}
}
}

View file

@ -1,77 +1,39 @@
<template>
<div
class="block block-root"
v-if="Object.keys(widgets).length && value !== null"
<Draggable
v-if="Object.keys(widgets).length"
v-model="value"
:key="blockKey"
:animation="200"
group="children"
ghost-class="ghost"
@start="dragStart"
@end="dragEnd"
handle=".dragger"
:class="{'block-show-dropzone': showDragDrop}"
class="block"
>
<BuilderBlockItem
v-for="(block, key) in value"
:key="block.id + '-' + key"
:item="block"
:widgets="widgets"
@remove-item="removeBlock(key)"
@drag-start="dragStart"
@drag-end="dragEnd"
/>
<div class="container">
<BuilderBlockCreate
:container="value"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="allowedWidgets"
v-if="value.length > 0"
position="top"
:allowedWidgets="[]"
/>
</div>
<Draggable
v-model="value"
:key="blockKey"
:animation="200"
group="children"
ghost-class="ghost"
@start="dragStart"
@end="dragEnd"
handle=".dragger"
:class="{'block-show-dropzone': showDragDrop}"
>
<BuilderBlockItem
v-for="(block, key) in value"
:key="block.id + '-' + key"
:item="block"
:widgets="widgets"
:openedBlocks="openedBlocks"
:depth="1"
@remove-item="removeBlock(key)"
@drag-start="dragStart"
@drag-end="dragEnd"
>
<template #action v-if="(key+1) !== value.length">
<BuilderBlockCreate
:container="value"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="allowedWidgets"
:position="key"
/>
</template>
</BuilderBlockItem>
<div class="container">
<BuilderBlockCreate
:container="value"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="allowedWidgets"
position="bottom"
/>
<div class="text-right">
<div>
<BuilderBlockCodeEditor
ref="dialog"
:value="value"
:widgets="widgets"
@update="codeUpdate"
/>
</div>
</div>
</div>
<textarea :name="name" class="d-none">{{ toJson(value) }}</textarea>
</Draggable>
</div>
<textarea :name="name" class="d-none">{{ toJson(value) }}</textarea>
</Draggable>
</template>
<script>
import BuilderBlockItem from './BuilderBlockItem'
import BuilderBlockCodeEditor from './BuilderBlockCodeEditor'
import BuilderBlockCreate from './BuilderBlockCreate'
import Routing from '../../../../../../../../../friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js'
import Draggable from 'vuedraggable'
@ -82,12 +44,6 @@ Routing.setRoutingData(routes)
export default {
name: 'BuilderBlock',
components: {
BuilderBlockItem,
BuilderBlockCreate,
Draggable,
BuilderBlockCodeEditor,
},
props: {
id: {
type: String,
@ -100,19 +56,12 @@ export default {
initialValue: {
type: Array,
required: false,
},
allowedWidgets: {
type: Array,
required: false,
default: [],
}
},
data() {
return {
value: null,
nextValue: null,
value: this.initialValue,
widgets: {},
openedBlocks: {},
blockKey: 0,
showDragDrop: false,
}
@ -124,9 +73,6 @@ export default {
triggerBuilderBlockEvent() {
document.querySelector('body').dispatchEvent(new Event('builder_block.update'))
},
codeUpdate(nextValue) {
this.value = nextValue
},
removeBlock(key) {
let newValue = []
@ -148,42 +94,19 @@ export default {
++this.blockKey
},
fixSettings(data) {
if (Array.isArray(data)) {
data.forEach((value, key) => {
data[key] = this.fixSettings(value)
})
} else {
const widget = this.widgets[data.widget]
if (!widget) {
return data
}
const nextSettings = {}
for (let setting in widget.settings) {
if (data.settings.hasOwnProperty(setting)) {
nextSettings[setting] = data.settings[setting]
} else {
nextSettings[setting] = widget.settings[setting].default
}
}
data.settings = nextSettings
data.children = this.fixSettings(data.children)
}
return data
},
},
components: {
BuilderBlockItem,
BuilderBlockCreate,
Draggable,
},
mounted() {
this.triggerBuilderBlockEvent()
const that = this
axios.get(Routing.generate('admin_editor_builder_block_widgets'))
.then((response) => {
that.widgets = response.data
that.value = that.fixSettings(that.initialValue)
})
},
created() {

View file

@ -1,163 +0,0 @@
<template>
<div>
<button
type="button"
class="btn btn-sm"
v-on:click="open"
>
<i class="fas fa-code"></i>
</button>
<dialog ref="dialog" class="modal-dialog modal-dialog-large builder-code-editor">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Code editor</h5>
<button
type="button"
class="close"
v-on:click="close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<textarea
class="form-control"
rows="10"
ref="codeEditor"
v-model="nextValue"
>
</textarea>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-secondary"
v-on:click="close"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
v-on:click="checkAndSaveNextValue"
>
Save
</button>
</div>
</div>
</dialog>
</div>
</template>
<script>
export default {
name: 'BuilderBlockCodeEditor',
props: {
value: {
type: Array,
required: true,
},
widgets: {
type: Object,
required: true
},
},
data() {
return {
nextValue: null
}
},
methods: {
toJson(value) {
return JSON.stringify(value, null, 2)
},
open() {
this.nextValue = this.toJson(this.value)
this.$refs.dialog.showModal()
},
close() {
this.$refs.codeEditor.classList.toggle('is-invalid', false)
this.$refs.dialog.close()
},
isNextValueItemValueValid(item) {
const hasId = typeof item.id === 'string'
const hasWidget = typeof item.widget === 'string' && this.widgets[item.widget]
const hasSettings = typeof item.settings === 'object'
const hasChildren = Array.isArray(item.children)
if (!hasId || !hasWidget || !hasSettings || !hasChildren) {
return false
}
for (let child of item.children) {
if (!this.isNextValueItemValueValid(child)) {
return false
}
}
return true
},
updateNextValueRecursiveIds(data) {
if (Array.isArray(data)) {
data.forEach((value, key) => {
data[key] = this.updateNextValueRecursiveIds(value)
})
} else {
data.id = this.makeId()
data.children = this.updateNextValueRecursiveIds(data.children)
}
return data
},
checkAndSaveNextValue() {
this.$refs.codeEditor.classList.toggle('is-invalid', false)
let hasError = false
try {
let data = JSON.parse(this.nextValue)
if (!Array.isArray(data)) {
hasError = true
} else {
for (let item of data) {
if (!this.isNextValueItemValueValid(item)) {
hasError = true
}
}
}
if (!hasError) {
this.$emit('update', this.updateNextValueRecursiveIds(data))
this.close()
}
} catch (e) {
hasError = true
}
return this.$refs.codeEditor.classList.toggle('is-invalid', hasError)
},
makeId() {
let result = ''
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'
const charactersLength = characters.length
for (let i = 0; i < 7; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return `block-${result}`
},
},
components: {
},
mounted() {
},
created() {
},
updated() {
}
}
</script>

View file

@ -1,92 +1,77 @@
<style scoped>
.builder-block-picker {
padding: 8px;
border: 1px solid #333;
border-radius: 4px;
background: #fff;
.categories {
padding: 10px;
text-align: left;
}
.builder-block-picker-menu {
width: 150px;
.category {
padding: 10px 0;
}
.builder-block-picker-widgets {
width: calc(100% - 150px - 10px);
padding-left: 5px;
}
.nav-item {
cursor: pointer;
width: 100%;
}
.widget-icon {
margin-right: 3px;
.category-label {
font-weight: bold;
margin-bottom: 5px;
}
.widget {
min-width: 100px;
}
.widget-content {
background: #fff;
padding: 10px;
text-align: center;
border-radius: 4px;
cursor: pointer;
margin-right: 7px;
margin-bottom: 9px;
border: 1px solid #b4b4b4;
font-weight: bold;
margin-right: 5px;
margin-bottom: 5px;
border: 1px solid #1e2430;
}
.widget-icon {
margin-top: 5px;
font-size: 25px;
}
.widget:hover {
background: #eee;
border: 1px solid #1e2430;
}
.search {
max-width: 300px;
.widget-label {
font-weight: bold;
}
</style>
<template>
<div class="builder-add" :class="{'builder-add-top': position === 'top'}">
<div class="builder-add-button" v-on:click="togglePicker">
+
</div>
<div class="builder-add">
<span class="btn btn-sm btn-secondary" v-on:click="togglePicker">
<span class="fa fa-plus"></span>
</span>
<div class="builder-block-picker mt-2 row" :class="{'d-none': !showPicker}">
<div class="col-auto builder-block-picker-menu">
<ul class="nav nav-pills pl-0">
<li
v-for="(category, key) in categories()"
v-if="Object.keys(category.widgets).length"
class="nav-item d-block"
>
<a
class="nav-link d-block mb-1"
:class="{'active': activeCategory == key}"
v-on:click="activeCategory = key"
>
{{ category.label }}
</a>
</li>
</ul>
</div>
<div class="categories mt-2" :class="{'d-none': !showPicker}">
<div
v-for="(category, key) in categories()"
v-for="category in categories()"
v-if="Object.keys(category.widgets).length"
class="col-auto builder-block-picker-widgets"
:class="{'d-none': activeCategory !== key}"
class="category row"
>
<div class="row">
<div class="col-12 mb-4">
<input v-model="search" placeholder="Type to search..." class="form-control search">
</div>
<div
v-for="(widget, name) in category.widgets"
v-on:click="add(name, widget)"
v-if="matchSearch(widget.label) || matchSearch(name)"
class="widget col-auto"
>
<span class="widget-icon" v-if="widget.icon" v-html="widget.icon"></span>
{{ widget.label }}
<div
v-if="category.label != 'none'"
v-text="category.label"
class="category-label col-12"
></div>
<div
v-for="(widget, name) in category.widgets"
v-on:click="add(name, widget)"
class="widget col-auto"
>
<div class="widget-content">
<div class="widget-label">
{{ widget.label }}
</div>
<div class="widget-icon" v-if="widget.icon" v-html="widget.icon">
</div>
</div>
</div>
</div>
@ -111,20 +96,10 @@ export default {
type: Array,
required: true
},
openedBlocks: {
type: Object,
required: true
},
position: {
type: [String, Number],
required: true
},
},
data() {
return {
showPicker: false,
activeCategory: 'all',
search: '',
}
},
methods: {
@ -135,25 +110,14 @@ export default {
settings[i] = widget.settings[i].default
}
const block = {
this.container.push({
id: this.makeId(),
widget: name,
settings,
children: [],
}
if (this.position === 'bottom') {
this.container.push(block)
this.$emit('updateContainer', this.container)
} else if (this.position === 'top') {
this.container.unshift(block)
} else {
this.container.splice(this.position+1, 0, block)
}
})
this.$emit('updateContainer', this.container)
this.openedBlocks[block.id] = true
this.togglePicker()
},
makeId() {
@ -167,26 +131,17 @@ export default {
return `block-${result}`
},
matchSearch(name) {
if (!this.search.trim().length) {
return true
}
return name.toLowerCase().includes(this.search.toLowerCase())
},
togglePicker() {
this.showPicker = !this.showPicker
},
categories() {
let items = {
all: {label: 'All', widgets: {}},
}
let items = {}
for (let widgetName in this.widgets) {
let value = this.widgets[widgetName]
if (!value.category) {
value.category = 'all'
value.category = 'none'
}
if (typeof items[value.category] === 'undefined') {
@ -198,7 +153,6 @@ export default {
if (!this.allowedWidgets.length || this.allowedWidgets.includes(widgetName)) {
items[value.category].widgets[widgetName] = value
items['all'].widgets[widgetName] = value
}
}

View file

@ -1,157 +1,88 @@
<template>
<div>
<div
class="block"
:class="'block-depth-' + depth"
v-if="widget"
:key="blockKey"
>
<div class="block-header d-flex justify-content-between">
<div>
<div
class="block-header-item block-label"
:title="item.widget"
>
<span
class="block-icon"
v-if="widget.icon"
v-html="widget.icon"
>
</span>
{{ widget.label }}
</div>
<button
type="button"
class="block-header-item btn btn-sm btn-outline-secondary"
v-on:click="toggleSettings"
v-if="Object.keys(widget.settings).length"
>
<span class="fa fa-cog"></span>
</button>
<span class="fa fa-arrows-alt dragger"></span>
</div>
<div
v-if="widget.preview && typeof item.settings[widget.preview] == 'string'"
class="block-preview"
>
{{ truncate(item.settings[widget.preview]) }}
</div>
<div>
<span class="block-id">
{{ item.id }}
</span>
<button
type="button"
class="block-header-item btn btn-sm text-white bg-danger"
v-on:click="removeMe(item)"
>
<span class="fa fa-trash"></span>
</button>
<div class="block" v-if="widget" :key="blockKey">
<div class="block-header">
<div class="float-right">
<span class="block-id">
{{ item.id }}
</span>
<div class="block-header-item text-white bg-danger" v-on:click="removeMe(item)">
<span class="fa fa-trash"></span>
</div>
</div>
<div class="block-settings" v-if="Object.keys(widget.settings).length" :class="{'d-none': !showSettings}">
<div class="row">
<BuilderBlockSetting
class="mb-0"
v-for="(params, setting) in widget.settings"
:key="item.id + '-' + setting"
:class="widget.class"
:item="item"
:params="params"
:setting="setting"
/>
</div>
</div>
<div v-if="widget.isContainer" class="container">
<BuilderBlockCreate
:container="item.children"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="widget.widgets"
v-if="item.children.length > 0"
position="top"
/>
</div>
<Draggable
v-if="widget.isContainer"
v-model="item.children"
ghost-class="ghost"
group="children"
@start="dragStart"
@end="dragEnd"
:animation="200"
handle=".dragger"
class="block-dropzone"
<div
class="block-header-item block-label"
:title="item.widget"
>
<template v-if="item.children !== null && item.children.length > 0">
<BuilderBlockItem
v-for="(child, key) in item.children"
:key="child.id"
:item="child"
:widgets="widgets"
:openedBlocks="openedBlocks"
:depth="depth + 1"
@remove-item="removeBlock(key)"
@drag-start="dragStart"
@drag-end="dragEnd"
>
<template #action v-if="(key+1) !== item.children.length">
<BuilderBlockCreate
:container="item.children"
:widgets="widgets"
:position="key"
:openedBlocks="openedBlocks"
:allowedWidgets="widget.widgets"
/>
</template>
</BuilderBlockItem>
</template>
</Draggable>
<div v-if="widget.isContainer" class="container">
<BuilderBlockCreate
:container="item.children"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="widget.widgets"
position="bottom"
/>
<div class="text-right">
<BuilderBlockCodeEditor
ref="dialog"
:value="item.children"
:widgets="widgets"
@update="codeUpdate"
/>
{{ widget.label }}
</div>
<div
class="block-header-item block-settings-inverse"
v-on:click="toggleSettings"
v-if="Object.keys(widget.settings).length"
>
<span class="fa fa-cog"></span>
</div>
<div
class="block-header-item block-settings-inverse dragger"
>
<span class="fa fa-arrows-alt"></span>
</div>
</div>
<div class="block-settings" v-if="Object.keys(widget.settings).length" :class="{'d-none': !showSettings}">
<div class="row">
<BuilderBlockSetting
class="mb-0"
v-for="(params, setting) in widget.settings"
:key="item.id + '-' + setting"
:class="widget.class"
:item="item"
:params="params"
:setting="setting"
/>
</div>
</div>
<slot name="action"></slot>
<Draggable
v-if="widget.isContainer"
v-model="item.children"
ghost-class="ghost"
group="children"
@start="dragStart"
@end="dragEnd"
:animation="200"
handle=".dragger"
class="block-dropzone"
>
<BuilderBlockItem
v-if="item.children !== null && item.children.length > 0"
v-for="(child, key) in item.children"
:key="child.id"
:item="child"
:widgets="widgets"
@remove-item="removeBlock(key)"
@drag-start="dragStart"
@drag-end="dragEnd"
/>
</Draggable>
<div v-if="widget.isContainer" class="container">
<BuilderBlockCreate
:container="item.children"
:widgets="widgets"
:allowedWidgets="widget.widgets"
/>
</div>
</div>
</template>
<script>
import BuilderBlockCreate from './BuilderBlockCreate'
import BuilderBlockCodeEditor from './BuilderBlockCodeEditor'
import BuilderBlockSetting from './BuilderBlockSetting'
import Draggable from 'vuedraggable'
export default {
name: 'BuilderBlockItem',
components: {
BuilderBlockCreate,
BuilderBlockSetting,
BuilderBlockCodeEditor,
Draggable,
},
props: {
widgets: {
type: Object,
@ -161,36 +92,21 @@ export default {
type: Object,
required: true
},
openedBlocks: {
type: Object,
required: true
},
depth: {
type: Number,
required: true
},
},
data() {
return {
widget: null,
showSettings: this.openedBlocks[this.item.id] === true,
showSettings: false,
blockKey: 0,
}
},
methods: {
toggleSettings() {
this.openedBlocks[this.item.id] = !this.openedBlocks[this.item.id]
this.showSettings = !this.showSettings
},
truncate(value) {
return value.replace(/(<([^>]+)>)/ig, '').trim()
},
removeMe() {
this.$emit('remove-item')
},
codeUpdate(nextValue) {
this.item.children = nextValue
},
removeBlock(key) {
let children = []
@ -211,6 +127,11 @@ export default {
++this.blockKey
},
},
components: {
BuilderBlockCreate,
BuilderBlockSetting,
Draggable,
},
mounted() {
this.widget = this.widgets[this.item.widget]
},

View file

@ -3,7 +3,7 @@
<span v-if="params.label && params.type !== 'checkbox'" v-text="params.label"></span>
<input
v-if="['number', 'checkbox', 'text', 'color', 'range'].includes(params.type)"
v-if="['number', 'checkbox', 'text'].includes(params.type)"
v-model="item.settings[setting]"
v-bind="params.attr"
:type="params.type"

View file

@ -202,7 +202,6 @@ import FileIcon from './FileIcon'
const axios = require('axios').default
const routes = require('../../../../../../../../../../public/js/fos_js_routes.json')
const $ = require('jquery')
Routing.setRoutingData(routes)

View file

@ -12,14 +12,12 @@ module.exports = () => {
el: component,
template: `<BuilderBlock
:initialValue="value"
:allowedWidgets="allowedWidgets"
name="${component.getAttribute('data-name')}"
id="${component.getAttribute('data-id')}"
/>`,
data() {
return {
value: JSON.parse(component.getAttribute('data-value')),
allowedWidgets: JSON.parse(component.getAttribute('data-allowedwidgets')),
value: JSON.parse(component.getAttribute('data-value'))
}
},
components: {

View file

@ -4,7 +4,6 @@ module.exports = () => {
document.querySelectorAll('*[data-jschoice]').forEach((item) => {
return new Choices(item, {
searchFields: ['label'],
removeItemButton: true,
})
})
}

View file

@ -1,14 +1,12 @@
const $ = require('jquery')
const openModal = function (url, createModal, dataAttributes) {
const openModal = function (url, createModal) {
if (createModal) {
var id = 'modal-container-' + parseInt(Math.floor(Math.random() * 1000))
} else {
var id = 'modal-container'
}
dataAttributes = dataAttributes ?? []
let container = $(`#${id}`)
const body = $('body')
const doTrigger = true
@ -17,10 +15,6 @@ const openModal = function (url, createModal, dataAttributes) {
const doTrigger = false
container = $(`<div id="${id}" class="modal">`)
dataAttributes.forEach((attribute) => {
container.attr(attribute.name, attribute.value)
})
body.append(container)
}
@ -93,18 +87,8 @@ module.exports = function () {
const element = $(e.target).is('[data-modal]') ? $(e.target) : $(e.target).parents('*[data-modal]').first()
const url = element.attr('data-modal')
const createModal = element.is('[data-modal-create]')
const attributes = element[0].attributes
const dataAttributes = []
for (let i = 0, len = attributes.length; i < len; i++) {
let item = attributes.item(i)
if (item.name.startsWith('data-') && !['data-modal', 'data-modal-create'].includes(item.name)) {
dataAttributes.push(item)
}
}
openModal(url, createModal, dataAttributes)
openModal(url, createModal)
}, 250)
})

View file

@ -1,38 +0,0 @@
<?php echo "<?php\n"; ?>
namespace <?php echo $namespace; ?>;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('builder_block.widget')]
class <?php echo $class_name; ?> extends BuilderBlock
{
public function configure()
{
$this
->setName('<?php echo $class_name; ?>')
->setCategory('Custom')
->setTemplate('<?php echo $template; ?>')
->setLabel('<?php echo $label; ?>')
// ->setOrder(1)
// ->setIsContainer(false)
// ->setIcon('<i class="fas fa-pencil-alt"></i>')
// ->setClass('col-md-12')
// ->addSetting(name: 'value1', label: 'Text', type: 'text', attributes: [], default: '')
// ->addSetting(name: 'value2', label: 'Checkbox', type: 'checkbox', attributes: [], default: true)
// ->addSetting(name: 'value2', label: 'Number', type: 'checkbox', attributes: [], default: true)
// ->addSetting(name: 'value3', label: 'Textarea', type: 'textarea', attributes: [], default: '')
// ->addSetting(name: 'value4', label: 'Choice', type: 'select', attributes: [], default: '', extraOptions: [
// 'options' => [
// ['text' => 'Choice 1', 'value' => 'choice1'],
// ['text' => 'Choice 2', 'value' => 'choice2'],
// ],
// ])
;
}
public function buildVars(array $data, array $context)
{
}
}

View file

@ -232,6 +232,3 @@
"Level": "Niveau"
"Insert line breaks": "Ajouter les retours chariot"
'Allow HTML': "Autoriser l'HTML"
"Custom class": "Classe personnalisée"
"Editors": "Éditeurs"
"All": "Tous"

View file

@ -149,7 +149,7 @@
</th>
{% endblock %}
{% endfor %}
{% if configuration.showActions(context) %}
{% if configuration.showActions %}
<th class="crud-action-column">
{{ 'Actions'|trans }}
</th>
@ -183,20 +183,15 @@
{% endif %}
{% block list_item %}
{%- set dbClick -%}
{%- if configuration.doubleClick(context) -%}
{%- if configuration.action(context, 'show', true, [item]) -%}
{{- path(configuration.pageRoute('show'), {entity: item.id}|merge(configuration.pageRouteParams('show'))) -}}
{%- elseif configuration.action(context, 'edit', true, [item]) -%}
{{- path(configuration.pageRoute('edit'), {entity: item.id}|merge(configuration.pageRouteParams('show'))) -}}
{%- endif -%}
{%- endif -%}
{%- endset -%}
{%- set dbClick %}
{% if configuration.action(context, 'show', true, [item]) %}
{{ path(configuration.pageRoute('show'), {entity: item.id}|merge(configuration.pageRouteParams('show'))) }}
{% elseif configuration.action(context, 'edit', true, [item]) %}
{{ path(configuration.pageRoute('edit'), {entity: item.id}|merge(configuration.pageRouteParams('show'))) }}
{% endif %}
{% endset -%}
{% set rowAttributes = configuration.listRowAttributes(context, item) %}
{% set rowClasses = rowAttributes['class']|default('') %}
<tr {{ dataSortableItem|raw }} {% if configuration.doubleClick(context) %}data-dblclick="{{- dbClick -}}"{% endif %} class="{{ loop.index is odd ? 'is-odd' : 'is-even' }} {{ rowClasses }}" {% for attr, attrValue in rowAttributes %}{% if attr != 'class' %}{{ attr }}="{{ attrValue }}" {% endif %}{% endfor %}>
<tr {{ dataSortableItem|raw }} {% if configuration.doubleClick(context) %}data-dblclick="{{ dbClick }}"{% endif %} class="{{ loop.index is odd ? 'is-odd' : 'is-even' }}">
{% if configuration.hasBatchAction(context) %}
<td class="crud-batch-column">
<input type="checkbox" class="batch_form" name="batch[items][{{ loop.index }}]" form="form-batch" value="{{ loop.index }}">
@ -233,7 +228,7 @@
</td>
{% endfor %}
{% if configuration.showActions(context) %}
{% if configuration.showActions %}
<td class="crud-action-column">
{% block list_item_actions %}
{% block list_item_actions_before %}{% endblock %}

View file

@ -18,7 +18,7 @@
{% block header_actions_before %}{% endblock %}
{% if configuration.action(context, 'back', true) %}
<a href="{{ path(configuration.pageRoute('index'), configuration.pageRouteParams('index')) }}" class="btn btn-light">
<a href="{{ path(configuration.pageRoute('index')) }}" class="btn btn-light">
<span class="fa fa-list pr-1"></span>
<span class="d-none d-md-inline">
{{ configuration.actionTitle(context, 'back', 'Back to the list')|trans }}
@ -27,7 +27,7 @@
{% endif %}
{% if configuration.action(context, 'edit', true, [entity]) %}
<a href="{{ path(configuration.pageRoute('edit'), {entity: entity.id}|merge(configuration.pageRouteParams('edit'))) }}" class="btn btn-primary">
<a href="{{ path(configuration.pageRoute('edit'), {entity: entity.id}) }}" class="btn btn-primary">
<span class="fa fa-edit pr-1"></span>
<span class="d-none d-md-inline">

View file

@ -1,5 +1,4 @@
{% apply spaceless %}
{% block html %}
<!DOCTYPE html>
<html>
<head>
@ -63,5 +62,4 @@
{% endblock %}
</body>
</html>
{% endblock %}
{% endapply %}

View file

@ -1,5 +1,5 @@
<div class="alert {% if settings.level|default(null) %}alert-{{ settings.level }}{% endif %}" id="{{ id }}">
{% for item in children %}
{{ item|block_to_html(context) }}
{{ item|block_to_html }}
{% endfor %}
</div>

View file

@ -6,8 +6,8 @@
'col-xl-': settings.sizeXl|default(null),
} %}
<div class="col {% for i, v in sizes%}{% if v|length %}{{ i }}{{ v }} {% endif %}{% endfor -%} {{ settings.customClass|default(null) }}" id="{{ id }}">
<div class="col {% for i, v in sizes%}{% if v|length %}{{ i }}{{ v }} {% endif %}{% endfor -%}" id="{{ id }}">
{% for item in children %}
{{ item|block_to_html(context) }}
{{ item|block_to_html }}
{% endfor %}
</div>

View file

@ -1,5 +1,5 @@
<div class="container{% if settings.isFluid|default(false) %}-fluid{% endif %} {{ settings.customClass|default(null) }}" id="{{ id }}">
<div class="container{% if settings.isFluid|default(false) %}-fluid{% endif %}" id="{{ id }}">
{% for item in children %}
{{ item|block_to_html(context) }}
{{ item|block_to_html }}
{% endfor %}
</div>

View file

@ -1,5 +1,5 @@
<div class="row {{ settings.customClass|default(null) }}" id="{{ id }}">
<div class="row" id="{{ id }}">
{% for item in children %}
{{ item|block_to_html(context) }}
{{ item|block_to_html }}
{% endfor %}
</div>

View file

@ -2,15 +2,9 @@
{% block builder_widget %}
{% set row_attr = row_attr|merge({class: 'builder-widget ' ~ (row_attr.class ?? '')}) %}
{% set value = value is iterable ? value|json_encode : value %}
{% set allowed_widgets = allowed_widgets is iterable ? allowed_widgets|json_encode : allowed_widgets %}
{% if value == '' %}
{% set value = '[]' %}
{% endif %}
<div {% for attr, value in row_attr %}{{ attr }}="{{ value }}" {% endfor %}>
<div class="builder-widget-component" data-value="{{ value }}" data-name="{{ full_name }}" data-id="{{ id }}" data-allowedwidgets="{{ allowed_widgets }}">
<div class="builder-widget-component" data-value="{{ value is iterable ? value|json_encode : value }}" data-name="{{ full_name }}" data-id="{{ id }}">
</div>
</div>
{% endblock %}
@ -34,7 +28,7 @@
{% block file_widget -%}
<div class="row">
<div class="col-12">
{% set data = form.vars.data %}
{% set value = form.vars.data %}
{% if form.parent.vars.file_type is defined %}
{% set fileType = form.parent.vars.file_type %}
@ -42,33 +36,31 @@
{% set fileType = 'auto' %}
{% endif %}
{% if data and form.vars.errors|length == 0 %}
{% if not data is iterable %}
{% set data = [data] %}
{% endif %}
{% if value %}
{% if fileType in ['auto', 'image'] and value.extension in ['jpeg', 'jpg', 'gif', 'png', 'svg', 'webp'] %}
<div class="card">
<div class="card-img-top bg-tiles text-center">
<a href="{{ asset(value.pathname) }}" target="_blank">
<img src="{{ asset(value.pathname) }}" class="img-fluid">
</a>
</div>
<div class="card-body">
{{- parent() -}}
</div>
</div>
{% else %}
<div class="card">
<div class="card-body">
{{- parent() -}}
<div class="card">
{% for item in data %}
{% if fileType in ['auto', 'image'] and item.extension in ['jpeg', 'jpg', 'gif', 'png', 'svg', 'webp'] %}
<div class="card-img-top bg-tiles text-center">
<a href="{{ asset(item.pathname) }}" target="_blank">
<img src="{{ asset(item.pathname) }}" class="img-fluid">
<div class="p-2 text-center">
<a class="btn btn-primary" href="{{ asset(value.pathname) }}" target="_blank">
{{ 'Download'|trans }}
</a>
</div>
{% else %}
<div class="card-body">
<div class="p-2 text-center">
<a class="btn btn-primary" href="{{ asset(item.pathname) }}" target="_blank">
{{ 'Download'|trans }}
</a>
</div>
</div>
{% endif %}
{% endfor %}
<div class="card-body">
{{- parent() -}}
</div>
</div>
</div>
{% endif %}
{% else %}
{{- parent() -}}
{% endif %}
@ -79,8 +71,6 @@
{% block collection_block_widget %}
{% set allow_delete = allow_delete|default(false) %}
{% set allow_add = allow_add|default(false) %}
{% set add_attr = add_attr|default({}) %}
{% set delete_attr = delete_attr|default({}) %}
<div data-collection="collection-{{ collection_name }}">
{% for item in form.value %}
@ -90,7 +80,7 @@
{% endfor %}
{% if allow_delete %}
<div {% for k, v in delete_attr %}{{ k }}="{{ v }}"{% endfor %}>
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger">
<span data-collection-delete="{{ loop.index }}">
<span data-collection-delete="{{ loop.index }}" class="fa fa-trash"></span>
@ -104,7 +94,7 @@
</div>
{% if allow_add %}
<div data-collection-add="collection-{{ collection_name }}" {% for k, v in add_attr %}{{ k }}="{{ v }}"{% endfor %}>
<div data-collection-add="collection-{{ collection_name }}" class="collection-add">
<span class="btn btn-sm btn-primary" data-collection-add="collection-{{ collection_name }}">
<span data-collection-add="collection-{{ collection_name }}" class="fa fa-plus"></span>
{{ label_add|trans }}
@ -116,7 +106,7 @@
{{ form_rest(form.value.vars.prototype) }}
{% if allow_delete %}
<div {% for k, v in delete_attr %}{{ k }}="{{ v }}"{% endfor %}>
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger">
{{ label_delete|trans }}
</span>
@ -131,8 +121,6 @@
{% set attrs = attr|merge({class: 'mb-1 ' ~ (attr.class ?? '')}) %}
{% set allow_delete = allow_delete|default(false) %}
{% set allow_add = allow_add|default(false) %}
{% set add_attr = add_attr|default({}) %}
{% set delete_attr = delete_attr|default({}) %}
<div data-collection="collection-{{ collection_name }}" {% for attr, value in row_attr %}{{ attr }}="{{ value }}"{% endfor %}>
{% for item in form %}
@ -150,7 +138,7 @@
{% endif %}
{% if allow_delete %}
<div {% for k, v in delete_attr %}{{ k }}="{{ v }}"{% endfor %}>
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger">
<span data-collection-delete="{{ loop.index }}">
<span data-collection-delete="{{ loop.index }}" class="fa fa-trash"></span>
@ -164,7 +152,7 @@
</div>
{% if allow_add %}
<div data-collection-add="collection-{{ collection_name }}" {% for k, v in add_attr %}{{ k }}="{{ v }}"{% endfor %}>
<div data-collection-add="collection-{{ collection_name }}" class="collection-add">
<span class="btn btn-sm btn-primary" data-collection-add="collection-{{ collection_name }}">
<span data-collection-add="collection-{{ collection_name }}" class="fa fa-plus"></span>
{{ label_add|trans }}
@ -176,7 +164,7 @@
{{ form_rest(form.vars.prototype) }}
{% if allow_delete %}
<div {% for k, v in delete_attr %}{{ k }}="{{ v }}"{% endfor %}>
<div class="text-right">
<span data-collection-delete-container class="btn btn-sm btn-danger">
{{ label_delete|trans }}
</span>

View file

@ -1,77 +1,20 @@
{% extends '@Core/admin/layout.html.twig' %}
{% set view = entity.options['view']|default('modal') %}
{% block html %}
{% if view == 'modal' %}
<div class="modal-dialog {% if options['view']|default('') == 'large' %}modal-dialog-large{% endif %}">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ entity.section|trans }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form action="{{ path('admin_navigation_setting_edit', {entity: entity.id, redirectTo: redirectTo}) }}" id="form-entity-edit" method="POST">
{{ include('@Core/setting/navigation_setting_admin/_form.html.twig') }}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" form="form-entity-edit" class="btn btn-primary">{{ 'Save'|trans }}</button>
</div>
</div>
<div class="modal-dialog {% if options['view']|default('') == 'large' %}modal-dialog-large{% endif %}">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ entity.section|trans }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}
{% block body_class %}has-form{% endblock %}
{% block title %}{{ entity.label }}{% endblock %}
{% block body %}
{% block header %}
<div class="bg-light">
<div class="crud-header">
{% block header_title %}
<h1 class="crud-header-title">{{ entity.label }}</h1>
{% endblock %}
{% block header_actions %}
<div class="crud-header-actions">
<div class="btn-group">
<a href="{{ path('admin_setting_index') }}" class="btn btn-light">
<span class="fa fa-list pr-1"></span>
<span class="d-none d-md-inline">
{{ 'Back to the list'|trans }}
</span>
</a>
<button type="submit" form="form-main" class="btn btn-primary">
<span class="fa fa-save pr-1"></span>
<span class="d-none d-md-inline">
{{ 'Save'|trans|build_string(entity) }}
</span>
</button>
</div>
</div>
{% endblock %}
</div>
<div class="modal-body">
<form action="{{ path('admin_navigation_setting_edit', {entity: entity.id, redirectTo: redirectTo}) }}" id="form-entity-edit" method="POST">
{{ include('@Core/setting/navigation_setting_admin/_form.html.twig') }}
</form>
</div>
{% endblock %}
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" form="form-entity-edit" class="btn btn-primary">{{ 'Save'|trans }}</button>
</div>
</div>
</div>
{% block form %}
<form action="{{ app.request.uri }}" method="post" id="form-main" enctype="multipart/form-data">
<div class="tab-content">
<div class="tab-pane active">
<div class="tab-form">
{{ include('@Core/setting/navigation_setting_admin/_form.html.twig') }}
</div>
</div>
</div>
</form>
{% endblock %}
{% endblock %}

View file

@ -1,77 +1,20 @@
{% extends '@Core/admin/layout.html.twig' %}
{% set view = entity.options['view']|default('modal') %}
{% block html %}
{% if view == 'modal' %}
<div class="modal-dialog {% if options['view']|default('') == 'large' %}modal-dialog-large{% endif %}">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ entity.section|trans }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form action="{{ path('admin_setting_edit', {entity: entity.id, redirectTo: redirectTo}) }}" id="form-entity-edit" method="POST">
{{ include('@Core/setting/setting_admin/_form.html.twig') }}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" form="form-entity-edit" class="btn btn-primary">{{ 'Save'|trans }}</button>
</div>
</div>
<div class="modal-dialog {% if options['view']|default('') == 'large' %}modal-dialog-large{% endif %}">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ entity.section|trans }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}
{% block body_class %}has-form{% endblock %}
{% block title %}{{ entity.label }}{% endblock %}
{% block body %}
{% block header %}
<div class="bg-light">
<div class="crud-header">
{% block header_title %}
<h1 class="crud-header-title">{{ entity.label }}</h1>
{% endblock %}
{% block header_actions %}
<div class="crud-header-actions">
<div class="btn-group">
<a href="{{ path('admin_setting_index') }}" class="btn btn-light">
<span class="fa fa-list pr-1"></span>
<span class="d-none d-md-inline">
{{ 'Back to the list'|trans }}
</span>
</a>
<button type="submit" form="form-main" class="btn btn-primary">
<span class="fa fa-save pr-1"></span>
<span class="d-none d-md-inline">
{{ 'Save'|trans|build_string(entity) }}
</span>
</button>
</div>
</div>
{% endblock %}
</div>
<div class="modal-body">
<form action="{{ path('admin_setting_edit', {entity: entity.id, redirectTo: redirectTo}) }}" id="form-entity-edit" method="POST">
{{ include('@Core/setting/setting_admin/_form.html.twig') }}
</form>
</div>
{% endblock %}
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Cancel'|trans }}</button>
<button type="submit" form="form-entity-edit" class="btn btn-primary">{{ 'Save'|trans }}</button>
</div>
</div>
</div>
{% block form %}
<form action="{{ app.request.uri }}" method="post" id="form-main" enctype="multipart/form-data">
<div class="tab-content">
<div class="tab-pane active">
<div class="tab-form">
{{ include('@Core/setting/setting_admin/_form.html.twig') }}
</div>
</div>
</div>
</form>
{% endblock %}
{% endblock %}

View file

@ -22,35 +22,21 @@
</thead>
<tbody>
{% for item in pager %}
{% set view = item.options['view']|default('modal') %}
{% set edit = path('admin_setting_edit', {entity: item.id, redirectTo: app.request.pathInfo}) %}
<tr>
<td class="col-5">
{% if view == 'modal' %}
<a href="#" data-modal="{{ edit }}" data-modal-create class="font-weight-bold text-body d-block">
{{ item.label }}
</a>
{% else %}
<a href="{{ edit }}" class="font-weight-bold text-body d-block">
{{ item.label }}
</a>
{% endif %}
<a href="#" data-modal="{{ edit }}" data-modal-create class="font-weight-bold text-body d-block">
{{ item.label|trans }}
</a>
</td>
<td class="col-5">
<span class="btn btn-light">{{ item.section|trans }}</span>
</td>
<td class="col-2 miw-100 text-right">
{% if view == 'modal' %}
<span data-modal="{{ edit }}" data-modal-create class="btn btn-sm btn-primary mr-1">
<span data-modal="{{ edit }}" class="fa fa-edit"></span>
</span>
{% else %}
<a href="{{ edit }}" class="btn btn-sm btn-primary mr-1">
<span class="fa fa-edit"></span>
</a>
{% endif %}
<span data-modal="{{ edit }}" data-modal-create class="btn btn-sm btn-primary mr-1">
<span data-modal="{{ edit }}" class="fa fa-edit"></span>
</span>
<button type="submit" form="form-delete-{{ item.id }}" class="btn btn-sm btn-danger">
<span class="fa fa-trash"></span>
</button>

View file

@ -54,36 +54,21 @@
</thead>
<tbody>
{% for item in datas.settings %}
{% set view = item.options['view']|default('modal') %}
{% set edit = path('admin_navigation_setting_edit', {entity: item.id, redirectTo: app.request.pathInfo}) %}
<tr data-dblclick="{{ edit }}">
<td class="col-5">
{% if view == 'modal' %}
<a href="#" data-modal="{{ edit }}" data-modal-create class="font-weight-bold text-body d-block">
{{ item.label }}
</a>
{% else %}
<a href="{{ edit }}" class="font-weight-bold text-body d-block">
{{ item.label }}
</a>
{% endif %}
<a href="#" data-modal="{{ edit }}" class="font-weight-bold text-body d-block">
{{ item.label|trans }}
</a>
</td>
<td class="col-5">
<span class="btn btn-light">{{ item.section|trans }}</span>
</td>
<td class="col-2 miw-100 text-right">
{% if view == 'modal' %}
<span data-modal="{{ edit }}" data-modal-create class="btn btn-sm btn-primary mr-1">
<span data-modal="{{ edit }}" class="fa fa-edit"></span>
</span>
{% else %}
<a href="{{ edit }}" class="btn btn-sm btn-primary mr-1">
<span class="fa fa-edit"></span>
</a>
{% endif %}
<span data-modal="{{ edit }}" class="btn btn-sm btn-primary mr-1">
<span data-modal="{{ edit }}" class="fa fa-edit"></span>
</span>
<button type="submit" form="form-delete-{{ item.id }}" class="btn btn-sm btn-danger">
<span class="fa fa-trash"></span>
</button>

View file

@ -24,14 +24,7 @@ class NavigationSettingManager
) {
}
public function init(
$navigation,
string $code,
string $section,
string $label,
$value = null,
array $options = [],
)
public function init($navigation, string $code, string $section, string $label, $value = null)
{
$entity = $this->get($this->getNavigation($navigation), $code);
$isNew = null === $entity;
@ -44,7 +37,6 @@ class NavigationSettingManager
$entity
->setSection($section)
->setLabel($label)
->setOptions($options)
;
if ($isNew) {

View file

@ -21,13 +21,7 @@ class SettingManager
) {
}
public function init(
string $code,
string $section,
string $label,
$value = null,
array $options = [],
)
public function init(string $code, string $section, string $label, $value = null)
{
$entity = $this->get($code);
$isNew = null === $entity;
@ -40,7 +34,6 @@ class SettingManager
$entity
->setSection($section)
->setLabel($label)
->setOptions($options)
;
if ($isNew) {

View file

@ -23,40 +23,27 @@ class BuilderExtension extends AbstractExtension
];
}
public function buildHtml(null|array|string $data, array $context = []): ?string
public function buildHtml($data): string
{
if (null === $data) {
return null;
}
if (is_string($data)) {
$data = json_decode($data, true);
}
if (!is_array($data)) {
return null;
}
if (isset($data['widget'])) {
if (!$this->container->hasWidget($data['widget'])) {
return null;
}
$widget = $this->container->getWidget($data['widget']);
$widget->buildVars($data, $context);
$widget->buildVars($data);
return $this->twig->render($widget->getTemplate(), [
'id' => $data['id'],
'settings' => $data['settings'],
'children' => $data['children'],
'context' => $context,
'vars' => $widget->getVars(),
]);
}
$render = '';
foreach ($data as $item) {
$render .= $this->buildHtml($item, $context);
$render .= $this->buildHtml($item);
}
return $render;