Compare commits

...

111 commits

Author SHA1 Message Date
Simon Vieille a80dc9999c Merge branch 'develop' 2024-05-17 22:02:18 +02:00
Simon Vieille b9566853ef
allow buildHtml to get null, array or string 2024-05-17 22:02:16 +02:00
Simon Vieille bc7e8ef263 Merge branch 'develop' 2024-05-17 21:38:50 +02:00
Simon Vieille 25efd11ea3
add BuilderBlockContainer::removeBlock 2024-05-17 21:38:48 +02:00
Simon Vieille e23f7d3c73 Merge branch 'develop' 2024-05-17 21:34:16 +02:00
Simon Vieille 85f054956c
sanitize builder component value 2024-05-17 21:34:08 +02:00
Simon Vieille b12f4db16f Merge branch 'develop' 2024-05-16 21:42:42 +02:00
Simon Vieille 6bb29dd5c3
add margin and border color to builder block widgets 2024-05-16 21:42:40 +02:00
Simon Vieille 7824e96bab Merge branch 'develop' 2024-05-16 11:17:10 +02:00
Simon Vieille 56177c14da
improve block builder picker 2024-05-16 11:16:48 +02:00
Simon Vieille 4f1c5b8f14 Merge branch 'develop' 2024-05-16 08:56:14 +02:00
Simon Vieille e8c9520378
update builder block default template 2024-05-16 08:56:06 +02:00
Simon Vieille 283223446a Merge branch 'develop' 2024-05-15 23:24:18 +02:00
Simon Vieille 46e01f504f
add maker for builder block 2024-05-15 23:24:11 +02:00
Simon Vieille a80ee03fcd Merge branch 'develop' 2024-05-15 18:39:15 +02:00
Simon Vieille d1649a4959
add builder as new type in the page maker 2024-05-15 18:39:09 +02:00
Simon Vieille 38791f1d7a Merge branch 'develop' 2024-05-15 18:27:22 +02:00
Simon Vieille d74cd52711
add builer block context 2024-05-15 18:27:19 +02:00
Simon Vieille b451df61e8 Merge branch 'develop' 2024-05-15 18:19:11 +02:00
Simon Vieille 7c124008c0
add builer block context 2024-05-15 18:18:56 +02:00
Simon Vieille 9cf95dba64 Merge branch 'develop' 2024-05-14 22:13:44 +02:00
Simon Vieille b680946daf
block builder: add test to check if the widget exists 2024-05-14 22:13:36 +02:00
Simon Vieille 1995298977 Merge branch 'develop' 2024-05-14 20:24:58 +02:00
Simon Vieille 4acba618cb
block builder: add colors depending of depth and keep open/closed settings when moved 2024-05-14 20:24:34 +02:00
Simon Vieille 92eaaba699 Merge branch 'develop' 2024-05-13 17:58:11 +02:00
Simon Vieille 357856f8ce
release v1.25.1 2024-05-13 17:58:08 +02:00
Simon Vieille dfbe282d0e
add drag and drop in the block builder 2024-05-13 17:40:05 +02:00
Simon Vieille 857fcd9897
add blocks 2024-05-13 13:58:07 +02:00
Simon Vieille 48ca5a96e6
rename blocks 2024-05-13 13:57:58 +02:00
Simon Vieille 065bb0db22
rename blocks 2024-05-13 13:57:49 +02:00
Simon Vieille bf6777054f
add order property 2024-05-13 13:57:34 +02:00
Simon Vieille 51acd82432 order blocks 2024-05-13 13:57:01 +02:00
Simon Vieille c6cbc405e4 Merge branch 'develop' 2024-05-13 11:20:11 +02:00
Simon Vieille 150f3afd6a
add build block vars 2024-05-13 11:19:56 +02:00
Simon Vieille 4a0e616897 Merge branch 'develop' 2024-05-13 09:27:18 +02:00
Simon Vieille 4eb13a4022
add diffent border color on the root block of the builder
add default class in blocks
2024-05-13 09:26:31 +02:00
Simon Vieille 8bb6267a77 Merge branch 'develop' 2024-05-12 22:49:46 +02:00
Simon Vieille 8044ff3605
update changelog 2024-05-12 22:49:43 +02:00
Simon Vieille 71d3d40d5f Merge branch 'develop' 2024-05-12 22:48:33 +02:00
Simon Vieille 9324837bd9
update changelog 2024-05-12 22:48:30 +02:00
Simon Vieille b8dfaaed10 Merge branch 'feature/blocks' into develop 2024-05-12 22:41:44 +02:00
Simon Vieille 2d49b8ddee
move build add button 2024-05-12 22:40:52 +02:00
Simon Vieille 232b92267e
add build block loader
add builder block rendering

fix issues with components
2024-05-12 22:24:51 +02:00
Simon Vieille ca23a22807
rollback indentation 2024-05-12 22:24:10 +02:00
Simon Vieille 78965bbf10
allow to move blocks
update builder view
2024-05-12 14:30:28 +02:00
Simon Vieille 7fe1acd47d
add base of the block builder 2024-05-11 17:45:57 +02:00
Simon Vieille 26cbaa8469 Merge branch 'develop' 2024-05-03 17:58:22 +02:00
Simon Vieille 7897bafcc2
fix undefined window.tinymce.murph 2024-05-03 17:57:43 +02:00
Simon Vieille 63a8a60e2d Merge branch 'develop' 2024-03-31 16:57:56 +02:00
Simon Vieille d01e9d618b
add border color on tinymce editor 2024-03-31 16:57:46 +02:00
Simon Vieille 96347a1730 Merge branch 'develop' 2024-03-31 16:50:47 +02:00
Simon Vieille aeb0e6c109 allow to use window.tinymceModes to add or override tinymce modes 2024-03-31 16:50:18 +02:00
Simon Vieille 64258a2d8c Merge branch 'develop' 2024-03-25 15:27:12 +01:00
Simon Vieille 70329ceeda
fix template of CrudController (maker) 2024-03-25 15:27:06 +01:00
Simon Vieille 1adb1ebe2b Merge branch 'develop' 2024-03-25 13:04:17 +01:00
Simon Vieille f57cc8e4d6
fix hidden save button in file manager 2024-03-25 13:04:16 +01:00
Simon Vieille b21967028e Merge branch 'develop' 2024-03-25 13:03:01 +01:00
Simon Vieille 5b22851674
fix hidden save button in file manager 2024-03-25 13:02:20 +01:00
Simon Vieille 4082bb171a
fix use of IsGranted is CrudController 2024-02-04 16:48:44 +01:00
Simon Vieille dc19617fb1 Merge branch 'develop' 2024-02-04 16:33:34 +01:00
Simon Vieille dbd2036fb0
rollback commit 614ae40 (add IsGranted in all methods of the CrudController maker template) 2024-02-04 16:33:30 +01:00
Simon Vieille 8e2566abc8
rollback commit 614ae40 (add IsGranted in all methods of the CrudController maker template) 2024-02-04 16:33:22 +01:00
Simon Vieille 66e1a9c87f
rollback commit 614ae40 (add IsGranted in all methods of the CrudController maker template) 2024-02-04 16:31:21 +01:00
Simon Vieille 6709c0a303
update changelog 2024-02-04 16:17:49 +01:00
Simon Vieille 614ae40901 add IsGranted in all methods of the CrudController maker template 2024-02-04 16:16:22 +01:00
Simon Vieille 430bff9433
fix default crud sort 2024-02-02 20:04:19 +01:00
Simon Vieille 6441da8a27
fix default crud sort 2024-02-02 20:03:57 +01:00
Simon Vieille 801e3317e7
update changelog 2024-02-02 20:02:00 +01:00
Simon Vieille f7604d2a45
fix default crud sort 2024-02-02 20:01:23 +01:00
Simon Vieille 8a632a1b14
update Murph version constant 2024-02-01 18:46:33 +01:00
Simon Vieille 4701090134
update changelog 2024-01-27 15:49:58 +01:00
Simon Vieille c40c7e3362
fix type casting in slugifier 2024-01-27 15:49:39 +01:00
Simon Vieille 8edbf0cc08
add default default on node's code when slugify 2023-11-27 10:32:38 +01:00
Simon Vieille 175321bc2d
fix issue on file manager when a file is selected in the file in the file picker 2023-11-16 23:06:00 +01:00
Simon Vieille 5d6531d197
fix sidebar scroll algo 2023-11-14 23:21:24 +01:00
Simon Vieille 90603f62e0
add side bar scroll animation 2023-11-14 23:08:09 +01:00
Simon Vieille 053f4aa5b8
add auto-scroll on current sidebar item 2023-11-14 23:03:43 +01:00
Simon Vieille 79754d45c1
add auto-scroll on current sidebar item
remove jquery from sidebar module
2023-11-14 22:51:40 +01:00
Simon Vieille c98ea50f30
fix undefined pager on index 2023-11-13 16:06:06 +01:00
Simon Vieille 0f1bc761b2
add no-wrap around the thead sort link 2023-11-10 19:42:45 +01:00
Simon Vieille 50dbb07314
update changelog 2023-11-10 19:35:48 +01:00
Simon Vieille 72e783f865
fix render of the URL when the window is small 2023-11-10 19:35:39 +01:00
Simon Vieille ee28c9abb7
copy the pager in the bottom of the index table
remove the with class of the action column
2023-11-10 19:28:20 +01:00
Simon Vieille 2bd6836a7f
fix issue on file manager when a file is selected in the file in the file picker 2023-11-09 17:19:09 +01:00
Simon Vieille 6f961ba79b
release v1.23.0 2023-11-01 16:31:33 +01:00
Simon Vieille e095fc4197
update changelog 2023-10-27 17:03:08 +02:00
Simon Vieille 6736f94eea
set searchFields option on jschoice manager 2023-10-27 17:02:15 +02:00
Simon Vieille 93a1e7811d
change colors on js-choices element 2023-10-25 20:15:21 +02:00
Simon Vieille a0027c0b69 Merge branch 'feature/theming' into develop 2023-10-25 19:59:32 +02:00
Simon Vieille 498c71081d
add red * on required label 2023-10-25 19:59:28 +02:00
Simon Vieille 8713b401f9
rollback modal changes 2023-10-25 19:36:10 +02:00
Simon Vieille 1463f43298
update changelog 2023-10-25 19:22:56 +02:00
Simon Vieille b89e036c49 change border colors of inputs when focused 2023-10-25 19:22:44 +02:00
Simon Vieille 1d0b657c83
add sass classes to mange with of elements
fix the aspect of the actions's column in the crud

add background in the modal header

change vavigation pills colors
2023-10-25 19:12:02 +02:00
Simon Vieille 0cadf28738
update changelog 2023-10-20 09:46:57 +02:00
Simon Vieille ede8d4fdcb
remove unsed twig in mail notifier 2023-10-20 09:45:58 +02:00
Simon Vieille 5c3f2ab1e7
refactor services using constructor property promotions 2023-10-20 09:44:18 +02:00
Simon Vieille c1eb277a6a
add 'Length' constraint in forms 2023-10-19 20:51:23 +02:00
Simon Vieille d3f27d97ad
apply php linter 2023-10-12 16:15:07 +02:00
Simon Vieille 5e392d469a
update changelog 2023-10-12 16:04:49 +02:00
Simon Vieille 67f79083ef
change params given to the callback of a global batch action (page removed, add selected items) 2023-10-12 16:04:43 +02:00
Simon Vieille b9b07c1409
fix issue with CrudConfiguration::setGlobalBatchAction 2023-10-12 16:04:03 +02:00
Simon Vieille 521ed5ce64 Merge branch 'feature/batch' into develop 2023-10-12 15:28:17 +02:00
Simon Vieille dda43ef3cc
change CrudController::doBatch to manage a global batch action 2023-10-12 15:28:14 +02:00
Simon Vieille c65cc26be8
batch form is not submitted with XHR when it's a global action 2023-10-12 15:27:36 +02:00
Simon Vieille 2f884df602
add CrudConfiguration::setGlobalBatchAction method 2023-10-12 15:25:52 +02:00
Simon Vieille 8979fc5beb
fix test in RepositoryQuery::addForcedFilterHandler 2023-10-10 10:18:25 +02:00
Simon Vieille bd663838f6
allow to define templates show before and after a murph collection item 2023-10-06 12:46:51 +02:00
Simon Vieille 177b23365b
update changelog 2023-10-04 13:55:08 +02:00
Simon Vieille 645ae700d4
remove parameter $option on CrudConfiguration::setForm
fix CrudController make template
2023-10-04 13:54:37 +02:00
Simon Vieille 7614c24012
fix regression on crud sorting 2023-10-04 13:47:01 +02:00
144 changed files with 2210 additions and 637 deletions

View file

@ -1,6 +1,52 @@
## [Unreleased]
## [v1.22.0] 2023-09-28
## [v1.25.1] - 2024-05-13
### Added
* add drag&drop in the block builder
## [v1.25.0] - 2024-05-12
### Added
* add block builder widget
* allow to use `window.tinymceModes` to add or override tinymce modes
* add border color on tinymce editor
### Fixed
* fix default crud sort
* fix hidden save button in file manager
* fix template of CrudController (maker)
* fix undefined `window.tinymce.murph`
## [v1.24.1] - 2024-02-01
### Fixed
* update Murph version constant
## [v1.24.0] - 2024-01-27
### Added
* add CSS class `no-wrap`
* copy the pager of the CRUD at the bottom of the list
### Fixed
* fix an issue with the file manager when editing an item opened in a modal
* fix type casting in slugifier
## [v1.23.0] - 2023-11-01
### Added
* allow to define templates show before and after a murph collection item
* add global batch actions
* add constraint `Length` in forms
* add sass classes to mange with of elements
* set searchFields option on jschoice manager (search on labels)
### Changed
* refactor services using constructor property promotions
* remove twig in the mail notifier service
* change pills colors
* change border colors of inputs when focused
* change colors on js-choices element
### Fixed
* fix regression on crud sorting
* fix test in RepositoryQuery::addForcedFilterHandler
* remove parameter $option on CrudConfiguration::setForm and fix CrudController make template
* fix the aspect of the actions's column in the crud
## [v1.22.0] - 2023-09-28
### Added
* add new options in BooleanField: `toggle|checkbox_class_when_true` and `toggle|checkbox_class_when_false`
* add `count` method in repository query

View file

@ -10,14 +10,12 @@ namespace App\Core\Ab;
class AbTest implements AbTestInterface
{
protected $results;
protected string $name;
protected array $variations = [];
protected array $probabilities = [];
protected int $duration = 3600 * 24;
public function __construct(string $name)
public function __construct(protected string $name)
{
$this->name = $name;
}
public function getName(): string

View file

@ -13,18 +13,16 @@ use App\Core\Repository\Analytic\ViewRepositoryQuery;
*/
class DateRangeAnalytic
{
protected ViewRepositoryQuery $viewQuery;
protected RefererRepositoryQuery $refererQuery;
protected ?Node $node;
protected ?\DateTime $from;
protected ?\DateTime $to;
protected bool $reload = true;
protected array $cache = [];
public function __construct(ViewRepositoryQuery $viewQuery, RefererRepositoryQuery $refererQuery)
{
$this->viewQuery = $viewQuery;
$this->refererQuery = $refererQuery;
public function __construct(
protected ViewRepositoryQuery $viewQuery,
protected RefererRepositoryQuery $refererQuery
) {
}
public function getViews(): array
@ -83,7 +81,7 @@ class DateRangeAnalytic
$datas[$index]['mobileViews'] += $entity->getMobileViews();
}
uasort($datas, function($a, $b) {
uasort($datas, function ($a, $b) {
if ($a['views'] > $b['views']) {
return -1;
}
@ -130,7 +128,7 @@ class DateRangeAnalytic
$datas[$index]['uris'][$path] += $entity->getViews();
}
uasort($datas, function($a, $b) {
uasort($datas, function ($a, $b) {
if ($a['views'] > $b['views']) {
return -1;
}

View file

@ -10,16 +10,10 @@ namespace App\Core\Annotation;
#[\Attribute]
class UrlGenerator
{
public string $service;
public string $method;
public array $options = [];
public function __construct(string $service, string $method, array $options = [])
{
$this->service = $service;
$this->method = $method;
$this->options = $options;
public function __construct(
public string $service,
public string $method,
public array $options = []
) {
}
}

View file

@ -23,20 +23,12 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private EntityManagerInterface $entityManager;
private UrlGeneratorInterface $urlGenerator;
private CsrfTokenManagerInterface $csrfTokenManager;
private UserPasswordEncoderInterface $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
public function __construct(
private EntityManagerInterface $entityManager,
private UrlGeneratorInterface $urlGenerator,
private CsrfTokenManagerInterface $csrfTokenManager,
private UserPasswordEncoderInterface $passwordEncoder
) {
}
public function supports(Request $request)

View file

@ -0,0 +1,47 @@
<?php
namespace App\Core\BuilderBlock\Block\Bootstrap;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
use Symfony\Contracts\Translation\TranslatorInterface;
#[AutoconfigureTag('builder_block.widget')]
class AlertBlock extends BootstrapBlock
{
public function __construct(protected TranslatorInterface $translator)
{
}
public function configure()
{
parent::configure();
$options = [];
foreach ([
'Primary' => 'primary',
'Secondary' => 'secondary',
'Info' => 'info',
'Success' => 'success',
'Danger' => 'danger',
'Warning' => 'warning',
'Light' => 'light',
'Dark' => 'dark',
] as $k => $v) {
$options[] = [
'text' => $this->translator->trans($k),
'value' => $v,
];
}
$this
->setName('bsAlert')
->setLabel('Alert')
->setOrder(4)
->setIsContainer(true)
->setIcon('<i class="fas fa-exclamation-circle"></i>')
->setTemplate('@Core/builder_block/bootstrap/alert.html.twig')
->addSetting(name: 'level', label: 'Level', type: 'select', extraOptions: ['options' => $options])
;
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace App\Core\BuilderBlock\Block\Bootstrap;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
class BootstrapBlock extends BuilderBlock
{
public function configure()
{
$this->setCategory('Bootstrap');
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Core\BuilderBlock\Block\Bootstrap;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('builder_block.widget')]
class ColumnBlock extends BootstrapBlock
{
public function configure()
{
parent::configure();
$this
->setName('bsColumn')
->setLabel('Column')
->setIsContainer(true)
->setOrder(3)
->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])
->addSetting(name: 'sizeSm', label: 'Small', type: 'number', attributes: ['min' => 0, 'max' => 12])
->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])
;
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Core\BuilderBlock\Block\Bootstrap;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('builder_block.widget')]
class ContainerBlock extends BootstrapBlock
{
public function configure()
{
parent::configure();
$this
->setName('bsContainer')
->setLabel('Container')
->setIsContainer(true)
->setOrder(1)
->setTemplate('@Core/builder_block/bootstrap/container.html.twig')
->setIcon('<i class="fas fa-th"></i>')
->addSetting(name: 'isFluid', label: 'Fluid', type: 'checkbox')
;
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Core\BuilderBlock\Block\Bootstrap;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('builder_block.widget')]
class RowBlock extends BootstrapBlock
{
public function configure()
{
parent::configure();
$this
->setName('bsRow')
->setLabel('Row')
->setOrder(2)
->setIsContainer(true)
->setIcon('<i class="fas fa-align-justify"></i>')
->setTemplate('@Core/builder_block/bootstrap/row.html.twig')
->addWidget('bsColumn')
;
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace App\Core\BuilderBlock\Block\Editor;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
class EditorBlock extends BuilderBlock
{
public function configure()
{
$this->setCategory('Editor');
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace App\Core\BuilderBlock\Block\Editor;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('builder_block.widget')]
class TextareaBlock extends EditorBlock
{
public function configure()
{
parent::configure();
$this
->setName('textarea')
->setLabel('Text')
->setIsContainer(false)
->setIcon('<i class="fas fa-pencil-alt"></i>')
->setTemplate('@Core/builder_block/editor/textarea.html.twig')
->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')
;
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Core\BuilderBlock\Block\Editor;
use App\Core\BuilderBlock\BuilderBlock;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('builder_block.widget')]
class TinymceBlock extends EditorBlock
{
public function configure()
{
parent::configure();
$this
->setName('tinymce')
->setLabel('TinyMCE')
->setIsContainer(false)
->setIcon('<i class="fas fa-pencil-alt"></i>')
->setTemplate('@Core/builder_block/editor/tinymce.html.twig')
->addSetting(name: 'value', type: 'textarea', attributes: ['data-tinymce' => ''])
;
}
}

View file

@ -0,0 +1,195 @@
<?php
namespace App\Core\BuilderBlock;
abstract class BuilderBlock
{
protected string $name;
protected string $label;
protected ?string $class = 'col-12';
protected ?string $category = null;
protected array $settings = [];
protected array $widgets = [];
protected array $vars = [];
protected string $template = '';
protected bool $isContainer = false;
protected ?string $icon = null;
protected int $order = 1;
abstract public function configure();
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
public function getLabel(): string
{
return $this->label;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getName(): string
{
return $this->name;
}
public function setCategory(?string $category): self
{
$this->category = $category;
return $this;
}
public function getCategory(): ?string
{
return $this->category;
}
public function setIsContainer(bool $isContainer): self
{
$this->isContainer = $isContainer;
return $this;
}
public function getIsContainer(): bool
{
return $this->isContainer;
}
public function setWidgets(array $widgets): self
{
$this->widgets = $widgets;
return $this;
}
public function addWidget(string $widget): self
{
if (!in_array($widget, $this->widgets)) {
$this->widgets[] = $widget;
}
return $this;
}
public function getWidgets(): array
{
return $this->widgets;
}
public function setSettings(array $settings): self
{
$this->settings = $settings;
return $this;
}
public function addSetting(
string $name,
string $type = 'input',
?string $label = null,
array $attributes = [],
array $extraOptions = [],
$default = null
) {
$this->settings[$name] = [
'label' => $label,
'type' => $type,
'attr' => $attributes,
'default' => $default,
];
foreach ($extraOptions as $key => $value) {
if (!in_array($key, array_keys($this->settings[$name]))) {
$this->settings[$name][$key] = $value;
}
}
return $this;
}
public function getSettings(): array
{
return $this->settings;
}
public function setTemplate(string $template): self
{
$this->template = $template;
return $this;
}
public function getTemplate(): string
{
return $this->template;
}
public function setClass(?string $class): self
{
$this->class = $class;
return $this;
}
public function getClass(): ?string
{
return $this->class;
}
public function setIcon(?string $icon): self
{
$this->icon = $icon;
return $this;
}
public function getIcon(): ?string
{
return $this->icon;
}
public function setOrder(int $order): self
{
$this->order = $order;
return $this;
}
public function getOrder(): int
{
return $this->order;
}
public function buildVars(array $data, array $context)
{
}
public function getVars(): array
{
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(),
];
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace App\Core\BuilderBlock;
class BuilderBlockContainer
{
protected array $widgets = [];
public function addWidget(BuilderBlock $widget): self
{
$widget->configure();
$this->widgets[$widget->getName()] = $widget;
return $this;
}
public function removeWidget(string $name)
{
unset($this->widgets[$name]);
return $this;
}
public function getWidgets(): array
{
usort($this->widgets, fn(BuilderBlock $a, BuilderBlock $b) => $a->getOrder() <=> $b->getOrder());
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

@ -12,8 +12,8 @@
namespace App\Core\Bundle;
use App\Core\DependencyInjection\CoreExtension;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class CoreBundle extends Bundle
{

View file

@ -5,13 +5,13 @@ namespace App\Core\Cache;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* class SymfonyCacheManager.
@ -20,15 +20,11 @@ use Symfony\Component\HttpClient\Exception\TransportException;
*/
class SymfonyCacheManager
{
protected KernelInterface $kernel;
protected HttpClientInterface $httpClient;
protected UrlGeneratorInterface $urlGenerator;
public function __construct(KernelInterface $kernel, HttpClientInterface $httpClient, UrlGeneratorInterface $urlGenerator)
{
$this->kernel = $kernel;
$this->httpClient = $httpClient;
$this->urlGenerator = $urlGenerator;
public function __construct(
protected KernelInterface $kernel,
protected HttpClientInterface $httpClient,
protected UrlGeneratorInterface $urlGenerator
) {
}
public function cleanRouting()

View file

@ -18,19 +18,12 @@ class UserCreateCommand extends Command
{
protected static $defaultName = 'murph:user:create';
protected static $defaultDescription = 'Creates a user';
protected UserFactory $userFactory;
protected EntityManager $entityManager;
protected TokenGeneratorInterface $tokenGenerator;
public function __construct(
UserFactory $userFactory,
EntityManager $entityManager,
TokenGeneratorInterface $tokenGenerator
protected UserFactory $userFactory,
protected EntityManager $entityManager,
protected TokenGeneratorInterface $tokenGenerator
) {
$this->userFactory = $userFactory;
$this->entityManager = $entityManager;
$this->tokenGenerator = $tokenGenerator;
parent::__construct();
}

View file

@ -162,7 +162,7 @@ abstract class CrudController extends AdminController
$lastRequest = $session->get($lastRequestId);
if ($lastRequest !== null && !$request->isMethod('POST')) {
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
uri: $request->getUri(),
method: 'POST',
@ -284,16 +284,39 @@ abstract class CrudController extends AdminController
$query->useFilters($this->filters);
if ('selection' === $target) {
$isSelection = true;
$pager = $query->paginate($page, $configuration->getMaxPerPage($context));
} else {
$isSelection = false;
$pager = $query->find();
$useSelection = 'selection' === $target;
if ($batchAction['isGlobal']) {
$selection = null;
if ($useSelection) {
$queryClone = clone $query;
$pager = $queryClone->paginate($page, $configuration->getMaxPerPage($context));
$selection = [];
foreach ($pager as $key => $entity) {
if (isset($items[$key + 1])) {
$selection[] = $entity;
}
}
}
$result = $callback($query, $entityManager, $selection);
if ($result instanceof Response) {
return $result;
}
return $this->redirect($request->query->get('redirectTo'));
}
$pager = $useSelection
? $query->paginate($page, $configuration->getMaxPerPage($context))
: $query->find()
;
foreach ($pager as $key => $entity) {
if (($isSelection && isset($items[$key + 1])) || !$isSelection) {
if (($useSelection && isset($items[$key + 1])) || !$useSelection) {
$callback($entity, $entityManager);
}
}
@ -401,19 +424,22 @@ abstract class CrudController extends AdminController
$sessionSortName = sprintf('%s_label', $sessionId);
$sessionSortDirection = sprintf('%s_direction', $sessionId);
$name = strtolower($request->query->get(
'_sort',
$session->get($sessionSortName, $defaultSort['label'] ?? 'asc')
));
$name = $request->query->get('_sort', $session->get($sessionSortName)) ?? $defaultSort['label'] ?? null;
$direction = strtolower($request->query->get(
'_sort_direction',
$session->get($sessionSortDirection, $defaultSort['direction'] ?? 'asc')
));
$direction = strtolower(
$request->query->get(
'_sort_direction',
$session->get($sessionSortDirection)
) ?? $defaultSort['direction'] ?? 'asc'
);
$session->set($sessionSortName, $name);
$session->set($sessionSortDirection, $direction);
if (empty($name)) {
return;
}
if (!in_array($direction, ['asc', 'desc'])) {
$direction = 'asc';
}

View file

@ -3,8 +3,6 @@
namespace App\Core\Controller\Dashboard;
use App\Core\Controller\Admin\AdminController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DashboardAdminController extends AdminController
{

View file

@ -0,0 +1,40 @@
<?php
namespace App\Core\Controller\Editor;
use App\Core\BuilderBlock\BuilderBlockContainer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
#[Route(path: '/admin/editor/builder_block')]
class BuilderBlockController extends AbstractController
{
public function __construct(protected TranslatorInterface $translator)
{
}
#[Route(path: '/widgets', name: 'admin_editor_builder_block_widgets', options: ['expose' => true])]
public function widgets(BuilderBlockContainer $container): JsonResponse
{
$data = [];
foreach ($container->getWidgets() as $widget) {
$data[$widget->getName()] = $this->translate($widget->toArray());
}
return $this->json($data);
}
protected function translate(array $data)
{
$data['label'] = $this->translator->trans($data['label']);
foreach ($data['settings'] as $key => $value) {
$data['settings'][$key]['label'] = $this->translator->trans($data['settings'][$key]['label']);
}
return $data;
}
}

View file

@ -102,11 +102,11 @@ class RedirectAdminController extends CrudController
'attr' => ['class' => 'col-6'],
])
->setField('index', 'Enabled', Field\ButtonField::class, [
'property_builder' => function(EntityInterface $entity) {
'property_builder' => function (EntityInterface $entity) {
return $entity->getIsEnabled() ? 'Yes' : 'No';
},
'attr' => ['class' => 'col-1'],
'button_attr_builder' => function(EntityInterface $entity) {
'button_attr_builder' => function (EntityInterface $entity) {
return ['class' => 'btn btn-sm btn-'.($entity->getIsEnabled() ? 'success' : 'primary')];
},
])

View file

@ -36,7 +36,7 @@ class NavigationSettingAdminController extends AdminController
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId);
if ($lastRequest !== null && !$request->isMethod('POST')) {
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
uri: $request->getUri(),
method: 'POST',

View file

@ -56,7 +56,7 @@ class SettingAdminController extends AdminController
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId);
if ($lastRequest !== null && !$request->isMethod('POST')) {
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
uri: $request->getUri(),
method: 'POST',

View file

@ -67,7 +67,7 @@ class NavigationAdminController extends CrudController
}
#[Route(path: '/admin/site/navigation/sort/{page}', name: 'admin_site_navigation_sort', methods: ['POST'], requirements: ['page' => '\d+'])]
public function sort(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1, ): Response
public function sort(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
return $this->doSort($page, $query, $entityManager, $request, $session);
}

View file

@ -2,7 +2,6 @@
namespace App\Core\Controller\Site;
use App\Core\Controller\Admin\AdminController;
use App\Core\Entity\Site\Node;
use App\Core\Entity\Site\Node as Entity;
use App\Core\Entity\Site\Page\Page;
@ -14,15 +13,15 @@ use App\Core\Form\Site\NodeType as EntityType;
use App\Core\Manager\EntityManager;
use App\Core\Repository\Site\NodeRepository;
use App\Core\Site\ControllerLocator;
use App\Core\Site\RoleLocator;
use App\Core\Site\PageLocator;
use App\Core\Site\RoleLocator;
use App\Core\Sitemap\SitemapBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
#[Route(path: '/admin/site/node')]
class NodeAdminController extends AbstractController
@ -145,7 +144,7 @@ class NodeAdminController extends AbstractController
$page = $entity->getPage();
if ($page !== null) {
if (null !== $page) {
$pageConfiguration = $pageLocator->getPages()[get_class($page)] ?? null;
} else {
$pageConfiguration = null;

View file

@ -5,19 +5,19 @@ namespace App\Core\Controller\Site;
use App\Core\Controller\Admin\Crud\CrudController;
use App\Core\Crud\CrudConfiguration;
use App\Core\Crud\Field;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Page\Page as Entity;
use App\Core\Event\Page\PageEditEvent;
use App\Core\Form\Site\Page\Filter\PageFilterType as FilterType;
use App\Core\Form\Site\Page\PageType as Type;
use App\Core\Manager\EntityManager;
use App\Core\Repository\Site\Page\PageRepositoryQuery as RepositoryQuery;
use App\Core\Site\PageLocator;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
use App\Core\Event\Page\PageEditEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use App\Core\Entity\EntityInterface;
class PageAdminController extends CrudController
{
@ -114,7 +114,7 @@ class PageAdminController extends CrudController
}],
'attr' => ['class' => 'col-6'],
])
->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) {
->setBatchAction('index', 'delete', 'Delete', function (EntityInterface $entity, EntityManager $manager) {
$manager->delete($entity);
})
;

View file

@ -17,7 +17,6 @@ use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
class UserAdminController extends CrudController
{

View file

@ -40,7 +40,7 @@ class CrudConfiguration
return self::$self;
}
/* -- */
// --
public function setPageTitle(string $page, string $title): self
{
@ -54,7 +54,7 @@ class CrudConfiguration
return $this->pageTitles[$page] ?? $default;
}
/* -- */
// --
public function setPageRoute(string $page, string $route): self
{
@ -80,9 +80,9 @@ class CrudConfiguration
return $this->pageRouteParams[$page] ?? [];
}
/* -- */
// --
public function setForm(string $context, string $form, array $options = []): self
public function setForm(string $context, string $form): self
{
$this->forms[$context] = $form;
@ -106,7 +106,7 @@ class CrudConfiguration
return $this->formOptions[$context] ?? [];
}
/* -- */
// --
public function setAction(string $page, string $action, bool|callable $enabled): self
{
@ -135,8 +135,24 @@ class CrudConfiguration
);
}
public function setBatchAction(string $page, string $action, string $label, callable $callback): self
{
public function setGlobalBatchAction(
string $page,
string $action,
string $label,
callable $callback
): self {
$this->setBatchAction($page, $action, $label, $callback);
$this->batchActions[$page][$action]['isGlobal'] = true;
return $this;
}
public function setBatchAction(
string $page,
string $action,
string $label,
callable $callback
): self {
if (!isset($this->batchActions[$page])) {
$this->batchActions[$page] = [];
}
@ -144,6 +160,7 @@ class CrudConfiguration
$this->batchActions[$page][$action] = [
'label' => $label,
'callback' => $callback,
'isGlobal' => false,
];
return $this;
@ -164,7 +181,7 @@ class CrudConfiguration
return !empty($this->batchActions[$page]);
}
/* -- */
// --
public function setActionTitle(string $page, string $action, string $title): self
{
@ -182,7 +199,7 @@ class CrudConfiguration
return $this->actionTitles[$page][$action] ?? $default;
}
/* -- */
// --
public function setView(string $context, string $view): self
{
@ -230,7 +247,7 @@ class CrudConfiguration
return $this->viewDatas[$context][$name] ?? $defaultValue;
}
/* -- */
// --
public function setField(string $context, string $label, string $field, array $options): self
{
@ -258,7 +275,7 @@ class CrudConfiguration
return $this;
}
/* -- */
// --
public function setMaxPerPage(string $page, int $max): self
{
@ -272,7 +289,7 @@ class CrudConfiguration
return $this->maxPerPage[$page] ?? $default;
}
/* -- */
// --
public function setDoubleClick(string $page, bool $enabled): self
{
@ -286,7 +303,7 @@ class CrudConfiguration
return $this->doubleClick[$page] ?? false;
}
/* -- */
// --
public function setI18n(array $locales, string $defaultLocale): self
{
@ -311,7 +328,7 @@ class CrudConfiguration
return !empty($this->locales);
}
/* -- */
// --
public function setDefaultSort(string $context, string $label, string $direction = 'asc'): self
{

View file

@ -51,8 +51,8 @@ abstract class Field
$resolver->setAllowedTypes('href_attr', ['array', 'callable']);
$resolver->setAllowedTypes('raw', 'boolean');
$resolver->setAllowedTypes('property_builder', ['null', 'callable']);
$resolver->setAllowedValues('sort', function($value) {
if ($value === null) {
$resolver->setAllowedValues('sort', function ($value) {
if (null === $value) {
return true;
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use App\Core\BuilderBlock\BuilderBlockContainer;
class BuilderBlockPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has(BuilderBlockContainer::class)) {
return;
}
$definition = $container->findDefinition(BuilderBlockContainer::class);
$taggedServices = $container->findTaggedServiceIds('builder_block.widget');
foreach ($taggedServices as $id => $tags) {
$definition->addMethodCall('addWidget', [new Reference($id)]);
}
}
}

View file

@ -139,6 +139,7 @@ class Configuration implements ConfigurationInterface
->end()
->end()
->end();
;
return $treeBuilder;
}

View file

@ -2,6 +2,7 @@
namespace App\Core\DependencyInjection;
use App\Core\DependencyInjection\Compiler\BuilderBlockPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;

View file

@ -2,10 +2,9 @@
namespace App\Core\Entity\Analytic;
use App\Core\Entity\Site\Node;
use App\Repository\Entity\Analytic\NodeViewRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Node;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Table(name: 'analytic_referer')]
#[ORM\Entity(repositoryClass: ViewRepository::class)]

View file

@ -2,10 +2,9 @@
namespace App\Core\Entity\Analytic;
use App\Core\Entity\Site\Node;
use App\Repository\Entity\Analytic\NodeViewRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Node;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Table(name: 'analytic_view')]
#[ORM\Entity(repositoryClass: ViewRepository::class)]

View file

@ -43,7 +43,7 @@ class Navigation implements EntityInterface
protected $locale = 'en';
#[ORM\Column(type: 'string', length: 7, nullable: true)]
protected $color = null;
protected $color;
#[ORM\Column(type: 'integer', nullable: true)]
protected $sortOrder;
@ -67,7 +67,7 @@ class Navigation implements EntityInterface
return $this->label;
}
public function setLabel(string $label): self
public function setLabel(?string $label): self
{
$this->label = $label;

View file

@ -0,0 +1,20 @@
<?php
namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class BuilderBlock extends JsonBlock
{
public function getValue()
{
$value = parent::getValue();
if (is_string($value)) {
return json_decode($value, true);
}
return [];
}
}

View file

@ -5,15 +5,6 @@ namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class ChoiceBlock extends Block
class ChoiceBlock extends JsonBlock
{
public function getValue()
{
return json_decode(parent::getValue(), true);
}
public function setValue($value): self
{
return parent::setValue(json_encode($value));
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class JsonBlock extends Block
{
public function getValue()
{
return json_decode(parent::getValue(), true);
}
public function setValue($value): self
{
return parent::setValue(json_encode($value));
}
}

View file

@ -5,13 +5,12 @@ namespace App\Core\Entity\Site\Page;
use App\Core\Doctrine\Timestampable;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Node;
use App\Core\File\FileAttribute;
use App\Core\Repository\Site\Page\PageRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\File\File;
use App\Core\File\FileAttribute;
#[ORM\Entity(repositoryClass: PageRepository::class)]
#[ORM\DiscriminatorColumn(name: 'class_key', type: 'string')]
@ -89,7 +88,7 @@ class Page implements EntityInterface
}
/**
* @return Collection|Block[]
* @return Block[]|Collection
*/
public function getBlocks(): Collection
{

View file

@ -15,11 +15,8 @@ class AbTestEvent extends Event
public const INIT_EVENT = 'ab_test.init';
public const RUN_EVENT = 'ab_test.run';
protected AbTest $test;
public function __construct(AbTest $test)
public function __construct(protected AbTest $test)
{
$this->test = $test;
}
public function getTest(): AbTest

View file

@ -12,13 +12,10 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class PasswordRequestEvent extends Event
{
const EVENT = 'account_event.password_request';
public const EVENT = 'account_event.password_request';
protected User $user;
public function __construct(User $user)
public function __construct(protected User $user)
{
$this->user = $user;
}
public function getUser(): User

View file

@ -12,18 +12,15 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class EntityManagerEvent extends Event
{
const CREATE_EVENT = 'entity_manager_event.create';
const UPDATE_EVENT = 'entity_manager_event.update';
const DELETE_EVENT = 'entity_manager_event.delete';
const PRE_CREATE_EVENT = 'entity_manager_event.pre_create';
const PRE_UPDATE_EVENT = 'entity_manager_event.pre_update';
const PRE_DELETE_EVENT = 'entity_manager_event.pre_delete';
public const CREATE_EVENT = 'entity_manager_event.create';
public const UPDATE_EVENT = 'entity_manager_event.update';
public const DELETE_EVENT = 'entity_manager_event.delete';
public const PRE_CREATE_EVENT = 'entity_manager_event.pre_create';
public const PRE_UPDATE_EVENT = 'entity_manager_event.pre_update';
public const PRE_DELETE_EVENT = 'entity_manager_event.pre_delete';
protected EntityInterface $entity;
public function __construct(EntityInterface $entity)
public function __construct(protected EntityInterface $entity)
{
$this->entity = $entity;
}
public function getEntity(): EntityInterface

View file

@ -2,8 +2,8 @@
namespace App\Core\Event\Page;
use Symfony\Contracts\EventDispatcher\Event;
use App\Core\Entity\Site\Page\Page;
use Symfony\Contracts\EventDispatcher\Event;
/**
* class PageEditEvent.
@ -12,14 +12,12 @@ use App\Core\Entity\Site\Page\Page;
*/
class PageEditEvent extends Event
{
const FORM_INIT_EVENT = 'page_edit_event.form_init';
public const FORM_INIT_EVENT = 'page_edit_event.form_init';
protected Page $page;
protected array $pageBuilderOptions = [];
public function __construct(Page $page)
public function __construct(protected Page $page)
{
$this->page = $page;
}
public function getPage()

View file

@ -11,14 +11,11 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class NavigationSettingEvent extends Event
{
const INIT_EVENT = 'navigation_setting_event.init';
const FORM_INIT_EVENT = 'navigation_setting_event.form_init';
public const INIT_EVENT = 'navigation_setting_event.init';
public const FORM_INIT_EVENT = 'navigation_setting_event.form_init';
protected $data;
public function __construct($data = null)
public function __construct(protected $data = null)
{
$this->data = $data;
}
public function getData()

View file

@ -11,14 +11,11 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class SettingEvent extends Event
{
const INIT_EVENT = 'setting_event.init';
const FORM_INIT_EVENT = 'setting_event.form_init';
public const INIT_EVENT = 'setting_event.init';
public const FORM_INIT_EVENT = 'setting_event.form_init';
protected $data;
public function __construct($data = null)
public function __construct(protected $data = null)
{
$this->data = $data;
}
public function getData()

View file

@ -11,7 +11,7 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class TaskInitEvent extends Event
{
const INIT_EVENT = 'task_event.init';
public const INIT_EVENT = 'task_event.init';
protected array $tasks = [];

View file

@ -14,17 +14,13 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class TaskRunRequestedEvent extends Event
{
const RUN_REQUEST_EVENT = 'task_event.run_request';
public const RUN_REQUEST_EVENT = 'task_event.run_request';
protected string $task;
protected InputBag $parameters;
protected BufferedOutput $output;
public function __construct(string $task, InputBag $parameters, BufferedOutput $output)
{
$this->task = $task;
$this->parameters = $parameters;
$this->output = $output;
public function __construct(
protected string $task,
protected InputBag $parameters,
protected BufferedOutput $output
) {
}
public function getTask(): string

View file

@ -6,7 +6,6 @@ use App\Core\Ab\AbContainer;
use App\Core\Ab\AbTest;
use App\Core\Entity\Site\Node;
use App\Core\Event\Ab\AbTestEvent;
use App\Core\Repository\Site\NodeRepository;
use App\Core\Site\SiteRequest;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Cookie;
@ -21,19 +20,13 @@ use Symfony\Component\HttpKernel\Event\ResponseEvent;
*/
class AbListener
{
protected EventDispatcherInterface $eventDispatcher;
protected AbContainer $container;
protected SiteRequest $siteRequest;
protected ?Node $node;
public function __construct(
AbContainer $container,
EventDispatcherInterface $eventDispatcher,
SiteRequest $siteRequest
protected AbContainer $container,
protected EventDispatcherInterface $eventDispatcher,
protected SiteRequest $siteRequest
) {
$this->eventDispatcher = $eventDispatcher;
$this->container = $container;
$this->siteRequest = $siteRequest;
}
public function onKernelRequest(RequestEvent $event)
@ -70,6 +63,16 @@ class AbListener
}
}
public function onKernelResponse(ResponseEvent $event)
{
$cookies = $event->getRequest()->attributes->get('ab_test_cookies', []);
foreach ($cookies as $name => $value) {
$cookie = Cookie::create($name, $value['value'], time() + $value['duration']);
$event->getResponse()->headers->setCookie($cookie);
}
}
protected function getCookieName(): string
{
return 'ab_test_'.$this->getAbTestCode();
@ -96,14 +99,4 @@ class AbListener
return true;
}
public function onKernelResponse(ResponseEvent $event)
{
$cookies = $event->getRequest()->attributes->get('ab_test_cookies', []);
foreach ($cookies as $name => $value) {
$cookie = Cookie::create($name, $value['value'], time() + $value['duration']);
$event->getResponse()->headers->setCookie($cookie);
}
}
}

View file

@ -23,30 +23,18 @@ use Symfony\Component\HttpKernel\Event\RequestEvent;
*/
class AnalyticListener
{
protected NodeRepository $nodeRepository;
protected ViewRepositoryQuery $viewRepositoryQuery;
protected ViewFactory $viewFactory;
protected RefererRepositoryQuery $refererRepositoryQuery;
protected RefererFactory $refererFactory;
protected EntityManager $manager;
protected DeviceDetector $deviceDetector;
protected Request $request;
protected Node $node;
public function __construct(
NodeRepository $nodeRepository,
ViewRepositoryQuery $viewRepositoryQuery,
ViewFactory $viewFactory,
RefererRepositoryQuery $refererRepositoryQuery,
RefererFactory $refererFactory,
EntityManager $manager
protected NodeRepository $nodeRepository,
protected ViewRepositoryQuery $viewRepositoryQuery,
protected ViewFactory $viewFactory,
protected RefererRepositoryQuery $refererRepositoryQuery,
protected RefererFactory $refererFactory,
protected EntityManager $manager
) {
$this->nodeRepository = $nodeRepository;
$this->viewRepositoryQuery = $viewRepositoryQuery;
$this->viewFactory = $viewFactory;
$this->refererRepositoryQuery = $refererRepositoryQuery;
$this->refererFactory = $refererFactory;
$this->manager = $manager;
$this->createDeviceDetector();
}

View file

@ -3,11 +3,10 @@
namespace App\Core\EventListener;
use App\Core\Repository\RedirectRepositoryQuery;
use App\Core\Router\RedirectBuilder;
use App\Core\Router\RedirectMatcher;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use App\Core\Router\RedirectBuilder;
/**
* class RedirectListener.
@ -16,15 +15,11 @@ use App\Core\Router\RedirectBuilder;
*/
class RedirectListener
{
protected RedirectMatcher $matcher;
protected RedirectBuilder $builder;
protected RedirectRepositoryQuery $repository;
public function __construct(RedirectMatcher $matcher, RedirectBuilder $builder, RedirectRepositoryQuery $repository)
{
$this->matcher = $matcher;
$this->builder = $builder;
$this->repository = $repository;
public function __construct(
protected RedirectMatcher $matcher,
protected RedirectBuilder $builder,
protected RedirectRepositoryQuery $repository
) {
}
public function onKernelException(ExceptionEvent $event)

View file

@ -17,24 +17,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class PasswordRequestEventSubscriber implements EventSubscriberInterface
{
protected MailNotifier $notifier;
protected UrlGeneratorInterface $urlGenerator;
protected EntityManager $entityManager;
protected TokenGeneratorInterface $tokenGenerator;
protected TranslatorInterface $translator;
public function __construct(
MailNotifier $notifier,
UrlGeneratorInterface $urlGenerator,
EntityManager $entityManager,
TokenGeneratorInterface $tokenGenerator,
TranslatorInterface $translator
protected MailNotifier $notifier,
protected UrlGeneratorInterface $urlGenerator,
protected EntityManager $entityManager,
protected TokenGeneratorInterface $tokenGenerator,
protected TranslatorInterface $translator
) {
$this->notifier = $notifier;
$this->urlGenerator = $urlGenerator;
$this->entityManager = $entityManager;
$this->tokenGenerator = $tokenGenerator;
$this->translator = $translator;
}
public static function getSubscribedEvents()

View file

@ -16,12 +16,12 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
*/
class RequestSecurityEventSubscriber implements EventSubscriberInterface
{
protected NodeRepository $nodeRepository;
protected AuthorizationChecker $authorizationChecker;
public function __construct(NodeRepository $nodeRepository, ContainerInterface $container)
{
$this->nodeRepository = $nodeRepository;
public function __construct(
protected NodeRepository $nodeRepository,
ContainerInterface $container
) {
$this->authorizationChecker = $container->get('security.authorization_checker');
}

View file

@ -4,18 +4,15 @@ namespace App\Core\EventSubscriber\Site;
use App\Core\Site\SiteRequest;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use function Symfony\Component\String\u;
class ForcedDomainEventSubscriber implements EventSubscriberInterface
{
protected SiteRequest $siteRequest;
public function __construct(SiteRequest $siteRequest)
public function __construct(protected SiteRequest $siteRequest)
{
$this->siteRequest = $siteRequest;
}
public function onKernelResponse(ResponseEvent $event)
@ -38,7 +35,8 @@ class ForcedDomainEventSubscriber implements EventSubscriberInterface
->replace(
'://'.$this->siteRequest->getDomain(),
'://'.$navigation->getDomain()
);
)
;
$event->getResponse()->headers->set('Location', $uri);
$event->getResponse()->setStatusCode(Response::HTTP_MOVED_PERMANENTLY);

View file

@ -20,27 +20,14 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class MenuEventSubscriber extends EntityManagerEventSubscriber
{
protected NodeFactory $nodeFactory;
protected NodeRepository $nodeRepository;
protected EntityManager $entityManager;
protected CodeSlugify $slugify;
protected SymfonyCacheManager $cacheManager;
protected TranslatorInterface $translation;
public function __construct(
NodeFactory $nodeFactory,
NodeRepository $nodeRepository,
EntityManager $entityManager,
CodeSlugify $slugify,
SymfonyCacheManager $cacheManager,
TranslatorInterface $translator
protected NodeFactory $nodeFactory,
protected NodeRepository $nodeRepository,
protected EntityManager $entityManager,
protected CodeSlugify $slugify,
protected SymfonyCacheManager $cacheManager,
protected TranslatorInterface $translator
) {
$this->nodeFactory = $nodeFactory;
$this->nodeRepository = $nodeRepository;
$this->entityManager = $entityManager;
$this->slugify = $slugify;
$this->cacheManager = $cacheManager;
$this->translator = $translator;
}
public function supports(EntityInterface $entity): bool

View file

@ -17,11 +17,9 @@ use App\Core\Slugify\CodeSlugify;
class NavigationEventSubscriber extends EntityManagerEventSubscriber
{
public function __construct(
EntityManager $entityManager,
CodeSlugify $slugify
protected EntityManager $entityManager,
protected CodeSlugify $slugify
) {
$this->entityManager = $entityManager;
$this->slugify = $slugify;
}
public function supports(EntityInterface $entity): bool

View file

@ -12,7 +12,6 @@ use App\Core\Repository\Site\NodeRepository;
use App\Core\Slugify\CodeSlugify;
use App\Core\Slugify\RouteParameterSlugify;
use App\Core\Slugify\Slugify;
use Symfony\Component\HttpKernel\KernelInterface;
use function Symfony\Component\String\u;
/**
@ -22,27 +21,14 @@ use function Symfony\Component\String\u;
*/
class NodeEventSubscriber extends EntityManagerEventSubscriber
{
protected NodeFactory $nodeFactory;
protected EntityManager $entityManager;
protected KernelInterface $kernel;
protected Slugify $slugify;
protected CodeSlugify $codeSlugify;
protected RouteParameterSlugify $routeParameterSlugify;
public function __construct(
NodeFactory $nodeFactory,
NodeRepository $nodeRepository,
EntityManager $entityManager,
Slugify $slugify,
CodeSlugify $codeSlugify,
RouteParameterSlugify $routeParameterSlugify
protected NodeFactory $nodeFactory,
protected NodeRepository $nodeRepository,
protected EntityManager $entityManager,
protected Slugify $slugify,
protected CodeSlugify $codeSlugify,
protected RouteParameterSlugify $routeParameterSlugify
) {
$this->nodeFactory = $nodeFactory;
$this->nodeRepository = $nodeRepository;
$this->entityManager = $entityManager;
$this->slugify = $slugify;
$this->codeSlugify = $codeSlugify;
$this->routeParameterSlugify = $routeParameterSlugify;
}
public function supports(EntityInterface $entity): bool
@ -63,7 +49,7 @@ class NodeEventSubscriber extends EntityManagerEventSubscriber
$node = $event->getEntity();
$node->setCode($this->codeSlugify->slugify($node->getCode()));
$node->setCode($this->codeSlugify->slugify($node->getCode() ?? ''));
if ($node->getDisableUrl()) {
$node->setUrl(null);

View file

@ -17,11 +17,8 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
class BlockEventSubscriber extends EntityManagerEventSubscriber
{
protected FileUploadHandler $fileUpload;
public function __construct(FileUploadHandler $fileUpload)
public function __construct(protected FileUploadHandler $fileUpload)
{
$this->fileUpload = $fileUpload;
}
public function supports(EntityInterface $entity): bool

View file

@ -16,11 +16,8 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
class PageEventSubscriber extends EntityManagerEventSubscriber
{
protected FileUploadHandler $fileUpload;
public function __construct(FileUploadHandler $fileUpload)
public function __construct(protected FileUploadHandler $fileUpload)
{
$this->fileUpload = $fileUpload;
}
public function supports(EntityInterface $entity): bool

View file

@ -18,13 +18,10 @@ use Symfony\Component\HttpKernel\KernelInterface;
*/
class SiteEventSubscriber extends EntityManagerEventSubscriber
{
protected KernelInterface $kernel;
protected SymfonyCacheManager $cacheManager;
public function __construct(KernelInterface $kernel, SymfonyCacheManager $cacheManager)
{
$this->kernel = $kernel;
$this->cacheManager = $cacheManager;
public function __construct(
protected KernelInterface $kernel,
protected SymfonyCacheManager $cacheManager
) {
}
public function supports(EntityInterface $entity): bool

View file

@ -13,11 +13,8 @@ use App\Core\Event\Task\TaskRunRequestedEvent;
*/
class CacheCleanTaskEventSubscriber extends TaskEventSubscriber
{
protected SymfonyCacheManager $cacheManager;
public function __construct(SymfonyCacheManager $cacheManager)
public function __construct(protected SymfonyCacheManager $cacheManager)
{
$this->cacheManager = $cacheManager;
}
public function onInit(TaskInitEvent $event)

View file

@ -2,7 +2,6 @@
namespace App\Core\Factory;
use App\Core\Factory\FactoryInterface;
use App\Core\Entity\Redirect as Entity;
class RedirectFactory implements FactoryInterface

View file

@ -23,22 +23,15 @@ class FsFileManager
protected string $path;
protected string $pathUri;
protected array $pathLocked;
protected FileUploadHandler $uploadHandler;
protected FileInformationFactory $fileInformationFactory;
protected FileInformationRepositoryQuery $fileInformationRepositoryQuery;
public function __construct(
ParameterBagInterface $params,
FileUploadHandler $uploadHandler,
FileInformationFactory $fileInformationFactory,
FileInformationRepositoryQuery $fileInformationRepositoryQuery
protected FileUploadHandler $uploadHandler,
protected FileInformationFactory $fileInformationFactory,
protected FileInformationRepositoryQuery $fileInformationRepositoryQuery
) {
$config = $params->get('core')['file_manager'];
$this->uploadHandler = $uploadHandler;
$this->fileInformationFactory = $fileInformationFactory;
$this->fileInformationRepositoryQuery = $fileInformationRepositoryQuery;
$this->mimes = $config['mimes'];
$this->path = $config['path'];
$this->pathUri = $this->normalizePath($config['path_uri']);

View file

@ -6,7 +6,6 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
class FilePickerType extends AbstractType
{

View file

@ -2,13 +2,11 @@
namespace App\Core\Form\Filter;
use App\Core\Entity\Redirect;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
class RedirectFilterType extends AbstractType
{

View file

@ -4,12 +4,12 @@ namespace App\Core\Form;
use App\Core\Entity\Redirect;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
class RedirectType extends AbstractType
{

View file

@ -7,6 +7,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class MenuType extends AbstractType
@ -23,6 +24,7 @@ class MenuType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -37,6 +39,7 @@ class MenuType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);

View file

@ -7,6 +7,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class NavigationAdditionalDomainType extends AbstractType
@ -24,6 +25,7 @@ class NavigationAdditionalDomainType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);

View file

@ -6,12 +6,12 @@ use App\Core\Entity\Site\Navigation;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\ColorType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\Extension\Core\Type\ColorType;
class NavigationType extends AbstractType
{
@ -27,6 +27,7 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -41,6 +42,7 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -69,6 +71,7 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -109,7 +112,7 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(['min' => 2, 'max' => 10]),
new Length(min: 2, max: 10),
],
]
);

View file

@ -13,6 +13,7 @@ use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class NodeType extends AbstractType
@ -29,6 +30,7 @@ class NodeType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -43,6 +45,7 @@ class NodeType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -82,6 +85,7 @@ class NodeType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -116,6 +120,9 @@ class NodeType extends AbstractType
return $choices;
}),
'constraints' => [
new Length(max: 255),
],
]
);

View file

@ -0,0 +1,32 @@
<?php
namespace App\Core\Form\Site\Page;
use App\Core\Entity\Site\Page\BuilderBlock;
use App\Core\Form\Type\BuilderType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class BuilderBlockType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add(
'value',
BuilderType::class,
array_merge([
'required' => false,
'label' => false,
], $options['options']),
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => BuilderBlock::class,
'options' => [],
]);
}
}

View file

@ -2,11 +2,11 @@
namespace App\Core\Form\Site\Page;
use App\Core\Entity\Site\Page\Block;
use App\Core\Form\FileManager\FilePickerType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\AbstractType;
use App\Core\Entity\Site\Page\Block;
class FilePickerBlockType extends AbstractType
{

View file

@ -6,10 +6,10 @@ use App\Core\Entity\Site\Navigation;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class PageFilterType extends AbstractType
{

View file

@ -10,6 +10,7 @@ use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Image;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class PageType extends AbstractType
@ -26,6 +27,7 @@ class PageType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -39,6 +41,7 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -52,6 +55,7 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -65,6 +69,7 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -78,6 +83,7 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);

View file

@ -0,0 +1,38 @@
<?php
namespace App\Core\Form\Type;
use Symfony\Component\Form\Extension\Core\Type\CollectionType as BaseCollectionType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class BuilderType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
parent::buildView($view, $form, $options);
$view->vars = array_replace($view->vars, [
]);
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'compound' => false,
]);
}
public function getBlockPrefix()
{
return 'builder';
}
}

View file

@ -22,6 +22,8 @@ class CollectionType extends BaseCollectionType
'label_delete' => $options['label_delete'],
'allow_add' => $options['allow_add'],
'allow_delete' => $options['allow_delete'],
'template_before_item' => $options['template_before_item'],
'template_after_item' => $options['template_after_item'],
]);
}
@ -33,6 +35,8 @@ class CollectionType extends BaseCollectionType
'collection_name' => '',
'label_add' => 'Add',
'label_delete' => 'Delete',
'template_before_item' => null,
'template_after_item' => null,
]);
}

View file

@ -0,0 +1,97 @@
<?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

@ -11,8 +11,8 @@ use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use function Symfony\Component\String\u;
use Symfony\Component\Filesystem\Filesystem;
use function Symfony\Component\String\u;
class MakeCrudController extends AbstractMaker
{

View file

@ -11,7 +11,6 @@ use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use function Symfony\Component\String\u;
class MakeFactory extends AbstractMaker
{

View file

@ -8,11 +8,11 @@ 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\Bundle\MakerBundle\Str;
use Symfony\Component\Filesystem\Filesystem;
class MakePage extends AbstractMaker
@ -92,7 +92,8 @@ class MakePage extends AbstractMaker
$this->writeSuccessMessage($io);
$io->text('Register the page in <comment>config/packages/app.yaml</comment>: ');
$io->text(<<< EOF
$io->text(
<<< EOF
core:
site:
@ -100,10 +101,18 @@ core:
{$pageClassNameDetails->getFullName()}:
name: {$pageClassNameDetails->getShortName()}
templates:
- {name: "Default", file: "${templatePath}"}
- {name: "Default", file: "{$templatePath}"}
EOF
);
);
}
public function configureDependencies(DependencyBuilder $dependencies)
{
$dependencies->addClassDependency(
Annotation::class,
'doctrine/annotations'
);
}
private function askForNextBlock(ConsoleStyle $io, array $blocks, bool $isFirstField)
@ -140,6 +149,7 @@ 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,
@ -183,12 +193,4 @@ EOF
$io->writeln(sprintf(' * <comment>%s</comment>', $type));
}
}
public function configureDependencies(DependencyBuilder $dependencies)
{
$dependencies->addClassDependency(
Annotation::class,
'doctrine/annotations'
);
}
}

View file

@ -4,7 +4,6 @@ namespace App\Core\Manager;
use App\Core\Entity\EntityInterface;
use App\Core\Event\EntityManager\EntityManagerEvent;
use Doctrine\ORM\EntityManager as DoctrineEntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@ -15,14 +14,10 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*/
class EntityManager
{
protected EventDispatcherInterface $eventDispatcher;
protected DoctrineEntityManager $entityManager;
public function __construct(EventDispatcherInterface $eventDispatcher, EntityManagerInterface $entityManager)
{
$this->eventDispatcher = $eventDispatcher;
$this->entityManager = $entityManager;
public function __construct(
protected EventDispatcherInterface $eventDispatcher,
protected EntityManagerInterface $entityManager
) {
}
public function create(EntityInterface $entity, bool $dispatchEvent = true, bool $flush = true): self

View file

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

View file

@ -2,10 +2,10 @@
namespace App\Core\Notification;
use App\Entity\User;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface;
use Twig\Environment as TwigEnvironment;
use App\Entity\User;
/**
* class MailNotifier.
@ -14,7 +14,6 @@ use App\Entity\User;
*/
class MailNotifier
{
protected MailerInterface $mailer;
protected array $attachments = [];
protected array $recipients = [];
protected array $bccRecipients = [];
@ -22,10 +21,8 @@ class MailNotifier
protected ?string $from = null;
protected ?string $replyTo = null;
public function __construct(TwigEnvironment $twig, MailerInterface $mailer)
public function __construct(protected MailerInterface $mailer)
{
$this->mailer = $mailer;
$this->twig = $twig;
}
public function setMailer(Swift_Mailer $mailer): self

View file

@ -7,8 +7,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Referer|null find($id, $lockMode = null, $lockVersion = null)
* @method Referer|null findOneBy(array $criteria, array $orderBy = null)
* @method null|Referer find($id, $lockMode = null, $lockVersion = null)
* @method null|Referer findOneBy(array $criteria, array $orderBy = null)
* @method Referer[] findAll()
* @method Referer[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/

View file

@ -3,9 +3,9 @@
namespace App\Core\Repository\Analytic;
use App\Core\Repository\Analytic\RefererRepository as Repository;
use App\Core\Repository\RepositoryQuery;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
use App\Core\Repository\RepositoryQuery;
class RefererRepositoryQuery extends RepositoryQuery
{

View file

@ -7,8 +7,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method View|null find($id, $lockMode = null, $lockVersion = null)
* @method View|null findOneBy(array $criteria, array $orderBy = null)
* @method null|View find($id, $lockMode = null, $lockVersion = null)
* @method null|View findOneBy(array $criteria, array $orderBy = null)
* @method View[] findAll()
* @method View[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/

View file

@ -3,9 +3,9 @@
namespace App\Core\Repository\Analytic;
use App\Core\Repository\Analytic\ViewRepository as Repository;
use App\Core\Repository\RepositoryQuery;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
use App\Core\Repository\RepositoryQuery;
class ViewRepositoryQuery extends RepositoryQuery
{

View file

@ -7,8 +7,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method FileInformation|null find($id, $lockMode = null, $lockVersion = null)
* @method FileInformation|null findOneBy(array $criteria, array $orderBy = null)
* @method null|FileInformation find($id, $lockMode = null, $lockVersion = null)
* @method null|FileInformation findOneBy(array $criteria, array $orderBy = null)
* @method FileInformation[] findAll()
* @method FileInformation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/

View file

@ -7,8 +7,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method NavigationSetting|null find($id, $lockMode = null, $lockVersion = null)
* @method NavigationSetting|null findOneBy(array $criteria, array $orderBy = null)
* @method null|NavigationSetting find($id, $lockMode = null, $lockVersion = null)
* @method null|NavigationSetting findOneBy(array $criteria, array $orderBy = null)
* @method NavigationSetting[] findAll()
* @method NavigationSetting[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/

View file

@ -7,8 +7,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Redirect|null find($id, $lockMode = null, $lockVersion = null)
* @method Redirect|null findOneBy(array $criteria, array $orderBy = null)
* @method null|Redirect find($id, $lockMode = null, $lockVersion = null)
* @method null|Redirect findOneBy(array $criteria, array $orderBy = null)
* @method Redirect[] findAll()
* @method Redirect[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/

View file

@ -2,9 +2,8 @@
namespace App\Core\Repository;
use App\Core\Repository\RepositoryQuery;
use Knp\Component\Pager\PaginatorInterface;
use App\Core\Repository\RedirectRepository as Repository;
use Knp\Component\Pager\PaginatorInterface;
class RedirectRepositoryQuery extends RepositoryQuery
{

View file

@ -98,9 +98,20 @@ abstract class RepositoryQuery
return $this;
}
public function count()
{
return $this
->select(sprintf('COUNT(%s.id) as total', $this->id))
->query
->getQuery()
->setMaxResults(1)
->getOneOrNullResult()['total']
;
}
protected function addForcedFilterHandler(string $name): self
{
if (in_array($name, $this->forcedFilterHandlers)) {
if (!in_array($name, $this->forcedFilterHandlers)) {
$this->forcedFilterHandlers[] = $name;
}
@ -131,15 +142,4 @@ abstract class RepositoryQuery
protected function filterHandler(string $name, $value)
{
}
public function count()
{
return $this
->select(sprintf('COUNT(%s.id) as total', $this->id))
->query
->getQuery()
->setMaxResults(1)
->getOneOrNullResult()['total']
;
}
}

View file

@ -7,8 +7,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Setting|null find($id, $lockMode = null, $lockVersion = null)
* @method Setting|null findOneBy(array $criteria, array $orderBy = null)
* @method null|Setting find($id, $lockMode = null, $lockVersion = null)
* @method null|Setting findOneBy(array $criteria, array $orderBy = null)
* @method Setting[] findAll()
* @method Setting[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/

View file

@ -32,9 +32,9 @@ class NodeRepository extends NestedTreeRepository
;
}
return $query->getQuery()
return null !== $query->getQuery()
->setMaxResults(1)
->getOneOrNullResult() !== null
->getOneOrNullResult()
;
}
}

View file

@ -12,6 +12,17 @@ $pagination-active-bg: #343a40 !default;
$sidebar-width: 260px !default;
$input-border-color: map-get($theme-colors, 'dark-blue');
$input-btn-focus-color: $input-border-color;
$component-active-color: map-get($theme-colors, 'dark-blue');
$nav-tabs-link-active-bg: map-get($theme-colors, 'dark-blue');
$nav-pills-link-active-bg: map-get($theme-colors, 'dark-blue');
$nav-tabs-link-active-color: lighten(map-get($theme-colors, 'dark-blue'), 100%);
$nav-pills-link-active-color: lighten(map-get($theme-colors, 'dark-blue'), 100%);
$input-focus-border-color: lighten(map-get($theme-colors, 'dark-blue'), 80%);
@import "~choices.js/src/styles/choices.scss";
@import "~bootstrap/scss/bootstrap.scss";
@import "~@fortawesome/fontawesome-free/css/all.css";
@ -19,9 +30,29 @@ $sidebar-width: 260px !default;
@import '~grapesjs/dist/css/grapes.min.css';
@import '~grapesjs-component-code-editor/dist/grapesjs-component-code-editor.min.css';
@for $i from 1 through 100 {
.miw-#{$i*5} {
min-width: $i * 5px;
@for $i from 1 through 400 {
.miw-#{$i}, .min-width-#{$i} {
min-width: #{$i}px;
}
.min-width-#{$i}p {
min-width: #{$i}#{"%"};
}
.maw-#{$i}, .max-width-#{$i} {
max-width: #{$i}px;
}
.max-width-#{$i}p {
max-width: #{$i}#{"%"};
}
.width-#{$i} {
width: #{$i}px;
}
.width-#{$i}p {
width: #{$i}#{"%"};
}
}
@ -51,6 +82,11 @@ body {
display: block;
}
.choices__inner, .is-focused .choices__inner, .is-open .choices__inner {
border: 1px solid map-get($theme-colors, 'dark-blue');
background: #fff;
}
.dropdown-toggle-hide-after {
&::after {
display: none;
@ -110,6 +146,7 @@ body {
.table .thead-light {
a, th {
color: map-get($theme-colors, 'dark-blue');
background: lighten(map-get($theme-colors, 'dark-blue'), 80%);
}
}
@ -582,7 +619,11 @@ fieldset.form-group {
&-filter {
padding-right: 20px;
padding-bottom: 20px;
padding-bottom: 15px;
.pagination {
margin-bottom: 0;
}
@media screen and (max-width: 769px) {
padding-right: 10px;
@ -613,8 +654,20 @@ fieldset.form-group {
}
}
.table .crud-batch-column {
width: 1%;
.table {
.crud-batch-column {
width: 1%;
}
.crud-action-column {
text-align: right;
white-space: nowrap;
width: 1px;
}
}
.no-wrap {
white-space: nowrap;
}
form {
@ -672,6 +725,16 @@ form {
}
}
label.required::after {
content: '*';
margin-left: 3px;
color: #b41215;
}
.invalid-feedback {
margin-top: -3px;
}
.gjs-editor-cont {
border-radius: 10px;
overflow: hidden !important;
@ -681,11 +744,93 @@ form {
background: map-get($theme-colors, 'dark-blue');
}
.tox.tox-silver-sink.tox-tinymce-aux {
z-index: 3000 !important;
.tox {
&.tox-silver-sink.tox-tinymce-aux {
z-index: 3000 !important;
}
&.tox-tinymce {
border-color: $input-border-color;
border-radius: 5px;
}
}
.field-boolean {
color: #49555b;
font-size: 20px;
}
.builder-widget {
.block {
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 {
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;
cursor: pointer;
}
}
$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 .btn {
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 .btn {
font-size: 12px;
line-height: 14px;
padding: 3px 5px;
}
.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-bottom: 5px;
}
.block-id {
font-size: 12px;
margin-right: 5px;
}
.block-show-dropzone {
.block-dropzone {
min-height: 40px;
}
}
}

View file

@ -1,6 +1,7 @@
import '../../../../../../../../assets/css/admin.scss';
require('../../../../../../../../node_modules/bootstrap/dist/js/bootstrap.min.js')
require('./modules/sidebar.js')()
require('./modules/table-fixed.js')()
require('./modules/form-confirm.js')()
require('./modules/form-file.js')()
@ -27,5 +28,5 @@ require('./modules/file-manager.js')()
require('./modules/file-picker.js')()
require('./modules/analytics.js')()
require('./modules/page.js')()
require('./modules/sidebar.js')()
require('./modules/node.js')()
require('./modules/builder-block.js')()

View file

@ -0,0 +1,123 @@
<template>
<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"
:openedBlocks="openedBlocks"
:depth="1"
@remove-item="removeBlock(key)"
@drag-start="dragStart"
@drag-end="dragEnd"
/>
<div class="container">
<BuilderBlockCreate
:container="value"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="[]"
/>
</div>
<textarea :name="name" class="d-none">{{ toJson(value) }}</textarea>
</Draggable>
</template>
<script>
import BuilderBlockItem from './BuilderBlockItem'
import BuilderBlockCreate from './BuilderBlockCreate'
import Routing from '../../../../../../../../../friendsofsymfony/jsrouting-bundle/Resources/public/js/router.min.js'
import Draggable from 'vuedraggable'
const axios = require('axios').default
const routes = require('../../../../../../../../../../public/js/fos_js_routes.json')
Routing.setRoutingData(routes)
export default {
name: 'BuilderBlock',
props: {
id: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
initialValue: {
type: Array,
required: false,
}
},
data() {
return {
value: this.initialValue,
widgets: {},
openedBlocks: {},
blockKey: 0,
showDragDrop: false,
}
},
methods: {
toJson(value) {
return JSON.stringify(value)
},
triggerBuilderBlockEvent() {
document.querySelector('body').dispatchEvent(new Event('builder_block.update'))
},
removeBlock(key) {
let newValue = []
this.value.forEach((v, k) => {
if (k !== key) {
newValue.push(v)
}
})
this.value = newValue
++this.blockKey
},
dragStart() {
this.showDragDrop = true
},
dragEnd() {
this.showDragDrop = false
++this.blockKey
},
},
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
})
},
created() {
this.triggerBuilderBlockEvent()
},
updated() {
this.triggerBuilderBlockEvent()
}
}
</script>

View file

@ -0,0 +1,181 @@
<style scoped>
.builder-block-picker {
padding: 8px;
border: 1px solid #333;
border-radius: 4px;
background: #fff;
}
.builder-block-picker-menu {
width: 150px;
}
.builder-block-picker-widgets {
width: calc(100% - 150px - 10px);
padding-left: 5px;
}
.nav-item {
cursor: pointer;
width: 100%;
}
.widget-icon {
margin-right: 3px;
}
.widget {
background: #fff;
padding: 10px;
border-radius: 4px;
cursor: pointer;
margin-right: 7px;
margin-bottom: 9px;
border: 1px solid #b4b4b4;
font-weight: bold;
}
.widget:hover {
background: #eee;
border: 1px solid #1e2430;
}
</style>
<template>
<div class="builder-add">
<span class="btn 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
v-for="(category, key) in categories()"
v-if="Object.keys(category.widgets).length"
class="col-auto builder-block-picker-widgets"
:class="{'d-none': activeCategory !== key}"
>
<div class="row">
<div
v-for="(widget, name) in category.widgets"
v-on:click="add(name, widget)"
class="widget col-auto"
>
<span class="widget-icon" v-if="widget.icon" v-html="widget.icon"></span>
{{ widget.label }}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'BuilderBlockCreate',
props: {
container: {
type: Array,
required: true
},
widgets: {
type: Object,
required: true
},
allowedWidgets: {
type: Array,
required: true
},
openedBlocks: {
type: Object,
required: true
}
},
data() {
return {
showPicker: false,
activeCategory: 'all',
}
},
methods: {
add(name, widget) {
let settings = {}
for (let i in widget.settings) {
settings[i] = widget.settings[i].default
}
const block = {
id: this.makeId(),
widget: name,
settings,
children: [],
}
this.container.push(block)
this.openedBlocks[block.id] = true
this.$emit('updateContainer', this.container)
this.togglePicker()
},
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}`
},
togglePicker() {
this.showPicker = !this.showPicker
},
categories() {
let items = {
all: {label: 'All', widgets: {}},
}
for (let widgetName in this.widgets) {
let value = this.widgets[widgetName]
if (!value.category) {
value.category = 'all'
}
if (typeof items[value.category] === 'undefined') {
items[value.category] = {
label: value.category,
widgets: {},
}
}
if (!this.allowedWidgets.length || this.allowedWidgets.includes(widgetName)) {
items[value.category].widgets[widgetName] = value
items['all'].widgets[widgetName] = value
}
}
return items
}
},
}
</script>

View file

@ -0,0 +1,159 @@
<template>
<div
class="block"
:class="'block-depth-' + depth"
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-header-item block-label"
:title="item.widget"
>
{{ 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>
<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"
:openedBlocks="openedBlocks"
:depth="depth + 1"
@remove-item="removeBlock(key)"
@drag-start="dragStart"
@drag-end="dragEnd"
/>
</Draggable>
<div v-if="widget.isContainer" class="container">
<BuilderBlockCreate
:container="item.children"
:widgets="widgets"
:openedBlocks="openedBlocks"
:allowedWidgets="widget.widgets"
/>
</div>
</div>
</template>
<script>
import BuilderBlockCreate from './BuilderBlockCreate'
import BuilderBlockSetting from './BuilderBlockSetting'
import Draggable from 'vuedraggable'
export default {
name: 'BuilderBlockItem',
props: {
widgets: {
type: Object,
required: true
},
item: {
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,
blockKey: 0,
}
},
methods: {
toggleSettings() {
this.openedBlocks[this.item.id] = !this.openedBlocks[this.item.id]
this.showSettings = !this.showSettings
},
removeMe() {
this.$emit('remove-item')
},
removeBlock(key) {
let children = []
this.item.children.forEach((v, k) => {
if (k !== key) {
children.push(v)
}
})
this.item.children = children
++this.blockKey
},
dragStart() {
this.$emit('drag-start')
},
dragEnd() {
this.$emit('drag-end')
++this.blockKey
},
},
components: {
BuilderBlockCreate,
BuilderBlockSetting,
Draggable,
},
mounted() {
this.widget = this.widgets[this.item.widget]
},
updated() {
document.querySelector('body').dispatchEvent(new Event('builder_block.update'))
}
}
</script>

View file

@ -0,0 +1,59 @@
<template>
<label class="form-group mb-2">
<span v-if="params.label && params.type !== 'checkbox'" v-text="params.label"></span>
<input
v-if="['number', 'checkbox', 'text'].includes(params.type)"
v-model="item.settings[setting]"
v-bind="params.attr"
:type="params.type"
:class="{'form-control': params.type !== 'checkbox'}"
/>
<span v-if="params.label && params.type == 'checkbox'" v-text="params.label"></span>
<textarea
v-if="params.type == 'textarea'"
v-model="item.settings[setting]"
v-bind="params.attr"
class="form-control"
></textarea>
<select
v-if="params.type == 'select'"
v-model="item.settings[setting]"
v-bind="params.attr"
class="form-control"
>
<option :value="v.value" v-for="(v, k) in params.options" :key="k">
{{ v.text }}
</option>
</select>
</label>
</template>
<script>
export default {
name: 'BuilderBlockSetting',
props: {
item: {
type: Object,
required: true,
},
params: {
type: Object,
required: true,
},
setting: {
type: String,
required: true,
}
},
}
</script>
<style scoped>
label > span {
margin-bottom: 3px;
}
</style>

Some files were not shown because too many files have changed in this diff Show more