init
This commit is contained in:
commit
d36edf88cf
|
@ -0,0 +1,32 @@
|
|||
# In all environments, the following files are loaded if they exist,
|
||||
# the latter taking precedence over the former:
|
||||
#
|
||||
# * .env contains default values for the environment variables needed by the app
|
||||
# * .env.local uncommitted file with local overrides
|
||||
# * .env.$APP_ENV committed environment-specific defaults
|
||||
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||
#
|
||||
# Real environment variables win over .env files.
|
||||
#
|
||||
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||
#
|
||||
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_SECRET=
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> symfony/mailer ###
|
||||
MAILER_DSN=smtp://localhost
|
||||
###< symfony/mailer ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||
#
|
||||
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
|
||||
# DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
|
@ -0,0 +1,6 @@
|
|||
# define your env variables for the test env here
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
APP_SECRET='$ecretf0rt3st'
|
||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"standard"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12
|
||||
},
|
||||
"plugins": [
|
||||
"vue"
|
||||
],
|
||||
"rules": {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
###> symfony/framework-bundle ###
|
||||
/.env.local
|
||||
/.env.local.php
|
||||
/.env.*.local
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/public/js/
|
||||
/src/Command/TestCommand.php
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> symfony/phpunit-bridge ###
|
||||
.phpunit
|
||||
.phpunit.result.cache
|
||||
/phpunit.xml
|
||||
###< symfony/phpunit-bridge ###
|
||||
|
||||
###> symfony/webpack-encore-bundle ###
|
||||
/node_modules/
|
||||
/public/build/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
###< symfony/webpack-encore-bundle ###
|
||||
|
||||
/public/uploads/*
|
||||
!/public/uploads/.gitkeep
|
||||
/public/media/
|
||||
/migrations/*
|
||||
!/migrations/.gitkeep
|
|
@ -0,0 +1 @@
|
|||
8.0
|
|
@ -0,0 +1,97 @@
|
|||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
### Fixed
|
||||
### Changed
|
||||
|
||||
## [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
|
|
@ -0,0 +1,27 @@
|
|||
COMPOSER_BIN ?= composer
|
||||
PHP_BIN ?= php8.0
|
||||
SSH_BIN ?= ssh
|
||||
YARN_BIN ?= yarn
|
||||
NPM_BIN ?= npm
|
||||
|
||||
all: build
|
||||
|
||||
asset-watch:
|
||||
$(YARN_BIN)
|
||||
$(NPM_BIN) run watch
|
||||
|
||||
asset: js-routing
|
||||
$(YARN_BIN)
|
||||
$(NPM_BIN) run build
|
||||
|
||||
js-routing: doctrine-migration
|
||||
$(PHP_BIN) bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json
|
||||
|
||||
clean:
|
||||
rm -fr var/cache/dev/*
|
||||
rm -fr var/cache/prod/*
|
||||
|
||||
doctrine-migration:
|
||||
PHP=$(PHP_BIN) ./bin/doctrine-migrate
|
||||
|
||||
build: clean js-routing asset
|
|
@ -0,0 +1,16 @@
|
|||
# Murph
|
||||
|
||||
Muprh 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/)
|
|
@ -0,0 +1,53 @@
|
|||
## [Unreleased]
|
||||
|
||||
## 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 }
|
||||
```
|
|
@ -0,0 +1,6 @@
|
|||
/* Custom variables */
|
||||
|
||||
@import "../../core/Resources/assets/css/admin.scss";
|
||||
|
||||
/* Custom CSS */
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/* CSS of you app */
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="92.5"
|
||||
height="92.500008"
|
||||
viewBox="0 0 24.473958 24.473961"
|
||||
version="1.1"
|
||||
id="svg2782"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
sodipodi:docname="logo.svg">
|
||||
<defs
|
||||
id="defs2776" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="144.24896"
|
||||
inkscape:cy="62.558177"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1918"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="41"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata2779">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-93.596354,-136.59635)">
|
||||
<g
|
||||
transform="translate(14.977383,9.0140333)"
|
||||
id="g2760">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="rect2455"
|
||||
d="m 80.981321,127.58232 h 19.749259 c 1.30874,0 2.36235,1.05361 2.36235,2.36235 v 19.74926 c 0,1.30874 -1.05361,2.36235 -2.36235,2.36235 H 80.981321 c -1.30874,0 -2.36235,-1.05361 -2.36235,-2.36235 v -19.74926 c 0,-1.30874 1.05361,-2.36235 2.36235,-2.36235 z"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;vector-effect:none;fill:#1e2430;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10.58333302;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke fill markers;enable-background:accumulate" />
|
||||
<g
|
||||
transform="translate(-28.224115,84.535074)"
|
||||
id="text2474-5"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24.23528671px;line-height:125%;font-family:Tahoma;-inkscape-font-specification:'Tahoma, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
aria-label="M">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path2522"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Trebuchet MS';-inkscape-font-specification:'Trebuchet MS';fill:#ffcc00;fill-opacity:1;stroke-width:0.26458332px"
|
||||
d="m 125.90001,62.475697 h -2.98208 l -1.79871,-9.348573 -3.49093,9.573412 h -1.10052 l -3.49093,-9.573412 -1.86971,9.348573 h -2.97024 l 3.49092,-17.34811 h 1.63304 l 3.75126,11.679798 3.66843,-11.679798 h 1.62121 z" />
|
||||
</g>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="rect2455-3-6"
|
||||
d="m 102.39375,128.26496 -23.092649,23.09213 c 0.427643,0.43204 1.021784,0.69919 1.680519,0.69919 h 19.74918 c 1.30874,0 2.36213,-1.05339 2.36213,-2.36213 v -19.74918 c 0,-0.65861 -0.26729,-1.25238 -0.69918,-1.68001 z"
|
||||
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;vector-effect:none;fill:#19b4db;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10.58333302;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke fill markers;enable-background:accumulate" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path2525-9-7"
|
||||
d="m 94.723251,132.53384 -4.531505,4.53151 -1.343589,4.27726 -0.713134,-2.22054 -2.044319,2.04432 2.213301,6.0694 h 1.10019 l 3.491262,-9.57306 1.798338,9.34826 h 2.982249 z m -11.105782,11.10527 -3.371887,3.37188 h 2.697509 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:24.23528481px;line-height:125%;font-family:'Trebuchet MS';-inkscape-font-specification:'Trebuchet MS';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#1e2430;fill-opacity:1;stroke:#1e2430;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.3 KiB |
|
@ -0,0 +1 @@
|
|||
import '../../core/Resources/assets/js/admin.js'
|
|
@ -0,0 +1 @@
|
|||
import '../css/app.scss'
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
use Symfony\Component\ErrorHandler\Debug;
|
||||
|
||||
if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
|
||||
echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL;
|
||||
}
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
|
||||
if (!class_exists(Application::class) || !class_exists(Dotenv::class)) {
|
||||
throw new LogicException('You need to add "symfony/framework-bundle" and "symfony/dotenv" as Composer dependencies.');
|
||||
}
|
||||
|
||||
$input = new ArgvInput();
|
||||
if (null !== $env = $input->getParameterOption(['--env', '-e'], null, true)) {
|
||||
putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
|
||||
}
|
||||
|
||||
if ($input->hasParameterOption('--no-debug', true)) {
|
||||
putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
|
||||
}
|
||||
|
||||
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||
|
||||
if ($_SERVER['APP_DEBUG']) {
|
||||
umask(0000);
|
||||
|
||||
if (class_exists(Debug::class)) {
|
||||
Debug::enable();
|
||||
}
|
||||
}
|
||||
|
||||
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
|
||||
$application = new Application($kernel);
|
||||
$application->run($input);
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
CLASS_NAME="$(echo "yes" | "$PHP" ./bin/console doctrine:migration:diff -e dev | grep -o "Version[0-9]*" | tail -n 1)"
|
||||
|
||||
if [ -n "$CLASS_NAME" ]; then
|
||||
echo "yes" | "$PHP" ./bin/console doctrine:migration:exec --up "DoctrineMigrations\\$CLASS_NAME" -e dev
|
||||
fi
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (!file_exists(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (false === getenv('SYMFONY_PHPUNIT_DIR')) {
|
||||
putenv('SYMFONY_PHPUNIT_DIR='.__DIR__.'/.phpunit');
|
||||
}
|
||||
|
||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
|
@ -0,0 +1,121 @@
|
|||
{
|
||||
"name": "murph/murph-skeleton",
|
||||
"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"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/browser-kit": "^5.4",
|
||||
"symfony/css-selector": "^5.4",
|
||||
"symfony/debug-bundle": "^5.4",
|
||||
"symfony/maker-bundle": "^1.0",
|
||||
"symfony/phpunit-bridge": "^5.4",
|
||||
"symfony/stopwatch": "^5.4",
|
||||
"symfony/var-dumper": "^5.4",
|
||||
"symfony/web-profiler-bundle": "^5.4"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": {
|
||||
"*": "dist"
|
||||
},
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"symfony/flex": true
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/",
|
||||
"App\\Core\\": "core/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"App\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-ctype": "*",
|
||||
"symfony/polyfill-iconv": "*",
|
||||
"symfony/polyfill-php72": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"auto-scripts": {
|
||||
"cache:clear": "symfony-cmd",
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||
},
|
||||
"post-install-cmd": [
|
||||
"@auto-scripts"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@auto-scripts"
|
||||
]
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/symfony": "*"
|
||||
},
|
||||
"extra": {
|
||||
"symfony": {
|
||||
"allow-contrib": false,
|
||||
"require": "5.4.*"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
|
||||
Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
|
||||
Knp\Bundle\PaginatorBundle\KnpPaginatorBundle::class => ['all' => true],
|
||||
Knp\DoctrineBehaviors\DoctrineBehaviorsBundle::class => ['all' => true],
|
||||
FOS\JsRoutingBundle\FOSJsRoutingBundle::class => ['all' => true],
|
||||
SPE\FilesizeExtensionBundle\SPEFilesizeExtensionBundle::class => ['all' => true],
|
||||
Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true],
|
||||
App\Core\Bundle\CoreBundle::class => ['all' => true],
|
||||
App\Bundle\AppBundle::class => ['all' => true],
|
||||
];
|
|
@ -0,0 +1,6 @@
|
|||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
SensioLabs\AnsiConverter\Bridge\Twig\AnsiExtension: null
|
|
@ -0,0 +1,42 @@
|
|||
core:
|
||||
site:
|
||||
name: "Murph"
|
||||
logo: "build/images/core/logo.svg"
|
||||
# controllers:
|
||||
# - {name: 'Foo', action: 'App\Controller\ExampleController::foo'}
|
||||
pages:
|
||||
App\Entity\Page\SimplePage:
|
||||
name: 'Simple page'
|
||||
templates:
|
||||
- {name: "Default", file: "page/simple/default.html.twig"}
|
||||
file_manager:
|
||||
# mimes:
|
||||
# - 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
|
||||
# path: "%kernel.project_dir%/public/uploads"
|
||||
# path_uri: "/uploads"
|
||||
# path_locked:
|
||||
# - "%kernel.project_dir%/public/uploads"
|
|
@ -0,0 +1,3 @@
|
|||
framework:
|
||||
assets:
|
||||
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
|
|
@ -0,0 +1,19 @@
|
|||
framework:
|
||||
cache:
|
||||
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||
#prefix_seed: your_vendor_name/app_name
|
||||
|
||||
# The "app" cache stores to the filesystem by default.
|
||||
# The data in this cache should persist between deploys.
|
||||
# Other options include:
|
||||
|
||||
# Redis
|
||||
#app: cache.adapter.redis
|
||||
#default_redis_provider: redis://localhost
|
||||
|
||||
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||
#app: cache.adapter.apcu
|
||||
|
||||
# Namespaced pools use the above "app" backend by default
|
||||
#pools:
|
||||
#my.dedicated.cache: null
|
|
@ -0,0 +1,4 @@
|
|||
debug:
|
||||
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
|
||||
# See the "server:dump" command to start a new server.
|
||||
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
|
|
@ -0,0 +1,19 @@
|
|||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
channels: ["!event"]
|
||||
# uncomment to get logging in your browser
|
||||
# you may have to allow bigger header sizes in your Web server configuration
|
||||
#firephp:
|
||||
# type: firephp
|
||||
# level: info
|
||||
#chromephp:
|
||||
# type: chromephp
|
||||
# level: info
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine", "!console"]
|
|
@ -0,0 +1,6 @@
|
|||
web_profiler:
|
||||
toolbar: true
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { only_exceptions: false }
|
|
@ -0,0 +1,30 @@
|
|||
doctrine:
|
||||
dbal:
|
||||
url: '%env(resolve:DATABASE_URL)%'
|
||||
|
||||
# IMPORTANT: You MUST configure your server version,
|
||||
# either here or in the DATABASE_URL env var (see .env file)
|
||||
#server_version: '13'
|
||||
orm:
|
||||
auto_generate_proxy_classes: true
|
||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||
auto_mapping: true
|
||||
mappings:
|
||||
App\Core\Entity:
|
||||
is_bundle: false
|
||||
type: annotation
|
||||
dir: '%kernel.project_dir%/core/Entity'
|
||||
prefix: 'App\Core\Entity'
|
||||
alias: App\Core\Entity
|
||||
App\Entity:
|
||||
is_bundle: false
|
||||
type: annotation
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App\Entity
|
||||
gedmo_tree:
|
||||
type: annotation
|
||||
prefix: Gedmo\Tree\Entity
|
||||
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Tree/Entity"
|
||||
alias: GedmoTree # (optional) it will default to the name set for the mapping
|
||||
is_bundle: false
|
|
@ -0,0 +1,5 @@
|
|||
doctrine_migrations:
|
||||
migrations_paths:
|
||||
# namespace is arbitrary but should be different from App\Migrations
|
||||
# as migrations classes should NOT be autoloaded
|
||||
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
|
@ -0,0 +1,3 @@
|
|||
fos_js_routing:
|
||||
routes_to_expose:
|
||||
- liip_imagine_filter
|
|
@ -0,0 +1,17 @@
|
|||
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||
framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
#csrf_protection: true
|
||||
#http_method_override: true
|
||||
|
||||
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||
# Remove or comment this section to explicitly disable session support.
|
||||
session:
|
||||
handler_id: null
|
||||
cookie_secure: auto
|
||||
cookie_samesite: lax
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
php_errors:
|
||||
log: true
|
|
@ -0,0 +1,11 @@
|
|||
knp_paginator:
|
||||
page_range: 4
|
||||
default_options:
|
||||
page_name: page # page query parameter name
|
||||
sort_field_name: sort # sort field query parameter name
|
||||
sort_direction_name: direction # sort direction query parameter name
|
||||
distinct: true # ensure distinct results, useful when ORM queries are using GROUP BY statements
|
||||
template:
|
||||
pagination: '@Core/pager/sliding.html.twig' # sliding pagination controls template
|
||||
sortable: '@KnpPaginator/Pagination/sortable_link.html.twig' # sort link template
|
||||
filtration: '@KnpPaginator/Pagination/filtration.html.twig' # filters template
|
|
@ -0,0 +1,9 @@
|
|||
# See dos how to configure the bundle: https://symfony.com/doc/current/bundles/LiipImagineBundle/basic-usage.html
|
||||
liip_imagine:
|
||||
# valid drivers options include "gd" or "gmagick" or "imagick"
|
||||
driver: "imagick"
|
||||
filter_sets:
|
||||
file_manager_thumbnail_filter:
|
||||
filters:
|
||||
downscale:
|
||||
max: [120, 120]
|
|
@ -0,0 +1,5 @@
|
|||
framework:
|
||||
mailer:
|
||||
dsn: '%env(MAILER_DSN)%'
|
||||
headers:
|
||||
From: 'Example <example@localhost>'
|
|
@ -0,0 +1,16 @@
|
|||
framework:
|
||||
notifier:
|
||||
#chatter_transports:
|
||||
# slack: '%env(SLACK_DSN)%'
|
||||
# telegram: '%env(TELEGRAM_DSN)%'
|
||||
#texter_transports:
|
||||
# twilio: '%env(TWILIO_DSN)%'
|
||||
# nexmo: '%env(NEXMO_DSN)%'
|
||||
channel_policy:
|
||||
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||
urgent: ['email']
|
||||
high: ['email']
|
||||
medium: ['email']
|
||||
low: ['email']
|
||||
admin_recipients:
|
||||
- { email: admin@example.com }
|
|
@ -0,0 +1,8 @@
|
|||
# As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||
#monolog:
|
||||
# channels: [deprecation]
|
||||
# handlers:
|
||||
# deprecation:
|
||||
# type: stream
|
||||
# channels: [deprecation]
|
||||
# path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log"
|
|
@ -0,0 +1,20 @@
|
|||
doctrine:
|
||||
orm:
|
||||
auto_generate_proxy_classes: false
|
||||
metadata_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
query_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.system_cache_pool
|
||||
result_cache_driver:
|
||||
type: pool
|
||||
pool: doctrine.result_cache_pool
|
||||
|
||||
framework:
|
||||
cache:
|
||||
pools:
|
||||
doctrine.result_cache_pool:
|
||||
adapter: cache.app
|
||||
doctrine.system_cache_pool:
|
||||
adapter: cache.system
|
|
@ -0,0 +1,16 @@
|
|||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||
nested:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
console:
|
||||
type: console
|
||||
process_psr_3_messages: false
|
||||
channels: ["!event", "!doctrine"]
|
|
@ -0,0 +1,3 @@
|
|||
framework:
|
||||
router:
|
||||
strict_requirements: null
|
|
@ -0,0 +1,4 @@
|
|||
#webpack_encore:
|
||||
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||
# Available in version 1.2
|
||||
#cache: true
|
|
@ -0,0 +1,7 @@
|
|||
framework:
|
||||
router:
|
||||
utf8: true
|
||||
|
||||
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||
#default_uri: http://localhost
|
|
@ -0,0 +1,15 @@
|
|||
# See the configuration reference at https://github.com/scheb/2fa/blob/master/doc/configuration.md
|
||||
scheb_two_factor:
|
||||
google:
|
||||
enabled: true
|
||||
issuer: "Muprh"
|
||||
server_name:
|
||||
digits: 6
|
||||
window: 1
|
||||
template: "@Core/auth/2fa.html.twig"
|
||||
security_tokens:
|
||||
- Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
|
||||
# If you're using guard-based authentication, you have to use this one:
|
||||
- Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken
|
||||
# If you're using authenticator-based security (introduced in Symfony 5.1), you have to use this one:
|
||||
# - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken
|
|
@ -0,0 +1,54 @@
|
|||
security:
|
||||
encoders:
|
||||
App\Entity\User:
|
||||
algorithm: auto
|
||||
|
||||
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
|
||||
providers:
|
||||
# used to reload user from session & other features (e.g. switch_user)
|
||||
app_user_provider:
|
||||
entity:
|
||||
class: App\Entity\User
|
||||
property: email
|
||||
|
||||
role_hierarchy:
|
||||
ROLE_WRITER: ROLE_USER
|
||||
ROLE_ADMIN: ROLE_WRITER
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
anonymous: ~
|
||||
two_factor:
|
||||
auth_form_path: 2fa_login # The route name you have used in the routes.yaml
|
||||
check_path: 2fa_login_check # The route name you have used in the routes.yaml
|
||||
guard:
|
||||
authenticators:
|
||||
- App\Core\Authenticator\LoginFormAuthenticator
|
||||
form_login:
|
||||
login_path: auth_login
|
||||
check_path: auth_login
|
||||
csrf_token_generator: security.csrf.token_manager
|
||||
logout:
|
||||
path: auth_logout
|
||||
target: /
|
||||
remember_me:
|
||||
secret: '%kernel.secret%'
|
||||
lifetime: 604800
|
||||
path: /
|
||||
|
||||
# Easy way to control access for large sections of your site
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/resetting, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||
- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
|
||||
- { path: ^/admin/user, roles: ROLE_ADMIN }
|
||||
- { path: ^/admin/task, roles: ROLE_ADMIN }
|
||||
- { path: ^/admin/setting, roles: ROLE_ADMIN }
|
||||
- { path: ^/admin/site, roles: ROLE_WRITER }
|
||||
- { path: ^/admin/file_manager, roles: ROLE_WRITER }
|
||||
- { path: ^/admin, roles: ROLE_USER }
|
||||
- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
|
@ -0,0 +1,3 @@
|
|||
sensio_framework_extra:
|
||||
router:
|
||||
annotations: false
|
|
@ -0,0 +1,4 @@
|
|||
# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
|
||||
# See the official DoctrineExtensions documentation for more details: https://github.com/Atlantic18/DoctrineExtensions/tree/master/doc/
|
||||
stof_doctrine_extensions:
|
||||
default_locale: en_US
|
|
@ -0,0 +1,4 @@
|
|||
framework:
|
||||
test: true
|
||||
session:
|
||||
storage_id: session.storage.mock_file
|
|
@ -0,0 +1,12 @@
|
|||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
channels: ["!event"]
|
||||
nested:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
|
@ -0,0 +1,2 @@
|
|||
twig:
|
||||
strict_variables: true
|
|
@ -0,0 +1,3 @@
|
|||
framework:
|
||||
validation:
|
||||
not_compromised_password: false
|
|
@ -0,0 +1,6 @@
|
|||
web_profiler:
|
||||
toolbar: false
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { collect: false }
|
|
@ -0,0 +1,2 @@
|
|||
#webpack_encore:
|
||||
# strict_mode: false
|
|
@ -0,0 +1,8 @@
|
|||
framework:
|
||||
default_locale: fr
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
paths:
|
||||
- '%kernel.project_dir%/core/Resources/translations'
|
||||
fallbacks:
|
||||
- en
|
|
@ -0,0 +1,6 @@
|
|||
twig:
|
||||
default_path: '%kernel.project_dir%/templates'
|
||||
form_themes: ['@Core/form/bootstrap_4_form_theme.html.twig']
|
||||
paths:
|
||||
'%kernel.project_dir%/templates/core/': Core
|
||||
'%kernel.project_dir%/core/Resources/views/': Core
|
|
@ -0,0 +1,8 @@
|
|||
framework:
|
||||
validation:
|
||||
email_validation_mode: html5
|
||||
|
||||
# Enables validator auto-mapping support.
|
||||
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||
#auto_mapping:
|
||||
# App\Entity\: []
|
|
@ -0,0 +1,30 @@
|
|||
webpack_encore:
|
||||
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
|
||||
output_path: '%kernel.project_dir%/public/build'
|
||||
# If multiple builds are defined (as shown below), you can disable the default build:
|
||||
# output_path: false
|
||||
|
||||
# Set attributes that will be rendered on all script and link tags
|
||||
script_attributes:
|
||||
defer: true
|
||||
# link_attributes:
|
||||
|
||||
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
|
||||
# crossorigin: 'anonymous'
|
||||
|
||||
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
|
||||
# preload: true
|
||||
|
||||
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
|
||||
# strict_mode: false
|
||||
|
||||
# If you have multiple builds:
|
||||
# builds:
|
||||
# pass "frontend" as the 3rg arg to the Twig functions
|
||||
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
|
||||
|
||||
# frontend: '%kernel.project_dir%/public/frontend/build'
|
||||
|
||||
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||
# Put in config/packages/prod/webpack_encore.yaml
|
||||
# cache: true
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#index:
|
||||
# path: /
|
||||
# controller: App\Controller\DefaultController::index
|
||||
|
||||
site_route:
|
||||
resource: 'site.route_loader::loadRoutes'
|
||||
type: extra
|
||||
|
||||
2fa_login:
|
||||
path: /2fa
|
||||
defaults:
|
||||
_controller: "scheb_two_factor.form_controller:form"
|
||||
|
||||
2fa_login_check:
|
||||
path: /2fa_check
|
|
@ -0,0 +1,11 @@
|
|||
controllers:
|
||||
resource: ../../src/Controller/
|
||||
type: annotation
|
||||
|
||||
core_controllers:
|
||||
resource: ../../core/Controller/
|
||||
type: annotation
|
||||
|
||||
kernel:
|
||||
resource: ../../src/Kernel.php
|
||||
type: annotation
|
|
@ -0,0 +1,3 @@
|
|||
_errors:
|
||||
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||
prefix: /_error
|
|
@ -0,0 +1,7 @@
|
|||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
prefix: /_profiler
|
|
@ -0,0 +1,2 @@
|
|||
_liip_imagine:
|
||||
resource: "@LiipImagineBundle/Resources/config/routing.yaml"
|
|
@ -0,0 +1,7 @@
|
|||
2fa_login:
|
||||
path: /2fa
|
||||
defaults:
|
||||
_controller: "scheb_two_factor.form_controller:form"
|
||||
|
||||
2fa_login_check:
|
||||
path: /2fa_check
|
|
@ -0,0 +1,65 @@
|
|||
# This file is the entry point to configure your own services.
|
||||
# Files in the packages/ subdirectory configure your dependencies.
|
||||
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
||||
parameters:
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
|
||||
# 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/'
|
||||
exclude:
|
||||
- '../core/DependencyInjection/'
|
||||
- '../core/Entity/'
|
||||
|
||||
App\Core\EventListener\RedirectListener:
|
||||
tags:
|
||||
- { name: kernel.event_listener, event: kernel.exception }
|
||||
|
||||
App\Core\EventListener\AnalyticListener:
|
||||
tags:
|
||||
- { name: kernel.event_listener, event: kernel.request }
|
||||
|
||||
App\:
|
||||
resource: '../src/'
|
||||
exclude:
|
||||
- '../src/DependencyInjection/'
|
||||
- '../src/Entity/'
|
||||
- '../src/Kernel.php'
|
||||
- '../src/Tests/'
|
||||
|
||||
App\Core\Maker\:
|
||||
resource: '../core/Maker/'
|
||||
tags: ['maker.command']
|
||||
|
||||
App\Core\Controller\:
|
||||
resource: '../core/Controller/'
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
App\Controller\:
|
||||
resource: '../src/Controller/'
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
site.route_loader:
|
||||
class: App\Core\Router\SiteRouteLoader
|
||||
tags: [routing.loader]
|
||||
|
||||
gedmo.listener.tree:
|
||||
class: Gedmo\Tree\TreeListener
|
||||
tags:
|
||||
- { name: doctrine.event_subscriber, connection: default }
|
||||
calls:
|
||||
- [ setAnnotationReader, [ "@annotation_reader" ] ]
|
||||
|
||||
App\UrlGenerator\FooUrlGenerator:
|
||||
public: true
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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 = [];
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,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';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?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'];
|
||||
|
||||
return parent::render($view, $parameters, $response);
|
||||
}
|
||||
|
||||
abstract protected function getSection(): string;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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 '';
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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 '';
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
<?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\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,
|
||||
Request $request
|
||||
): Response {
|
||||
$entity = $factory->create($node->getMenu());
|
||||
$form = $this->createForm(EntityType::class, $entity, [
|
||||
'pages' => $pageLocator->getPages(),
|
||||
'controllers' => $controllerLocator->getControllers(),
|
||||
'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,
|
||||
Request $request,
|
||||
string $tab = 'content'
|
||||
): Response {
|
||||
$form = $this->createForm(EntityType::class, $entity, [
|
||||
'pages' => $pageLocator->getPages(),
|
||||
'controllers' => $controllerLocator->getControllers(),
|
||||
'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)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
<?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;
|
||||
|
||||
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): Response
|
||||
{
|
||||
$entity = $factory->create($this->getUser());
|
||||
|
||||
return $this->doNew($factory->create(), $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';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace App\Core\Crud\Exception;
|
||||
|
||||
/**
|
||||
* class CrudConfigurationException.
|
||||
*
|
||||
* @author Simon Vieille <simon@deblan.fr>
|
||||
*/
|
||||
class CrudConfigurationException extends \Exception
|
||||
{
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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'];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?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('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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue