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