init of Murph
This commit is contained in:
commit
b92ad093f6
35
.env
Normal file
35
.env
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# In all environments, the following files are loaded if they exist,
|
||||||
|
# the latter taking precedence over the former:
|
||||||
|
#
|
||||||
|
# * .env contains default values for the environment variables needed by the app
|
||||||
|
# * .env.local uncommitted file with local overrides
|
||||||
|
# * .env.$APP_ENV committed environment-specific defaults
|
||||||
|
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||||
|
#
|
||||||
|
# Real environment variables win over .env files.
|
||||||
|
#
|
||||||
|
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||||
|
#
|
||||||
|
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||||
|
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
APP_ENV=dev
|
||||||
|
APP_SECRET=e6e287f176fe2c69112fc620e1801bf0
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
|
###> doctrine/doctrine-bundle ###
|
||||||
|
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||||
|
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
|
||||||
|
#
|
||||||
|
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||||
|
# DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7"
|
||||||
|
DATABASE_URL="postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=13&charset=utf8"
|
||||||
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
|
###> symfony/swiftmailer-bundle ###
|
||||||
|
# For Gmail as a transport, use: "gmail://username:password@localhost"
|
||||||
|
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
|
||||||
|
# Delivery is disabled by default via "null://localhost"
|
||||||
|
MAILER_URL=null://localhost
|
||||||
|
###< symfony/swiftmailer-bundle ###
|
6
.env.test
Normal file
6
.env.test
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# define your env variables for the test env here
|
||||||
|
KERNEL_CLASS='App\Kernel'
|
||||||
|
APP_SECRET='$ecretf0rt3st'
|
||||||
|
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||||
|
PANTHER_APP_ENV=panther
|
||||||
|
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
27
.gitignore
vendored
Normal file
27
.gitignore
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
###> symfony/framework-bundle ###
|
||||||
|
/.env.local
|
||||||
|
/.env.local.php
|
||||||
|
/.env.*.local
|
||||||
|
/config/secrets/prod/prod.decrypt.private.php
|
||||||
|
/public/bundles/
|
||||||
|
/src/Command/TestCommand.php
|
||||||
|
/var/
|
||||||
|
/vendor/
|
||||||
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
|
###> symfony/phpunit-bridge ###
|
||||||
|
.phpunit
|
||||||
|
.phpunit.result.cache
|
||||||
|
/phpunit.xml
|
||||||
|
###< symfony/phpunit-bridge ###
|
||||||
|
|
||||||
|
###> symfony/webpack-encore-bundle ###
|
||||||
|
/node_modules/
|
||||||
|
/public/build/
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
###< symfony/webpack-encore-bundle ###
|
||||||
|
|
||||||
|
/public/uploads/
|
||||||
|
!/public/uploads/.gitkeep
|
27
Makefile
Normal file
27
Makefile
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
COMPOSER ?= composer
|
||||||
|
PHP ?= php7.4
|
||||||
|
SSH ?= ssh
|
||||||
|
WEBPACK ?= webpack
|
||||||
|
YARN ?= yarn
|
||||||
|
|
||||||
|
all: dep asset clean
|
||||||
|
|
||||||
|
.ONESHELL:
|
||||||
|
dep:
|
||||||
|
$(COMPOSER) update --ignore-platform-reqs
|
||||||
|
$(COMPOSER) install --ignore-platform-reqs
|
||||||
|
$(YARN)
|
||||||
|
|
||||||
|
asset-watch:
|
||||||
|
$(WEBPACK) -w
|
||||||
|
|
||||||
|
asset:
|
||||||
|
$(YARN)
|
||||||
|
$(WEBPACK)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -fr var/cache/dev/*
|
||||||
|
rm -fr var/cache/prod/*
|
||||||
|
|
||||||
|
doctrine-migration:
|
||||||
|
PHP=$(PHP) ./bin/doctrine-migrate
|
440
assets/css/admin.scss
Normal file
440
assets/css/admin.scss
Normal file
|
@ -0,0 +1,440 @@
|
||||||
|
$theme-colors: (
|
||||||
|
"primary": #1ab5dc,
|
||||||
|
"primary-light": lighten(#3183aa, 40%),
|
||||||
|
"dark-blue": #1e2430,
|
||||||
|
);
|
||||||
|
|
||||||
|
$grid-gutter-width: 0px;
|
||||||
|
$pagination-color: #343a40;
|
||||||
|
$pagination-bg: #ffffff;
|
||||||
|
$pagination-active-color: #ffffff;
|
||||||
|
$pagination-active-bg: #343a40;
|
||||||
|
|
||||||
|
@import "~choices.js/src/styles/choices.scss";
|
||||||
|
@import "~bootstrap/scss/bootstrap.scss";
|
||||||
|
@import "~@fortawesome/fontawesome-free/css/all.css";
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choices__list--dropdown {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choices__list--dropdown.is-active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle-hide-after {
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login {
|
||||||
|
&-container {
|
||||||
|
margin-top: 5%;
|
||||||
|
margin-bottom: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-form {
|
||||||
|
padding: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-image {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 100; /* Behind the navbar */
|
||||||
|
padding: 71px 0 0; /* Height of navbar */
|
||||||
|
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-sticky {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
height: calc(100vh - 71px);
|
||||||
|
padding-top: .5rem;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports ((position: -webkit-sticky) or (position: sticky)) {
|
||||||
|
.sidebar-sticky {
|
||||||
|
position: -webkit-sticky;
|
||||||
|
position: sticky;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-container {
|
||||||
|
padding-right: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thead-light {
|
||||||
|
a, th {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.table-primary-light {
|
||||||
|
background-color: #ecf5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td-nowrap {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-dark-blue {
|
||||||
|
background: #242b3b;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.nav-item-label {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-pills {
|
||||||
|
.nav-item {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:not(.active) {
|
||||||
|
color: #333;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
.nav-link {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
border-left: 4px solid map-get($theme-colors, 'dark-blue');
|
||||||
|
padding-top: 14px;
|
||||||
|
padding-bottom: 14px;
|
||||||
|
|
||||||
|
.fa {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-right: 5px;
|
||||||
|
min-width: 30px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
font-weight: bold;
|
||||||
|
border-left: 4px solid map-get($theme-colors, 'primary');
|
||||||
|
background: map-get($theme-colors, 'dark-blue');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-heading {
|
||||||
|
font-size: .75rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1130px) {
|
||||||
|
.nav-link {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 770px) {
|
||||||
|
.nav {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-heading {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*[data-selectable-selector] {
|
||||||
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[data-selectable-selector] {
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
height: 35px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
padding-top: 60px;
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 580px) {
|
||||||
|
.body {
|
||||||
|
margin-left: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 50px;
|
||||||
|
max-width: 100% !important;
|
||||||
|
|
||||||
|
.sidebar-sticky {
|
||||||
|
width: 50px;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.table-fixed, .table-fixed > table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead, tbody, tr, td, th {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
td, th {
|
||||||
|
float: left;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
tr {
|
||||||
|
th {
|
||||||
|
float: left;
|
||||||
|
|
||||||
|
&.sorted {
|
||||||
|
&::before {
|
||||||
|
content: '\f0dc';
|
||||||
|
font-family: 'FontAwesome';
|
||||||
|
color: #aaa;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-container {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1060;
|
||||||
|
|
||||||
|
.toast-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1060;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-form {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-margin {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-icon {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-ib {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-checkbox {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-strenth {
|
||||||
|
padding: 0 0 5px 0;
|
||||||
|
margin-top: -4px;
|
||||||
|
|
||||||
|
.col-sm {
|
||||||
|
height: 8px;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-info {
|
||||||
|
font-size: 13px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-bell:not([disabled]) {
|
||||||
|
[data-counter]:after {
|
||||||
|
display: block;
|
||||||
|
color: #fff;
|
||||||
|
background: red;
|
||||||
|
width: 9px;
|
||||||
|
height: 9px;
|
||||||
|
position: absolute;
|
||||||
|
content: ' ';
|
||||||
|
top: 4px;
|
||||||
|
right: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-error-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-file-label::after {
|
||||||
|
content: "Parcourir";
|
||||||
|
}
|
||||||
|
|
||||||
|
#lease_template_html {
|
||||||
|
height: calc(100vh - 270px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
&-toggler {
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&:not(.active) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*[data-collection-delete-container] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[data-collection-add] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-image {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree {
|
||||||
|
position: relative;
|
||||||
|
background: white;
|
||||||
|
color: #212529;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-style: italic;
|
||||||
|
letter-spacing: .4px;
|
||||||
|
color: #a8a8a8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-folder-open, .fa-folder {
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-html5 {
|
||||||
|
color: #f21f10;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 5px;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
padding-left: 15px;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: 0;
|
||||||
|
width: 10px;
|
||||||
|
height: 1px;
|
||||||
|
margin: auto;
|
||||||
|
content: '';
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
content: '';
|
||||||
|
background-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child:after {
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset.form-group {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
BIN
assets/images/blank.png
Normal file
BIN
assets/images/blank.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 163 B |
92
assets/images/core/logo.svg
Normal file
92
assets/images/core/logo.svg
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="92.5"
|
||||||
|
height="92.500008"
|
||||||
|
viewBox="0 0 24.473958 24.473961"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2782"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||||
|
sodipodi:docname="logo.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2776" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="3.959798"
|
||||||
|
inkscape:cx="144.24896"
|
||||||
|
inkscape:cy="62.558177"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:window-width="1918"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="41"
|
||||||
|
inkscape:window-maximized="0" />
|
||||||
|
<metadata
|
||||||
|
id="metadata2779">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-93.596354,-136.59635)">
|
||||||
|
<g
|
||||||
|
transform="translate(14.977383,9.0140333)"
|
||||||
|
id="g2760">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="rect2455"
|
||||||
|
d="m 80.981321,127.58232 h 19.749259 c 1.30874,0 2.36235,1.05361 2.36235,2.36235 v 19.74926 c 0,1.30874 -1.05361,2.36235 -2.36235,2.36235 H 80.981321 c -1.30874,0 -2.36235,-1.05361 -2.36235,-2.36235 v -19.74926 c 0,-1.30874 1.05361,-2.36235 2.36235,-2.36235 z"
|
||||||
|
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;vector-effect:none;fill:#1e2430;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10.58333302;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke fill markers;enable-background:accumulate" />
|
||||||
|
<g
|
||||||
|
transform="translate(-28.224115,84.535074)"
|
||||||
|
id="text2474-5"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24.23528671px;line-height:125%;font-family:Tahoma;-inkscape-font-specification:'Tahoma, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
aria-label="M">
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2522"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Trebuchet MS';-inkscape-font-specification:'Trebuchet MS';fill:#ffcc00;fill-opacity:1;stroke-width:0.26458332px"
|
||||||
|
d="m 125.90001,62.475697 h -2.98208 l -1.79871,-9.348573 -3.49093,9.573412 h -1.10052 l -3.49093,-9.573412 -1.86971,9.348573 h -2.97024 l 3.49092,-17.34811 h 1.63304 l 3.75126,11.679798 3.66843,-11.679798 h 1.62121 z" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="rect2455-3-6"
|
||||||
|
d="m 102.39375,128.26496 -23.092649,23.09213 c 0.427643,0.43204 1.021784,0.69919 1.680519,0.69919 h 19.74918 c 1.30874,0 2.36213,-1.05339 2.36213,-2.36213 v -19.74918 c 0,-0.65861 -0.26729,-1.25238 -0.69918,-1.68001 z"
|
||||||
|
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;vector-effect:none;fill:#19b4db;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:10.58333302;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:stroke fill markers;enable-background:accumulate" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2525-9-7"
|
||||||
|
d="m 94.723251,132.53384 -4.531505,4.53151 -1.343589,4.27726 -0.713134,-2.22054 -2.044319,2.04432 2.213301,6.0694 h 1.10019 l 3.491262,-9.57306 1.798338,9.34826 h 2.982249 z m -11.105782,11.10527 -3.371887,3.37188 h 2.697509 z"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:24.23528481px;line-height:125%;font-family:'Trebuchet MS';-inkscape-font-specification:'Trebuchet MS';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#1e2430;fill-opacity:1;stroke:#1e2430;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/images/logo.png
Normal file
BIN
assets/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
assets/images/no-image.png
Normal file
BIN
assets/images/no-image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
31
assets/js/addons/checkbox-checker.js
Normal file
31
assets/js/addons/checkbox-checker.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('*[data-checkbox-ckecker]').click(function() {
|
||||||
|
const wrapperName = $(this).attr('data-checkbox-ckecker');
|
||||||
|
|
||||||
|
if (!wrapperName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkboxes = $('*[data-checkbox-wrapper="' + wrapperName + '"] *[data-checkbox] input[type="checkbox"]');
|
||||||
|
|
||||||
|
$(checkboxes).each(function(i, v) {
|
||||||
|
$(v).prop('checked', true);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
$('*[data-checkbox-unckecker]').click(function() {
|
||||||
|
const wrapperName = $(this).attr('data-checkbox-unckecker');
|
||||||
|
|
||||||
|
if (!wrapperName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkboxes = $('*[data-checkbox-wrapper="' + wrapperName + '"] *[data-checkbox] input[type="checkbox"]');
|
||||||
|
|
||||||
|
$(checkboxes).each(function(i, v) {
|
||||||
|
$(v).prop('checked', false);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
8
assets/js/addons/choices.js
Normal file
8
assets/js/addons/choices.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const Choices = require('choices.js');
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('*[data-jschoice]').each(function(key, item) {
|
||||||
|
new Choices(item);
|
||||||
|
});
|
||||||
|
}
|
26
assets/js/addons/datepicker.js
Normal file
26
assets/js/addons/datepicker.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
const Datepicker = require('vanillajs-datepicker')
|
||||||
|
|
||||||
|
const isDateSupported = () => {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
const value = 'a';
|
||||||
|
|
||||||
|
input.setAttribute('type', 'date');
|
||||||
|
input.setAttribute('value', value);
|
||||||
|
|
||||||
|
return input.value !== value;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
if (isDateSupported()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputs = document.querySelectorAll('input[type="date"]')
|
||||||
|
const size = inputs.length
|
||||||
|
|
||||||
|
for (var i = 0, c = inputs.length; i < c; i++) {
|
||||||
|
new Datepicker.Datepicker(inputs[i], {
|
||||||
|
format: 'yyyy-mm-dd'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
7
assets/js/addons/dbclick.js
Normal file
7
assets/js/addons/dbclick.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('*[data-dblclick]').dblclick(function(e) {
|
||||||
|
document.location.href = $(this).attr('data-dblclick');
|
||||||
|
})
|
||||||
|
};
|
43
assets/js/addons/document-selector.js
Normal file
43
assets/js/addons/document-selector.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
let DocumentSelector = () => {
|
||||||
|
let forms = $('.document-selector-form');
|
||||||
|
let btnSubmit = $('#download-archive-form button');
|
||||||
|
|
||||||
|
let handler = function() {
|
||||||
|
forms.each((fi, f) => {
|
||||||
|
let form = $(f);
|
||||||
|
let ids = form.find('.document-selector-ids');
|
||||||
|
let btn = form.find('.document-selector-button');
|
||||||
|
|
||||||
|
ids.html('');
|
||||||
|
let hasSelection = false;
|
||||||
|
|
||||||
|
$('*[data-documents] *[data-selectable-row] input[data-selectable-checkbox]').each((i, c) => {
|
||||||
|
let checkbox = $(c);
|
||||||
|
|
||||||
|
if (checkbox.is(':checked')) {
|
||||||
|
ids.append(checkbox[0].outerHTML);
|
||||||
|
hasSelection = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasSelection && btn.length) {
|
||||||
|
btn.removeAttr('disabled');
|
||||||
|
ids.find('input').prop('checked', true);
|
||||||
|
} else {
|
||||||
|
btn.attr('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$('*[data-documents] *[data-selectable-row]').click(function() {
|
||||||
|
window.setTimeout(handler, 100)
|
||||||
|
});
|
||||||
|
|
||||||
|
$('*[data-documents] *[data-selectable-row]').on('clicked', function() {
|
||||||
|
window.setTimeout(handler, 100)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DocumentSelector;
|
23
assets/js/addons/editor.js
Normal file
23
assets/js/addons/editor.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
module.exports = function() {
|
||||||
|
if (typeof tinymce === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tinymce.init({
|
||||||
|
selector: '*[data-tinymce]',
|
||||||
|
base_url: '/vendor/tinymce/',
|
||||||
|
cache_suffix: '?v=4.1.6',
|
||||||
|
language: 'fr_FR',
|
||||||
|
plugins: 'print preview importcss searchreplace visualblocks visualchars fullscreen template table charmap hr pagebreak nonbreaking toc insertdatetime advlist lists wordcount textpattern noneditable help charmap quickbars',
|
||||||
|
menubar: 'file edit view insert format tools table tc help',
|
||||||
|
toolbar: 'undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist checklist | forecolor backcolor casechange permanentpen formatpainter removeformat | pagebreak | charmap | fullscreen preview | code',
|
||||||
|
importcss_append: true,
|
||||||
|
image_caption: true,
|
||||||
|
quickbars_selection_toolbar: 'bold italic | quicklink h2 h3 blockquote quickimage quicktable',
|
||||||
|
noneditable_noneditable_class: "mceNonEditable",
|
||||||
|
toolbar_drawer: 'sliding',
|
||||||
|
spellchecker_dialog: true,
|
||||||
|
tinycomments_mode: 'embedded',
|
||||||
|
contextmenu: "link image imagetools table configurepermanentpen",
|
||||||
|
});
|
||||||
|
};
|
84
assets/js/addons/form-collection.js
Normal file
84
assets/js/addons/form-collection.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
const DeleteHandler = (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
const target = e.target;
|
||||||
|
let button = $(target);
|
||||||
|
|
||||||
|
if (button.is('[data-collection-delete-container]')) {
|
||||||
|
button = button.find('*[data-collection-delete]').first()
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = button.attr('data-collection-delete');
|
||||||
|
const collection = button.parents('[data-collection]')
|
||||||
|
const item = collection.find('*[data-collection-item="' + id + '"]')
|
||||||
|
|
||||||
|
if (confirm('Validez-vous la suppression ?')) {
|
||||||
|
item.remove();
|
||||||
|
collection.trigger('collection.update');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CollectionInitilizedAndUpdated = (e) => {
|
||||||
|
const target = $(e.target)
|
||||||
|
|
||||||
|
target.find('*[data-collection-empty]').toggleClass(
|
||||||
|
'd-none',
|
||||||
|
target.find('*[data-collection-item]').length !== 0
|
||||||
|
);
|
||||||
|
|
||||||
|
target.find('*[data-collection-nonempty]').toggleClass(
|
||||||
|
'd-none',
|
||||||
|
target.find('*[data-collection-item]').length === 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormCollection = () => {
|
||||||
|
$('*[data-collection]').on(
|
||||||
|
'collection.update',
|
||||||
|
CollectionInitilizedAndUpdated
|
||||||
|
);
|
||||||
|
|
||||||
|
$('*[data-collection]').on(
|
||||||
|
'collection.init',
|
||||||
|
CollectionInitilizedAndUpdated
|
||||||
|
);
|
||||||
|
|
||||||
|
$('body').on(
|
||||||
|
'click',
|
||||||
|
'*[data-collection-delete], *[data-collection-delete-container]',
|
||||||
|
DeleteHandler
|
||||||
|
);
|
||||||
|
|
||||||
|
$('body').on('click', '*[data-collection-add]', (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const collectionId = $(e.target).attr('data-collection-add')
|
||||||
|
const collectionContainer = $('*[data-collection="' + collectionId + '"]')
|
||||||
|
const prototypeContent = $('#' + collectionId).html()
|
||||||
|
let name = 0
|
||||||
|
|
||||||
|
collectionContainer.find('*[data-collection-item]').each(function() {
|
||||||
|
var n = parseInt($(this).attr('data-collection-item'))
|
||||||
|
|
||||||
|
if (n >= name) {
|
||||||
|
name = n + 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
collectionContainer.append(prototypeContent)
|
||||||
|
|
||||||
|
const item = collectionContainer.children('*[data-collection-item]:last-child')
|
||||||
|
const deleteBtn = $('<span data-collection-delete="__name__" class="fa fa-trash"></span>')
|
||||||
|
|
||||||
|
item.find('*[data-collection-delete-container]').first().append(deleteBtn)
|
||||||
|
item.html(item.html().replace(/__name__/g, name))
|
||||||
|
item.attr('data-collection-item', name)
|
||||||
|
|
||||||
|
collectionContainer.trigger('collection.update');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('*[data-collection]').trigger('collection.init');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = FormCollection;
|
15
assets/js/addons/form-confirm.js
Normal file
15
assets/js/addons/form-confirm.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('*[data-form-confirm]').submit(function(e) {
|
||||||
|
let message = $(this).attr('data-form-confirm');
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
message = 'Confimez-vous cette action ?';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm(message)) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
11
assets/js/addons/form.js
Normal file
11
assets/js/addons/form.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('.custom-file-input').on('change', function(event) {
|
||||||
|
let inputFile = event.currentTarget;
|
||||||
|
|
||||||
|
$(inputFile).parent()
|
||||||
|
.find('.custom-file-label')
|
||||||
|
.html(inputFile.files[0].name);
|
||||||
|
});
|
||||||
|
};
|
31
assets/js/addons/modal.js
Normal file
31
assets/js/addons/modal.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('body').on('click', '*[data-modal]', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
let container = $('#modal-container');
|
||||||
|
|
||||||
|
if (!container.length) {
|
||||||
|
container = $('<div id="modal-container" class="modal">');
|
||||||
|
|
||||||
|
$('body').append(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
container.html('');
|
||||||
|
|
||||||
|
const url = $(e.target).attr('data-modal');
|
||||||
|
|
||||||
|
container.load(url, function() {
|
||||||
|
$(container).modal('show');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
const dataModal = urlParams.get('data-modal')
|
||||||
|
|
||||||
|
if (dataModal) {
|
||||||
|
$('*[data-modal="' + dataModal + '"]').first().click();
|
||||||
|
}
|
||||||
|
}
|
47
assets/js/addons/panel.js
Normal file
47
assets/js/addons/panel.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
let Pannel = () => {
|
||||||
|
let panels = $('.panel');
|
||||||
|
|
||||||
|
panels.each((i, p) => {
|
||||||
|
let panel = $(p);
|
||||||
|
let content = panel.find('.panel-content').first();
|
||||||
|
let togglers = panel.find('.panel-toggler');
|
||||||
|
|
||||||
|
togglers.each((k, t) => {
|
||||||
|
let toggler = $(t);
|
||||||
|
|
||||||
|
if (!toggler.is('.fa')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.is('.active')) {
|
||||||
|
toggler.removeClass('fa-arrow-down');
|
||||||
|
toggler.addClass('fa-arrow-up');
|
||||||
|
} else {
|
||||||
|
toggler.removeClass('fa-arrow-up');
|
||||||
|
toggler.addClass('fa-arrow-down');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
togglers.click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
content.toggleClass('active');
|
||||||
|
|
||||||
|
togglers.each((k, t) => {
|
||||||
|
let toggler = $(t);
|
||||||
|
|
||||||
|
if (!toggler.is('.fa')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggler
|
||||||
|
.toggleClass('fa-arrow-down')
|
||||||
|
.toggleClass('fa-arrow-up');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Pannel;
|
82
assets/js/addons/password.js
Normal file
82
assets/js/addons/password.js
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
const zxcvbn = require('zxcvbn');
|
||||||
|
|
||||||
|
let scoreColors = [
|
||||||
|
'danger',
|
||||||
|
'danger',
|
||||||
|
'warning',
|
||||||
|
'warning',
|
||||||
|
'success',
|
||||||
|
];
|
||||||
|
|
||||||
|
let scoreInfos = {
|
||||||
|
"This is a top-10 common password": "Parmis le top 10 des mots de passes communs",
|
||||||
|
"This is a top-100 common password": "Parmis le top 100 des mots de passes communs",
|
||||||
|
"This is a very common password": "Mot de passe vraiment trop commun",
|
||||||
|
"This is similar to a commonly used password": "Similaire à un mot de passe commun",
|
||||||
|
"A word by itself is easy to guess": "Ce mot est trop simple à deviner",
|
||||||
|
"Names and surnames by themselves are easy to guess": "Les noms ou les surnoms sont simples à deviner",
|
||||||
|
"Common names and surnames are easy to guess": "Les noms ou les surnoms sont simples à deviner",
|
||||||
|
"Straight rows of keys are easy to guess": "Combinaison de touches trop simple",
|
||||||
|
"Short keyboard patterns are easy to guess": "Combinaison de touches trop simple",
|
||||||
|
"Repeats like \"aaa\" are easy to guess'": "Les répétitions comme \"aaa\" sont simples à deviner",
|
||||||
|
"Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": "Les répétitions comme \"abcabcabc\" sont simples à deviner",
|
||||||
|
"Sequences like abc or 6543 are easy to guess": "Les séquences comme \"abc\" ou \"6543\" sont simples à deviner",
|
||||||
|
"Recent years are easy to guess": "Les années sont simples à deviner",
|
||||||
|
"Dates are often easy to guess": "Les dates sont souvent simples à deviner",
|
||||||
|
}
|
||||||
|
|
||||||
|
let checkPassword = function(password, confirmation, indicator, submit) {
|
||||||
|
let result = zxcvbn(password.val());
|
||||||
|
let score = result.score;
|
||||||
|
let cols = indicator.children('.col-sm');
|
||||||
|
let info = indicator.children('.password-strenth-info');
|
||||||
|
|
||||||
|
info.text('');
|
||||||
|
cols.attr('class', 'col-sm');
|
||||||
|
|
||||||
|
for (var u = 0; u <= 5; u++) {
|
||||||
|
let col = cols.eq(u);
|
||||||
|
if (u <= score) {
|
||||||
|
col.addClass('bg-' + scoreColors[score]);
|
||||||
|
} else {
|
||||||
|
col.addClass('bg-light');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(result)
|
||||||
|
|
||||||
|
info.text(scoreInfos[result.feedback.warning]);
|
||||||
|
info.attr('class', 'col-12 password-strenth-info text-' + scoreColors[score]);
|
||||||
|
|
||||||
|
if (score < 4 || confirmation.val() !== password.val()) {
|
||||||
|
submit.attr('disabled', 'disabled');
|
||||||
|
} else {
|
||||||
|
submit.removeAttr('disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
let passwordNew = $('#form-password-new');
|
||||||
|
let passwordConfirmation = $('#form-password-confirmation');
|
||||||
|
let passwordSubmit = $('#form-password-submit');
|
||||||
|
let passwordStrength = $('#form-password-strength');
|
||||||
|
|
||||||
|
if (passwordStrength.length) {
|
||||||
|
passwordNew.keyup(function() {
|
||||||
|
checkPassword(passwordNew, passwordConfirmation, passwordStrength, passwordSubmit);
|
||||||
|
});
|
||||||
|
|
||||||
|
passwordNew.change(function() {
|
||||||
|
checkPassword(passwordNew, passwordConfirmation, passwordStrength, passwordSubmit);
|
||||||
|
});
|
||||||
|
|
||||||
|
passwordConfirmation.keyup(function() {
|
||||||
|
checkPassword(passwordNew, passwordConfirmation, passwordStrength, passwordSubmit);
|
||||||
|
});
|
||||||
|
|
||||||
|
passwordConfirmation.change(function() {
|
||||||
|
checkPassword(passwordNew, passwordConfirmation, passwordStrength, passwordSubmit);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
44
assets/js/addons/push-state.js
Normal file
44
assets/js/addons/push-state.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('*[data-pushstate]').click((e) => {
|
||||||
|
var url = $(e.target).attr('data-pushstate');
|
||||||
|
|
||||||
|
history.pushState({url: url}, null, url);
|
||||||
|
history.replaceState({url: url}, null, url);
|
||||||
|
});
|
||||||
|
|
||||||
|
let forms = $('form[data-formpushstate]');
|
||||||
|
|
||||||
|
let checkAndUsePushState = () => {
|
||||||
|
let state = [window.location.pathname, window.location.search].join('');
|
||||||
|
|
||||||
|
$('*[data-pushstate]').each((i, v) => {
|
||||||
|
let method = 'compare';
|
||||||
|
|
||||||
|
if ($(v).is('[data-pushstate-method]')) {
|
||||||
|
method = $(v).attr('data-pushstate-method')
|
||||||
|
}
|
||||||
|
|
||||||
|
var isThisOne = false;
|
||||||
|
|
||||||
|
if (method === 'compare' && $(v).attr('data-pushstate') === state) {
|
||||||
|
isThisOne = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === 'indexOf' && state.indexOf($(v).attr('data-pushstate')) !== -1) {
|
||||||
|
isThisOne = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isThisOne) {
|
||||||
|
$(v).click();
|
||||||
|
|
||||||
|
forms.attr('action', state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAndUsePushState();
|
||||||
|
|
||||||
|
$(window).on('statechange', checkAndUsePushState, false);
|
||||||
|
}
|
27
assets/js/addons/rest-choices.js
Normal file
27
assets/js/addons/rest-choices.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
const Choices = require('choices.js');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('*[data-rest-choices]').each(function(key, item) {
|
||||||
|
const url = $(this).attr('data-rest-choices');
|
||||||
|
|
||||||
|
new Choices(item, {
|
||||||
|
searchPlaceholderValue: 'Chercher',
|
||||||
|
}).setChoices(function() {
|
||||||
|
return fetch(url)
|
||||||
|
.then(function(response) {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
return data.map(function(d) {
|
||||||
|
return {
|
||||||
|
label: d.label,
|
||||||
|
value: d.value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(function(instance) {
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
24
assets/js/addons/table-fixed.js
Normal file
24
assets/js/addons/table-fixed.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
let resizeTbody = (tbody) => {
|
||||||
|
tbody.height($(window).height() - tbody.offset().top - 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tableFixed = () => {
|
||||||
|
let tables = $('table[data-table-fixed], *[data-table-fixed] > table');
|
||||||
|
|
||||||
|
tables.each((i, t) => {
|
||||||
|
let table = $(t);
|
||||||
|
table.addClass('table-fixed');
|
||||||
|
|
||||||
|
let tbody = table.find('tbody');
|
||||||
|
|
||||||
|
resizeTbody(tbody);
|
||||||
|
|
||||||
|
$(window).resize(function() {
|
||||||
|
resizeTbody(tbody);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = tableFixed;
|
122
assets/js/addons/table-selectable.js
Normal file
122
assets/js/addons/table-selectable.js
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
const selectedClass = 'table-primary-light';
|
||||||
|
|
||||||
|
let toggleRow = (row, checkbox, checkboxIsClicked) => {
|
||||||
|
row.toggleClass(selectedClass);
|
||||||
|
|
||||||
|
if (checkboxIsClicked) {
|
||||||
|
checkbox.prop('checked', checkbox.prop('checked'));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkbox.length) {
|
||||||
|
checkbox.prop('checked', !checkbox.prop('checked'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let unactiveRow = (row, checkbox) => {
|
||||||
|
row.removeClass(selectedClass);
|
||||||
|
|
||||||
|
if (checkbox.length) {
|
||||||
|
checkbox.prop('checked', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeRow = (row, checkbox) => {
|
||||||
|
row.addClass(selectedClass);
|
||||||
|
|
||||||
|
if (checkbox.length) {
|
||||||
|
checkbox.prop('checked', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tableSelectable = () => {
|
||||||
|
let tables = $('*[data-selectable]');
|
||||||
|
|
||||||
|
tables.each((i, t) => {
|
||||||
|
var table = $(t);
|
||||||
|
var rows = table.find('*[data-selectable-row]');
|
||||||
|
let selectedIndex = null;
|
||||||
|
|
||||||
|
var tbody = table.find('tbody');
|
||||||
|
|
||||||
|
var resizer = () => {
|
||||||
|
tbody.height($(window).height() - tbody.offset().top - 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.setInterval(resizer, 1000);
|
||||||
|
resizer();
|
||||||
|
$(window).resize(resizer);
|
||||||
|
|
||||||
|
|
||||||
|
((rows) => {
|
||||||
|
rows.each((i, r) => {
|
||||||
|
let row = $(r);
|
||||||
|
let checkbox = row.find('*[data-selectable-checkbox]');
|
||||||
|
let selectors = row.find('*[data-selectable-selector]');
|
||||||
|
|
||||||
|
((row, selectors, checkbox, index) => {
|
||||||
|
selectors.click((e) => {
|
||||||
|
if (event.target.nodeName === 'INPUT') {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
checkbox.trigger('clicked');
|
||||||
|
|
||||||
|
return toggleRow(row, checkbox, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.event.ctrlKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
return toggleRow(row, checkbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.event.button === 0) {
|
||||||
|
if (!window.event.ctrlKey && !window.event.shiftKey) {
|
||||||
|
rows.each((z, r2) => {
|
||||||
|
unactiveRow($(r2), $(r2).find('*[data-selectable-checkbox]'));
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleRow(row, checkbox);
|
||||||
|
|
||||||
|
if (row.hasClass(selectedClass)) {
|
||||||
|
selectedIndex = index;
|
||||||
|
} else {
|
||||||
|
selectedIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.event.shiftKey) {
|
||||||
|
if (selectedIndex !== null) {
|
||||||
|
rows.each((z, r2) => {
|
||||||
|
if (selectedIndex <= index) {
|
||||||
|
if (z >= selectedIndex && z <= index) {
|
||||||
|
activeRow($(r2), $(r2).find('*[data-selectable-checkbox]'));
|
||||||
|
} else {
|
||||||
|
unactiveRow($(r2), $(r2).find('*[data-selectable-checkbox]'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (z <= selectedIndex && z >= index) {
|
||||||
|
activeRow($(r2), $(r2).find('*[data-selectable-checkbox]'));
|
||||||
|
} else {
|
||||||
|
unactiveRow($(r2), $(r2).find('*[data-selectable-checkbox]'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//selectedIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})(row, selectors, checkbox, i);
|
||||||
|
});
|
||||||
|
})(rows);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = tableSelectable;
|
11
assets/js/addons/toast.js
Normal file
11
assets/js/addons/toast.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('.toast').toast({
|
||||||
|
animation: true,
|
||||||
|
autohide: true,
|
||||||
|
delay: 5000,
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.toast').toast('show');
|
||||||
|
};
|
5
assets/js/addons/tooltip.js
Normal file
5
assets/js/addons/tooltip.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
const $ = require('jquery');
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
$('*[data-toggle="tooltip"]').tooltip();
|
||||||
|
};
|
28
assets/js/admin.js
Normal file
28
assets/js/admin.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*const imagesContext = require.context(
|
||||||
|
'../images',
|
||||||
|
true, /\.(png|jpg|jpeg|gif|ico|svg|webp)$/
|
||||||
|
);
|
||||||
|
|
||||||
|
imagesContext.keys().forEach(imagesContext);*/
|
||||||
|
|
||||||
|
import '../css/admin.scss';
|
||||||
|
|
||||||
|
require('../../node_modules/bootstrap/dist/js/bootstrap.min.js');
|
||||||
|
// require('./addons/table-selectable.js')();
|
||||||
|
require('./addons/table-fixed.js')();
|
||||||
|
// require('./addons/document-selector.js')();
|
||||||
|
require('./addons/form-confirm.js')();
|
||||||
|
require('./addons/form.js')();
|
||||||
|
require('./addons/dbclick.js')();
|
||||||
|
require('./addons/toast.js')();
|
||||||
|
require('./addons/modal.js')();
|
||||||
|
require('./addons/push-state.js')();
|
||||||
|
require('./addons/password.js')();
|
||||||
|
require('./addons/tooltip.js')();
|
||||||
|
require('./addons/editor.js')();
|
||||||
|
require('./addons/panel.js')();
|
||||||
|
require('./addons/choices.js')();
|
||||||
|
require('./addons/checkbox-checker.js')();
|
||||||
|
require('./addons/rest-choices.js')();
|
||||||
|
require('./addons/form-collection.js')();
|
||||||
|
require('./addons/datepicker.js')();
|
43
bin/console
Executable file
43
bin/console
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Kernel;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||||
|
use Symfony\Component\Console\Input\ArgvInput;
|
||||||
|
use Symfony\Component\Dotenv\Dotenv;
|
||||||
|
use Symfony\Component\ErrorHandler\Debug;
|
||||||
|
|
||||||
|
if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
|
||||||
|
echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
require dirname(__DIR__).'/vendor/autoload.php';
|
||||||
|
|
||||||
|
if (!class_exists(Application::class) || !class_exists(Dotenv::class)) {
|
||||||
|
throw new LogicException('You need to add "symfony/framework-bundle" and "symfony/dotenv" as Composer dependencies.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = new ArgvInput();
|
||||||
|
if (null !== $env = $input->getParameterOption(['--env', '-e'], null, true)) {
|
||||||
|
putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->hasParameterOption('--no-debug', true)) {
|
||||||
|
putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||||
|
|
||||||
|
if ($_SERVER['APP_DEBUG']) {
|
||||||
|
umask(0000);
|
||||||
|
|
||||||
|
if (class_exists(Debug::class)) {
|
||||||
|
Debug::enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
|
||||||
|
$application = new Application($kernel);
|
||||||
|
$application->run($input);
|
7
bin/doctrine-migrate
Executable file
7
bin/doctrine-migrate
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
CLASS_NAME="$(echo "yes" | "$PHP" ./bin/console doctrine:migration:diff -e dev | grep -o "Version[0-9]*" | tail -n 1)"
|
||||||
|
|
||||||
|
if [ -n "$CLASS_NAME" ]; then
|
||||||
|
echo "yes" | "$PHP" ./bin/console doctrine:migration:exec --up "DoctrineMigrations\\$CLASS_NAME" -e dev
|
||||||
|
fi
|
13
bin/phpunit
Executable file
13
bin/phpunit
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (!file_exists(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||||
|
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === getenv('SYMFONY_PHPUNIT_DIR')) {
|
||||||
|
putenv('SYMFONY_PHPUNIT_DIR='.__DIR__.'/.phpunit');
|
||||||
|
}
|
||||||
|
|
||||||
|
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
110
composer.json
Normal file
110
composer.json
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"type": "project",
|
||||||
|
"license": "proprietary",
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true,
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2.5",
|
||||||
|
"ext-ctype": "*",
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"bjeavons/zxcvbn-php": "^1.2",
|
||||||
|
"cocur/slugify": "^4.0",
|
||||||
|
"composer/package-versions-deprecated": "1.11.99.1",
|
||||||
|
"doctrine/annotations": "^1.0",
|
||||||
|
"doctrine/doctrine-bundle": "^2.2",
|
||||||
|
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||||
|
"doctrine/orm": "^2.8",
|
||||||
|
"knplabs/knp-paginator-bundle": "^5.4",
|
||||||
|
"phpdocumentor/reflection-docblock": "^5.2",
|
||||||
|
"scheb/2fa-google-authenticator": "^5.7",
|
||||||
|
"scheb/2fa-qr-code": "^5.7",
|
||||||
|
"sensio/framework-extra-bundle": "^6.1",
|
||||||
|
"stof/doctrine-extensions-bundle": "^1.6",
|
||||||
|
"symfony/apache-pack": "^1.0",
|
||||||
|
"symfony/asset": "5.2.*",
|
||||||
|
"symfony/console": "5.2.*",
|
||||||
|
"symfony/dotenv": "5.2.*",
|
||||||
|
"symfony/event-dispatcher": "5.2.*",
|
||||||
|
"symfony/expression-language": "5.2.*",
|
||||||
|
"symfony/finder": "5.2.*",
|
||||||
|
"symfony/flex": "^1.3.1",
|
||||||
|
"symfony/form": "5.2.*",
|
||||||
|
"symfony/framework-bundle": "5.2.*",
|
||||||
|
"symfony/http-client": "5.2.*",
|
||||||
|
"symfony/intl": "5.2.*",
|
||||||
|
"symfony/mailer": "5.2.*",
|
||||||
|
"symfony/mime": "5.2.*",
|
||||||
|
"symfony/monolog-bundle": "^3.1",
|
||||||
|
"symfony/notifier": "5.2.*",
|
||||||
|
"symfony/process": "5.2.*",
|
||||||
|
"symfony/property-access": "5.2.*",
|
||||||
|
"symfony/property-info": "5.2.*",
|
||||||
|
"symfony/proxy-manager-bridge": "5.2.*",
|
||||||
|
"symfony/security-bundle": "5.2.*",
|
||||||
|
"symfony/serializer": "5.2.*",
|
||||||
|
"symfony/string": "5.2.*",
|
||||||
|
"symfony/swiftmailer-bundle": "^3.5",
|
||||||
|
"symfony/translation": "5.2.*",
|
||||||
|
"symfony/twig-bundle": "^5.2",
|
||||||
|
"symfony/validator": "5.2.*",
|
||||||
|
"symfony/web-link": "5.2.*",
|
||||||
|
"symfony/webpack-encore-bundle": "^1.11",
|
||||||
|
"symfony/yaml": "5.2.*",
|
||||||
|
"twig/extra-bundle": "^2.12|^3.0",
|
||||||
|
"twig/twig": "^2.12|^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/browser-kit": "^5.2",
|
||||||
|
"symfony/css-selector": "^5.2",
|
||||||
|
"symfony/debug-bundle": "^5.2",
|
||||||
|
"symfony/maker-bundle": "^1.0",
|
||||||
|
"symfony/phpunit-bridge": "^5.2",
|
||||||
|
"symfony/stopwatch": "^5.2",
|
||||||
|
"symfony/var-dumper": "^5.2",
|
||||||
|
"symfony/web-profiler-bundle": "^5.2"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"optimize-autoloader": true,
|
||||||
|
"preferred-install": {
|
||||||
|
"*": "dist"
|
||||||
|
},
|
||||||
|
"sort-packages": true
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/",
|
||||||
|
"App\\Core\\": "core/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"symfony/polyfill-ctype": "*",
|
||||||
|
"symfony/polyfill-iconv": "*",
|
||||||
|
"symfony/polyfill-php72": "*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"auto-scripts": {
|
||||||
|
"cache:clear": "symfony-cmd",
|
||||||
|
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||||
|
},
|
||||||
|
"post-install-cmd": [
|
||||||
|
"@auto-scripts"
|
||||||
|
],
|
||||||
|
"post-update-cmd": [
|
||||||
|
"@auto-scripts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"symfony/symfony": "*"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"symfony": {
|
||||||
|
"allow-contrib": false,
|
||||||
|
"require": "5.2.*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
config/bundles.php
Normal file
22
config/bundles.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||||
|
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||||
|
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
|
||||||
|
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||||
|
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||||
|
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||||
|
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||||
|
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
|
||||||
|
Knp\Bundle\PaginatorBundle\KnpPaginatorBundle::class => ['all' => true],
|
||||||
|
Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
|
||||||
|
Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true],
|
||||||
|
Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
|
||||||
|
App\Core\Bundle\CoreBundle::class => ['all' => true],
|
||||||
|
App\Bundle\AppBundle::class => ['all' => true],
|
||||||
|
];
|
9
config/packages/app.yaml
Normal file
9
config/packages/app.yaml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
core:
|
||||||
|
site:
|
||||||
|
name: "Murph"
|
||||||
|
logo: "build/images/core/logo.svg"
|
||||||
|
pages:
|
||||||
|
App\Entity\Page\SimplePage:
|
||||||
|
name: 'Page simple'
|
||||||
|
templates:
|
||||||
|
- {name: "Template 1", file: "page/simple/page.html.twig"}
|
3
config/packages/assets.yaml
Normal file
3
config/packages/assets.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
framework:
|
||||||
|
assets:
|
||||||
|
json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
|
19
config/packages/cache.yaml
Normal file
19
config/packages/cache.yaml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
framework:
|
||||||
|
cache:
|
||||||
|
# Unique name of your app: used to compute stable namespaces for cache keys.
|
||||||
|
#prefix_seed: your_vendor_name/app_name
|
||||||
|
|
||||||
|
# The "app" cache stores to the filesystem by default.
|
||||||
|
# The data in this cache should persist between deploys.
|
||||||
|
# Other options include:
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
#app: cache.adapter.redis
|
||||||
|
#default_redis_provider: redis://localhost
|
||||||
|
|
||||||
|
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
|
||||||
|
#app: cache.adapter.apcu
|
||||||
|
|
||||||
|
# Namespaced pools use the above "app" backend by default
|
||||||
|
#pools:
|
||||||
|
#my.dedicated.cache: null
|
4
config/packages/dev/debug.yaml
Normal file
4
config/packages/dev/debug.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
debug:
|
||||||
|
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
|
||||||
|
# See the "server:dump" command to start a new server.
|
||||||
|
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
|
19
config/packages/dev/monolog.yaml
Normal file
19
config/packages/dev/monolog.yaml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
channels: ["!event"]
|
||||||
|
# uncomment to get logging in your browser
|
||||||
|
# you may have to allow bigger header sizes in your Web server configuration
|
||||||
|
#firephp:
|
||||||
|
# type: firephp
|
||||||
|
# level: info
|
||||||
|
#chromephp:
|
||||||
|
# type: chromephp
|
||||||
|
# level: info
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine", "!console"]
|
4
config/packages/dev/swiftmailer.yaml
Normal file
4
config/packages/dev/swiftmailer.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# See https://symfony.com/doc/current/email/dev_environment.html
|
||||||
|
swiftmailer:
|
||||||
|
# send all emails to a specific address
|
||||||
|
#delivery_addresses: ['me@example.com']
|
6
config/packages/dev/web_profiler.yaml
Normal file
6
config/packages/dev/web_profiler.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
web_profiler:
|
||||||
|
toolbar: true
|
||||||
|
intercept_redirects: false
|
||||||
|
|
||||||
|
framework:
|
||||||
|
profiler: { only_exceptions: false }
|
30
config/packages/doctrine.yaml
Normal file
30
config/packages/doctrine.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
doctrine:
|
||||||
|
dbal:
|
||||||
|
url: '%env(resolve:DATABASE_URL)%'
|
||||||
|
|
||||||
|
# IMPORTANT: You MUST configure your server version,
|
||||||
|
# either here or in the DATABASE_URL env var (see .env file)
|
||||||
|
#server_version: '13'
|
||||||
|
orm:
|
||||||
|
auto_generate_proxy_classes: true
|
||||||
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||||
|
auto_mapping: true
|
||||||
|
mappings:
|
||||||
|
App\Core\Entity:
|
||||||
|
is_bundle: false
|
||||||
|
type: annotation
|
||||||
|
dir: '%kernel.project_dir%/core/Entity'
|
||||||
|
prefix: 'App\Core\Entity'
|
||||||
|
alias: App\Core\Entity
|
||||||
|
App\Entity:
|
||||||
|
is_bundle: false
|
||||||
|
type: annotation
|
||||||
|
dir: '%kernel.project_dir%/src/Entity'
|
||||||
|
prefix: 'App\Entity'
|
||||||
|
alias: App\Entity
|
||||||
|
gedmo_tree:
|
||||||
|
type: annotation
|
||||||
|
prefix: Gedmo\Tree\Entity
|
||||||
|
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Tree/Entity"
|
||||||
|
alias: GedmoTree # (optional) it will default to the name set for the mapping
|
||||||
|
is_bundle: false
|
5
config/packages/doctrine_migrations.yaml
Normal file
5
config/packages/doctrine_migrations.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
doctrine_migrations:
|
||||||
|
migrations_paths:
|
||||||
|
# namespace is arbitrary but should be different from App\Migrations
|
||||||
|
# as migrations classes should NOT be autoloaded
|
||||||
|
'DoctrineMigrations': '%kernel.project_dir%/migrations'
|
17
config/packages/framework.yaml
Normal file
17
config/packages/framework.yaml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# see https://symfony.com/doc/current/reference/configuration/framework.html
|
||||||
|
framework:
|
||||||
|
secret: '%env(APP_SECRET)%'
|
||||||
|
#csrf_protection: true
|
||||||
|
#http_method_override: true
|
||||||
|
|
||||||
|
# Enables session support. Note that the session will ONLY be started if you read or write from it.
|
||||||
|
# Remove or comment this section to explicitly disable session support.
|
||||||
|
session:
|
||||||
|
handler_id: null
|
||||||
|
cookie_secure: auto
|
||||||
|
cookie_samesite: lax
|
||||||
|
|
||||||
|
#esi: true
|
||||||
|
#fragments: true
|
||||||
|
php_errors:
|
||||||
|
log: true
|
3
config/packages/mailer.yaml
Normal file
3
config/packages/mailer.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
framework:
|
||||||
|
mailer:
|
||||||
|
dsn: '%env(MAILER_DSN)%'
|
16
config/packages/notifier.yaml
Normal file
16
config/packages/notifier.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
framework:
|
||||||
|
notifier:
|
||||||
|
#chatter_transports:
|
||||||
|
# slack: '%env(SLACK_DSN)%'
|
||||||
|
# telegram: '%env(TELEGRAM_DSN)%'
|
||||||
|
#texter_transports:
|
||||||
|
# twilio: '%env(TWILIO_DSN)%'
|
||||||
|
# nexmo: '%env(NEXMO_DSN)%'
|
||||||
|
channel_policy:
|
||||||
|
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
|
||||||
|
urgent: ['email']
|
||||||
|
high: ['email']
|
||||||
|
medium: ['email']
|
||||||
|
low: ['email']
|
||||||
|
admin_recipients:
|
||||||
|
- { email: admin@example.com }
|
8
config/packages/prod/deprecations.yaml
Normal file
8
config/packages/prod/deprecations.yaml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists
|
||||||
|
#monolog:
|
||||||
|
# channels: [deprecation]
|
||||||
|
# handlers:
|
||||||
|
# deprecation:
|
||||||
|
# type: stream
|
||||||
|
# channels: [deprecation]
|
||||||
|
# path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log"
|
20
config/packages/prod/doctrine.yaml
Normal file
20
config/packages/prod/doctrine.yaml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
doctrine:
|
||||||
|
orm:
|
||||||
|
auto_generate_proxy_classes: false
|
||||||
|
metadata_cache_driver:
|
||||||
|
type: pool
|
||||||
|
pool: doctrine.system_cache_pool
|
||||||
|
query_cache_driver:
|
||||||
|
type: pool
|
||||||
|
pool: doctrine.system_cache_pool
|
||||||
|
result_cache_driver:
|
||||||
|
type: pool
|
||||||
|
pool: doctrine.result_cache_pool
|
||||||
|
|
||||||
|
framework:
|
||||||
|
cache:
|
||||||
|
pools:
|
||||||
|
doctrine.result_cache_pool:
|
||||||
|
adapter: cache.app
|
||||||
|
doctrine.system_cache_pool:
|
||||||
|
adapter: cache.system
|
16
config/packages/prod/monolog.yaml
Normal file
16
config/packages/prod/monolog.yaml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
|
||||||
|
nested:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
||||||
|
console:
|
||||||
|
type: console
|
||||||
|
process_psr_3_messages: false
|
||||||
|
channels: ["!event", "!doctrine"]
|
3
config/packages/prod/routing.yaml
Normal file
3
config/packages/prod/routing.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
framework:
|
||||||
|
router:
|
||||||
|
strict_requirements: null
|
4
config/packages/prod/webpack_encore.yaml
Normal file
4
config/packages/prod/webpack_encore.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#webpack_encore:
|
||||||
|
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||||
|
# Available in version 1.2
|
||||||
|
#cache: true
|
7
config/packages/routing.yaml
Normal file
7
config/packages/routing.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
framework:
|
||||||
|
router:
|
||||||
|
utf8: true
|
||||||
|
|
||||||
|
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
|
||||||
|
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
|
||||||
|
#default_uri: http://localhost
|
15
config/packages/scheb_2fa.yaml
Normal file
15
config/packages/scheb_2fa.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# See the configuration reference at https://github.com/scheb/2fa/blob/master/doc/configuration.md
|
||||||
|
scheb_two_factor:
|
||||||
|
google:
|
||||||
|
enabled: true
|
||||||
|
issuer: "Muprh"
|
||||||
|
server_name:
|
||||||
|
digits: 6
|
||||||
|
window: 1
|
||||||
|
template: auth/2fa.html.twig
|
||||||
|
security_tokens:
|
||||||
|
- Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
|
||||||
|
# If you're using guard-based authentication, you have to use this one:
|
||||||
|
- Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken
|
||||||
|
# If you're using authenticator-based security (introduced in Symfony 5.1), you have to use this one:
|
||||||
|
# - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken
|
50
config/packages/security.yaml
Normal file
50
config/packages/security.yaml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
security:
|
||||||
|
encoders:
|
||||||
|
App\Entity\User:
|
||||||
|
algorithm: auto
|
||||||
|
|
||||||
|
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
|
||||||
|
providers:
|
||||||
|
# used to reload user from session & other features (e.g. switch_user)
|
||||||
|
app_user_provider:
|
||||||
|
entity:
|
||||||
|
class: App\Entity\User
|
||||||
|
property: email
|
||||||
|
|
||||||
|
role_hierarchy:
|
||||||
|
ROLE_WRITER: ROLE_USER
|
||||||
|
ROLE_ADMIN: ROLE_WRITER
|
||||||
|
|
||||||
|
firewalls:
|
||||||
|
dev:
|
||||||
|
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||||
|
security: false
|
||||||
|
main:
|
||||||
|
anonymous: ~
|
||||||
|
two_factor:
|
||||||
|
auth_form_path: 2fa_login # The route name you have used in the routes.yaml
|
||||||
|
check_path: 2fa_login_check # The route name you have used in the routes.yaml
|
||||||
|
guard:
|
||||||
|
authenticators:
|
||||||
|
- App\Core\Authenticator\LoginFormAuthenticator
|
||||||
|
form_login:
|
||||||
|
login_path: auth_login
|
||||||
|
check_path: auth_login
|
||||||
|
csrf_token_generator: security.csrf.token_manager
|
||||||
|
logout:
|
||||||
|
path: auth_logout
|
||||||
|
target: /
|
||||||
|
remember_me:
|
||||||
|
secret: '%kernel.secret%'
|
||||||
|
lifetime: 604800
|
||||||
|
path: /
|
||||||
|
|
||||||
|
# Easy way to control access for large sections of your site
|
||||||
|
# Note: Only the *first* access control that matches will be used
|
||||||
|
access_control:
|
||||||
|
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
|
- { path: ^/resetting, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
||||||
|
- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
|
||||||
|
- { path: ^/admin/user, roles: ROLE_ADMIN }
|
||||||
|
- { path: ^/admin, roles: ROLE_USER }
|
||||||
|
- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY }
|
3
config/packages/sensio_framework_extra.yaml
Normal file
3
config/packages/sensio_framework_extra.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
sensio_framework_extra:
|
||||||
|
router:
|
||||||
|
annotations: false
|
4
config/packages/stof_doctrine_extensions.yaml
Normal file
4
config/packages/stof_doctrine_extensions.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
|
||||||
|
# See the official DoctrineExtensions documentation for more details: https://github.com/Atlantic18/DoctrineExtensions/tree/master/doc/
|
||||||
|
stof_doctrine_extensions:
|
||||||
|
default_locale: en_US
|
4
config/packages/swiftmailer.yaml
Normal file
4
config/packages/swiftmailer.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
swiftmailer:
|
||||||
|
url: '%env(MAILER_URL)%'
|
||||||
|
spool: { type: 'memory' }
|
||||||
|
sender_address: system@localhost
|
4
config/packages/test/framework.yaml
Normal file
4
config/packages/test/framework.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
framework:
|
||||||
|
test: true
|
||||||
|
session:
|
||||||
|
storage_id: session.storage.mock_file
|
12
config/packages/test/monolog.yaml
Normal file
12
config/packages/test/monolog.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
monolog:
|
||||||
|
handlers:
|
||||||
|
main:
|
||||||
|
type: fingers_crossed
|
||||||
|
action_level: error
|
||||||
|
handler: nested
|
||||||
|
excluded_http_codes: [404, 405]
|
||||||
|
channels: ["!event"]
|
||||||
|
nested:
|
||||||
|
type: stream
|
||||||
|
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||||
|
level: debug
|
2
config/packages/test/swiftmailer.yaml
Normal file
2
config/packages/test/swiftmailer.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
swiftmailer:
|
||||||
|
disable_delivery: true
|
2
config/packages/test/twig.yaml
Normal file
2
config/packages/test/twig.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
twig:
|
||||||
|
strict_variables: true
|
3
config/packages/test/validator.yaml
Normal file
3
config/packages/test/validator.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
not_compromised_password: false
|
6
config/packages/test/web_profiler.yaml
Normal file
6
config/packages/test/web_profiler.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
web_profiler:
|
||||||
|
toolbar: false
|
||||||
|
intercept_redirects: false
|
||||||
|
|
||||||
|
framework:
|
||||||
|
profiler: { collect: false }
|
2
config/packages/test/webpack_encore.yaml
Normal file
2
config/packages/test/webpack_encore.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#webpack_encore:
|
||||||
|
# strict_mode: false
|
6
config/packages/translation.yaml
Normal file
6
config/packages/translation.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
framework:
|
||||||
|
default_locale: en
|
||||||
|
translator:
|
||||||
|
default_path: '%kernel.project_dir%/translations'
|
||||||
|
fallbacks:
|
||||||
|
- en
|
6
config/packages/twig.yaml
Normal file
6
config/packages/twig.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
twig:
|
||||||
|
default_path: '%kernel.project_dir%/templates'
|
||||||
|
form_themes: ['@Core/form/bootstrap_4_form_theme.html.twig']
|
||||||
|
paths:
|
||||||
|
'%kernel.project_dir%/templates/core/': Core
|
||||||
|
'%kernel.project_dir%/core/Resources/views/': Core
|
8
config/packages/validator.yaml
Normal file
8
config/packages/validator.yaml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
framework:
|
||||||
|
validation:
|
||||||
|
email_validation_mode: html5
|
||||||
|
|
||||||
|
# Enables validator auto-mapping support.
|
||||||
|
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||||
|
#auto_mapping:
|
||||||
|
# App\Entity\: []
|
30
config/packages/webpack_encore.yaml
Normal file
30
config/packages/webpack_encore.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
webpack_encore:
|
||||||
|
# The path where Encore is building the assets - i.e. Encore.setOutputPath()
|
||||||
|
output_path: '%kernel.project_dir%/public/build'
|
||||||
|
# If multiple builds are defined (as shown below), you can disable the default build:
|
||||||
|
# output_path: false
|
||||||
|
|
||||||
|
# Set attributes that will be rendered on all script and link tags
|
||||||
|
script_attributes:
|
||||||
|
defer: true
|
||||||
|
# link_attributes:
|
||||||
|
|
||||||
|
# If using Encore.enableIntegrityHashes() and need the crossorigin attribute (default: false, or use 'anonymous' or 'use-credentials')
|
||||||
|
# crossorigin: 'anonymous'
|
||||||
|
|
||||||
|
# Preload all rendered script and link tags automatically via the HTTP/2 Link header
|
||||||
|
# preload: true
|
||||||
|
|
||||||
|
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
|
||||||
|
# strict_mode: false
|
||||||
|
|
||||||
|
# If you have multiple builds:
|
||||||
|
# builds:
|
||||||
|
# pass "frontend" as the 3rg arg to the Twig functions
|
||||||
|
# {{ encore_entry_script_tags('entry1', null, 'frontend') }}
|
||||||
|
|
||||||
|
# frontend: '%kernel.project_dir%/public/frontend/build'
|
||||||
|
|
||||||
|
# Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
|
||||||
|
# Put in config/packages/prod/webpack_encore.yaml
|
||||||
|
# cache: true
|
5
config/preload.php
Normal file
5
config/preload.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||||
|
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||||
|
}
|
15
config/routes.yaml
Normal file
15
config/routes.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#index:
|
||||||
|
# path: /
|
||||||
|
# controller: App\Controller\DefaultController::index
|
||||||
|
|
||||||
|
site_route:
|
||||||
|
resource: 'site.route_loader::loadRoutes'
|
||||||
|
type: extra
|
||||||
|
|
||||||
|
2fa_login:
|
||||||
|
path: /2fa
|
||||||
|
defaults:
|
||||||
|
_controller: "scheb_two_factor.form_controller:form"
|
||||||
|
|
||||||
|
2fa_login_check:
|
||||||
|
path: /2fa_check
|
11
config/routes/annotations.yaml
Normal file
11
config/routes/annotations.yaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
controllers:
|
||||||
|
resource: ../../src/Controller/
|
||||||
|
type: annotation
|
||||||
|
|
||||||
|
core_controllers:
|
||||||
|
resource: ../../core/Controller/
|
||||||
|
type: annotation
|
||||||
|
|
||||||
|
kernel:
|
||||||
|
resource: ../../src/Kernel.php
|
||||||
|
type: annotation
|
3
config/routes/dev/framework.yaml
Normal file
3
config/routes/dev/framework.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
_errors:
|
||||||
|
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
|
||||||
|
prefix: /_error
|
7
config/routes/dev/web_profiler.yaml
Normal file
7
config/routes/dev/web_profiler.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
web_profiler_wdt:
|
||||||
|
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||||
|
prefix: /_wdt
|
||||||
|
|
||||||
|
web_profiler_profiler:
|
||||||
|
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||||
|
prefix: /_profiler
|
7
config/routes/scheb_2fa.yaml
Normal file
7
config/routes/scheb_2fa.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
2fa_login:
|
||||||
|
path: /2fa
|
||||||
|
defaults:
|
||||||
|
_controller: "scheb_two_factor.form_controller:form"
|
||||||
|
|
||||||
|
2fa_login_check:
|
||||||
|
path: /2fa_check
|
54
config/services.yaml
Normal file
54
config/services.yaml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# This file is the entry point to configure your own services.
|
||||||
|
# Files in the packages/ subdirectory configure your dependencies.
|
||||||
|
|
||||||
|
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||||
|
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
|
||||||
|
parameters:
|
||||||
|
|
||||||
|
services:
|
||||||
|
# default configuration for services in *this* file
|
||||||
|
_defaults:
|
||||||
|
autowire: true # Automatically injects dependencies in your services.
|
||||||
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||||
|
|
||||||
|
# makes classes in src/ available to be used as services
|
||||||
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
|
App\Core\:
|
||||||
|
resource: '../core/'
|
||||||
|
exclude:
|
||||||
|
- '../core/DependencyInjection/'
|
||||||
|
- '../core/Entity/'
|
||||||
|
App\:
|
||||||
|
resource: '../src/'
|
||||||
|
exclude:
|
||||||
|
- '../src/DependencyInjection/'
|
||||||
|
- '../src/Entity/'
|
||||||
|
- '../src/Kernel.php'
|
||||||
|
- '../src/Tests/'
|
||||||
|
|
||||||
|
# controllers are imported separately to make sure services can be injected
|
||||||
|
# as action arguments even if you don't extend any base controller class
|
||||||
|
App\Core\Controller\:
|
||||||
|
resource: '../core/Controller/'
|
||||||
|
tags: ['controller.service_arguments']
|
||||||
|
|
||||||
|
App\Controller\:
|
||||||
|
resource: '../src/Controller/'
|
||||||
|
tags: ['controller.service_arguments']
|
||||||
|
|
||||||
|
site.route_loader:
|
||||||
|
class: App\Core\Router\SiteRouteLoader
|
||||||
|
tags: [routing.loader]
|
||||||
|
|
||||||
|
gedmo.listener.tree:
|
||||||
|
class: Gedmo\Tree\TreeListener
|
||||||
|
tags:
|
||||||
|
- { name: doctrine.event_subscriber, connection: default }
|
||||||
|
calls:
|
||||||
|
- [ setAnnotationReader, [ "@annotation_reader" ] ]
|
||||||
|
|
||||||
|
App\UrlGenerator\FooUrlGenerator:
|
||||||
|
public: true
|
||||||
|
|
||||||
|
# add more service definitions when explicit configuration is needed
|
||||||
|
# please note that last definitions always *replace* previous ones
|
20
core/Annotation/UrlGenerator.php
Normal file
20
core/Annotation/UrlGenerator.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Annotation;
|
||||||
|
|
||||||
|
use Doctrine\Common\Annotations\Annotation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class UrlGenerator.
|
||||||
|
*
|
||||||
|
* @author Simon Vieille <simon@deblan.fr>
|
||||||
|
* @Annotation
|
||||||
|
*/
|
||||||
|
class UrlGenerator
|
||||||
|
{
|
||||||
|
public string $service;
|
||||||
|
|
||||||
|
public string $method;
|
||||||
|
|
||||||
|
public array $options = [];
|
||||||
|
}
|
96
core/Authenticator/LoginFormAuthenticator.php
Normal file
96
core/Authenticator/LoginFormAuthenticator.php
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Authenticator;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||||
|
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
|
||||||
|
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
use Symfony\Component\Security\Csrf\CsrfToken;
|
||||||
|
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
|
||||||
|
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
|
||||||
|
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||||
|
|
||||||
|
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
|
||||||
|
{
|
||||||
|
use TargetPathTrait;
|
||||||
|
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private UrlGeneratorInterface $urlGenerator;
|
||||||
|
|
||||||
|
private CsrfTokenManagerInterface $csrfTokenManager;
|
||||||
|
|
||||||
|
private UserPasswordEncoderInterface $passwordEncoder;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
|
||||||
|
{
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->urlGenerator = $urlGenerator;
|
||||||
|
$this->csrfTokenManager = $csrfTokenManager;
|
||||||
|
$this->passwordEncoder = $passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports(Request $request)
|
||||||
|
{
|
||||||
|
return 'auth_login' === $request->attributes->get('_route') && $request->isMethod('POST');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCredentials(Request $request)
|
||||||
|
{
|
||||||
|
$credentials = [
|
||||||
|
'email' => $request->request->get('_username'),
|
||||||
|
'password' => $request->request->get('_password'),
|
||||||
|
'csrf_token' => $request->request->get('_csrf_token'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$request->getSession()->set(Security::LAST_USERNAME, $credentials['email']);
|
||||||
|
|
||||||
|
return $credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser($credentials, UserProviderInterface $userProvider)
|
||||||
|
{
|
||||||
|
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
|
||||||
|
|
||||||
|
if (!$this->csrfTokenManager->isTokenValid($token)) {
|
||||||
|
throw new InvalidCsrfTokenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
// fail authentication with a custom error
|
||||||
|
throw new CustomUserMessageAuthenticationException('Email could not be found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkCredentials($credentials, UserInterface $user)
|
||||||
|
{
|
||||||
|
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
|
||||||
|
{
|
||||||
|
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
|
||||||
|
return new RedirectResponse($targetPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RedirectResponse($this->urlGenerator->generate('admin_dashboard_index'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLoginUrl()
|
||||||
|
{
|
||||||
|
return $this->urlGenerator->generate('auth_login');
|
||||||
|
}
|
||||||
|
}
|
23
core/Bundle/CoreBundle.php
Normal file
23
core/Bundle/CoreBundle.php
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
class CoreBundle extends Bundle
|
||||||
|
{
|
||||||
|
public function getContainerExtension()
|
||||||
|
{
|
||||||
|
return new CoreExtension();
|
||||||
|
}
|
||||||
|
}
|
0
core/Controller/.gitignore
vendored
Normal file
0
core/Controller/.gitignore
vendored
Normal file
150
core/Controller/Account/AccountAdminController.php
Normal file
150
core/Controller/Account/AccountAdminController.php
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Controller\Account;
|
||||||
|
|
||||||
|
use App\Core\Controller\Admin\AdminController;
|
||||||
|
use App\Core\Manager\EntityManager;
|
||||||
|
use App\Core\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', 'Le code n\'est pas valide.');
|
||||||
|
} else {
|
||||||
|
$this->addFlash('success', 'Double authentification activée.');
|
||||||
|
|
||||||
|
$entityManager->update($account);
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_account');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$enable && $account->isTotpAuthenticationEnabled()) {
|
||||||
|
$account->setTotpSecret(null);
|
||||||
|
|
||||||
|
$entityManager->update($account);
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Double authentification désactivée.');
|
||||||
|
|
||||||
|
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', 'Le formulaire n\'est pas valide.');
|
||||||
|
|
||||||
|
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', 'Mot de passe modifié !');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_account');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addFlash('error', 'Le formulaire n\'est pas valide.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_account');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function getSection(): string
|
||||||
|
{
|
||||||
|
return 'account';
|
||||||
|
}
|
||||||
|
}
|
31
core/Controller/Admin/AdminController.php
Normal file
31
core/Controller/Admin/AdminController.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Controller\Admin;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
|
|
||||||
|
abstract class AdminController extends AbstractController
|
||||||
|
{
|
||||||
|
protected array $coreParameters;
|
||||||
|
|
||||||
|
public function __construct(ParameterBagInterface $parameters)
|
||||||
|
{
|
||||||
|
$this->coreParameters = $parameters->get('core');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@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;
|
||||||
|
}
|
155
core/Controller/Auth/AuthController.php
Normal file
155
core/Controller/Auth/AuthController.php
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Controller\Auth;
|
||||||
|
|
||||||
|
use App\Core\Event\Account\PasswordRequestEvent;
|
||||||
|
use App\Core\Manager\EntityManager;
|
||||||
|
use App\Repository\UserRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\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;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
|
|
||||||
|
class AuthController extends AbstractController
|
||||||
|
{
|
||||||
|
protected array $coreParameters;
|
||||||
|
|
||||||
|
public function __construct(ParameterBagInterface $parameters)
|
||||||
|
{
|
||||||
|
$this->coreParameters = $parameters->get('core');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/login", name="auth_login")
|
||||||
|
*/
|
||||||
|
public function login(AuthenticationUtils $authenticationUtils): Response
|
||||||
|
{
|
||||||
|
if ($this->getUser()) {
|
||||||
|
return $this->redirectToRoute('admin_dashboard_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = $authenticationUtils->getLastAuthenticationError();
|
||||||
|
$lastUsername = $authenticationUtils->getLastUsername();
|
||||||
|
|
||||||
|
return $this->render('@Core/auth/login.html.twig', [
|
||||||
|
'last_username' => $lastUsername,
|
||||||
|
'error' => $error,
|
||||||
|
'site_name' => $this->coreParameters['site']['name'],
|
||||||
|
'site_logo' => $this->coreParameters['site']['logo'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/resetting/request", name="auth_resetting_request")
|
||||||
|
*/
|
||||||
|
public function requestResetting(Request $request, UserRepository $repository, EventDispatcherInterface $eventDispatcher): Response
|
||||||
|
{
|
||||||
|
if ($this->getUser()) {
|
||||||
|
return $this->redirectToRoute('admin_dashboard_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$csrfToken = $request->request->get('_csrf_token');
|
||||||
|
|
||||||
|
if (!$this->isCsrfTokenValid('resetting_request', $csrfToken)) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = trim((string) $request->request->get('username'));
|
||||||
|
|
||||||
|
if (!$username) {
|
||||||
|
throw $this->createAccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$account = $repository->findOneByEmail($username);
|
||||||
|
|
||||||
|
if ($account) {
|
||||||
|
$requestedAt = $account->getPasswordRequestedAt();
|
||||||
|
|
||||||
|
if (null === $requestedAt || $requestedAt->getTimestamp() < (time() - 3600 / 2)) {
|
||||||
|
$eventDispatcher->dispatch(new PasswordRequestEvent($account), PasswordRequestEvent::EVENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@Core/auth/resetting_request.html.twig', [
|
||||||
|
'email_sent' => $request->isMethod('POST'),
|
||||||
|
'site_name' => $this->coreParameters['site']['name'],
|
||||||
|
'site_logo' => $this->coreParameters['site']['logo'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/resetting/update/{token}", name="auth_resetting_update")
|
||||||
|
*/
|
||||||
|
public function requestUpdate(
|
||||||
|
string $token,
|
||||||
|
Request $request,
|
||||||
|
UserRepository $repository,
|
||||||
|
TokenGeneratorInterface $tokenGenerator,
|
||||||
|
UserPasswordEncoderInterface $encoder,
|
||||||
|
EntityManager $entityManager
|
||||||
|
): Response {
|
||||||
|
if ($this->getUser()) {
|
||||||
|
return $this->redirectToRoute('admin_dashboard_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
$account = $repository->findOneByConfirmationToken($token);
|
||||||
|
$passwordUpdated = false;
|
||||||
|
$expired = true;
|
||||||
|
|
||||||
|
if ($account) {
|
||||||
|
$requestedAt = $account->getPasswordRequestedAt();
|
||||||
|
$expired = (null === $requestedAt || ($requestedAt->getTimestamp() < (time() - 3600 * 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->isMethod('POST') && !$expired) {
|
||||||
|
$csrfToken = $request->request->get('_csrf_token');
|
||||||
|
|
||||||
|
if ($this->isCsrfTokenValid('resetting_update', $csrfToken)) {
|
||||||
|
$password = $request->request->get('password');
|
||||||
|
$password2 = $request->request->get('password2');
|
||||||
|
|
||||||
|
$zxcvbn = new Zxcvbn();
|
||||||
|
$strength = $zxcvbn->passwordStrength($password, []);
|
||||||
|
|
||||||
|
if (4 === $strength['score'] && $password === $password2) {
|
||||||
|
$account
|
||||||
|
->setPassword($encoder->encodePassword(
|
||||||
|
$account,
|
||||||
|
$password
|
||||||
|
))
|
||||||
|
->setConfirmationToken($tokenGenerator->generateToken())
|
||||||
|
->setPasswordRequestedAt(new \DateTime('now'))
|
||||||
|
;
|
||||||
|
|
||||||
|
$entityManager->update($account);
|
||||||
|
|
||||||
|
$passwordUpdated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@Core/auth/resetting_update.html.twig', [
|
||||||
|
'password_updated' => $passwordUpdated,
|
||||||
|
'token' => $token,
|
||||||
|
'expired' => $expired,
|
||||||
|
'site_name' => $this->coreParameters['site']['name'],
|
||||||
|
'site_logo' => $this->coreParameters['site']['logo'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/logout", name="auth_logout")
|
||||||
|
*/
|
||||||
|
public function logout()
|
||||||
|
{
|
||||||
|
throw new \Exception('This method can be blank - it will be intercepted by the logout key on your firewall');
|
||||||
|
}
|
||||||
|
}
|
27
core/Controller/Dashboard/DashboardAdminController.php
Normal file
27
core/Controller/Dashboard/DashboardAdminController.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Controller\Dashboard;
|
||||||
|
|
||||||
|
use App\Core\Controller\Admin\AdminController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/admin")
|
||||||
|
*/
|
||||||
|
class DashboardAdminController extends AdminController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/", name="admin_dashboard_index")
|
||||||
|
*/
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
return $this->render('@Core/dashboard/index.html.twig', [
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getSection(): string
|
||||||
|
{
|
||||||
|
return 'dashboard';
|
||||||
|
}
|
||||||
|
}
|
82
core/Controller/Site/MenuAdminController.php
Normal file
82
core/Controller/Site/MenuAdminController.php
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Controller\Site;
|
||||||
|
|
||||||
|
use App\Core\Controller\Admin\AdminController;
|
||||||
|
use App\Core\Entity\Site\Menu as Entity;
|
||||||
|
use App\Core\Entity\Site\Navigation;
|
||||||
|
use App\Core\Factory\Site\MenuFactory as EntityFactory;
|
||||||
|
use App\Core\Form\Site\MenuType as EntityType;
|
||||||
|
use App\Core\Manager\EntityManager;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/admin/site/menu")
|
||||||
|
*/
|
||||||
|
class MenuAdminController extends AdminController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/new/{navigation}", name="admin_site_menu_new", methods={"POST"})
|
||||||
|
*/
|
||||||
|
public function new(Navigation $navigation, EntityFactory $factory, EntityManager $entityManager, Request $request): Response
|
||||||
|
{
|
||||||
|
$entity = $factory->create($navigation);
|
||||||
|
$form = $this->createForm(EntityType::class, $entity);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$entityManager->create($entity);
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
} else {
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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', 'Donnée enregistrée.');
|
||||||
|
} else {
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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', 'Données supprimée..');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||||
|
'navigation' => $entity->getNavigation()->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSection(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
116
core/Controller/Site/NavigationAdminController.php
Normal file
116
core/Controller/Site/NavigationAdminController.php
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Controller\Site;
|
||||||
|
|
||||||
|
use App\Core\Controller\Admin\AdminController;
|
||||||
|
use App\Core\Entity\Site\Navigation as Entity;
|
||||||
|
use App\Core\Factory\Site\NavigationFactory as EntityFactory;
|
||||||
|
use App\Core\Form\Site\NavigationType as EntityType;
|
||||||
|
use App\Core\Manager\EntityManager;
|
||||||
|
use App\Core\Repository\Site\NavigationRepositoryQuery as RepositoryQuery;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/admin/site/navigation")
|
||||||
|
*/
|
||||||
|
class NavigationAdminController extends AdminController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/{page}", name="admin_site_navigation_index", requirements={"page": "\d+"})
|
||||||
|
*/
|
||||||
|
public function index(int $page = 1, RepositoryQuery $query, Request $request): Response
|
||||||
|
{
|
||||||
|
$pager = $query->paginate($page);
|
||||||
|
|
||||||
|
return $this->render('@Core/site/navigation_admin/index.html.twig', [
|
||||||
|
'pager' => $pager,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/new", name="admin_site_navigation_new")
|
||||||
|
*/
|
||||||
|
public function new(EntityFactory $factory, EntityManager $entityManager, Request $request): Response
|
||||||
|
{
|
||||||
|
$entity = $factory->create();
|
||||||
|
$form = $this->createForm(EntityType::class, $entity);
|
||||||
|
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$entityManager->create($entity);
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_navigation_edit', [
|
||||||
|
'entity' => $entity->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@Core/site/navigation_admin/new.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'entity' => $entity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/edit/{entity}", name="admin_site_navigation_edit")
|
||||||
|
*/
|
||||||
|
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(EntityType::class, $entity);
|
||||||
|
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$entityManager->update($entity);
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_navigation_edit', [
|
||||||
|
'entity' => $entity->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@Core/site/navigation_admin/edit.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'entity' => $entity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/show/{entity}", name="admin_site_navigation_show")
|
||||||
|
*/
|
||||||
|
public function show(Entity $entity): Response
|
||||||
|
{
|
||||||
|
return $this->render('@Core/site/navigation_admin/show.html.twig', [
|
||||||
|
'entity' => $entity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/delete/{entity}", name="admin_site_navigation_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', 'Données supprimée..');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_navigation_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSection(): string
|
||||||
|
{
|
||||||
|
return 'site_navigation';
|
||||||
|
}
|
||||||
|
}
|
283
core/Controller/Site/NodeAdminController.php
Normal file
283
core/Controller/Site/NodeAdminController.php
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
<?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\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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/admin/site/node")
|
||||||
|
*/
|
||||||
|
class NodeAdminController extends AdminController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/new/{node}", name="admin_site_node_new")
|
||||||
|
*/
|
||||||
|
public function new(
|
||||||
|
Node $node,
|
||||||
|
EntityFactory $factory,
|
||||||
|
PageFactory $pageFactory,
|
||||||
|
EntityManager $entityManager,
|
||||||
|
NodeRepository $nodeRepository,
|
||||||
|
PageLocator $pageLocator,
|
||||||
|
Request $request
|
||||||
|
): Response {
|
||||||
|
$entity = $factory->create($node->getMenu());
|
||||||
|
$form = $this->createForm(EntityType::class, $entity, [
|
||||||
|
'pages' => $pageLocator->getPages(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
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->update($entity);
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||||
|
'navigation' => $node->getMenu()->getNavigation()->getId(),
|
||||||
|
'data-modal' => $this->generateUrl('admin_site_node_edit', ['entity' => $entity->getId()]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||||
|
'navigation' => $node->getMenu()->getNavigation()->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,
|
||||||
|
string $tab = 'content',
|
||||||
|
EntityManager $entityManager,
|
||||||
|
PageFactory $pageFactory,
|
||||||
|
PageLocator $pageLocator,
|
||||||
|
Request $request
|
||||||
|
): Response {
|
||||||
|
$form = $this->createForm(EntityType::class, $entity, [
|
||||||
|
'pages' => $pageLocator->getPages(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
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', 'Donnée enregistrée.');
|
||||||
|
} else {
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||||
|
'navigation' => $entity->getMenu()->getNavigation()->getId(),
|
||||||
|
'data-modal' => $this->generateUrl('admin_site_node_edit', ['entity' => $entity->getId()]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@Core/site/node_admin/edit.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'entity' => $entity,
|
||||||
|
'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', 'Donnée enregistrée.');
|
||||||
|
} else {
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||||
|
'navigation' => $entity->getMenu()->getNavigation()->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', 'Donnée enregistrée.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_tree_navigation', [
|
||||||
|
'navigation' => $entity->getMenu()->getNavigation()->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(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSection(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
} elseif ('existing' === $pageAction) {
|
||||||
|
if ($pageEntity) {
|
||||||
|
$entity->setPage($pageEntity);
|
||||||
|
} else {
|
||||||
|
$this->addFlash('info', 'Aucun changement de page effectué.');
|
||||||
|
}
|
||||||
|
} elseif ('none' === $pageAction) {
|
||||||
|
$entity->setPage(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
core/Controller/Site/PageAdminController.php
Normal file
109
core/Controller/Site/PageAdminController.php
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Controller\Site;
|
||||||
|
|
||||||
|
use App\Core\Controller\Admin\AdminController;
|
||||||
|
use App\Core\Entity\Site\Page\Page as Entity;
|
||||||
|
use App\Core\Factory\Site\Page\PageFactory as EntityFactory;
|
||||||
|
use App\Core\Form\Site\Page\PageType as EntityType;
|
||||||
|
use App\Core\Manager\EntityManager;
|
||||||
|
use App\Core\Page\FooPage;
|
||||||
|
use App\Core\Page\SimplePage;
|
||||||
|
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\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/admin/site/page")
|
||||||
|
*/
|
||||||
|
class PageAdminController extends AdminController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/{page}", name="admin_site_page_index", requirements={"page": "\d+"})
|
||||||
|
*/
|
||||||
|
public function index(int $page = 1, RepositoryQuery $query, Request $request): Response
|
||||||
|
{
|
||||||
|
$pager = $query->paginate($page);
|
||||||
|
|
||||||
|
return $this->render('@Core/site/page_admin/index.html.twig', [
|
||||||
|
'pager' => $pager,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/new", name="admin_site_page_new")
|
||||||
|
*/
|
||||||
|
public function new(EntityFactory $factory, EntityManager $entityManager): Response
|
||||||
|
{
|
||||||
|
// $entity = $factory->create(FooPage::class);
|
||||||
|
$entity = $factory->create(SimplePage::class);
|
||||||
|
$entity->setName('Page de test '.mt_rand());
|
||||||
|
|
||||||
|
$entityManager->create($entity);
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_page_edit', [
|
||||||
|
'entity' => $entity->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/edit/{entity}", name="admin_site_page_edit")
|
||||||
|
*/
|
||||||
|
public function edit(
|
||||||
|
int $entity,
|
||||||
|
EntityFactory $factory,
|
||||||
|
EntityManager $entityManager,
|
||||||
|
RepositoryQuery $repositoryQuery,
|
||||||
|
PageLocator $pageLocator,
|
||||||
|
Request $request
|
||||||
|
): Response {
|
||||||
|
$entity = $repositoryQuery->filterById($entity)->findOne();
|
||||||
|
$form = $this->createForm(EntityType::class, $entity, [
|
||||||
|
'pageConfiguration' => $pageLocator->getPage(get_class($entity)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$entityManager->update($entity);
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_page_edit', [
|
||||||
|
'entity' => $entity->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@Core/site/page_admin/edit.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'entity' => $entity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/delete/{entity}", name="admin_site_page_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', 'Données supprimée..');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_site_page_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSection(): string
|
||||||
|
{
|
||||||
|
return 'site_page';
|
||||||
|
}
|
||||||
|
}
|
48
core/Controller/Site/PageController.php
Normal file
48
core/Controller/Site/PageController.php
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<?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\Request;
|
||||||
|
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(Request $request, SiteRequest $siteRequest): Response
|
||||||
|
{
|
||||||
|
if (!$siteRequest->getPage()) {
|
||||||
|
throw $this->createNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->defaultRender($siteRequest->getPage()->getTemplate());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defaultRender(string $view, array $parameters = [], Response $response = null): Response
|
||||||
|
{
|
||||||
|
$parameters = array_merge($this->getDefaultRenderParameters(), $parameters);
|
||||||
|
|
||||||
|
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(),
|
||||||
|
'_store' => $this->siteStore,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
40
core/Controller/Site/SitemapController.php
Normal file
40
core/Controller/Site/SitemapController.php
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Controller\Site;
|
||||||
|
|
||||||
|
use App\Core\Repository\Site\NavigationRepositoryQuery;
|
||||||
|
use App\Core\Sitemap\SitemapBuilder;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
class SitemapController extends AbstractController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/sitemap.xml", name="sitemap")
|
||||||
|
*/
|
||||||
|
public function sitemap(Request $request, NavigationRepositoryQuery $navigationRepositoryQuery, SitemapBuilder $builder): Response
|
||||||
|
{
|
||||||
|
$navigations = $navigationRepositoryQuery
|
||||||
|
->whereDomain($request->getHost())
|
||||||
|
->find()
|
||||||
|
;
|
||||||
|
|
||||||
|
$items = [];
|
||||||
|
|
||||||
|
foreach ($navigations as $navigation) {
|
||||||
|
$items = array_merge(
|
||||||
|
$items,
|
||||||
|
$builder->build($navigation)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = new Response();
|
||||||
|
$response->headers->set('Content-Type', 'text/xml');
|
||||||
|
|
||||||
|
return $this->render('@Core/site/sitemap/sitemap.xml.twig', [
|
||||||
|
'items' => $items,
|
||||||
|
], $response);
|
||||||
|
}
|
||||||
|
}
|
66
core/Controller/Site/TreeAdminController.php
Normal file
66
core/Controller/Site/TreeAdminController.php
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<?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\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/admin/site/tree")
|
||||||
|
*/
|
||||||
|
class TreeAdminController extends AdminController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/", name="admin_site_tree_index")
|
||||||
|
*/
|
||||||
|
public function index(NavigationRepositoryQuery $navigationQuery): Response
|
||||||
|
{
|
||||||
|
$navigation = $navigationQuery->create()->findOne();
|
||||||
|
|
||||||
|
if (null === $navigation) {
|
||||||
|
$this->addFlash('warning', 'Vous devez ajouter une 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
|
||||||
|
): Response {
|
||||||
|
$navigations = $navigationQuery->create()->find();
|
||||||
|
|
||||||
|
$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';
|
||||||
|
}
|
||||||
|
}
|
138
core/Controller/User/UserAdminController.php
Normal file
138
core/Controller/User/UserAdminController.php
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Controller\User;
|
||||||
|
|
||||||
|
use App\Core\Controller\Admin\AdminController;
|
||||||
|
use App\Core\Event\Account\PasswordRequestEvent;
|
||||||
|
use App\Core\Factory\UserFactory as EntityFactory;
|
||||||
|
use App\Core\Form\UserType as EntityType;
|
||||||
|
use App\Core\Manager\EntityManager;
|
||||||
|
use App\Entity\User as Entity;
|
||||||
|
use App\Repository\UserRepositoryQuery as RepositoryQuery;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/admin/user")
|
||||||
|
*/
|
||||||
|
class UserAdminController extends AdminController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/{page}", name="admin_user_index", requirements={"page": "\d+"})
|
||||||
|
*/
|
||||||
|
public function index(int $page = 1, RepositoryQuery $query, Request $request): Response
|
||||||
|
{
|
||||||
|
$pager = $query->paginate($page);
|
||||||
|
|
||||||
|
return $this->render('@Core/user/user_admin/index.html.twig', [
|
||||||
|
'pager' => $pager,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/new", name="admin_user_new")
|
||||||
|
*/
|
||||||
|
public function new(
|
||||||
|
EntityFactory $factory,
|
||||||
|
EntityManager $entityManager,
|
||||||
|
UserPasswordEncoderInterface $encoder,
|
||||||
|
Request $request
|
||||||
|
): Response {
|
||||||
|
$entity = $factory->create($this->getUser());
|
||||||
|
$form = $this->createForm(EntityType::class, $entity);
|
||||||
|
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$entityManager->create($entity);
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_user_edit', [
|
||||||
|
'entity' => $entity->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@Core/user/user_admin/new.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'entity' => $entity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/edit/{entity}", name="admin_user_edit")
|
||||||
|
*/
|
||||||
|
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(EntityType::class, $entity);
|
||||||
|
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$entityManager->update($entity);
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_user_edit', [
|
||||||
|
'entity' => $entity->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@Core/user/user_admin/edit.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'entity' => $entity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/show/{entity}", name="admin_user_show")
|
||||||
|
*/
|
||||||
|
public function show(Entity $entity): Response
|
||||||
|
{
|
||||||
|
return $this->render('@Core/user/user_admin/show.html.twig', [
|
||||||
|
'entity' => $entity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/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', 'Demande envoyée.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_user_edit', [
|
||||||
|
'entity' => $entity->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/delete/{entity}", name="admin_user_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', 'Données supprimée..');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_user_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSection(): string
|
||||||
|
{
|
||||||
|
return 'user';
|
||||||
|
}
|
||||||
|
}
|
52
core/DependencyInjection/Configuration.php
Normal file
52
core/DependencyInjection/Configuration.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?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
|
||||||
|
{
|
||||||
|
$treeBuilder = new TreeBuilder('core');
|
||||||
|
|
||||||
|
$treeBuilder->getRootNode()
|
||||||
|
->children()
|
||||||
|
->arrayNode('site')
|
||||||
|
->children()
|
||||||
|
->scalarNode('name')
|
||||||
|
->isRequired()
|
||||||
|
->cannotBeEmpty()
|
||||||
|
->end()
|
||||||
|
->scalarNode('logo')
|
||||||
|
->isRequired()
|
||||||
|
->cannotBeEmpty()
|
||||||
|
->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();
|
||||||
|
|
||||||
|
return $treeBuilder;
|
||||||
|
}
|
||||||
|
}
|
28
core/DependencyInjection/CoreExtension.php
Normal file
28
core/DependencyInjection/CoreExtension.php
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\DependencyInjection;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||||
|
|
||||||
|
class CoreExtension extends Extension
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function load(array $configs, ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
$configuration = $this->getConfiguration($configs, $container);
|
||||||
|
$config = $this->processConfiguration($configuration, $configs);
|
||||||
|
|
||||||
|
$container->setParameter('core', $config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getConfiguration(array $configs, ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
return new Configuration();
|
||||||
|
}
|
||||||
|
}
|
59
core/Doctrine/Timestampable.php
Normal file
59
core/Doctrine/Timestampable.php
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Doctrine;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
trait Timestampable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Column(name="created_at", type="datetime")
|
||||||
|
*/
|
||||||
|
protected $createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(name="updated_at", type="datetime")
|
||||||
|
*/
|
||||||
|
protected $updatedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\PrePersist
|
||||||
|
*/
|
||||||
|
public function onPrePersist(): void
|
||||||
|
{
|
||||||
|
$this->createdAt = new \DateTime();
|
||||||
|
$this->updatedAt = new \DateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\PreUpdate
|
||||||
|
*/
|
||||||
|
public function onPreUpdate(): void
|
||||||
|
{
|
||||||
|
$this->updatedAt = new \DateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedAt(?\DateTime $createdAt): self
|
||||||
|
{
|
||||||
|
$this->createdAt = $createdAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): ?\DateTime
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUpdatedAt(?\DateTime $updatedAt): self
|
||||||
|
{
|
||||||
|
$this->updatedAt = $updatedAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedAt(): ?\DateTime
|
||||||
|
{
|
||||||
|
return $this->updatedAt;
|
||||||
|
}
|
||||||
|
}
|
0
core/Entity/.gitignore
vendored
Normal file
0
core/Entity/.gitignore
vendored
Normal file
7
core/Entity/EntityInterface.php
Normal file
7
core/Entity/EntityInterface.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Entity;
|
||||||
|
|
||||||
|
interface EntityInterface
|
||||||
|
{
|
||||||
|
}
|
146
core/Entity/Site/Menu.php
Normal file
146
core/Entity/Site/Menu.php
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Entity\Site;
|
||||||
|
|
||||||
|
use App\Core\Doctrine\Timestampable;
|
||||||
|
use App\Core\Entity\EntityInterface;
|
||||||
|
use App\Core\Repository\Site\MenuRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass=MenuRepository::class)
|
||||||
|
* @ORM\HasLifecycleCallbacks
|
||||||
|
*/
|
||||||
|
class Menu implements EntityInterface
|
||||||
|
{
|
||||||
|
use Timestampable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private $label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private $code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=Navigation::class, inversedBy="menus")
|
||||||
|
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
|
||||||
|
*/
|
||||||
|
private $navigation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity=Node::class, mappedBy="menu", orphanRemoval=true, cascade={"remove", "persist"})
|
||||||
|
*/
|
||||||
|
private $nodes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToOne(targetEntity=Node::class, cascade={"persist"})
|
||||||
|
* @ORM\JoinColumn(onDelete="CASCADE")
|
||||||
|
*/
|
||||||
|
private $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());
|
||||||
|
}
|
||||||
|
}
|
138
core/Entity/Site/Navigation.php
Normal file
138
core/Entity/Site/Navigation.php
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Entity\Site;
|
||||||
|
|
||||||
|
use App\Core\Doctrine\Timestampable;
|
||||||
|
use App\Core\Entity\EntityInterface;
|
||||||
|
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")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private $label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private $code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private $domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity=Menu::class, mappedBy="navigation")
|
||||||
|
*/
|
||||||
|
private $menus;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->menus = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
}
|
400
core/Entity/Site/Node.php
Normal file
400
core/Entity/Site/Node.php
Normal file
|
@ -0,0 +1,400 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Entity\Site;
|
||||||
|
|
||||||
|
use App\Core\Doctrine\Timestampable;
|
||||||
|
use App\Core\Entity\EntityInterface;
|
||||||
|
use App\Core\Entity\Site\Page\Page;
|
||||||
|
use App\Core\Repository\Site\NodeRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Gedmo\Mapping\Annotation as Gedmo;
|
||||||
|
use function Symfony\Component\String\u;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Gedmo\Tree(type="nested")
|
||||||
|
* @ORM\HasLifecycleCallbacks
|
||||||
|
* @ORM\Entity(repositoryClass=NodeRepository::class)
|
||||||
|
*/
|
||||||
|
class Node implements EntityInterface
|
||||||
|
{
|
||||||
|
use Timestampable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=Menu::class, inversedBy="nodes", cascade={"persist", "remove"})
|
||||||
|
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
|
||||||
|
*/
|
||||||
|
private $menu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255, nullable=true)
|
||||||
|
*/
|
||||||
|
private $label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255, nullable=true)
|
||||||
|
*/
|
||||||
|
private $url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="boolean", options={"default"=0})
|
||||||
|
*/
|
||||||
|
private $isVisible = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Gedmo\TreeLeft
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $treeLeft;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Gedmo\TreeLevel
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $treeLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Gedmo\TreeRight
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $treeRight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Gedmo\TreeRoot
|
||||||
|
* @ORM\ManyToOne(targetEntity="Node")
|
||||||
|
* @ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
|
||||||
|
*/
|
||||||
|
private $treeRoot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Gedmo\TreeParent
|
||||||
|
* @ORM\ManyToOne(targetEntity="Node", inversedBy="children")
|
||||||
|
* @ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
|
||||||
|
*/
|
||||||
|
private $parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity="Node", mappedBy="parent")
|
||||||
|
* @ORM\OrderBy({"treeLeft"="ASC"})
|
||||||
|
*/
|
||||||
|
private $children;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=Page::class, inversedBy="nodes", cascade={"persist"})
|
||||||
|
* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
|
||||||
|
*/
|
||||||
|
private $page;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255, nullable=true)
|
||||||
|
*/
|
||||||
|
private $code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="array", nullable=true)
|
||||||
|
*/
|
||||||
|
private $parameters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="array", nullable=true)
|
||||||
|
*/
|
||||||
|
private $attributes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255, nullable=true)
|
||||||
|
*/
|
||||||
|
private $controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="array", nullable=true)
|
||||||
|
*/
|
||||||
|
private $sitemapParameters = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->children = 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(): Collection
|
||||||
|
{
|
||||||
|
if (null === $this->children) {
|
||||||
|
$this->children = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(): 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;
|
||||||
|
});
|
||||||
|
|
||||||
|
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 getIsVisible(): ?bool
|
||||||
|
{
|
||||||
|
return $this->isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsVisible(bool $isVisible): self
|
||||||
|
{
|
||||||
|
$this->isVisible = $isVisible;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTreeLabel()
|
||||||
|
{
|
||||||
|
$prefix = str_repeat('-', ($this->getTreeLevel() - 1) * 5);
|
||||||
|
|
||||||
|
return trim($prefix.' '.$this->getLabel());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPage(): ?Page
|
||||||
|
{
|
||||||
|
return $this->page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPage(?Page $page): self
|
||||||
|
{
|
||||||
|
$this->page = $page;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRouteName(): string
|
||||||
|
{
|
||||||
|
return $this->getMenu()->getRouteName().'_'.($this->getCode() ? $this->getCode() : $this->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): ?string
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCode(?string $code): self
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParameters(): ?array
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
return $this->controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setController(?string $controller): self
|
||||||
|
{
|
||||||
|
$this->controller = $controller;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSitemapParameters(): ?array
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
82
core/Entity/Site/Page/Block.php
Normal file
82
core/Entity/Site/Page/Block.php
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Entity\Site\Page;
|
||||||
|
|
||||||
|
use App\Core\Doctrine\Timestampable;
|
||||||
|
use App\Core\Repository\Site\Page\BlockRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity(repositoryClass=BlockRepository::class)
|
||||||
|
* @ORM\DiscriminatorColumn(name="class_key", type="string")
|
||||||
|
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||||
|
* @ORM\HasLifecycleCallbacks
|
||||||
|
*/
|
||||||
|
class Block
|
||||||
|
{
|
||||||
|
use Timestampable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text", nullable=true)
|
||||||
|
*/
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=Page::class, inversedBy="blocks")
|
||||||
|
* @ORM\JoinColumn(onDelete="CASCADE")
|
||||||
|
*/
|
||||||
|
private $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;
|
||||||
|
}
|
||||||
|
}
|
36
core/Entity/Site/Page/FileBlock.php
Normal file
36
core/Entity/Site/Page/FileBlock.php
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Core\Entity\Site\Page;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
*/
|
||||||
|
class FileBlock extends Block
|
||||||
|
{
|
||||||
|
public function getValue()
|
||||||
|
{
|
||||||
|
$value = parent::getValue();
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
|
if (file_exists($value)) {
|
||||||
|
return new File($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setValue($value): self
|
||||||
|
{
|
||||||
|
if ($this->getValue() instanceof File && null === $value) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::setValue($value);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue