Compare commits

...

155 commits

Author SHA1 Message Date
Simon Vieille 79d549254f Merge branch 'develop'
All checks were successful
ci/woodpecker/push/woodpecker/2 Pipeline was successful
ci/woodpecker/push/woodpecker/1 Pipeline was successful
2024-03-25 16:00:53 +01:00
Simon Vieille c79e96e291
apply consensus as access decision manager strategy
All checks were successful
ci/woodpecker/push/woodpecker/2 Pipeline was successful
ci/woodpecker/push/woodpecker/1 Pipeline was successful
2024-03-25 16:00:51 +01:00
Simon Vieille 092b490ae2 Merge branch 'develop'
Some checks failed
ci/woodpecker/push/woodpecker/1 Pipeline failed
ci/woodpecker/push/woodpecker/2 Pipeline was successful
2024-03-25 15:46:57 +01:00
Simon Vieille fcecf74f90
add default voter
Some checks failed
ci/woodpecker/push/woodpecker/2 Pipeline failed
ci/woodpecker/push/woodpecker/1 Pipeline failed
2024-03-25 15:46:53 +01:00
Simon Vieille c00723a246 Merge branch 'develop'
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker/2 Pipeline was successful
ci/woodpecker/push/woodpecker/1 Pipeline was successful
2023-11-01 16:34:57 +01:00
Simon Vieille e81b11c5ec
release v1.23.0
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-11-01 16:34:54 +01:00
Simon Vieille af2212ac8c Merge branch 'develop'
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-11-01 16:33:49 +01:00
Simon Vieille b57b0e3770
update murph
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-11-01 16:33:44 +01:00
Simon Vieille ba76a83163 Merge branch 'develop' 2023-09-28 18:14:35 +02:00
Simon Vieille 2874f579dc
update changelog 2023-09-28 18:14:22 +02:00
Simon Vieille 287e1d66cb
fix #1: add UniqueEntity constraint in the User entity 2023-09-28 18:13:18 +02:00
Simon Vieille b23a4fe9c5
update changelog 2023-09-07 14:11:11 +02:00
Simon Vieille 59bbd24b76
update woodpecker ci base file 2023-09-07 14:10:26 +02:00
Simon Vieille 2dfe6ef1ca Merge branch 'develop' 2023-08-11 09:48:53 +02:00
Simon Vieille 66f82ecc13
release v1.21.0 2023-08-11 09:48:50 +02:00
Simon Vieille 8432fc123e Merge branch 'develop' 2023-07-27 18:17:48 +02:00
Simon Vieille 627927a11e
update murph 2023-07-27 18:17:15 +02:00
Simon Vieille 67871179a8
update murph 2023-07-27 18:16:36 +02:00
Simon Vieille dbec7a884e
update changelog 2023-07-20 09:47:12 +02:00
Simon Vieille 8c6ad05cbd
add chdir in the console entrypoint 2023-07-20 09:21:17 +02:00
Simon Vieille cc0296e6e2
fix user crud controllers 2023-07-19 21:29:52 +02:00
Simon Vieille 4f94bd5640
add user admin controller and simples views in default files 2023-07-19 21:24:15 +02:00
Simon Vieille 9d8d6b510a Merge branch 'develop'
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-04-15 10:53:11 +02:00
Simon Vieille edb51485e1
release v1.19.0
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-15 10:53:04 +02:00
Simon Vieille c596f1d62d
add image/webp in default configuration documentation
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-03-02 19:11:11 +01:00
Simon Vieille 813aa8c618
feat(upgrade): yarn lock 2023-01-25 20:55:14 +01:00
Simon Vieille 39e92e949a Merge branch 'develop'
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-01-13 18:27:52 +01:00
Simon Vieille e550bc54e2
release v1.18.0
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-01-13 18:27:44 +01:00
Simon Vieille ab808ee511
ci(conf): add ci
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-13 18:14:30 +01:00
Simon Vieille 1e8baa5ee7 Merge branch 'feature/updates' into develop 2023-01-08 20:55:51 +01:00
Simon Vieille 7f5fcc98d7
feat(dep): update composer.json
remove symfony/flex

update murph/murph-core
2023-01-08 20:55:46 +01:00
Simon Vieille 0f4767b8e0
fix(config): fix firewall config
add entry_point

remove anonymous
2023-01-08 20:53:34 +01:00
Simon Vieille 70bac192e2
fix(config): fix typo in 2fa conf 2023-01-08 20:52:55 +01:00
Simon Vieille 2b72994cde
feat(update): apply recipes:update scheb/2fa-bundle 2023-01-08 19:27:58 +01:00
Simon Vieille 74d12bba3b
feat(update): apply recipes:update symfony/webpack-encore-bundle 2023-01-08 19:26:29 +01:00
Simon Vieille 1cacfd0bb7
feat(update): apply recipes:update symfony/web-profiler-bundle 2023-01-08 19:25:03 +01:00
Simon Vieille 740f90294d
feat(update): apply recipes:update symfony/validator 2023-01-08 19:24:49 +01:00
Simon Vieille da12738b94
feat(update): apply recipes:update symfony/twig-bundle 2023-01-08 19:24:37 +01:00
Simon Vieille 089cd2bb85
feat(update): apply recipes:update symfony/translation 2023-01-08 19:23:15 +01:00
Simon Vieille b22db21ea4
feat(update): apply recipes:update symfony/security-bundle 2023-01-08 19:22:59 +01:00
Simon Vieille b9b6d968aa
feat(update): apply recipes:update symfony/routing 2023-01-08 19:21:15 +01:00
Simon Vieille b8dd7aa3bf
feat(update): apply recipes:update symfony/monolog-bundle 2023-01-08 19:20:56 +01:00
Simon Vieille 6a56bc2c36
feat(update): apply recipes:update symfony/framework-bundle 2023-01-08 19:20:22 +01:00
Simon Vieille 1abc6a7b60
feat(update): apply recipes:update symfony/mailer 2023-01-08 19:18:18 +01:00
Simon Vieille a9c64637ec
feat(update): apply recipes:update symfony/flex 2023-01-08 19:17:22 +01:00
Simon Vieille 72299ba2a5
feat(update): apply recipes:update symfony/debug-bundle 2023-01-08 19:16:59 +01:00
Simon Vieille a2ce7ed609
feat(update): apply recipes:update symfony/console 2023-01-08 19:16:48 +01:00
Simon Vieille 41abaa20d8
feat(update): apply recipes:update symfony/apache-pack 2023-01-08 19:16:12 +01:00
Simon Vieille f7e374cebc
feat(update): apply recipes:update stof/doctrine-extensions-bundle 2023-01-08 19:15:53 +01:00
Simon Vieille c7e3867a93
feat(update): apply recipes:update liip/imagine-bundle 2023-01-08 19:15:02 +01:00
Simon Vieille 1366e92ca0
feat(update): apply recipes:update doctrine/doctrine-migrations-bundle 2023-01-08 19:14:36 +01:00
Simon Vieille 301f22bb4a
feat(update): apply recipes:update doctrine/doctrine-bundle 2023-01-08 19:13:47 +01:00
Simon Vieille 7397a1cde8
feat(test): apply new recipe for phpunit 2023-01-08 19:09:12 +01:00
Simon Vieille e232e44dcd
chore(git): add ignored files
phpunit files
2023-01-08 19:07:35 +01:00
Simon Vieille edb8de9800
feat(test): add database for tests 2023-01-08 19:06:56 +01:00
Simon Vieille d0eb452847
feat(dep): update dependencies 2023-01-08 19:06:38 +01:00
Simon Vieille 75a29b6372 Merge branch 'develop' 2022-11-19 20:35:43 +01:00
Simon Vieille 9bd1ff142b
add documentation 2022-11-19 20:35:30 +01:00
Simon Vieille 69ad422624 Merge branch 'develop' 2022-11-19 19:57:09 +01:00
Simon Vieille e0af777a34
release v1.17.0 2022-11-19 19:57:05 +01:00
Simon Vieille 310c5076a0 Merge branch 'develop' 2022-11-19 19:54:16 +01:00
Simon Vieille 8fcc6faebb
release v1.17.0 2022-11-19 19:54:06 +01:00
Simon Vieille 65b3583411 Merge branch 'develop' 2022-11-19 19:48:32 +01:00
Simon Vieille 58de4e8908
replace annotation with attributes 2022-11-19 19:46:03 +01:00
Simon Vieille 450972ca1e
add encore from node_modules in npm scripts 2022-11-19 19:18:08 +01:00
Simon Vieille a0b4137fb3 Merge branch 'develop' 2022-09-06 12:08:26 +02:00
Simon Vieille 05506fb5db
update murph 2022-09-06 12:07:58 +02:00
Simon Vieille ab14728bec Merge branch 'develop' 2022-09-06 12:02:29 +02:00
Simon Vieille 21cec0647a
update changelog 2022-09-06 12:02:20 +02:00
Simon Vieille c364cafd17 Merge branch 'develop' 2022-06-12 19:07:59 +02:00
Simon Vieille 673132adf2
add meta description in base.html.twig 2022-06-12 19:07:55 +02:00
Simon Vieille 58b5252986 Merge branch 'develop' 2022-05-20 14:24:06 +02:00
Simon Vieille 674846ab53
update services 2022-05-20 14:23:53 +02:00
Simon Vieille 5f942a904b Merge branch 'develop' 2022-05-14 11:32:17 +02:00
Simon Vieille 5ad22f21d2
create an empty directory of core in templates 2022-05-14 11:31:47 +02:00
Simon Vieille be7003d613
remove core directory from template 2022-05-14 11:30:09 +02:00
Simon Vieille 325401f37d Merge branch 'develop' 2022-05-13 22:42:12 +02:00
Simon Vieille 6addbc7de4
add a admin dashboard controller 2022-05-13 22:42:06 +02:00
Simon Vieille bda7a4a195 Merge branch 'develop' 2022-05-09 14:58:43 +02:00
Simon Vieille a79011c276
update changelog 2022-05-09 14:58:37 +02:00
Simon Vieille 1113c0a91f Merge branch 'develop' 2022-05-09 14:55:33 +02:00
Simon Vieille cbf05e3ce3
update changelog 2022-05-09 14:55:31 +02:00
Simon Vieille 2ecb972a79
upgrade yarn.lock 2022-05-09 14:54:41 +02:00
Simon Vieille b26738fe49 Merge branch 'develop' 2022-05-05 16:41:05 +02:00
Simon Vieille 69e2e919de
add grapesjs-component-code-editor and grapesjs-parser-postcss symlinks 2022-05-05 16:40:59 +02:00
Simon Vieille c4c5aa867a Merge branch 'develop' 2022-05-05 14:41:21 +02:00
Simon Vieille 5b68c61cc0
upgrade murph 2022-05-05 14:41:17 +02:00
Simon Vieille 7052f2516d
release v1.14.3 2022-04-30 12:37:12 +02:00
Simon Vieille 52be00fc77 Merge branch 'develop' 2022-04-30 12:34:23 +02:00
Simon Vieille 3626a65f1f
release v1.14.2 2022-04-30 12:34:16 +02:00
Simon Vieille 70cae6f7a0
upgrade murph 2022-04-30 12:33:19 +02:00
Simon Vieille 5f1111d2a0 Merge branch 'develop' 2022-04-25 21:08:48 +02:00
Simon Vieille 1ee868b354
update changelog 2022-04-25 21:08:44 +02:00
Simon Vieille 2307efa341
add blocks in default template 2022-04-25 21:08:25 +02:00
Simon Vieille f19fdb0ecf Merge branch 'develop' 2022-04-22 15:29:09 +02:00
Simon Vieille 4ce765ed72
add security policy 2022-04-22 15:29:05 +02:00
Simon Vieille 3c52a00bc4 Merge branch 'develop' 2022-04-20 15:32:54 +02:00
Simon Vieille cd88d7626a
upgrade yarn.lock 2022-04-20 15:32:50 +02:00
Simon Vieille 3c274a14b9 Merge branch 'develop' 2022-04-20 15:28:33 +02:00
Simon Vieille 371a9f8bb6
update changelog 2022-04-20 15:28:30 +02:00
Simon Vieille bb62d1ce78 Merge branch 'develop' 2022-04-20 15:27:42 +02:00
Simon Vieille b043e42b96
fix missing envvar in makefile (npm) 2022-04-20 15:27:29 +02:00
Simon Vieille 2a91b5917f Merge branch 'develop' 2022-04-20 14:51:52 +02:00
Simon Vieille 2e9b942024
upgrade murph 2022-04-20 14:51:50 +02:00
Simon Vieille 22d7389183 Merge branch 'develop' 2022-04-20 14:48:01 +02:00
Simon Vieille d0e187746a
upgrade murph 2022-04-20 14:47:58 +02:00
Simon Vieille 933e360e26 Merge branch 'develop' 2022-04-17 18:32:39 +02:00
Simon Vieille c4bc470e8c
upgrade murph 2022-04-17 18:32:36 +02:00
Simon Vieille 9dd0f7376b Merge branch 'develop' 2022-03-26 16:19:12 +01:00
Simon Vieille 52536ccefb update yarn.lock 2022-03-26 16:19:10 +01:00
Simon Vieille f240db7ba4 Merge branch 'develop' 2022-03-26 16:13:40 +01:00
Simon Vieille aa808d07e8 upgrade murph/murph-core 2022-03-26 16:13:37 +01:00
Simon Vieille bb01163e84 Merge branch 'develop' 2022-03-22 15:01:33 +01:00
Simon Vieille 1dbed2d52f upgrade murph/murph-core 2022-03-22 15:01:28 +01:00
Simon Vieille 489a0411a0 update changelog 2022-03-22 15:00:48 +01:00
Simon Vieille 2a5d0ae1f4 Merge branch 'develop' 2022-03-21 11:21:22 +01:00
Simon Vieille 2adff5f583 update changelog 2022-03-21 11:21:20 +01:00
Simon Vieille b40c77d7a3 use murph-npm to install npm requirements 2022-03-21 11:20:17 +01:00
Simon Vieille 27bccc9acd Merge branch 'develop' 2022-03-17 10:59:41 +01:00
Simon Vieille be31af32bc update changelog, murph version, upgrade doc 2022-03-17 10:59:39 +01:00
Simon Vieille 246c4621c0 Merge branch 'develop' 2022-03-16 12:51:55 +01:00
Simon Vieille 4d235c4ec5 update changelog 2022-03-16 12:51:53 +01:00
Simon Vieille 2be2da285a remove useless env var from makefile 2022-03-16 12:51:44 +01:00
Simon Vieille 1498ab5308 update dashboard template 2022-03-16 12:51:22 +01:00
Simon Vieille 68c6ee8081 Merge branch 'develop' 2022-03-14 10:47:31 +01:00
Simon Vieille bec4edfa45 upgrade murph/murph-core 2022-03-14 10:47:29 +01:00
Simon Vieille 7623546c10 Merge branch 'develop' 2022-03-14 10:14:34 +01:00
Simon Vieille 4cbd601476 backports murph-core 2022-03-14 10:14:31 +01:00
Simon Vieille 63237d50e7 Merge branch 'develop' 2022-03-14 10:13:27 +01:00
Simon Vieille 2d8637617b upgrade murph/murph-core 2022-03-14 10:13:23 +01:00
Simon Vieille 81d1fa9cad Merge branch 'develop' 2022-03-13 21:17:03 +01:00
Simon Vieille 47b83ee968 update upgrade doc 2022-03-13 21:17:01 +01:00
Simon Vieille 6086ceaf35 Merge branch 'develop' 2022-03-13 21:14:14 +01:00
Simon Vieille 94e2fc966d update changelog 2022-03-13 21:14:04 +01:00
Simon Vieille b809ee9c88 use murph/murph-core instead of the core directory 2022-03-13 21:12:20 +01:00
Simon Vieille a3ef3540c1 Merge branch 'develop' 2022-03-13 12:20:11 +01:00
Simon Vieille 47e6f06bd7 add default value for security operator 2022-03-13 12:20:04 +01:00
Simon Vieille a3a1822339 Merge branch 'develop' 2022-03-11 15:24:35 +01:00
Simon Vieille 7523f479d5 change apparence of murph version 2022-03-11 15:24:33 +01:00
Simon Vieille 59d5fed75e Merge branch 'develop' 2022-03-11 09:49:45 +01:00
Simon Vieille 4e1003159c update changelog and murph version 2022-03-11 09:49:25 +01:00
Simon Vieille fd36561cf1 add murph version in admin ui 2022-03-11 09:47:35 +01:00
Simon Vieille 73b82578e2 Merge branch 'develop' 2022-03-10 22:31:19 +01:00
Simon Vieille e7b4f7f81e refactoring of RequestSecurityEventSubscriber 2022-03-10 22:31:17 +01:00
Simon Vieille dc34a954f6 Merge branch 'develop' 2022-03-10 22:29:59 +01:00
Simon Vieille 3017880a64 refactoring of RequestSecurityEventSubscriber 2022-03-10 22:29:45 +01:00
Simon Vieille 0424a9457e Merge branch 'develop' 2022-03-10 22:01:06 +01:00
Simon Vieille bd6a5bb44e update translations 2022-03-10 22:00:53 +01:00
Simon Vieille b32133e7cb Merge branch 'develop' 2022-03-10 21:50:09 +01:00
Simon Vieille a37c6626c6 fix Node::getSecurityRoles 2022-03-10 21:50:01 +01:00
Simon Vieille 4e1166c19d Merge branch 'develop' 2022-03-10 21:38:33 +01:00
Simon Vieille 098a0f17c0 update changelog and upgrade doc 2022-03-10 21:38:05 +01:00
Simon Vieille f7ddd54d99 Merge branch 'feature/node-security' into develop 2022-03-10 21:37:14 +01:00
Simon Vieille 39637d4094 update changelog and upgrade doc 2022-03-10 21:37:11 +01:00
Simon Vieille ce5c69d467 rename core/EventSuscriber with core/EventSubscriber
add security roles in app configuration

add option to restrict node access to specific roles
2022-03-10 21:32:22 +01:00
350 changed files with 6114 additions and 22793 deletions

7
.env
View file

@ -9,6 +9,7 @@
# Real environment variables win over .env files.
#
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
# https://symfony.com/doc/current/configuration/secrets.html
#
# 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
@ -19,7 +20,7 @@ APP_SECRET=
###< symfony/framework-bundle ###
###> symfony/mailer ###
MAILER_DSN=smtp://localhost
# MAILER_DSN=smtp://localhost
###< symfony/mailer ###
###> doctrine/doctrine-bundle ###
@ -27,6 +28,6 @@ MAILER_DSN=smtp://localhost
# 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"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8&charset=utf8mb4"
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=14&charset=utf8"
###< doctrine/doctrine-bundle ###

View file

@ -4,3 +4,4 @@ APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_tests.db"

6
.gitignore vendored
View file

@ -12,7 +12,6 @@
###< symfony/framework-bundle ###
###> symfony/phpunit-bridge ###
.phpunit
.phpunit.result.cache
/phpunit.xml
###< symfony/phpunit-bridge ###
@ -29,3 +28,8 @@ yarn-error.log
/public/media/
/migrations/*
!/migrations/.gitkeep
###> phpunit/phpunit ###
/phpunit.xml
.phpunit.result.cache
###< phpunit/phpunit ###

49
.woodpecker.yml Normal file
View file

@ -0,0 +1,49 @@
matrix:
PHP_VERSION:
- 8.0
- 8.1
services:
db:
image: mariadb:10.3
environment:
- MARIADB_ROOT_PASSWORD=root
steps:
db_wait:
image: gitnet.fr/deblan/timeout:latest
commands:
- /bin/timeout -t 30 -v -c 'while true; do nc -z -v db 3306 2>&1 | grep succeeded && exit 0; sleep 0.5; done'
db_create:
image: mariadb:10.3
commands:
- mysql -hdb -uroot -proot -e "CREATE DATABASE app"
config:
image: deblan/php:${PHP_VERSION}
commands:
- echo APP_ENV=prod >> .env.local
- echo APP_SECRET=$(openssl rand -hex 32) >> .env.local
- echo DATABASE_URL=mysql://root:root@db/app >> .env.local
composer:
image: deblan/php:${PHP_VERSION}
commands:
- apt-get update && apt-get -y install git
- composer install --no-scripts
db_migrate:
image: deblan/php:${PHP_VERSION}
environment:
- PHP=php
commands:
- ./bin/doctrine-migrate
node:
image: node:16-slim
commands:
- yarn
- test -d public/js || mkdir public/js
- test -f public/js/fos_js_routes.json || echo "{}" > public/js/fos_js_routes.json
- npm run build

View file

@ -1,8 +1,135 @@
## [Unreleased]
## [v1.23.0] - 2023-09-28
### Changed
* upgrade murph/murph-core
## [v1.22.0] - 2023-09-28
### Added
* update woodpecker ci base file
### Fixed
* fix #1: add UniqueEntity constraint in the User entity
### Changed
* upgrade murph/murph-core
## [1.21.0] - 2023-08-11
### Changed
* upgrade murph/murph-core
## [1.20.0] - 2023-07-27
### Fixed
* fix collection widget: allow_add/allow_delete and prototype
### Added
* add user admin controller and simples views in default files
* add chdir in the console entrypoint
### Changed
* upgrade murph/murph-core
## [1.19.0] - 2023-04-15
### Changed
* upgrade murph/murph-core
## [1.18.0] - 2023-01-13
### Added
* feat(dep): update dependencies
* feat(update): apply new recipe for phpunit
* feat(update): apply recipes:update doctrine/doctrine-bundle
* feat(update): apply recipes:update doctrine/doctrine-migrations-bundle
* feat(update): apply recipes:update liip/imagine-bundle
* feat(update): apply recipes:update stof/doctrine-extensions-bundle
* feat(update): apply recipes:update symfony/apache-pack
* feat(update): apply recipes:update symfony/console
* feat(update): apply recipes:update symfony/debug-bundle
* feat(update): apply recipes:update symfony/flex
* feat(update): apply recipes:update symfony/mailer
* feat(update): apply recipes:update symfony/framework-bundle
* feat(update): apply recipes:update symfony/monolog-bundle
* feat(update): apply recipes:update symfony/routing
* feat(update): apply recipes:update symfony/security-bundle
* feat(update): apply recipes:update symfony/translation
* feat(update): apply recipes:update symfony/twig-bundle
* feat(update): apply recipes:update symfony/validator
* feat(update): apply recipes:update symfony/web-profiler-bundle
* feat(update): apply recipes:update symfony/webpack-encore-bundle
* feat(update): apply recipes:update scheb/2fa-bundle
### Fixed
* fix(config): fix typo in 2fa conf
* fix(config): fix firewall config
## [1.17.0] - 2022-11-19
### Changed
* upgrade murph/murph-core
* replace annotation with attributes
* use encore from node_modules in npm scripts
## [1.16.0]
### Added
* add a admin dashboard controller
* add meta description in base.html.twig
### Fixed
### Changed
* upgrade murph/murph-core
## [1.15.0]
### Changed
* upgrade murph/murph-core
## [1.14.3]
### Added
* add blocks in default template
### Changed
* upgrade murph/murph-core
## [1.14.2]
## [1.14.1]
### Fixed
* fix missing envvar in makefile (npm)
## [1.14.0]
### Changed
* upgrade murph/murph-core
## [1.13.0]
### Changed
* upgrade murph/murph-core
## [1.12.0]
### Changed
* upgrade murph/murph-core
## [1.11.0]
### Changed
* upgrade murph/murph-core
* use murph-npm to install npm requirements
## [1.10.0]
### Added
* add translated title in dashboard template
### Fixed
* remove useless env var from makefile
### Changed
* upgrade murph/murph-core
## [1.9.1] - 2022-03-14
### Added
* add murph version in autoload file
### Changed
* remove AdminController constructor
## [1.9.0] - 2022-03-13
### Added
* add murph version in admin ui
### Changed
* the core is now installed with composer
## [1.8.0] - 2022-03-10
### Added
* add security roles in app configuration
* add option to restrict node access to specific roles
### Changed
* rename `core/EventSuscriber` with `core/EventSubscriber`
## [1.7.3] - 2022-03-06
### Added

View file

@ -1,6 +1,5 @@
COMPOSER_BIN ?= composer
PHP_BIN ?= php8.1
SSH_BIN ?= ssh
YARN_BIN ?= yarn
NPM_BIN ?= npm

12
SECURITY.md Normal file
View file

@ -0,0 +1,12 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ---------- | --------- |
| >= 2.0 | :x: |
| >= 1.0 | :white_check_mark: |
## Reporting a Vulnerability
If you discover a security vulnerability within Murph, send an email to security [at] murph-project.org.

View file

@ -1,5 +1,67 @@
## General process
Upgrade dependencies:
* `composer update`
* `yarn upgrade`
Build:
* `make build`
## [Unreleased]
## Upgrade to v1.17.0
Replace all annotations with PHP8 attributes and change the doctrine configuration:
```
# config/packages/doctrine.yaml
doctrine:
...
orm:
...
mappings:
App\Core\Entity:
type: attribute
...
App\Entity:
type: attribute
...
```
## Upgrade to v1.15.0
```
cd public/vendor
ln -rs ../../node_modules/grapesjs-plugin-export .
ln -rs ../../node_modules/grapesjs-parser-postcss .
```
## Upgrade to v1.10.0
### Commands
```
make doctrine-migration
```
## Upgrade to v1.9.0
This version uses the package `murph/murph-core`.
## Upgrade to v1.8.0
### Commands
```
make doctrine-migration
```
### Files
Event subscribers in `src/EventSubscriber` must update namespaces.
## Upgrade to v1.7.0
### Commands

View file

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

View file

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

View file

@ -3,41 +3,17 @@
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;
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}
set_time_limit(0);
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
require dirname(__DIR__).'/vendor/autoload.php';
chdir(__DIR__.'/../');
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.');
}
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
$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);
return new Application($kernel);
};

View file

@ -1,13 +1,19 @@
#!/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 (!ini_get('date.timezone')) {
ini_set('date.timezone', 'UTC');
}
if (false === getenv('SYMFONY_PHPUNIT_DIR')) {
putenv('SYMFONY_PHPUNIT_DIR='.__DIR__.'/.phpunit');
}
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
require PHPUNIT_COMPOSER_INSTALL;
PHPUnit\TextUI\Command::main();
} else {
if (!is_file(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);
}
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
}

View file

@ -7,66 +7,14 @@
"prefer-stable": true,
"require": {
"php": ">=8.0.0",
"ext-ctype": "*",
"ext-iconv": "*",
"bjeavons/zxcvbn-php": "^1.3",
"cocur/slugify": "^4.1",
"composer/package-versions-deprecated": "1.11.99.1",
"doctrine/annotations": "^1.0",
"doctrine/doctrine-bundle": "^2.5",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.11",
"friendsofsymfony/jsrouting-bundle": "^2.8",
"jaybizzle/crawler-detect": "^1.2",
"knplabs/doctrine-behaviors": "^2.6",
"knplabs/knp-paginator-bundle": "^5.8",
"liip/imagine-bundle": "^2.7",
"matomo/device-detector": "^5.0",
"phpdocumentor/reflection-docblock": "^5.3",
"scheb/2fa-google-authenticator": "^5.13",
"scheb/2fa-qr-code": "^5.13",
"sensio/framework-extra-bundle": "^6.2",
"sensiolabs/ansi-to-html": "^1.2",
"spe/filesize-extension-bundle": "~2.0.0",
"stof/doctrine-extensions-bundle": "^1.7",
"symfony/apache-pack": "^1.0",
"symfony/asset": "5.4.*",
"symfony/console": "5.4.*",
"symfony/dotenv": "5.4.*",
"symfony/event-dispatcher": "5.4.*",
"symfony/expression-language": "5.4.*",
"symfony/finder": "5.4.*",
"symfony/flex": "^1.3.1",
"symfony/form": "5.4.*",
"symfony/framework-bundle": "5.4.*",
"symfony/http-client": "5.4.*",
"symfony/intl": "5.4.*",
"symfony/mailer": "5.4.*",
"symfony/mime": "5.4.*",
"symfony/monolog-bundle": "^3.1",
"symfony/notifier": "5.4.*",
"symfony/process": "5.4.*",
"symfony/property-access": "5.4.*",
"symfony/property-info": "5.4.*",
"symfony/proxy-manager-bridge": "5.4.*",
"symfony/security-bundle": "5.4.*",
"symfony/serializer": "5.4.*",
"symfony/string": "5.4.*",
"symfony/translation": "5.4.*",
"symfony/twig-bundle": "^5.2",
"symfony/validator": "5.4.*",
"symfony/web-link": "5.4.*",
"symfony/webpack-encore-bundle": "^1.11",
"symfony/yaml": "5.4.*",
"twig/extra-bundle": "^2.12|^3.3",
"twig/twig": "^2.12|^3.3"
"murph/murph-core": "^1.23"
},
"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/phpunit-bridge": "^6.2",
"symfony/stopwatch": "^5.4",
"symfony/var-dumper": "^5.4",
"symfony/web-profiler-bundle": "^5.4"
@ -78,13 +26,13 @@
},
"sort-packages": true,
"allow-plugins": {
"symfony/flex": true
"symfony/flex": true,
"symfony/runtime": true
}
},
"autoload": {
"psr-4": {
"App\\": "src/",
"App\\Core\\": "core/"
"App\\": "src/"
}
},
"autoload-dev": {

View file

@ -6,7 +6,7 @@ return [
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\DebugBundle\DebugBundle::class => ['dev' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],

View file

@ -9,11 +9,16 @@ core:
name: 'Simple page'
templates:
- {name: "Default", file: "page/simple/default.html.twig"}
# security:
# roles:
# - {name: 'Role foo', role: 'ROLE_FOO'}
# - {name: 'Role bar', role: 'ROLE_BAR'}
file_manager:
# mimes:
# - image/png
# - image/jpg
# - image/jpeg
# - image/webp
# - image/gif
# - image/svg+xml
# - video/mp4

View file

@ -1,3 +0,0 @@
framework:
assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'

View file

@ -0,0 +1,5 @@
when@dev:
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)%"

View file

@ -1,4 +0,0 @@
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)%"

View file

@ -1,19 +0,0 @@
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"]

View file

@ -1,6 +0,0 @@
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler: { only_exceptions: false }

View file

@ -4,7 +4,7 @@ doctrine:
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '13'
#server_version: '14'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
@ -12,13 +12,13 @@ doctrine:
mappings:
App\Core\Entity:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/core/Entity'
type: attribute
dir: '%kernel.project_dir%/vendor/murph/murph-core/src/core/Entity'
prefix: 'App\Core\Entity'
alias: App\Core\Entity
App\Entity:
is_bundle: false
type: annotation
type: attribute
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App\Entity
@ -28,3 +28,29 @@ doctrine:
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
when@test:
doctrine:
dbal:
# "TEST_TOKEN" is typically set by ParaTest
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
auto_generate_proxy_classes: false
proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
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

View file

@ -3,3 +3,4 @@ doctrine_migrations:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false

View file

@ -2,7 +2,7 @@
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
#http_method_override: true
http_method_override: false
# 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.
@ -10,8 +10,15 @@ framework:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
storage_factory_id: session.storage.factory.native
#esi: true
#fragments: true
php_errors:
log: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file

View file

@ -1,4 +1,4 @@
# See dos how to configure the bundle: https://symfony.com/doc/current/bundles/LiipImagineBundle/basic-usage.html
# Documentation on how to configure the bundle can be found at: https://symfony.com/doc/current/bundles/LiipImagineBundle/basic-usage.html
liip_imagine:
# valid drivers options include "gd" or "gmagick" or "imagick"
driver: "imagick"

View file

@ -0,0 +1,61 @@
monolog:
channels:
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
when@dev:
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"]
when@test:
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
when@prod:
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: php://stderr
level: debug
formatter: monolog.formatter.json
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
deprecation:
type: stream
channels: [deprecation]
path: php://stderr

View file

@ -1,8 +0,0 @@
# 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"

View file

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

View file

@ -1,16 +0,0 @@
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"]

View file

@ -1,3 +0,0 @@
framework:
router:
strict_requirements: null

View file

@ -1,4 +0,0 @@
#webpack_encore:
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# Available in version 1.2
#cache: true

View file

@ -5,3 +5,8 @@ framework:
# 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
when@prod:
framework:
router:
strict_requirements: null

View file

@ -2,7 +2,7 @@
scheb_two_factor:
google:
enabled: true
issuer: "Muprh"
issuer: "Murph"
server_name:
digits: 6
window: 1

View file

@ -3,7 +3,16 @@ security:
App\Entity\User:
algorithm: auto
access_decision_manager:
strategy: consensus
allow_if_all_abstain: false
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
@ -20,7 +29,6 @@ security:
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
@ -30,7 +38,7 @@ security:
form_login:
login_path: auth_login
check_path: auth_login
csrf_token_generator: security.csrf.token_manager
enable_csrf: true
logout:
path: auth_logout
target: /
@ -39,6 +47,8 @@ security:
lifetime: 604800
path: /
entry_point: form_login
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
@ -52,3 +62,16 @@ security:
- { path: ^/admin/file_manager, roles: ROLE_WRITER }
- { path: ^/admin, roles: ROLE_USER }
- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon

View file

@ -1,4 +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/
# See the official DoctrineExtensions documentation for more details: https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc
stof_doctrine_extensions:
default_locale: en_US

View file

@ -1,4 +0,0 @@
framework:
test: true
session:
storage_id: session.storage.mock_file

View file

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

View file

@ -1,2 +0,0 @@
twig:
strict_variables: true

View file

@ -1,3 +0,0 @@
framework:
validation:
not_compromised_password: false

View file

@ -1,6 +0,0 @@
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View file

@ -1,2 +0,0 @@
#webpack_encore:
# strict_mode: false

View file

@ -3,6 +3,13 @@ framework:
translator:
default_path: '%kernel.project_dir%/translations'
paths:
- '%kernel.project_dir%/core/Resources/translations'
- '%kernel.project_dir%/vendor/murph/murph-core/src/core/Resources/translations'
fallbacks:
- en
# providers:
# crowdin:
# dsn: '%env(CROWDIN_DSN)%'
# loco:
# dsn: '%env(LOCO_DSN)%'
# lokalise:
# dsn: '%env(LOKALISE_DSN)%'

View file

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

View file

@ -6,3 +6,8 @@ framework:
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false

View file

@ -0,0 +1,15 @@
when@dev:
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler: { only_exceptions: false }
when@test:
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View file

@ -7,7 +7,12 @@ webpack_encore:
# Set attributes that will be rendered on all script and link tags
script_attributes:
defer: true
# Uncomment (also under link_attributes) if using Turbo Drive
# https://turbo.hotwired.dev/handbook/drive#reloading-when-assets-change
# 'data-turbo-track': reload
# link_attributes:
# Uncomment if using Turbo Drive
# 'data-turbo-track': reload
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
# crossorigin: 'anonymous'
@ -20,11 +25,21 @@ webpack_encore:
# 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
# pass the build name as the 3rd argument to the Twig functions
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
framework:
assets:
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
#when@prod:
# webpack_encore:
# # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
# # Available in version 1.2
# cache: true
#when@test:
# webpack_encore:
# strict_mode: false

View file

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

View file

@ -1,3 +0,0 @@
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error

View file

@ -1,7 +0,0 @@
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

View file

@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error

View file

@ -1,7 +1,7 @@
2fa_login:
path: /2fa
defaults:
_controller: "scheb_two_factor.form_controller:form"
_controller: "scheb_two_factor.form_controller::form"
2fa_login_check:
path: /2fa_check

View file

@ -0,0 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

View file

@ -2,7 +2,7 @@
# 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
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
@ -11,22 +11,29 @@ services:
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
# Murph services
App\Core\:
resource: '../core/'
resource: '../vendor/murph/murph-core/src/core/'
exclude:
- '../core/DependencyInjection/'
- '../core/Entity/'
- '../vendor/murph/murph-core/src/core/DependencyInjection/'
- '../vendor/murph/murph-core/src/core/Entity/'
# Redirections
App\Core\EventListener\RedirectListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
# Analytics
App\Core\EventListener\AnalyticListener:
tags:
- { name: kernel.event_listener, event: kernel.request }
# A/B Testing
App\Core\EventListener\AbListener:
tags:
- { name: kernel.event_listener, event: kernel.request }
- { name: kernel.event_listener, event: kernel.response }
App\:
resource: '../src/'
exclude:
@ -36,11 +43,11 @@ services:
- '../src/Tests/'
App\Core\Maker\:
resource: '../core/Maker/'
resource: '../vendor/murph/murph-core/src/core/Maker/'
tags: ['maker.command']
App\Core\Controller\:
resource: '../core/Controller/'
resource: '../vendor/murph/murph-core/src/core/Controller/'
tags: ['controller.service_arguments']
App\Controller\:
@ -58,8 +65,8 @@ services:
calls:
- [ setAnnotationReader, [ "@annotation_reader" ] ]
App\UrlGenerator\FooUrlGenerator:
public: true
# App\UrlGenerator\FooUrlGenerator:
# public: true
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

View file

@ -1,204 +0,0 @@
<?php
namespace App\Core\Analytic;
use App\Core\Entity\Site\Node;
use App\Core\Repository\Analytic\RefererRepositoryQuery;
use App\Core\Repository\Analytic\ViewRepositoryQuery;
/**
* class DateRangeAnalytic.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class DateRangeAnalytic
{
protected ViewRepositoryQuery $viewQuery;
protected RefererRepositoryQuery $refererQuery;
protected ?Node $node;
protected ?\DateTime $from;
protected ?\DateTime $to;
protected bool $reload = true;
protected array $cache = [];
public function __construct(ViewRepositoryQuery $viewQuery, RefererRepositoryQuery $refererQuery)
{
$this->viewQuery = $viewQuery;
$this->refererQuery = $refererQuery;
}
public function getViews(): array
{
$entities = $this->getEntities('view');
$this->reload = false;
if ($entities) {
$first = $entities[0];
$last = $entities[count($entities) - 1];
$diff = $first->getDate()->diff($last->getDate());
if ($diff->days >= 90) {
$format = 'Y-m';
} else {
$format = 'Y-m-d';
}
}
$datas = [];
foreach ($entities as $entity) {
$index = $entity->getDate()->format($format);
if (!isset($datas[$index])) {
$datas[$index] = 0;
}
$datas[$index] += $entity->getViews();
}
return $datas;
}
public function getPathViews(): array
{
$entities = $this->getEntities('view');
$this->reload = false;
$datas = [];
foreach ($entities as $entity) {
$index = $entity->getPath();
if (!isset($datas[$index])) {
$datas[$index] = [
'views' => 0,
'desktopViews' => 0,
'mobileViews' => 0,
];
}
$datas[$index]['views'] += $entity->getViews();
$datas[$index]['desktopViews'] += $entity->getDesktopViews();
$datas[$index]['mobileViews'] += $entity->getMobileViews();
}
uasort($datas, function($a, $b) {
if ($a['views'] > $b['views']) {
return -1;
}
if ($a['views'] < $b['views']) {
return 1;
}
return 0;
});
return $datas;
}
public function getReferers(): array
{
$entities = $this->getEntities('referer');
$this->reload = false;
$datas = [];
foreach ($entities as $entity) {
$index = parse_url($entity->getUri(), PHP_URL_HOST);
if (!isset($datas[$index])) {
$datas[$index] = [
'views' => 0,
'uris' => [],
];
}
$datas[$index]['views'] += $entity->getViews();
$path = parse_url($entity->getUri(), PHP_URL_PATH);
if (empty($path)) {
$path = '/';
}
if (!isset($datas[$index]['uris'][$path])) {
$datas[$index]['uris'][$path] = 0;
}
$datas[$index]['uris'][$path] += $entity->getViews();
}
uasort($datas, function($a, $b) {
if ($a['views'] > $b['views']) {
return -1;
}
if ($a['views'] < $b['views']) {
return 1;
}
return 0;
});
return $datas;
}
public function setDateRange(?\DateTime $from, ?\DateTime $to): self
{
$this->from = $from;
$this->to = $to;
$this->reload = true;
return $this;
}
public function setNode(?Node $node): self
{
$this->node = $node;
$this->reload = true;
return $this;
}
protected function getEntities(string $type): array
{
if ('view' === $type) {
$query = $this->viewQuery->create();
} elseif ('referer' === $type) {
$query = $this->refererQuery->create();
} else {
throw new \InvalidArgumentException('Invalid type');
}
if (!$this->reload && isset($this->cache[$type])) {
return $this->cache[$type];
}
if (null !== $this->from) {
$query
->andWhere('.date >= :from')
->setParameter(':from', $this->from)
;
}
if (null !== $this->to) {
$query
->andWhere('.date <= :to')
->setParameter(':to', $this->to)
;
}
if (null !== $this->node) {
$query
->andWhere('.node = :node')
->setParameter(':node', $this->node->getId())
;
}
$this->cache[$type] = $query->orderBy('.date')->find();
return $this->cache[$type];
}
}

View file

@ -1,20 +0,0 @@
<?php
namespace App\Core\Annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* class UrlGenerator.
*
* @author Simon Vieille <simon@deblan.fr>
* @Annotation
*/
class UrlGenerator
{
public string $service;
public string $method;
public array $options = [];
}

View file

@ -1,96 +0,0 @@
<?php
namespace App\Core\Authenticator;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private EntityManagerInterface $entityManager;
private UrlGeneratorInterface $urlGenerator;
private CsrfTokenManagerInterface $csrfTokenManager;
private UserPasswordEncoderInterface $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return 'auth_login' === $request->attributes->get('_route') && $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'email' => $request->request->get('_username'),
'password' => $request->request->get('_password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(Security::LAST_USERNAME, $credentials['email']);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Email could not be found.');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('admin_dashboard_index'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('auth_login');
}
}

View file

@ -1,24 +0,0 @@
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Core\Bundle;
use App\Core\DependencyInjection\CoreExtension;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
class CoreBundle extends Bundle
{
public function getContainerExtension(): ?ExtensionInterface
{
return new CoreExtension();
}
}

View file

@ -1,73 +0,0 @@
<?php
namespace App\Core\Cache;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpClient\Exception\TransportException;
/**
* class SymfonyCacheManager.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class SymfonyCacheManager
{
protected KernelInterface $kernel;
protected HttpClientInterface $httpClient;
protected UrlGeneratorInterface $urlGenerator;
public function __construct(KernelInterface $kernel, HttpClientInterface $httpClient, UrlGeneratorInterface $urlGenerator)
{
$this->kernel = $kernel;
$this->httpClient = $httpClient;
$this->urlGenerator = $urlGenerator;
}
public function cleanRouting()
{
$finder = new Finder();
$finder
->in($this->kernel->getCacheDir())
->depth('== 0')
->name('url_*.php*')
;
$pingUrl = $this->urlGenerator->generate('_ping', [], UrlGeneratorInterface::ABSOLUTE_URL);
foreach ($finder as $file) {
unlink((string) $file->getPathname());
}
try {
// Hack: used to regenerate cache of url generator
$this->httpClient->request('POST', $pingUrl);
} catch (ClientException $e) {
} catch (TransportException $e) {
}
}
public function cleanAll(OutputInterface $output = null)
{
$application = new Application($this->kernel);
$application->setAutoExit(false);
if (null === $output) {
$output = new BufferedOutput();
}
$input = new ArrayInput([
'command' => 'cache:warmup',
'-e' => $this->kernel->getEnvironment(),
]);
$application->run($input, $output);
}
}

View file

@ -1,108 +0,0 @@
<?php
namespace App\Core\Command;
use App\Core\Factory\UserFactory;
use App\Core\Manager\EntityManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
class UserCreateCommand extends Command
{
protected static $defaultName = 'murph:user:create';
protected static $defaultDescription = 'Creates a user';
protected UserFactory $userFactory;
protected EntityManager $entityManager;
protected TokenGeneratorInterface $tokenGenerator;
public function __construct(
UserFactory $userFactory,
EntityManager $entityManager,
TokenGeneratorInterface $tokenGenerator
) {
$this->userFactory = $userFactory;
$this->entityManager = $entityManager;
$this->tokenGenerator = $tokenGenerator;
parent::__construct();
}
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->addArgument('email', InputArgument::OPTIONAL, 'E-mail')
->addOption('is-admin', null, InputOption::VALUE_NONE, 'Add the admin role')
->addOption('is-writer', null, InputOption::VALUE_NONE, 'Add the write role')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$helper = $this->getHelper('question');
$emailQuestion = new Question('E-mail: ');
$emailQuestion->setValidator(function ($value) {
if (empty($value)) {
throw new \RuntimeException('The email must not be empty.');
}
return $value;
});
$passwordQuestion = new Question('Password (leave empty to generate a random password): ');
$passwordQuestion->setHidden(true);
$isAdminDefault = $input->getOption('is-admin');
$isWriterDefault = $input->getOption('is-writer');
$isAdminQuestionLabel = sprintf('Administrator [%s] ', $isAdminDefault ? 'Y/n' : 'y/N');
$isWriterQuestionLabel = sprintf('Writer [%s] ', $isWriterDefault ? 'Y/n' : 'y/N');
$isAdminQuestion = new ConfirmationQuestion($isAdminQuestionLabel, $isAdminDefault);
$isWriterQuestion = new ConfirmationQuestion($isWriterQuestionLabel, $isWriterDefault);
$io->section('Authentication');
$email = $input->getArgument('email');
if (empty($email)) {
$email = $helper->ask($input, $output, $emailQuestion);
}
$password = $helper->ask($input, $output, $passwordQuestion);
$showPassword = empty($password);
if ($showPassword) {
$password = mb_substr($this->tokenGenerator->generateToken(), 0, 18);
$io->info(sprintf('Password: %s', $password));
} else {
$io->newLine();
}
$io->section('Roles');
$isAdmin = $helper->ask($input, $output, $isAdminQuestion);
$isWriter = $helper->ask($input, $output, $isWriterQuestion);
$user = $this->userFactory->create($email, $password);
$user->setIsAdmin($isAdmin);
$user->setIsWriter($isWriter);
$this->entityManager->create($user);
$io->newLine();
$io->success('User created!');
return Command::SUCCESS;
}
}

View file

@ -1,150 +0,0 @@
<?php
namespace App\Core\Controller\Account;
use App\Core\Controller\Admin\AdminController;
use App\Core\Manager\EntityManager;
use App\Repository\UserRepository;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface;
use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticatorInterface as TotpAuthenticatorInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
use ZxcvbnPhp\Zxcvbn;
/**
* @Route("/admin/account")
*/
class AccountAdminController extends AdminController
{
/**
* @Route("/", name="admin_account")
*/
public function account(Request $request, TotpAuthenticatorInterface $totpAuthenticatorService): Response
{
$account = $this->getUser();
return $this->render('@Core/account/admin/edit.html.twig', [
'account' => $account,
]);
}
/**
* @Route("/2fa", name="admin_account_2fa")
*/
public function twoFactorAuthentication(
Request $request,
GoogleAuthenticatorInterface $totpAuthenticatorService,
EntityManager $entityManager
): Response {
if ($request->isMethod('GET')) {
return $this->redirectToRoute('admin_account');
}
$account = $this->getUser();
$csrfToken = $request->request->get('_csrf_token');
$enable = (bool) $request->request->get('enable');
$code = $request->request->get('code', '');
$secret = $request->request->get('secret', '');
$qrCodeContent = null;
if ($this->isCsrfTokenValid('2fa', $csrfToken)) {
if ($enable && !$account->isTotpAuthenticationEnabled()) {
if (empty($secret)) {
$secret = $totpAuthenticatorService->generateSecret();
$account->setTotpSecret($secret);
$qrCodeContent = $totpAuthenticatorService->getQRContent($account);
} else {
$account->setTotpSecret($secret);
$qrCodeContent = $totpAuthenticatorService->getQRContent($account);
if (!$totpAuthenticatorService->checkCode($account, $code)) {
$this->addFlash('error', 'The code is not valid.');
} else {
$this->addFlash('success', 'Double authentication enabled.');
$entityManager->update($account);
return $this->redirectToRoute('admin_account');
}
}
}
if (!$enable && $account->isTotpAuthenticationEnabled()) {
$account->setTotpSecret(null);
$entityManager->update($account);
$this->addFlash('success', 'Double authentication disabled.');
return $this->redirectToRoute('admin_account');
}
}
return $this->render('@Core/account/admin/edit.html.twig', [
'account' => $account,
'twoFaKey' => $secret,
'twoFaQrCodeContent' => $qrCodeContent,
]);
}
/**
* @Route("/password", name="admin_account_password", methods={"POST"})
*/
public function password(
Request $request,
UserRepository $repository,
TokenGeneratorInterface $tokenGenerator,
UserPasswordEncoderInterface $encoder,
EntityManager $entityManager
): Response {
$account = $this->getUser();
$csrfToken = $request->request->get('_csrf_token');
if ($this->isCsrfTokenValid('password', $csrfToken)) {
$password = $request->request->get('password');
if (!$encoder->isPasswordValid($account, $password)) {
$this->addFlash('error', 'The form is not valid.');
return $this->redirectToRoute('admin_account');
}
$password1 = $request->request->get('password1');
$password2 = $request->request->get('password2');
$zxcvbn = new Zxcvbn();
$strength = $zxcvbn->passwordStrength($password1, []);
if (4 === $strength['score'] && $password1 === $password2) {
$account
->setPassword($encoder->encodePassword($account, $password1))
->setConfirmationToken($tokenGenerator->generateToken())
;
$entityManager->update($account);
$this->addFlash('success', 'Password updated.');
return $this->redirectToRoute('admin_account');
}
}
$this->addFlash('error', 'The form is not valid.');
return $this->redirectToRoute('admin_account');
}
/**
* {@inheritdoc}
*/
protected function getSection(): string
{
return 'account';
}
}

View file

@ -1,40 +0,0 @@
<?php
namespace App\Core\Controller\Admin;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
abstract class AdminController extends AbstractController
{
protected array $coreParameters;
public function __construct(ParameterBagInterface $parameters)
{
$this->coreParameters = $parameters->get('core');
}
/**
* @Route("/_ping", name="_ping")
*/
public function ping()
{
return $this->json(true);
}
/**
* {@inheritdoc}
*/
protected function render(string $view, array $parameters = [], Response $response = null): Response
{
$parameters['section'] = $this->getSection();
$parameters['site_name'] = $this->coreParameters['site']['name'];
$parameters['site_logo'] = $this->coreParameters['site']['logo'];
return parent::render($view, $parameters, $response);
}
abstract protected function getSection(): string;
}

View file

@ -1,342 +0,0 @@
<?php
namespace App\Core\Controller\Admin\Crud;
use App\Core\Controller\Admin\AdminController;
use App\Core\Crud\CrudConfiguration;
use App\Core\Entity\EntityInterface;
use App\Core\Manager\EntityManager;
use App\Core\Repository\RepositoryQuery;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
/**
* class CrudController.
*
* @author Simon Vieille <simon@deblan.fr>
*/
abstract class CrudController extends AdminController
{
protected array $filters = [];
protected array $sort = [
'label' => null,
'direction' => null,
];
abstract protected function getConfiguration(): CrudConfiguration;
protected function doIndex(int $page = 1, RepositoryQuery $query, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
$this->applySort('index', $query, $request);
$this->updateFilters($request, $session);
$pager = $query
->usefilters($this->filters)
->paginate($page, $configuration->getmaxperpage('index'))
;
return $this->render($this->getConfiguration()->getView('index'), [
'configuration' => $configuration,
'pager' => $pager,
'sort' => $this->sort,
'filters' => [
'show' => null !== $configuration->getForm('filter'),
'isEmpty' => empty($this->filters),
],
]);
}
protected function doNew(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeCreate = null): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm('new'), $entity, $configuration->getFormOptions('new'));
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
if (null !== $beforeCreate) {
call_user_func_array($beforeCreate, [$entity, $form, $request]);
}
$entityManager->create($entity);
$this->addFlash('success', 'The data has been saved.');
return $this->redirectToRoute($configuration->getPageRoute('edit'), array_merge(
['entity' => $entity->getId()],
$configuration->getPageRouteParams('edit')
));
}
$this->addFlash('warning', 'The form is not valid.');
}
return $this->render($configuration->getView('new'), [
'form' => $form->createView(),
'configuration' => $configuration,
'entity' => $entity,
]);
}
protected function doShow(EntityInterface $entity): Response
{
$configuration = $this->getConfiguration();
return $this->render($configuration->getView('show'), [
'entity' => $entity,
'configuration' => $configuration,
]);
}
protected function doEdit(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeUpdate = null): Response
{
$configuration = $this->getConfiguration();
$this->prepareEntity($entity);
$form = $this->createForm($configuration->getForm('edit'), $entity, $configuration->getFormOptions('edit'));
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isValid()) {
if (null !== $beforeUpdate) {
call_user_func_array($beforeUpdate, [$entity, $form, $request]);
}
$entityManager->update($entity);
$this->addFlash('success', 'The data has been saved.');
return $this->redirectToRoute($configuration->getPageRoute('edit'), array_merge(
['entity' => $entity->getId()],
$configuration->getPageRouteParams('edit')
));
}
$this->addFlash('warning', 'The form is not valid.');
}
return $this->render($configuration->getView('edit'), [
'form' => $form->createView(),
'configuration' => $configuration,
'entity' => $entity,
]);
}
protected function doSort(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
$context = $request->query->get('context', 'index');
if (!$configuration->getIsSortableCollection($context)) {
throw $this->createNotFoundException();
}
$this->applySort($context, $query, $request);
$this->updateFilters($request, $session);
$pager = $query
->useFilters($this->filters)
->paginate($page, $configuration->getMaxPerPage($context))
;
if ($this->isCsrfTokenValid('sort', $request->query->get('_token'))) {
$items = $request->request->get('items', []);
$setter = 'set'.$configuration->getSortableCollectionProperty();
$orderStart = ($page - 1) * $configuration->getMaxPerPage($context);
foreach ($pager as $key => $entity) {
if (isset($items[$key + 1])) {
$entity->{$setter}($items[$key + 1] + $orderStart);
$entityManager->update($entity);
}
}
$this->addFlash('success', 'The data has been saved.');
} else {
$this->addFlash('warning', 'The form is not valid.');
}
return $this->json([]);
}
protected function doBatch(int $page = 1, RepositoryQuery $query, EntityManager $entityManager, Request $request, Session $session): Response
{
$configuration = $this->getConfiguration();
$datas = $request->request->get('batch', []);
$context = $datas['context'] ?? 'index';
$target = $datas['target'] ?? null;
$action = $datas['action'] ?? null;
$token = $datas['_token'] ?? null;
$items = $datas['items'] ?? [];
$batchAction = $configuration->getBatchAction($context, $action);
if (empty($context) || empty($action) || empty($target)) {
return $this->json([]);
}
if (!$this->isCsrfTokenValid('batch', $token) || empty($batchAction)) {
$this->addFlash('warning', 'The form is not valid.');
return $this->json([]);
}
$callback = $batchAction['callback'];
$this->applySort($context, $query, $request);
$this->updateFilters($request, $session);
$query->useFilters($this->filters);
if ('selection' === $target) {
$isSelection = true;
$pager = $query->paginate($page, $configuration->getMaxPerPage($context));
} else {
$isSelection = false;
$pager = $query->find();
}
foreach ($pager as $key => $entity) {
if (($isSelection && isset($items[$key + 1])) || !$isSelection) {
$callback($entity, $entityManager);
}
}
$this->addFlash('success', 'Batch action done.');
return $this->json([]);
}
protected function doDelete(EntityInterface $entity, EntityManager $entityManager, Request $request, callable $beforeDelete = null): Response
{
$configuration = $this->getConfiguration();
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
if (null !== $beforeDelete) {
call_user_func($beforeDelete, $entity);
}
$entityManager->delete($entity);
$this->addFlash('success', 'The data has been removed.');
}
return $this->redirectToRoute($configuration->getPageRoute('index'));
}
protected function doFilter(Session $session): Response
{
$configuration = $this->getConfiguration();
$type = $configuration->getForm('filter');
if (null === $type) {
throw $this->createNotFoundException();
}
$form = $this->createForm($type);
$form->submit($session->get($form->getName(), []));
return $this->render($configuration->getView('filter'), [
'form' => $form->createView(),
'configuration' => $configuration,
]);
}
protected function updateFilters(Request $request, Session $session)
{
$configuration = $this->getConfiguration();
$type = $configuration->getForm('filter');
if (null === $type) {
return;
}
$form = $this->createForm($type);
if ($request->query->has($form->getName())) {
$filters = $request->query->get($form->getName());
if ('0' === $filters) {
$filters = [];
}
} elseif ($session->has($form->getName())) {
$filters = $session->get($form->getName());
} else {
$filters = [];
}
$form->submit($filters);
if (empty($filters)) {
$this->filters = $filters;
$session->set($form->getName(), $filters);
} elseif ($form->isValid()) {
$this->filters = $form->getData();
$session->set($form->getName(), $filters);
}
}
protected function prepareEntity(EntityInterface $entity)
{
$configuration = $this->getConfiguration();
if ($configuration->isI18n()) {
foreach ($configuration->getLocales() as $locale) {
$entity->addTranslation($entity->translate($locale, false));
}
}
}
protected function applySort(string $context, RepositoryQuery $query, Request $request)
{
$configuration = $this->getConfiguration();
if ($configuration->getIsSortableCollection($context)) {
$query->orderBy(sprintf('.%s', $configuration->getSortableCollectionProperty()));
return;
}
$defaultSort = $configuration->getDefaultSort($context);
$name = $request->query->get('_sort', $defaultSort['label'] ?? null);
$direction = strtolower($request->query->get('_sort_direction', $defaultSort['direction'] ?? 'asc'));
if (!in_array($direction, ['asc', 'desc'])) {
$direction = 'asc';
}
foreach ($configuration->getFields($context) as $label => $field) {
$sortOption = $field['options']['sort'] ?? null;
if (null === $sortOption) {
continue;
}
if ($sortOption[0] !== $name) {
continue;
}
$sorter = $sortOption[1];
if (is_string($sorter)) {
$query->orderBy($sorter, $direction);
} else {
call_user_func_array($sorter, [$query, $direction]);
}
$this->sort = [
'label' => $label,
'direction' => $direction,
];
return;
}
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace App\Core\Controller\Analytic;
use App\Core\Analytic\DateRangeAnalytic;
use App\Core\Entity\Site\Node;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/admin/analytic")
*/
class AnalyticController extends AbstractController
{
/**
* @Route("/stats/{node}/{range}", name="admin_analytic_stats")
*/
public function stats(Node $node, DateRangeAnalytic $analytic, string $range = '7days'): Response
{
if (!in_array($range, ['7days', '30days', '90days', '1year'])) {
throw $this->createNotFoundException();
}
$analytic
->setDateRange(new \DateTime('now - '.$range), new \DateTime())
->setNode($node)
;
return $this->render('@Core/analytic/stats.html.twig', [
'range' => $range,
'views' => $analytic->getViews(),
'pathViews' => $analytic->getPathViews(),
'referers' => $analytic->getReferers(),
'node' => $node,
]);
}
}

View file

@ -1,155 +0,0 @@
<?php
namespace App\Core\Controller\Auth;
use App\Core\Event\Account\PasswordRequestEvent;
use App\Core\Manager\EntityManager;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use ZxcvbnPhp\Zxcvbn;
class AuthController extends AbstractController
{
protected array $coreParameters;
public function __construct(ParameterBagInterface $parameters)
{
$this->coreParameters = $parameters->get('core');
}
/**
* @Route("/login", name="auth_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('admin_dashboard_index');
}
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('@Core/auth/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
'site_name' => $this->coreParameters['site']['name'],
'site_logo' => $this->coreParameters['site']['logo'],
]);
}
/**
* @Route("/resetting/request", name="auth_resetting_request")
*/
public function requestResetting(Request $request, UserRepository $repository, EventDispatcherInterface $eventDispatcher): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('admin_dashboard_index');
}
if ($request->isMethod('POST')) {
$csrfToken = $request->request->get('_csrf_token');
if (!$this->isCsrfTokenValid('resetting_request', $csrfToken)) {
throw $this->createAccessDeniedException();
}
$username = trim((string) $request->request->get('username'));
if (!$username) {
throw $this->createAccessDeniedException();
}
$account = $repository->findOneByEmail($username);
if ($account) {
$requestedAt = $account->getPasswordRequestedAt();
if (null === $requestedAt || $requestedAt->getTimestamp() < (time() - 3600 / 2)) {
$eventDispatcher->dispatch(new PasswordRequestEvent($account), PasswordRequestEvent::EVENT);
}
}
}
return $this->render('@Core/auth/resetting_request.html.twig', [
'email_sent' => $request->isMethod('POST'),
'site_name' => $this->coreParameters['site']['name'],
'site_logo' => $this->coreParameters['site']['logo'],
]);
}
/**
* @Route("/resetting/update/{token}", name="auth_resetting_update")
*/
public function requestUpdate(
string $token,
Request $request,
UserRepository $repository,
TokenGeneratorInterface $tokenGenerator,
UserPasswordEncoderInterface $encoder,
EntityManager $entityManager
): Response {
if ($this->getUser()) {
return $this->redirectToRoute('admin_dashboard_index');
}
$account = $repository->findOneByConfirmationToken($token);
$passwordUpdated = false;
$expired = true;
if ($account) {
$requestedAt = $account->getPasswordRequestedAt();
$expired = (null === $requestedAt || ($requestedAt->getTimestamp() < (time() - 3600 * 2)));
}
if ($request->isMethod('POST') && !$expired) {
$csrfToken = $request->request->get('_csrf_token');
if ($this->isCsrfTokenValid('resetting_update', $csrfToken)) {
$password = $request->request->get('password');
$password2 = $request->request->get('password2');
$zxcvbn = new Zxcvbn();
$strength = $zxcvbn->passwordStrength($password, []);
if (4 === $strength['score'] && $password === $password2) {
$account
->setPassword($encoder->encodePassword(
$account,
$password
))
->setConfirmationToken($tokenGenerator->generateToken())
->setPasswordRequestedAt(new \DateTime('now'))
;
$entityManager->update($account);
$passwordUpdated = true;
}
}
}
return $this->render('@Core/auth/resetting_update.html.twig', [
'password_updated' => $passwordUpdated,
'token' => $token,
'expired' => $expired,
'site_name' => $this->coreParameters['site']['name'],
'site_logo' => $this->coreParameters['site']['logo'],
]);
}
/**
* @Route("/logout", name="auth_logout")
*/
public function logout()
{
throw new \Exception('This method can be blank - it will be intercepted by the logout key on your firewall');
}
}

View file

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

View file

@ -1,438 +0,0 @@
<?php
namespace App\Core\Controller\FileManager;
use App\Core\Controller\Admin\AdminController;
use App\Core\FileManager\FsFileManager;
use App\Core\Form\FileManager\DirectoryCreateType;
use App\Core\Form\FileManager\DirectoryRenameType;
use App\Core\Form\FileManager\FileInformationType;
use App\Core\Form\FileManager\FileRenameType;
use App\Core\Form\FileManager\FileUploadType;
use App\Core\Manager\EntityManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @Route("/admin/file_manager")
*/
class FileManagerAdminController extends AdminController
{
/**
* @Route("/", name="admin_file_manager_index")
*/
public function index(): Response
{
return $this->render('@Core/file_manager/index.html.twig');
}
/**
* @Route("/api/directory", name="admin_file_manager_api_directory", options={"expose"=true})
*/
public function directory(FsFileManager $manager, Request $request): Response
{
$options = [
'sort' => $request->query->get('_sort', 'name'),
'sort_direction' => $request->query->get('_sort_direction', 'asc'),
];
$files = $manager->list($request->query->get('directory', '/'), $options);
return $this->json($files);
}
/**
* @Route("/info/{tab}/{context}/{ajax}", name="admin_file_manager_info", options={"expose"=true})
*/
public function info(
FsFileManager $manager,
Request $request,
EntityManager $entityManager,
TranslatorInterface $translator,
string $context = 'crud',
string $tab = 'information',
bool $ajax = false
): Response {
$splInfo = $manager->getSplInfo($request->query->get('file'));
if (!$splInfo) {
throw $this->createNotFoundException();
}
$fileInfo = $manager->getFileInformation($request->query->get('file'));
$path = $manager->getPathUri().'/'.$splInfo->getRelativePathname();
$form = $this->createForm(FileInformationType::class, $fileInfo);
if ($request->isMethod('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';
}
}

View file

@ -1,154 +0,0 @@
<?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';
}
}

View file

@ -1,81 +0,0 @@
<?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 '';
}
}

View file

@ -1,99 +0,0 @@
<?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';
}
}

View file

@ -1,82 +0,0 @@
<?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 '';
}
}

View file

@ -1,142 +0,0 @@
<?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';
}
}

View file

@ -1,305 +0,0 @@
<?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)
;
}
}
}

View file

@ -1,136 +0,0 @@
<?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';
}
}

View file

@ -1,57 +0,0 @@
<?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,
];
}
}

View file

@ -1,40 +0,0 @@
<?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);
}
}

View file

@ -1,87 +0,0 @@
<?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';
}
}

View file

@ -1,62 +0,0 @@
<?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';
}
}

View file

@ -1,131 +0,0 @@
<?php
namespace App\Core\Controller\User;
use App\Core\Controller\Admin\Crud\CrudController;
use App\Core\Crud\CrudConfiguration;
use App\Core\Crud\Field;
use App\Core\Event\Account\PasswordRequestEvent;
use App\Core\Factory\UserFactory as Factory;
use App\Core\Manager\EntityManager;
use App\Entity\User as Entity;
use App\Form\UserType as Type;
use App\Repository\UserRepositoryQuery as RepositoryQuery;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
use App\Core\Security\TokenGenerator;
class UserAdminController extends CrudController
{
/**
* @Route("/admin/user/{page}", name="admin_user_index", methods={"GET"}, requirements={"page":"\d+"})
*/
public function index(RepositoryQuery $query, Request $request, Session $session, int $page = 1): Response
{
return $this->doIndex($page, $query, $request, $session);
}
/**
* @Route("/admin/user/new", name="admin_user_new", methods={"GET", "POST"})
*/
public function new(Factory $factory, EntityManager $entityManager, Request $request, TokenGenerator $tokenGenerator): Response
{
return $this->doNew($factory->create(null, $tokenGenerator->generateToken()), $entityManager, $request);
}
/**
* @Route("/admin/user/show/{entity}", name="admin_user_show", methods={"GET"})
*/
public function show(Entity $entity): Response
{
return $this->doShow($entity);
}
/**
* @Route("/admin/user/filter", name="admin_user_filter", methods={"GET"})
*/
public function filter(Session $session): Response
{
return $this->doFilter($session);
}
/**
* @Route("/admin/user/edit/{entity}", name="admin_user_edit", methods={"GET", "POST"})
*/
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doEdit($entity, $entityManager, $request);
}
/**
* @Route("/admin/user/delete/{entity}", name="admin_user_delete", methods={"DELETE"})
*/
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
{
return $this->doDelete($entity, $entityManager, $request);
}
/**
* @Route("/admin/user/resetting_request/{entity}", name="admin_user_resetting_request", methods={"POST"})
*/
public function requestResetting(Entity $entity, EventDispatcherInterface $eventDispatcher, Request $request): Response
{
if ($this->isCsrfTokenValid('resetting_request'.$entity->getId(), $request->request->get('_token'))) {
$eventDispatcher->dispatch(new PasswordRequestEvent($entity), PasswordRequestEvent::EVENT);
$this->addFlash('success', 'E-mail sent.');
}
return $this->redirectToRoute('admin_user_edit', [
'entity' => $entity->getId(),
]);
}
protected function getConfiguration(): CrudConfiguration
{
return CrudConfiguration::create()
->setPageTitle('index', 'Users')
->setPageTitle('edit', '{username}')
->setPageTitle('new', 'New user')
->setPageTitle('show', '{username}')
->setPageRoute('index', 'admin_user_index')
->setPageRoute('new', 'admin_user_new')
->setPageRoute('edit', 'admin_user_edit')
->setPageRoute('show', 'admin_user_show')
->setPageRoute('delete', 'admin_user_delete')
->setPageRoute('filter', 'admin_user_filter')
->setForm('edit', Type::class, [])
->setForm('new', Type::class)
->setView('form', '@Core/user/user_admin/_form.html.twig')
->setView('index', '@Core/user/user_admin/index.html.twig')
->setView('new', '@Core/user/user_admin/new.html.twig')
->setView('show', '@Core/user/user_admin/show.html.twig')
->setView('show_entity', '@Core/user/user_admin/_show.html.twig')
->setView('edit', '@Core/user/user_admin/edit.html.twig')
->setDefaultSort('index', 'username')
->setField('index', 'E-mail', Field\TextField::class, [
'property' => 'email',
'sort' => ['email', '.email'],
'attr' => ['class' => 'miw-200'],
])
->setField('index', 'Display name', Field\TextField::class, [
'property' => 'displayName',
'sort' => ['displayName', '.displayName'],
'attr' => ['class' => 'miw-200'],
])
;
}
protected function getSection(): string
{
return 'user';
}
}

View file

@ -1,330 +0,0 @@
<?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;
}
}

View file

@ -1,12 +0,0 @@
<?php
namespace App\Core\Crud\Exception;
/**
* class CrudConfigurationException.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class CrudConfigurationException extends \Exception
{
}

View file

@ -1,41 +0,0 @@
<?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;
}
}

View file

@ -1,25 +0,0 @@
<?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;
}
}

View file

@ -1,25 +0,0 @@
<?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;
}
}

View file

@ -1,92 +0,0 @@
<?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'];
}
}

View file

@ -1,27 +0,0 @@
<?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;
}
}

View file

@ -1,24 +0,0 @@
<?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;
}
}

View file

@ -1,122 +0,0 @@
<?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;
}
}

View file

@ -1,28 +0,0 @@
<?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();
}
}

View file

@ -1,59 +0,0 @@
<?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;
}
}

View file

View file

@ -1,103 +0,0 @@
<?php
namespace App\Core\Entity\Analytic;
use App\Core\Entity\Site\Node;
use App\Repository\Entity\Analytic\NodeViewRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
/**
* @ORM\Entity(repositoryClass=ViewRepository::class)
* @ORM\Table(name="analytic_referer")
*/
class Referer implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="analyticReferers")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $node;
/**
* @ORM\Column(type="string", length=255)
*/
protected $uri;
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
protected $views = 0;
/**
* @ORM\Column(type="date")
*/
protected $date;
public function getId(): ?int
{
return $this->id;
}
public function getNode(): ?Node
{
return $this->node;
}
public function setNode(?Node $node): self
{
$this->node = $node;
return $this;
}
public function getUri(): ?string
{
return $this->uri;
}
public function setUri(string $uri): self
{
$this->uri = $uri;
return $this;
}
public function getViews(): ?int
{
return $this->views;
}
public function setViews(int $views): self
{
$this->views = $views;
return $this;
}
public function addView(): self
{
++$this->views;
return $this;
}
public function getDate(): ?\DateTimeInterface
{
return $this->date;
}
public function setDate(\DateTimeInterface $date): self
{
$this->date = $date;
return $this;
}
}

View file

@ -1,151 +0,0 @@
<?php
namespace App\Core\Entity\Analytic;
use App\Core\Entity\Site\Node;
use App\Repository\Entity\Analytic\NodeViewRepository;
use Doctrine\ORM\Mapping as ORM;
use App\Core\Entity\EntityInterface;
/**
* @ORM\Entity(repositoryClass=ViewRepository::class)
* @ORM\Table(name="analytic_view")
*/
class View implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="analyticViews")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $node;
/**
* @ORM\Column(type="string", length=255)
*/
protected $path;
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
protected $views = 0;
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
protected $desktopViews = 0;
/**
* @ORM\Column(type="integer", options={"default"=0})
*/
protected $mobileViews = 0;
/**
* @ORM\Column(type="date")
*/
protected $date;
public function getId(): ?int
{
return $this->id;
}
public function getNode(): ?Node
{
return $this->node;
}
public function setNode(?Node $node): self
{
$this->node = $node;
return $this;
}
public function getPath(): ?string
{
return $this->path;
}
public function setPath(string $path): self
{
$this->path = $path;
return $this;
}
public function getViews(): ?int
{
return $this->views;
}
public function setViews(int $views): self
{
$this->views = $views;
return $this;
}
public function addView(): self
{
++$this->views;
return $this;
}
public function getDesktopViews(): ?int
{
return $this->desktopViews;
}
public function setDesktopViews(int $desktopViews): self
{
$this->desktopViews = $desktopViews;
return $this;
}
public function addDesktopView(): self
{
++$this->desktopViews;
return $this;
}
public function getMobileViews(): ?int
{
return $this->mobileViews;
}
public function setMobileViews(int $mobileViews): self
{
$this->mobileViews = $mobileViews;
return $this;
}
public function addMobileView(): self
{
++$this->mobileViews;
return $this;
}
public function getDate(): ?\DateTimeInterface
{
return $this->date;
}
public function setDate(\DateTimeInterface $date): self
{
$this->date = $date;
return $this;
}
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Core\Entity;
interface EntityInterface
{
}

View file

@ -1,48 +0,0 @@
<?php
namespace App\Core\Entity;
use App\Repository\Entity\FileInformationRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=FileInformationRepository::class)
*/
class FileInformation implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="NONE")
* @ORM\Column(type="string", length=96, unique=true)
*/
protected $id;
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $attributes;
public function getId(): ?string
{
return $this->id;
}
public function setId(string $id): self
{
$this->id = $id;
return $this;
}
public function getAttributes()
{
return (array) json_decode($this->attributes, true);
}
public function setAttributes($attributes): self
{
$this->attributes = json_encode($attributes);
return $this;
}
}

View file

@ -1,111 +0,0 @@
<?php
namespace App\Core\Entity;
use App\Core\Entity\Site\Navigation;
use App\Core\Repository\NavigationSettingRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=NavigationSettingRepository::class)
*/
class NavigationSetting implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
protected $section;
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
/**
* @ORM\Column(type="string", length=255)
*/
protected $code;
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $value;
/**
* @ORM\ManyToOne(targetEntity=Navigation::class, inversedBy="navigationSettings")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $navigation;
public function getId(): ?int
{
return $this->id;
}
public function getSection(): ?string
{
return $this->section;
}
public function setSection(string $section): self
{
$this->section = $section;
return $this;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): self
{
$this->code = $code;
return $this;
}
public function getValue()
{
return json_decode($this->value, true);
}
public function setValue($value): self
{
$this->value = json_encode($value);
return $this;
}
public function getNavigation(): ?Navigation
{
return $this->navigation;
}
public function setNavigation(?Navigation $navigation): self
{
$this->navigation = $navigation;
return $this;
}
}

View file

@ -1,211 +0,0 @@
<?php
namespace App\Core\Entity;
use App\Core\Repository\RedirectRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=RedirectRepository::class)
*/
class Redirect implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string", length=5)
*/
protected $scheme;
/**
* @ORM\Column(type="string", length=255)
*/
protected $domain;
/**
* @ORM\Column(type="string", length=6)
*/
protected $domainType;
/**
* @ORM\Column(type="string", length=255)
*/
protected $rule;
/**
* @ORM\Column(type="string", length=6)
*/
protected $ruleType;
/**
* @ORM\Column(type="string", length=255)
*/
protected $location;
/**
* @ORM\Column(type="integer")
*/
protected $redirectCode;
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
/**
* @ORM\Column(type="integer", nullable=true)
*/
protected $sortOrder;
/**
* @ORM\Column(type="boolean")
*/
protected $isEnabled;
/**
* @ORM\Column(type="boolean")
*/
protected $reuseQueryString;
public function getId(): ?int
{
return $this->id;
}
public function getScheme(): ?string
{
return $this->scheme;
}
public function setScheme(string $scheme): self
{
$this->scheme = $scheme;
return $this;
}
public function getDomain(): ?string
{
return $this->domain;
}
public function setDomain(string $domain): self
{
$this->domain = $domain;
return $this;
}
public function getDomainType(): ?string
{
return $this->domainType;
}
public function setDomainType(string $domainType): self
{
$this->domainType = $domainType;
return $this;
}
public function getRule(): ?string
{
return $this->rule;
}
public function setRule(string $rule): self
{
$this->rule = $rule;
return $this;
}
public function getRuleType(): ?string
{
return $this->ruleType;
}
public function setRuleType(string $ruleType): self
{
$this->ruleType = $ruleType;
return $this;
}
public function getLocation(): ?string
{
return $this->location;
}
public function setLocation(string $location): self
{
$this->location = $location;
return $this;
}
public function getRedirectCode(): ?int
{
return $this->redirectCode;
}
public function setRedirectCode(int $redirectCode): self
{
$this->redirectCode = $redirectCode;
return $this;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
public function getSortOrder(): ?int
{
return $this->sortOrder;
}
public function setSortOrder(?int $sortOrder): self
{
$this->sortOrder = $sortOrder;
return $this;
}
public function getIsEnabled(): ?bool
{
return $this->isEnabled;
}
public function setIsEnabled(bool $isEnabled): self
{
$this->isEnabled = $isEnabled;
return $this;
}
public function getReuseQueryString(): ?bool
{
return $this->reuseQueryString;
}
public function setReuseQueryString(bool $reuseQueryString): self
{
$this->reuseQueryString = $reuseQueryString;
return $this;
}
}

View file

@ -1,92 +0,0 @@
<?php
namespace App\Core\Entity;
use App\Core\Repository\SettingRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=SettingRepository::class)
*/
class Setting implements EntityInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
protected $section;
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
/**
* @ORM\Column(type="string", length=255)
*/
protected $code;
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $value;
public function getId(): ?int
{
return $this->id;
}
public function getSection(): ?string
{
return $this->section;
}
public function setSection(string $section): self
{
$this->section = $section;
return $this;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): self
{
$this->code = $code;
return $this;
}
public function getValue()
{
return json_decode($this->value, true);
}
public function setValue($value): self
{
$this->value = json_encode($value);
return $this;
}
}

View file

@ -1,146 +0,0 @@
<?php
namespace App\Core\Entity\Site;
use App\Core\Doctrine\Timestampable;
use App\Core\Entity\EntityInterface;
use App\Core\Repository\Site\MenuRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=MenuRepository::class)
* @ORM\HasLifecycleCallbacks
*/
class Menu implements EntityInterface
{
use Timestampable;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
/**
* @ORM\Column(type="string", length=255)
*/
protected $code;
/**
* @ORM\ManyToOne(targetEntity=Navigation::class, inversedBy="menus")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $navigation;
/**
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="menu", orphanRemoval=true, cascade={"remove", "persist"})
*/
protected $nodes;
/**
* @ORM\OneToOne(targetEntity=Node::class, cascade={"persist"})
* @ORM\JoinColumn(onDelete="CASCADE")
*/
protected $rootNode;
public function __construct()
{
$this->nodes = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): self
{
$this->code = $code;
return $this;
}
public function getNavigation(): ?Navigation
{
return $this->navigation;
}
public function setNavigation(?Navigation $navigation): self
{
$this->navigation = $navigation;
return $this;
}
/**
* @return Collection|Node[]
*/
public function getNodes(): Collection
{
return $this->nodes;
}
public function addNode(Node $node): self
{
if (!$this->nodes->contains($node)) {
$this->nodes[] = $node;
$node->setMenu($this);
}
return $this;
}
public function removeNode(Node $node): self
{
if ($this->nodes->removeElement($node)) {
// set the owning side to null (unless already changed)
if ($node->getMenu() === $this) {
$node->setMenu(null);
}
}
return $this;
}
public function getRootNode(): ?Node
{
return $this->rootNode;
}
public function setRootNode(?Node $rootNode): self
{
$this->rootNode = $rootNode;
return $this;
}
public function getRouteName(): string
{
return $this->getNavigation()->getRouteName().'_'.($this->getCode() ? $this->getCode() : $this->getId());
}
}

View file

@ -1,243 +0,0 @@
<?php
namespace App\Core\Entity\Site;
use App\Core\Doctrine\Timestampable;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\NavigationSetting;
use App\Core\Repository\Site\NavigationRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=NavigationRepository::class)
* @ORM\HasLifecycleCallbacks
*/
class Navigation implements EntityInterface
{
use Timestampable;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
protected $label;
/**
* @ORM\Column(type="string", length=255)
*/
protected $code;
/**
* @ORM\Column(type="string", length=255)
*/
protected $domain;
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
protected $forceDomain = false;
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $additionalDomains = '[]';
/**
* @ORM\OneToMany(targetEntity=Menu::class, mappedBy="navigation")
*/
protected $menus;
/**
* @ORM\Column(type="string", length=10)
*/
protected $locale = 'en';
/**
* @ORM\Column(type="integer", nullable=true)
*/
protected $sortOrder;
/**
* @ORM\OneToMany(targetEntity=NavigationSetting::class, mappedBy="navigation", orphanRemoval=true)
*/
protected $navigationSettings;
public function __construct()
{
$this->menus = new ArrayCollection();
$this->navigationSettings = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(string $label): self
{
$this->label = $label;
return $this;
}
public function getCode(): ?string
{
return $this->code;
}
public function setCode(string $code): self
{
$this->code = $code;
return $this;
}
public function getDomain(): ?string
{
return $this->domain;
}
public function setDomain(string $domain): self
{
$this->domain = $domain;
return $this;
}
public function getForceDomain(): ?bool
{
return $this->forceDomain;
}
public function setForceDomain(bool $forceDomain): self
{
$this->forceDomain = $forceDomain;
return $this;
}
public function getAdditionalDomains(): array
{
return (array) json_decode($this->additionalDomains, true);
}
public function setAdditionalDomains(array $additionalDomains): self
{
$this->additionalDomains = json_encode($additionalDomains);
return $this;
}
/**
* @return Collection|Menu[]
*/
public function getMenus(): Collection
{
return $this->menus;
}
public function addMenu(Menu $menu): self
{
if (!$this->menus->contains($menu)) {
$this->menus[] = $menu;
$menu->setNavigation($this);
}
return $this;
}
public function removeMenu(Menu $menu): self
{
if ($this->menus->removeElement($menu)) {
// set the owning side to null (unless already changed)
if ($menu->getNavigation() === $this) {
$menu->setNavigation(null);
}
}
return $this;
}
public function getMenu(string $code): ?Menu
{
foreach ($this->menus as $menu) {
if ($menu->getCode() === $code) {
return $menu;
}
}
return $menu;
}
public function getRouteName(): string
{
return $this->getCode() ? $this->getCode() : 'navigation_'.$this->getId();
}
public function getLocale(): ?string
{
return $this->locale;
}
public function setLocale(string $locale): self
{
$this->locale = $locale;
return $this;
}
public function getSortOrder(): ?int
{
return $this->sortOrder;
}
public function setSortOrder(?int $sortOrder): self
{
$this->sortOrder = $sortOrder;
return $this;
}
/**
* @return Collection|NavigationSetting[]
*/
public function getNavigationSettings(): Collection
{
return $this->navigationSettings;
}
public function addNavigationSetting(NavigationSetting $navigationSetting): self
{
if (!$this->navigationSettings->contains($navigationSetting)) {
$this->navigationSettings[] = $navigationSetting;
$navigationSetting->setNavigation($this);
}
return $this;
}
public function removeNavigationSetting(NavigationSetting $navigationSetting): self
{
if ($this->navigationSettings->removeElement($navigationSetting)) {
// set the owning side to null (unless already changed)
if ($navigationSetting->getNavigation() === $this) {
$navigationSetting->setNavigation(null);
}
}
return $this;
}
}

View file

@ -1,643 +0,0 @@
<?php
namespace App\Core\Entity\Site;
use App\Core\Doctrine\Timestampable;
use App\Core\Entity\EntityInterface;
use App\Core\Entity\NodeView;
use App\Core\Entity\Site\Page\Page;
use App\Core\Repository\Site\NodeRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use function Symfony\Component\String\u;
use App\Core\Entity\Analytic\View;
use App\Core\Entity\Analytic\Referer;
/**
* @Gedmo\Tree(type="nested")
* @ORM\HasLifecycleCallbacks
* @ORM\Entity(repositoryClass=NodeRepository::class)
*/
class Node implements EntityInterface
{
use Timestampable;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity=Menu::class, inversedBy="nodes", cascade={"persist", "remove"})
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
protected $menu;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $label;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $url;
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
protected $disableUrl = false;
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
protected $isVisible = false;
/**
* @Gedmo\TreeLeft
* @ORM\Column(type="integer")
*/
protected $treeLeft;
/**
* @Gedmo\TreeLevel
* @ORM\Column(type="integer")
*/
protected $treeLevel;
/**
* @Gedmo\TreeRight
* @ORM\Column(type="integer")
*/
protected $treeRight;
/**
* @Gedmo\TreeRoot
* @ORM\ManyToOne(targetEntity="Node")
* @ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
*/
protected $treeRoot;
/**
* @Gedmo\TreeParent
* @ORM\ManyToOne(targetEntity="Node", inversedBy="children")
* @ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
*/
protected $parent;
/**
* @ORM\OneToMany(targetEntity="Node", mappedBy="parent")
* @ORM\OrderBy({"treeLeft"="ASC"})
*/
protected $children;
/**
* @ORM\ManyToOne(targetEntity=Page::class, inversedBy="nodes", cascade={"persist"})
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
protected $page;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $code;
/**
* @ORM\Column(type="array", nullable=true)
*/
protected $parameters = [];
/**
* @ORM\Column(type="array", nullable=true)
*/
protected $attributes = [];
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $controller;
/**
* @ORM\Column(type="array", nullable=true)
*/
protected $sitemapParameters = [];
/**
* @ORM\ManyToOne(targetEntity=Node::class, inversedBy="aliasNodes")
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
protected $aliasNode;
/**
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="aliasNode")
*/
protected $aliasNodes;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $contentType;
/**
* @ORM\Column(type="boolean", options={"default"=0})
*/
protected $enableAnalytics = false;
/**
* @ORM\OneToMany(targetEntity=View::class, mappedBy="node")
*/
protected $analyticViews;
/**
* @ORM\OneToMany(targetEntity=Referer::class, mappedBy="node")
*/
protected $analyticReferers;
public function __construct()
{
$this->children = new ArrayCollection();
$this->aliasNodes = new ArrayCollection();
$this->analyticViews = new ArrayCollection();
$this->analyticReferers = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getMenu(): ?Menu
{
return $this->menu;
}
public function setMenu(?Menu $menu): self
{
$this->menu = $menu;
return $this;
}
public function getTreeLeft(): ?int
{
return $this->treeLeft;
}
public function setTreeLeft(int $treeLeft): self
{
$this->treeLeft = $treeLeft;
return $this;
}
public function getTreeLevel(): ?int
{
return $this->treeLevel;
}
public function setTreeLevel(int $treeLevel): self
{
$this->treeLevel = $treeLevel;
return $this;
}
public function getTreeRight(): ?int
{
return $this->treeRight;
}
public function setTreeRight(int $treeRight): self
{
$this->treeRight = $treeRight;
return $this;
}
public function getTreeRoot(): ?self
{
return $this->treeRoot;
}
public function setTreeRoot(?self $treeRoot): self
{
$this->treeRoot = $treeRoot;
return $this;
}
public function getParent(): ?self
{
return $this->parent;
}
public function setParent(?self $parent): self
{
$this->parent = $parent;
return $this;
}
/**
* @return Collection|Node[]
*/
public function getChildren(array $criteria = []): Collection
{
if (null === $this->children) {
$this->children = new ArrayCollection();
}
if (!empty($criteria)) {
$children = new ArrayCollection();
foreach ($this->children as $child) {
$add = true;
if (isset($criteria['visible']) && $child->getIsVisible() !== $criteria['visible']) {
$add = false;
}
if ($add) {
$children->add($child);
}
}
return $children;
}
return $this->children;
}
public function addChild(Node $child): self
{
if (!$this->children->contains($child)) {
$this->children[] = $child;
$child->setParent($this);
}
return $this;
}
public function removeChild(Node $child): self
{
if ($this->children->removeElement($child)) {
// set the owning side to null (unless already changed)
if ($child->getParent() === $this) {
$child->setParent(null);
}
}
return $this;
}
public function getAllChildren(array $criteria = []): ArrayCollection
{
$children = [];
$getChildren = function (Node $node) use (&$children, &$getChildren) {
foreach ($node->getChildren() as $nodeChildren) {
$children[] = $nodeChildren;
$getChildren($nodeChildren);
}
};
$getChildren($this);
usort($children, function ($a, $b) {
return $a->getTreeLeft() < $b->getTreeLeft() ? -1 : 1;
});
if (!empty($criteria)) {
foreach ($children as $key => $child) {
if (isset($criteria['visible']) && $child->getIsVisible() !== $criteria['visible']) {
unset($children[$key]);
}
}
}
return new ArrayCollection($children);
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(?string $label): self
{
$this->label = $label;
return $this;
}
public function getUrl(): ?string
{
return $this->url;
}
public function setUrl(?string $url): self
{
$this->url = $url;
return $this;
}
public function hasExternalUrl(): bool
{
$string = u($this->getUrl());
return $string->startsWith('http://') || $string->startsWith('https://');
}
public function hasAppUrl(): bool
{
$string = u($this->getUrl());
foreach (['tel:', 'fax:', 'mailto:'] as $prefix) {
if ($string->startsWith($prefix)) {
return true;
}
}
return false;
}
public function getIsVisible(): ?bool
{
return $this->isVisible;
}
public function setIsVisible(bool $isVisible): self
{
$this->isVisible = $isVisible;
return $this;
}
public function getDisableUrl(): ?bool
{
return $this->disableUrl;
}
public function setDisableUrl(bool $disableUrl): self
{
$this->disableUrl = $disableUrl;
return $this;
}
public function getTreeLabel()
{
$prefix = str_repeat('-', ($this->getTreeLevel() - 1) * 5);
return trim($prefix.' '.$this->getLabel());
}
public function getPage(): ?Page
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getPage();
}
return $this->page;
}
public function setPage(?Page $page): self
{
$this->page = $page;
return $this;
}
public function getRouteName(): string
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getRouteName();
}
return $this->getMenu()->getRouteName().'_'.($this->getCode() ? $this->getCode() : $this->getId());
}
public function getCode(): ?string
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getCode();
}
return $this->code;
}
public function setCode(?string $code): self
{
$this->code = $code;
return $this;
}
public function getParameters(): ?array
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getParameters();
}
if (!is_array($this->parameters)) {
$this->parameters = [];
}
return $this->parameters;
}
public function setParameters(array $parameters): self
{
$this->parameters = $parameters;
return $this;
}
public function getAttributes(): ?array
{
if (!is_array($this->attributes)) {
$this->attributes = [];
}
return $this->attributes;
}
public function setAttributes(array $attributes): self
{
$this->attributes = $attributes;
return $this;
}
public function getController(): ?string
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getController();
}
return $this->controller;
}
public function setController(?string $controller): self
{
$this->controller = $controller;
return $this;
}
public function getSitemapParameters(): ?array
{
if ($this->getAliasNode()) {
return $this->getAliasNode()->getSitemapParameters();
}
if (!is_array($this->sitemapParameters)) {
$this->sitemapParameters = [
'isVisible' => false,
'priority' => 0,
'changeFrequency' => 'daily',
];
}
return $this->sitemapParameters;
}
public function setSitemapParameters(?array $sitemapParameters): self
{
$this->sitemapParameters = $sitemapParameters;
return $this;
}
public function getAliasNode(): ?self
{
return $this->aliasNode;
}
public function setAliasNode(?self $aliasNode): self
{
$this->aliasNode = $aliasNode;
return $this;
}
/**
* @return Collection|self[]
*/
public function getAliasNodes(): Collection
{
return $this->aliasNodes;
}
public function addAliasNode(self $aliasNode): self
{
if (!$this->aliasNodes->contains($aliasNode)) {
$this->aliasNodes[] = $aliasNode;
$aliasNode->setAliasNode($this);
}
return $this;
}
public function removeAliasNode(self $aliasNode): self
{
if ($this->aliasNodes->removeElement($aliasNode)) {
// set the owning side to null (unless already changed)
if ($aliasNode->getAliasNode() === $this) {
$aliasNode->setAliasNode(null);
}
}
return $this;
}
public function getContentType(): ?string
{
return $this->contentType;
}
public function setContentType(?string $contentType): self
{
$this->contentType = $contentType;
return $this;
}
public function getEnableAnalytics(): ?bool
{
return $this->enableAnalytics;
}
public function setEnableAnalytics(bool $enableAnalytics): self
{
$this->enableAnalytics = $enableAnalytics;
return $this;
}
/**
* @return Collection|View[]
*/
public function getAnalyticViews(): Collection
{
return $this->analyticViews;
}
public function addAnalyticView(View $view): self
{
if (!$this->analyticViews->contains($view)) {
$this->analyticViews[] = $view;
$view->setNode($this);
}
return $this;
}
public function removeAnalyticView(View $view): self
{
if ($this->analyticViews->removeElement($view)) {
// set the owning side to null (unless already changed)
if ($view->getNode() === $this) {
$view->setNode(null);
}
}
return $this;
}
/**
* @return Collection|Referer[]
*/
public function getAnalyticReferers(): Collection
{
return $this->analyticReferers;
}
public function addAnalyticReferer(Referer $referer): self
{
if (!$this->analyticReferers->contains($referer)) {
$this->analyticReferers[] = $referer;
$referer->setNode($this);
}
return $this;
}
public function removeAnalyticReferer(Referer $referer): self
{
if ($this->analyticReferers->removeElement($referer)) {
// set the owning side to null (unless already changed)
if ($referer->getNode() === $this) {
$referer->setNode(null);
}
}
return $this;
}
}

View file

@ -1,82 +0,0 @@
<?php
namespace App\Core\Entity\Site\Page;
use App\Core\Doctrine\Timestampable;
use App\Core\Repository\Site\Page\BlockRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=BlockRepository::class)
* @ORM\DiscriminatorColumn(name="class_key", type="string")
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\HasLifecycleCallbacks
*/
class Block
{
use Timestampable;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255)
*/
protected $name;
/**
* @ORM\Column(type="text", nullable=true)
*/
protected $value;
/**
* @ORM\ManyToOne(targetEntity=Page::class, inversedBy="blocks")
* @ORM\JoinColumn(onDelete="CASCADE")
*/
protected $page;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getValue()
{
return $this->value;
}
public function setValue($value): self
{
$this->value = $value;
return $this;
}
public function getPage(): ?Page
{
return $this->page;
}
public function setPage(?Page $page): self
{
$this->page = $page;
return $this;
}
}

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