Browse Source

use murph/murph-core instead of the core directory

v2
Simon Vieille 3 months ago
parent
commit
b809ee9c88
  1. 2
      assets/css/admin.scss
  2. 2
      assets/js/admin.js
  3. 57
      composer.json
  4. 2
      config/packages/doctrine.yaml
  5. 2
      config/packages/translation.yaml
  6. 2
      config/packages/twig.yaml
  7. 2
      config/routes/annotations.yaml
  8. 14
      config/services.yaml
  9. 204
      core/Analytic/DateRangeAnalytic.php
  10. 20
      core/Annotation/UrlGenerator.php
  11. 96
      core/Authenticator/LoginFormAuthenticator.php
  12. 24
      core/Bundle/CoreBundle.php
  13. 73
      core/Cache/SymfonyCacheManager.php
  14. 108
      core/Command/UserCreateCommand.php
  15. 0
      core/Controller/.gitignore
  16. 150
      core/Controller/Account/AccountAdminController.php
  17. 41
      core/Controller/Admin/AdminController.php
  18. 342
      core/Controller/Admin/Crud/CrudController.php
  19. 38
      core/Controller/Analytic/AnalyticController.php
  20. 155
      core/Controller/Auth/AuthController.php
  21. 27
      core/Controller/Dashboard/DashboardAdminController.php
  22. 438
      core/Controller/FileManager/FileManagerAdminController.php
  23. 154
      core/Controller/Redirect/RedirectAdminController.php
  24. 81
      core/Controller/Setting/NavigationSettingAdminController.php
  25. 99
      core/Controller/Setting/SettingAdminController.php
  26. 82
      core/Controller/Site/MenuAdminController.php
  27. 142
      core/Controller/Site/NavigationAdminController.php
  28. 310
      core/Controller/Site/NodeAdminController.php
  29. 136
      core/Controller/Site/PageAdminController.php
  30. 57
      core/Controller/Site/PageController.php
  31. 40
      core/Controller/Site/SitemapController.php
  32. 87
      core/Controller/Site/TreeAdminController.php
  33. 62
      core/Controller/Task/TaskAdminController.php
  34. 131
      core/Controller/User/UserAdminController.php
  35. 330
      core/Crud/CrudConfiguration.php
  36. 12
      core/Crud/Exception/CrudConfigurationException.php
  37. 41
      core/Crud/Field/ButtonField.php
  38. 25
      core/Crud/Field/DateField.php
  39. 25
      core/Crud/Field/DatetimeField.php
  40. 92
      core/Crud/Field/Field.php
  41. 27
      core/Crud/Field/ImageField.php
  42. 24
      core/Crud/Field/TextField.php
  43. 138
      core/DependencyInjection/Configuration.php
  44. 28
      core/DependencyInjection/CoreExtension.php
  45. 59
      core/Doctrine/Timestampable.php
  46. 0
      core/Entity/.gitignore
  47. 103
      core/Entity/Analytic/Referer.php
  48. 151
      core/Entity/Analytic/View.php
  49. 7
      core/Entity/EntityInterface.php
  50. 48
      core/Entity/FileInformation.php
  51. 111
      core/Entity/NavigationSetting.php
  52. 211
      core/Entity/Redirect.php
  53. 92
      core/Entity/Setting.php
  54. 146
      core/Entity/Site/Menu.php
  55. 243
      core/Entity/Site/Navigation.php
  56. 676
      core/Entity/Site/Node.php
  57. 82
      core/Entity/Site/Page/Block.php
  58. 21
      core/Entity/Site/Page/ChoiceBlock.php
  59. 21
      core/Entity/Site/Page/CollectionBlock.php
  60. 27
      core/Entity/Site/Page/FileBlock.php
  61. 276
      core/Entity/Site/Page/Page.php
  62. 12
      core/Entity/Site/Page/TextBlock.php
  63. 28
      core/Event/Account/PasswordRequestEvent.php
  64. 33
      core/Event/EntityManager/EntityManagerEvent.php
  65. 44
      core/Event/Page/PageEditEvent.php
  66. 35
      core/Event/Setting/NavigationSettingEvent.php
  67. 35
      core/Event/Setting/SettingEvent.php
  68. 37
      core/Event/Task/TaskInitEvent.php
  69. 44
      core/Event/Task/TaskRunRequestedEvent.php
  70. 153
      core/EventListener/AnalyticListener.php
  71. 50
      core/EventListener/RedirectListener.php
  72. 69
      core/EventSubscriber/Account/PasswordRequestEventSubscriber.php
  73. 52
      core/EventSubscriber/EntityManagerEventSubscriber.php
  74. 32
      core/EventSubscriber/NavigationSettingEventSubscriber.php
  75. 69
      core/EventSubscriber/RequestSecurityEventSubscriber.php
  76. 32
      core/EventSubscriber/SettingEventSubscriber.php
  77. 53
      core/EventSubscriber/Site/ForcedDomainEventSubscriber.php
  78. 107
      core/EventSubscriber/Site/MenuEventSubscriber.php
  79. 46
      core/EventSubscriber/Site/NavigationEventSubscriber.php
  80. 183
      core/EventSubscriber/Site/NodeEventSubscriber.php
  81. 59
      core/EventSubscriber/Site/Page/BlockEventSubscriber.php
  82. 56
      core/EventSubscriber/Site/Page/PageEventSubscriber.php
  83. 53
      core/EventSubscriber/Site/SiteEventSubscriber.php
  84. 36
      core/EventSubscriber/Task/CacheCleanTaskEventSubscriber.php
  85. 33
      core/EventSubscriber/Task/TaskEventSubscriber.php
  86. 22
      core/Factory/Analytic/RefererFactory.php
  87. 22
      core/Factory/Analytic/ViewFactory.php
  88. 12
      core/Factory/FactoryInterface.php
  89. 16
      core/Factory/FileInformationFactory.php
  90. 26
      core/Factory/NavigationSettingFactory.php
  91. 14
      core/Factory/RedirectFactory.php
  92. 22
      core/Factory/SettingFactory.php
  93. 26
      core/Factory/Site/MenuFactory.php
  94. 19
      core/Factory/Site/NavigationFactory.php
  95. 30
      core/Factory/Site/NodeFactory.php
  96. 22
      core/Factory/Site/Page/PageFactory.php
  97. 38
      core/Factory/UserFactory.php
  98. 30
      core/File/FileAttribute.php
  99. 321
      core/FileManager/FsFileManager.php
  100. 40
      core/Form/FileManager/DirectoryCreateType.php
  101. Some files were not shown because too many files have changed in this diff Show More

2
assets/css/admin.scss

@ -1,6 +1,6 @@
/* Custom variables */
@import "../../core/Resources/assets/css/admin.scss";
@import "../../vendor/murph/murph-core/src/core/Resources/assets/css/admin.scss";
/* Custom CSS */

2
assets/js/admin.js

@ -1 +1 @@
import '../../core/Resources/assets/js/admin.js'
import '../../vendor/murph/murph-core/src/core/Resources/assets/js/admin.js'

57
composer.json

@ -7,59 +7,7 @@
"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"
"murph/murph-core": "^1.0"
},
"require-dev": {
"symfony/browser-kit": "^5.4",
@ -83,8 +31,7 @@
},
"autoload": {
"psr-4": {
"App\\": "src/",
"App\\Core\\": "core/"
"App\\": "src/"
}
},
"autoload-dev": {

2
config/packages/doctrine.yaml

@ -13,7 +13,7 @@ doctrine:
App\Core\Entity:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/core/Entity'
dir: '%kernel.project_dir%/vendor/murph/murph-core/src/core/Entity'
prefix: 'App\Core\Entity'
alias: App\Core\Entity
App\Entity:

2
config/packages/translation.yaml

@ -3,6 +3,6 @@ framework:
translator:
default_path: '%kernel.project_dir%/translations'
paths:
- '%kernel.project_dir%/core/Resources/translations'
- '%kernel.project_dir%/vendor/murph/murph-core/src/core/Resources/translations'
fallbacks:
- en

2
config/packages/twig.yaml

@ -3,4 +3,4 @@ twig:
form_themes: ['@Core/form/bootstrap_4_form_theme.html.twig']
paths:
'%kernel.project_dir%/templates/core/': Core
'%kernel.project_dir%/core/Resources/views/': Core
'%kernel.project_dir%/vendor/murph/murph-core/src/core/Resources/views/': Core

2
config/routes/annotations.yaml

@ -3,7 +3,7 @@ controllers:
type: annotation
core_controllers:
resource: ../../core/Controller/
resource: ../../vendor/murph/murph-core/src/core/Controller/
type: annotation
kernel:

14
config/services.yaml

@ -14,10 +14,10 @@ services:
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\Core\:
resource: '../core/'
resource: '../vendor/murph/murph-core/src/core/'
exclude:
- '../core/DependencyInjection/'
- '../core/Entity/'
- '../vendor/murph/murph-core/src/core/DependencyInjection/'
- '../vendor/murph/murph-core/src/core/Entity/'
App\Core\EventListener\RedirectListener:
tags:
@ -36,11 +36,11 @@ services:
- '../src/Tests/'
App\Core\Maker\:
resource: '../core/Maker/'
resource: '../vendor/murph/murph-core/src/core/Maker/'
tags: ['maker.command']
App\Core\Controller\:
resource: '../core/Controller/'
resource: '../vendor/murph/murph-core/src/core/Controller/'
tags: ['controller.service_arguments']
App\Controller\:
@ -58,8 +58,8 @@ services:
calls:
- [ setAnnotationReader, [ "@annotation_reader" ] ]
App\UrlGenerator\FooUrlGenerator:
public: true
# App\UrlGenerator\FooUrlGenerator:
# public: true
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

204
core/Analytic/DateRangeAnalytic.php

@ -1,204 +0,0 @@
<?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
core/Annotation/UrlGenerator.php

@ -1,20 +0,0 @@
<?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
core/Authenticator/LoginFormAuthenticator.php

@ -1,96 +0,0 @@
<?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
core/Bundle/CoreBundle.php

@ -1,24 +0,0 @@
<?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
core/Cache/SymfonyCacheManager.php

@ -1,73 +0,0 @@
<?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
core/Command/UserCreateCommand.php

@ -1,108 +0,0 @@
<?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
core/Controller/.gitignore vendored

150
core/Controller/Account/AccountAdminController.php

@ -1,150 +0,0 @@
<?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
core/Controller/Admin/AdminController.php

@ -1,41 +0,0 @@
<?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
core/Controller/Admin/Crud/CrudController.php

@ -1,342 +0,0 @@
<?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
core/Controller/Analytic/AnalyticController.php

@ -1,38 +0,0 @@
<?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
core/Controller/Auth/AuthController.php

@ -1,155 +0,0 @@
<?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
core/Controller/Dashboard/DashboardAdminController.php

@ -1,27 +0,0 @@
<?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
core/Controller/FileManager/FileManagerAdminController.php

@ -1,438 +0,0 @@
<?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('