Compare commits

..

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

185 changed files with 2052 additions and 3257 deletions

View file

@ -1,151 +1,8 @@
## [Unreleased]
### Added
* 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
* add `addForcedFilterHandler` method in repository query
* add `inline_form_validation` option to validate inline forms with custom algo
* add crud sorting parameters in the session
* add flush option in the entity manager on create, update, remove, and persist methods
## [1.21.1] - 2023-08-17
### Added
* add form error handle in inline edit action and refill the form using the previous request content
* add form error handle in ssettings actions and refill the form using the previous request content
### Fixed
* fix tinymce reload when modal is closed and reopened
* fix modal hiding when a file is successfuly uploaded in the file manager
## [1.21.0] - 2023-08-11
### Added
* allow to use array syntax in string builder filter
* add color property in Navigation
* add badge with navigation color in admin views
* add `default_value` option in crud fields
* add `display` option in BooleanField
* add associated nodes in page form
### Fixed
* fix routes in the global settings controller
## [1.20.0] - 2023-07-27
### Added
* enable double click on cruds
* add block class name for the choice type in the page maker
* update file details view on the file manager
* add form options in the crud filter action
* add trans filter in inline form modal title
* add setter to define all fields in a defined context
* add filename generator setter in FileUploadHandler
* add variable for the sidebar size
* add twig block to override defaults actions in crud index template
* add option to remove iterable values and/or specifics keys in the twig toArray function
* add boolean field for CRUD
* add context variable in each controllers to simplify overrides
* core.site.name and core.site.logo are not longer required
* add default templates when a crud is generated
* add boolean 'is_disabled' in the menu item template options
### Fixed
* fix filemanager date ordering
* fix maker CrudController template: remove bad pasted code
* fix redirect listener: use boolean instead of integer
* fix responsive of account edit template
* fix collection widget: allow_add/allow_delete and prototype
### Changed
* user admin routes are defined in core, custom controller is not required
## [1.19.0] - 2023-04-15
### Added
* feat(page): forms for metas, opengraph and extra informations can be removed
* feat(navigation): user interface is improved
* feat(file): webp is allowed and shown in form widgets and in file manager details
* feat(file): the file manager now show the size and the modification date of a file
* feat(crud): add option `action` in field to add a link to the view page or to the edition page
* feat(crud): add option `inline_form` in field to configure to edit the data
* feat(crud): add `setDoubleClick` in the crud configuration
## [1.18.0] - 2023-01-13
### Added
* feat(dep): add symfony/runtime
* feat(dep): add symfony/flex
### Fixed
* fix(crud): allow POST in delete actions
* fix(crud): remove default page value in abstract crud controller
* fix(admin): test site_logo before using it
* fix(ui): update z-index of choices__list--dropdown
## [1.17.1] - 2022-12-03
### Fixed
* add mising attribute on timestampable (doctrine)
## [1.17.0] - 2022-11-19
### Fixed
* fix tinymce modal z-index in tox
### Changed
* replace annotation with attributes
## [1.16.0] - 2022-09-06
### Added
* add A/B testing feature
* add cleanup of html string extracted from grapesjs content
### Fixed
* fix file block type
### Changed
* remove dashboard action from the core
## [1.15.0] - 2022-05-09
### Added
* CrudConfiguration::setAction can receive a callable instead of a boolean in 'enabled' param
* add grapesjs-component-code-editor and grapesjs-parser-postcss
* hide the backoffice site name when small resolution
* add entity_to_array twig function
* add default field to show in crud configuration
### Fixed
* fix the mail notifier
* fix sitemap: navigation with several domains
* fix regression with editorjs: content not loaded
### Changed
* change default template to show an entity using `entity_to_array`
## [1.14.1] - 2022-04-30
### Added

View file

@ -37,7 +37,7 @@
"symfony/event-dispatcher": "5.4.*",
"symfony/expression-language": "5.4.*",
"symfony/finder": "5.4.*",
"symfony/flex": "^2.2",
"symfony/flex": "^1.3.1",
"symfony/form": "5.4.*",
"symfony/framework-bundle": "5.4.*",
"symfony/http-client": "5.4.*",
@ -60,8 +60,7 @@
"symfony/webpack-encore-bundle": "^1.11",
"symfony/yaml": "5.4.*",
"twig/extra-bundle": "^2.12|^3.3",
"twig/twig": "^2.12|^3.3",
"symfony/runtime": "^5.4"
"twig/twig": "^2.12|^3.3"
},
"autoload": {
"psr-4": {

View file

@ -1,30 +0,0 @@
<?php
namespace App\Core\Ab;
/**
* class AbContainer.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class AbContainer implements AbContainerInterface
{
protected array $tests = [];
public function add(AbTestInterface $test): self
{
$this->tests[$test->getName()] = $test;
return $this;
}
public function has(string $name): bool
{
return isset($this->tests[$name]);
}
public function get(string $name): AbTestInterface
{
return $this->tests[$name];
}
}

View file

@ -1,17 +0,0 @@
<?php
namespace App\Core\Ab;
/**
* interface AbContainerInterface.
*
* @author Simon Vieille <simon@deblan.fr>
*/
interface AbContainerInterface
{
public function add(AbTestInterface $test): self;
public function has(string $name): bool;
public function get(string $name): AbTestInterface;
}

View file

@ -1,123 +0,0 @@
<?php
namespace App\Core\Ab;
/**
* class AbTest.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class AbTest implements AbTestInterface
{
protected $results;
protected array $variations = [];
protected array $probabilities = [];
protected int $duration = 3600 * 24;
public function __construct(protected string $name)
{
}
public function getName(): string
{
return $this->name;
}
public function getResult()
{
return $this->result;
}
public function setResult(string $result): self
{
$this->result = $result;
return $this;
}
public function isValidVariation($variation): bool
{
return array_key_exists($variation, $this->variations);
}
public function addVariation(string $name, $value, int $probability = null): self
{
$this->variations[$name] = $value;
$this->probabilities[$name] = $probability;
return $this;
}
public function getVariation($variation)
{
return $this->variations[$variation];
}
public function getResultValue()
{
return $this->getVariation($this->getResult());
}
public function setDuration(int $duration): self
{
$this->duration = $duration;
return $this;
}
public function getDuration(): int
{
return $this->duration;
}
public function run(): self
{
$this->result = $this->chance();
return $this;
}
protected function chance(): string
{
$sum = 0;
$empty = 0;
foreach ($this->probabilities as $name => $value) {
$sum += $value;
if (empty($value)) {
++$empty;
}
}
if ($sum > 100) {
throw new \LogicException('Test Error: Total variation probabilities is bigger than 100%');
}
if ($sum < 100) {
foreach ($this->probabilities as $name => $value) {
if (empty($value)) {
$this->probabilities[$name] = (100 - $sum) / $empty;
}
}
}
krsort($this->probabilities);
$number = mt_rand(0, (int) array_sum($this->probabilities) * 10);
$starter = 0;
$return = '';
foreach ($this->probabilities as $key => $val) {
$starter += $val * 10;
if ($number <= $starter) {
$return = $key;
break;
}
}
return $return;
}
}

View file

@ -1,31 +0,0 @@
<?php
namespace App\Core\Ab;
/**
* interface AbTestInterface.
*
* @author Simon Vieille <simon@deblan.fr>
*/
interface AbTestInterface
{
public function getName(): string;
public function getResult();
public function setResult(string $result): self;
public function isValidVariation($variation): bool;
public function addVariation(string $name, $value, int $probability = null): self;
public function getVariation($variation);
public function getResultValue();
public function setDuration(int $duration): self;
public function getDuration(): int;
public function run(): self;
}

View file

@ -13,16 +13,18 @@ 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(
protected ViewRepositoryQuery $viewQuery,
protected RefererRepositoryQuery $refererQuery
) {
public function __construct(ViewRepositoryQuery $viewQuery, RefererRepositoryQuery $refererQuery)
{
$this->viewQuery = $viewQuery;
$this->refererQuery = $refererQuery;
}
public function getViews(): array
@ -81,7 +83,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;
}
@ -128,7 +130,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

@ -2,18 +2,19 @@
namespace App\Core\Annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* class UrlGenerator.
*
* @author Simon Vieille <simon@deblan.fr>
* @Annotation
*/
#[\Attribute]
class UrlGenerator
{
public function __construct(
public string $service,
public string $method,
public array $options = []
) {
}
public string $service;
public string $method;
public array $options = [];
}

View file

@ -23,12 +23,20 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
public function __construct(
private EntityManagerInterface $entityManager,
private UrlGeneratorInterface $urlGenerator,
private CsrfTokenManagerInterface $csrfTokenManager,
private UserPasswordEncoderInterface $passwordEncoder
) {
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 supports(Request $request)

View file

@ -12,8 +12,8 @@
namespace App\Core\Bundle;
use App\Core\DependencyInjection\CoreExtension;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
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\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;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpClient\Exception\TransportException;
/**
* class SymfonyCacheManager.
@ -20,11 +20,15 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
*/
class SymfonyCacheManager
{
public function __construct(
protected KernelInterface $kernel,
protected HttpClientInterface $httpClient,
protected UrlGeneratorInterface $urlGenerator
) {
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 cleanRouting()

View file

@ -18,12 +18,19 @@ 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(
protected UserFactory $userFactory,
protected EntityManager $entityManager,
protected TokenGeneratorInterface $tokenGenerator
UserFactory $userFactory,
EntityManager $entityManager,
TokenGeneratorInterface $tokenGenerator
) {
$this->userFactory = $userFactory;
$this->entityManager = $entityManager;
$this->tokenGenerator = $tokenGenerator;
parent::__construct();
}

View file

@ -14,10 +14,14 @@ use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
use ZxcvbnPhp\Zxcvbn;
#[Route(path: '/admin/account')]
/**
* @Route("/admin/account")
*/
class AccountAdminController extends AdminController
{
#[Route(path: '/', name: 'admin_account')]
/**
* @Route("/", name="admin_account")
*/
public function account(Request $request, TotpAuthenticatorInterface $totpAuthenticatorService): Response
{
$account = $this->getUser();
@ -27,7 +31,9 @@ class AccountAdminController extends AdminController
]);
}
#[Route(path: '/2fa', name: 'admin_account_2fa')]
/**
* @Route("/2fa", name="admin_account_2fa")
*/
public function twoFactorAuthentication(
Request $request,
GoogleAuthenticatorInterface $totpAuthenticatorService,
@ -87,7 +93,9 @@ class AccountAdminController extends AdminController
]);
}
#[Route(path: '/password', name: 'admin_account_password', methods: ['POST'])]
/**
* @Route("/password", name="admin_account_password", methods={"POST"})
*/
public function password(
Request $request,
UserRepository $repository,

View file

@ -9,7 +9,9 @@ use Symfony\Component\Routing\Annotation\Route;
abstract class AdminController extends AbstractController
{
#[Route(path: '/_ping', name: '_ping')]
/**
* @Route("/_ping", name="_ping")
*/
public function ping()
{
return $this->json(true);

View file

@ -26,7 +26,7 @@ abstract class CrudController extends AdminController
abstract protected function getConfiguration(): CrudConfiguration;
protected function doIndex(int $page, RepositoryQuery $query, Request $request, Session $session, string $context = 'index'): Response
protected function doIndex(int $page = 1, RepositoryQuery $query, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
@ -35,14 +35,13 @@ abstract class CrudController extends AdminController
$pager = $query
->usefilters($this->filters)
->paginate($page, $configuration->getMaxPerPage($context))
->paginate($page, $configuration->getmaxperpage('index'))
;
return $this->render($this->getConfiguration()->getView($context), [
return $this->render($this->getConfiguration()->getView('index'), [
'configuration' => $configuration,
'pager' => $pager,
'sort' => $this->sort,
'context' => $context,
'filters' => [
'show' => null !== $configuration->getForm('filter'),
'isEmpty' => empty($this->filters),
@ -50,13 +49,13 @@ abstract class CrudController extends AdminController
]);
}
protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeCreate = null, string $context = 'new'): Response
protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeCreate = null): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm('new'), $entity, $configuration->getFormOptions($context));
$form = $this->createForm($configuration->getForm('new'), $entity, $configuration->getFormOptions('new'));
if ($request->isMethod('POST')) {
$form->handleRequest($request);
@ -77,32 +76,30 @@ abstract class CrudController extends AdminController
$this->addFlash('warning', 'The form is not valid.');
}
return $this->render($configuration->getView($context), [
return $this->render($configuration->getView('new'), [
'form' => $form->createView(),
'configuration' => $configuration,
'context' => $context,
'entity' => $entity,
]);
}
protected function doShow(EntityInterface $entity, string $context = 'show'): Response
protected function doShow(EntityInterface $entity): Response
{
$configuration = $this->getConfiguration();
return $this->render($configuration->getView($context), [
return $this->render($configuration->getView('show'), [
'entity' => $entity,
'context' => $context,
'configuration' => $configuration,
]);
}
protected function doEdit(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null, string $context = 'edit'): Response
protected function doEdit(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm('edit'), $entity, $configuration->getFormOptions($context));
$form = $this->createForm($configuration->getForm('edit'), $entity, $configuration->getFormOptions('edit'));
if ($request->isMethod('POST')) {
$form->handleRequest($request);
@ -115,109 +112,22 @@ abstract class CrudController extends AdminController
$entityManager->update($entity);
$this->addFlash('success', 'The data has been saved.');
return $this->redirectToRoute($configuration->getPageRoute($context), array_merge(
return $this->redirectToRoute($configuration->getPageRoute('edit'), array_merge(
['entity' => $entity->getId()],
$configuration->getPageRouteParams($context)
$configuration->getPageRouteParams('edit')
));
}
$this->addFlash('warning', 'The form is not valid.');
}
return $this->render($configuration->getView($context), [
return $this->render($configuration->getView('edit'), [
'form' => $form->createView(),
'context' => $context,
'configuration' => $configuration,
'entity' => $entity,
]);
}
protected function doInlineEdit(string $context, string $label, EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$builder = $this->createFormBuilder($entity);
$callback = $configuration->getFields($context)[$label]['options']['inline_form'] ?? null;
$validationCallback = $configuration->getFields($context)[$label]['options']['inline_form_validation'] ?? null;
if (null === $callback) {
throw $this->createNotFoundException();
}
call_user_func_array($callback, [$builder, $entity]);
$redirectTo = $request->query->get('redirectTo');
$form = $builder->getForm();
$session = $request->getSession();
$lastRequestId = sprintf(
'inline_request_%s_%s_%s_%s',
get_class($entity),
$entity->getId(),
$context,
$label
);
$lastRequest = $session->get($lastRequestId);
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
uri: $request->getUri(),
method: 'POST',
parameters: [$form->getName() => $lastRequest]
);
$form->handleRequest($fakeRequest);
if (null !== $validationCallback) {
call_user_func_array($validationCallback, [$entity, $form, $request]);
}
$session->remove($lastRequestId);
}
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if (null !== $validationCallback) {
call_user_func_array($validationCallback, [$entity, $form, $request]);
}
if ($form->isValid()) {
if (null !== $beforeUpdate) {
call_user_func_array($beforeUpdate, [$entity, $form, $request]);
}
$session->remove($lastRequestId);
$entityManager->update($entity);
$this->addFlash('success', 'The data has been saved.');
return $this->redirect($redirectTo);
}
$session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.');
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->render($configuration->getView('inline_edit'), [
'form' => $form->createView(),
'configuration' => $configuration,
'entity' => $entity,
'context' => $context,
'label' => $label,
'redirectTo' => $redirectTo,
]);
}
protected function doSort(int $page, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
protected function doSort(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
$context = $request->query->get('context', 'index');
@ -255,7 +165,7 @@ abstract class CrudController extends AdminController
return $this->json([]);
}
protected function doBatch(int $page, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
protected function doBatch(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
$datas = $request->request->get('batch', []);
@ -284,39 +194,16 @@ abstract class CrudController extends AdminController
$query->useFilters($this->filters);
$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'));
if ('selection' === $target) {
$isSelection = true;
$pager = $query->paginate($page, $configuration->getMaxPerPage($context));
} else {
$isSelection = false;
$pager = $query->find();
}
$pager = $useSelection
? $query->paginate($page, $configuration->getMaxPerPage($context))
: $query->find()
;
foreach ($pager as $key => $entity) {
if (($useSelection && isset($items[$key + 1])) || !$useSelection) {
if (($isSelection && isset($items[$key + 1])) || !$isSelection) {
$callback($entity, $entityManager);
}
}
@ -326,7 +213,7 @@ abstract class CrudController extends AdminController
return $this->json([]);
}
protected function doDelete(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeDelete = null, string $route = 'index'): Response
protected function doDelete(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeDelete = null): Response
{
$configuration = $this->getConfiguration();
@ -340,10 +227,10 @@ abstract class CrudController extends AdminController
$this->addFlash('success', 'The data has been removed.');
}
return $this->redirectToRoute($configuration->getPageRoute($route));
return $this->redirectToRoute($configuration->getPageRoute('index'));
}
protected function doFilter(Session $session, string $context = 'filter'): Response
protected function doFilter(Session $session): Response
{
$configuration = $this->getConfiguration();
$type = $configuration->getForm('filter');
@ -352,12 +239,11 @@ abstract class CrudController extends AdminController
throw $this->createNotFoundException();
}
$form = $this->createForm($type, null, $configuration->getFormOptions('filter'));
$form = $this->createForm($type);
$form->submit($session->get($form->getName(), []));
return $this->render($configuration->getView($context), [
return $this->render($configuration->getView('filter'), [
'form' => $form->createView(),
'context' => $context,
'configuration' => $configuration,
]);
}
@ -371,7 +257,7 @@ abstract class CrudController extends AdminController
return;
}
$form = $this->createForm($type, null, $configuration->getFormOptions('filter'));
$form = $this->createForm($type);
if ($request->query->has($form->getName())) {
$filters = $request->query->get($form->getName());
@ -418,27 +304,9 @@ abstract class CrudController extends AdminController
}
$defaultSort = $configuration->getDefaultSort($context);
$session = $request->getSession();
$sessionId = sprintf('%s_%s_sort', $context, get_called_class());
$sessionSortName = sprintf('%s_label', $sessionId);
$sessionSortDirection = sprintf('%s_direction', $sessionId);
$name = $request->query->get('_sort', $session->get($sessionSortName)) ?? $defaultSort['label'] ?? null;
$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;
}
$name = $request->query->get('_sort', $defaultSort['label'] ?? null);
$direction = strtolower($request->query->get('_sort_direction', $defaultSort['direction'] ?? 'asc'));
if (!in_array($direction, ['asc', 'desc'])) {
$direction = 'asc';

View file

@ -8,10 +8,14 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route(path: '/admin/analytic')]
/**
* @Route("/admin/analytic")
*/
class AnalyticController extends AbstractController
{
#[Route(path: '/stats/{node}/{range}', name: 'admin_analytic_stats')]
/**
* @Route("/stats/{node}/{range}", name="admin_analytic_stats")
*/
public function stats(Node $node, DateRangeAnalytic $analytic, string $range = '7days'): Response
{
if (!in_array($range, ['7days', '30days', '90days', '1year'])) {

View file

@ -25,7 +25,9 @@ class AuthController extends AbstractController
$this->coreParameters = $parameters->get('core');
}
#[Route(path: '/login', name: 'auth_login')]
/**
* @Route("/login", name="auth_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
@ -43,7 +45,9 @@ class AuthController extends AbstractController
]);
}
#[Route(path: '/resetting/request', name: 'auth_resetting_request')]
/**
* @Route("/resetting/request", name="auth_resetting_request")
*/
public function requestResetting(Request $request, UserRepository $repository, EventDispatcherInterface $eventDispatcher): Response
{
if ($this->getUser()) {
@ -81,7 +85,9 @@ class AuthController extends AbstractController
]);
}
#[Route(path: '/resetting/update/{token}', name: 'auth_resetting_update')]
/**
* @Route("/resetting/update/{token}", name="auth_resetting_update")
*/
public function requestUpdate(
string $token,
Request $request,
@ -139,7 +145,9 @@ class AuthController extends AbstractController
]);
}
#[Route(path: '/logout', name: 'auth_logout')]
/**
* @Route("/logout", name="auth_logout")
*/
public function logout()
{
throw new \Exception('This method can be blank - it will be intercepted by the logout key on your firewall');

View file

@ -3,9 +3,23 @@
namespace App\Core\Controller\Dashboard;
use App\Core\Controller\Admin\AdminController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin")
*/
class DashboardAdminController extends AdminController
{
/**
* @Route("/", name="admin_dashboard_index")
*/
public function index(): Response
{
return $this->render('@Core/dashboard/index.html.twig', [
]);
}
protected function getSection(): string
{
return 'dashboard';

View file

@ -9,10 +9,14 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\HttpClient\HttpClientInterface;
#[Route(path: '/admin/editor/editorjs')]
/**
* @Route("/admin/editor/editorjs")
*/
class EditorJsController extends AbstractController
{
#[Route(path: '/fetch_url', name: 'admin_editor_editorjs_fetch_url', options: ['expose' => true])]
/**
* @Route("/fetch_url", name="admin_editor_editorjs_fetch_url", options={"expose"=true})
*/
public function fetchUrl(Request $request, HttpClientInterface $client): JsonResponse
{
$url = filter_var($request->query->get('url'), FILTER_VALIDATE_URL);

View file

@ -15,16 +15,22 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
#[Route(path: '/admin/file_manager')]
/**
* @Route("/admin/file_manager")
*/
class FileManagerAdminController extends AdminController
{
#[Route(path: '/', name: 'admin_file_manager_index')]
/**
* @Route("/", name="admin_file_manager_index")
*/
public function index(): Response
{
return $this->render('@Core/file_manager/index.html.twig');
}
#[Route(path: '/api/directory', name: 'admin_file_manager_api_directory', options: ['expose' => true])]
/**
* @Route("/api/directory", name="admin_file_manager_api_directory", options={"expose"=true})
*/
public function directory(FsFileManager $manager, Request $request): Response
{
$options = [
@ -37,7 +43,9 @@ class FileManagerAdminController extends AdminController
return $this->json($files);
}
#[Route(path: '/info/{tab}/{context}/{ajax}', name: 'admin_file_manager_info', options: ['expose' => true])]
/**
* @Route("/info/{tab}/{context}/{ajax}", name="admin_file_manager_info", options={"expose"=true})
*/
public function info(
FsFileManager $manager,
Request $request,
@ -107,7 +115,9 @@ class FileManagerAdminController extends AdminController
]);
}
#[Route(path: '/directory/new/{ajax}', name: 'admin_file_manager_directory_new', options: ['expose' => true], methods: ['GET', 'POST'])]
/**
* @Route("/directory/new/{ajax}", name="admin_file_manager_directory_new", options={"expose"=true}, methods={"GET", "POST"})
*/
public function directoryNew(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -174,7 +184,9 @@ class FileManagerAdminController extends AdminController
]);
}
#[Route(path: '/directory/rename/{ajax}', name: 'admin_file_manager_directory_rename', methods: ['GET', 'POST'])]
/**
* @Route("/directory/rename/{ajax}", name="admin_file_manager_directory_rename", methods={"GET", "POST"})
*/
public function directoryRename(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -243,7 +255,9 @@ class FileManagerAdminController extends AdminController
]);
}
#[Route(path: '/file/rename/{ajax}', name: 'admin_file_manager_file_rename', methods: ['GET', 'POST'])]
/**
* @Route("/file/rename/{ajax}", name="admin_file_manager_file_rename", methods={"GET", "POST"})
*/
public function fileRename(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -313,7 +327,9 @@ class FileManagerAdminController extends AdminController
]);
}
#[Route(path: '/upload/{ajax}', name: 'admin_file_manager_upload', options: ['expose' => true], methods: ['GET', 'POST'])]
/**
* @Route("/upload/{ajax}", name="admin_file_manager_upload", options={"expose"=true}, methods={"GET", "POST"})
*/
public function upload(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -390,7 +406,9 @@ class FileManagerAdminController extends AdminController
]);
}
#[Route(path: '/delete', name: 'admin_file_manager_delete', methods: ['DELETE', 'POST'])]
/**
* @Route("/delete", name="admin_file_manager_delete", methods={"DELETE"})
*/
public function delete(FsFileManager $manager, Request $request): Response
{
$path = $request->request->get('file');

View file

@ -19,49 +19,65 @@ use Symfony\Component\Routing\Annotation\Route;
class RedirectAdminController extends CrudController
{
#[Route(path: '/admin/redirect/{page}', name: 'admin_redirect_index', methods: ['GET'], requirements: ['page' => '\d+'])]
/**
* @Route("/admin/redirect/{page}", name="admin_redirect_index", methods={"GET"}, requirements={"page":"\d+"})
*/
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return $this->doIndex($page, $query, $request, $session);
}
#[Route(path: '/admin/redirect/new', name: 'admin_redirect_new', methods: ['GET', 'POST'])]
/**
* @Route("/admin/redirect/new", name="admin_redirect_new", methods={"GET", "POST"})
*/
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
{
return $this->doNew($factory->create(), $entityManager, $request);
}
#[Route(path: '/admin/redirect/show/{entity}', name: 'admin_redirect_show', methods: ['GET'])]
/**
* @Route("/admin/redirect/show/{entity}", name="admin_redirect_show", methods={"GET"})
*/
public function show(Entity $entity): Response
{
return $this->doShow($entity);
}
#[Route(path: '/admin/redirect/filter', name: 'admin_redirect_filter', methods: ['GET'])]
/**
* @Route("/admin/redirect/filter", name="admin_redirect_filter", methods={"GET"})
*/
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
#[Route(path: '/admin/redirect/edit/{entity}', name: 'admin_redirect_edit', methods: ['GET', 'POST'])]
/**
* @Route("/admin/redirect/edit/{entity}", name="admin_redirect_edit", methods={"GET", "POST"})
*/
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doEdit($entity, $entityManager, $request);
}
#[Route(path: '/admin/redirect/sort/{page}', name: 'admin_redirect_sort', methods: ['POST'], requirements: ['page' => '\d+'])]
/**
* @Route("/admin/redirect/sort/{page}", name="admin_redirect_sort", methods={"POST"}, requirements={"page":"\d+"})
*/
public function sort(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
return $this->doSort($page, $query, $entityManager, $request, $session);
}
#[Route(path: '/admin/redirect/batch/{page}', name: 'admin_redirect_batch', methods: ['POST'], requirements: ['page' => '\d+'])]
/**
* @Route("/admin/redirect/batch/{page}", name="admin_redirect_batch", methods={"POST"}, requirements={"page":"\d+"})
*/
public function batch(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
return $this->doBatch($page, $query, $entityManager, $request, $session);
}
#[Route(path: '/admin/redirect/delete/{entity}', name: 'admin_redirect_delete', methods: ['DELETE', 'POST'])]
/**
* @Route("/admin/redirect/delete/{entity}", name="admin_redirect_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
@ -102,11 +118,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

@ -11,10 +11,14 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route(path: '/admin/navigation_setting')]
/**
* @Route("/admin/navigation_setting")
*/
class NavigationSettingAdminController extends AdminController
{
#[Route(path: '/edit/{entity}', name: 'admin_navigation_setting_edit')]
/**
* @Route("/edit/{entity}", name="admin_navigation_setting_edit")
*/
public function edit(
Entity $entity,
EntityManager $entityManager,
@ -31,28 +35,11 @@ class NavigationSettingAdminController extends AdminController
$eventDispatcher->dispatch($event, NavigationSettingEvent::FORM_INIT_EVENT);
$form = $builder->getForm();
$redirectTo = $request->query->get('redirectTo');
$session = $request->getSession();
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId);
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
uri: $request->getUri(),
method: 'POST',
parameters: [$form->getName() => $lastRequest]
);
$form->handleRequest($fakeRequest);
$session->remove($lastRequestId);
}
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
$entityManager->update($entity);
$session->remove($lastRequestId);
$entityManager->update($entity);
$this->addFlash('success', 'The data has been saved.');
@ -61,25 +48,19 @@ class NavigationSettingAdminController extends AdminController
]);
}
$session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.');
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->render('@Core/setting/navigation_setting_admin/edit.html.twig', [
'form' => $form->createView(),
'entity' => $entity,
'options' => $event->getData()['options'],
'redirectTo' => $redirectTo,
]);
}
#[Route(path: '/delete/{entity}', name: 'admin_navigation_setting_delete', methods: ['DELETE', 'POST'])]
/**
* @Route("/delete/{entity}", name="admin_navigation_setting_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {

View file

@ -12,10 +12,14 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route(path: '/admin/setting')]
/**
* @Route("/admin/setting")
*/
class SettingAdminController extends AdminController
{
#[Route(path: '/{page}', name: 'admin_setting_index', requirements: ['page' => '\d+'])]
/**
* @Route("/{page}", name="admin_setting_index", requirements={"page": "\d+"})
*/
public function index(
RepositoryQuery $query,
EventDispatcherInterface $eventDispatcher,
@ -34,7 +38,9 @@ class SettingAdminController extends AdminController
]);
}
#[Route(path: '/edit/{entity}', name: 'admin_setting_edit')]
/**
* @Route("/edit/{entity}", name="admin_setting_edit")
*/
public function edit(
Entity $entity,
EntityManager $entityManager,
@ -51,53 +57,30 @@ class SettingAdminController extends AdminController
$eventDispatcher->dispatch($event, SettingEvent::FORM_INIT_EVENT);
$form = $builder->getForm();
$redirectTo = $request->query->get('redirectTo');
$session = $request->getSession();
$lastRequestId = sprintf('setting_request_%s_%s', get_class($entity), $entity->getId());
$lastRequest = $session->get($lastRequestId);
if (null !== $lastRequest && !$request->isMethod('POST')) {
$fakeRequest = Request::create(
uri: $request->getUri(),
method: 'POST',
parameters: [$form->getName() => $lastRequest]
);
$form->handleRequest($fakeRequest);
$session->remove($lastRequestId);
}
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
$entityManager->update($entity);
$session->remove($lastRequestId);
$entityManager->update($entity);
$this->addFlash('success', 'The data has been saved.');
return $this->redirectToRoute('admin_setting_index');
}
$session->set($lastRequestId, $request->request->get('form'));
$this->addFlash('warning', 'The form is not valid.');
return $this->redirect(sprintf(
'%s?data-modal=%s',
$redirectTo,
urlencode($request->getUri())
));
}
return $this->render('@Core/setting/setting_admin/edit.html.twig', [
'form' => $form->createView(),
'entity' => $entity,
'options' => $event->getData()['options'],
'redirectTo' => $redirectTo,
]);
}
#[Route(path: '/delete/{entity}', name: 'admin_setting_delete', methods: ['DELETE', 'POST'])]
/**
* @Route("/delete/{entity}", name="admin_setting_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {

View file

@ -12,10 +12,14 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route(path: '/admin/site/menu')]
/**
* @Route("/admin/site/menu")
*/
class MenuAdminController extends AdminController
{
#[Route(path: '/new/{navigation}', name: 'admin_site_menu_new', methods: ['POST'])]
/**
* @Route("/new/{navigation}", name="admin_site_menu_new", methods={"POST"})
*/
public function new(Navigation $navigation, EntityFactory $factory, EntityManager $entityManager, Request $request): Response
{
$entity = $factory->create($navigation);
@ -35,7 +39,9 @@ class MenuAdminController extends AdminController
]);
}
#[Route(path: '/edit/{entity}', name: 'admin_site_menu_edit', methods: ['POST'])]
/**
* @Route("/edit/{entity}", name="admin_site_menu_edit", methods={"POST"})
*/
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
$form = $this->createForm(EntityType::class, $entity);
@ -53,7 +59,9 @@ class MenuAdminController extends AdminController
]);
}
#[Route(path: '/delete/{entity}', name: 'admin_site_menu_delete', methods: ['DELETE', 'POST'])]
/**
* @Route("/delete/{entity}", name="admin_site_menu_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {

View file

@ -20,19 +20,25 @@ use Symfony\Component\Routing\Annotation\Route;
class NavigationAdminController extends CrudController
{
#[Route(path: '/admin/site/navigation/{page}', name: 'admin_site_navigation_index', methods: ['GET'], requirements: ['page' => '\d+'])]
/**
* @Route("/admin/site/navigation/{page}", name="admin_site_navigation_index", methods={"GET"}, requirements={"page":"\d+"})
*/
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return $this->doIndex($page, $query, $request, $session);
}
#[Route(path: '/admin/site/navigation/new', name: 'admin_site_navigation_new', methods: ['GET', 'POST'])]
/**
* @Route("/admin/site/navigation/new", name="admin_site_navigation_new", methods={"GET", "POST"})
*/
public function new(Factory $factory, EntityManager $entityManager, Request $request): Response
{
return $this->doNew($factory->create(), $entityManager, $request);
}
#[Route(path: '/admin/site/navigation/show/{entity}', name: 'admin_site_navigation_show', methods: ['GET'])]
/**
* @Route("/admin/site/navigation/show/{entity}", name="admin_site_navigation_show", methods={"GET"})
*/
public function show(
Entity $entity,
EventDispatcherInterface $eventDispatcher,
@ -54,25 +60,33 @@ class NavigationAdminController extends CrudController
return $this->doShow($entity);
}
#[Route(path: '/admin/site/navigation/filter', name: 'admin_site_navigation_filter', methods: ['GET'])]
/**
* @Route("/admin/site/navigation/filter", name="admin_site_navigation_filter", methods={"GET"})
*/
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
#[Route(path: '/admin/site/navigation/edit/{entity}', name: 'admin_site_navigation_edit', methods: ['GET', 'POST'])]
/**
* @Route("/admin/site/navigation/edit/{entity}", name="admin_site_navigation_edit", methods={"GET", "POST"})
*/
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doEdit($entity, $entityManager, $request);
}
#[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
/**
* @Route("/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
{
return $this->doSort($page, $query, $entityManager, $request, $session);
}
#[Route(path: '/admin/site/navigation/delete/{entity}', name: 'admin_site_navigation_delete', methods: ['DELETE', 'POST'])]
/**
* @Route("/admin/site/navigation/delete/{entity}", name="admin_site_navigation_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
@ -104,11 +118,9 @@ class NavigationAdminController extends CrudController
->setView('form', '@Core/site/navigation_admin/_form.html.twig')
->setIsSortableCollection('index', true)
->setDoubleClick('index', true)
->setField('index', 'Label', Field\TextField::class, [
'property' => 'label',
'view' => '@Core/site/navigation_admin/field/label.html.twig',
'attr' => ['class' => 'miw-200'],
])
->setField('index', 'Domain', Field\ButtonField::class, [

View file

@ -2,6 +2,7 @@
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;
@ -13,20 +14,24 @@ 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\PageLocator;
use App\Core\Site\RoleLocator;
use App\Core\Site\PageLocator;
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')]
/**
* @Route("/admin/site/node")
*/
class NodeAdminController extends AbstractController
{
#[Route(path: '/new/{node}', name: 'admin_site_node_new')]
/**
* @Route("/new/{node}", name="admin_site_node_new")
*/
public function new(
Node $node,
EntityFactory $factory,
@ -98,7 +103,9 @@ class NodeAdminController extends AbstractController
]);
}
#[Route(path: '/edit/{entity}/{tab}', name: 'admin_site_node_edit')]
/**
* @Route("/edit/{entity}/{tab}", name="admin_site_node_edit")
*/
public function edit(
Entity $entity,
EntityManager $entityManager,
@ -144,7 +151,7 @@ class NodeAdminController extends AbstractController
$page = $entity->getPage();
if (null !== $page) {
if ($page !== null) {
$pageConfiguration = $pageLocator->getPages()[get_class($page)] ?? null;
} else {
$pageConfiguration = null;
@ -159,7 +166,9 @@ class NodeAdminController extends AbstractController
]);
}
#[Route(path: '/urls/{entity}', name: 'admin_site_node_urls')]
/**
* @Route("/urls/{entity}", name="admin_site_node_urls")
*/
public function urls(Entity $entity, SitemapBuilder $builder): Response
{
return $this->render('@Core/site/node_admin/urls.html.twig', [
@ -168,7 +177,9 @@ class NodeAdminController extends AbstractController
]);
}
#[Route(path: '/move/{entity}', name: 'admin_site_node_move')]
/**
* @Route("/move/{entity}", name="admin_site_node_move")
*/
public function move(
Entity $entity,
EntityManager $entityManager,
@ -222,7 +233,9 @@ class NodeAdminController extends AbstractController
]);
}
#[Route(path: '/toggle/visibility/{entity}', name: 'admin_site_node_toggle_visibility', methods: ['POST'])]
/**
* @Route("/toggle/visibility/{entity}", name="admin_site_node_toggle_visibility", methods={"POST"})
*/
public function toggleVisibility(Entity $entity, EntityManager $entityManager, Request $request): Response
{
if ($this->isCsrfTokenValid('toggle_visibility'.$entity->getId(), $request->request->get('_token'))) {
@ -238,7 +251,9 @@ class NodeAdminController extends AbstractController
]).sprintf('#node-%d', $entity->getId()));
}
#[Route(path: '/delete/{entity}', name: 'admin_site_node_delete', methods: ['DELETE', 'POST'])]
/**
* @Route("/delete/{entity}", name="admin_site_node_delete", methods={"DELETE"})
*/
public function delete(
Entity $entity,
NodeRepository $nodeRepository,

View file

@ -5,41 +5,49 @@ 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
{
#[Route(path: '/admin/site/page/{page}', name: 'admin_site_page_index', methods: ['GET'], requirements: ['page' => '\d+'])]
/**
* @Route("/admin/site/page/{page}", name="admin_site_page_index", methods={"GET"}, requirements={"page":"\d+"})
*/
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return $this->doIndex($page, $query, $request, $session);
}
#[Route(path: '/admin/site/page/show/{entity}', name: 'admin_site_page_show', methods: ['GET'])]
/**
* @Route("/admin/site/page/show/{entity}", name="admin_site_page_show", methods={"GET"})
*/
public function show(Entity $entity): Response
{
return $this->doShow($entity);
}
#[Route(path: '/admin/site/page/filter', name: 'admin_site_page_filter', methods: ['GET'])]
/**
* @Route("/admin/site/page/filter", name="admin_site_page_filter", methods={"GET"})
*/
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
#[Route(path: '/admin/site/page/edit/{entity}', name: 'admin_site_page_edit', methods: ['GET', 'POST'])]
/**
* @Route("/admin/site/page/edit/{entity}", name="admin_site_page_edit", methods={"GET", "POST"})
*/
public function edit(
int $entity,
EntityManager $entityManager,
@ -61,13 +69,17 @@ class PageAdminController extends CrudController
return $this->doEdit($entity, $entityManager, $request);
}
#[Route(path: '/admin/site/page/delete/{entity}', name: 'admin_site_page_delete', methods: ['DELETE', 'POST'])]
/**
* @Route("/admin/site/page/delete/{entity}", name="admin_site_page_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
}
#[Route(path: '/admin/site/page/batch/{page}', name: 'admin_site_page_batch', methods: ['POST'], requirements: ['page' => '\d+'])]
/**
* @Route("/admin/site/page/batch/{page}", name="admin_site_page_batch", methods={"POST"}, requirements={"page":"\d+"})
*/
public function batch(RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session, int $page = 1): Response
{
return $this->doBatch($page, $query, $entityManager, $request, $session);
@ -95,8 +107,6 @@ class PageAdminController extends CrudController
->setAction('index', 'show', false)
->setAction('edit', 'show', false)
->setDoubleClick('index', true)
->setField('index', 'Name', Field\TextField::class, [
'property' => 'name',
'sort' => ['name', '.name'],
@ -114,7 +124,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

@ -11,21 +11,22 @@ use Symfony\Component\Routing\Annotation\Route;
class SitemapController extends AbstractController
{
#[Route(path: '/sitemap.xml', name: 'sitemap')]
public function sitemap(Request $request, NavigationRepositoryQuery $query, SitemapBuilder $builder): Response
/**
* @Route("/sitemap.xml", name="sitemap")
*/
public function sitemap(Request $request, NavigationRepositoryQuery $navigationRepositoryQuery, SitemapBuilder $builder): Response
{
$navigations = $query->create()->find();
$navigations = $navigationRepositoryQuery
->whereDomain($request->getHost())
->find()
;
$items = [];
foreach ($navigations as $navigation) {
if (!$navigation->matchDomain($request->getHost())) {
continue;
}
$items = array_merge(
$items,
$builder->build($navigation, $request->getHost())
$builder->build($navigation)
);
}

View file

@ -11,10 +11,14 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
#[Route(path: '/admin/site/tree')]
/**
* @Route("/admin/site/tree")
*/
class TreeAdminController extends AdminController
{
#[Route(path: '/', name: 'admin_site_tree_index')]
/**
* @Route("/", name="admin_site_tree_index")
*/
public function index(NavigationRepositoryQuery $navigationQuery, Session $session): Response
{
$navigation = null;
@ -44,7 +48,9 @@ class TreeAdminController extends AdminController
]);
}
#[Route(path: '/navigation/{navigation}', name: 'admin_site_tree_navigation')]
/**
* @Route("/navigation/{navigation}", name="admin_site_tree_navigation")
*/
public function navigation(
Navigation $navigation,
NavigationRepositoryQuery $navigationQuery,

View file

@ -13,10 +13,14 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route(path: '/admin/task')]
/**
* @Route("/admin/task")
*/
class TaskAdminController extends AdminController
{
#[Route(path: '/', name: 'admin_task_index')]
/**
* @Route("/", name="admin_task_index")
*/
public function index(EventDispatcherInterface $eventDispatcher): Response
{
$event = new TaskInitEvent();
@ -27,7 +31,9 @@ class TaskAdminController extends AdminController
]);
}
#[Route(path: '/run/{task}', name: 'admin_task_run', methods: ['GET'])]
/**
* @Route("/run/{task}", name="admin_task_run", methods={"GET"})
*/
public function run(
string $task,
Request $request,

View file

@ -8,55 +8,69 @@ use App\Core\Crud\Field;
use App\Core\Event\Account\PasswordRequestEvent;
use App\Core\Factory\UserFactory as Factory;
use App\Core\Manager\EntityManager;
use App\Core\Security\TokenGenerator;
use App\Entity\User as Entity;
use App\Form\UserType as Type;
use App\Repository\UserRepositoryQuery as RepositoryQuery;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
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;
use App\Core\Security\TokenGenerator;
class UserAdminController extends CrudController
{
protected ?CrudConfiguration $configuration = null;
/**
* @Route("/admin/user/{page}", name="admin_user_index", methods={"GET"}, requirements={"page":"\d+"})
*/
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return $this->doIndex($page, $query, $request, $session);
}
/**
* @Route("/admin/user/new", name="admin_user_new", methods={"GET", "POST"})
*/
public function new(Factory $factory, EntityManager $entityManager, Request $request, TokenGenerator $tokenGenerator): Response
{
return $this->doNew($factory->create(null, $tokenGenerator->generateToken()), $entityManager, $request);
}
/**
* @Route("/admin/user/show/{entity}", name="admin_user_show", methods={"GET"})
*/
public function show(Entity $entity): Response
{
return $this->doShow($entity);
}
/**
* @Route("/admin/user/filter", name="admin_user_filter", methods={"GET"})
*/
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
/**
* @Route("/admin/user/edit/{entity}", name="admin_user_edit", methods={"GET", "POST"})
*/
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doEdit($entity, $entityManager, $request);
}
public function inlineEdit(string $context, string $label, Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doInlineEdit($context, $label, $entity, $entityManager, $request);
}
/**
* @Route("/admin/user/delete/{entity}", name="admin_user_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
}
/**
* @Route("/admin/user/resetting_request/{entity}", name="admin_user_resetting_request", methods={"POST"})
*/
public function requestResetting(Entity $entity, EventDispatcherInterface $eventDispatcher, Request $request): Response
{
if ($this->isCsrfTokenValid('resetting_request'.$entity->getId(), $request->request->get('_token'))) {
@ -81,7 +95,6 @@ class UserAdminController extends CrudController
->setPageRoute('index', 'admin_user_index')
->setPageRoute('new', 'admin_user_new')
->setPageRoute('edit', 'admin_user_edit')
->setPageRoute('inline_edit', 'admin_user_inline_edit')
->setPageRoute('show', 'admin_user_show')
->setPageRoute('delete', 'admin_user_delete')
->setPageRoute('filter', 'admin_user_filter')
@ -97,7 +110,6 @@ class UserAdminController extends CrudController
->setView('edit', '@Core/user/user_admin/edit.html.twig')
->setDefaultSort('index', 'username')
->setDoubleClick('index', true)
->setField('index', 'E-mail', Field\TextField::class, [
'property' => 'email',
@ -108,9 +120,6 @@ class UserAdminController extends CrudController
'property' => 'displayName',
'sort' => ['displayName', '.displayName'],
'attr' => ['class' => 'miw-200'],
'inline_form' => function (FormBuilderInterface $builder) {
$builder->add('displayName', null);
},
])
;
}

View file

@ -2,6 +2,8 @@
namespace App\Core\Crud;
use App\Core\Crud\Exception\CrudConfigurationException;
/**
* class CrudConfiguration.
*
@ -17,7 +19,6 @@ class CrudConfiguration
protected array $actionTitles = [];
protected array $forms = [];
protected array $formOptions = [];
protected array $inlineForms = [];
protected array $views = [];
protected array $viewDatas = [];
protected array $fields = [];
@ -40,7 +41,7 @@ class CrudConfiguration
return self::$self;
}
// --
/* -- */
public function setPageTitle(string $page, string $title): self
{
@ -54,7 +55,7 @@ class CrudConfiguration
return $this->pageTitles[$page] ?? $default;
}
// --
/* -- */
public function setPageRoute(string $page, string $route): self
{
@ -80,9 +81,9 @@ class CrudConfiguration
return $this->pageRouteParams[$page] ?? [];
}
// --
/* -- */
public function setForm(string $context, string $form): self
public function setForm(string $context, string $form, array $options = []): self
{
$this->forms[$context] = $form;
@ -106,9 +107,9 @@ class CrudConfiguration
return $this->formOptions[$context] ?? [];
}
// --
/* -- */
public function setAction(string $page, string $action, bool|callable $enabled): self
public function setAction(string $page, string $action, bool $enabled): self
{
if (!isset($this->actions[$page])) {
$this->actions[$page] = [];
@ -119,40 +120,13 @@ class CrudConfiguration
return $this;
}
public function getAction(string $page, string $action, bool $default = true, array $callableParamaters = [])
public function getAction(string $page, string $action, bool $default = true)
{
if (!isset($this->actions[$page][$action])) {
return $default;
}
if (is_bool($this->actions[$page][$action])) {
return $this->actions[$page][$action];
}
return call_user_func_array(
$this->actions[$page][$action],
$callableParamaters
);
return $this->actions[$page][$action] ?? $default;
}
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 {
public function setBatchAction(string $page, string $action, string $label, callable $callback): self
{
if (!isset($this->batchActions[$page])) {
$this->batchActions[$page] = [];
}
@ -160,7 +134,6 @@ class CrudConfiguration
$this->batchActions[$page][$action] = [
'label' => $label,
'callback' => $callback,
'isGlobal' => false,
];
return $this;
@ -181,7 +154,7 @@ class CrudConfiguration
return !empty($this->batchActions[$page]);
}
// --
/* -- */
public function setActionTitle(string $page, string $action, string $title): self
{
@ -199,7 +172,7 @@ class CrudConfiguration
return $this->actionTitles[$page][$action] ?? $default;
}
// --
/* -- */
public function setView(string $context, string $view): self
{
@ -247,7 +220,7 @@ class CrudConfiguration
return $this->viewDatas[$context][$name] ?? $defaultValue;
}
// --
/* -- */
public function setField(string $context, string $label, string $field, array $options): self
{
@ -268,16 +241,9 @@ class CrudConfiguration
return $this->fields[$context] ?? [];
}
public function setFields(string $context, array $fields): self
{
$this->fields[$context] = $fields;
/* -- */
return $this;
}
// --
public function setMaxPerPage(string $page, int $max): self
public function setMaxPerPage(string $page, int $max)
{
$this->maxPerPage[$page] = $max;
@ -289,21 +255,7 @@ class CrudConfiguration
return $this->maxPerPage[$page] ?? $default;
}
// --
public function setDoubleClick(string $page, bool $enabled): self
{
$this->doubleClick[$page] = $enabled;
return $this;
}
public function getDoubleClick(string $page): bool
{
return $this->doubleClick[$page] ?? false;
}
// --
/* -- */
public function setI18n(array $locales, string $defaultLocale): self
{
@ -328,7 +280,7 @@ class CrudConfiguration
return !empty($this->locales);
}
// --
/* -- */
public function setDefaultSort(string $context, string $label, string $direction = 'asc'): self
{

View file

@ -1,32 +0,0 @@
<?php
namespace App\Core\Crud\Field;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* class BooleanField.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class BooleanField extends Field
{
public function configureOptions(OptionsResolver $resolver): OptionsResolver
{
parent::configureOptions($resolver);
$resolver->setDefaults([
'view' => '@Core/admin/crud/field/boolean.html.twig',
'display' => 'toggle',
'checkbox_class_when_true' => 'fa-check-square',
'checkbox_class_when_false' => 'fa-square',
'toggle_class_when_true' => 'bg-success',
'toggle_class_when_false' => 'bg-secondary',
'default_value' => false,
]);
$resolver->setAllowedTypes('display', 'string');
return $resolver;
}
}

View file

@ -28,31 +28,24 @@ abstract class Field
$resolver->setDefaults([
'property' => null,
'property_builder' => null,
'default_value' => null,
'view' => null,
'action' => null,
'raw' => false,
'sort' => null,
'href' => null,
'href_attr' => [],
'attr' => [],
'inline_form' => null,
'inline_form_validation' => null,
]);
$resolver->setRequired('view');
$resolver->setAllowedTypes('property', ['null', 'string']);
$resolver->setAllowedTypes('view', 'string');
$resolver->setAllowedTypes('action', ['null', 'string']);
$resolver->setAllowedTypes('attr', 'array');
$resolver->setAllowedTypes('href', ['null', 'string', 'callable']);
$resolver->setAllowedTypes('inline_form', ['null', 'callable']);
$resolver->setAllowedTypes('inline_form_validation', ['null', 'callable']);
$resolver->setAllowedTypes('href_attr', ['array', 'callable']);
$resolver->setAllowedTypes('href_attr', 'array', 'callable');
$resolver->setAllowedTypes('raw', 'boolean');
$resolver->setAllowedTypes('property_builder', ['null', 'callable']);
$resolver->setAllowedValues('sort', function ($value) {
if (null === $value) {
$resolver->setAllowedValues('sort', function($value) {
if ($value === null) {
return true;
}

View file

@ -15,7 +15,6 @@ class Configuration implements ConfigurationInterface
'image/jpeg',
'image/gif',
'image/svg+xml',
'image/webp',
'video/mp4',
'audio/mpeg3',
'audio/x-mpeg-3',
@ -46,100 +45,101 @@ class Configuration implements ConfigurationInterface
$treeBuilder->getRootNode()
->children()
->arrayNode('site')
->children()
->scalarNode('name')
->defaultValue('Murph')
->isRequired()
->end()
->scalarNode('logo')
->defaultValue('build/images/core/logo.svg')
->isRequired()
->end()
->arrayNode('controllers')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('action')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->arrayNode('security')
->children()
->arrayNode('roles')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('role')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('pages')
->prototype('array')
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('templates')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('file')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('editor_js')
->children()
->arrayNode('blocks')
->scalarPrototype()
->end()
->end()
->end()
->end()
->arrayNode('file_manager')
->children()
->arrayNode('mimes')
->scalarPrototype()
->end()
->defaultValue($defaultMimetypes)
->end()
->scalarNode('path')
->defaultValue('%kernel.project_dir%/public/uploads')
->cannotBeEmpty()
->end()
->scalarNode('path_uri')
->defaultValue('/uploads')
->cannotBeEmpty()
->end()
->arrayNode('path_locked')
->scalarPrototype()
->end()
->defaultValue($defaultLocked)
->end()
->end()
->end()
->end()
;
->arrayNode('site')
->children()
->scalarNode('name')
->defaultValue('Murph')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('logo')
->defaultValue('build/images/core/logo.svg')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('controllers')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('action')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->arrayNode('security')
->children()
->arrayNode('roles')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('role')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('pages')
->prototype('array')
->children()
->scalarNode('name')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('templates')
->prototype('array')
->children()
->scalarNode('name')
->cannotBeEmpty()
->end()
->scalarNode('file')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->arrayNode('editor_js')
->children()
->arrayNode('blocks')
->scalarPrototype()
->end()
->end()
->end()
->end()
->arrayNode('file_manager')
->children()
->arrayNode('mimes')
->scalarPrototype()
->end()
->defaultValue($defaultMimetypes)
->end()
->scalarNode('path')
->defaultValue('%kernel.project_dir%/public/uploads')
->cannotBeEmpty()
->end()
->scalarNode('path_uri')
->defaultValue('/uploads')
->cannotBeEmpty()
->end()
->arrayNode('path_locked')
->scalarPrototype()
->end()
->defaultValue($defaultLocked)
->end()
->end()
->end()
->end();
return $treeBuilder;
}

View file

@ -6,20 +6,28 @@ use Doctrine\ORM\Mapping as ORM;
trait Timestampable
{
#[ORM\Column(name: 'created_at', type: 'datetime')]
/**
* @ORM\Column(name="created_at", type="datetime")
*/
protected $createdAt;
#[ORM\Column(name: 'updated_at', type: 'datetime')]
/**
* @ORM\Column(name="updated_at", type="datetime")
*/
protected $updatedAt;
#[ORM\PrePersist]
/**
* @ORM\PrePersist
*/
public function onPrePersist(): void
{
$this->createdAt = new \DateTime();
$this->updatedAt = new \DateTime();
}
#[ORM\PreUpdate]
/**
* @ORM\PreUpdate
*/
public function onPreUpdate(): void
{
$this->updatedAt = new \DateTime();

View file

@ -2,30 +2,43 @@
namespace App\Core\Entity\Analytic;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Node;
use App\Repository\Entity\Analytic\NodeViewRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
#[ORM\Table(name: 'analytic_referer')]
#[ORM\Entity(repositoryClass: ViewRepository::class)]
/**
* @ORM\Entity(repositoryClass=ViewRepository::class)
* @ORM\Table(name="analytic_referer")
*/
class Referer implements EntityInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\ManyToOne(targetEntity: Node::class, inversedBy: 'analyticReferers')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="analyticReferers")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $node;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $uri;
#[ORM\Column(type: 'integer', options: ['default' => 0])]
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
protected $views = 0;
#[ORM\Column(type: 'date')]
/**
* @ORM\Column(type="date")
*/
protected $date;
public function getId(): ?int

View file

@ -2,36 +2,53 @@
namespace App\Core\Entity\Analytic;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\Site\Node;
use App\Repository\Entity\Analytic\NodeViewRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
#[ORM\Table(name: 'analytic_view')]
#[ORM\Entity(repositoryClass: ViewRepository::class)]
/**
* @ORM\Entity(repositoryClass=ViewRepository::class)
* @ORM\Table(name="analytic_view")
*/
class View implements EntityInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\ManyToOne(targetEntity: Node::class, inversedBy: 'analyticViews')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="analyticViews")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $node;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $path;
#[ORM\Column(type: 'integer', options: ['default' => 0])]
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
protected $views = 0;
#[ORM\Column(type: 'integer', options: ['default' => 0])]
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
protected $desktopViews = 0;
#[ORM\Column(type: 'integer', options: ['default' => 0])]
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
protected $mobileViews = 0;
#[ORM\Column(type: 'date')]
/**
* @ORM\Column(type="date")
*/
protected $date;
public function getId(): ?int

View file

@ -5,15 +5,21 @@ namespace App\Core\Entity;
use App\Repository\Entity\FileInformationRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: FileInformationRepository::class)]
/**
* @ORM\Entity(repositoryClass=FileInformationRepository::class)
*/
class FileInformation implements EntityInterface
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'NONE')]
#[ORM\Column(type: 'string', length: 96, unique: true)]
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
* @ORM\Column(type="string", length=96, unique=true)
*/
protected $id;
#[ORM\Column(type: 'text', nullable: true)]
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $attributes;
public function getId(): ?string

View file

@ -6,28 +6,42 @@ use App\Core\Entity\Site\Navigation;
use App\Core\Repository\NavigationSettingRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: NavigationSettingRepository::class)]
/**
* @ORM\Entity(repositoryClass=NavigationSettingRepository::class)
*/
class NavigationSetting implements EntityInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $section;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $code;
#[ORM\Column(type: 'text', nullable: true)]
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $value;
#[ORM\ManyToOne(targetEntity: Navigation::class, inversedBy: 'navigationSettings')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
/**
* @ORM\ManyToOne(targetEntity=Navigation::class, inversedBy="navigationSettings")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $navigation;
public function getId(): ?int

View file

@ -5,45 +5,71 @@ namespace App\Core\Entity;
use App\Core\Repository\RedirectRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: RedirectRepository::class)]
/**
* @ORM\Entity(repositoryClass=RedirectRepository::class)
*/
class Redirect implements EntityInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\Column(type: 'string', length: 5)]
/**
* @ORM\Column(type="string", length=5)
*/
protected $scheme;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $domain;
#[ORM\Column(type: 'string', length: 6)]
/**
* @ORM\Column(type="string", length=6)
*/
protected $domainType;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $rule;
#[ORM\Column(type: 'string', length: 6)]
/**
* @ORM\Column(type="string", length=6)
*/
protected $ruleType;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $location;
#[ORM\Column(type: 'integer')]
/**
* @ORM\Column(type="integer")
*/
protected $redirectCode;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
#[ORM\Column(type: 'integer', nullable: true)]
/**
* @ORM\Column(type="integer", nullable=true)
*/
protected $sortOrder;
#[ORM\Column(type: 'boolean')]
/**
* @ORM\Column(type="boolean")
*/
protected $isEnabled;
#[ORM\Column(type: 'boolean')]
/**
* @ORM\Column(type="boolean")
*/
protected $reuseQueryString;
public function getId(): ?int

View file

@ -5,24 +5,36 @@ namespace App\Core\Entity;
use App\Core\Repository\SettingRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: SettingRepository::class)]
/**
* @ORM\Entity(repositoryClass=SettingRepository::class)
*/
class Setting implements EntityInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $section;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $code;
#[ORM\Column(type: 'text', nullable: true)]
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $value;
public function getId(): ?int

View file

@ -9,32 +9,46 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MenuRepository::class)]
#[ORM\HasLifecycleCallbacks]
/**
* @ORM\Entity(repositoryClass=MenuRepository::class)
* @ORM\HasLifecycleCallbacks
*/
class Menu implements EntityInterface
{
use Timestampable;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $code;
#[ORM\ManyToOne(targetEntity: Navigation::class, inversedBy: 'menus')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
/**
* @ORM\ManyToOne(targetEntity=Navigation::class, inversedBy="menus")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $navigation;
#[ORM\OneToMany(targetEntity: Node::class, mappedBy: 'menu', orphanRemoval: true, cascade: ['remove', 'persist'])]
/**
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="menu", orphanRemoval=true, cascade={"remove", "persist"})
*/
protected $nodes;
#[ORM\OneToOne(targetEntity: Node::class, cascade: ['persist'])]
#[ORM\JoinColumn(onDelete: 'CASCADE')]
/**
* @ORM\OneToOne(targetEntity=Node::class, cascade={"persist"})
* @ORM\JoinColumn(onDelete="CASCADE")
*/
protected $rootNode;
public function __construct()

View file

@ -10,45 +10,64 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: NavigationRepository::class)]
#[ORM\HasLifecycleCallbacks]
/**
* @ORM\Entity(repositoryClass=NavigationRepository::class)
* @ORM\HasLifecycleCallbacks
*/
class Navigation implements EntityInterface
{
use Timestampable;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $code;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $domain;
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
protected $forceDomain = false;
#[ORM\Column(type: 'text', nullable: true)]
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $additionalDomains = '[]';
#[ORM\OneToMany(targetEntity: Menu::class, mappedBy: 'navigation')]
/**
* @ORM\OneToMany(targetEntity=Menu::class, mappedBy="navigation")
*/
protected $menus;
#[ORM\Column(type: 'string', length: 10)]
/**
* @ORM\Column(type="string", length=10)
*/
protected $locale = 'en';
#[ORM\Column(type: 'string', length: 7, nullable: true)]
protected $color;
#[ORM\Column(type: 'integer', nullable: true)]
/**
* @ORM\Column(type="integer", nullable=true)
*/
protected $sortOrder;
#[ORM\OneToMany(targetEntity: NavigationSetting::class, mappedBy: 'navigation', orphanRemoval: true)]
/**
* @ORM\OneToMany(targetEntity=NavigationSetting::class, mappedBy="navigation", orphanRemoval=true)
*/
protected $navigationSettings;
public function __construct()
@ -67,7 +86,7 @@ class Navigation implements EntityInterface
return $this->label;
}
public function setLabel(?string $label): self
public function setLabel(string $label): self
{
$this->label = $label;
@ -221,35 +240,4 @@ class Navigation implements EntityInterface
return $this;
}
public function matchDomain(string $domain): bool
{
if ($domain === $this->getDomain()) {
return true;
}
foreach ($this->getAdditionalDomains() as $additionalDomain) {
if ('domain' === $additionalDomain['type'] && $additionalDomain['domain'] === $domain) {
return true;
}
if ('regexp' === $additionalDomain['type'] && preg_match('#'.$additionalDomain['domain'].'#', $domain) > 0) {
return true;
}
}
return false;
}
public function setColor(string $color): self
{
$this->color = $color;
return $this;
}
public function getColor(): ?string
{
return $this->color;
}
}

View file

@ -16,120 +16,156 @@ use function Symfony\Component\String\u;
/**
* @Gedmo\Tree(type="nested")
* @ORM\HasLifecycleCallbacks
* @ORM\Entity(repositoryClass=NodeRepository::class)
*/
#[ORM\HasLifecycleCallbacks]
#[ORM\Entity(repositoryClass: NodeRepository::class)]
class Node implements EntityInterface
{
use Timestampable;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\ManyToOne(targetEntity: Menu::class, inversedBy: 'nodes', cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
/**
* @ORM\ManyToOne(targetEntity=Menu::class, inversedBy="nodes", cascade={"persist", "remove"})
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $menu;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $label;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $url;
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
protected $disableUrl = false;
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
protected $isVisible = false;
/**
* @Gedmo\TreeLeft
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: 'integer')]
protected $treeLeft;
/**
* @Gedmo\TreeLevel
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: 'integer')]
protected $treeLevel;
/**
* @Gedmo\TreeRight
* @ORM\Column(type="integer")
*/
#[ORM\Column(type: 'integer')]
protected $treeRight;
/**
* @Gedmo\TreeRoot
* @ORM\ManyToOne(targetEntity="Node")
* @ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: 'Node')]
#[ORM\JoinColumn(referencedColumnName: 'id', onDelete: 'CASCADE')]
protected $treeRoot;
/**
* @Gedmo\TreeParent
* @ORM\ManyToOne(targetEntity="Node", inversedBy="children")
* @ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
*/
#[ORM\ManyToOne(targetEntity: 'Node', inversedBy: 'children')]
#[ORM\JoinColumn(referencedColumnName: 'id', onDelete: 'CASCADE')]
protected $parent;
#[ORM\OneToMany(targetEntity: 'Node', mappedBy: 'parent')]
#[ORM\OrderBy(['treeLeft' => 'ASC'])]
/**
* @ORM\OneToMany(targetEntity="Node", mappedBy="parent")
* @ORM\OrderBy({"treeLeft"="ASC"})
*/
protected $children;
#[ORM\ManyToOne(targetEntity: Page::class, inversedBy: 'nodes', cascade: ['persist'])]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
/**
* @ORM\ManyToOne(targetEntity=Page::class, inversedBy="nodes", cascade={"persist"})
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
protected $page;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $code;
#[ORM\Column(type: 'array', nullable: true)]
/**
* @ORM\Column(type="array", nullable=true)
*/
protected $parameters = [];
#[ORM\Column(type: 'array', nullable: true)]
/**
* @ORM\Column(type="array", nullable=true)
*/
protected $attributes = [];
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $controller;
#[ORM\Column(type: 'array', nullable: true)]
/**
* @ORM\Column(type="array", nullable=true)
*/
protected $sitemapParameters = [];
#[ORM\ManyToOne(targetEntity: Node::class, inversedBy: 'aliasNodes')]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="aliasNodes")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
protected $aliasNode;
#[ORM\OneToMany(targetEntity: Node::class, mappedBy: 'aliasNode')]
/**
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="aliasNode")
*/
protected $aliasNodes;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $contentType;
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
protected $enableAnalytics = false;
#[ORM\OneToMany(targetEntity: View::class, mappedBy: 'node')]
/**
* @ORM\OneToMany(targetEntity=View::class, mappedBy="node")
*/
protected $analyticViews;
#[ORM\OneToMany(targetEntity: Referer::class, mappedBy: 'node')]
/**
* @ORM\OneToMany(targetEntity=Referer::class, mappedBy="node")
*/
protected $analyticReferers;
#[ORM\Column(type: 'array', nullable: true)]
/**
* @ORM\Column(type="array", nullable=true)
*/
private $securityRoles = [];
#[ORM\Column(type: 'string', length: 3, nullable: true)]
/**
* @ORM\Column(type="string", length=3, nullable=true)
*/
private $securityOperator = 'or';
#[ORM\Column(type: 'boolean', options: ['default' => 0])]
private $hasAbTest = false;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $abTestCode;
public function __construct()
{
$this->children = new ArrayCollection();
@ -637,28 +673,4 @@ class Node implements EntityInterface
return $this;
}
public function getHasAbTest(): ?bool
{
return $this->hasAbTest;
}
public function setHasAbTest(bool $hasAbTest): self
{
$this->hasAbTest = $hasAbTest;
return $this;
}
public function getAbTestCode(): ?string
{
return $this->abTestCode;
}
public function setAbTestCode(?string $abTestCode): self
{
$this->abTestCode = $abTestCode;
return $this;
}
}

View file

@ -6,27 +6,37 @@ use App\Core\Doctrine\Timestampable;
use App\Core\Repository\Site\Page\BlockRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: BlockRepository::class)]
#[ORM\DiscriminatorColumn(name: 'class_key', type: 'string')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\HasLifecycleCallbacks]
/**
* @ORM\Entity(repositoryClass=BlockRepository::class)
* @ORM\DiscriminatorColumn(name="class_key", type="string")
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\HasLifecycleCallbacks
*/
class Block
{
use Timestampable;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $name;
#[ORM\Column(type: 'text', nullable: true)]
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $value;
#[ORM\ManyToOne(targetEntity: Page::class, inversedBy: 'blocks')]
#[ORM\JoinColumn(onDelete: 'CASCADE')]
/**
* @ORM\ManyToOne(targetEntity=Page::class, inversedBy="blocks")
* @ORM\JoinColumn(onDelete="CASCADE")
*/
protected $page;
public function getId(): ?int

View file

@ -4,7 +4,9 @@ namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
/**
* @ORM\Entity
*/
class ChoiceBlock extends Block
{
public function getValue()

View file

@ -4,7 +4,9 @@ namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
/**
* @ORM\Entity
*/
class CollectionBlock extends Block
{
public function getValue()

View file

@ -6,7 +6,9 @@ use App\Core\File\FileAttribute;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
#[ORM\Entity]
/**
* @ORM\Entity
*/
class FileBlock extends Block
{
public function getValue()

View file

@ -5,51 +5,74 @@ 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')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\HasLifecycleCallbacks]
/**
* @ORM\Entity(repositoryClass=PageRepository::class)
* @ORM\DiscriminatorColumn(name="class_key", type="string")
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\HasLifecycleCallbacks
*/
class Page implements EntityInterface
{
use Timestampable;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
#[ORM\Column(type: 'string', length: 255)]
/**
* @ORM\Column(type="string", length=255)
*/
protected $name;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $template;
#[ORM\OneToMany(targetEntity: Block::class, mappedBy: 'page', cascade: ['persist'])]
/**
* @ORM\OneToMany(targetEntity=Block::class, mappedBy="page", cascade={"persist"})
*/
protected $blocks;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $metaTitle;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $metaDescription;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $ogTitle;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $ogDescription;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $ogImage;
#[ORM\OneToMany(targetEntity: Node::class, mappedBy: 'page')]
/**
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="page")
*/
protected $nodes;
public function __construct()
@ -88,7 +111,7 @@ class Page implements EntityInterface
}
/**
* @return Block[]|Collection
* @return Collection|Block[]
*/
public function getBlocks(): Collection
{

View file

@ -4,7 +4,9 @@ namespace App\Core\Entity\Site\Page;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
/**
* @ORM\Entity
*/
class TextBlock extends Block
{
}

View file

@ -1,26 +0,0 @@
<?php
namespace App\Core\Event\Ab;
use App\Core\Ab\AbTest;
use Symfony\Contracts\EventDispatcher\Event;
/**
* class AbTestEvent.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class AbTestEvent extends Event
{
public const INIT_EVENT = 'ab_test.init';
public const RUN_EVENT = 'ab_test.run';
public function __construct(protected AbTest $test)
{
}
public function getTest(): AbTest
{
return $this->test;
}
}

View file

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

View file

@ -12,15 +12,18 @@ use Symfony\Contracts\EventDispatcher\Event;
*/
class EntityManagerEvent extends Event
{
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';
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 function __construct(protected EntityInterface $entity)
protected EntityInterface $entity;
public function __construct(EntityInterface $entity)
{
$this->entity = $entity;
}
public function getEntity(): EntityInterface

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,102 +0,0 @@
<?php
namespace App\Core\EventListener;
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\Site\SiteRequest;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
/**
* class AbListener.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class AbListener
{
protected ?Node $node;
public function __construct(
protected AbContainer $container,
protected EventDispatcherInterface $eventDispatcher,
protected SiteRequest $siteRequest
) {
}
public function onKernelRequest(RequestEvent $event)
{
$this->node = $this->siteRequest->getNode();
if (!$this->supports($event->getRequest())) {
return;
}
$request = $event->getRequest();
$cookieName = md5($this->getCookieName());
$cookieValue = $event->getRequest()->cookies->get($cookieName);
$abTest = new AbTest($this->getAbTestCode());
$event = new AbTestEvent($abTest);
$this->container->add($abTest);
$this->eventDispatcher->dispatch($event, AbTestEvent::INIT_EVENT);
if (!$abTest->isValidVariation($cookieValue)) {
$abTest->run();
$result = $abTest->getResult();
$attributes = array_merge($request->attributes->get('ab_test_cookies', []), [
$cookieName => ['value' => $result, 'duration' => $abTest->getDuration()],
]);
$request->attributes->set('ab_test_cookies', $attributes);
$this->eventDispatcher->dispatch($event, AbTestEvent::RUN_EVENT);
} else {
$abTest->setResult($cookieValue);
}
}
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();
}
protected function getAbTestCode(): string
{
return $this->node->getAbTestCode();
}
protected function supports(Request $request): bool
{
if (!$this->node) {
return false;
}
if (!$this->node->getHasAbTest()) {
return false;
}
if (!$this->node->getAbTestCode()) {
return false;
}
return true;
}
}

View file

@ -23,18 +23,30 @@ 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(
protected NodeRepository $nodeRepository,
protected ViewRepositoryQuery $viewRepositoryQuery,
protected ViewFactory $viewFactory,
protected RefererRepositoryQuery $refererRepositoryQuery,
protected RefererFactory $refererFactory,
protected EntityManager $manager
NodeRepository $nodeRepository,
ViewRepositoryQuery $viewRepositoryQuery,
ViewFactory $viewFactory,
RefererRepositoryQuery $refererRepositoryQuery,
RefererFactory $refererFactory,
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,10 +3,11 @@
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.
@ -15,11 +16,15 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
*/
class RedirectListener
{
public function __construct(
protected RedirectMatcher $matcher,
protected RedirectBuilder $builder,
protected RedirectRepositoryQuery $repository
) {
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 onKernelException(ExceptionEvent $event)
@ -32,7 +37,7 @@ class RedirectListener
$redirects = $this->repository
->orderBy('.sortOrder')
->where('.isEnabled=true')
->where('.isEnabled=1')
->find()
;

View file

@ -1,32 +0,0 @@
<?php
namespace App\Core\EventSubscriber;
use App\Core\Event\Ab\AbTestEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* class AbEventSubscriber.
*
* @author Simon Vieille <simon@deblan.fr>
*/
abstract class AbEventSubscriber implements EventSubscriberInterface
{
protected static int $priority = 0;
public static function getSubscribedEvents()
{
return [
AbTestEvent::INIT_EVENT => ['onInit', self::$priority],
AbTestEvent::RUN_EVENT => ['onRun', self::$priority],
];
}
public function onInit(AbTestEvent $event)
{
}
public function onRun(AbTestEvent $event)
{
}
}

View file

@ -17,13 +17,24 @@ 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(
protected MailNotifier $notifier,
protected UrlGeneratorInterface $urlGenerator,
protected EntityManager $entityManager,
protected TokenGeneratorInterface $tokenGenerator,
protected TranslatorInterface $translator
MailNotifier $notifier,
UrlGeneratorInterface $urlGenerator,
EntityManager $entityManager,
TokenGeneratorInterface $tokenGenerator,
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(
protected NodeRepository $nodeRepository,
ContainerInterface $container
) {
public function __construct(NodeRepository $nodeRepository, ContainerInterface $container)
{
$this->nodeRepository = $nodeRepository;
$this->authorizationChecker = $container->get('security.authorization_checker');
}

View file

@ -4,15 +4,18 @@ namespace App\Core\EventSubscriber\Site;
use App\Core\Site\SiteRequest;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpFoundation\Response;
use function Symfony\Component\String\u;
class ForcedDomainEventSubscriber implements EventSubscriberInterface
{
public function __construct(protected SiteRequest $siteRequest)
protected SiteRequest $siteRequest;
public function __construct(SiteRequest $siteRequest)
{
$this->siteRequest = $siteRequest;
}
public function onKernelResponse(ResponseEvent $event)
@ -35,8 +38,7 @@ 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,14 +20,27 @@ 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(
protected NodeFactory $nodeFactory,
protected NodeRepository $nodeRepository,
protected EntityManager $entityManager,
protected CodeSlugify $slugify,
protected SymfonyCacheManager $cacheManager,
protected TranslatorInterface $translator
NodeFactory $nodeFactory,
NodeRepository $nodeRepository,
EntityManager $entityManager,
CodeSlugify $slugify,
SymfonyCacheManager $cacheManager,
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,9 +17,11 @@ use App\Core\Slugify\CodeSlugify;
class NavigationEventSubscriber extends EntityManagerEventSubscriber
{
public function __construct(
protected EntityManager $entityManager,
protected CodeSlugify $slugify
EntityManager $entityManager,
CodeSlugify $slugify
) {
$this->entityManager = $entityManager;
$this->slugify = $slugify;
}
public function supports(EntityInterface $entity): bool

View file

@ -12,6 +12,7 @@ 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;
/**
@ -21,14 +22,27 @@ 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(
protected NodeFactory $nodeFactory,
protected NodeRepository $nodeRepository,
protected EntityManager $entityManager,
protected Slugify $slugify,
protected CodeSlugify $codeSlugify,
protected RouteParameterSlugify $routeParameterSlugify
NodeFactory $nodeFactory,
NodeRepository $nodeRepository,
EntityManager $entityManager,
Slugify $slugify,
CodeSlugify $codeSlugify,
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
@ -49,7 +63,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,8 +17,11 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
class BlockEventSubscriber extends EntityManagerEventSubscriber
{
public function __construct(protected FileUploadHandler $fileUpload)
protected FileUploadHandler $fileUpload;
public function __construct(FileUploadHandler $fileUpload)
{
$this->fileUpload = $fileUpload;
}
public function supports(EntityInterface $entity): bool

View file

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

View file

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

View file

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

View file

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

View file

@ -23,15 +23,22 @@ 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,
protected FileUploadHandler $uploadHandler,
protected FileInformationFactory $fileInformationFactory,
protected FileInformationRepositoryQuery $fileInformationRepositoryQuery
FileUploadHandler $uploadHandler,
FileInformationFactory $fileInformationFactory,
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']);
@ -84,16 +91,12 @@ class FsFileManager
$this->applySort($finder, $options['sort'] ?? 'name', $options['sort_direction'] ?? 'asc');
foreach ($finder as $file) {
$splInfo = $this->getSplInfo($directory.'/'.$file->getBasename());
$data['files'][] = [
'basename' => $file->getBasename(),
'path' => $directory,
'webPath' => $this->pathUri.'/'.$directory.'/'.$file->getBasename(),
'locked' => $this->isLocked($directory.'/'.$file->getBasename()),
'mime' => mime_content_type($file->getRealPath()),
'size' => $splInfo ? $splInfo->getSize() : null,
'updated_at' => $splInfo ? date('Y-m-d H:i', $splInfo->getMTime()) : null,
];
}
@ -294,16 +297,14 @@ class FsFileManager
protected function applySort(Finder $finder, string $sort, string $direction)
{
$sorts = [
'name' => 'sortByName',
'type' => 'sortByType',
'updated_at' => 'sortByModifiedTime',
];
if ('name' === $sort) {
$finder->sortByName();
} elseif ('modification_date' === $sort) {
$finder->sortByModifiedTime();
}
if ('desc' === $direction) {
$finder->{$sorts[$sort]}()->reverseSorting();
} else {
$finder->{$sorts[$sort]}();
$finder->reverseSorting();
}
}

View file

@ -6,6 +6,7 @@ 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

@ -11,15 +11,6 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
class FileUploadHandler
{
protected $filenameGenerator;
public function setFilenameGenerator(callable $filenameGenerator): self
{
$this->filenameGenerator = $filenameGenerator;
return $this;
}
public function handleForm(?UploadedFile $uploadedFile, string $path, ?callable $afterUploadCallback = null, bool $keepOriginalFilename = false): void
{
if (null === $uploadedFile) {
@ -30,11 +21,9 @@ class FileUploadHandler
if ($keepOriginalFilename) {
$filename = $originalFilename.'.'.$uploadedFile->guessExtension();
} elseif (!is_callable($this->filenameGenerator)) {
} else {
$safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
$filename = date('Ymd-his').$safeFilename.'.'.$uploadedFile->guessExtension();
} else {
$filename = call_user_func($this->filenameGenerator, $uploadedFile);
}
$uploadedFile->move($path, $filename);

View file

@ -2,11 +2,13 @@
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,7 +7,6 @@ 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
@ -24,7 +23,6 @@ class MenuType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -39,7 +37,6 @@ class MenuType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);

View file

@ -7,7 +7,6 @@ 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
@ -25,7 +24,6 @@ class NavigationAdditionalDomainType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);

View file

@ -6,7 +6,6 @@ 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;
@ -27,7 +26,6 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -40,21 +38,6 @@ class NavigationType extends AbstractType
'required' => true,
'attr' => [
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
$builder->add(
'color',
ColorType::class,
[
'label' => 'Color',
'required' => true,
'attr' => [
],
'constraints' => [
new NotBlank(),
],
@ -71,7 +54,6 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -112,7 +94,7 @@ class NavigationType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(min: 2, max: 10),
new Length(['min' => 2, 'max' => 10]),
],
]
);

View file

@ -13,7 +13,6 @@ 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
@ -30,7 +29,6 @@ class NodeType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -45,7 +43,6 @@ class NodeType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -85,7 +82,6 @@ class NodeType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -120,9 +116,6 @@ class NodeType extends AbstractType
return $choices;
}),
'constraints' => [
new Length(max: 255),
],
]
);
@ -161,37 +154,6 @@ class NodeType extends AbstractType
);
}
$builder->add(
'hasAbTest',
CheckboxType::class,
[
'label' => 'Enable A/B Testing',
'required' => false,
]
);
$builder->add(
'abTestCode',
TextType::class,
[
'label' => 'Code',
'required' => $builder->getData()->getHasAbTest(),
]
);
$builder->add(
'securityOperator',
ChoiceType::class,
[
'label' => 'Condition',
'required' => true,
'choices' => [
'At least one role' => 'or',
'All roles' => 'and',
],
]
);
$actions = [
'New page' => 'new',
'Use an existing page' => 'existing',

View file

@ -5,8 +5,6 @@ namespace App\Core\Form\Site\Page;
use App\Core\Entity\Site\Page\FileBlock;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FileBlockType extends TextBlockType
@ -23,21 +21,11 @@ class FileBlockType extends TextBlockType
);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace($view->vars, [
'file_type' => $options['file_type'],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => FileBlock::class,
'file_type' => 'auto',
'block_prefix' => 'file_block',
'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

@ -4,7 +4,6 @@ namespace App\Core\Form\Site\Page;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Image;
class ImageBlockType extends FileBlockType
@ -23,11 +22,4 @@ class ImageBlockType extends FileBlockType
], $options['options']),
);
}
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver->setDefault('is_image', true);
}
}

View file

@ -10,7 +10,6 @@ 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
@ -27,7 +26,6 @@ class PageType extends AbstractType
],
'constraints' => [
new NotBlank(),
new Length(max: 255),
],
]
);
@ -41,7 +39,6 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -55,7 +52,6 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -69,7 +65,6 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);
@ -83,7 +78,6 @@ class PageType extends AbstractType
'attr' => [
],
'constraints' => [
new Length(max: 255),
],
]
);

View file

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

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 Symfony\Component\Filesystem\Filesystem;
use function Symfony\Component\String\u;
class MakeCrudController extends AbstractMaker
@ -104,17 +103,6 @@ class MakeCrudController extends AbstractMaker
$options
);
$views = ['_form.html.twig', '_show.html.twig'];
$directory = sprintf('templates/admin/%s_admin/', $options['route']);
$filesystem = new Filesystem();
$filesystem->mkdir($directory);
foreach ($views as $view) {
$filesystem->dumpFile(
$directory.$view,
sprintf("{{ include('@Core/admin/crud/%s') }}\n", $view)
);
}
$generator->writeChanges();
$this->writeSuccessMessage($io);

View file

@ -11,6 +11,7 @@ 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,8 +92,7 @@ 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:
@ -101,18 +100,10 @@ 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)
@ -147,7 +138,7 @@ EOF
$types = [
'text' => null,
'textarea' => null,
'choice' => 'BlockEntity\\ChoiceBlock::class',
'choice' => null,
'collection' => 'BlockEntity\\CollectionBlock::class',
'editor_js_textarea' => null,
'file' => 'BlockEntity\\FileBlock::class',
@ -192,4 +183,12 @@ EOF
$io->writeln(sprintf(' * <comment>%s</comment>', $type));
}
}
public function configureDependencies(DependencyBuilder $dependencies)
{
$dependencies->addClassDependency(
Annotation::class,
'doctrine/annotations'
);
}
}

View file

@ -4,6 +4,7 @@ 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;
@ -14,19 +15,23 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*/
class EntityManager
{
public function __construct(
protected EventDispatcherInterface $eventDispatcher,
protected EntityManagerInterface $entityManager
) {
protected EventDispatcherInterface $eventDispatcher;
protected DoctrineEntityManager $entityManager;
public function __construct(EventDispatcherInterface $eventDispatcher, EntityManagerInterface $entityManager)
{
$this->eventDispatcher = $eventDispatcher;
$this->entityManager = $entityManager;
}
public function create(EntityInterface $entity, bool $dispatchEvent = true, bool $flush = true): self
public function create(EntityInterface $entity, bool $dispatchEvent = true): self
{
if ($dispatchEvent) {
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_CREATE_EVENT);
}
$this->persist($entity, $flush);
$this->persist($entity);
if ($dispatchEvent) {
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::CREATE_EVENT);
@ -35,13 +40,13 @@ class EntityManager
return $this;
}
public function update(EntityInterface $entity, bool $dispatchEvent = true, bool $flush = true): self
public function update(EntityInterface $entity, bool $dispatchEvent = true): self
{
if ($dispatchEvent) {
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_UPDATE_EVENT);
}
$this->persist($entity, $flush);
$this->persist($entity);
if ($dispatchEvent) {
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::UPDATE_EVENT);
@ -50,17 +55,14 @@ class EntityManager
return $this;
}
public function delete(EntityInterface $entity, bool $dispatchEvent = true, bool $flush = true): self
public function delete(EntityInterface $entity, bool $dispatchEvent = true): self
{
if ($dispatchEvent) {
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_DELETE_EVENT);
}
$this->entityManager->remove($entity);
if ($flush) {
$this->flush();
}
$this->flush();
if ($dispatchEvent) {
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::DELETE_EVENT);
@ -88,12 +90,9 @@ class EntityManager
return $this->entityManager;
}
protected function persist(EntityInterface $entity, bool $flush = true)
protected function persist(EntityInterface $entity)
{
$this->entityManager->persist($entity);
if ($flush) {
$this->flush();
}
$this->flush();
}
}

View file

@ -11,13 +11,10 @@ use App\Core\Entity\EntityInterface;
*/
class TranslatableEntityManager extends EntityManager
{
protected function persist(EntityInterface $entity, bool $flush = true)
protected function persist(EntityInterface $entity)
{
$this->entityManager->persist($entity, $flush);
$this->entityManager->persist($entity);
$entity->mergeNewTranslations();
if ($flush) {
$this->flush();
}
$this->flush();
}
}

View file

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

View file

@ -2,7 +2,6 @@
namespace App\Core\Notification;
use App\Entity\User;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface;
use Twig\Environment as TwigEnvironment;
@ -14,6 +13,7 @@ use Twig\Environment as TwigEnvironment;
*/
class MailNotifier
{
protected MailerInterface $mailer;
protected array $attachments = [];
protected array $recipients = [];
protected array $bccRecipients = [];
@ -21,8 +21,10 @@ class MailNotifier
protected ?string $from = null;
protected ?string $replyTo = null;
public function __construct(protected MailerInterface $mailer)
public function __construct(TwigEnvironment $twig, MailerInterface $mailer)
{
$this->mailer = $mailer;
$this->twig = $twig;
}
public function setMailer(Swift_Mailer $mailer): self
@ -137,19 +139,19 @@ class MailNotifier
return $this;
}
public function addRecipientByUser(User $user, bool $isBcc = false): self
public function addRecipientByAccount(Account $account, bool $isBcc = false): self
{
return $this->addRecipient($user->getEmail(), $isBcc);
return $this->addRecipient($account->getEmail(), $isBcc);
}
public function addRecipientsByUsers($users, bool $isBcc = false)
public function addRecipientsByAccounts($accounts, bool $isBcc = false)
{
if (!is_array($users)) {
if (!is_array($accounts)) {
throw new InvalidArgumentException('The "accounts" parameter must be an array or an instance of ObjectCollection');
}
foreach ($users as $user) {
$this->addRecipientByUser($user, $isBcc);
foreach ($accounts as $account) {
$this->addRecipientByAccount($account, $isBcc);
}
return $this;

View file

@ -7,8 +7,8 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method null|Referer find($id, $lockMode = null, $lockVersion = null)
* @method null|Referer findOneBy(array $criteria, array $orderBy = null)
* @method Referer|null find($id, $lockMode = null, $lockVersion = null)
* @method Referer|null 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 null|View find($id, $lockMode = null, $lockVersion = null)
* @method null|View findOneBy(array $criteria, array $orderBy = null)
* @method View|null find($id, $lockMode = null, $lockVersion = null)
* @method View|null 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
{

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