v1.9.0
This commit is contained in:
commit
b79faea51f
117
CHANGELOG.md
Normal file
117
CHANGELOG.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
### Fixed
|
||||
### Changed
|
||||
|
||||
## [1.9.0] - 2022-03-13
|
||||
### Added
|
||||
* add murph version in admin ui
|
||||
### Changed
|
||||
* the core is now installed with composer
|
||||
|
||||
## [1.8.0] - 2022-03-10
|
||||
### Added
|
||||
* add security roles in app configuration
|
||||
* add option to restrict node access to specific roles
|
||||
### Changed
|
||||
* rename `core/EventSuscriber` with `core/EventSubscriber`
|
||||
|
||||
## [1.7.3] - 2022-03-06
|
||||
### Added
|
||||
* add ability to rename file in the file manager
|
||||
### Fixed
|
||||
* fix user factory
|
||||
* fix user creation from ui
|
||||
|
||||
## [1.7.2] - 2022-03-03
|
||||
### Added
|
||||
* add templates to render sections and items in the admin menu
|
||||
### Fixed
|
||||
* fix the analytic table when a path is a long
|
||||
|
||||
## [1.7.1] - 2022-03-01
|
||||
### Added
|
||||
* add translations
|
||||
### Fixed
|
||||
* fix missing directories
|
||||
|
||||
## [1.7.0] - 2022-03-01
|
||||
### Fixed
|
||||
* fix the analytic referers table when a referer has a long domain
|
||||
### Changed
|
||||
* upgrade dependencies
|
||||
* move assets to the core directory
|
||||
|
||||
## [1.6.0] - 2022-02-28
|
||||
### Added
|
||||
* add block in field templates to allow override
|
||||
* merge route params in crud admin redirects
|
||||
* improve murph:user:create command
|
||||
|
||||
### Fixed
|
||||
* fix form namespace prefix in the crud controller maker
|
||||
* fix date field when the value is empty
|
||||
* fix crud batch column width
|
||||
* fix sidebar icon width
|
||||
* fix cache clear task
|
||||
|
||||
### Changed
|
||||
* remove password generation from the user factory
|
||||
|
||||
## [1.5.0] - 2022-02-25
|
||||
### Added
|
||||
* add desktop views and mobile views
|
||||
|
||||
### Changed
|
||||
* upgrade dependencies
|
||||
* replace jaybizzle/crawler-detect with matomo/device-detector
|
||||
|
||||
## [1.4.1] - 2022-02-23
|
||||
### Added
|
||||
* handle app urls in twig routing filters
|
||||
|
||||
### Fixed
|
||||
* fix views in analytics modal
|
||||
* replace empty path with "/" in analytics
|
||||
### Changed
|
||||
* update default templates
|
||||
|
||||
## [1.4.0] - 2022-02-21
|
||||
### Added
|
||||
* add basic analytics
|
||||
|
||||
## [1.3.0] - 2022-02-19
|
||||
### Added
|
||||
* add support of regexp with substitution in redirect
|
||||
* url tags can be used as redirect location
|
||||
* add builders to replace file information tags and url tags
|
||||
|
||||
### Fixed
|
||||
* fix filemanager sorting
|
||||
* fix batch action setter
|
||||
|
||||
## [1.2.0] - 2022-02-14
|
||||
### Added
|
||||
* add sort in file manager
|
||||
* add redirect manager
|
||||
|
||||
### Changed
|
||||
* replace node-sass with sass
|
||||
|
||||
## [1.1.0] - 2022-02-29
|
||||
### Added
|
||||
* add directory upload in file manager
|
||||
|
||||
### Fixed
|
||||
* fix admin node routing
|
||||
|
||||
### Changed
|
||||
* symfony/swiftmailer-bundle is replaced by symfony/mailer
|
||||
|
||||
## [1.0.1] - 2022-02-25
|
||||
### Fixed
|
||||
* fix Makefile environment vars (renaming)
|
||||
* fix composer minimum stability
|
||||
|
||||
## [1.0.0] - 2022-01-23
|
16
README.md
Normal file
16
README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Murph
|
||||
|
||||
Murph is an **open-source CMF** built on top of Symfony that helps you to **build your own CMS with several domains and languages**. It comes with:
|
||||
|
||||
* A fully implemented and customizable **tree manager** 🌳
|
||||
* A **CRUD generator** ✏️
|
||||
* A global **settings manager** and a navigation settings manager ⚙️
|
||||
* A **tasks manager** 🧹
|
||||
* A basic **web analytics** 📊
|
||||
* **2FA authentication** 🔒
|
||||
|
||||
**Symfony developers will love build on Murph 🧪**
|
||||
|
||||
**End users will be fond of the interface and the powerful tools 💜**
|
||||
|
||||
📗 [Read the documentation](https://doc.murph-project.org/)
|
64
UPGRADE.md
Normal file
64
UPGRADE.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
## [Unreleased]
|
||||
|
||||
## Upgrade to v1.8.0
|
||||
### Commands
|
||||
|
||||
```
|
||||
make doctrine-migration
|
||||
```
|
||||
|
||||
### Files
|
||||
|
||||
Event subscribers in `src/EventSubscriber` must update namespaces.
|
||||
|
||||
## Upgrade to v1.7.0
|
||||
### Commands
|
||||
|
||||
```
|
||||
yarn add sortablejs@^1.14.0
|
||||
|
||||
```
|
||||
|
||||
### Files
|
||||
|
||||
* `assets/css/_admin_extend.scss` is removed
|
||||
* `assets/css/_admin_vars.scss` is removed
|
||||
* `assets/css/_admin_vars.scss` is changed
|
||||
* `assets/js/admin` is removed
|
||||
* `assets/js/admin.js` is changed
|
||||
|
||||
|
||||
## Upgrade to v1.5.0
|
||||
### Commands
|
||||
|
||||
```
|
||||
composer remove jaybizzle/crawler-detect
|
||||
composer require matomo/device-detector
|
||||
make doctrine-migration
|
||||
```
|
||||
|
||||
## Upgrade to v1.4.0
|
||||
### Commands
|
||||
|
||||
```
|
||||
yarn remove node-sass
|
||||
yarn add sass --dev --save
|
||||
yarn add chart.js --save
|
||||
composer require jaybizzle/crawler-detect
|
||||
make doctrine-migration
|
||||
make asset
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```
|
||||
// config/services.yaml
|
||||
services:
|
||||
App\Core\EventListener\RedirectListener:
|
||||
tags:
|
||||
- { name: kernel.event_listener, event: kernel.exception }
|
||||
|
||||
App\Core\EventListener\AnalyticListener:
|
||||
tags:
|
||||
- { name: kernel.event_listener, event: kernel.request }
|
||||
```
|
69
composer.json
Normal file
69
composer.json
Normal file
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "murph/murph-core",
|
||||
"description": "A powerful CMS framework",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"php": ">=8.0.0",
|
||||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"bjeavons/zxcvbn-php": "^1.3",
|
||||
"cocur/slugify": "^4.1",
|
||||
"composer/package-versions-deprecated": "1.11.99.1",
|
||||
"doctrine/annotations": "^1.0",
|
||||
"doctrine/doctrine-bundle": "^2.5",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.2",
|
||||
"doctrine/orm": "^2.11",
|
||||
"friendsofsymfony/jsrouting-bundle": "^2.8",
|
||||
"jaybizzle/crawler-detect": "^1.2",
|
||||
"knplabs/doctrine-behaviors": "^2.6",
|
||||
"knplabs/knp-paginator-bundle": "^5.8",
|
||||
"liip/imagine-bundle": "^2.7",
|
||||
"matomo/device-detector": "^5.0",
|
||||
"phpdocumentor/reflection-docblock": "^5.3",
|
||||
"scheb/2fa-google-authenticator": "^5.13",
|
||||
"scheb/2fa-qr-code": "^5.13",
|
||||
"sensio/framework-extra-bundle": "^6.2",
|
||||
"sensiolabs/ansi-to-html": "^1.2",
|
||||
"spe/filesize-extension-bundle": "~2.0.0",
|
||||
"stof/doctrine-extensions-bundle": "^1.7",
|
||||
"symfony/apache-pack": "^1.0",
|
||||
"symfony/asset": "5.4.*",
|
||||
"symfony/console": "5.4.*",
|
||||
"symfony/dotenv": "5.4.*",
|
||||
"symfony/event-dispatcher": "5.4.*",
|
||||
"symfony/expression-language": "5.4.*",
|
||||
"symfony/finder": "5.4.*",
|
||||
"symfony/flex": "^1.3.1",
|
||||
"symfony/form": "5.4.*",
|
||||
"symfony/framework-bundle": "5.4.*",
|
||||
"symfony/http-client": "5.4.*",
|
||||
"symfony/intl": "5.4.*",
|
||||
"symfony/mailer": "5.4.*",
|
||||
"symfony/mime": "5.4.*",
|
||||
"symfony/monolog-bundle": "^3.1",
|
||||
"symfony/notifier": "5.4.*",
|
||||
"symfony/process": "5.4.*",
|
||||
"symfony/property-access": "5.4.*",
|
||||
"symfony/property-info": "5.4.*",
|
||||
"symfony/proxy-manager-bridge": "5.4.*",
|
||||
"symfony/security-bundle": "5.4.*",
|
||||
"symfony/serializer": "5.4.*",
|
||||
"symfony/string": "5.4.*",
|
||||
"symfony/translation": "5.4.*",
|
||||
"symfony/twig-bundle": "^5.2",
|
||||
"symfony/validator": "5.4.*",
|
||||
"symfony/web-link": "5.4.*",
|
||||
"symfony/webpack-encore-bundle": "^1.11",
|
||||
"symfony/yaml": "5.4.*",
|
||||
"twig/extra-bundle": "^2.12|^3.3",
|
||||
"twig/twig": "^2.12|^3.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\Core\\": "src/core/"
|
||||
}
|
||||
}
|
||||
}
|
204
src/core/Analytic/DateRangeAnalytic.php
Normal file
204
src/core/Analytic/DateRangeAnalytic.php
Normal file
|
@ -0,0 +1,204 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Analytic;
|
||||
|
||||
use App\Core\Entity\Site\Node;
|
||||
use App\Core\Repository\Analytic\RefererRepositoryQuery;
|
||||
use App\Core\Repository\Analytic\ViewRepositoryQuery;
|
||||
|
||||
/**
|
||||
* class DateRangeAnalytic.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class DateRangeAnalytic
|
||||
{
|
||||
protected ViewRepositoryQuery $viewQuery;
|
||||
protected RefererRepositoryQuery $refererQuery;
|
||||
protected ?Node $node;
|
||||
protected ?\DateTime $from;
|
||||
protected ?\DateTime $to;
|
||||
protected bool $reload = true;
|
||||
protected array $cache = [];
|
||||
|
||||
public function __construct(ViewRepositoryQuery $viewQuery, RefererRepositoryQuery $refererQuery)
|
||||
{
|
||||
$this->viewQuery = $viewQuery;
|
||||
$this->refererQuery = $refererQuery;
|
||||
}
|
||||
|
||||
public function getViews(): array
|
||||
{
|
||||
$entities = $this->getEntities('view');
|
||||
$this->reload = false;
|
||||
|
||||
if ($entities) {
|
||||
$first = $entities[0];
|
||||
$last = $entities[count($entities) - 1];
|
||||
|
||||
$diff = $first->getDate()->diff($last->getDate());
|
||||
|
||||
if ($diff->days >= 90) {
|
||||
$format = 'Y-m';
|
||||
} else {
|
||||
$format = 'Y-m-d';
|
||||
}
|
||||
}
|
||||
|
||||
$datas = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$index = $entity->getDate()->format($format);
|
||||
|
||||
if (!isset($datas[$index])) {
|
||||
$datas[$index] = 0;
|
||||
}
|
||||
|
||||
$datas[$index] += $entity->getViews();
|
||||
}
|
||||
|
||||
return $datas;
|
||||
}
|
||||
|
||||
public function getPathViews(): array
|
||||
{
|
||||
$entities = $this->getEntities('view');
|
||||
$this->reload = false;
|
||||
|
||||
$datas = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$index = $entity->getPath();
|
||||
|
||||
if (!isset($datas[$index])) {
|
||||
$datas[$index] = [
|
||||
'views' => 0,
|
||||
'desktopViews' => 0,
|
||||
'mobileViews' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$datas[$index]['views'] += $entity->getViews();
|
||||
$datas[$index]['desktopViews'] += $entity->getDesktopViews();
|
||||
$datas[$index]['mobileViews'] += $entity->getMobileViews();
|
||||
}
|
||||
|
||||
uasort($datas, function($a, $b) {
|
||||
if ($a['views'] > $b['views']) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($a['views'] < $b['views']) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return $datas;
|
||||
}
|
||||
|
||||
public function getReferers(): array
|
||||
{
|
||||
$entities = $this->getEntities('referer');
|
||||
$this->reload = false;
|
||||
|
||||
$datas = [];
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$index = parse_url($entity->getUri(), PHP_URL_HOST);
|
||||
|
||||
if (!isset($datas[$index])) {
|
||||
$datas[$index] = [
|
||||
'views' => 0,
|
||||
'uris' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$datas[$index]['views'] += $entity->getViews();
|
||||
|
||||
$path = parse_url($entity->getUri(), PHP_URL_PATH);
|
||||
|
||||
if (empty($path)) {
|
||||
$path = '/';
|
||||
}
|
||||
|
||||
if (!isset($datas[$index]['uris'][$path])) {
|
||||
$datas[$index]['uris'][$path] = 0;
|
||||
}
|
||||
|
||||
$datas[$index]['uris'][$path] += $entity->getViews();
|
||||
}
|
||||
|
||||
uasort($datas, function($a, $b) {
|
||||
if ($a['views'] > $b['views']) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($a['views'] < $b['views']) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return $datas;
|
||||
}
|
||||
|
||||
public function setDateRange(?\DateTime $from, ?\DateTime $to): self
|
||||
{
|
||||
$this->from = $from;
|
||||
$this->to = $to;
|
||||
$this->reload = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setNode(?Node $node): self
|
||||
{
|
||||
$this->node = $node;
|
||||
$this->reload = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getEntities(string $type): array
|
||||
{
|
||||
if ('view' === $type) {
|
||||
$query = $this->viewQuery->create();
|
||||
} elseif ('referer' === $type) {
|
||||
$query = $this->refererQuery->create();
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid type');
|
||||
}
|
||||
|
||||
if (!$this->reload && isset($this->cache[$type])) {
|
||||
return $this->cache[$type];
|
||||
}
|
||||
|
||||
if (null !== $this->from) {
|
||||
$query
|
||||
->andWhere('.date >= :from')
|
||||
->setParameter(':from', $this->from)
|
||||
;
|
||||
}
|
||||
|
||||
if (null !== $this->to) {
|
||||
$query
|
||||
->andWhere('.date <= :to')
|
||||
->setParameter(':to', $this->to)
|
||||
;
|
||||
}
|
||||
|
||||
if (null !== $this->node) {
|
||||
$query
|
||||
->andWhere('.node = :node')
|
||||
->setParameter(':node', $this->node->getId())
|
||||
;
|
||||
}
|
||||
|
||||
$this->cache[$type] = $query->orderBy('.date')->find();
|
||||
|
||||
return $this->cache[$type];
|
||||
}
|
||||
}
|
20
src/core/Annotation/UrlGenerator.php
Normal file
20
src/core/Annotation/UrlGenerator.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Annotation;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
|
||||
/**
|
||||
* class UrlGenerator.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
* @Annotation
|
||||
*/
|
||||
class UrlGenerator
|
||||
{
|
||||
public string $service;
|
||||
|
||||
public string $method;
|
||||
|
||||
public array $options = [];
|
||||
}
|
96
src/core/Authenticator/LoginFormAuthenticator.php
Normal file
96
src/core/Authenticator/LoginFormAuthenticator.php
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Authenticator;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
|
||||
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
|
||||
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
|
||||
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||
|
||||
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
|
||||
{
|
||||
use TargetPathTrait;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private UrlGeneratorInterface $urlGenerator;
|
||||
|
||||
private CsrfTokenManagerInterface $csrfTokenManager;
|
||||
|
||||
private UserPasswordEncoderInterface $passwordEncoder;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->csrfTokenManager = $csrfTokenManager;
|
||||
$this->passwordEncoder = $passwordEncoder;
|
||||
}
|
||||
|
||||
public function supports(Request $request)
|
||||
{
|
||||
return 'auth_login' === $request->attributes->get('_route') && $request->isMethod('POST');
|
||||
}
|
||||
|
||||
public function getCredentials(Request $request)
|
||||
{
|
||||
$credentials = [
|
||||
'email' => $request->request->get('_username'),
|
||||
'password' => $request->request->get('_password'),
|
||||
'csrf_token' => $request->request->get('_csrf_token'),
|
||||
];
|
||||
|
||||
$request->getSession()->set(Security::LAST_USERNAME, $credentials['email']);
|
||||
|
||||
return $credentials;
|
||||
}
|
||||
|
||||
public function getUser($credentials, UserProviderInterface $userProvider)
|
||||
{
|
||||
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
|
||||
|
||||
if (!$this->csrfTokenManager->isTokenValid($token)) {
|
||||
throw new InvalidCsrfTokenException();
|
||||
}
|
||||
|
||||
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);
|
||||
|
||||
if (!$user) {
|
||||
// fail authentication with a custom error
|
||||
throw new CustomUserMessageAuthenticationException('Email could not be found.');
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function checkCredentials($credentials, UserInterface $user)
|
||||
{
|
||||
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
|
||||
}
|
||||
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
|
||||
{
|
||||
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
|
||||
return new RedirectResponse($targetPath);
|
||||
}
|
||||
|
||||
return new RedirectResponse($this->urlGenerator->generate('admin_dashboard_index'));
|
||||
}
|
||||
|
||||
protected function getLoginUrl()
|
||||
{
|
||||
return $this->urlGenerator->generate('auth_login');
|
||||
}
|
||||
}
|
24
src/core/Bundle/CoreBundle.php
Normal file
24
src/core/Bundle/CoreBundle.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Twig.
|
||||
*
|
||||
* (c) Fabien Potencier
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Core\Bundle;
|
||||
|
||||
use App\Core\DependencyInjection\CoreExtension;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
|
||||
|
||||
class CoreBundle extends Bundle
|
||||
{
|
||||
public function getContainerExtension(): ?ExtensionInterface
|
||||
{
|
||||
return new CoreExtension();
|
||||
}
|
||||
}
|
73
src/core/Cache/SymfonyCacheManager.php
Normal file
73
src/core/Cache/SymfonyCacheManager.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Cache;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||
|
||||
/**
|
||||
* class SymfonyCacheManager.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class SymfonyCacheManager
|
||||
{
|
||||
protected KernelInterface $kernel;
|
||||
protected HttpClientInterface $httpClient;
|
||||
protected UrlGeneratorInterface $urlGenerator;
|
||||
|
||||
public function __construct(KernelInterface $kernel, HttpClientInterface $httpClient, UrlGeneratorInterface $urlGenerator)
|
||||
{
|
||||
$this->kernel = $kernel;
|
||||
$this->httpClient = $httpClient;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function cleanRouting()
|
||||
{
|
||||
$finder = new Finder();
|
||||
$finder
|
||||
->in($this->kernel->getCacheDir())
|
||||
->depth('== 0')
|
||||
->name('url_*.php*')
|
||||
;
|
||||
|
||||
$pingUrl = $this->urlGenerator->generate('_ping', [], UrlGeneratorInterface::ABSOLUTE_URL);
|
||||
|
||||
foreach ($finder as $file) {
|
||||
unlink((string) $file->getPathname());
|
||||
}
|
||||
|
||||
try {
|
||||
// Hack: used to regenerate cache of url generator
|
||||
$this->httpClient->request('POST', $pingUrl);
|
||||
} catch (ClientException $e) {
|
||||
} catch (TransportException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function cleanAll(OutputInterface $output = null)
|
||||
{
|
||||
$application = new Application($this->kernel);
|
||||
$application->setAutoExit(false);
|
||||
|
||||
if (null === $output) {
|
||||
$output = new BufferedOutput();
|
||||
}
|
||||
|
||||
$input = new ArrayInput([
|
||||
'command' => 'cache:warmup',
|
||||
'-e' => $this->kernel->getEnvironment(),
|
||||
]);
|
||||
|
||||
$application->run($input, $output);
|
||||
}
|
||||
}
|
108
src/core/Command/UserCreateCommand.php
Normal file
108
src/core/Command/UserCreateCommand.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Command;
|
||||
|
||||
use App\Core\Factory\UserFactory;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
|
||||
|
||||
class UserCreateCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'murph:user:create';
|
||||
protected static $defaultDescription = 'Creates a user';
|
||||
protected UserFactory $userFactory;
|
||||
protected EntityManager $entityManager;
|
||||
protected TokenGeneratorInterface $tokenGenerator;
|
||||
|
||||
public function __construct(
|
||||
UserFactory $userFactory,
|
||||
EntityManager $entityManager,
|
||||
TokenGeneratorInterface $tokenGenerator
|
||||
) {
|
||||
$this->userFactory = $userFactory;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->tokenGenerator = $tokenGenerator;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDescription(self::$defaultDescription)
|
||||
->addArgument('email', InputArgument::OPTIONAL, 'E-mail')
|
||||
->addOption('is-admin', null, InputOption::VALUE_NONE, 'Add the admin role')
|
||||
->addOption('is-writer', null, InputOption::VALUE_NONE, 'Add the write role')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$helper = $this->getHelper('question');
|
||||
|
||||
$emailQuestion = new Question('E-mail: ');
|
||||
$emailQuestion->setValidator(function ($value) {
|
||||
if (empty($value)) {
|
||||
throw new \RuntimeException('The email must not be empty.');
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
|
||||
$passwordQuestion = new Question('Password (leave empty to generate a random password): ');
|
||||
$passwordQuestion->setHidden(true);
|
||||
|
||||
$isAdminDefault = $input->getOption('is-admin');
|
||||
$isWriterDefault = $input->getOption('is-writer');
|
||||
|
||||
$isAdminQuestionLabel = sprintf('Administrator [%s] ', $isAdminDefault ? 'Y/n' : 'y/N');
|
||||
$isWriterQuestionLabel = sprintf('Writer [%s] ', $isWriterDefault ? 'Y/n' : 'y/N');
|
||||
|
||||
$isAdminQuestion = new ConfirmationQuestion($isAdminQuestionLabel, $isAdminDefault);
|
||||
$isWriterQuestion = new ConfirmationQuestion($isWriterQuestionLabel, $isWriterDefault);
|
||||
|
||||
$io->section('Authentication');
|
||||
|
||||
$email = $input->getArgument('email');
|
||||
|
||||
if (empty($email)) {
|
||||
$email = $helper->ask($input, $output, $emailQuestion);
|
||||
}
|
||||
|
||||
$password = $helper->ask($input, $output, $passwordQuestion);
|
||||
|
||||
$showPassword = empty($password);
|
||||
|
||||
if ($showPassword) {
|
||||
$password = mb_substr($this->tokenGenerator->generateToken(), 0, 18);
|
||||
$io->info(sprintf('Password: %s', $password));
|
||||
} else {
|
||||
$io->newLine();
|
||||
}
|
||||
|
||||
$io->section('Roles');
|
||||
|
||||
$isAdmin = $helper->ask($input, $output, $isAdminQuestion);
|
||||
$isWriter = $helper->ask($input, $output, $isWriterQuestion);
|
||||
|
||||
$user = $this->userFactory->create($email, $password);
|
||||
$user->setIsAdmin($isAdmin);
|
||||
$user->setIsWriter($isWriter);
|
||||
|
||||
$this->entityManager->create($user);
|
||||
|
||||
$io->newLine();
|
||||
$io->success('User created!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
0
src/core/Controller/.gitignore
vendored
Normal file
0
src/core/Controller/.gitignore
vendored
Normal file
150
src/core/Controller/Account/AccountAdminController.php
Normal file
150
src/core/Controller/Account/AccountAdminController.php
Normal file
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Account;
|
||||
|
||||
use App\Core\Controller\Admin\AdminController;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Repository\UserRepository;
|
||||
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface;
|
||||
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface as TotpAuthenticatorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
|
||||
use ZxcvbnPhp\Zxcvbn;
|
||||
|
||||
/**
|
||||
* @Route("/admin/account")
|
||||
*/
|
||||
class AccountAdminController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @Route("/", name="admin_account")
|
||||
*/
|
||||
public function account(Request $request, TotpAuthenticatorInterface $totpAuthenticatorService): Response
|
||||
{
|
||||
$account = $this->getUser();
|
||||
|
||||
return $this->render('@Core/account/admin/edit.html.twig', [
|
||||
'account' => $account,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/2fa", name="admin_account_2fa")
|
||||
*/
|
||||
public function twoFactorAuthentication(
|
||||
Request $request,
|
||||
GoogleAuthenticatorInterface $totpAuthenticatorService,
|
||||
EntityManager $entityManager
|
||||
): Response {
|
||||
if ($request->isMethod('GET')) {
|
||||
return $this->redirectToRoute('admin_account');
|
||||
}
|
||||
|
||||
$account = $this->getUser();
|
||||
$csrfToken = $request->request->get('_csrf_token');
|
||||
$enable = (bool) $request->request->get('enable');
|
||||
$code = $request->request->get('code', '');
|
||||
$secret = $request->request->get('secret', '');
|
||||
$qrCodeContent = null;
|
||||
|
||||
if ($this->isCsrfTokenValid('2fa', $csrfToken)) {
|
||||
if ($enable && !$account->isTotpAuthenticationEnabled()) {
|
||||
if (empty($secret)) {
|
||||
$secret = $totpAuthenticatorService->generateSecret();
|
||||
|
||||
$account->setTotpSecret($secret);
|
||||
|
||||
$qrCodeContent = $totpAuthenticatorService->getQRContent($account);
|
||||
} else {
|
||||
$account->setTotpSecret($secret);
|
||||
|
||||
$qrCodeContent = $totpAuthenticatorService->getQRContent($account);
|
||||
|
||||
if (!$totpAuthenticatorService->checkCode($account, $code)) {
|
||||
$this->addFlash('error', 'The code is not valid.');
|
||||
} else {
|
||||
$this->addFlash('success', 'Double authentication enabled.');
|
||||
|
||||
$entityManager->update($account);
|
||||
|
||||
return $this->redirectToRoute('admin_account');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$enable && $account->isTotpAuthenticationEnabled()) {
|
||||
$account->setTotpSecret(null);
|
||||
|
||||
$entityManager->update($account);
|
||||
|
||||
$this->addFlash('success', 'Double authentication disabled.');
|
||||
|
||||
return $this->redirectToRoute('admin_account');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('@Core/account/admin/edit.html.twig', [
|
||||
'account' => $account,
|
||||
'twoFaKey' => $secret,
|
||||
'twoFaQrCodeContent' => $qrCodeContent,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/password", name="admin_account_password", methods={"POST"})
|
||||
*/
|
||||
public function password(
|
||||
Request $request,
|
||||
UserRepository $repository,
|
||||
TokenGeneratorInterface $tokenGenerator,
|
||||
UserPasswordEncoderInterface $encoder,
|
||||
EntityManager $entityManager
|
||||
): Response {
|
||||
$account = $this->getUser();
|
||||
$csrfToken = $request->request->get('_csrf_token');
|
||||
|
||||
if ($this->isCsrfTokenValid('password', $csrfToken)) {
|
||||
$password = $request->request->get('password');
|
||||
|
||||
if (!$encoder->isPasswordValid($account, $password)) {
|
||||
$this->addFlash('error', 'The form is not valid.');
|
||||
|
||||
return $this->redirectToRoute('admin_account');
|
||||
}
|
||||
|
||||
$password1 = $request->request->get('password1');
|
||||
$password2 = $request->request->get('password2');
|
||||
|
||||
$zxcvbn = new Zxcvbn();
|
||||
$strength = $zxcvbn->passwordStrength($password1, []);
|
||||
|
||||
if (4 === $strength['score'] && $password1 === $password2) {
|
||||
$account
|
||||
->setPassword($encoder->encodePassword($account, $password1))
|
||||
->setConfirmationToken($tokenGenerator->generateToken())
|
||||
;
|
||||
|
||||
$entityManager->update($account);
|
||||
|
||||
$this->addFlash('success', 'Password updated.');
|
||||
|
||||
return $this->redirectToRoute('admin_account');
|
||||
}
|
||||
}
|
||||
|
||||
$this->addFlash('error', 'The form is not valid.');
|
||||
|
||||
return $this->redirectToRoute('admin_account');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'account';
|
||||
}
|
||||
}
|
41
src/core/Controller/Admin/AdminController.php
Normal file
41
src/core/Controller/Admin/AdminController.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Admin;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
abstract class AdminController extends AbstractController
|
||||
{
|
||||
protected array $coreParameters;
|
||||
|
||||
public function __construct(ParameterBagInterface $parameters)
|
||||
{
|
||||
$this->coreParameters = $parameters->get('core');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/_ping", name="_ping")
|
||||
*/
|
||||
public function ping()
|
||||
{
|
||||
return $this->json(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function render(string $view, array $parameters = [], Response $response = null): Response
|
||||
{
|
||||
$parameters['section'] = $this->getSection();
|
||||
$parameters['site_name'] = $this->coreParameters['site']['name'];
|
||||
$parameters['site_logo'] = $this->coreParameters['site']['logo'];
|
||||
$parameters['murph_version'] = defined('MURPH_VERSION') ? MURPH_VERSION : null;
|
||||
|
||||
return parent::render($view, $parameters, $response);
|
||||
}
|
||||
|
||||
abstract protected function getSection(): string;
|
||||
}
|
342
src/core/Controller/Admin/Crud/CrudController.php
Normal file
342
src/core/Controller/Admin/Crud/CrudController.php
Normal file
|
@ -0,0 +1,342 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Admin\Crud;
|
||||
|
||||
use App\Core\Controller\Admin\AdminController;
|
||||
use App\Core\Crud\CrudConfiguration;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Core\Repository\RepositoryQuery;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
|
||||
/**
|
||||
* class CrudController.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
abstract class CrudController extends AdminController
|
||||
{
|
||||
protected array $filters = [];
|
||||
protected array $sort = [
|
||||
'label' => null,
|
||||
'direction' => null,
|
||||
];
|
||||
|
||||
abstract protected function getConfiguration(): CrudConfiguration;
|
||||
|
||||
protected function doIndex(int $page = 1, RepositoryQuery $query, Request $request, Session $session): Response
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
$this->applySort('index', $query, $request);
|
||||
$this->updateFilters($request, $session);
|
||||
|
||||
$pager = $query
|
||||
->usefilters($this->filters)
|
||||
->paginate($page, $configuration->getmaxperpage('index'))
|
||||
;
|
||||
|
||||
return $this->render($this->getConfiguration()->getView('index'), [
|
||||
'configuration' => $configuration,
|
||||
'pager' => $pager,
|
||||
'sort' => $this->sort,
|
||||
'filters' => [
|
||||
'show' => null !== $configuration->getForm('filter'),
|
||||
'isEmpty' => empty($this->filters),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
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('new'));
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
if (null !== $beforeCreate) {
|
||||
call_user_func_array($beforeCreate, [$entity, $form, $request]);
|
||||
}
|
||||
|
||||
$entityManager->create($entity);
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
|
||||
return $this->redirectToRoute($configuration->getPageRoute('edit'), array_merge(
|
||||
['entity' => $entity->getId()],
|
||||
$configuration->getPageRouteParams('edit')
|
||||
));
|
||||
}
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
}
|
||||
|
||||
return $this->render($configuration->getView('new'), [
|
||||
'form' => $form->createView(),
|
||||
'configuration' => $configuration,
|
||||
'entity' => $entity,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function doShow(EntityInterface $entity): Response
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
return $this->render($configuration->getView('show'), [
|
||||
'entity' => $entity,
|
||||
'configuration' => $configuration,
|
||||
]);
|
||||
}
|
||||
|
||||
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('edit'));
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
if (null !== $beforeUpdate) {
|
||||
call_user_func_array($beforeUpdate, [$entity, $form, $request]);
|
||||
}
|
||||
|
||||
$entityManager->update($entity);
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
|
||||
return $this->redirectToRoute($configuration->getPageRoute('edit'), array_merge(
|
||||
['entity' => $entity->getId()],
|
||||
$configuration->getPageRouteParams('edit')
|
||||
));
|
||||
}
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
}
|
||||
|
||||
return $this->render($configuration->getView('edit'), [
|
||||
'form' => $form->createView(),
|
||||
'configuration' => $configuration,
|
||||
'entity' => $entity,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function doSort(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
$context = $request->query->get('context', 'index');
|
||||
|
||||
if (!$configuration->getIsSortableCollection($context)) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$this->applySort($context, $query, $request);
|
||||
$this->updateFilters($request, $session);
|
||||
|
||||
$pager = $query
|
||||
->useFilters($this->filters)
|
||||
->paginate($page, $configuration->getMaxPerPage($context))
|
||||
;
|
||||
|
||||
if ($this->isCsrfTokenValid('sort', $request->query->get('_token'))) {
|
||||
$items = $request->request->get('items', []);
|
||||
$setter = 'set'.$configuration->getSortableCollectionProperty();
|
||||
$orderStart = ($page - 1) * $configuration->getMaxPerPage($context);
|
||||
|
||||
foreach ($pager as $key => $entity) {
|
||||
if (isset($items[$key + 1])) {
|
||||
$entity->{$setter}($items[$key + 1] + $orderStart);
|
||||
|
||||
$entityManager->update($entity);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
}
|
||||
|
||||
return $this->json([]);
|
||||
}
|
||||
|
||||
protected function doBatch(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
$datas = $request->request->get('batch', []);
|
||||
|
||||
$context = $datas['context'] ?? 'index';
|
||||
$target = $datas['target'] ?? null;
|
||||
$action = $datas['action'] ?? null;
|
||||
$token = $datas['_token'] ?? null;
|
||||
$items = $datas['items'] ?? [];
|
||||
$batchAction = $configuration->getBatchAction($context, $action);
|
||||
|
||||
if (empty($context) || empty($action) || empty($target)) {
|
||||
return $this->json([]);
|
||||
}
|
||||
|
||||
if (!$this->isCsrfTokenValid('batch', $token) || empty($batchAction)) {
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
|
||||
return $this->json([]);
|
||||
}
|
||||
|
||||
$callback = $batchAction['callback'];
|
||||
|
||||
$this->applySort($context, $query, $request);
|
||||
$this->updateFilters($request, $session);
|
||||
|
||||
$query->useFilters($this->filters);
|
||||
|
||||
if ('selection' === $target) {
|
||||
$isSelection = true;
|
||||
$pager = $query->paginate($page, $configuration->getMaxPerPage($context));
|
||||
} else {
|
||||
$isSelection = false;
|
||||
$pager = $query->find();
|
||||
}
|
||||
|
||||
foreach ($pager as $key => $entity) {
|
||||
if (($isSelection && isset($items[$key + 1])) || !$isSelection) {
|
||||
$callback($entity, $entityManager);
|
||||
}
|
||||
}
|
||||
|
||||
$this->addFlash('success', 'Batch action done.');
|
||||
|
||||
return $this->json([]);
|
||||
}
|
||||
|
||||
protected function doDelete(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeDelete = null): Response
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
|
||||
if (null !== $beforeDelete) {
|
||||
call_user_func($beforeDelete, $entity);
|
||||
}
|
||||
|
||||
$entityManager->delete($entity);
|
||||
|
||||
$this->addFlash('success', 'The data has been removed.');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute($configuration->getPageRoute('index'));
|
||||
}
|
||||
|
||||
protected function doFilter(Session $session): Response
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
$type = $configuration->getForm('filter');
|
||||
|
||||
if (null === $type) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$form = $this->createForm($type);
|
||||
$form->submit($session->get($form->getName(), []));
|
||||
|
||||
return $this->render($configuration->getView('filter'), [
|
||||
'form' => $form->createView(),
|
||||
'configuration' => $configuration,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function updateFilters(Request $request, Session $session)
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
$type = $configuration->getForm('filter');
|
||||
|
||||
if (null === $type) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form = $this->createForm($type);
|
||||
|
||||
if ($request->query->has($form->getName())) {
|
||||
$filters = $request->query->get($form->getName());
|
||||
|
||||
if ('0' === $filters) {
|
||||
$filters = [];
|
||||
}
|
||||
} elseif ($session->has($form->getName())) {
|
||||
$filters = $session->get($form->getName());
|
||||
} else {
|
||||
$filters = [];
|
||||
}
|
||||
|
||||
$form->submit($filters);
|
||||
|
||||
if (empty($filters)) {
|
||||
$this->filters = $filters;
|
||||
$session->set($form->getName(), $filters);
|
||||
} elseif ($form->isValid()) {
|
||||
$this->filters = $form->getData();
|
||||
$session->set($form->getName(), $filters);
|
||||
}
|
||||
}
|
||||
|
||||
protected function prepareEntity(EntityInterface $entity)
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
if ($configuration->isI18n()) {
|
||||
foreach ($configuration->getLocales() as $locale) {
|
||||
$entity->addTranslation($entity->translate($locale, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function applySort(string $context, RepositoryQuery $query, Request $request)
|
||||
{
|
||||
$configuration = $this->getConfiguration();
|
||||
|
||||
if ($configuration->getIsSortableCollection($context)) {
|
||||
$query->orderBy(sprintf('.%s', $configuration->getSortableCollectionProperty()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultSort = $configuration->getDefaultSort($context);
|
||||
|
||||
$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';
|
||||
}
|
||||
|
||||
foreach ($configuration->getFields($context) as $label => $field) {
|
||||
$sortOption = $field['options']['sort'] ?? null;
|
||||
|
||||
if (null === $sortOption) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sortOption[0] !== $name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sorter = $sortOption[1];
|
||||
|
||||
if (is_string($sorter)) {
|
||||
$query->orderBy($sorter, $direction);
|
||||
} else {
|
||||
call_user_func_array($sorter, [$query, $direction]);
|
||||
}
|
||||
|
||||
$this->sort = [
|
||||
'label' => $label,
|
||||
'direction' => $direction,
|
||||
];
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
38
src/core/Controller/Analytic/AnalyticController.php
Normal file
38
src/core/Controller/Analytic/AnalyticController.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Analytic;
|
||||
|
||||
use App\Core\Analytic\DateRangeAnalytic;
|
||||
use App\Core\Entity\Site\Node;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/admin/analytic")
|
||||
*/
|
||||
class AnalyticController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @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'])) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$analytic
|
||||
->setDateRange(new \DateTime('now - '.$range), new \DateTime())
|
||||
->setNode($node)
|
||||
;
|
||||
|
||||
return $this->render('@Core/analytic/stats.html.twig', [
|
||||
'range' => $range,
|
||||
'views' => $analytic->getViews(),
|
||||
'pathViews' => $analytic->getPathViews(),
|
||||
'referers' => $analytic->getReferers(),
|
||||
'node' => $node,
|
||||
]);
|
||||
}
|
||||
}
|
155
src/core/Controller/Auth/AuthController.php
Normal file
155
src/core/Controller/Auth/AuthController.php
Normal file
|
@ -0,0 +1,155 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Auth;
|
||||
|
||||
use App\Core\Event\Account\PasswordRequestEvent;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Repository\UserRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
use ZxcvbnPhp\Zxcvbn;
|
||||
|
||||
class AuthController extends AbstractController
|
||||
{
|
||||
protected array $coreParameters;
|
||||
|
||||
public function __construct(ParameterBagInterface $parameters)
|
||||
{
|
||||
$this->coreParameters = $parameters->get('core');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/login", name="auth_login")
|
||||
*/
|
||||
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||
{
|
||||
if ($this->getUser()) {
|
||||
return $this->redirectToRoute('admin_dashboard_index');
|
||||
}
|
||||
|
||||
$error = $authenticationUtils->getLastAuthenticationError();
|
||||
$lastUsername = $authenticationUtils->getLastUsername();
|
||||
|
||||
return $this->render('@Core/auth/login.html.twig', [
|
||||
'last_username' => $lastUsername,
|
||||
'error' => $error,
|
||||
'site_name' => $this->coreParameters['site']['name'],
|
||||
'site_logo' => $this->coreParameters['site']['logo'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/resetting/request", name="auth_resetting_request")
|
||||
*/
|
||||
public function requestResetting(Request $request, UserRepository $repository, EventDispatcherInterface $eventDispatcher): Response
|
||||
{
|
||||
if ($this->getUser()) {
|
||||
return $this->redirectToRoute('admin_dashboard_index');
|
||||
}
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$csrfToken = $request->request->get('_csrf_token');
|
||||
|
||||
if (!$this->isCsrfTokenValid('resetting_request', $csrfToken)) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$username = trim((string) $request->request->get('username'));
|
||||
|
||||
if (!$username) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$account = $repository->findOneByEmail($username);
|
||||
|
||||
if ($account) {
|
||||
$requestedAt = $account->getPasswordRequestedAt();
|
||||
|
||||
if (null === $requestedAt || $requestedAt->getTimestamp() < (time() - 3600 / 2)) {
|
||||
$eventDispatcher->dispatch(new PasswordRequestEvent($account), PasswordRequestEvent::EVENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('@Core/auth/resetting_request.html.twig', [
|
||||
'email_sent' => $request->isMethod('POST'),
|
||||
'site_name' => $this->coreParameters['site']['name'],
|
||||
'site_logo' => $this->coreParameters['site']['logo'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/resetting/update/{token}", name="auth_resetting_update")
|
||||
*/
|
||||
public function requestUpdate(
|
||||
string $token,
|
||||
Request $request,
|
||||
UserRepository $repository,
|
||||
TokenGeneratorInterface $tokenGenerator,
|
||||
UserPasswordEncoderInterface $encoder,
|
||||
EntityManager $entityManager
|
||||
): Response {
|
||||
if ($this->getUser()) {
|
||||
return $this->redirectToRoute('admin_dashboard_index');
|
||||
}
|
||||
|
||||
$account = $repository->findOneByConfirmationToken($token);
|
||||
$passwordUpdated = false;
|
||||
$expired = true;
|
||||
|
||||
if ($account) {
|
||||
$requestedAt = $account->getPasswordRequestedAt();
|
||||
$expired = (null === $requestedAt || ($requestedAt->getTimestamp() < (time() - 3600 * 2)));
|
||||
}
|
||||
|
||||
if ($request->isMethod('POST') && !$expired) {
|
||||
$csrfToken = $request->request->get('_csrf_token');
|
||||
|
||||
if ($this->isCsrfTokenValid('resetting_update', $csrfToken)) {
|
||||
$password = $request->request->get('password');
|
||||
$password2 = $request->request->get('password2');
|
||||
|
||||
$zxcvbn = new Zxcvbn();
|
||||
$strength = $zxcvbn->passwordStrength($password, []);
|
||||
|
||||
if (4 === $strength['score'] && $password === $password2) {
|
||||
$account
|
||||
->setPassword($encoder->encodePassword(
|
||||
$account,
|
||||
$password
|
||||
))
|
||||
->setConfirmationToken($tokenGenerator->generateToken())
|
||||
->setPasswordRequestedAt(new \DateTime('now'))
|
||||
;
|
||||
|
||||
$entityManager->update($account);
|
||||
|
||||
$passwordUpdated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('@Core/auth/resetting_update.html.twig', [
|
||||
'password_updated' => $passwordUpdated,
|
||||
'token' => $token,
|
||||
'expired' => $expired,
|
||||
'site_name' => $this->coreParameters['site']['name'],
|
||||
'site_logo' => $this->coreParameters['site']['logo'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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');
|
||||
}
|
||||
}
|
27
src/core/Controller/Dashboard/DashboardAdminController.php
Normal file
27
src/core/Controller/Dashboard/DashboardAdminController.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
438
src/core/Controller/FileManager/FileManagerAdminController.php
Normal file
438
src/core/Controller/FileManager/FileManagerAdminController.php
Normal file
|
@ -0,0 +1,438 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\FileManager;
|
||||
|
||||
use App\Core\Controller\Admin\AdminController;
|
||||
use App\Core\FileManager\FsFileManager;
|
||||
use App\Core\Form\FileManager\DirectoryCreateType;
|
||||
use App\Core\Form\FileManager\DirectoryRenameType;
|
||||
use App\Core\Form\FileManager\FileInformationType;
|
||||
use App\Core\Form\FileManager\FileRenameType;
|
||||
use App\Core\Form\FileManager\FileUploadType;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @Route("/admin/file_manager")
|
||||
*/
|
||||
class FileManagerAdminController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @Route("/", name="admin_file_manager_index")
|
||||
*/
|
||||
public function index(): Response
|
||||
{
|
||||
return $this->render('@Core/file_manager/index.html.twig');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/api/directory", name="admin_file_manager_api_directory", options={"expose"=true})
|
||||
*/
|
||||
public function directory(FsFileManager $manager, Request $request): Response
|
||||
{
|
||||
$options = [
|
||||
'sort' => $request->query->get('_sort', 'name'),
|
||||
'sort_direction' => $request->query->get('_sort_direction', 'asc'),
|
||||
];
|
||||
|
||||
$files = $manager->list($request->query->get('directory', '/'), $options);
|
||||
|
||||
return $this->json($files);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/info/{tab}/{context}/{ajax}", name="admin_file_manager_info", options={"expose"=true})
|
||||
*/
|
||||
public function info(
|
||||
FsFileManager $manager,
|
||||
Request $request,
|
||||
EntityManager $entityManager,
|
||||
TranslatorInterface $translator,
|
||||
string $context = 'crud',
|
||||
string $tab = 'information',
|
||||
bool $ajax = false
|
||||
): Response {
|
||||
$splInfo = $manager->getSplInfo($request->query->get('file'));
|
||||
|
||||
if (!$splInfo) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
$fileInfo = $manager->getFileInformation($request->query->get('file'));
|
||||
$path = $manager->getPathUri().'/'.$splInfo->getRelativePathname();
|
||||
|
||||
$form = $this->createForm(FileInformationType::class, $fileInfo);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$entityManager->update($fileInfo);
|
||||
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 0,
|
||||
'_message' => $translator->trans('The data has been saved.'),
|
||||
'_level' => 'success',
|
||||
'_dispatch' => 'file_manager.info.update.success',
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 1,
|
||||
'_message' => $translator->trans('The form is not valid.'),
|
||||
'_level' => 'warning',
|
||||
'_dispatch' => 'file_manager.info.update.error',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'data-modal' => $this->generateUrl('admin_file_manager_info', [
|
||||
'file' => $request->query->get('file'),
|
||||
'tab' => 'attributes',
|
||||
]),
|
||||
'path' => $splInfo->getRelativePath(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('@Core/file_manager/info.html.twig', [
|
||||
'splInfo' => $splInfo,
|
||||
'path' => $path,
|
||||
'isLocked' => $manager->isLocked($splInfo->getRelativePathname()),
|
||||
'tab' => $tab,
|
||||
'form' => $form->createView(),
|
||||
'context' => $context,
|
||||
'ajax' => $ajax,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'));
|
||||
|
||||
if (!$splInfo) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if (!$splInfo->isDir()) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ($manager->isLocked($request->query->get('file'))) {
|
||||
return $this->render('@Core/file_manager/directory_new.html.twig', [
|
||||
'locked' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$form = $this->createForm(DirectoryCreateType::class);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$status = $manager->createDirectory($form->get('name')->getData(), $request->query->get('file'));
|
||||
|
||||
if (true === $status) {
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('success', 'Directory created.');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 0,
|
||||
'_message' => $translator->trans('Directory created.'),
|
||||
'_level' => 'success',
|
||||
'_dispatch' => 'file_manager.directory.new.success',
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('warning', 'Directory not created.');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 1,
|
||||
'_message' => $translator->trans('Directory not created.'),
|
||||
'_level' => 'warning',
|
||||
'_dispatch' => 'file_manager.directory.new.error',
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('warning', 'Unauthorized char(s).');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'path' => $splInfo->getRelativePathname(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('@Core/file_manager/directory_new.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'file' => $request->query->get('file'),
|
||||
'ajax' => $ajax,
|
||||
'locked' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'));
|
||||
|
||||
if (!$splInfo) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if (!$splInfo->isDir()) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ($manager->isLocked($request->query->get('file'))) {
|
||||
return $this->render('@Core/file_manager/directory_rename.html.twig', [
|
||||
'locked' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$form = $this->createForm(DirectoryRenameType::class, [
|
||||
'name' => $splInfo->getFilename(),
|
||||
]);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$status = $manager->renameDirectory($form->get('name')->getData(), $request->query->get('file'));
|
||||
|
||||
if (true === $status) {
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('success', 'Directory renamed.');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 0,
|
||||
'_message' => $translator->trans('Directory renamed.'),
|
||||
'_level' => 'success',
|
||||
'_dispatch' => 'file_manager.directory.rename.success',
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('warning', 'Directory not renamed.');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 1,
|
||||
'_message' => $translator->trans('Directory not renamed.'),
|
||||
'_level' => 'warning',
|
||||
'_dispatch' => 'file_manager.directory.rename.error',
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('warning', 'Unauthorized char(s).');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'path' => $splInfo->getRelativePath(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('@Core/file_manager/directory_rename.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'file' => $request->query->get('file'),
|
||||
'locked' => false,
|
||||
'ajax' => $ajax,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'));
|
||||
|
||||
if (!$splInfo) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ($splInfo->isDir()) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ($manager->isLocked($request->query->get('file'))) {
|
||||
return $this->render('@Core/file_manager/file_rename.html.twig', [
|
||||
'locked' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$form = $this->createForm(FileRenameType::class, [
|
||||
'name' => preg_replace(sprintf('/\.%s/', $splInfo->getExtension()), '', $splInfo->getFilename()),
|
||||
]);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$status = $manager->renameFile($form->get('name')->getData(), $request->query->get('file'));
|
||||
|
||||
if (true === $status) {
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('success', 'File renamed.');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 0,
|
||||
'_message' => $translator->trans('File renamed.'),
|
||||
'_level' => 'success',
|
||||
'_dispatch' => 'file_manager.file.rename.success',
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('warning', 'File not renamed.');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 1,
|
||||
'_message' => $translator->trans('File not renamed.'),
|
||||
'_level' => 'warning',
|
||||
'_dispatch' => 'file_manager.file.rename.error',
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->addFlash('warning', 'Unauthorized char(s).');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'path' => $splInfo->getRelativePath(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('@Core/file_manager/file_rename.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'file' => $request->query->get('file'),
|
||||
'exention' => $splInfo->getExtension(),
|
||||
'locked' => false,
|
||||
'ajax' => $ajax,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'));
|
||||
|
||||
if (!$splInfo) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if (!$splInfo->isDir()) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
if ($manager->isLocked($request->query->get('file'))) {
|
||||
return $this->render('@Core/file_manager/upload.html.twig', [
|
||||
'locked' => true,
|
||||
]);
|
||||
}
|
||||
$form = $this->createForm(FileUploadType::class, null, [
|
||||
'mimes' => $manager->getMimes(),
|
||||
]);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
if ($form->get('files')->getData()) {
|
||||
$manager->upload(
|
||||
$form->get('files')->getData(),
|
||||
$request->query->get('file')
|
||||
);
|
||||
}
|
||||
|
||||
if ($form->get('directory')->getData()) {
|
||||
$manager->upload(
|
||||
$form->get('directory')->getData(),
|
||||
$request->query->get('file'),
|
||||
$_FILES['file_upload']['full_path']['directory'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('success', 'Files uploaded.');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 0,
|
||||
'_message' => $translator->trans('Files uploaded.'),
|
||||
'_level' => 'success',
|
||||
'_dispatch' => 'file_manager.file.new.success',
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if (!$request->isXmlHttpRequest()) {
|
||||
$this->addFlash('warning', 'Unauthorized file type(s).');
|
||||
} else {
|
||||
return $this->json([
|
||||
'_error' => 1,
|
||||
'_message' => $translator->trans('Unauthorized file type(s).'),
|
||||
'_level' => 'warning',
|
||||
'_dispatch' => 'file_manager.file.new.error',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'path' => $splInfo->getRelativePathname(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->render('@Core/file_manager/upload.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'file' => $request->query->get('file'),
|
||||
'locked' => false,
|
||||
'ajax' => $ajax,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/delete", name="admin_file_manager_delete", methods={"DELETE"})
|
||||
*/
|
||||
public function delete(FsFileManager $manager, Request $request): Response
|
||||
{
|
||||
$path = $request->request->get('file');
|
||||
$splInfo = $manager->getSplInfo($request->request->get('file'));
|
||||
|
||||
if (!$splInfo) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
if ($this->isCsrfTokenValid('delete', $request->request->get('_token'))) {
|
||||
if ($manager->delete($path)) {
|
||||
$this->addFlash('success', 'The data has been removed.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'The data has not been removed.');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_file_manager_index', [
|
||||
'path' => $splInfo->getRelativePath(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'file_manager';
|
||||
}
|
||||
}
|
154
src/core/Controller/Redirect/RedirectAdminController.php
Normal file
154
src/core/Controller/Redirect/RedirectAdminController.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Redirect;
|
||||
|
||||
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\Redirect as Entity;
|
||||
use App\Core\Factory\RedirectFactory as Factory;
|
||||
use App\Core\Form\Filter\RedirectFilterType as FilterType;
|
||||
use App\Core\Form\RedirectType as Type;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Core\Repository\RedirectRepositoryQuery as RepositoryQuery;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class RedirectAdminController extends CrudController
|
||||
{
|
||||
/**
|
||||
* @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("/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("/admin/redirect/show/{entity}", name="admin_redirect_show", methods={"GET"})
|
||||
*/
|
||||
public function show(Entity $entity): Response
|
||||
{
|
||||
return $this->doShow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/redirect/filter", name="admin_redirect_filter", methods={"GET"})
|
||||
*/
|
||||
public function filter(Session $session): Response
|
||||
{
|
||||
return $this->doFilter($session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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("/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("/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("/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);
|
||||
}
|
||||
|
||||
protected function getConfiguration(): CrudConfiguration
|
||||
{
|
||||
return CrudConfiguration::create()
|
||||
->setPageTitle('index', 'Redirects')
|
||||
->setPageTitle('edit', '{label}')
|
||||
->setPageTitle('new', 'New redirect')
|
||||
|
||||
->setPageRoute('index', 'admin_redirect_index')
|
||||
->setPageRoute('new', 'admin_redirect_new')
|
||||
->setPageRoute('edit', 'admin_redirect_edit')
|
||||
->setPageRoute('sort', 'admin_redirect_sort')
|
||||
->setPageRoute('batch', 'admin_redirect_batch')
|
||||
->setPageRoute('delete', 'admin_redirect_delete')
|
||||
->setPageRoute('filter', 'admin_redirect_filter')
|
||||
|
||||
->setForm('edit', Type::class, [])
|
||||
->setForm('new', Type::class)
|
||||
->setForm('filter', FilterType::class)
|
||||
|
||||
->setView('form', '@Core/redirect/redirect_admin/_form.html.twig')
|
||||
->setMaxPerPage('index', 100)
|
||||
->setIsSortableCollection('index', true)
|
||||
|
||||
->setAction('index', 'show', false)
|
||||
->setAction('edit', 'show', false)
|
||||
|
||||
->setField('index', 'Label', Field\TextField::class, [
|
||||
'property' => 'label',
|
||||
'attr' => ['class' => 'col-4'],
|
||||
])
|
||||
->setField('index', 'Rule', Field\TextField::class, [
|
||||
'view' => '@Core/redirect/redirect_admin/field/rule.html.twig',
|
||||
'attr' => ['class' => 'col-6'],
|
||||
])
|
||||
->setField('index', 'Enabled', Field\ButtonField::class, [
|
||||
'property_builder' => function(EntityInterface $entity) {
|
||||
return $entity->getIsEnabled() ? 'Yes' : 'No';
|
||||
},
|
||||
'attr' => ['class' => 'col-1'],
|
||||
'button_attr_builder' => function(EntityInterface $entity) {
|
||||
return ['class' => 'btn btn-sm btn-'.($entity->getIsEnabled() ? 'success' : 'primary')];
|
||||
},
|
||||
])
|
||||
->setField('index', 'Type', Field\ButtonField::class, [
|
||||
'property' => 'redirectCode',
|
||||
'attr' => ['class' => 'col-1'],
|
||||
'button_attr' => ['class' => 'btn btn-sm btn-light border-secondary font-weight-bold'],
|
||||
])
|
||||
->setBatchAction('index', 'enable', 'Enable', function (EntityInterface $entity, EntityManager $manager) {
|
||||
$entity->setIsEnabled(true);
|
||||
|
||||
$manager->update($entity);
|
||||
})
|
||||
->setBatchAction('index', 'disable', 'Disable', function (EntityInterface $entity, EntityManager $manager) {
|
||||
$entity->setIsEnabled(false);
|
||||
|
||||
$manager->update($entity);
|
||||
})
|
||||
->setBatchAction('index', 'delete', 'Delete', function (EntityInterface $entity, EntityManager $manager) {
|
||||
$manager->delete($entity);
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'site_navigation';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Setting;
|
||||
|
||||
use App\Core\Controller\Admin\AdminController;
|
||||
use App\Core\Entity\NavigationSetting as Entity;
|
||||
use App\Core\Event\Setting\NavigationSettingEvent;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/admin/navigation_setting")
|
||||
*/
|
||||
class NavigationSettingAdminController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @Route("/edit/{entity}", name="admin_navigation_setting_edit")
|
||||
*/
|
||||
public function edit(
|
||||
Entity $entity,
|
||||
EntityManager $entityManager,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
Request $request
|
||||
): Response {
|
||||
$builder = $this->createFormBuilder($entity);
|
||||
$event = new NavigationSettingEvent([
|
||||
'builder' => $builder,
|
||||
'entity' => $entity,
|
||||
'options' => [],
|
||||
]);
|
||||
|
||||
$eventDispatcher->dispatch($event, NavigationSettingEvent::FORM_INIT_EVENT);
|
||||
|
||||
$form = $builder->getForm();
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$entityManager->update($entity);
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
|
||||
return $this->redirectToRoute('admin_site_navigation_show', [
|
||||
'entity' => $entity->getNavigation()->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
}
|
||||
|
||||
return $this->render('@Core/setting/navigation_setting_admin/edit.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'entity' => $entity,
|
||||
'options' => $event->getData()['options'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'))) {
|
||||
$entityManager->delete($entity);
|
||||
|
||||
$this->addFlash('success', 'The data has been removed.');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_site_navigation_show', [
|
||||
'entity' => $entity->getNavigation()->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getSection(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
99
src/core/Controller/Setting/SettingAdminController.php
Normal file
99
src/core/Controller/Setting/SettingAdminController.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Setting;
|
||||
|
||||
use App\Core\Controller\Admin\AdminController;
|
||||
use App\Core\Entity\Setting as Entity;
|
||||
use App\Core\Event\Setting\SettingEvent;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Core\Repository\SettingRepositoryQuery as RepositoryQuery;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/admin/setting")
|
||||
*/
|
||||
class SettingAdminController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @Route("/{page}", name="admin_setting_index", requirements={"page": "\d+"})
|
||||
*/
|
||||
public function index(
|
||||
RepositoryQuery $query,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
Request $request,
|
||||
int $page = 1
|
||||
): Response {
|
||||
$eventDispatcher->dispatch(new SettingEvent(), SettingEvent::INIT_EVENT);
|
||||
|
||||
$pager = $query
|
||||
->orderBy('.section, .label')
|
||||
->paginate($page)
|
||||
;
|
||||
|
||||
return $this->render('@Core/setting/setting_admin/index.html.twig', [
|
||||
'pager' => $pager,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/edit/{entity}", name="admin_setting_edit")
|
||||
*/
|
||||
public function edit(
|
||||
Entity $entity,
|
||||
EntityManager $entityManager,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
Request $request
|
||||
): Response {
|
||||
$builder = $this->createFormBuilder($entity);
|
||||
$event = new SettingEvent([
|
||||
'builder' => $builder,
|
||||
'entity' => $entity,
|
||||
'options' => [],
|
||||
]);
|
||||
|
||||
$eventDispatcher->dispatch($event, SettingEvent::FORM_INIT_EVENT);
|
||||
|
||||
$form = $builder->getForm();
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$entityManager->update($entity);
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
|
||||
return $this->redirectToRoute('admin_setting_index');
|
||||
}
|
||||
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
}
|
||||
|
||||
return $this->render('@Core/setting/setting_admin/edit.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'entity' => $entity,
|
||||
'options' => $event->getData()['options'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'))) {
|
||||
$entityManager->delete($entity);
|
||||
|
||||
$this->addFlash('success', 'The data has been removed.');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_setting_index');
|
||||
}
|
||||
|
||||
public function getSection(): string
|
||||
{
|
||||
return 'setting';
|
||||
}
|
||||
}
|
82
src/core/Controller/Site/MenuAdminController.php
Normal file
82
src/core/Controller/Site/MenuAdminController.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Site;
|
||||
|
||||
use App\Core\Controller\Admin\AdminController;
|
||||
use App\Core\Entity\Site\Menu as Entity;
|
||||
use App\Core\Entity\Site\Navigation;
|
||||
use App\Core\Factory\Site\MenuFactory as EntityFactory;
|
||||
use App\Core\Form\Site\MenuType as EntityType;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/admin/site/menu")
|
||||
*/
|
||||
class MenuAdminController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @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);
|
||||
$form = $this->createForm(EntityType::class, $entity);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$entityManager->create($entity);
|
||||
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||
'navigation' => $navigation->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$entityManager->update($entity);
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||
'navigation' => $entity->getNavigation()->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'))) {
|
||||
$entityManager->delete($entity);
|
||||
|
||||
$this->addFlash('success', 'The data has been removed.');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||
'navigation' => $entity->getNavigation()->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getSection(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
142
src/core/Controller/Site/NavigationAdminController.php
Normal file
142
src/core/Controller/Site/NavigationAdminController.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
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\Site\Navigation as Entity;
|
||||
use App\Core\Event\Setting\NavigationSettingEvent;
|
||||
use App\Core\Factory\Site\NavigationFactory as Factory;
|
||||
use App\Core\Form\Site\NavigationType as Type;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Core\Repository\NavigationSettingRepositoryQuery;
|
||||
use App\Core\Repository\Site\NavigationRepositoryQuery as RepositoryQuery;
|
||||
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;
|
||||
|
||||
class NavigationAdminController extends CrudController
|
||||
{
|
||||
/**
|
||||
* @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("/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("/admin/site/navigation/show/{entity}", name="admin_site_navigation_show", methods={"GET"})
|
||||
*/
|
||||
public function show(
|
||||
Entity $entity,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
NavigationSettingRepositoryQuery $settingQuery
|
||||
): Response {
|
||||
$eventDispatcher->dispatch(new NavigationSettingEvent([
|
||||
'navigation' => $entity,
|
||||
]), NavigationSettingEvent::INIT_EVENT);
|
||||
|
||||
$settings = $settingQuery
|
||||
->where('.navigation = :navigation')
|
||||
->orderBy('.section, .label')
|
||||
->setParameter(':navigation', $entity->getId())
|
||||
->paginate(1, 1000)
|
||||
;
|
||||
|
||||
$this->getConfiguration()->addViewData('show', 'settings', $settings);
|
||||
|
||||
return $this->doShow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/site/navigation/filter", name="admin_site_navigation_filter", methods={"GET"})
|
||||
*/
|
||||
public function filter(Session $session): Response
|
||||
{
|
||||
return $this->doFilter($session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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("/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("/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);
|
||||
}
|
||||
|
||||
protected function getConfiguration(): CrudConfiguration
|
||||
{
|
||||
return CrudConfiguration::create()
|
||||
->setPageTitle('index', 'Navigations')
|
||||
->setPageTitle('edit', '{label}')
|
||||
->setPageTitle('new', 'New navigation')
|
||||
->setPageTitle('show', '{label}')
|
||||
|
||||
->setPageRoute('index', 'admin_site_navigation_index')
|
||||
->setPageRoute('new', 'admin_site_navigation_new')
|
||||
->setPageRoute('edit', 'admin_site_navigation_edit')
|
||||
->setPageRoute('show', 'admin_site_navigation_show')
|
||||
->setPageRoute('sort', 'admin_site_navigation_sort')
|
||||
->setPageRoute('delete', 'admin_site_navigation_delete')
|
||||
->setPageRoute('filter', 'admin_site_navigation_filter')
|
||||
->setPageRoute('redirects', 'admin_redirect_index')
|
||||
|
||||
->setForm('edit', Type::class, [])
|
||||
->setForm('new', Type::class)
|
||||
|
||||
->setView('index', '@Core/site/navigation_admin/index.html.twig')
|
||||
->setView('show', '@Core/site/navigation_admin/show.html.twig')
|
||||
->setView('show_entity', '@Core/site/navigation_admin/_show.html.twig')
|
||||
->setView('form', '@Core/site/navigation_admin/_form.html.twig')
|
||||
|
||||
->setIsSortableCollection('index', true)
|
||||
|
||||
->setField('index', 'Label', Field\TextField::class, [
|
||||
'property' => 'label',
|
||||
'attr' => ['class' => 'miw-200'],
|
||||
])
|
||||
->setField('index', 'Domain', Field\ButtonField::class, [
|
||||
'property' => 'domain',
|
||||
'button_attr' => ['class' => 'btn btn-light'],
|
||||
'attr' => ['class' => 'miw-200'],
|
||||
])
|
||||
->setField('index', 'Locale', Field\ButtonField::class, [
|
||||
'property' => 'locale',
|
||||
'button_attr' => ['class' => 'btn btn-light'],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'site_navigation';
|
||||
}
|
||||
}
|
310
src/core/Controller/Site/NodeAdminController.php
Normal file
310
src/core/Controller/Site/NodeAdminController.php
Normal file
|
@ -0,0 +1,310 @@
|
|||
<?php
|
||||
|
||||
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;
|
||||
use App\Core\Event\EntityManager\EntityManagerEvent;
|
||||
use App\Core\Factory\Site\NodeFactory as EntityFactory;
|
||||
use App\Core\Factory\Site\Page\PageFactory;
|
||||
use App\Core\Form\Site\NodeMoveType;
|
||||
use App\Core\Form\Site\NodeType as EntityType;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Core\Repository\Site\NodeRepository;
|
||||
use App\Core\Site\ControllerLocator;
|
||||
use App\Core\Site\RoleLocator;
|
||||
use App\Core\Site\PageLocator;
|
||||
use App\Core\Sitemap\SitemapBuilder;
|
||||
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("/admin/site/node")
|
||||
*/
|
||||
class NodeAdminController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route("/new/{node}", name="admin_site_node_new")
|
||||
*/
|
||||
public function new(
|
||||
Node $node,
|
||||
EntityFactory $factory,
|
||||
PageFactory $pageFactory,
|
||||
EntityManager $entityManager,
|
||||
NodeRepository $nodeRepository,
|
||||
PageLocator $pageLocator,
|
||||
ControllerLocator $controllerLocator,
|
||||
RoleLocator $roleLocator,
|
||||
Request $request
|
||||
): Response {
|
||||
$entity = $factory->create($node->getMenu());
|
||||
$form = $this->createForm(EntityType::class, $entity, [
|
||||
'pages' => $pageLocator->getPages(),
|
||||
'controllers' => $controllerLocator->getControllers(),
|
||||
'roles' => $roleLocator->getRoles(),
|
||||
'navigation' => $node->getMenu()->getNavigation(),
|
||||
]);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$position = $form->get('position')->getData();
|
||||
|
||||
$parent = 'above' === $position ? $node : $node->getParent();
|
||||
$entity->setParent($parent);
|
||||
|
||||
if ('above' === $position) {
|
||||
$nodeRepository->persistAsLastChild($entity, $node);
|
||||
} else {
|
||||
if ('after' === $position) {
|
||||
$nodeRepository->persistAsNextSiblingOf($entity, $node);
|
||||
} elseif ('before' === $position) {
|
||||
$nodeRepository->persistAsPrevSiblingOf($entity, $node);
|
||||
}
|
||||
}
|
||||
|
||||
$this->handlePageAssociation(
|
||||
$form->get('pageAction')->getData(),
|
||||
$form->get('pageEntity')->getData(),
|
||||
$form->get('pageType')->getData(),
|
||||
$entity,
|
||||
$pageFactory,
|
||||
$pageLocator
|
||||
);
|
||||
|
||||
$entityManager->create($entity);
|
||||
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
|
||||
return $this->redirect($this->generateUrl('admin_site_tree_navigation', [
|
||||
'navigation' => $node->getMenu()->getNavigation()->getId(),
|
||||
'data-modal' => $this->generateUrl('admin_site_node_edit', ['entity' => $entity->getId()]),
|
||||
]).sprintf('#node-%d', $entity->getId()));
|
||||
}
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
|
||||
return $this->redirect($this->generateUrl('admin_site_tree_navigation', [
|
||||
'navigation' => $entity->getMenu()->getNavigation()->getId(),
|
||||
]).sprintf('#node-%d', $entity->getId()));
|
||||
}
|
||||
|
||||
return $this->render('@Core/site/node_admin/new.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'node' => $node,
|
||||
'entity' => $entity,
|
||||
'tab' => 'content',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/edit/{entity}/{tab}", name="admin_site_node_edit")
|
||||
*/
|
||||
public function edit(
|
||||
Entity $entity,
|
||||
EntityManager $entityManager,
|
||||
PageFactory $pageFactory,
|
||||
PageLocator $pageLocator,
|
||||
ControllerLocator $controllerLocator,
|
||||
RoleLocator $roleLocator,
|
||||
Request $request,
|
||||
string $tab = 'content'
|
||||
): Response {
|
||||
$form = $this->createForm(EntityType::class, $entity, [
|
||||
'pages' => $pageLocator->getPages(),
|
||||
'controllers' => $controllerLocator->getControllers(),
|
||||
'roles' => $roleLocator->getRoles(),
|
||||
'navigation' => $entity->getMenu()->getNavigation(),
|
||||
]);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$this->handlePageAssociation(
|
||||
$form->get('pageAction')->getData(),
|
||||
$form->get('pageEntity')->getData(),
|
||||
$form->get('pageType')->getData(),
|
||||
$entity,
|
||||
$pageFactory,
|
||||
$pageLocator
|
||||
);
|
||||
|
||||
$entityManager->update($entity);
|
||||
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
}
|
||||
|
||||
return $this->redirect($this->generateUrl('admin_site_tree_navigation', [
|
||||
'navigation' => $entity->getMenu()->getNavigation()->getId(),
|
||||
'data-modal' => $this->generateUrl('admin_site_node_edit', ['entity' => $entity->getId()]),
|
||||
]).sprintf('#node-%d', $entity->getId()));
|
||||
}
|
||||
|
||||
$page = $entity->getPage();
|
||||
|
||||
if ($page !== null) {
|
||||
$pageConfiguration = $pageLocator->getPages()[get_class($page)] ?? null;
|
||||
} else {
|
||||
$pageConfiguration = null;
|
||||
}
|
||||
|
||||
return $this->render('@Core/site/node_admin/edit.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'entity' => $entity,
|
||||
'page' => $page,
|
||||
'pageConfiguration' => $pageConfiguration,
|
||||
'tab' => $tab,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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', [
|
||||
'entity' => $entity,
|
||||
'urls' => $builder->getNodeUrls($entity),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/move/{entity}", name="admin_site_node_move")
|
||||
*/
|
||||
public function move(
|
||||
Entity $entity,
|
||||
EntityManager $entityManager,
|
||||
NodeRepository $nodeRepository,
|
||||
Request $request
|
||||
): Response {
|
||||
$form = $this->createForm(NodeMoveType::class, null, [
|
||||
'menu' => $entity->getMenu(),
|
||||
]);
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->get('node')->getData()->getId() === $entity->getId()) {
|
||||
$form->get('node')->addError(new FormError('Élement de référence invalide.'));
|
||||
}
|
||||
|
||||
if ($form->isValid()) {
|
||||
$position = $form->get('position')->getData();
|
||||
$node = $form->get('node')->getData();
|
||||
|
||||
$parent = 'above' === $position ? $node : $node->getParent();
|
||||
$entity->setParent($parent);
|
||||
|
||||
if ('above' === $position) {
|
||||
$nodeRepository->persistAsLastChild($entity, $node);
|
||||
$entityManager->flush();
|
||||
} else {
|
||||
if ('after' === $position) {
|
||||
$nodeRepository->persistAsNextSiblingOf($entity, $node);
|
||||
} elseif ('before' === $position) {
|
||||
$nodeRepository->persistAsPrevSiblingOf($entity, $node);
|
||||
}
|
||||
|
||||
$entityManager->flush();
|
||||
}
|
||||
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
} else {
|
||||
$this->addFlash('warning', 'The form is not valid.');
|
||||
}
|
||||
|
||||
return $this->redirect($this->generateUrl('admin_site_tree_navigation', [
|
||||
'navigation' => $entity->getMenu()->getNavigation()->getId(),
|
||||
]).sprintf('#node-%d', $entity->getId()));
|
||||
}
|
||||
|
||||
return $this->render('@Core/site/node_admin/move.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'entity' => $entity,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'))) {
|
||||
$entity->setIsVisible(!$entity->getIsVisible());
|
||||
|
||||
$entityManager->update($entity);
|
||||
|
||||
$this->addFlash('success', 'The data has been saved.');
|
||||
}
|
||||
|
||||
return $this->redirect($this->generateUrl('admin_site_tree_navigation', [
|
||||
'navigation' => $entity->getMenu()->getNavigation()->getId(),
|
||||
]).sprintf('#node-%d', $entity->getId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/delete/{entity}", name="admin_site_node_delete", methods={"DELETE"})
|
||||
*/
|
||||
public function delete(
|
||||
Entity $entity,
|
||||
NodeRepository $nodeRepository,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
Request $request
|
||||
): Response {
|
||||
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
|
||||
$eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::PRE_DELETE_EVENT);
|
||||
$nodeRepository->removeFromTree($entity);
|
||||
$nodeRepository->reorder($entity->getMenu()->getRootNode());
|
||||
$eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::DELETE_EVENT);
|
||||
|
||||
$this->addFlash('success', 'Donnée supprimée.');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||
'navigation' => $entity->getMenu()->getNavigation()->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function handlePageAssociation(
|
||||
string $pageAction,
|
||||
?Page $pageEntity,
|
||||
string $pageType,
|
||||
Entity $entity,
|
||||
PageFactory $pageFactory,
|
||||
PageLocator $pageLocator
|
||||
) {
|
||||
if ('new' === $pageAction) {
|
||||
$pageConfiguration = $pageLocator->getPage($pageType);
|
||||
$page = $pageFactory->create($pageType, $entity->getLabel());
|
||||
$page->setTemplate($pageConfiguration->getTemplates()[0]['file']);
|
||||
|
||||
$entity
|
||||
->setPage($page)
|
||||
->setAliasNode(null)
|
||||
;
|
||||
} elseif ('existing' === $pageAction) {
|
||||
if ($pageEntity) {
|
||||
$entity->setPage($pageEntity);
|
||||
} else {
|
||||
$this->addFlash('info', 'Aucun changement de page effectué.');
|
||||
}
|
||||
$entity->setAliasNode(null);
|
||||
} elseif ('alias' === $pageAction) {
|
||||
$entity->setPage(null);
|
||||
} elseif ('none' === $pageAction) {
|
||||
$entity
|
||||
->setPage(null)
|
||||
->setAliasNode(null)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
136
src/core/Controller/Site/PageAdminController.php
Normal file
136
src/core/Controller/Site/PageAdminController.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
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\Site\Page\Page as Entity;
|
||||
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\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("/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("/admin/site/page/show/{entity}", name="admin_site_page_show", methods={"GET"})
|
||||
*/
|
||||
public function show(Entity $entity): Response
|
||||
{
|
||||
return $this->doShow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/site/page/filter", name="admin_site_page_filter", methods={"GET"})
|
||||
*/
|
||||
public function filter(Session $session): Response
|
||||
{
|
||||
return $this->doFilter($session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/admin/site/page/edit/{entity}", name="admin_site_page_edit", methods={"GET", "POST"})
|
||||
*/
|
||||
public function edit(
|
||||
int $entity,
|
||||
EntityManager $entityManager,
|
||||
RepositoryQuery $repositoryQuery,
|
||||
PageLocator $pageLocator,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
Request $request
|
||||
): Response {
|
||||
$entity = $repositoryQuery->filterById($entity)->findOne();
|
||||
|
||||
$event = new PageEditEvent($entity);
|
||||
$eventDispatcher->dispatch($event, PageEditEvent::FORM_INIT_EVENT);
|
||||
|
||||
$this->getConfiguration()->setFormOptions('edit', [
|
||||
'page_configuration' => $pageLocator->getPage(get_class($entity)),
|
||||
'page_builder_options' => $event->getPageBuilderOptions(),
|
||||
]);
|
||||
|
||||
return $this->doEdit($entity, $entityManager, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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("/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);
|
||||
}
|
||||
|
||||
protected function getConfiguration(): CrudConfiguration
|
||||
{
|
||||
return CrudConfiguration::create()
|
||||
->setPageTitle('index', 'Pages')
|
||||
->setPageTitle('edit', '{name}')
|
||||
->setPageTitle('show', '{name}')
|
||||
|
||||
->setPageRoute('index', 'admin_site_page_index')
|
||||
->setPageRoute('edit', 'admin_site_page_edit')
|
||||
->setPageRoute('delete', 'admin_site_page_delete')
|
||||
->setPageRoute('filter', 'admin_site_page_filter')
|
||||
->setPageRoute('batch', 'admin_site_page_batch')
|
||||
|
||||
->setForm('edit', Type::class, [])
|
||||
->setForm('filter', FilterType::class)
|
||||
->setView('form', '@Core/site/page_admin/_form.html.twig')
|
||||
|
||||
->setAction('index', 'new', false)
|
||||
->setAction('index', 'show', false)
|
||||
->setAction('edit', 'show', false)
|
||||
|
||||
->setField('index', 'Name', Field\TextField::class, [
|
||||
'property' => 'name',
|
||||
'sort' => ['name', '.name'],
|
||||
'attr' => ['class' => 'col-4'],
|
||||
])
|
||||
->setField('index', 'Elements', Field\TextField::class, [
|
||||
'view' => '@Core/site/page_admin/fields/nodes.html.twig',
|
||||
'sort' => ['navigation', function (RepositoryQuery $query, $direction) {
|
||||
$query
|
||||
->leftJoin('.nodes', 'node')
|
||||
->leftJoin('node.menu', 'menu')
|
||||
->leftJoin('menu.navigation', 'navigation')
|
||||
->orderBy('navigation.label', $direction)
|
||||
;
|
||||
}],
|
||||
'attr' => ['class' => 'col-6'],
|
||||
])
|
||||
->setBatchAction('index', 'delete', 'Delete', function(EntityInterface $entity, EntityManager $manager) {
|
||||
$manager->delete($entity);
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'site_page';
|
||||
}
|
||||
}
|
57
src/core/Controller/Site/PageController.php
Normal file
57
src/core/Controller/Site/PageController.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Site;
|
||||
|
||||
use App\Core\Site\SiteRequest;
|
||||
use App\Core\Site\SiteStore;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class PageController extends AbstractController
|
||||
{
|
||||
protected SiteRequest $siteRequest;
|
||||
protected SiteStore $siteStore;
|
||||
|
||||
public function __construct(SiteRequest $siteRequest, SiteStore $siteStore)
|
||||
{
|
||||
$this->siteRequest = $siteRequest;
|
||||
$this->siteStore = $siteStore;
|
||||
}
|
||||
|
||||
public function show(): Response
|
||||
{
|
||||
if (!$this->siteRequest->getPage()) {
|
||||
throw $this->createNotFoundException();
|
||||
}
|
||||
|
||||
return $this->defaultRender($this->siteRequest->getPage()->getTemplate());
|
||||
}
|
||||
|
||||
protected function defaultRender(string $view, array $parameters = [], Response $response = null): Response
|
||||
{
|
||||
$parameters = array_merge($this->getDefaultRenderParameters(), $parameters);
|
||||
|
||||
if (null === $response) {
|
||||
$contentType = $this->siteRequest->getNode()->getContentType();
|
||||
|
||||
$response = new Response(null, 200, [
|
||||
'Content-Type' => $contentType ?? 'text/html',
|
||||
]);
|
||||
}
|
||||
|
||||
return parent::render($view, $parameters, $response);
|
||||
}
|
||||
|
||||
protected function getDefaultRenderParameters(): array
|
||||
{
|
||||
return [
|
||||
'_node' => $this->siteRequest->getNode(),
|
||||
'_page' => $this->siteRequest->getPage(),
|
||||
'_menu' => $this->siteRequest->getMenu(),
|
||||
'_navigation' => $this->siteRequest->getNavigation(),
|
||||
'_domain' => $this->siteRequest->getDomain(),
|
||||
'_locale' => $this->siteRequest->getNavigation()->getLocale(),
|
||||
'_store' => $this->siteStore,
|
||||
];
|
||||
}
|
||||
}
|
40
src/core/Controller/Site/SitemapController.php
Normal file
40
src/core/Controller/Site/SitemapController.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Site;
|
||||
|
||||
use App\Core\Repository\Site\NavigationRepositoryQuery;
|
||||
use App\Core\Sitemap\SitemapBuilder;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class SitemapController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route("/sitemap.xml", name="sitemap")
|
||||
*/
|
||||
public function sitemap(Request $request, NavigationRepositoryQuery $navigationRepositoryQuery, SitemapBuilder $builder): Response
|
||||
{
|
||||
$navigations = $navigationRepositoryQuery
|
||||
->whereDomain($request->getHost())
|
||||
->find()
|
||||
;
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($navigations as $navigation) {
|
||||
$items = array_merge(
|
||||
$items,
|
||||
$builder->build($navigation)
|
||||
);
|
||||
}
|
||||
|
||||
$response = new Response();
|
||||
$response->headers->set('Content-Type', 'text/xml');
|
||||
|
||||
return $this->render('@Core/site/sitemap/sitemap.xml.twig', [
|
||||
'items' => $items,
|
||||
], $response);
|
||||
}
|
||||
}
|
87
src/core/Controller/Site/TreeAdminController.php
Normal file
87
src/core/Controller/Site/TreeAdminController.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Site;
|
||||
|
||||
use App\Core\Controller\Admin\AdminController;
|
||||
use App\Core\Entity\Site\Navigation;
|
||||
use App\Core\Factory\Site\MenuFactory;
|
||||
use App\Core\Form\Site\MenuType;
|
||||
use App\Core\Repository\Site\NavigationRepositoryQuery;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/admin/site/tree")
|
||||
*/
|
||||
class TreeAdminController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @Route("/", name="admin_site_tree_index")
|
||||
*/
|
||||
public function index(NavigationRepositoryQuery $navigationQuery, Session $session): Response
|
||||
{
|
||||
$navigation = null;
|
||||
|
||||
if ($session->has('site_tree_last_navigation')) {
|
||||
$navigation = $navigationQuery->create()
|
||||
->filterById((int) $session->get('site_tree_last_navigation'))
|
||||
->findOne()
|
||||
;
|
||||
}
|
||||
|
||||
if (null === $navigation) {
|
||||
$navigation = $navigationQuery->create()
|
||||
->orderBy('.sortOrder')
|
||||
->findOne()
|
||||
;
|
||||
}
|
||||
|
||||
if (null === $navigation) {
|
||||
$this->addFlash('warning', 'You must add a navigation.');
|
||||
|
||||
return $this->redirectToRoute('admin_site_navigation_new');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||
'navigation' => $navigation->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/navigation/{navigation}", name="admin_site_tree_navigation")
|
||||
*/
|
||||
public function navigation(
|
||||
Navigation $navigation,
|
||||
NavigationRepositoryQuery $navigationQuery,
|
||||
MenuFactory $menuFactory,
|
||||
Session $session
|
||||
): Response {
|
||||
$navigations = $navigationQuery->create()
|
||||
->orderBy('.sortOrder')
|
||||
->find()
|
||||
;
|
||||
|
||||
$session->set('site_tree_last_navigation', $navigation->getId());
|
||||
|
||||
$forms = [
|
||||
'menu' => $this->createForm(MenuType::class, $menuFactory->create())->createView(),
|
||||
'menus' => [],
|
||||
];
|
||||
|
||||
foreach ($navigation->getMenus() as $menu) {
|
||||
$forms['menus'][$menu->getId()] = $this->createForm(MenuType::class, $menu)->createView();
|
||||
}
|
||||
|
||||
return $this->render('@Core/site/tree_admin/navigation.html.twig', [
|
||||
'navigation' => $navigation,
|
||||
'navigations' => $navigations,
|
||||
'forms' => $forms,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getSection(): string
|
||||
{
|
||||
return 'site_tree';
|
||||
}
|
||||
}
|
62
src/core/Controller/Task/TaskAdminController.php
Normal file
62
src/core/Controller/Task/TaskAdminController.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\Task;
|
||||
|
||||
use App\Core\Controller\Admin\AdminController;
|
||||
use App\Core\Event\Task\TaskInitEvent;
|
||||
use App\Core\Event\Task\TaskRunRequestedEvent;
|
||||
use SensioLabs\AnsiConverter\AnsiToHtmlConverter;
|
||||
use SensioLabs\AnsiConverter\Theme\SolarizedTheme;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* @Route("/admin/task")
|
||||
*/
|
||||
class TaskAdminController extends AdminController
|
||||
{
|
||||
/**
|
||||
* @Route("/", name="admin_task_index")
|
||||
*/
|
||||
public function index(EventDispatcherInterface $eventDispatcher): Response
|
||||
{
|
||||
$event = new TaskInitEvent();
|
||||
$eventDispatcher->dispatch($event, TaskInitEvent::INIT_EVENT);
|
||||
|
||||
return $this->render('@Core/task/task_admin/index.html.twig', [
|
||||
'pager' => $event->getTasks(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/run/{task}", name="admin_task_run", methods={"GET"})
|
||||
*/
|
||||
public function run(
|
||||
string $task,
|
||||
Request $request,
|
||||
EventDispatcherInterface $eventDispatcher
|
||||
): Response {
|
||||
if (!$this->isCsrfTokenValid('task_run', $request->query->get('_token'))) {
|
||||
throw $this->createAccessDeniedException();
|
||||
}
|
||||
|
||||
$output = new BufferedOutput();
|
||||
$event = new TaskRunRequestedEvent($task, $request->query, $output);
|
||||
$eventDispatcher->dispatch($event, TaskRunRequestedEvent::RUN_REQUEST_EVENT);
|
||||
|
||||
$converter = new AnsiToHtmlConverter(new SolarizedTheme());
|
||||
$content = $converter->convert($output->fetch());
|
||||
|
||||
return $this->render('@Core/task/task_admin/run.html.twig', [
|
||||
'output' => $content,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getSection(): string
|
||||
{
|
||||
return 'task';
|
||||
}
|
||||
}
|
131
src/core/Controller/User/UserAdminController.php
Normal file
131
src/core/Controller/User/UserAdminController.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Controller\User;
|
||||
|
||||
use App\Core\Controller\Admin\Crud\CrudController;
|
||||
use App\Core\Crud\CrudConfiguration;
|
||||
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\Entity\User as Entity;
|
||||
use App\Form\UserType as Type;
|
||||
use App\Repository\UserRepositoryQuery as RepositoryQuery;
|
||||
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\Security\TokenGenerator;
|
||||
|
||||
class UserAdminController extends CrudController
|
||||
{
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'))) {
|
||||
$eventDispatcher->dispatch(new PasswordRequestEvent($entity), PasswordRequestEvent::EVENT);
|
||||
|
||||
$this->addFlash('success', 'E-mail sent.');
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_user_edit', [
|
||||
'entity' => $entity->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getConfiguration(): CrudConfiguration
|
||||
{
|
||||
return CrudConfiguration::create()
|
||||
->setPageTitle('index', 'Users')
|
||||
->setPageTitle('edit', '{username}')
|
||||
->setPageTitle('new', 'New user')
|
||||
->setPageTitle('show', '{username}')
|
||||
|
||||
->setPageRoute('index', 'admin_user_index')
|
||||
->setPageRoute('new', 'admin_user_new')
|
||||
->setPageRoute('edit', 'admin_user_edit')
|
||||
->setPageRoute('show', 'admin_user_show')
|
||||
->setPageRoute('delete', 'admin_user_delete')
|
||||
->setPageRoute('filter', 'admin_user_filter')
|
||||
|
||||
->setForm('edit', Type::class, [])
|
||||
->setForm('new', Type::class)
|
||||
|
||||
->setView('form', '@Core/user/user_admin/_form.html.twig')
|
||||
->setView('index', '@Core/user/user_admin/index.html.twig')
|
||||
->setView('new', '@Core/user/user_admin/new.html.twig')
|
||||
->setView('show', '@Core/user/user_admin/show.html.twig')
|
||||
->setView('show_entity', '@Core/user/user_admin/_show.html.twig')
|
||||
->setView('edit', '@Core/user/user_admin/edit.html.twig')
|
||||
|
||||
->setDefaultSort('index', 'username')
|
||||
|
||||
->setField('index', 'E-mail', Field\TextField::class, [
|
||||
'property' => 'email',
|
||||
'sort' => ['email', '.email'],
|
||||
'attr' => ['class' => 'miw-200'],
|
||||
])
|
||||
->setField('index', 'Display name', Field\TextField::class, [
|
||||
'property' => 'displayName',
|
||||
'sort' => ['displayName', '.displayName'],
|
||||
'attr' => ['class' => 'miw-200'],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
protected function getSection(): string
|
||||
{
|
||||
return 'user';
|
||||
}
|
||||
}
|
330
src/core/Crud/CrudConfiguration.php
Normal file
330
src/core/Crud/CrudConfiguration.php
Normal file
|
@ -0,0 +1,330 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Crud;
|
||||
|
||||
use App\Core\Crud\Exception\CrudConfigurationException;
|
||||
|
||||
/**
|
||||
* class CrudConfiguration.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class CrudConfiguration
|
||||
{
|
||||
protected array $pageTitles = [];
|
||||
protected array $pageRoutes = [];
|
||||
protected array $pageRouteParams = [];
|
||||
protected array $actions = [];
|
||||
protected array $batchActions = [];
|
||||
protected array $actionTitles = [];
|
||||
protected array $forms = [];
|
||||
protected array $formOptions = [];
|
||||
protected array $views = [];
|
||||
protected array $viewDatas = [];
|
||||
protected array $fields = [];
|
||||
protected array $maxPerPage = [];
|
||||
protected array $locales = [];
|
||||
protected array $defaultSort = [];
|
||||
protected array $isSortableCollection = [];
|
||||
protected string $sortableCollectionProperty = 'sortOrder';
|
||||
protected ?string $defaultLocale = null;
|
||||
protected bool $showActions = true;
|
||||
|
||||
protected static $self;
|
||||
|
||||
public static function create()
|
||||
{
|
||||
if (null === self::$self) {
|
||||
self::$self = new self();
|
||||
}
|
||||
|
||||
return self::$self;
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setPageTitle(string $page, string $title): self
|
||||
{
|
||||
$this->pageTitles[$page] = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPageTitle(string $page, ?string $default = null): ?string
|
||||
{
|
||||
return $this->pageTitles[$page] ?? $default;
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setPageRoute(string $page, string $route): self
|
||||
{
|
||||
$this->pageRoutes[$page] = $route;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPageRoute(string $page): ?string
|
||||
{
|
||||
return $this->pageRoutes[$page];
|
||||
}
|
||||
|
||||
public function setPageRouteParams(string $page, array $params): self
|
||||
{
|
||||
$this->pageRouteParams[$page] = $params;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPageRouteParams(string $page): array
|
||||
{
|
||||
return $this->pageRouteParams[$page] ?? [];
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setForm(string $context, string $form, array $options = []): self
|
||||
{
|
||||
$this->forms[$context] = $form;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getForm(string $context): ?string
|
||||
{
|
||||
return $this->forms[$context] ?? null;
|
||||
}
|
||||
|
||||
public function setFormOptions(string $context, array $options = []): self
|
||||
{
|
||||
$this->formOptions[$context] = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFormOptions(string $context): array
|
||||
{
|
||||
return $this->formOptions[$context] ?? [];
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setAction(string $page, string $action, bool $enabled): self
|
||||
{
|
||||
if (!isset($this->actions[$page])) {
|
||||
$this->actions[$page] = [];
|
||||
}
|
||||
|
||||
$this->actions[$page][$action] = $enabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAction(string $page, string $action, bool $default = true)
|
||||
{
|
||||
return $this->actions[$page][$action] ?? $default;
|
||||
}
|
||||
|
||||
public function setBatchAction(string $page, string $action, string $label, callable $callback): self
|
||||
{
|
||||
if (!isset($this->batchActions[$page])) {
|
||||
$this->batchActions[$page] = [];
|
||||
}
|
||||
|
||||
$this->batchActions[$page][$action] = [
|
||||
'label' => $label,
|
||||
'callback' => $callback,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBatchActions(string $page)
|
||||
{
|
||||
return $this->batchActions[$page] ?? [];
|
||||
}
|
||||
|
||||
public function getBatchAction(string $page, string $action)
|
||||
{
|
||||
return $this->batchActions[$page][$action] ?? null;
|
||||
}
|
||||
|
||||
public function hasBatchAction(string $page)
|
||||
{
|
||||
return !empty($this->batchActions[$page]);
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setActionTitle(string $page, string $action, string $title): self
|
||||
{
|
||||
if (!isset($this->actionTitles[$page])) {
|
||||
$this->actionTitles[$page] = [];
|
||||
}
|
||||
|
||||
$this->actions[$page][$action] = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getActionTitle(string $page, string $action, ?string $default = null): ?string
|
||||
{
|
||||
return $this->actionTitles[$page][$action] ?? $default;
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setView(string $context, string $view): self
|
||||
{
|
||||
$this->views[$context] = $view;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getView(string $context, ?string $default = null)
|
||||
{
|
||||
if (null === $default) {
|
||||
$default = sprintf('@Core/admin/crud/%s.html.twig', $context);
|
||||
}
|
||||
|
||||
return $this->views[$context] ?? $default;
|
||||
}
|
||||
|
||||
public function addViewData(string $context, string $name, $value): self
|
||||
{
|
||||
if (!isset($this->viewDatas[$context])) {
|
||||
$this->viewDatas[$context] = [];
|
||||
}
|
||||
|
||||
$this->viewDatas[$context][$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setViewDatas(string $context, array $datas): self
|
||||
{
|
||||
foreach ($datas as $name => $value) {
|
||||
$this->addViewData($name, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViewDatas(string $context): array
|
||||
{
|
||||
return $this->viewDatas[$context] ?? [];
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setField(string $context, string $label, string $field, array $options): self
|
||||
{
|
||||
if (!isset($this->fields[$context])) {
|
||||
$this->fields[$context] = [];
|
||||
}
|
||||
|
||||
$this->fields[$context][$label] = [
|
||||
'field' => $field,
|
||||
'options' => $options,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFields(string $context): array
|
||||
{
|
||||
return $this->fields[$context] ?? [];
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setMaxPerPage(string $page, int $max)
|
||||
{
|
||||
$this->maxPerPage[$page] = $max;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMaxPerPage(string $page, int $default = 20)
|
||||
{
|
||||
return $this->maxPerPage[$page] ?? $default;
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setI18n(array $locales, string $defaultLocale): self
|
||||
{
|
||||
$this->locales = $locales;
|
||||
$this->defaultLocale = $defaultLocale;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocales(): array
|
||||
{
|
||||
return $this->locales;
|
||||
}
|
||||
|
||||
public function getDefaultLocale(): ?string
|
||||
{
|
||||
return $this->defaultLocale;
|
||||
}
|
||||
|
||||
public function isI18n(): bool
|
||||
{
|
||||
return !empty($this->locales);
|
||||
}
|
||||
|
||||
/* -- */
|
||||
|
||||
public function setDefaultSort(string $context, string $label, string $direction = 'asc'): self
|
||||
{
|
||||
$this->defaultSort[$context] = [
|
||||
'label' => $label,
|
||||
'direction' => $direction,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDefaultSort(string $context)
|
||||
{
|
||||
return $this->defaultSort[$context] ?? null;
|
||||
}
|
||||
|
||||
public function setIsSortableCollection(string $page, bool $isSortableCollection): self
|
||||
{
|
||||
$this->isSortableCollection[$page] = $isSortableCollection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsSortableCollection(string $page): bool
|
||||
{
|
||||
return $this->isSortableCollection[$page] ?? false;
|
||||
}
|
||||
|
||||
public function setSortableCollectionProperty(string $sortableCollectionProperty): self
|
||||
{
|
||||
$this->sortableCollectionProperty = $sortableCollectionProperty;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSortableCollectionProperty(): string
|
||||
{
|
||||
return $this->sortableCollectionProperty;
|
||||
}
|
||||
|
||||
public function setShowActions(bool $showActions): self
|
||||
{
|
||||
$this->showActions = $showActions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getShowActions(): bool
|
||||
{
|
||||
return $this->showActions;
|
||||
}
|
||||
}
|
12
src/core/Crud/Exception/CrudConfigurationException.php
Normal file
12
src/core/Crud/Exception/CrudConfigurationException.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Crud\Exception;
|
||||
|
||||
/**
|
||||
* class CrudConfigurationException.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class CrudConfigurationException extends \Exception
|
||||
{
|
||||
}
|
41
src/core/Crud/Field/ButtonField.php
Normal file
41
src/core/Crud/Field/ButtonField.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Crud\Field;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* class ButtonField.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class ButtonField extends Field
|
||||
{
|
||||
public function buildView(Environment $twig, $entity, array $options, ?string $locale = null)
|
||||
{
|
||||
if (isset($options['button_attr_builder']) && is_callable($options['button_attr_builder'])) {
|
||||
$options['button_attr'] = (array) call_user_func($options['button_attr_builder'], $entity, $options);
|
||||
}
|
||||
|
||||
return parent::buildView($twig, $entity, $options, $locale);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): OptionsResolver
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'view' => '@Core/admin/crud/field/button.html.twig',
|
||||
'button_attr' => [],
|
||||
'button_attr_builder' => null,
|
||||
'button_tag' => 'button',
|
||||
]);
|
||||
|
||||
$resolver->setAllowedTypes('button_attr', ['array']);
|
||||
$resolver->setAllowedTypes('button_tag', ['string']);
|
||||
$resolver->setAllowedTypes('button_attr_builder', ['null', 'callable']);
|
||||
|
||||
return $resolver;
|
||||
}
|
||||
}
|
25
src/core/Crud/Field/DateField.php
Normal file
25
src/core/Crud/Field/DateField.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Crud\Field;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* class DateField.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class DateField extends Field
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): OptionsResolver
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'view' => '@Core/admin/crud/field/date.html.twig',
|
||||
'format' => 'Y-m-d',
|
||||
]);
|
||||
|
||||
return $resolver;
|
||||
}
|
||||
}
|
25
src/core/Crud/Field/DatetimeField.php
Normal file
25
src/core/Crud/Field/DatetimeField.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Crud\Field;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* class DatetimeField.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class DatetimeField extends Field
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): OptionsResolver
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'view' => '@Core/admin/crud/field/date.html.twig',
|
||||
'format' => 'Y-m-d H:i:s',
|
||||
]);
|
||||
|
||||
return $resolver;
|
||||
}
|
||||
}
|
92
src/core/Crud/Field/Field.php
Normal file
92
src/core/Crud/Field/Field.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Crud\Field;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* class Field.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
abstract class Field
|
||||
{
|
||||
public function buildView(Environment $twig, $entity, array $options, ?string $locale = null)
|
||||
{
|
||||
return $twig->render($this->getView($options), [
|
||||
'entity' => $entity,
|
||||
'value' => $this->getValue($entity, $options, $locale),
|
||||
'options' => $options,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): OptionsResolver
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'property' => null,
|
||||
'property_builder' => null,
|
||||
'view' => null,
|
||||
'raw' => false,
|
||||
'sort' => null,
|
||||
'href' => null,
|
||||
'href_attr' => [],
|
||||
'attr' => [],
|
||||
]);
|
||||
|
||||
$resolver->setRequired('view');
|
||||
$resolver->setAllowedTypes('property', ['null', 'string']);
|
||||
$resolver->setAllowedTypes('view', 'string');
|
||||
$resolver->setAllowedTypes('attr', 'array');
|
||||
$resolver->setAllowedTypes('href', ['null', 'string', 'callable']);
|
||||
$resolver->setAllowedTypes('href_attr', 'array', 'callable');
|
||||
$resolver->setAllowedTypes('raw', 'boolean');
|
||||
$resolver->setAllowedTypes('property_builder', ['null', 'callable']);
|
||||
$resolver->setAllowedValues('sort', function($value) {
|
||||
if ($value === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!is_array($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$isValidParam1 = !empty($value[0]) && is_string($value[0]);
|
||||
$isValidParam2 = !empty($value[1]) && (is_string($value[1]) || is_callable($value[1]));
|
||||
|
||||
return $isValidParam1 && $isValidParam2;
|
||||
});
|
||||
|
||||
return $resolver;
|
||||
}
|
||||
|
||||
protected function getValue($entity, array $options, ?string $locale = null)
|
||||
{
|
||||
if (null !== $options['property']) {
|
||||
$propertyAccessor = PropertyAccess::createPropertyAccessorBuilder()->getPropertyAccessor();
|
||||
|
||||
try {
|
||||
$value = $propertyAccessor->getValue($entity, $options['property']);
|
||||
} catch (NoSuchPropertyException $e) {
|
||||
if (null !== $locale) {
|
||||
$value = $propertyAccessor->getValue($entity->translate($locale), $options['property']);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
} elseif (null !== $options['property_builder']) {
|
||||
$value = call_user_func($options['property_builder'], $entity, $options);
|
||||
} else {
|
||||
$value = null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function getView(array $options)
|
||||
{
|
||||
return $options['view'];
|
||||
}
|
||||
}
|
27
src/core/Crud/Field/ImageField.php
Normal file
27
src/core/Crud/Field/ImageField.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Crud\Field;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* class ImageField.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class ImageField extends Field
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): OptionsResolver
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'view' => '@Core/admin/crud/field/image.html.twig',
|
||||
'image_attr' => [],
|
||||
]);
|
||||
|
||||
$resolver->setAllowedTypes('image_attr', ['array']);
|
||||
|
||||
return $resolver;
|
||||
}
|
||||
}
|
24
src/core/Crud/Field/TextField.php
Normal file
24
src/core/Crud/Field/TextField.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Crud\Field;
|
||||
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* class TextField.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class TextField extends Field
|
||||
{
|
||||
public function configureOptions(OptionsResolver $resolver): OptionsResolver
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
$resolver->setDefaults([
|
||||
'view' => '@Core/admin/crud/field/text.html.twig',
|
||||
]);
|
||||
|
||||
return $resolver;
|
||||
}
|
||||
}
|
138
src/core/DependencyInjection/Configuration.php
Normal file
138
src/core/DependencyInjection/Configuration.php
Normal file
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
|
||||
use Symfony\Component\Config\Definition\ConfigurationInterface;
|
||||
|
||||
class Configuration implements ConfigurationInterface
|
||||
{
|
||||
public function getConfigTreeBuilder(): TreeBuilder
|
||||
{
|
||||
$defaultMimetypes = [
|
||||
'image/png',
|
||||
'image/jpg',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/svg+xml',
|
||||
'video/mp4',
|
||||
'audio/mpeg3',
|
||||
'audio/x-mpeg-3',
|
||||
'multipart/x-zip',
|
||||
'multipart/x-gzip',
|
||||
'application/pdf',
|
||||
'application/ogg',
|
||||
'application/zip',
|
||||
'application/rar',
|
||||
'application/x-rar-compressed',
|
||||
'application/x-zip-compressed',
|
||||
'application/tar',
|
||||
'application/x-tar',
|
||||
'application/x-bzip',
|
||||
'application/x-bzip2',
|
||||
'application/x-gzip',
|
||||
'application/octet-stream',
|
||||
'application/msword',
|
||||
'text/plain',
|
||||
'text/css',
|
||||
];
|
||||
|
||||
$defaultLocked = [
|
||||
'%kernel.project_dir%/public/uploads',
|
||||
];
|
||||
|
||||
$treeBuilder = new TreeBuilder('core');
|
||||
|
||||
$treeBuilder->getRootNode()
|
||||
->children()
|
||||
->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('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;
|
||||
}
|
||||
}
|
28
src/core/DependencyInjection/CoreExtension.php
Normal file
28
src/core/DependencyInjection/CoreExtension.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||
|
||||
class CoreExtension extends Extension
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
$configuration = $this->getConfiguration($configs, $container);
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
|
||||
$container->setParameter('core', $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfiguration(array $configs, ContainerBuilder $container)
|
||||
{
|
||||
return new Configuration();
|
||||
}
|
||||
}
|
59
src/core/Doctrine/Timestampable.php
Normal file
59
src/core/Doctrine/Timestampable.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Doctrine;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
trait Timestampable
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(name="created_at", type="datetime")
|
||||
*/
|
||||
protected $createdAt;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="updated_at", type="datetime")
|
||||
*/
|
||||
protected $updatedAt;
|
||||
|
||||
/**
|
||||
* @ORM\PrePersist
|
||||
*/
|
||||
public function onPrePersist(): void
|
||||
{
|
||||
$this->createdAt = new \DateTime();
|
||||
$this->updatedAt = new \DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @ORM\PreUpdate
|
||||
*/
|
||||
public function onPreUpdate(): void
|
||||
{
|
||||
$this->updatedAt = new \DateTime();
|
||||
}
|
||||
|
||||
public function setCreatedAt(?\DateTime $createdAt): self
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?\DateTime
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?\DateTime $updatedAt): self
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?\DateTime
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
}
|
0
src/core/Entity/.gitignore
vendored
Normal file
0
src/core/Entity/.gitignore
vendored
Normal file
103
src/core/Entity/Analytic/Referer.php
Normal file
103
src/core/Entity/Analytic/Referer.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Analytic;
|
||||
|
||||
use App\Core\Entity\Site\Node;
|
||||
use App\Repository\Entity\Analytic\NodeViewRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=ViewRepository::class)
|
||||
* @ORM\Table(name="analytic_referer")
|
||||
*/
|
||||
class Referer implements EntityInterface
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="analyticReferers")
|
||||
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
|
||||
*/
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer", options={"default"=0})
|
||||
*/
|
||||
protected $views = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="date")
|
||||
*/
|
||||
protected $date;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getNode(): ?Node
|
||||
{
|
||||
return $this->node;
|
||||
}
|
||||
|
||||
public function setNode(?Node $node): self
|
||||
{
|
||||
$this->node = $node;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUri(): ?string
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
public function setUri(string $uri): self
|
||||
{
|
||||
$this->uri = $uri;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViews(): ?int
|
||||
{
|
||||
return $this->views;
|
||||
}
|
||||
|
||||
public function setViews(int $views): self
|
||||
{
|
||||
$this->views = $views;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addView(): self
|
||||
{
|
||||
++$this->views;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDate(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setDate(\DateTimeInterface $date): self
|
||||
{
|
||||
$this->date = $date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
151
src/core/Entity/Analytic/View.php
Normal file
151
src/core/Entity/Analytic/View.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Analytic;
|
||||
|
||||
use App\Core\Entity\Site\Node;
|
||||
use App\Repository\Entity\Analytic\NodeViewRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=ViewRepository::class)
|
||||
* @ORM\Table(name="analytic_view")
|
||||
*/
|
||||
class View implements EntityInterface
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="analyticViews")
|
||||
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
|
||||
*/
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer", options={"default"=0})
|
||||
*/
|
||||
protected $views = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer", options={"default"=0})
|
||||
*/
|
||||
protected $desktopViews = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer", options={"default"=0})
|
||||
*/
|
||||
protected $mobileViews = 0;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="date")
|
||||
*/
|
||||
protected $date;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getNode(): ?Node
|
||||
{
|
||||
return $this->node;
|
||||
}
|
||||
|
||||
public function setNode(?Node $node): self
|
||||
{
|
||||
$this->node = $node;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPath(): ?string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function setPath(string $path): self
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getViews(): ?int
|
||||
{
|
||||
return $this->views;
|
||||
}
|
||||
|
||||
public function setViews(int $views): self
|
||||
{
|
||||
$this->views = $views;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addView(): self
|
||||
{
|
||||
++$this->views;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDesktopViews(): ?int
|
||||
{
|
||||
return $this->desktopViews;
|
||||
}
|
||||
|
||||
public function setDesktopViews(int $desktopViews): self
|
||||
{
|
||||
$this->desktopViews = $desktopViews;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addDesktopView(): self
|
||||
{
|
||||
++$this->desktopViews;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMobileViews(): ?int
|
||||
{
|
||||
return $this->mobileViews;
|
||||
}
|
||||
|
||||
public function setMobileViews(int $mobileViews): self
|
||||
{
|
||||
$this->mobileViews = $mobileViews;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addMobileView(): self
|
||||
{
|
||||
++$this->mobileViews;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDate(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setDate(\DateTimeInterface $date): self
|
||||
{
|
||||
$this->date = $date;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
7
src/core/Entity/EntityInterface.php
Normal file
7
src/core/Entity/EntityInterface.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity;
|
||||
|
||||
interface EntityInterface
|
||||
{
|
||||
}
|
48
src/core/Entity/FileInformation.php
Normal file
48
src/core/Entity/FileInformation.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity;
|
||||
|
||||
use App\Repository\Entity\FileInformationRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=FileInformationRepository::class)
|
||||
*/
|
||||
class FileInformation implements EntityInterface
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="NONE")
|
||||
* @ORM\Column(type="string", length=96, unique=true)
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
protected $attributes;
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(string $id): self
|
||||
{
|
||||
$this->id = $id;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAttributes()
|
||||
{
|
||||
return (array) json_decode($this->attributes, true);
|
||||
}
|
||||
|
||||
public function setAttributes($attributes): self
|
||||
{
|
||||
$this->attributes = json_encode($attributes);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
111
src/core/Entity/NavigationSetting.php
Normal file
111
src/core/Entity/NavigationSetting.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity;
|
||||
|
||||
use App\Core\Entity\Site\Navigation;
|
||||
use App\Core\Repository\NavigationSettingRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=NavigationSettingRepository::class)
|
||||
*/
|
||||
class NavigationSetting implements EntityInterface
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $section;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $code;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Navigation::class, inversedBy="navigationSettings")
|
||||
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
|
||||
*/
|
||||
protected $navigation;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getSection(): ?string
|
||||
{
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
public function setSection(string $section): self
|
||||
{
|
||||
$this->section = $section;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(string $label): self
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCode(): ?string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function setCode(string $code): self
|
||||
{
|
||||
$this->code = $code;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return json_decode($this->value, true);
|
||||
}
|
||||
|
||||
public function setValue($value): self
|
||||
{
|
||||
$this->value = json_encode($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNavigation(): ?Navigation
|
||||
{
|
||||
return $this->navigation;
|
||||
}
|
||||
|
||||
public function setNavigation(?Navigation $navigation): self
|
||||
{
|
||||
$this->navigation = $navigation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
211
src/core/Entity/Redirect.php
Normal file
211
src/core/Entity/Redirect.php
Normal file
|
@ -0,0 +1,211 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity;
|
||||
|
||||
use App\Core\Repository\RedirectRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=RedirectRepository::class)
|
||||
*/
|
||||
class Redirect implements EntityInterface
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=5)
|
||||
*/
|
||||
protected $scheme;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $domain;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=6)
|
||||
*/
|
||||
protected $domainType;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $rule;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=6)
|
||||
*/
|
||||
protected $ruleType;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $location;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $redirectCode;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer", nullable=true)
|
||||
*/
|
||||
protected $sortOrder;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $isEnabled;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $reuseQueryString;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getScheme(): ?string
|
||||
{
|
||||
return $this->scheme;
|
||||
}
|
||||
|
||||
public function setScheme(string $scheme): self
|
||||
{
|
||||
$this->scheme = $scheme;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDomain(): ?string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
public function setDomain(string $domain): self
|
||||
{
|
||||
$this->domain = $domain;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDomainType(): ?string
|
||||
{
|
||||
return $this->domainType;
|
||||
}
|
||||
|
||||
public function setDomainType(string $domainType): self
|
||||
{
|
||||
$this->domainType = $domainType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRule(): ?string
|
||||
{
|
||||
return $this->rule;
|
||||
}
|
||||
|
||||
public function setRule(string $rule): self
|
||||
{
|
||||
$this->rule = $rule;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRuleType(): ?string
|
||||
{
|
||||
return $this->ruleType;
|
||||
}
|
||||
|
||||
public function setRuleType(string $ruleType): self
|
||||
{
|
||||
$this->ruleType = $ruleType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocation(): ?string
|
||||
{
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
public function setLocation(string $location): self
|
||||
{
|
||||
$this->location = $location;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRedirectCode(): ?int
|
||||
{
|
||||
return $this->redirectCode;
|
||||
}
|
||||
|
||||
public function setRedirectCode(int $redirectCode): self
|
||||
{
|
||||
$this->redirectCode = $redirectCode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(string $label): self
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSortOrder(): ?int
|
||||
{
|
||||
return $this->sortOrder;
|
||||
}
|
||||
|
||||
public function setSortOrder(?int $sortOrder): self
|
||||
{
|
||||
$this->sortOrder = $sortOrder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsEnabled(): ?bool
|
||||
{
|
||||
return $this->isEnabled;
|
||||
}
|
||||
|
||||
public function setIsEnabled(bool $isEnabled): self
|
||||
{
|
||||
$this->isEnabled = $isEnabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getReuseQueryString(): ?bool
|
||||
{
|
||||
return $this->reuseQueryString;
|
||||
}
|
||||
|
||||
public function setReuseQueryString(bool $reuseQueryString): self
|
||||
{
|
||||
$this->reuseQueryString = $reuseQueryString;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
92
src/core/Entity/Setting.php
Normal file
92
src/core/Entity/Setting.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity;
|
||||
|
||||
use App\Core\Repository\SettingRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=SettingRepository::class)
|
||||
*/
|
||||
class Setting implements EntityInterface
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $section;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $code;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getSection(): ?string
|
||||
{
|
||||
return $this->section;
|
||||
}
|
||||
|
||||
public function setSection(string $section): self
|
||||
{
|
||||
$this->section = $section;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(string $label): self
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCode(): ?string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function setCode(string $code): self
|
||||
{
|
||||
$this->code = $code;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return json_decode($this->value, true);
|
||||
}
|
||||
|
||||
public function setValue($value): self
|
||||
{
|
||||
$this->value = json_encode($value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
146
src/core/Entity/Site/Menu.php
Normal file
146
src/core/Entity/Site/Menu.php
Normal file
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Site;
|
||||
|
||||
use App\Core\Doctrine\Timestampable;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Repository\Site\MenuRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=MenuRepository::class)
|
||||
* @ORM\HasLifecycleCallbacks
|
||||
*/
|
||||
class Menu implements EntityInterface
|
||||
{
|
||||
use Timestampable;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $code;
|
||||
|
||||
/**
|
||||
* @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"})
|
||||
*/
|
||||
protected $nodes;
|
||||
|
||||
/**
|
||||
* @ORM\OneToOne(targetEntity=Node::class, cascade={"persist"})
|
||||
* @ORM\JoinColumn(onDelete="CASCADE")
|
||||
*/
|
||||
protected $rootNode;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->nodes = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(string $label): self
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCode(): ?string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function setCode(string $code): self
|
||||
{
|
||||
$this->code = $code;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNavigation(): ?Navigation
|
||||
{
|
||||
return $this->navigation;
|
||||
}
|
||||
|
||||
public function setNavigation(?Navigation $navigation): self
|
||||
{
|
||||
$this->navigation = $navigation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|Node[]
|
||||
*/
|
||||
public function getNodes(): Collection
|
||||
{
|
||||
return $this->nodes;
|
||||
}
|
||||
|
||||
public function addNode(Node $node): self
|
||||
{
|
||||
if (!$this->nodes->contains($node)) {
|
||||
$this->nodes[] = $node;
|
||||
$node->setMenu($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeNode(Node $node): self
|
||||
{
|
||||
if ($this->nodes->removeElement($node)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($node->getMenu() === $this) {
|
||||
$node->setMenu(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRootNode(): ?Node
|
||||
{
|
||||
return $this->rootNode;
|
||||
}
|
||||
|
||||
public function setRootNode(?Node $rootNode): self
|
||||
{
|
||||
$this->rootNode = $rootNode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRouteName(): string
|
||||
{
|
||||
return $this->getNavigation()->getRouteName().'_'.($this->getCode() ? $this->getCode() : $this->getId());
|
||||
}
|
||||
}
|
243
src/core/Entity/Site/Navigation.php
Normal file
243
src/core/Entity/Site/Navigation.php
Normal file
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Site;
|
||||
|
||||
use App\Core\Doctrine\Timestampable;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Entity\NavigationSetting;
|
||||
use App\Core\Repository\Site\NavigationRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass=NavigationRepository::class)
|
||||
* @ORM\HasLifecycleCallbacks
|
||||
*/
|
||||
class Navigation implements EntityInterface
|
||||
{
|
||||
use Timestampable;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $code;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $domain;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean", options={"default"=0})
|
||||
*/
|
||||
protected $forceDomain = false;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
protected $additionalDomains = '[]';
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=Menu::class, mappedBy="navigation")
|
||||
*/
|
||||
protected $menus;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=10)
|
||||
*/
|
||||
protected $locale = 'en';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer", nullable=true)
|
||||
*/
|
||||
protected $sortOrder;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=NavigationSetting::class, mappedBy="navigation", orphanRemoval=true)
|
||||
*/
|
||||
protected $navigationSettings;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->menus = new ArrayCollection();
|
||||
$this->navigationSettings = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(string $label): self
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCode(): ?string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function setCode(string $code): self
|
||||
{
|
||||
$this->code = $code;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDomain(): ?string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
public function setDomain(string $domain): self
|
||||
{
|
||||
$this->domain = $domain;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getForceDomain(): ?bool
|
||||
{
|
||||
return $this->forceDomain;
|
||||
}
|
||||
|
||||
public function setForceDomain(bool $forceDomain): self
|
||||
{
|
||||
$this->forceDomain = $forceDomain;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAdditionalDomains(): array
|
||||
{
|
||||
return (array) json_decode($this->additionalDomains, true);
|
||||
}
|
||||
|
||||
public function setAdditionalDomains(array $additionalDomains): self
|
||||
{
|
||||
$this->additionalDomains = json_encode($additionalDomains);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|Menu[]
|
||||
*/
|
||||
public function getMenus(): Collection
|
||||
{
|
||||
return $this->menus;
|
||||
}
|
||||
|
||||
public function addMenu(Menu $menu): self
|
||||
{
|
||||
if (!$this->menus->contains($menu)) {
|
||||
$this->menus[] = $menu;
|
||||
$menu->setNavigation($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeMenu(Menu $menu): self
|
||||
{
|
||||
if ($this->menus->removeElement($menu)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($menu->getNavigation() === $this) {
|
||||
$menu->setNavigation(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMenu(string $code): ?Menu
|
||||
{
|
||||
foreach ($this->menus as $menu) {
|
||||
if ($menu->getCode() === $code) {
|
||||
return $menu;
|
||||
}
|
||||
}
|
||||
|
||||
return $menu;
|
||||
}
|
||||
|
||||
public function getRouteName(): string
|
||||
{
|
||||
return $this->getCode() ? $this->getCode() : 'navigation_'.$this->getId();
|
||||
}
|
||||
|
||||
public function getLocale(): ?string
|
||||
{
|
||||
return $this->locale;
|
||||
}
|
||||
|
||||
public function setLocale(string $locale): self
|
||||
{
|
||||
$this->locale = $locale;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSortOrder(): ?int
|
||||
{
|
||||
return $this->sortOrder;
|
||||
}
|
||||
|
||||
public function setSortOrder(?int $sortOrder): self
|
||||
{
|
||||
$this->sortOrder = $sortOrder;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|NavigationSetting[]
|
||||
*/
|
||||
public function getNavigationSettings(): Collection
|
||||
{
|
||||
return $this->navigationSettings;
|
||||
}
|
||||
|
||||
public function addNavigationSetting(NavigationSetting $navigationSetting): self
|
||||
{
|
||||
if (!$this->navigationSettings->contains($navigationSetting)) {
|
||||
$this->navigationSettings[] = $navigationSetting;
|
||||
$navigationSetting->setNavigation($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeNavigationSetting(NavigationSetting $navigationSetting): self
|
||||
{
|
||||
if ($this->navigationSettings->removeElement($navigationSetting)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($navigationSetting->getNavigation() === $this) {
|
||||
$navigationSetting->setNavigation(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
676
src/core/Entity/Site/Node.php
Normal file
676
src/core/Entity/Site/Node.php
Normal file
|
@ -0,0 +1,676 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Site;
|
||||
|
||||
use App\Core\Doctrine\Timestampable;
|
||||
use App\Core\Entity\Analytic\Referer;
|
||||
use App\Core\Entity\Analytic\View;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Entity\Site\Page\Page;
|
||||
use App\Core\Repository\Site\NodeRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
use function Symfony\Component\String\u;
|
||||
|
||||
/**
|
||||
* @Gedmo\Tree(type="nested")
|
||||
* @ORM\HasLifecycleCallbacks
|
||||
* @ORM\Entity(repositoryClass=NodeRepository::class)
|
||||
*/
|
||||
class Node implements EntityInterface
|
||||
{
|
||||
use Timestampable;
|
||||
|
||||
/**
|
||||
* @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")
|
||||
*/
|
||||
protected $menu;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean", options={"default"=0})
|
||||
*/
|
||||
protected $disableUrl = false;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean", options={"default"=0})
|
||||
*/
|
||||
protected $isVisible = false;
|
||||
|
||||
/**
|
||||
* @Gedmo\TreeLeft
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $treeLeft;
|
||||
|
||||
/**
|
||||
* @Gedmo\TreeLevel
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $treeLevel;
|
||||
|
||||
/**
|
||||
* @Gedmo\TreeRight
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $treeRight;
|
||||
|
||||
/**
|
||||
* @Gedmo\TreeRoot
|
||||
* @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")
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* @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")
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $code;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="array", nullable=true)
|
||||
*/
|
||||
protected $parameters = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="array", nullable=true)
|
||||
*/
|
||||
protected $attributes = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="array", nullable=true)
|
||||
*/
|
||||
protected $sitemapParameters = [];
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="aliasNodes")
|
||||
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
|
||||
*/
|
||||
protected $aliasNode;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="aliasNode")
|
||||
*/
|
||||
protected $aliasNodes;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $contentType;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean", options={"default"=0})
|
||||
*/
|
||||
protected $enableAnalytics = false;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=View::class, mappedBy="node")
|
||||
*/
|
||||
protected $analyticViews;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=Referer::class, mappedBy="node")
|
||||
*/
|
||||
protected $analyticReferers;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="array")
|
||||
*/
|
||||
private $securityRoles = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=3, options={"default"="or"})
|
||||
*/
|
||||
private $securityOperator = 'or';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->children = new ArrayCollection();
|
||||
$this->aliasNodes = new ArrayCollection();
|
||||
$this->analyticViews = new ArrayCollection();
|
||||
$this->analyticReferers = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getMenu(): ?Menu
|
||||
{
|
||||
return $this->menu;
|
||||
}
|
||||
|
||||
public function setMenu(?Menu $menu): self
|
||||
{
|
||||
$this->menu = $menu;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTreeLeft(): ?int
|
||||
{
|
||||
return $this->treeLeft;
|
||||
}
|
||||
|
||||
public function setTreeLeft(int $treeLeft): self
|
||||
{
|
||||
$this->treeLeft = $treeLeft;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTreeLevel(): ?int
|
||||
{
|
||||
return $this->treeLevel;
|
||||
}
|
||||
|
||||
public function setTreeLevel(int $treeLevel): self
|
||||
{
|
||||
$this->treeLevel = $treeLevel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTreeRight(): ?int
|
||||
{
|
||||
return $this->treeRight;
|
||||
}
|
||||
|
||||
public function setTreeRight(int $treeRight): self
|
||||
{
|
||||
$this->treeRight = $treeRight;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTreeRoot(): ?self
|
||||
{
|
||||
return $this->treeRoot;
|
||||
}
|
||||
|
||||
public function setTreeRoot(?self $treeRoot): self
|
||||
{
|
||||
$this->treeRoot = $treeRoot;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParent(): ?self
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function setParent(?self $parent): self
|
||||
{
|
||||
$this->parent = $parent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|Node[]
|
||||
*/
|
||||
public function getChildren(array $criteria = []): Collection
|
||||
{
|
||||
if (null === $this->children) {
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
|
||||
if (!empty($criteria)) {
|
||||
$children = new ArrayCollection();
|
||||
|
||||
foreach ($this->children as $child) {
|
||||
$add = true;
|
||||
|
||||
if (isset($criteria['visible']) && $child->getIsVisible() !== $criteria['visible']) {
|
||||
$add = false;
|
||||
}
|
||||
|
||||
if ($add) {
|
||||
$children->add($child);
|
||||
}
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function addChild(Node $child): self
|
||||
{
|
||||
if (!$this->children->contains($child)) {
|
||||
$this->children[] = $child;
|
||||
$child->setParent($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeChild(Node $child): self
|
||||
{
|
||||
if ($this->children->removeElement($child)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($child->getParent() === $this) {
|
||||
$child->setParent(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAllChildren(array $criteria = []): ArrayCollection
|
||||
{
|
||||
$children = [];
|
||||
|
||||
$getChildren = function (Node $node) use (&$children, &$getChildren) {
|
||||
foreach ($node->getChildren() as $nodeChildren) {
|
||||
$children[] = $nodeChildren;
|
||||
|
||||
$getChildren($nodeChildren);
|
||||
}
|
||||
};
|
||||
|
||||
$getChildren($this);
|
||||
|
||||
usort($children, function ($a, $b) {
|
||||
return $a->getTreeLeft() < $b->getTreeLeft() ? -1 : 1;
|
||||
});
|
||||
|
||||
if (!empty($criteria)) {
|
||||
foreach ($children as $key => $child) {
|
||||
if (isset($criteria['visible']) && $child->getIsVisible() !== $criteria['visible']) {
|
||||
unset($children[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayCollection($children);
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
public function setLabel(?string $label): self
|
||||
{
|
||||
$this->label = $label;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUrl(): ?string
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function setUrl(?string $url): self
|
||||
{
|
||||
$this->url = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasExternalUrl(): bool
|
||||
{
|
||||
$string = u($this->getUrl());
|
||||
|
||||
return $string->startsWith('http://') || $string->startsWith('https://');
|
||||
}
|
||||
|
||||
public function hasAppUrl(): bool
|
||||
{
|
||||
$string = u($this->getUrl());
|
||||
|
||||
foreach (['tel:', 'fax:', 'mailto:'] as $prefix) {
|
||||
if ($string->startsWith($prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getIsVisible(): ?bool
|
||||
{
|
||||
return $this->isVisible;
|
||||
}
|
||||
|
||||
public function setIsVisible(bool $isVisible): self
|
||||
{
|
||||
$this->isVisible = $isVisible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDisableUrl(): ?bool
|
||||
{
|
||||
return $this->disableUrl;
|
||||
}
|
||||
|
||||
public function setDisableUrl(bool $disableUrl): self
|
||||
{
|
||||
$this->disableUrl = $disableUrl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTreeLabel()
|
||||
{
|
||||
$prefix = str_repeat('-', ($this->getTreeLevel() - 1) * 5);
|
||||
|
||||
return trim($prefix.' '.$this->getLabel());
|
||||
}
|
||||
|
||||
public function getPage(): ?Page
|
||||
{
|
||||
if ($this->getAliasNode()) {
|
||||
return $this->getAliasNode()->getPage();
|
||||
}
|
||||
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function setPage(?Page $page): self
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRouteName(): string
|
||||
{
|
||||
if ($this->getAliasNode()) {
|
||||
return $this->getAliasNode()->getRouteName();
|
||||
}
|
||||
|
||||
return $this->getMenu()->getRouteName().'_'.($this->getCode() ? $this->getCode() : $this->getId());
|
||||
}
|
||||
|
||||
public function getCode(): ?string
|
||||
{
|
||||
if ($this->getAliasNode()) {
|
||||
return $this->getAliasNode()->getCode();
|
||||
}
|
||||
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function setCode(?string $code): self
|
||||
{
|
||||
$this->code = $code;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParameters(): ?array
|
||||
{
|
||||
if ($this->getAliasNode()) {
|
||||
return $this->getAliasNode()->getParameters();
|
||||
}
|
||||
|
||||
if (!is_array($this->parameters)) {
|
||||
$this->parameters = [];
|
||||
}
|
||||
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function setParameters(array $parameters): self
|
||||
{
|
||||
$this->parameters = $parameters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAttributes(): ?array
|
||||
{
|
||||
if (!is_array($this->attributes)) {
|
||||
$this->attributes = [];
|
||||
}
|
||||
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
public function setAttributes(array $attributes): self
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getController(): ?string
|
||||
{
|
||||
if ($this->getAliasNode()) {
|
||||
return $this->getAliasNode()->getController();
|
||||
}
|
||||
|
||||
return $this->controller;
|
||||
}
|
||||
|
||||
public function setController(?string $controller): self
|
||||
{
|
||||
$this->controller = $controller;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSitemapParameters(): ?array
|
||||
{
|
||||
if ($this->getAliasNode()) {
|
||||
return $this->getAliasNode()->getSitemapParameters();
|
||||
}
|
||||
|
||||
if (!is_array($this->sitemapParameters)) {
|
||||
$this->sitemapParameters = [
|
||||
'isVisible' => false,
|
||||
'priority' => 0,
|
||||
'changeFrequency' => 'daily',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->sitemapParameters;
|
||||
}
|
||||
|
||||
public function setSitemapParameters(?array $sitemapParameters): self
|
||||
{
|
||||
$this->sitemapParameters = $sitemapParameters;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAliasNode(): ?self
|
||||
{
|
||||
return $this->aliasNode;
|
||||
}
|
||||
|
||||
public function setAliasNode(?self $aliasNode): self
|
||||
{
|
||||
$this->aliasNode = $aliasNode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|self[]
|
||||
*/
|
||||
public function getAliasNodes(): Collection
|
||||
{
|
||||
return $this->aliasNodes;
|
||||
}
|
||||
|
||||
public function addAliasNode(self $aliasNode): self
|
||||
{
|
||||
if (!$this->aliasNodes->contains($aliasNode)) {
|
||||
$this->aliasNodes[] = $aliasNode;
|
||||
$aliasNode->setAliasNode($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAliasNode(self $aliasNode): self
|
||||
{
|
||||
if ($this->aliasNodes->removeElement($aliasNode)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($aliasNode->getAliasNode() === $this) {
|
||||
$aliasNode->setAliasNode(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContentType(): ?string
|
||||
{
|
||||
return $this->contentType;
|
||||
}
|
||||
|
||||
public function setContentType(?string $contentType): self
|
||||
{
|
||||
$this->contentType = $contentType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEnableAnalytics(): ?bool
|
||||
{
|
||||
return $this->enableAnalytics;
|
||||
}
|
||||
|
||||
public function setEnableAnalytics(bool $enableAnalytics): self
|
||||
{
|
||||
$this->enableAnalytics = $enableAnalytics;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|View[]
|
||||
*/
|
||||
public function getAnalyticViews(): Collection
|
||||
{
|
||||
return $this->analyticViews;
|
||||
}
|
||||
|
||||
public function addAnalyticView(View $view): self
|
||||
{
|
||||
if (!$this->analyticViews->contains($view)) {
|
||||
$this->analyticViews[] = $view;
|
||||
$view->setNode($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAnalyticView(View $view): self
|
||||
{
|
||||
if ($this->analyticViews->removeElement($view)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($view->getNode() === $this) {
|
||||
$view->setNode(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|Referer[]
|
||||
*/
|
||||
public function getAnalyticReferers(): Collection
|
||||
{
|
||||
return $this->analyticReferers;
|
||||
}
|
||||
|
||||
public function addAnalyticReferer(Referer $referer): self
|
||||
{
|
||||
if (!$this->analyticReferers->contains($referer)) {
|
||||
$this->analyticReferers[] = $referer;
|
||||
$referer->setNode($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAnalyticReferer(Referer $referer): self
|
||||
{
|
||||
if ($this->analyticReferers->removeElement($referer)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($referer->getNode() === $this) {
|
||||
$referer->setNode(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSecurityRoles(): array
|
||||
{
|
||||
return !is_array($this->securityRoles) ? [] : $this->securityRoles;
|
||||
}
|
||||
|
||||
public function setSecurityRoles(array $securityRoles): self
|
||||
{
|
||||
$this->securityRoles = $securityRoles;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSecurityOperator(): ?string
|
||||
{
|
||||
return $this->securityOperator;
|
||||
}
|
||||
|
||||
public function setSecurityOperator(string $securityOperator): self
|
||||
{
|
||||
$this->securityOperator = $securityOperator;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
82
src/core/Entity/Site/Page/Block.php
Normal file
82
src/core/Entity/Site/Page/Block.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Site\Page;
|
||||
|
||||
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
|
||||
*/
|
||||
class Block
|
||||
{
|
||||
use Timestampable;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=Page::class, inversedBy="blocks")
|
||||
* @ORM\JoinColumn(onDelete="CASCADE")
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue($value): self
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPage(): ?Page
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function setPage(?Page $page): self
|
||||
{
|
||||
$this->page = $page;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
21
src/core/Entity/Site/Page/ChoiceBlock.php
Normal file
21
src/core/Entity/Site/Page/ChoiceBlock.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Site\Page;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class ChoiceBlock extends Block
|
||||
{
|
||||
public function getValue()
|
||||
{
|
||||
return json_decode(parent::getValue(), true);
|
||||
}
|
||||
|
||||
public function setValue($value): self
|
||||
{
|
||||
return parent::setValue(json_encode($value));
|
||||
}
|
||||
}
|
21
src/core/Entity/Site/Page/CollectionBlock.php
Normal file
21
src/core/Entity/Site/Page/CollectionBlock.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Site\Page;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class CollectionBlock extends Block
|
||||
{
|
||||
public function getValue()
|
||||
{
|
||||
return json_decode(parent::getValue(), true);
|
||||
}
|
||||
|
||||
public function setValue($value): self
|
||||
{
|
||||
return parent::setValue(json_encode($value));
|
||||
}
|
||||
}
|
27
src/core/Entity/Site/Page/FileBlock.php
Normal file
27
src/core/Entity/Site/Page/FileBlock.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Site\Page;
|
||||
|
||||
use App\Core\File\FileAttribute;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class FileBlock extends Block
|
||||
{
|
||||
public function getValue()
|
||||
{
|
||||
return FileAttribute::handleFile(parent::getValue());
|
||||
}
|
||||
|
||||
public function setValue($value): self
|
||||
{
|
||||
if ($this->getValue() instanceof File && null === $value) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return parent::setValue($value);
|
||||
}
|
||||
}
|
276
src/core/Entity/Site/Page/Page.php
Normal file
276
src/core/Entity/Site/Page/Page.php
Normal file
|
@ -0,0 +1,276 @@
|
|||
<?php
|
||||
|
||||
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\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
|
||||
*/
|
||||
class Page implements EntityInterface
|
||||
{
|
||||
use Timestampable;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=Block::class, mappedBy="page", cascade={"persist"})
|
||||
*/
|
||||
protected $blocks;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $metaTitle;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $metaDescription;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $ogTitle;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $ogDescription;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255, nullable=true)
|
||||
*/
|
||||
protected $ogImage;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="page")
|
||||
*/
|
||||
protected $nodes;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->blocks = new ArrayCollection();
|
||||
$this->nodes = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTemplate(): ?string
|
||||
{
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
public function setTemplate(?string $template): self
|
||||
{
|
||||
$this->template = $template;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|Block[]
|
||||
*/
|
||||
public function getBlocks(): Collection
|
||||
{
|
||||
return $this->blocks;
|
||||
}
|
||||
|
||||
public function addBlock(Block $block): self
|
||||
{
|
||||
if (!$this->blocks->contains($block)) {
|
||||
$this->blocks[] = $block;
|
||||
$block->setPage($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeBlock(Block $block): self
|
||||
{
|
||||
if ($this->blocks->removeElement($block)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($block->getPage() === $this) {
|
||||
$block->setPage(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
}
|
||||
|
||||
public function getBlock($name, string $className = null)
|
||||
{
|
||||
foreach ($this->getBlocks() as $block) {
|
||||
if ($block->getName() === $name) {
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
|
||||
if ($className) {
|
||||
$block = new $className();
|
||||
} else {
|
||||
$block = new Block();
|
||||
}
|
||||
|
||||
$block->setName($name);
|
||||
$block->setPage($this);
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
public function setBlock(Block $block): self
|
||||
{
|
||||
foreach ($this->blocks->toArray() as $key => $value) {
|
||||
if ($value->getName() === $block->getName()) {
|
||||
$this->blocks->remove($key);
|
||||
$this->blocks->add($block);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
$this->blocks->add($block);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMetaTitle(): ?string
|
||||
{
|
||||
return $this->metaTitle;
|
||||
}
|
||||
|
||||
public function setMetaTitle(?string $metaTitle): self
|
||||
{
|
||||
$this->metaTitle = $metaTitle;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMetaDescription(): ?string
|
||||
{
|
||||
return $this->metaDescription;
|
||||
}
|
||||
|
||||
public function setMetaDescription(?string $metaDescription): self
|
||||
{
|
||||
$this->metaDescription = $metaDescription;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOgTitle(): ?string
|
||||
{
|
||||
return $this->ogTitle;
|
||||
}
|
||||
|
||||
public function setOgTitle(?string $ogTitle): self
|
||||
{
|
||||
$this->ogTitle = $ogTitle;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOgDescription(): ?string
|
||||
{
|
||||
return $this->ogDescription;
|
||||
}
|
||||
|
||||
public function setOgDescription(?string $ogDescription): self
|
||||
{
|
||||
$this->ogDescription = $ogDescription;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOgImage()
|
||||
{
|
||||
return FileAttribute::handleFile($this->ogImage);
|
||||
}
|
||||
|
||||
public function setOgImage($ogImage): self
|
||||
{
|
||||
if (null !== $this->ogImage && null === $ogImage) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->ogImage = $ogImage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|Node[]
|
||||
*/
|
||||
public function getNodes(): Collection
|
||||
{
|
||||
return $this->nodes;
|
||||
}
|
||||
|
||||
public function addNode(Node $node): self
|
||||
{
|
||||
if (!$this->nodes->contains($node)) {
|
||||
$this->nodes[] = $node;
|
||||
$node->setPage($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeNode(Node $node): self
|
||||
{
|
||||
if ($this->nodes->removeElement($node)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($node->getPage() === $this) {
|
||||
$node->setPage(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
12
src/core/Entity/Site/Page/TextBlock.php
Normal file
12
src/core/Entity/Site/Page/TextBlock.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Entity\Site\Page;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class TextBlock extends Block
|
||||
{
|
||||
}
|
28
src/core/Event/Account/PasswordRequestEvent.php
Normal file
28
src/core/Event/Account/PasswordRequestEvent.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Event\Account;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* class PasswordRequestEvent.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class PasswordRequestEvent extends Event
|
||||
{
|
||||
const EVENT = 'account_event.password_request';
|
||||
|
||||
protected User $user;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function getUser(): USer
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
}
|
33
src/core/Event/EntityManager/EntityManagerEvent.php
Normal file
33
src/core/Event/EntityManager/EntityManagerEvent.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Event\EntityManager;
|
||||
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* class EntityManagerEvent.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class EntityManagerEvent extends Event
|
||||
{
|
||||
const CREATE_EVENT = 'entity_manager_event.create';
|
||||
const UPDATE_EVENT = 'entity_manager_event.update';
|
||||
const DELETE_EVENT = 'entity_manager_event.delete';
|
||||
const PRE_CREATE_EVENT = 'entity_manager_event.pre_create';
|
||||
const PRE_UPDATE_EVENT = 'entity_manager_event.pre_update';
|
||||
const PRE_DELETE_EVENT = 'entity_manager_event.pre_delete';
|
||||
|
||||
protected EntityInterface $entity;
|
||||
|
||||
public function __construct(EntityInterface $entity)
|
||||
{
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
public function getEntity(): EntityInterface
|
||||
{
|
||||
return $this->entity;
|
||||
}
|
||||
}
|
44
src/core/Event/Page/PageEditEvent.php
Normal file
44
src/core/Event/Page/PageEditEvent.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Event\Page;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
use App\Core\Entity\Site\Page\Page;
|
||||
|
||||
/**
|
||||
* class PageEditEvent.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class PageEditEvent extends Event
|
||||
{
|
||||
const FORM_INIT_EVENT = 'page_edit_event.form_init';
|
||||
|
||||
protected Page $page;
|
||||
protected array $pageBuilderOptions = [];
|
||||
|
||||
public function __construct(Page $page)
|
||||
{
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
public function getPage()
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function getPageBuilderOptions(): array
|
||||
{
|
||||
return $this->pageBuilderOptions;
|
||||
}
|
||||
|
||||
public function addPageBuilderOptions(array $options): self
|
||||
{
|
||||
$this->pageBuilderOptions = array_merge(
|
||||
$this->pageBuilderOptions,
|
||||
$options
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
35
src/core/Event/Setting/NavigationSettingEvent.php
Normal file
35
src/core/Event/Setting/NavigationSettingEvent.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Event\Setting;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* class NavigationSettingEvent.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class NavigationSettingEvent extends Event
|
||||
{
|
||||
const INIT_EVENT = 'navigation_setting_event.init';
|
||||
const FORM_INIT_EVENT = 'navigation_setting_event.form_init';
|
||||
|
||||
protected $data;
|
||||
|
||||
public function __construct($data = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function setOption(string $key, $value): self
|
||||
{
|
||||
$this->data['options'][$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
35
src/core/Event/Setting/SettingEvent.php
Normal file
35
src/core/Event/Setting/SettingEvent.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Event\Setting;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* class SettingEvent.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class SettingEvent extends Event
|
||||
{
|
||||
const INIT_EVENT = 'setting_event.init';
|
||||
const FORM_INIT_EVENT = 'setting_event.form_init';
|
||||
|
||||
protected $data;
|
||||
|
||||
public function __construct($data = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function setOption(string $key, $value): self
|
||||
{
|
||||
$this->data['options'][$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
37
src/core/Event/Task/TaskInitEvent.php
Normal file
37
src/core/Event/Task/TaskInitEvent.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Event\Task;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* class TaskInitEvent.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class TaskInitEvent extends Event
|
||||
{
|
||||
const INIT_EVENT = 'task_event.init';
|
||||
|
||||
protected array $tasks = [];
|
||||
|
||||
public function getTasks(): array
|
||||
{
|
||||
usort($this->tasks, function ($t1, $t2) {
|
||||
return $t1['section'] != $t2['section'];
|
||||
});
|
||||
|
||||
return $this->tasks;
|
||||
}
|
||||
|
||||
public function addTask(string $task, string $section, string $label): self
|
||||
{
|
||||
$this->tasks[$task] = [
|
||||
'label' => $label,
|
||||
'section' => $section,
|
||||
'task' => $task,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
44
src/core/Event/Task/TaskRunRequestedEvent.php
Normal file
44
src/core/Event/Task/TaskRunRequestedEvent.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Event\Task;
|
||||
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\InputBag;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* class TaskRunRequestedEvent.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class TaskRunRequestedEvent extends Event
|
||||
{
|
||||
const RUN_REQUEST_EVENT = 'task_event.run_request';
|
||||
|
||||
protected string $task;
|
||||
protected InputBag $parameters;
|
||||
protected BufferedOutput $output;
|
||||
|
||||
public function __construct(string $task, InputBag $parameters, BufferedOutput $output)
|
||||
{
|
||||
$this->task = $task;
|
||||
$this->parameters = $parameters;
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
public function getTask(): string
|
||||
{
|
||||
return $this->task;
|
||||
}
|
||||
|
||||
public function getParameters(): ParameterBagInterface
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
public function getOutput(): BufferedOutput
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
}
|
153
src/core/EventListener/AnalyticListener.php
Normal file
153
src/core/EventListener/AnalyticListener.php
Normal file
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventListener;
|
||||
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Entity\Site\Node;
|
||||
use App\Core\Factory\Analytic\RefererFactory;
|
||||
use App\Core\Factory\Analytic\ViewFactory;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Core\Repository\Analytic\RefererRepositoryQuery;
|
||||
use App\Core\Repository\Analytic\ViewRepositoryQuery;
|
||||
use App\Core\Repository\Site\NodeRepository;
|
||||
use DeviceDetector\Cache\PSR6Bridge;
|
||||
use DeviceDetector\DeviceDetector;
|
||||
use Symfony\Component\Cache\Adapter\ApcuAdapter;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
|
||||
/**
|
||||
* class AnalyticListener.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class AnalyticListener
|
||||
{
|
||||
protected NodeRepository $nodeRepository;
|
||||
protected ViewRepositoryQuery $viewRepositoryQuery;
|
||||
protected ViewFactory $viewFactory;
|
||||
protected RefererRepositoryQuery $refererRepositoryQuery;
|
||||
protected RefererFactory $refererFactory;
|
||||
protected EntityManager $manager;
|
||||
protected DeviceDetector $deviceDetector;
|
||||
protected Request $request;
|
||||
protected Node $node;
|
||||
|
||||
public function __construct(
|
||||
NodeRepository $nodeRepository,
|
||||
ViewRepositoryQuery $viewRepositoryQuery,
|
||||
ViewFactory $viewFactory,
|
||||
RefererRepositoryQuery $refererRepositoryQuery,
|
||||
RefererFactory $refererFactory,
|
||||
EntityManager $manager
|
||||
) {
|
||||
$this->nodeRepository = $nodeRepository;
|
||||
$this->viewRepositoryQuery = $viewRepositoryQuery;
|
||||
$this->viewFactory = $viewFactory;
|
||||
$this->refererRepositoryQuery = $refererRepositoryQuery;
|
||||
$this->refererFactory = $refererFactory;
|
||||
$this->manager = $manager;
|
||||
$this->createDeviceDetector();
|
||||
}
|
||||
|
||||
public function onKernelRequest(RequestEvent $event)
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
|
||||
if (!$request->attributes->has('_node')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->deviceDetector->setUserAgent($request->headers->get('user-agent'));
|
||||
$this->deviceDetector->parse();
|
||||
|
||||
if ($this->deviceDetector->isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$node = $this->nodeRepository->findOneBy([
|
||||
'id' => $request->attributes->get('_node'),
|
||||
'enableAnalytics' => true,
|
||||
]);
|
||||
|
||||
if (!$node) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->node = $node;
|
||||
$this->request = $request;
|
||||
|
||||
$this->createView();
|
||||
$this->createReferer();
|
||||
}
|
||||
|
||||
protected function createDeviceDetector()
|
||||
{
|
||||
$cache = new ApcuAdapter();
|
||||
|
||||
$this->deviceDetector = new DeviceDetector();
|
||||
$this->deviceDetector->setCache(new PSR6Bridge($cache));
|
||||
}
|
||||
|
||||
protected function createView()
|
||||
{
|
||||
$entity = $this->viewRepositoryQuery->create()
|
||||
->filterByRequest($this->request)
|
||||
->andWhere('.date=CURRENT_DATE()')
|
||||
->findOne()
|
||||
;
|
||||
|
||||
if (!$entity) {
|
||||
$entity = $this->viewFactory->create($this->node, $this->request->getPathInfo());
|
||||
}
|
||||
|
||||
$entity->addView();
|
||||
|
||||
if ($this->deviceDetector->isDesktop()) {
|
||||
$entity->addDesktopView();
|
||||
} elseif ($this->deviceDetector->isMobile()) {
|
||||
$entity->addMobileView();
|
||||
}
|
||||
|
||||
$this->save($entity);
|
||||
}
|
||||
|
||||
protected function createReferer()
|
||||
{
|
||||
if (!$this->request->headers->has('referer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$referer = $this->request->headers->get('referer');
|
||||
|
||||
if (!filter_var($referer, FILTER_VALIDATE_URL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array(parse_url($referer, PHP_URL_SCHEME), ['http', 'https'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity = $this->refererRepositoryQuery->create()
|
||||
->filterByRequest($this->request)
|
||||
->andWhere('.date=CURRENT_DATE()')
|
||||
->findOne()
|
||||
;
|
||||
|
||||
if (!$entity) {
|
||||
$entity = $this->refererFactory->create($this->node, $referer);
|
||||
}
|
||||
|
||||
$entity->addView();
|
||||
$this->save($entity);
|
||||
}
|
||||
|
||||
protected function save(EntityInterface $entity)
|
||||
{
|
||||
if ($entity->getId()) {
|
||||
$this->manager->update($entity);
|
||||
} else {
|
||||
$this->manager->create($entity);
|
||||
}
|
||||
}
|
||||
}
|
50
src/core/EventListener/RedirectListener.php
Normal file
50
src/core/EventListener/RedirectListener.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventListener;
|
||||
|
||||
use App\Core\Repository\RedirectRepositoryQuery;
|
||||
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.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class RedirectListener
|
||||
{
|
||||
protected RedirectMatcher $matcher;
|
||||
protected RedirectBuilder $builder;
|
||||
protected RedirectRepositoryQuery $repository;
|
||||
|
||||
public function __construct(RedirectMatcher $matcher, RedirectBuilder $builder, RedirectRepositoryQuery $repository)
|
||||
{
|
||||
$this->matcher = $matcher;
|
||||
$this->builder = $builder;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function onKernelException(ExceptionEvent $event)
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
|
||||
if (!$event->getThrowable() instanceof NotFoundHttpException) {
|
||||
return;
|
||||
}
|
||||
|
||||
$redirects = $this->repository
|
||||
->orderBy('.sortOrder')
|
||||
->where('.isEnabled=1')
|
||||
->find()
|
||||
;
|
||||
|
||||
foreach ($redirects as $redirect) {
|
||||
if ($this->matcher->match($redirect, $event->getRequest()->getUri())) {
|
||||
$event->setResponse($this->builder->buildResponse($redirect, $event->getRequest()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Account;
|
||||
|
||||
use App\Core\Event\Account\PasswordRequestEvent;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Core\Notification\MailNotifier;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* class EventListener.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class PasswordRequestEventSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
protected MailNotifier $notifier;
|
||||
protected UrlGeneratorInterface $urlGenerator;
|
||||
protected EntityManager $entityManager;
|
||||
protected TokenGeneratorInterface $tokenGenerator;
|
||||
protected TranslatorInterface $translator;
|
||||
|
||||
public function __construct(
|
||||
MailNotifier $notifier,
|
||||
UrlGeneratorInterface $urlGenerator,
|
||||
EntityManager $entityManager,
|
||||
TokenGeneratorInterface $tokenGenerator,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->notifier = $notifier;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->tokenGenerator = $tokenGenerator;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
PasswordRequestEvent::EVENT => 'onRequest',
|
||||
];
|
||||
}
|
||||
|
||||
public function onRequest(PasswordRequestEvent $event)
|
||||
{
|
||||
$user = $event->getUser();
|
||||
$user->setConfirmationToken($this->tokenGenerator->generateToken());
|
||||
$user->setPasswordRequestedAt(new \DateTime('now'));
|
||||
|
||||
$this->entityManager->update($user);
|
||||
|
||||
$this->notifier
|
||||
->setSubject($this->translator->trans('Mot de passe perdu'))
|
||||
->addRecipient($user->getEmail())
|
||||
->notify('@Core/mail/account/resetting_request.html.twig', [
|
||||
'reseting_update_link' => $this->urlGenerator->generate(
|
||||
'auth_resetting_update',
|
||||
[
|
||||
'token' => $user->getConfirmationToken(),
|
||||
],
|
||||
UrlGeneratorInterface::ABSOLUTE_URL
|
||||
),
|
||||
])
|
||||
;
|
||||
}
|
||||
}
|
52
src/core/EventSubscriber/EntityManagerEventSubscriber.php
Normal file
52
src/core/EventSubscriber/EntityManagerEventSubscriber.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber;
|
||||
|
||||
use App\Core\Event\EntityManager\EntityManagerEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* class EntityManagerEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
abstract class EntityManagerEventSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
protected static int $priority = 0;
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
EntityManagerEvent::CREATE_EVENT => ['onCreate', self::$priority],
|
||||
EntityManagerEvent::UPDATE_EVENT => ['onUpdate', self::$priority],
|
||||
EntityManagerEvent::DELETE_EVENT => ['onDelete', self::$priority],
|
||||
EntityManagerEvent::PRE_CREATE_EVENT => ['onPreCreate', self::$priority],
|
||||
EntityManagerEvent::PRE_UPDATE_EVENT => ['onPreUpdate', self::$priority],
|
||||
EntityManagerEvent::PRE_DELETE_EVENT => ['onPreDelete', self::$priority],
|
||||
];
|
||||
}
|
||||
|
||||
public function onCreate(EntityManagerEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function onUpdate(EntityManagerEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function onDelete(EntityManagerEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function onPreCreate(EntityManagerEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function onPreUpdate(EntityManagerEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function onPreDelete(EntityManagerEvent $event)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber;
|
||||
|
||||
use App\Core\Event\Setting\NavigationSettingEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* class NavigationSettingEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
abstract class NavigationSettingEventSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
protected static int $priority = 0;
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
NavigationSettingEvent::INIT_EVENT => ['onInit', self::$priority],
|
||||
NavigationSettingEvent::FORM_INIT_EVENT => ['onFormInit', self::$priority],
|
||||
];
|
||||
}
|
||||
|
||||
public function onInit(NavigationSettingEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function onFormInit(NavigationSettingEvent $event)
|
||||
{
|
||||
}
|
||||
}
|
69
src/core/EventSubscriber/RequestSecurityEventSubscriber.php
Normal file
69
src/core/EventSubscriber/RequestSecurityEventSubscriber.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber;
|
||||
|
||||
use App\Core\Repository\Site\NodeRepository;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationChecker;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
|
||||
/**
|
||||
* class RequestSecurityEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class RequestSecurityEventSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
protected NodeRepository $nodeRepository;
|
||||
protected AuthorizationChecker $authorizationChecker;
|
||||
|
||||
public function __construct(NodeRepository $nodeRepository, ContainerInterface $container)
|
||||
{
|
||||
$this->nodeRepository = $nodeRepository;
|
||||
$this->authorizationChecker = $container->get('security.authorization_checker');
|
||||
}
|
||||
|
||||
public function onKernelRequest(RequestEvent $event)
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
|
||||
if (!$request->attributes->has('_node')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$node = $this->nodeRepository->findOneBy([
|
||||
'id' => $request->attributes->get('_node'),
|
||||
]);
|
||||
|
||||
$roles = $node->getSecurityRoles();
|
||||
|
||||
if (empty($roles)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$operator = $node->getSecurityOperator();
|
||||
$exception = new AccessDeniedException('Access denied.');
|
||||
|
||||
foreach ($roles as $role) {
|
||||
$isGranted = $this->authorizationChecker->isGranted($role);
|
||||
|
||||
if ('or' === $operator && $isGranted) {
|
||||
return;
|
||||
}
|
||||
if ('and' === $operator && !$isGranted) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
RequestEvent::class => ['onKernelRequest', 1],
|
||||
];
|
||||
}
|
||||
}
|
32
src/core/EventSubscriber/SettingEventSubscriber.php
Normal file
32
src/core/EventSubscriber/SettingEventSubscriber.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber;
|
||||
|
||||
use App\Core\Event\Setting\SettingEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* class SettingEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
abstract class SettingEventSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
protected static int $priority = 0;
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
SettingEvent::INIT_EVENT => ['onInit', self::$priority],
|
||||
SettingEvent::FORM_INIT_EVENT => ['onFormInit', self::$priority],
|
||||
];
|
||||
}
|
||||
|
||||
public function onInit(SettingEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function onFormInit(SettingEvent $event)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Site;
|
||||
|
||||
use App\Core\Site\SiteRequest;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use function Symfony\Component\String\u;
|
||||
|
||||
class ForcedDomainEventSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
protected SiteRequest $siteRequest;
|
||||
|
||||
public function __construct(SiteRequest $siteRequest)
|
||||
{
|
||||
$this->siteRequest = $siteRequest;
|
||||
}
|
||||
|
||||
public function onKernelResponse(ResponseEvent $event)
|
||||
{
|
||||
$navigation = $this->siteRequest->getNavigation();
|
||||
|
||||
if (!$navigation) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$navigation->getForceDomain()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($navigation->getDomain() === $this->siteRequest->getDomain()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uri = u($this->siteRequest->getUri())
|
||||
->replace(
|
||||
'://'.$this->siteRequest->getDomain(),
|
||||
'://'.$navigation->getDomain()
|
||||
);
|
||||
|
||||
$event->getResponse()->headers->set('Location', $uri);
|
||||
$event->getResponse()->setStatusCode(Response::HTTP_MOVED_PERMANENTLY);
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
KernelEvents::RESPONSE => [['onKernelResponse', 20]],
|
||||
];
|
||||
}
|
||||
}
|
107
src/core/EventSubscriber/Site/MenuEventSubscriber.php
Normal file
107
src/core/EventSubscriber/Site/MenuEventSubscriber.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Site;
|
||||
|
||||
use App\Core\Cache\SymfonyCacheManager;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Entity\Site\Menu;
|
||||
use App\Core\Event\EntityManager\EntityManagerEvent;
|
||||
use App\Core\EventSubscriber\EntityManagerEventSubscriber;
|
||||
use App\Core\Factory\Site\NodeFactory;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Core\Repository\Site\NodeRepository;
|
||||
use App\Core\Slugify\CodeSlugify;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* class MenuEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class MenuEventSubscriber extends EntityManagerEventSubscriber
|
||||
{
|
||||
protected NodeFactory $nodeFactory;
|
||||
protected NodeRepository $nodeRepository;
|
||||
protected EntityManager $entityManager;
|
||||
protected CodeSlugify $slugify;
|
||||
protected SymfonyCacheManager $cacheManager;
|
||||
protected TranslatorInterface $translation;
|
||||
|
||||
public function __construct(
|
||||
NodeFactory $nodeFactory,
|
||||
NodeRepository $nodeRepository,
|
||||
EntityManager $entityManager,
|
||||
CodeSlugify $slugify,
|
||||
SymfonyCacheManager $cacheManager,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->nodeFactory = $nodeFactory;
|
||||
$this->nodeRepository = $nodeRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->slugify = $slugify;
|
||||
$this->cacheManager = $cacheManager;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function support(EntityInterface $entity)
|
||||
{
|
||||
return $entity instanceof Menu;
|
||||
}
|
||||
|
||||
public function onPreUpdate(EntityManagerEvent $event)
|
||||
{
|
||||
if (!$this->support($event->getEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu = $event->getEntity();
|
||||
$menu->setCode($this->slugify->slugify($menu->getCode()));
|
||||
}
|
||||
|
||||
public function onCreate(EntityManagerEvent $event)
|
||||
{
|
||||
if (!$this->support($event->getEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu = $event->getEntity();
|
||||
|
||||
if (count($menu->getNodes()) > 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootNode = $menu->getRootNode();
|
||||
|
||||
if (!$rootNode) {
|
||||
$rootNode = $this->nodeFactory->create($menu);
|
||||
}
|
||||
|
||||
$childNode = $this->nodeFactory->create($menu, '/');
|
||||
$childNode
|
||||
->setParent($rootNode)
|
||||
->setLabel($this->translator->trans('First element'))
|
||||
;
|
||||
|
||||
$menu->setRootNode($rootNode);
|
||||
|
||||
$this->entityManager->getEntityManager()->persist($rootNode);
|
||||
$this->entityManager->getEntityManager()->persist($childNode);
|
||||
|
||||
$this->entityManager->getEntityManager()->persist($menu);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->nodeRepository->persistAsFirstChild($childNode, $rootNode);
|
||||
|
||||
$this->cacheManager->cleanRouting();
|
||||
}
|
||||
|
||||
public function onUpdate(EntityManagerEvent $event)
|
||||
{
|
||||
return $this->onCreate($event);
|
||||
}
|
||||
|
||||
public function onPreCreate(EntityManagerEvent $event)
|
||||
{
|
||||
return $this->onPreUpdate($event);
|
||||
}
|
||||
}
|
46
src/core/EventSubscriber/Site/NavigationEventSubscriber.php
Normal file
46
src/core/EventSubscriber/Site/NavigationEventSubscriber.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Site;
|
||||
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Entity\Site\Navigation;
|
||||
use App\Core\Event\EntityManager\EntityManagerEvent;
|
||||
use App\Core\EventSubscriber\EntityManagerEventSubscriber;
|
||||
use App\Core\Manager\EntityManager;
|
||||
use App\Core\Slugify\CodeSlugify;
|
||||
|
||||
/**
|
||||
* class NavigationEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class NavigationEventSubscriber extends EntityManagerEventSubscriber
|
||||
{
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
CodeSlugify $slugify
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->slugify = $slugify;
|
||||
}
|
||||
|
||||
public function support(EntityInterface $entity)
|
||||
{
|
||||
return $entity instanceof Navigation;
|
||||
}
|
||||
|
||||
public function onPreUpdate(EntityManagerEvent $event)
|
||||
{
|
||||
if (!$this->support($event->getEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu = $event->getEntity();
|
||||
$menu->setCode($this->slugify->slugify($menu->getCode()));
|
||||
}
|
||||
|
||||
public function onPreCreate(EntityManagerEvent $event)
|
||||
{
|
||||
return $this->onPreUpdate($event);
|
||||
}
|
||||
}
|
183
src/core/EventSubscriber/Site/NodeEventSubscriber.php
Normal file
183
src/core/EventSubscriber/Site/NodeEventSubscriber.php
Normal file
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Site;
|
||||
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Entity\Site\Node;
|
||||
use App\Core\Event\EntityManager\EntityManagerEvent;
|
||||
use App\Core\EventSubscriber\EntityManagerEventSubscriber;
|
||||
use App\Core\Factory\Site\NodeFactory;
|
||||
use App\Core\Manager\EntityManager;
|
||||
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;
|
||||
|
||||
/**
|
||||
* class NodeEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class NodeEventSubscriber extends EntityManagerEventSubscriber
|
||||
{
|
||||
protected NodeFactory $nodeFactory;
|
||||
protected EntityManager $entityManager;
|
||||
protected KernelInterface $kernel;
|
||||
protected Slugify $slugify;
|
||||
protected CodeSlugify $codeSlugify;
|
||||
protected RouteParameterSlugify $routeParameterSlugify;
|
||||
|
||||
public function __construct(
|
||||
NodeFactory $nodeFactory,
|
||||
NodeRepository $nodeRepository,
|
||||
EntityManager $entityManager,
|
||||
Slugify $slugify,
|
||||
CodeSlugify $codeSlugify,
|
||||
RouteParameterSlugify $routeParameterSlugify
|
||||
) {
|
||||
$this->nodeFactory = $nodeFactory;
|
||||
$this->nodeRepository = $nodeRepository;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->slugify = $slugify;
|
||||
$this->codeSlugify = $codeSlugify;
|
||||
$this->routeParameterSlugify = $routeParameterSlugify;
|
||||
}
|
||||
|
||||
public function support(EntityInterface $entity)
|
||||
{
|
||||
return $entity instanceof Node;
|
||||
}
|
||||
|
||||
public function onPreCreate(EntityManagerEvent $event)
|
||||
{
|
||||
return $this->onPreUpdate($event);
|
||||
}
|
||||
|
||||
public function onPreUpdate(EntityManagerEvent $event)
|
||||
{
|
||||
if (!$this->support($event->getEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$node = $event->getEntity();
|
||||
|
||||
$node->setCode($this->codeSlugify->slugify($node->getCode()));
|
||||
|
||||
if ($node->getDisableUrl()) {
|
||||
$node->setUrl(null);
|
||||
} else {
|
||||
if ($node->getUrl()) {
|
||||
$generatedUrl = $node->getUrl();
|
||||
} else {
|
||||
$path = [];
|
||||
$parent = $node->getParent();
|
||||
|
||||
if ($parent && $parent->getUrl()) {
|
||||
$pPath = trim($parent->getUrl(), '/');
|
||||
|
||||
if ($pPath) {
|
||||
$path[] = $pPath;
|
||||
}
|
||||
}
|
||||
|
||||
$path[] = $this->slugify->slugify($node->getLabel());
|
||||
|
||||
$generatedUrl = '/'.implode('/', $path);
|
||||
}
|
||||
|
||||
if ('/' !== $generatedUrl) {
|
||||
$generatedUrl = rtrim($generatedUrl, '/');
|
||||
}
|
||||
|
||||
$parameters = $node->getParameters();
|
||||
$routeParameters = [];
|
||||
|
||||
foreach ($parameters as $key => $parameter) {
|
||||
$parameter['name'] = $this->routeParameterSlugify->slugify($parameter['name']);
|
||||
$routeParameter = sprintf('{%s}', $parameter['name']);
|
||||
$regex = '/'.preg_quote($routeParameter).'/';
|
||||
$routeParameters[] = $parameter['name'];
|
||||
|
||||
if (!preg_match($regex, $generatedUrl)) {
|
||||
$generatedUrl .= '/'.$routeParameter;
|
||||
}
|
||||
|
||||
$parameters[$key] = $parameter;
|
||||
}
|
||||
|
||||
preg_match_all('/\{(.*)\}/isU', $generatedUrl, $matches, PREG_SET_ORDER);
|
||||
|
||||
foreach ($matches as $match) {
|
||||
if (!in_array($match[1], $routeParameters)) {
|
||||
$parameters[] = [
|
||||
'name' => $this->routeParameterSlugify->slugify($match[1]),
|
||||
'defaultValue' => null,
|
||||
'requirement' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$node->setParameters($parameters);
|
||||
|
||||
$urlExists = $this->nodeRepository->urlExists($generatedUrl, $node);
|
||||
|
||||
if ($urlExists) {
|
||||
$number = 1;
|
||||
|
||||
while ($this->nodeRepository->urlExists($generatedUrl.'-'.$number, $node)) {
|
||||
++$number;
|
||||
}
|
||||
|
||||
$generatedUrl = $generatedUrl.'-'.$number;
|
||||
}
|
||||
|
||||
if (
|
||||
!u($generatedUrl)->startsWith('https://')
|
||||
&& !u($generatedUrl)->startsWith('http://')
|
||||
&& !u($generatedUrl)->startsWith('tel:')
|
||||
&& !u($generatedUrl)->startsWith('mailto:')
|
||||
&& !u($generatedUrl)->startsWith('fax:')
|
||||
) {
|
||||
$generatedUrl = '/'.$generatedUrl;
|
||||
$generatedUrl = preg_replace('#/{2,}#', '/', $generatedUrl);
|
||||
}
|
||||
|
||||
$node->setUrl($generatedUrl);
|
||||
}
|
||||
|
||||
$attributes = $node->getAttributes();
|
||||
$realAttributes = [];
|
||||
|
||||
foreach ($attributes as $key => $attribute) {
|
||||
$realAttributes[$this->routeParameterSlugify->slugify($attribute['label'])] = $attribute;
|
||||
}
|
||||
|
||||
$node->setAttributes($realAttributes);
|
||||
}
|
||||
|
||||
public function onDelete(EntityManagerEvent $event)
|
||||
{
|
||||
if (!$this->support($event->getEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu = $event->getEntity()->getMenu();
|
||||
$rootNode = $menu->getRootNode();
|
||||
|
||||
if (0 !== count($rootNode->getChildren())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$childNode = $this->nodeFactory->create($menu);
|
||||
$childNode
|
||||
->setParent($rootNode)
|
||||
->setLabel('Premier élément')
|
||||
;
|
||||
|
||||
$this->entityManager->update($rootNode, false);
|
||||
$this->entityManager->create($childNode, false);
|
||||
$this->nodeRepository->persistAsFirstChild($childNode, $rootNode);
|
||||
}
|
||||
}
|
59
src/core/EventSubscriber/Site/Page/BlockEventSubscriber.php
Normal file
59
src/core/EventSubscriber/Site/Page/BlockEventSubscriber.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Site\Page;
|
||||
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Entity\Site\Page\FileBlock;
|
||||
use App\Core\Entity\Site\Page\Page;
|
||||
use App\Core\Event\EntityManager\EntityManagerEvent;
|
||||
use App\Core\EventSubscriber\EntityManagerEventSubscriber;
|
||||
use App\Core\Form\FileUploadHandler;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
/**
|
||||
* class BlockEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class BlockEventSubscriber extends EntityManagerEventSubscriber
|
||||
{
|
||||
protected FileUploadHandler $fileUpload;
|
||||
|
||||
public function __construct(FileUploadHandler $fileUpload)
|
||||
{
|
||||
$this->fileUpload = $fileUpload;
|
||||
}
|
||||
|
||||
public function support(EntityInterface $entity)
|
||||
{
|
||||
return $entity instanceof Page;
|
||||
}
|
||||
|
||||
public function onPreUpdate(EntityManagerEvent $event)
|
||||
{
|
||||
if (!$this->support($event->getEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($event->getEntity()->getBlocks() as $block) {
|
||||
if ($block instanceof FileBlock) {
|
||||
if ($block->getValue() instanceof UploadedFile) {
|
||||
$directory = 'uploads/page/block';
|
||||
|
||||
$this->fileUpload->handleForm(
|
||||
$block->getValue(),
|
||||
$directory,
|
||||
function ($filename) use ($block, $directory) {
|
||||
$block->setValue($directory.'/'.$filename);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function onPreCreate(EntityManagerEvent $event)
|
||||
{
|
||||
return $this->onPreUpdate($event);
|
||||
}
|
||||
}
|
56
src/core/EventSubscriber/Site/Page/PageEventSubscriber.php
Normal file
56
src/core/EventSubscriber/Site/Page/PageEventSubscriber.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Site\Page;
|
||||
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Entity\Site\Page\Page;
|
||||
use App\Core\Event\EntityManager\EntityManagerEvent;
|
||||
use App\Core\EventSubscriber\EntityManagerEventSubscriber;
|
||||
use App\Core\Form\FileUploadHandler;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
/**
|
||||
* class PageEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class PageEventSubscriber extends EntityManagerEventSubscriber
|
||||
{
|
||||
protected FileUploadHandler $fileUpload;
|
||||
|
||||
public function __construct(FileUploadHandler $fileUpload)
|
||||
{
|
||||
$this->fileUpload = $fileUpload;
|
||||
}
|
||||
|
||||
public function support(EntityInterface $entity)
|
||||
{
|
||||
return $entity instanceof Page;
|
||||
}
|
||||
|
||||
public function onPreUpdate(EntityManagerEvent $event)
|
||||
{
|
||||
if (!$this->support($event->getEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$page = $event->getEntity();
|
||||
|
||||
if ($page->getOgImage() instanceof UploadedFile) {
|
||||
$directory = 'uploads/page/ogImage';
|
||||
|
||||
$this->fileUpload->handleForm(
|
||||
$page->getOgImage(),
|
||||
$directory,
|
||||
function ($filename) use ($page, $directory) {
|
||||
$page->setOgImage($directory.'/'.$filename);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function onPreCreate(EntityManagerEvent $event)
|
||||
{
|
||||
return $this->onPreUpdate($event);
|
||||
}
|
||||
}
|
53
src/core/EventSubscriber/Site/SiteEventSubscriber.php
Normal file
53
src/core/EventSubscriber/Site/SiteEventSubscriber.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Site;
|
||||
|
||||
use App\Core\Cache\SymfonyCacheManager;
|
||||
use App\Core\Entity\EntityInterface;
|
||||
use App\Core\Entity\Site\Menu;
|
||||
use App\Core\Entity\Site\Navigation;
|
||||
use App\Core\Entity\Site\Node;
|
||||
use App\Core\Event\EntityManager\EntityManagerEvent;
|
||||
use App\Core\EventSubscriber\EntityManagerEventSubscriber;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
|
||||
/**
|
||||
* class SiteEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class SiteEventSubscriber extends EntityManagerEventSubscriber
|
||||
{
|
||||
protected KernelInterface $kernel;
|
||||
protected SymfonyCacheManager $cacheManager;
|
||||
|
||||
public function __construct(KernelInterface $kernel, SymfonyCacheManager $cacheManager)
|
||||
{
|
||||
$this->kernel = $kernel;
|
||||
$this->cacheManager = $cacheManager;
|
||||
}
|
||||
|
||||
public function support(EntityInterface $entity)
|
||||
{
|
||||
return $entity instanceof Node || $entity instanceof Menu || $entity instanceof Navigation;
|
||||
}
|
||||
|
||||
public function onUpdate(EntityManagerEvent $event)
|
||||
{
|
||||
if (!$this->support($event->getEntity())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cacheManager->cleanRouting();
|
||||
}
|
||||
|
||||
public function onCreate(EntityManagerEvent $event)
|
||||
{
|
||||
return $this->onUpdate($event);
|
||||
}
|
||||
|
||||
public function onDelete(EntityManagerEvent $event)
|
||||
{
|
||||
return $this->onUpdate($event);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Task;
|
||||
|
||||
use App\Core\Cache\SymfonyCacheManager;
|
||||
use App\Core\Event\Task\TaskInitEvent;
|
||||
use App\Core\Event\Task\TaskRunRequestedEvent;
|
||||
|
||||
/**
|
||||
* class CacheCleanTaskEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class CacheCleanTaskEventSubscriber extends TaskEventSubscriber
|
||||
{
|
||||
protected SymfonyCacheManager $cacheManager;
|
||||
|
||||
public function __construct(SymfonyCacheManager $cacheManager)
|
||||
{
|
||||
$this->cacheManager = $cacheManager;
|
||||
}
|
||||
|
||||
public function onInit(TaskInitEvent $event)
|
||||
{
|
||||
$event->addTask('cache:clear', '♻️ Cache', 'Clean all cache');
|
||||
}
|
||||
|
||||
public function onRunRequest(TaskRunRequestedEvent $event)
|
||||
{
|
||||
if ('cache:clear' !== $event->getTask()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cacheManager->cleanAll($event->getOutput());
|
||||
}
|
||||
}
|
33
src/core/EventSubscriber/Task/TaskEventSubscriber.php
Normal file
33
src/core/EventSubscriber/Task/TaskEventSubscriber.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\EventSubscriber\Task;
|
||||
|
||||
use App\Core\Event\Task\TaskInitEvent;
|
||||
use App\Core\Event\Task\TaskRunRequestedEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* class TaskEventSubscriber.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
abstract class TaskEventSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
protected static int $priority = 0;
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
TaskInitEvent::INIT_EVENT => ['onInit', self::$priority],
|
||||
TaskRunRequestedEvent::RUN_REQUEST_EVENT => ['onRunRequest', self::$priority],
|
||||
];
|
||||
}
|
||||
|
||||
public function onInit(TaskInitEvent $event)
|
||||
{
|
||||
}
|
||||
|
||||
public function onRunRequest(TaskRunRequestedEvent $event)
|
||||
{
|
||||
}
|
||||
}
|
22
src/core/Factory/Analytic/RefererFactory.php
Normal file
22
src/core/Factory/Analytic/RefererFactory.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory\Analytic;
|
||||
|
||||
use App\Core\Entity\Analytic\Referer as Entity;
|
||||
use App\Core\Entity\Site\Node;
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
|
||||
class RefererFactory implements FactoryInterface
|
||||
{
|
||||
public function create(Node $node, string $uri): Entity
|
||||
{
|
||||
$entity = new Entity();
|
||||
$entity
|
||||
->setNode($node)
|
||||
->setUri($uri)
|
||||
->setDate(new \DateTime())
|
||||
;
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
22
src/core/Factory/Analytic/ViewFactory.php
Normal file
22
src/core/Factory/Analytic/ViewFactory.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory\Analytic;
|
||||
|
||||
use App\Core\Entity\Analytic\View as Entity;
|
||||
use App\Core\Entity\Site\Node;
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
|
||||
class ViewFactory implements FactoryInterface
|
||||
{
|
||||
public function create(Node $node, string $path): Entity
|
||||
{
|
||||
$entity = new Entity();
|
||||
$entity
|
||||
->setNode($node)
|
||||
->setPath($path)
|
||||
->setDate(new \DateTime())
|
||||
;
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
12
src/core/Factory/FactoryInterface.php
Normal file
12
src/core/Factory/FactoryInterface.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory;
|
||||
|
||||
/**
|
||||
* interface FactoryInterface.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
interface FactoryInterface
|
||||
{
|
||||
}
|
16
src/core/Factory/FileInformationFactory.php
Normal file
16
src/core/Factory/FileInformationFactory.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory;
|
||||
|
||||
use App\Core\Entity\FileInformation as Entity;
|
||||
|
||||
class FileInformationFactory implements FactoryInterface
|
||||
{
|
||||
public function create(string $id): Entity
|
||||
{
|
||||
$entity = new Entity();
|
||||
$entity->setId($id);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
26
src/core/Factory/NavigationSettingFactory.php
Normal file
26
src/core/Factory/NavigationSettingFactory.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory;
|
||||
|
||||
use App\Core\Entity\NavigationSetting;
|
||||
use App\Core\Entity\Site\Navigation;
|
||||
|
||||
/**
|
||||
* class NavigationSettingFactory.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class NavigationSettingFactory implements FactoryInterface
|
||||
{
|
||||
public function create(Navigation $navigation, string $code): NavigationSetting
|
||||
{
|
||||
$entity = new NavigationSetting();
|
||||
|
||||
$entity
|
||||
->setNavigation($navigation)
|
||||
->setCode($code)
|
||||
;
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
14
src/core/Factory/RedirectFactory.php
Normal file
14
src/core/Factory/RedirectFactory.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory;
|
||||
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
use App\Core\Entity\Redirect as Entity;
|
||||
|
||||
class RedirectFactory implements FactoryInterface
|
||||
{
|
||||
public function create(): Entity
|
||||
{
|
||||
return new Entity();
|
||||
}
|
||||
}
|
22
src/core/Factory/SettingFactory.php
Normal file
22
src/core/Factory/SettingFactory.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory;
|
||||
|
||||
use App\Core\Entity\Setting;
|
||||
|
||||
/**
|
||||
* class SettingFactory.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class SettingFactory implements FactoryInterface
|
||||
{
|
||||
public function create(string $code): Setting
|
||||
{
|
||||
$entity = new Setting();
|
||||
|
||||
$entity->setCode($code);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
26
src/core/Factory/Site/MenuFactory.php
Normal file
26
src/core/Factory/Site/MenuFactory.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory\Site;
|
||||
|
||||
use App\Core\Entity\Site\Menu;
|
||||
use App\Core\Entity\Site\Navigation;
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
|
||||
/**
|
||||
* class MenuFactory.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class MenuFactory implements FactoryInterface
|
||||
{
|
||||
public function create(?Navigation $navigation = null): Menu
|
||||
{
|
||||
$entity = new Menu();
|
||||
|
||||
if (null !== $navigation) {
|
||||
$entity->setNavigation($navigation);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
19
src/core/Factory/Site/NavigationFactory.php
Normal file
19
src/core/Factory/Site/NavigationFactory.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory\Site;
|
||||
|
||||
use App\Core\Entity\Site\Navigation;
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
|
||||
/**
|
||||
* class NavigationFactory.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class NavigationFactory implements FactoryInterface
|
||||
{
|
||||
public function create(): Navigation
|
||||
{
|
||||
return new Navigation();
|
||||
}
|
||||
}
|
30
src/core/Factory/Site/NodeFactory.php
Normal file
30
src/core/Factory/Site/NodeFactory.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory\Site;
|
||||
|
||||
use App\Core\Entity\Site\Menu;
|
||||
use App\Core\Entity\Site\Node;
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
|
||||
/**
|
||||
* class NodeFactory.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class NodeFactory implements FactoryInterface
|
||||
{
|
||||
public function create(?Menu $menu = null, string $url = null): Node
|
||||
{
|
||||
$entity = new Node();
|
||||
|
||||
if (null !== $menu) {
|
||||
$entity->setMenu($menu);
|
||||
}
|
||||
|
||||
if (null !== $url) {
|
||||
$entity->setUrl($url);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
22
src/core/Factory/Site/Page/PageFactory.php
Normal file
22
src/core/Factory/Site/Page/PageFactory.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory\Site\Page;
|
||||
|
||||
use App\Core\Entity\Site\Page\Page;
|
||||
use App\Core\Factory\FactoryInterface;
|
||||
|
||||
/**
|
||||
* class PageFactory.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class PageFactory implements FactoryInterface
|
||||
{
|
||||
public function create(string $className, string $name): Page
|
||||
{
|
||||
$entity = new $className();
|
||||
$entity->setName($name);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
38
src/core/Factory/UserFactory.php
Normal file
38
src/core/Factory/UserFactory.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Factory;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
|
||||
|
||||
/**
|
||||
* class UserFactory.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class UserFactory implements FactoryInterface
|
||||
{
|
||||
protected TokenGeneratorInterface $tokenGenerator;
|
||||
protected UserPasswordEncoderInterface $encoder;
|
||||
|
||||
public function __construct(UserPasswordEncoderInterface $encoder)
|
||||
{
|
||||
$this->encoder = $encoder;
|
||||
}
|
||||
|
||||
public function create(?string $email = null, ?string $password = null): User
|
||||
{
|
||||
$entity = new User();
|
||||
|
||||
if (null !== $email) {
|
||||
$entity->setEmail($email);
|
||||
}
|
||||
|
||||
if (null !== $password) {
|
||||
$entity->setPassword($this->encoder->encodePassword($entity, $password));
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
}
|
30
src/core/File/FileAttribute.php
Normal file
30
src/core/File/FileAttribute.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\File;
|
||||
|
||||
use Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
/**
|
||||
* class FileAttribute.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class FileAttribute
|
||||
{
|
||||
public static function handleFile($attribute, string $class = null)
|
||||
{
|
||||
if (null === $class) {
|
||||
$class = File::class;
|
||||
}
|
||||
|
||||
if (is_string($attribute)) {
|
||||
if (file_exists($attribute)) {
|
||||
return new $class($attribute);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
}
|
321
src/core/FileManager/FsFileManager.php
Normal file
321
src/core/FileManager/FsFileManager.php
Normal file
|
@ -0,0 +1,321 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\FileManager;
|
||||
|
||||
use App\Core\Entity\FileInformation;
|
||||
use App\Core\Factory\FileInformationFactory;
|
||||
use App\Core\Form\FileUploadHandler;
|
||||
use App\Core\Repository\FileInformationRepositoryQuery;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
use function Symfony\Component\String\u;
|
||||
|
||||
/**
|
||||
* class FsFileManager.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class FsFileManager
|
||||
{
|
||||
protected array $mimes;
|
||||
protected string $path;
|
||||
protected string $pathUri;
|
||||
protected array $pathLocked;
|
||||
protected FileUploadHandler $uploadHandler;
|
||||
protected FileInformationFactory $fileInformationFactory;
|
||||
protected FileInformationRepositoryQuery $fileInformationRepositoryQuery;
|
||||
|
||||
public function __construct(
|
||||
ParameterBagInterface $params,
|
||||
FileUploadHandler $uploadHandler,
|
||||
FileInformationFactory $fileInformationFactory,
|
||||
FileInformationRepositoryQuery $fileInformationRepositoryQuery
|
||||
) {
|
||||
$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']);
|
||||
|
||||
foreach ($config['path_locked'] as $k => $v) {
|
||||
$config['path_locked'][$k] = sprintf('/%s/', $this->normalizePath($v));
|
||||
}
|
||||
|
||||
$this->pathLocked = $config['path_locked'];
|
||||
}
|
||||
|
||||
public function list(string $directory, array $options = []): array
|
||||
{
|
||||
$directory = $this->normalizePath($directory);
|
||||
|
||||
$breadcrumb = ['/'];
|
||||
|
||||
if ($directory) {
|
||||
$breadcrumb = array_merge(
|
||||
$breadcrumb,
|
||||
explode('/', $directory)
|
||||
);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'breadcrumb' => $breadcrumb,
|
||||
'parent' => dirname($directory),
|
||||
'directories' => [],
|
||||
'files' => [],
|
||||
];
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->directories()->depth('== 0')->in($this->path.'/'.$directory);
|
||||
|
||||
$this->applySort($finder, $options['sort'] ?? 'name', $options['sort_direction'] ?? 'asc');
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$data['directories'][] = [
|
||||
'basename' => $file->getBasename(),
|
||||
'path' => $directory.'/'.$file->getBasename(),
|
||||
'webPath' => $this->pathUri.'/'.$directory.'/'.$file->getBasename(),
|
||||
'locked' => $this->isLocked($directory.'/'.$file->getBasename()),
|
||||
'mime' => null,
|
||||
];
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->files()->depth('== 0')->in($this->path.'/'.$directory);
|
||||
|
||||
$this->applySort($finder, $options['sort'] ?? 'name', $options['sort_direction'] ?? 'asc');
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$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()),
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getSplInfo(string $path): ?SplFileInfo
|
||||
{
|
||||
$path = $this->normalizePath($path);
|
||||
|
||||
if ('' === $path) {
|
||||
return new SplFileInfo($this->path, '', '');
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
$finder->in($this->path)
|
||||
->depth('== '.substr_count($path, '/'))
|
||||
->name(basename($path))
|
||||
;
|
||||
|
||||
$dirname = dirname($path);
|
||||
|
||||
if ('.' === $dirname) {
|
||||
$dirname = '';
|
||||
}
|
||||
|
||||
foreach ($finder as $file) {
|
||||
if ($file->getRelativePath() === $dirname) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getFileInformation(string $path): ?FileInformation
|
||||
{
|
||||
$file = $this->getSplInfo($path);
|
||||
|
||||
if (!$file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($file->isDir()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$hash = hash_file('sha384', $file->getPathName());
|
||||
|
||||
$info = $this->fileInformationRepositoryQuery
|
||||
->where('.id = :hash')
|
||||
->setParameter(':hash', $hash)
|
||||
->findOne()
|
||||
;
|
||||
|
||||
if (!$info) {
|
||||
$info = $this->fileInformationFactory->create($hash);
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
public function createDirectory(string $name, string $path): bool
|
||||
{
|
||||
$file = $this->getSplInfo($path);
|
||||
|
||||
if (!$file || $this->isLocked($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem = new Filesystem();
|
||||
$path = $file->getPathname().'/'.$this->normalizePath($name);
|
||||
|
||||
if ($filesystem->exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem->mkdir($path, 0755);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function renameDirectory(string $name, string $path): bool
|
||||
{
|
||||
$file = $this->getSplInfo($path);
|
||||
|
||||
if (!$file || $this->isLocked($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem = new Filesystem();
|
||||
$newPath = $file->getPath().'/'.$this->normalizePath($name);
|
||||
|
||||
if ($filesystem->exists($newPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem->rename($file->getPathName(), $newPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function renameFile(string $name, string $path, bool $keepExtension = true): bool
|
||||
{
|
||||
$file = $this->getSplInfo($path);
|
||||
|
||||
if (!$file || $this->isLocked($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem = new Filesystem();
|
||||
$newPath = $file->getPath().'/'.$this->normalizePath($name);
|
||||
|
||||
if ($keepExtension && $file->getExtension()) {
|
||||
$newPath .= sprintf('.%s', $file->getExtension());
|
||||
}
|
||||
|
||||
if ($filesystem->exists($newPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filesystem->rename($file->getPathName(), $newPath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function upload($files, string $path, array $fullPaths = [])
|
||||
{
|
||||
if ($files instanceof UploadedFile) {
|
||||
$files = [$files];
|
||||
}
|
||||
|
||||
foreach ($files as $key => $file) {
|
||||
$directory = $this->path.'/'.$path;
|
||||
|
||||
if (isset($fullPaths[$key])) {
|
||||
$directory .= '/'.trim(dirname($fullPaths[$key]), '/');
|
||||
}
|
||||
|
||||
$this->uploadHandler->handleForm($file, $directory, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete(string $path): bool
|
||||
{
|
||||
$file = $this->getSplInfo($path);
|
||||
|
||||
if ($this->isLocked($file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($file) {
|
||||
$filesystem = new Filesystem();
|
||||
$filesystem->remove($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isLocked($path): bool
|
||||
{
|
||||
$file = $this->getSplInfo($path);
|
||||
|
||||
if (!$file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->pathLocked as $lock) {
|
||||
if (u($file->getPathName().'/')->startsWith($lock)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getPathUri(): string
|
||||
{
|
||||
return $this->pathUri;
|
||||
}
|
||||
|
||||
public function getMimes(): array
|
||||
{
|
||||
return $this->mimes;
|
||||
}
|
||||
|
||||
public function getPathLocked(): array
|
||||
{
|
||||
return $this->pathLocked;
|
||||
}
|
||||
|
||||
protected function applySort(Finder $finder, string $sort, string $direction)
|
||||
{
|
||||
if ('name' === $sort) {
|
||||
$finder->sortByName();
|
||||
} elseif ('modification_date' === $sort) {
|
||||
$finder->sortByModifiedTime();
|
||||
}
|
||||
|
||||
if ('desc' === $direction) {
|
||||
$finder->reverseSorting();
|
||||
}
|
||||
}
|
||||
|
||||
protected function normalizePath(string $path): string
|
||||
{
|
||||
return (string) u($path)
|
||||
->replace('..', '.')
|
||||
->replaceMatches('#/{2,}#', '/')
|
||||
->replaceMatches('#^.$#', '')
|
||||
->trim('/')
|
||||
->trim()
|
||||
;
|
||||
}
|
||||
}
|
40
src/core/Form/FileManager/DirectoryCreateType.php
Normal file
40
src/core/Form/FileManager/DirectoryCreateType.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Form\FileManager;
|
||||
|
||||
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\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\Regex;
|
||||
|
||||
class DirectoryCreateType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add(
|
||||
'name',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'Name',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new Regex([
|
||||
'pattern' => '#['.preg_quote('\\/?%*:|"<>').'\t\n\r]+#',
|
||||
'match' => false,
|
||||
]),
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
]);
|
||||
}
|
||||
}
|
40
src/core/Form/FileManager/DirectoryRenameType.php
Normal file
40
src/core/Form/FileManager/DirectoryRenameType.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Form\FileManager;
|
||||
|
||||
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\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\Regex;
|
||||
|
||||
class DirectoryRenameType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add(
|
||||
'name',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'Name',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new Regex([
|
||||
'pattern' => '#['.preg_quote('\\/?%*:|"<>').'\t\n\r]+#',
|
||||
'match' => false,
|
||||
]),
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
]);
|
||||
}
|
||||
}
|
49
src/core/Form/FileManager/FileInformationAttributeType.php
Normal file
49
src/core/Form/FileManager/FileInformationAttributeType.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Form\FileManager;
|
||||
|
||||
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\NotBlank;
|
||||
|
||||
class FileInformationAttributeType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add(
|
||||
'label',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'Label',
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
new NotBlank(),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$builder->add(
|
||||
'value',
|
||||
TextType::class,
|
||||
[
|
||||
'label' => 'Value',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
],
|
||||
'constraints' => [
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => null,
|
||||
]);
|
||||
}
|
||||
}
|
34
src/core/Form/FileManager/FileInformationType.php
Normal file
34
src/core/Form/FileManager/FileInformationType.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Form\FileManager;
|
||||
|
||||
use App\Core\Entity\FileInformation;
|
||||
use App\Core\Form\Type\CollectionType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class FileInformationType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder->add(
|
||||
'attributes',
|
||||
CollectionType::class,
|
||||
[
|
||||
'entry_type' => FileInformationAttributeType::class,
|
||||
'by_reference' => false,
|
||||
'allow_add' => true,
|
||||
'allow_delete' => true,
|
||||
'prototype' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => FileInformation::class,
|
||||
]);
|
||||
}
|
||||
}
|
37
src/core/Form/FileManager/FilePickerType.php
Normal file
37
src/core/Form/FileManager/FilePickerType.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Form\FileManager;
|
||||
|
||||
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
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
$view->vars['pattern'] = null;
|
||||
unset($view->vars['attr']['pattern']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return TextType::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
return 'file_picker';
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue