Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
332a1a4fa9 | |||
9e7b35fdd9 | |||
13ba510d55 | |||
0fa3b88e08 | |||
51ddf6510e | |||
08cffee1c1 | |||
2bd4a99da1 | |||
93830df031 | |||
9073737e36 | |||
a4ffaca6a6 | |||
dd6105e8e9 | |||
0aecfd62eb | |||
7141c8d1f3 | |||
5651251da7 | |||
6e30de336f | |||
8c3c1e3216 | |||
a545af7edb | |||
cb09b879f9 | |||
1c9a9cab3e | |||
6ad4845bca | |||
4173fd53f1 | |||
e42d09b788 | |||
f762a5a5a1 | |||
fd0f74bdad | |||
f3dcd7b37c | |||
7b87aef501 | |||
e5f7d5a721 | |||
b7a73e42ee | |||
896c6e12e0 | |||
f071fb8b9a | |||
b968006b4d | |||
62fe712f0b | |||
2568db1736 | |||
913dcae23a | |||
d79fe72155 | |||
c268f8873d | |||
c3df560fc7 | |||
1289186463 | |||
e8554bcecc | |||
f6572696f4 | |||
087e26f417 | |||
7330349a5a | |||
3eda535868 | |||
a71184f9f1 | |||
9ecc72628c | |||
2b52d18da5 | |||
1ded9cf762 | |||
3a8fc71eb2 | |||
72ee30cc01 | |||
9d2aa6a2e4 | |||
7d0b43aa4d | |||
d52dedd707 | |||
6b56a34c6d | |||
062c5f5895 | |||
38f7355d42 | |||
14c6ce39ab | |||
c925421381 | |||
333d00b2a0 | |||
4e39f6cb12 | |||
db0eb5a3fb | |||
e548bc1820 | |||
38283bcf73 | |||
9064bba152 | |||
65d7685e72 | |||
ad642ce6ac | |||
ffd94e1ae6 | |||
d6e1679aba | |||
6e140f84f7 | |||
4cab96f9b6 | |||
f858359a51 | |||
3c4be32c6e | |||
15b33a11d7 | |||
43c8f9af85 |
@ -5,3 +5,4 @@ vendor/
|
|||||||
|
|
||||||
uploads/*
|
uploads/*
|
||||||
!uploads/.gitkeep
|
!uploads/.gitkeep
|
||||||
|
node_modules/
|
11
.env
11
.env
@ -15,8 +15,9 @@
|
|||||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||||
|
|
||||||
###> symfony/framework-bundle ###
|
###> symfony/framework-bundle ###
|
||||||
APP_ENV=dev
|
APP_ENV=prod
|
||||||
APP_SECRET=
|
APP_SECRET=
|
||||||
|
APP_DEBUG=false
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
###> doctrine/doctrine-bundle ###
|
###> doctrine/doctrine-bundle ###
|
||||||
@ -37,7 +38,13 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
|||||||
###< symfony/messenger ###
|
###< symfony/messenger ###
|
||||||
|
|
||||||
###> symfony/mailer ###
|
###> symfony/mailer ###
|
||||||
MAILER_DSN=null://null
|
MAILER_DSN=sendmail://default
|
||||||
###< symfony/mailer ###
|
###< symfony/mailer ###
|
||||||
|
|
||||||
BASE_PREFIX=kumora
|
BASE_PREFIX=kumora
|
||||||
|
DEFAULT_IMAGE=https://camelia-studio.org/v5/images/camelia_studio.png
|
||||||
|
APP_VERSION=1.0.0
|
||||||
|
GIT_URL=https://git.crystalyx.net/camelia-studio/Kumora
|
||||||
|
CAMELIA_URL=https://camelia-studio.org/
|
||||||
|
TSUBAKIMONO_URL=https://tsubakimono.camelia-studio.org/
|
||||||
|
DISCORD_URL=https://discord.gg/nBuZ9vJ
|
||||||
|
17
.gitea/workflows/php-cs-fixer.yml
Normal file
17
.gitea/workflows/php-cs-fixer.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
name: Apply PHP CS Fixer
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
php-cs-fixer:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Run PHP CS Fixer
|
||||||
|
uses: docker://oskarstark/php-cs-fixer-ga@sha256:5cfb9c812528bbafd614ca16a7d48e7ab35421d930cab611e815d6bfe8f07bfa
|
||||||
|
with:
|
||||||
|
args: --diff --dry-run
|
||||||
|
|
21
.gitea/workflows/phpstan.yml
Normal file
21
.gitea/workflows/phpstan.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup PHP with pre-release PECL extension
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.2'
|
||||||
|
- name: Download Composer
|
||||||
|
run: php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && php composer-setup.php && php -r "unlink('composer-setup.php');"
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: php composer.phar install
|
||||||
|
- name: Run PHPStan
|
||||||
|
run: php vendor/bin/phpstan
|
||||||
|
|
18
.gitea/workflows/rector.yml
Normal file
18
.gitea/workflows/rector.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
on: [push, pull_request]
|
||||||
|
name: rector
|
||||||
|
jobs:
|
||||||
|
rector:
|
||||||
|
name: Rector
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup PHP with pre-release PECL extension
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.2'
|
||||||
|
- name: Download Composer
|
||||||
|
run: php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && php composer-setup.php && php -r "unlink('composer-setup.php');"
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: php composer.phar install
|
||||||
|
- name: Run Rector
|
||||||
|
run: vendor/bin/rector --dry-run
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -34,3 +34,12 @@
|
|||||||
!uploads/.gitkeep
|
!uploads/.gitkeep
|
||||||
|
|
||||||
/public/kumora/
|
/public/kumora/
|
||||||
|
###> friendsofphp/php-cs-fixer ###
|
||||||
|
/.php-cs-fixer.php
|
||||||
|
/.php-cs-fixer.cache
|
||||||
|
###< friendsofphp/php-cs-fixer ###
|
||||||
|
|
||||||
|
###> phpstan/phpstan ###
|
||||||
|
phpstan.neon
|
||||||
|
###< phpstan/phpstan ###
|
||||||
|
node_modules/
|
103
.php-cs-fixer.dist.php
Normal file
103
.php-cs-fixer.dist.php
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of PHP CS Fixer.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
|
||||||
|
*
|
||||||
|
* This source file is subject to the MIT license that is bundled
|
||||||
|
* with this source code in the file LICENSE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$finder = PhpCsFixer\Finder::create()
|
||||||
|
->ignoreVCSIgnored(true)
|
||||||
|
->in(__DIR__)
|
||||||
|
->append([
|
||||||
|
__DIR__ . '/dev-tools/doc.php',
|
||||||
|
// __DIR__.'/php-cs-fixer', disabled, as we want to be able to run bootstrap file even on lower PHP version, to show nice message
|
||||||
|
__FILE__,
|
||||||
|
])
|
||||||
|
;
|
||||||
|
|
||||||
|
$config = new PhpCsFixer\Config();
|
||||||
|
$config
|
||||||
|
->setRiskyAllowed(true)
|
||||||
|
->setRules([
|
||||||
|
'@PHP82Migration' => true,
|
||||||
|
'@PHP80Migration:risky' => true,
|
||||||
|
'@PhpCsFixer:risky' => true,
|
||||||
|
'@PSR1' => true,
|
||||||
|
'@PSR2' => true,
|
||||||
|
'@PSR12' => true,
|
||||||
|
'align_multiline_comment' => [
|
||||||
|
'comment_type' => 'phpdocs_only',
|
||||||
|
],
|
||||||
|
'array_indentation' => true,
|
||||||
|
'array_syntax' => [
|
||||||
|
'syntax' => 'short',
|
||||||
|
],
|
||||||
|
// Anciennement 'braces'
|
||||||
|
'single_space_around_construct' => true,
|
||||||
|
'control_structure_braces' => true,
|
||||||
|
'control_structure_continuation_position' => true,
|
||||||
|
'declare_parentheses' => true,
|
||||||
|
'no_multiple_statements_per_line' => true,
|
||||||
|
'braces_position' => true,
|
||||||
|
'statement_indentation' => true,
|
||||||
|
|
||||||
|
'compact_nullable_type_declaration' => true,
|
||||||
|
'concat_space' => ['spacing' => 'one'],
|
||||||
|
'doctrine_annotation_array_assignment' => [
|
||||||
|
'operator' => '=',
|
||||||
|
],
|
||||||
|
'doctrine_annotation_spaces' => [
|
||||||
|
'after_array_assignments_equals' => false,
|
||||||
|
'before_array_assignments_equals' => false,
|
||||||
|
],
|
||||||
|
'php_unit_internal_class' => false,
|
||||||
|
'php_unit_test_class_requires_covers' => false,
|
||||||
|
'no_extra_blank_lines' => [
|
||||||
|
'tokens' => [
|
||||||
|
'break',
|
||||||
|
'continue',
|
||||||
|
'curly_brace_block',
|
||||||
|
'extra',
|
||||||
|
'parenthesis_brace_block',
|
||||||
|
'return',
|
||||||
|
'square_brace_block',
|
||||||
|
'throw',
|
||||||
|
'use',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'no_useless_else' => true,
|
||||||
|
'no_useless_return' => true,
|
||||||
|
'ordered_imports' => [
|
||||||
|
'imports_order' => [
|
||||||
|
'class',
|
||||||
|
'function',
|
||||||
|
'const',
|
||||||
|
],
|
||||||
|
'sort_algorithm' => 'alpha',
|
||||||
|
],
|
||||||
|
'php_unit_method_casing' => [
|
||||||
|
'case' => 'camel_case',
|
||||||
|
],
|
||||||
|
'phpdoc_order' => true,
|
||||||
|
'phpdoc_separation' => true,
|
||||||
|
'phpdoc_trim_consecutive_blank_line_separation' => true,
|
||||||
|
'phpdoc_types_order' => ['null_adjustment' => 'always_last'],
|
||||||
|
'strict_comparison' => true,
|
||||||
|
'strict_param' => true,
|
||||||
|
'yoda_style' => true,
|
||||||
|
'modernize_strpos' => true, // needs PHP 8+ or polyfill
|
||||||
|
'native_constant_invocation' => false,
|
||||||
|
'php_unit_strict' => false,
|
||||||
|
'native_function_invocation' => false,
|
||||||
|
])
|
||||||
|
->setFinder($finder)
|
||||||
|
;
|
||||||
|
|
||||||
|
return $config;
|
@ -1,4 +1,4 @@
|
|||||||
FROM dunglas/frankenphp@sha256:455232397cc29d3dd8f872b38e07711b9ccd4adb7f6ee2c518bffa6dc84fb05a
|
FROM dunglas/frankenphp@sha256:bc16b2c6900748ffd951b751a0798dba6a13ffa22ed4c793cf460ca0be4bc446
|
||||||
|
|
||||||
ENV SERVER_NAME=":80"
|
ENV SERVER_NAME=":80"
|
||||||
|
|
||||||
|
@ -6,5 +6,4 @@ import './bootstrap.js';
|
|||||||
* which should already be in your base.html.twig.
|
* which should already be in your base.html.twig.
|
||||||
*/
|
*/
|
||||||
import './styles/app.css';
|
import './styles/app.css';
|
||||||
|
import 'flowbite';
|
||||||
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');
|
|
||||||
|
@ -1,3 +1,28 @@
|
|||||||
@tailwind base;
|
@import 'tailwindcss';
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
@import "flowbite/src/themes/default";
|
||||||
|
@plugin "flowbite/plugin";
|
||||||
|
|
||||||
|
@source '../../vendor/tales-from-a-dev/flowbite-bundle/templates/**/*.html.twig';
|
||||||
|
@source "../node_modules/flowbite";
|
||||||
|
|
||||||
|
@custom-variant dark (@media (prefers-color-scheme: dark));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
||||||
|
so we've added these compatibility styles to make sure everything still
|
||||||
|
looks the same as it did with Tailwind CSS v3.
|
||||||
|
|
||||||
|
If we ever want to remove these styles, we need to add an explicit border
|
||||||
|
color utility to any element that depends on these defaults.
|
||||||
|
*/
|
||||||
|
@layer base {
|
||||||
|
*,
|
||||||
|
::after,
|
||||||
|
::before,
|
||||||
|
::backdrop,
|
||||||
|
::file-selector-button {
|
||||||
|
border-color: var(--color-gray-200, currentColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,14 +7,14 @@
|
|||||||
"php": ">=8.2",
|
"php": ">=8.2",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"doctrine/dbal": "^4.2.1",
|
"doctrine/dbal": "^4.2.2",
|
||||||
"doctrine/doctrine-bundle": "^2.13.1",
|
"doctrine/doctrine-bundle": "^2.13.2",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.3.1",
|
"doctrine/doctrine-migrations-bundle": "^3.4.1",
|
||||||
"doctrine/orm": "^3.3.1",
|
"doctrine/orm": "^3.3.2",
|
||||||
"league/flysystem": "^3.29.1",
|
"league/flysystem": "^3.29.1",
|
||||||
"oneup/flysystem-bundle": "^4.12.3",
|
"oneup/flysystem-bundle": "^4.12.4",
|
||||||
"phpdocumentor/reflection-docblock": "^5.6.1",
|
"phpdocumentor/reflection-docblock": "^5.6.1",
|
||||||
"phpstan/phpdoc-parser": "^2.0",
|
"phpstan/phpdoc-parser": "^2.0.2",
|
||||||
"symfony/apache-pack": "^1.0.1",
|
"symfony/apache-pack": "^1.0.1",
|
||||||
"symfony/asset": "7.2.*",
|
"symfony/asset": "7.2.*",
|
||||||
"symfony/asset-mapper": "7.2.*",
|
"symfony/asset-mapper": "7.2.*",
|
||||||
@ -38,26 +38,28 @@
|
|||||||
"symfony/security-bundle": "7.2.*",
|
"symfony/security-bundle": "7.2.*",
|
||||||
"symfony/security-csrf": "7.2.*",
|
"symfony/security-csrf": "7.2.*",
|
||||||
"symfony/serializer": "7.2.*",
|
"symfony/serializer": "7.2.*",
|
||||||
"symfony/stimulus-bundle": "^2.22.1",
|
"symfony/stimulus-bundle": "^2.23.0",
|
||||||
"symfony/string": "7.2.*",
|
"symfony/string": "7.2.*",
|
||||||
"symfony/translation": "7.2.*",
|
"symfony/translation": "7.2.*",
|
||||||
"symfony/twig-bundle": "7.2.*",
|
"symfony/twig-bundle": "7.2.*",
|
||||||
"symfony/uid": "7.2.*",
|
"symfony/uid": "7.2.*",
|
||||||
"symfony/ux-dropzone": "^2.22.1",
|
"symfony/ux-dropzone": "^2.23.0",
|
||||||
"symfony/ux-icons": "^2.22.1",
|
"symfony/ux-icons": "^2.23",
|
||||||
"symfony/ux-turbo": "^2.22.1",
|
"symfony/ux-turbo": "^2.23.0",
|
||||||
"symfony/ux-twig-component": "^2.22.1",
|
"symfony/ux-twig-component": "^2.23.0",
|
||||||
"symfony/validator": "7.2.*",
|
"symfony/validator": "7.2.*",
|
||||||
"symfony/web-link": "7.2.*",
|
"symfony/web-link": "7.2.*",
|
||||||
"symfony/yaml": "7.2.*",
|
"symfony/yaml": "7.2.*",
|
||||||
"symfonycasts/tailwind-bundle": "^0.6.1",
|
"symfonycasts/reset-password-bundle": "^1.23.1",
|
||||||
|
"symfonycasts/tailwind-bundle": "^0.7.1",
|
||||||
"tales-from-a-dev/flowbite-bundle": "^0.7.1",
|
"tales-from-a-dev/flowbite-bundle": "^0.7.1",
|
||||||
"twig/extra-bundle": "^2.12|^3.18",
|
"twig/extra-bundle": "^2.12|^3.20",
|
||||||
"twig/twig": "^2.12|^3.18"
|
"twig/twig": "^2.12|^3.20"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"php-http/discovery": true,
|
"php-http/discovery": true,
|
||||||
|
"phpstan/extension-installer": true,
|
||||||
"symfony/flex": true,
|
"symfony/flex": true,
|
||||||
"symfony/runtime": true
|
"symfony/runtime": true
|
||||||
},
|
},
|
||||||
@ -107,11 +109,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^11.5.2",
|
"friendsofphp/php-cs-fixer": "^3.69.1",
|
||||||
|
"phpstan/extension-installer": "^1.4.3",
|
||||||
|
"phpstan/phpstan-strict-rules": "^2.0.3",
|
||||||
|
"phpstan/phpstan-symfony": "^2.0.2",
|
||||||
|
"phpunit/phpunit": "^11.5.8",
|
||||||
|
"rector/rector": "^2.0.9",
|
||||||
"symfony/browser-kit": "7.2.*",
|
"symfony/browser-kit": "7.2.*",
|
||||||
"symfony/css-selector": "7.2.*",
|
"symfony/css-selector": "7.2.*",
|
||||||
"symfony/debug-bundle": "7.2.*",
|
"symfony/debug-bundle": "7.2.*",
|
||||||
"symfony/maker-bundle": "^1.61",
|
"symfony/maker-bundle": "^1.62.1",
|
||||||
"symfony/phpunit-bridge": "^7.2",
|
"symfony/phpunit-bridge": "^7.2",
|
||||||
"symfony/stopwatch": "7.2.*",
|
"symfony/stopwatch": "7.2.*",
|
||||||
"symfony/web-profiler-bundle": "7.2.*"
|
"symfony/web-profiler-bundle": "7.2.*"
|
||||||
|
1910
composer.lock
generated
Executable file → Normal file
1910
composer.lock
generated
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||||
@ -20,4 +22,5 @@ return [
|
|||||||
TalesFromADev\Twig\Extra\Tailwind\Bridge\Symfony\Bundle\TalesFromADevTwigExtraTailwindBundle::class => ['all' => true],
|
TalesFromADev\Twig\Extra\Tailwind\Bridge\Symfony\Bundle\TalesFromADevTwigExtraTailwindBundle::class => ['all' => true],
|
||||||
TalesFromADev\FlowbiteBundle\TalesFromADevFlowbiteBundle::class => ['all' => true],
|
TalesFromADev\FlowbiteBundle\TalesFromADevFlowbiteBundle::class => ['all' => true],
|
||||||
Symfony\UX\Dropzone\DropzoneBundle::class => ['all' => true],
|
Symfony\UX\Dropzone\DropzoneBundle::class => ['all' => true],
|
||||||
|
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
doctrine:
|
doctrine:
|
||||||
dbal:
|
dbal:
|
||||||
url: '%env(resolve:DATABASE_URL)%'
|
url: '%env(resolve:DATABASE_URL)%'
|
||||||
|
mapping_types:
|
||||||
|
uuid: string
|
||||||
|
types:
|
||||||
|
uuid: Symfony\Bridge\Doctrine\Types\UuidType
|
||||||
|
|
||||||
# IMPORTANT: You MUST configure your server version,
|
# IMPORTANT: You MUST configure your server version,
|
||||||
# either here or in the DATABASE_URL env var (see .env file)
|
# either here or in the DATABASE_URL env var (see .env file)
|
||||||
|
@ -13,7 +13,7 @@ framework:
|
|||||||
max_retries: 3
|
max_retries: 3
|
||||||
multiplier: 2
|
multiplier: 2
|
||||||
failed: 'doctrine://default?queue_name=failed'
|
failed: 'doctrine://default?queue_name=failed'
|
||||||
# sync: 'sync://'
|
sync: 'sync://'
|
||||||
|
|
||||||
default_bus: messenger.bus.default
|
default_bus: messenger.bus.default
|
||||||
|
|
||||||
@ -21,9 +21,9 @@ framework:
|
|||||||
messenger.bus.default: []
|
messenger.bus.default: []
|
||||||
|
|
||||||
routing:
|
routing:
|
||||||
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
|
Symfony\Component\Mailer\Messenger\SendEmailMessage: sync
|
||||||
Symfony\Component\Notifier\Message\ChatMessage: async
|
Symfony\Component\Notifier\Message\ChatMessage: sync
|
||||||
Symfony\Component\Notifier\Message\SmsMessage: async
|
Symfony\Component\Notifier\Message\SmsMessage: sync
|
||||||
|
|
||||||
# Route your messages to the transports
|
# Route your messages to the transports
|
||||||
# 'App\Message\YourMessage': async
|
# 'App\Message\YourMessage': async
|
||||||
|
2
config/packages/reset_password.yaml
Normal file
2
config/packages/reset_password.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
symfonycasts_reset_password:
|
||||||
|
request_password_repository: App\Repository\ResetPasswordRequestRepository
|
2
config/packages/symfonycasts_tailwind.yaml
Normal file
2
config/packages/symfonycasts_tailwind.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
symfonycasts_tailwind:
|
||||||
|
binary_version: 'v4.0.7'
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
if (file_exists(dirname(__DIR__) . '/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
if (file_exists(dirname(__DIR__) . '/var/cache/prod/App_KernelProdContainer.preload.php')) {
|
||||||
require dirname(__DIR__) . '/var/cache/prod/App_KernelProdContainer.preload.php';
|
require dirname(__DIR__) . '/var/cache/prod/App_KernelProdContainer.preload.php';
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,15 @@
|
|||||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||||
parameters:
|
parameters:
|
||||||
base.prefix: '%env(BASE_PREFIX)%'
|
base.prefix: '%env(BASE_PREFIX)%'
|
||||||
|
default.image: '%env(DEFAULT_IMAGE)%'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# default configuration for services in *this* file
|
# default configuration for services in *this* file
|
||||||
_defaults:
|
_defaults:
|
||||||
autowire: true # Automatically injects dependencies in your services.
|
autowire: true # Automatically injects dependencies in your services.
|
||||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||||
|
bind:
|
||||||
|
string $defaultImage: '%default.image%'
|
||||||
# makes classes in src/ available to be used as services
|
# makes classes in src/ available to be used as services
|
||||||
# this creates a service per class whose id is the fully-qualified class name
|
# this creates a service per class whose id is the fully-qualified class name
|
||||||
App\:
|
App\:
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the importmap for this application.
|
* Returns the importmap for this application.
|
||||||
*
|
*
|
||||||
@ -25,4 +27,17 @@ return [
|
|||||||
'@hotwired/turbo' => [
|
'@hotwired/turbo' => [
|
||||||
'version' => '8.0.12',
|
'version' => '8.0.12',
|
||||||
],
|
],
|
||||||
|
'flowbite' => [
|
||||||
|
'version' => '2.5.2',
|
||||||
|
],
|
||||||
|
'@popperjs/core' => [
|
||||||
|
'version' => '2.11.8',
|
||||||
|
],
|
||||||
|
'flowbite-datepicker' => [
|
||||||
|
'version' => '1.3.0',
|
||||||
|
],
|
||||||
|
'flowbite/dist/flowbite.min.css' => [
|
||||||
|
'version' => '2.5.2',
|
||||||
|
'type' => 'css',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
final class Version20241229133017 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return 'Ajout de la table user et messenger_messages';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->addSql('CREATE TABLE "user" (id BLOB NOT NULL --(DC2Type:uuid)
|
|
||||||
, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
|
|
||||||
, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
|
|
||||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
|
|
||||||
$this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
|
||||||
, available_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
|
||||||
, delivered_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable)
|
|
||||||
)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
|
|
||||||
$this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
$this->addSql('DROP TABLE "user"');
|
|
||||||
$this->addSql('DROP TABLE messenger_messages');
|
|
||||||
}
|
|
||||||
}
|
|
40
migrations/Version20250122182101.php
Normal file
40
migrations/Version20250122182101.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250122182101 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE reset_password_request (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, selector VARCHAR(20) NOT NULL, hashed_token VARCHAR(100) NOT NULL, requested_at DATETIME NOT NULL, expires_at DATETIME NOT NULL, user_id INTEGER NOT NULL, CONSTRAINT FK_7CE748AA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_7CE748AA76ED395 ON reset_password_request (user_id)');
|
||||||
|
$this->addSql('CREATE TABLE "user" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL, password VARCHAR(255) NOT NULL)');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
|
||||||
|
$this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL, available_at DATETIME NOT NULL, delivered_at DATETIME DEFAULT NULL)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('DROP TABLE reset_password_request');
|
||||||
|
$this->addSql('DROP TABLE "user"');
|
||||||
|
$this->addSql('DROP TABLE messenger_messages');
|
||||||
|
}
|
||||||
|
}
|
31
migrations/Version20250122182130.php
Normal file
31
migrations/Version20250122182130.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250122182130 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE parent_directory (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, owner_role VARCHAR(255) NOT NULL)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('DROP TABLE parent_directory');
|
||||||
|
}
|
||||||
|
}
|
35
migrations/Version20250122183447.php
Normal file
35
migrations/Version20250122183447.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250122183447 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE user ADD COLUMN folder_role VARCHAR(255) NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password FROM "user"');
|
||||||
|
$this->addSql('DROP TABLE "user"');
|
||||||
|
$this->addSql('CREATE TABLE "user" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL, password VARCHAR(255) NOT NULL)');
|
||||||
|
$this->addSql('INSERT INTO "user" (id, email, roles, password) SELECT id, email, roles, password FROM __temp__user');
|
||||||
|
$this->addSql('DROP TABLE __temp__user');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
|
||||||
|
}
|
||||||
|
}
|
36
migrations/Version20250122192521.php
Normal file
36
migrations/Version20250122192521.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250122192521 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE user ADD COLUMN fullname VARCHAR(255) NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password, folder_role FROM "user"');
|
||||||
|
$this->addSql('DROP TABLE "user"');
|
||||||
|
$this->addSql('CREATE TABLE "user" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL, password VARCHAR(255) NOT NULL, folder_role VARCHAR(255) NOT NULL)');
|
||||||
|
$this->addSql('INSERT INTO "user" (id, email, roles, password, folder_role) SELECT id, email, roles, password, folder_role FROM __temp__user');
|
||||||
|
$this->addSql('DROP TABLE __temp__user');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
|
||||||
|
}
|
||||||
|
}
|
40
migrations/Version20250123225212.php
Normal file
40
migrations/Version20250123225212.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250123225212 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__parent_directory AS SELECT id, name, owner_role FROM parent_directory');
|
||||||
|
$this->addSql('DROP TABLE parent_directory');
|
||||||
|
$this->addSql('CREATE TABLE parent_directory (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, owner_role VARCHAR(255) NOT NULL, user_created_id INTEGER NOT NULL, CONSTRAINT FK_B7336B34F987D8A8 FOREIGN KEY (user_created_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||||
|
$this->addSql('INSERT INTO parent_directory (id, name, owner_role) SELECT id, name, owner_role FROM __temp__parent_directory');
|
||||||
|
$this->addSql('DROP TABLE __temp__parent_directory');
|
||||||
|
$this->addSql('CREATE INDEX IDX_B7336B34F987D8A8 ON parent_directory (user_created_id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__parent_directory AS SELECT id, name, owner_role FROM parent_directory');
|
||||||
|
$this->addSql('DROP TABLE parent_directory');
|
||||||
|
$this->addSql('CREATE TABLE parent_directory (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, owner_role VARCHAR(255) NOT NULL)');
|
||||||
|
$this->addSql('INSERT INTO parent_directory (id, name, owner_role) SELECT id, name, owner_role FROM __temp__parent_directory');
|
||||||
|
$this->addSql('DROP TABLE __temp__parent_directory');
|
||||||
|
}
|
||||||
|
}
|
36
migrations/Version20250123230226.php
Normal file
36
migrations/Version20250123230226.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250123230226 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE parent_directory ADD COLUMN is_public BOOLEAN NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TEMPORARY TABLE __temp__parent_directory AS SELECT id, name, owner_role, user_created_id FROM parent_directory');
|
||||||
|
$this->addSql('DROP TABLE parent_directory');
|
||||||
|
$this->addSql('CREATE TABLE parent_directory (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, owner_role VARCHAR(255) NOT NULL, user_created_id INTEGER NOT NULL, CONSTRAINT FK_B7336B34F987D8A8 FOREIGN KEY (user_created_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||||
|
$this->addSql('INSERT INTO parent_directory (id, name, owner_role, user_created_id) SELECT id, name, owner_role, user_created_id FROM __temp__parent_directory');
|
||||||
|
$this->addSql('DROP TABLE __temp__parent_directory');
|
||||||
|
$this->addSql('CREATE INDEX IDX_B7336B34F987D8A8 ON parent_directory (user_created_id)');
|
||||||
|
}
|
||||||
|
}
|
32
migrations/Version20250126120344.php
Normal file
32
migrations/Version20250126120344.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250126120344 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE parent_directory_permission (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, role VARCHAR(255) NOT NULL, read BOOLEAN NOT NULL, write BOOLEAN NOT NULL, parent_directory_id INTEGER NOT NULL, CONSTRAINT FK_F93986627CFA5BB1 FOREIGN KEY (parent_directory_id) REFERENCES parent_directory (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_F93986627CFA5BB1 ON parent_directory_permission (parent_directory_id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('DROP TABLE parent_directory_permission');
|
||||||
|
}
|
||||||
|
}
|
297
package-lock.json
generated
Normal file
297
package-lock.json
generated
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
{
|
||||||
|
"name": "Kumora",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"flowbite": "^3.1.2",
|
||||||
|
"tailwindcss": "^4.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@popperjs/core": {
|
||||||
|
"version": "2.11.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||||
|
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/popperjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-node-resolve": {
|
||||||
|
"version": "15.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
|
||||||
|
"integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"@types/resolve": "1.20.2",
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"is-module": "^1.0.0",
|
||||||
|
"resolve": "^1.22.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/pluginutils": {
|
||||||
|
"version": "5.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
|
||||||
|
"integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.0",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"picomatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/estree": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/resolve": {
|
||||||
|
"version": "1.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||||
|
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/estree-walker": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/flowbite": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-MkwSgbbybCYgMC+go6Da5idEKUFfMqc/AmSjm/2ZbdmvoKf5frLPq/eIhXc9P+rC8t9boZtUXzHDgt5whZ6A/Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.9.3",
|
||||||
|
"flowbite-datepicker": "^1.3.1",
|
||||||
|
"mini-svg-data-uri": "^1.4.3",
|
||||||
|
"postcss": "^8.5.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/flowbite-datepicker": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"flowbite": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/flowbite-datepicker/node_modules/flowbite": {
|
||||||
|
"version": "2.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.5.2.tgz",
|
||||||
|
"integrity": "sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.9.3",
|
||||||
|
"flowbite-datepicker": "^1.3.0",
|
||||||
|
"mini-svg-data-uri": "^1.4.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-core-module": {
|
||||||
|
"version": "2.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-module": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/mini-svg-data-uri": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||||
|
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"mini-svg-data-uri": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nanoid": {
|
||||||
|
"version": "3.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||||
|
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-parse": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/picocolors": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/picomatch": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss": {
|
||||||
|
"version": "8.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||||
|
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"nanoid": "^3.3.8",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"source-map-js": "^1.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/resolve": {
|
||||||
|
"version": "1.22.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
|
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-core-module": "^2.16.0",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"resolve": "bin/resolve"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/source-map-js": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwindcss": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.8.tgz",
|
||||||
|
"integrity": "sha512-Me7N5CKR+D2A1xdWA5t5+kjjT7bwnxZOE6/yDI/ixJdJokszsn2n++mdU5yJwrsTpqFX2B9ZNMBJDwcqk9C9lw==",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
package.json
Normal file
6
package.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"flowbite": "^3.1.2",
|
||||||
|
"tailwindcss": "^4.0.8"
|
||||||
|
}
|
||||||
|
}
|
7
phpstan-baseline.neon
Normal file
7
phpstan-baseline.neon
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
parameters:
|
||||||
|
ignoreErrors:
|
||||||
|
-
|
||||||
|
message: '#^Property App\\Entity\\ResetPasswordRequest\:\:\$id \(int\|null\) is never assigned int so it can be removed from the property type\.$#'
|
||||||
|
identifier: property.unusedType
|
||||||
|
count: 1
|
||||||
|
path: src/Entity/ResetPasswordRequest.php
|
21
phpstan.dist.neon
Normal file
21
phpstan.dist.neon
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
includes:
|
||||||
|
- phpstan-baseline.neon
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
level: 5
|
||||||
|
paths:
|
||||||
|
- src
|
||||||
|
excludePaths:
|
||||||
|
- tests
|
||||||
|
symfony:
|
||||||
|
containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml
|
||||||
|
strictRules:
|
||||||
|
noVariableVariables: false
|
||||||
|
ignoreErrors:
|
||||||
|
-
|
||||||
|
identifier: property.unusedType
|
||||||
|
|
||||||
|
|
||||||
|
reportUnmatchedIgnoredErrors: false
|
||||||
|
treatPhpDocTypesAsCertain: false
|
||||||
|
|
@ -1,9 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Kernel;
|
use App\Kernel;
|
||||||
|
|
||||||
require_once dirname(__DIR__) . '/vendor/autoload_runtime.php';
|
require_once dirname(__DIR__) . '/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
return function (array $context) {
|
return static fn (array $context) => new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
|
||||||
};
|
|
||||||
|
32
rector.php
Normal file
32
rector.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
|
||||||
|
use Rector\Config\RectorConfig;
|
||||||
|
use Rector\Doctrine\Set\DoctrineSetList;
|
||||||
|
use Rector\Set\ValueObject\LevelSetList;
|
||||||
|
use Rector\Set\ValueObject\SetList;
|
||||||
|
use Rector\Symfony\Set\SymfonySetList;
|
||||||
|
|
||||||
|
return static function (RectorConfig $rectorConfig): void {
|
||||||
|
$rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml');
|
||||||
|
|
||||||
|
$rectorConfig->sets([
|
||||||
|
LevelSetList::UP_TO_PHP_82,
|
||||||
|
SymfonySetList::SYMFONY_71,
|
||||||
|
SymfonySetList::SYMFONY_CODE_QUALITY,
|
||||||
|
SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION,
|
||||||
|
SetList::DEAD_CODE,
|
||||||
|
SetList::CODE_QUALITY,
|
||||||
|
SetList::PHP_82,
|
||||||
|
DoctrineSetList::DOCTRINE_CODE_QUALITY,
|
||||||
|
]);
|
||||||
|
$rectorConfig->paths([
|
||||||
|
__DIR__ . '/src',
|
||||||
|
__DIR__ . '/tests',
|
||||||
|
__DIR__ . '/migrations',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
|
||||||
|
};
|
@ -1,8 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Command;
|
namespace App\Command;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
use App\Enum\RoleEnum;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
@ -24,40 +27,40 @@ class CreateUserCommand extends Command
|
|||||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
private readonly EntityManagerInterface $entityManager,
|
private readonly EntityManagerInterface $entityManager,
|
||||||
private readonly UserRepository $userRepository
|
private readonly UserRepository $userRepository
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
$io = new SymfonyStyle($input, $output);
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$fullname = $io->ask('Nom de l\'utilisateur');
|
||||||
$email = $io->ask('Email de l\'utilisateur');
|
$email = $io->ask('Email de l\'utilisateur');
|
||||||
$password = $io->askHidden('Mot de passe de l\'utilisateur');
|
$password = $io->askHidden('Mot de passe de l\'utilisateur');
|
||||||
$isAdmin = $io->confirm('Est-ce un administrateur ?');
|
$isAdmin = $io->confirm('Est-ce un administrateur ?');
|
||||||
|
$folderRole = $io->choice('Groupe', array_map(static fn ($role) => $role->value, RoleEnum::cases()), RoleEnum::VISITEUR->value);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$user = $this->userRepository->findOneBy(['email' => $email]);
|
$user = $this->userRepository->findOneBy(['email' => $email]);
|
||||||
if ($user) {
|
if (null !== $user) {
|
||||||
$io->error('Un utilisateur existe déjà avec cet email');
|
$io->error('Un utilisateur existe déjà avec cet email');
|
||||||
return Command::FAILURE;
|
return Command::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = new User();
|
$user = new User();
|
||||||
|
$user->setFullname($fullname);
|
||||||
$user->setEmail($email);
|
$user->setEmail($email);
|
||||||
$user->setPassword($this->passwordHasher->hashPassword($user, $password));
|
$user->setPassword($this->passwordHasher->hashPassword($user, $password));
|
||||||
$user->setRoles($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER']);
|
$user->setRoles($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER']);
|
||||||
$user->initId();
|
$user->setFolderRole(RoleEnum::from($folderRole));
|
||||||
|
|
||||||
$this->entityManager->persist($user);
|
$this->entityManager->persist($user);
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
$io->success('Utilisateur créé avec succès');
|
$io->success('Utilisateur créé avec succès');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$io->error('Une erreur est survenue lors de la création de l\'utilisateur');
|
$io->error('Une erreur est survenue lors de la création de l\'utilisateur : ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
@ -40,13 +42,12 @@ class AdminController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[Route('/users/create', name: 'user_create')]
|
#[Route('/users/create', name: 'user_create')]
|
||||||
#[Route('/users/edit/{user}', name: 'user_edit')]
|
#[Route('/users/edit/{user}', name: 'user_edit')]
|
||||||
public function editUsers(#[MapEntity(id: 'user')] ?User $user, Request $request): Response
|
public function editUsers(#[MapEntity(id: 'user')] ?User $user, Request $request): Response
|
||||||
{
|
{
|
||||||
$isNew = false;
|
$isNew = false;
|
||||||
if (!$user) {
|
if (!$user instanceof \App\Entity\User) {
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$isNew = true;
|
$isNew = true;
|
||||||
}
|
}
|
||||||
@ -58,7 +59,6 @@ class AdminController extends AbstractController
|
|||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$role = $form->get('role')->getData();
|
$role = $form->get('role')->getData();
|
||||||
$user->setRoles([$role]);
|
$user->setRoles([$role]);
|
||||||
$user->initId();
|
|
||||||
|
|
||||||
if ($form->has('plainPassword')) {
|
if ($form->has('plainPassword')) {
|
||||||
$plainPassword = $form->get('plainPassword')->getData();
|
$plainPassword = $form->get('plainPassword')->getData();
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\ParentDirectory;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Enum\RoleEnum;
|
||||||
use App\Form\CreateDirectoryType;
|
use App\Form\CreateDirectoryType;
|
||||||
|
use App\Form\FilePermissionType;
|
||||||
use App\Form\RenameType;
|
use App\Form\RenameType;
|
||||||
use App\Form\UploadType;
|
use App\Form\UploadType;
|
||||||
|
use App\Repository\ParentDirectoryRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use League\Flysystem\Filesystem;
|
use League\Flysystem\Filesystem;
|
||||||
use League\Flysystem\FilesystemException;
|
use League\Flysystem\FilesystemException;
|
||||||
|
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use Symfony\Component\HttpFoundation\HeaderUtils;
|
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||||
@ -20,35 +29,71 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
|||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
#[Route('/files', 'app_files_')]
|
#[Route('/files', 'app_files_')]
|
||||||
#[IsGranted('ROLE_USER')]
|
|
||||||
class FilesController extends AbstractController
|
class FilesController extends AbstractController
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly ParentDirectoryRepository $parentDirectoryRepository,
|
||||||
|
private readonly Filesystem $defaultAdapter,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws FilesystemException
|
* @throws FilesystemException
|
||||||
*/
|
*/
|
||||||
#[Route('/', name: 'index')]
|
#[Route('/', name: 'index')]
|
||||||
public function index(Filesystem $defaultAdapter, UrlGeneratorInterface $urlGenerator, #[MapQueryParameter('path')] string $path = ''): Response
|
#[IsGranted('ROLE_USER')]
|
||||||
|
public function index(UrlGeneratorInterface $urlGenerator, #[MapQueryParameter('path')] string $path = ''): Response
|
||||||
{
|
{
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
|
$this->getUser();
|
||||||
|
if ('' !== $path) {
|
||||||
|
$pathExploded = explode('/', $path);
|
||||||
|
|
||||||
if ($path !== '' && !$defaultAdapter->directoryExists($path)) {
|
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $pathExploded[0]]);
|
||||||
|
|
||||||
|
if (null === $parentDir || !$this->defaultAdapter->directoryExists($path)) {
|
||||||
throw $this->createNotFoundException("Ce dossier n'existe pas !");
|
throw $this->createNotFoundException("Ce dossier n'existe pas !");
|
||||||
}
|
}
|
||||||
|
|
||||||
$files = $defaultAdapter->listContents('/' . $path);
|
if (!$this->isGranted('file_read', $parentDir)) {
|
||||||
|
throw $this->createNotFoundException("Vous n'avez pas le droit d'accéder à ce dossier !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = $this->defaultAdapter->listContents('/' . $path);
|
||||||
|
|
||||||
$realFiles = [];
|
$realFiles = [];
|
||||||
|
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
$filename = basename($file['path']);
|
$filename = basename((string) $file['path']);
|
||||||
if (!str_starts_with($filename, '.')) {
|
if (!str_starts_with($filename, '.')) {
|
||||||
|
// On vérifie si l'utilisateur a le droit d'accéder au fichier (vérifier que owner_role du parentDirectory correspondant est bien le folderRole de l'utilisateur)
|
||||||
|
$pathFile = explode('/', (string) $file['path']);
|
||||||
|
if ('' !== $path) {
|
||||||
|
$parentDirectory = $this->parentDirectoryRepository->findOneBy(['name' => $pathFile[0]]);
|
||||||
|
|
||||||
|
if (null === $parentDirectory || !$this->isGranted('file_read', $parentDirectory)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} elseif ('file' !== $file['type']) {
|
||||||
|
$parentDirectory = $this->parentDirectoryRepository->findOneBy(['name' => $filename]);
|
||||||
|
|
||||||
|
if (null === $parentDirectory || !$this->isGranted('file_read', $parentDirectory)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$realFiles[] = [
|
$realFiles[] = [
|
||||||
'type' => $file['type'],
|
'type' => $file['type'],
|
||||||
'path' => $file['path'],
|
'path' => $file['path'],
|
||||||
'last_modified' => $file['lastModified'],
|
'last_modified' => $file['lastModified'],
|
||||||
'size' => $file['fileSize'] ?? null,
|
'size' => $file['fileSize'] ?? $this->calculateSize($file),
|
||||||
'url' => $file['type'] === 'file'
|
'url' => 'file' === $file['type']
|
||||||
? $this->generateUrl('app_files_app_file_proxy', ['filename' => $file['path']], UrlGeneratorInterface::ABSOLUTE_URL)
|
? $this->generateUrl('app_files_app_file_proxy', ['filename' => $file['path'], 'preview' => false], UrlGeneratorInterface::ABSOLUTE_URL)
|
||||||
|
: $this->generateUrl('app_files_index', ['path' => $file['path']]),
|
||||||
|
'previewUrl' => 'file' === $file['type']
|
||||||
|
? $this->generateUrl('app_files_app_file_proxy', ['filename' => $file['path'], 'preview' => true], UrlGeneratorInterface::ABSOLUTE_URL)
|
||||||
: $this->generateUrl('app_files_index', ['path' => $file['path']]),
|
: $this->generateUrl('app_files_index', ['path' => $file['path']]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -58,26 +103,42 @@ class FilesController extends AbstractController
|
|||||||
usort($realFiles, static function ($a, $b) {
|
usort($realFiles, static function ($a, $b) {
|
||||||
if ($a['type'] === $b['type']) {
|
if ($a['type'] === $b['type']) {
|
||||||
return $a['path'] <=> $b['path'];
|
return $a['path'] <=> $b['path'];
|
||||||
} else {
|
|
||||||
return $a['type'] <=> $b['type'];
|
|
||||||
}
|
}
|
||||||
|
return $a['type'] <=> $b['type'];
|
||||||
});
|
});
|
||||||
|
|
||||||
return $this->render('files/index.html.twig', [
|
return $this->render('files/index.html.twig', [
|
||||||
'files' => $realFiles,
|
'files' => $realFiles,
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
|
'parentDir' => $parentDir ?? null,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws FilesystemException
|
||||||
|
*/
|
||||||
#[Route('/file-proxy', name: 'app_file_proxy')]
|
#[Route('/file-proxy', name: 'app_file_proxy')]
|
||||||
public function fileProxy(Filesystem $defaultAdapter, #[MapQueryParameter('filename')]string $filename)
|
public function fileProxy(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename, #[MapQueryParameter('preview')] bool $preview)
|
||||||
{
|
{
|
||||||
$file = $this->normalizePath($filename);
|
$file = $this->normalizePath($filename);
|
||||||
|
|
||||||
|
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => explode('/', $file)[0]]);
|
||||||
|
|
||||||
|
if (null === $parentDir) {
|
||||||
|
throw $this->createNotFoundException("Vous n'avez pas le droit d'accéder à ce fichier !");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si l'owner role sur le parent est visiteur, on peut accéder au fichier sans être connecté
|
||||||
|
if (!$this->isGranted('file_read', $parentDir)) {
|
||||||
|
throw $this->createNotFoundException("Vous n'avez pas le droit d'accéder à ce fichier !");
|
||||||
|
}
|
||||||
|
|
||||||
$mimetype = $defaultAdapter->mimeType($file);
|
$mimetype = $defaultAdapter->mimeType($file);
|
||||||
if ($mimetype === '') {
|
if ('' === $mimetype) {
|
||||||
$mimetype = 'application/octet-stream';
|
$mimetype = 'application/octet-stream';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$response = new StreamedResponse(static function () use ($file, $defaultAdapter): void {
|
$response = new StreamedResponse(static function () use ($file, $defaultAdapter): void {
|
||||||
$outputStream = fopen('php://output', 'w');
|
$outputStream = fopen('php://output', 'w');
|
||||||
$fileStream = $defaultAdapter->readStream($file);
|
$fileStream = $defaultAdapter->readStream($file);
|
||||||
@ -85,11 +146,14 @@ class FilesController extends AbstractController
|
|||||||
});
|
});
|
||||||
|
|
||||||
$response->headers->set('Content-Type', $mimetype);
|
$response->headers->set('Content-Type', $mimetype);
|
||||||
|
|
||||||
|
if (!$preview) {
|
||||||
$disposition = HeaderUtils::makeDisposition(
|
$disposition = HeaderUtils::makeDisposition(
|
||||||
HeaderUtils::DISPOSITION_ATTACHMENT,
|
HeaderUtils::DISPOSITION_ATTACHMENT,
|
||||||
basename($file)
|
basename($file)
|
||||||
);
|
);
|
||||||
$response->headers->set('Content-Disposition', $disposition);
|
$response->headers->set('Content-Disposition', $disposition);
|
||||||
|
}
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
@ -98,11 +162,24 @@ class FilesController extends AbstractController
|
|||||||
* @throws FilesystemException
|
* @throws FilesystemException
|
||||||
*/
|
*/
|
||||||
#[Route('/file-delete', name: 'delete')]
|
#[Route('/file-delete', name: 'delete')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
public function fileDelete(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename): RedirectResponse
|
public function fileDelete(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename): RedirectResponse
|
||||||
{
|
{
|
||||||
|
$this->getUser();
|
||||||
$file = $this->normalizePath($filename);
|
$file = $this->normalizePath($filename);
|
||||||
|
|
||||||
if ($file !== '' && !str_starts_with($file, '.') && $defaultAdapter->fileExists($file)) {
|
$realPath = explode('/', $file);
|
||||||
|
$parentDir = null;
|
||||||
|
|
||||||
|
if (count($realPath) > 1) {
|
||||||
|
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
|
||||||
|
|
||||||
|
if (null === $parentDir || !$this->isGranted('file_write', $parentDir)) {
|
||||||
|
throw $this->createNotFoundException("Vous n'avez pas le droit de supprimer ce fichier !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' !== $file && !str_starts_with($file, '.') && $defaultAdapter->fileExists($file)) {
|
||||||
$defaultAdapter->delete($file);
|
$defaultAdapter->delete($file);
|
||||||
|
|
||||||
$this->addFlash('success', 'Le fichier a bien été supprimé.');
|
$this->addFlash('success', 'Le fichier a bien été supprimé.');
|
||||||
@ -119,21 +196,39 @@ class FilesController extends AbstractController
|
|||||||
* @throws FilesystemException
|
* @throws FilesystemException
|
||||||
*/
|
*/
|
||||||
#[Route('/directory-delete', name: 'delete_directory')]
|
#[Route('/directory-delete', name: 'delete_directory')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
public function directoryDelete(Filesystem $defaultAdapter, #[MapQueryParameter('path')] string $path): RedirectResponse
|
public function directoryDelete(Filesystem $defaultAdapter, #[MapQueryParameter('path')] string $path): RedirectResponse
|
||||||
{
|
{
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
|
$this->getUser();
|
||||||
|
|
||||||
|
$realPath = explode('/', $path);
|
||||||
|
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
|
||||||
|
|
||||||
if ($path !== '' && !str_starts_with($path, '.') && $defaultAdapter->directoryExists($path)) {
|
if (null === $parentDir || !$this->isGranted('file_write', $parentDir)) {
|
||||||
|
throw $this->createNotFoundException("Vous n'avez pas le droit de supprimer ce dossier !");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' !== $path && !str_starts_with($path, '.') && $defaultAdapter->directoryExists($path)) {
|
||||||
$defaultAdapter->deleteDirectory($path);
|
$defaultAdapter->deleteDirectory($path);
|
||||||
|
if ($parentDir->getName() === $path) {
|
||||||
|
$this->entityManager->remove($parentDir);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
$this->addFlash('success', 'Le dossier a bien été supprimé.');
|
$this->addFlash('success', 'Le dossier a bien été supprimé.');
|
||||||
} else {
|
} else {
|
||||||
$this->addFlash('error', 'Le dossier n\'existe pas.');
|
$this->addFlash('error', 'Le dossier n\'existe pas.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$newPath = dirname($path);
|
||||||
|
|
||||||
|
if ('.' === $newPath) {
|
||||||
|
$newPath = '';
|
||||||
|
}
|
||||||
|
|
||||||
return $this->redirectToRoute('app_files_index', [
|
return $this->redirectToRoute('app_files_index', [
|
||||||
'path' => dirname($path),
|
'path' => $newPath,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,14 +236,26 @@ class FilesController extends AbstractController
|
|||||||
* @throws FilesystemException
|
* @throws FilesystemException
|
||||||
*/
|
*/
|
||||||
#[Route('/rename', name: 'rename')]
|
#[Route('/rename', name: 'rename')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
public function rename(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response
|
public function rename(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response
|
||||||
{
|
{
|
||||||
$filepath = $this->normalizePath($filepath);
|
$filepath = $this->normalizePath($filepath);
|
||||||
|
$this->getUser();
|
||||||
|
|
||||||
if ($filepath === '' || str_starts_with($filepath, '.') || !$defaultAdapter->fileExists($filepath)) {
|
if ('' === $filepath || str_starts_with($filepath, '.') || !$defaultAdapter->fileExists($filepath)) {
|
||||||
throw $this->createNotFoundException("Ce fichier n'existe pas !");
|
throw $this->createNotFoundException("Ce fichier n'existe pas !");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$realPath = explode('/', $filepath);
|
||||||
|
|
||||||
|
if (count($realPath) > 1) {
|
||||||
|
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
|
||||||
|
|
||||||
|
if (null === $parentDir || !$this->isGranted('file_write', $parentDir)) {
|
||||||
|
throw $this->createNotFoundException("Vous n'avez pas le droit de renommer ce fichier !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'newName' => pathinfo($filepath, PATHINFO_BASENAME),
|
'newName' => pathinfo($filepath, PATHINFO_BASENAME),
|
||||||
];
|
];
|
||||||
@ -183,9 +290,22 @@ class FilesController extends AbstractController
|
|||||||
* @throws FilesystemException
|
* @throws FilesystemException
|
||||||
*/
|
*/
|
||||||
#[Route('/create-directory', name: 'create_directory')]
|
#[Route('/create-directory', name: 'create_directory')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
public function createDirectory(Request $request, Filesystem $defaultAdapter, #[MapQueryParameter('base')] string $basePath): Response
|
public function createDirectory(Request $request, Filesystem $defaultAdapter, #[MapQueryParameter('base')] string $basePath): Response
|
||||||
{
|
{
|
||||||
$basePath = $this->normalizePath($basePath);
|
$basePath = $this->normalizePath($basePath);
|
||||||
|
$realPath = explode('/', $basePath);
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
if (count($realPath) > 1) {
|
||||||
|
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
|
||||||
|
if (null === $parentDir || !$this->isGranted('file_write', $parentDir)) {
|
||||||
|
throw $this->createNotFoundException("Vous n'avez pas le droit de créer de sous-dossier dans ce dossier !");
|
||||||
|
}
|
||||||
|
}
|
||||||
$form = $this->createForm(CreateDirectoryType::class);
|
$form = $this->createForm(CreateDirectoryType::class);
|
||||||
|
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
@ -195,10 +315,38 @@ class FilesController extends AbstractController
|
|||||||
|
|
||||||
$name = $data['name'];
|
$name = $data['name'];
|
||||||
|
|
||||||
|
if (count(explode('/', (string) $name)) > 1) {
|
||||||
|
$name = explode('/', (string) $name)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($defaultAdapter->directoryExists($basePath . '/' . $name)) {
|
||||||
|
$this->addFlash('error', 'Le dossier existe déjà.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_files_index', [
|
||||||
|
'path' => $basePath,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$defaultAdapter->createDirectory($basePath . '/' . $name);
|
$defaultAdapter->createDirectory($basePath . '/' . $name);
|
||||||
|
|
||||||
$defaultAdapter->write($basePath . '/' . $name . '/.gitkeep', '');
|
$defaultAdapter->write($basePath . '/' . $name . '/.gitkeep', '');
|
||||||
|
|
||||||
|
// si basePath est vide, on crée un parentDirectory
|
||||||
|
if ('' === $basePath) {
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->getUser();
|
||||||
|
$parentDirectory = new ParentDirectory();
|
||||||
|
$parentDirectory->setName($name);
|
||||||
|
$parentDirectory->setOwnerRole($user->getFolderRole());
|
||||||
|
$parentDirectory->setIsPublic(false);
|
||||||
|
$parentDirectory->setUserCreated($user);
|
||||||
|
|
||||||
|
$this->entityManager->persist($parentDirectory);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
$this->addFlash('success', 'Le dossier a bien été créé.');
|
$this->addFlash('success', 'Le dossier a bien été créé.');
|
||||||
|
|
||||||
return $this->redirectToRoute('app_files_index', [
|
return $this->redirectToRoute('app_files_index', [
|
||||||
@ -216,11 +364,19 @@ class FilesController extends AbstractController
|
|||||||
* @throws FilesystemException
|
* @throws FilesystemException
|
||||||
*/
|
*/
|
||||||
#[Route('/rename-directory', name: 'rename-directory')]
|
#[Route('/rename-directory', name: 'rename-directory')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
public function renameDirectory(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response
|
public function renameDirectory(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response
|
||||||
{
|
{
|
||||||
$filepath = $this->normalizePath($filepath);
|
$filepath = $this->normalizePath($filepath);
|
||||||
|
|
||||||
if ($filepath === '' || str_starts_with($filepath, '.') || !$defaultAdapter->directoryExists($filepath)) {
|
$realPath = explode('/', $filepath);
|
||||||
|
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
|
||||||
|
|
||||||
|
if (null === $parentDir || !$this->isGranted('file_write', $parentDir)) {
|
||||||
|
throw $this->createNotFoundException("Vous n'avez pas le droit de renommer ce dossier !");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' === $filepath || str_starts_with($filepath, '.') || !$defaultAdapter->directoryExists($filepath)) {
|
||||||
throw $this->createNotFoundException("Ce dossier n'existe pas !");
|
throw $this->createNotFoundException("Ce dossier n'existe pas !");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,6 +394,13 @@ class FilesController extends AbstractController
|
|||||||
|
|
||||||
$newPath = dirname($filepath) . '/' . $newName;
|
$newPath = dirname($filepath) . '/' . $newName;
|
||||||
|
|
||||||
|
// Si c'est un parent directory, on renomme le parent directory dans la base de données
|
||||||
|
if ($parentDir->getName() === $filepath) {
|
||||||
|
$parentDir->setName($newName);
|
||||||
|
$this->entityManager->persist($parentDir);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
$defaultAdapter->move($filepath, $newPath);
|
$defaultAdapter->move($filepath, $newPath);
|
||||||
|
|
||||||
$this->addFlash('success', 'Le dossier a bien été renommé.');
|
$this->addFlash('success', 'Le dossier a bien été renommé.');
|
||||||
@ -258,13 +421,27 @@ class FilesController extends AbstractController
|
|||||||
* @throws FilesystemException
|
* @throws FilesystemException
|
||||||
*/
|
*/
|
||||||
#[Route('/upload', name: 'upload')]
|
#[Route('/upload', name: 'upload')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
public function upload(#[MapQueryParameter('path')] string $path, Request $request, Filesystem $defaultAdapter): Response
|
public function upload(#[MapQueryParameter('path')] string $path, Request $request, Filesystem $defaultAdapter): Response
|
||||||
{
|
{
|
||||||
$path = $this->normalizePath($path);
|
$path = $this->normalizePath($path);
|
||||||
|
|
||||||
|
$this->getUser();
|
||||||
|
|
||||||
|
if ('' === $path) {
|
||||||
|
throw $this->createNotFoundException("Vous ne pouvez pas téléverser de fichier à la racine !");
|
||||||
|
}
|
||||||
|
|
||||||
|
$realPath = explode('/', $path);
|
||||||
|
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
|
||||||
|
|
||||||
|
if (null === $parentDir || !$this->isGranted('file_write', $parentDir)) {
|
||||||
|
throw $this->createNotFoundException("Vous n'avez pas le droit de téléverser des fichiers dans ce dossier !");
|
||||||
|
}
|
||||||
|
|
||||||
$form = $this->createForm(UploadType::class);
|
$form = $this->createForm(UploadType::class);
|
||||||
|
|
||||||
if ($path !== '' && !$defaultAdapter->directoryExists($path)) {
|
if (!$defaultAdapter->directoryExists($path)) {
|
||||||
throw $this->createNotFoundException("Ce dossier n'existe pas !");
|
throw $this->createNotFoundException("Ce dossier n'existe pas !");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,8 +478,70 @@ class FilesController extends AbstractController
|
|||||||
$path = trim($path, '/');
|
$path = trim($path, '/');
|
||||||
// On retire les chemins relatifs
|
// On retire les chemins relatifs
|
||||||
$path = str_replace('..', '', $path);
|
$path = str_replace('..', '', $path);
|
||||||
$path = str_replace('//', '/', $path);
|
// On retire les . qui sont seul dans la chaîne, en vérifiant qu'il n'y a pas de lettre avant ou après
|
||||||
|
$path = preg_replace('/(?<!\w)\.(?!\w)/', '', $path);
|
||||||
|
|
||||||
return $path;
|
return str_replace('//', '/', $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/file-edit-permission/{parentDir}', name: 'file_edit_permission')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
|
public function fileRead(#[MapEntity(mapping: ['parentDir' => 'name'])] ParentDirectory $parentDir, Request $request): Response
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
// 2 possibilités : soit l'utilisateur est le créateur du dossier, soit le dossier est public et l'utilisateur a le role Conseil d'administration
|
||||||
|
// Si ce n'est pas le cas, on redirige vers la page d'accueil
|
||||||
|
if ($parentDir->getUserCreated() !== $user) {
|
||||||
|
if ($parentDir->isPublic() && RoleEnum::CONSEIL_ADMINISTRATION !== $user->getFolderRole()) {
|
||||||
|
$this->addFlash('error', 'Vous n\'avez pas le droit de modifier les permissions de ce dossier.');
|
||||||
|
return $this->redirectToRoute('app_files_index');
|
||||||
|
} elseif (!$parentDir->isPublic()) {
|
||||||
|
$this->addFlash('error', 'Vous n\'avez pas le droit de modifier les permissions de ce dossier.');
|
||||||
|
return $this->redirectToRoute('app_files_index');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = $this->createForm(FilePermissionType::class, $parentDir);
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$datas = $form->getData();
|
||||||
|
|
||||||
|
foreach ($datas->getParentDirectoryPermissions() as $parentPerm) {
|
||||||
|
$this->entityManager->persist($parentPerm);
|
||||||
|
}
|
||||||
|
$this->entityManager->persist($datas);
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Les permissions ont bien été modifiées.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_files_index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('files/file_edit.html.twig', [
|
||||||
|
'parentDir' => $parentDir,
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calculateSize($file): int
|
||||||
|
{
|
||||||
|
$folderPath = $file['path'];
|
||||||
|
// On récupère tout les fichiers dans le dossier
|
||||||
|
$files = $this->defaultAdapter->listContents($folderPath, true);
|
||||||
|
|
||||||
|
$size = 0;
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$size += $file['fileSize'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
108
src/Controller/ProfileController.php
Normal file
108
src/Controller/ProfileController.php
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\DTO\EmailDTO;
|
||||||
|
use App\DTO\PasswordDTO;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Form\EmailFormType;
|
||||||
|
use App\Form\PasswordFormType;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
|
class ProfileController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/profile', name: 'app_profile')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
return $this->render('profile/index.html.twig', [
|
||||||
|
'user' => $user,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/profile/edit/email', name: 'app_profile_email_edit')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
|
public function editEmail(Request $request): Response
|
||||||
|
{
|
||||||
|
$emailDTO = new EmailDTO();
|
||||||
|
$form = $this->createForm(EmailFormType::class, $emailDTO);
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
if ($this->passwordHasher->isPasswordValid($user, $emailDTO->password)) {
|
||||||
|
$user->setEmail($emailDTO->email);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Votre adresse email a bien été modifiée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addFlash('error', 'Le mot de passe est incorrect.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('profile/edit_email.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[Route('/profile/edit/password', name: 'app_profile_password_edit')]
|
||||||
|
#[IsGranted('ROLE_USER')]
|
||||||
|
public function editPassword(Request $request): Response
|
||||||
|
{
|
||||||
|
$passwordDTO = new PasswordDTO();
|
||||||
|
$form = $this->createForm(PasswordFormType::class, $passwordDTO);
|
||||||
|
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
if ($this->passwordHasher->isPasswordValid($user, $passwordDTO->password)) {
|
||||||
|
$user->setPassword($this->passwordHasher->hashPassword($user, $passwordDTO->newPassword));
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Votre mot de passe a bien été modifiée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_profile');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addFlash('error', 'Le mot de passe est incorrect.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('profile/edit_password.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
181
src/Controller/ResetPasswordController.php
Normal file
181
src/Controller/ResetPasswordController.php
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Form\ChangePasswordFormType;
|
||||||
|
use App\Form\ResetPasswordRequestFormType;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Mailer\MailerInterface;
|
||||||
|
use Symfony\Component\Mime\Address;
|
||||||
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
|
||||||
|
|
||||||
|
#[Route('/reset-password')]
|
||||||
|
class ResetPasswordController extends AbstractController
|
||||||
|
{
|
||||||
|
use ResetPasswordControllerTrait;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private ResetPasswordHelperInterface $resetPasswordHelper,
|
||||||
|
private EntityManagerInterface $entityManager
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display & process form to request a password reset.
|
||||||
|
*/
|
||||||
|
#[Route('', name: 'app_forgot_password_request')]
|
||||||
|
public function request(Request $request, MailerInterface $mailer, TranslatorInterface $translator): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(ResetPasswordRequestFormType::class);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
/** @var string $email */
|
||||||
|
$email = $form->get('email')->getData();
|
||||||
|
|
||||||
|
return $this->processSendingPasswordResetEmail(
|
||||||
|
$email,
|
||||||
|
$mailer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('reset_password/request.html.twig', [
|
||||||
|
'requestForm' => $form,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirmation page after a user has requested a password reset.
|
||||||
|
*/
|
||||||
|
#[Route('/check-email', name: 'app_check_email')]
|
||||||
|
public function checkEmail(): Response
|
||||||
|
{
|
||||||
|
// Generate a fake token if the user does not exist or someone hit this page directly.
|
||||||
|
// This prevents exposing whether or not a user was found with the given email address or not
|
||||||
|
if (!$resetToken = $this->getTokenObjectFromSession() instanceof \SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordToken) {
|
||||||
|
$resetToken = $this->resetPasswordHelper->generateFakeResetToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('reset_password/check_email.html.twig', [
|
||||||
|
'resetToken' => $resetToken,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates and process the reset URL that the user clicked in their email.
|
||||||
|
*/
|
||||||
|
#[Route('/reset/{token}', name: 'app_reset_password')]
|
||||||
|
public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, string $token = null): Response
|
||||||
|
{
|
||||||
|
if (null !== $token) {
|
||||||
|
// We store the token in session and remove it from the URL, to avoid the URL being
|
||||||
|
// loaded in a browser and potentially leaking the token to 3rd party JavaScript.
|
||||||
|
$this->storeTokenInSession($token);
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_reset_password');
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->getTokenFromSession();
|
||||||
|
if (null === $token) {
|
||||||
|
throw $this->createNotFoundException('No reset password token found in the URL or in the session.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
|
||||||
|
} catch (ResetPasswordExceptionInterface $e) {
|
||||||
|
$this->addFlash('reset_password_error', sprintf(
|
||||||
|
'%s - %s',
|
||||||
|
$translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE, [], 'ResetPasswordBundle'),
|
||||||
|
$translator->trans($e->getReason(), [], 'ResetPasswordBundle')
|
||||||
|
));
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_forgot_password_request');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The token is valid; allow the user to change their password.
|
||||||
|
$form = $this->createForm(ChangePasswordFormType::class);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
// A password reset token should be used only once, remove it.
|
||||||
|
$this->resetPasswordHelper->removeResetRequest($token);
|
||||||
|
|
||||||
|
/** @var string $plainPassword */
|
||||||
|
$plainPassword = $form->get('plainPassword')->getData();
|
||||||
|
|
||||||
|
// Encode(hash) the plain password, and set it.
|
||||||
|
$user->setPassword($passwordHasher->hashPassword($user, $plainPassword));
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
// The session is cleaned up after the password has been changed.
|
||||||
|
$this->cleanSessionAfterReset();
|
||||||
|
|
||||||
|
$this->addFlash('reset_password_success', 'Votre mot de passe a été réinitialisé avec succès.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_home');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('reset_password/reset.html.twig', [
|
||||||
|
'resetForm' => $form,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = $this->entityManager->getRepository(User::class)->findOneBy([
|
||||||
|
'email' => $emailFormData,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Do not reveal whether a user account was found or not.
|
||||||
|
if (null === $user) {
|
||||||
|
return $this->redirectToRoute('app_check_email');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resetToken = $this->resetPasswordHelper->generateResetToken($user);
|
||||||
|
} catch (ResetPasswordExceptionInterface) {
|
||||||
|
// If you want to tell the user why a reset email was not sent, uncomment
|
||||||
|
// the lines below and change the redirect to 'app_forgot_password_request'.
|
||||||
|
// Caution: This may reveal if a user is registered or not.
|
||||||
|
//
|
||||||
|
// $this->addFlash('reset_password_error', sprintf(
|
||||||
|
// '%s - %s',
|
||||||
|
// $translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE, [], 'ResetPasswordBundle'),
|
||||||
|
// $translator->trans($e->getReason(), [], 'ResetPasswordBundle')
|
||||||
|
// ));
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_check_email');
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = (new TemplatedEmail())
|
||||||
|
->from(new Address('contact@camelia-studio.org', 'Camélia Studio'))
|
||||||
|
->to((string) $user->getEmail())
|
||||||
|
->subject('Kumora - Votre demande de réinitialisation de mot de passe')
|
||||||
|
->htmlTemplate('reset_password/email.html.twig')
|
||||||
|
->context([
|
||||||
|
'resetToken' => $resetToken,
|
||||||
|
])
|
||||||
|
;
|
||||||
|
|
||||||
|
$mailer->send($email);
|
||||||
|
|
||||||
|
// Store the token object in session for retrieval in check-email route.
|
||||||
|
$this->setTokenObjectInSession($resetToken);
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_check_email');
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
|
||||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||||
|
|
||||||
class SecurityController extends AbstractController
|
class SecurityController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly UrlGeneratorInterface $urlGenerator)
|
public function __construct()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
15
src/DTO/EmailDTO.php
Normal file
15
src/DTO/EmailDTO.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\DTO;
|
||||||
|
|
||||||
|
class EmailDTO
|
||||||
|
{
|
||||||
|
|
||||||
|
public string $email;
|
||||||
|
public string $password;
|
||||||
|
public function __construct(
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
12
src/DTO/PasswordDTO.php
Normal file
12
src/DTO/PasswordDTO.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\DTO;
|
||||||
|
|
||||||
|
class PasswordDTO
|
||||||
|
{
|
||||||
|
public string $password;
|
||||||
|
public string $newPassword;
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
125
src/Entity/ParentDirectory.php
Normal file
125
src/Entity/ParentDirectory.php
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Enum\RoleEnum;
|
||||||
|
use App\Repository\ParentDirectoryRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: ParentDirectoryRepository::class)]
|
||||||
|
class ParentDirectory
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
private ?string $name = null;
|
||||||
|
|
||||||
|
#[ORM\Column(enumType: RoleEnum::class)]
|
||||||
|
private ?RoleEnum $ownerRole = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'parentDirectories')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?User $userCreated = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?bool $isPublic = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, ParentDirectoryPermission>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: ParentDirectoryPermission::class, mappedBy: 'parentDirectory', orphanRemoval: true)]
|
||||||
|
private Collection $parentDirectoryPermissions;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->parentDirectoryPermissions = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): ?string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name): static
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOwnerRole(): ?RoleEnum
|
||||||
|
{
|
||||||
|
return $this->ownerRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setOwnerRole(RoleEnum $ownerRole): static
|
||||||
|
{
|
||||||
|
$this->ownerRole = $ownerRole;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserCreated(): ?User
|
||||||
|
{
|
||||||
|
return $this->userCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUserCreated(?User $userCreated): static
|
||||||
|
{
|
||||||
|
$this->userCreated = $userCreated;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPublic(): ?bool
|
||||||
|
{
|
||||||
|
return $this->isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsPublic(bool $isPublic): static
|
||||||
|
{
|
||||||
|
$this->isPublic = $isPublic;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, ParentDirectoryPermission>
|
||||||
|
*/
|
||||||
|
public function getParentDirectoryPermissions(): Collection
|
||||||
|
{
|
||||||
|
return $this->parentDirectoryPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addParentDirectoryPermission(ParentDirectoryPermission $parentDirectoryPermission): static
|
||||||
|
{
|
||||||
|
if (!$this->parentDirectoryPermissions->contains($parentDirectoryPermission)) {
|
||||||
|
$this->parentDirectoryPermissions->add($parentDirectoryPermission);
|
||||||
|
$parentDirectoryPermission->setParentDirectory($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeParentDirectoryPermission(ParentDirectoryPermission $parentDirectoryPermission): static
|
||||||
|
{
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($this->parentDirectoryPermissions->removeElement($parentDirectoryPermission) && $parentDirectoryPermission->getParentDirectory() === $this) {
|
||||||
|
$parentDirectoryPermission->setParentDirectory(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
84
src/Entity/ParentDirectoryPermission.php
Normal file
84
src/Entity/ParentDirectoryPermission.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Enum\RoleEnum;
|
||||||
|
use App\Repository\ParentDirectoryPermissionRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: ParentDirectoryPermissionRepository::class)]
|
||||||
|
class ParentDirectoryPermission
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(enumType: RoleEnum::class)]
|
||||||
|
private ?RoleEnum $role = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?bool $read = null;
|
||||||
|
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?bool $write = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'parentDirectoryPermissions')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?ParentDirectory $parentDirectory = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRole(): ?RoleEnum
|
||||||
|
{
|
||||||
|
return $this->role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRole(RoleEnum $role): static
|
||||||
|
{
|
||||||
|
$this->role = $role;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isRead(): ?bool
|
||||||
|
{
|
||||||
|
return $this->read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRead(bool $read): static
|
||||||
|
{
|
||||||
|
$this->read = $read;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isWrite(): ?bool
|
||||||
|
{
|
||||||
|
return $this->write;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setWrite(bool $write): static
|
||||||
|
{
|
||||||
|
$this->write = $write;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParentDirectory(): ?ParentDirectory
|
||||||
|
{
|
||||||
|
return $this->parentDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setParentDirectory(?ParentDirectory $parentDirectory): static
|
||||||
|
{
|
||||||
|
$this->parentDirectory = $parentDirectory;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
38
src/Entity/ResetPasswordRequest.php
Normal file
38
src/Entity/ResetPasswordRequest.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\ResetPasswordRequestRepository;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestTrait;
|
||||||
|
|
||||||
|
#[ORM\Entity(repositoryClass: ResetPasswordRequestRepository::class)]
|
||||||
|
class ResetPasswordRequest implements ResetPasswordRequestInterface
|
||||||
|
{
|
||||||
|
use ResetPasswordRequestTrait;
|
||||||
|
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
public function __construct(#[ORM\ManyToOne]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private ?User $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken)
|
||||||
|
{
|
||||||
|
$this->initialize($expiresAt, $selector, $hashedToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Enum\RoleEnum;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||||
@ -16,8 +21,9 @@ use Symfony\Component\Uid\Uuid;
|
|||||||
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: 'uuid', length: 180)]
|
#[ORM\GeneratedValue]
|
||||||
private ?Uuid $id = null;
|
#[ORM\Column]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
private ?string $email = null;
|
private ?string $email = null;
|
||||||
@ -34,17 +40,24 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?string $password = null;
|
private ?string $password = null;
|
||||||
|
|
||||||
|
#[ORM\Column(enumType: RoleEnum::class)]
|
||||||
|
private ?RoleEnum $folder_role = null;
|
||||||
|
|
||||||
public function initId(): void
|
#[ORM\Column(length: 255)]
|
||||||
|
private ?string $fullname = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, ParentDirectory>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: ParentDirectory::class, mappedBy: 'userCreated')]
|
||||||
|
private Collection $parentDirectories;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
{
|
{
|
||||||
if ($this->id !== null) {
|
$this->parentDirectories = new ArrayCollection();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->id = Uuid::v4();
|
public function getId(): ?int
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): ?Uuid
|
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
@ -118,4 +131,56 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFolderRole(): ?RoleEnum
|
||||||
|
{
|
||||||
|
return $this->folder_role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFolderRole(RoleEnum $folder_role): static
|
||||||
|
{
|
||||||
|
$this->folder_role = $folder_role;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFullname(): ?string
|
||||||
|
{
|
||||||
|
return $this->fullname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFullname(string $fullname): static
|
||||||
|
{
|
||||||
|
$this->fullname = $fullname;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, ParentDirectory>
|
||||||
|
*/
|
||||||
|
public function getParentDirectories(): Collection
|
||||||
|
{
|
||||||
|
return $this->parentDirectories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addParentDirectory(ParentDirectory $parentDirectory): static
|
||||||
|
{
|
||||||
|
if (!$this->parentDirectories->contains($parentDirectory)) {
|
||||||
|
$this->parentDirectories->add($parentDirectory);
|
||||||
|
$parentDirectory->setUserCreated($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeParentDirectory(ParentDirectory $parentDirectory): static
|
||||||
|
{
|
||||||
|
// set the owning side to null (unless already changed)
|
||||||
|
if ($this->parentDirectories->removeElement($parentDirectory) && $parentDirectory->getUserCreated() === $this) {
|
||||||
|
$parentDirectory->setUserCreated(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
53
src/Enum/RoleEnum.php
Normal file
53
src/Enum/RoleEnum.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enum;
|
||||||
|
|
||||||
|
enum RoleEnum: string
|
||||||
|
{
|
||||||
|
case CONSEIL_ADMINISTRATION = 'Conseil d\'administration';
|
||||||
|
case ADMINISTRATEUR = 'Administrateur';
|
||||||
|
case MEMBRE = 'Membre';
|
||||||
|
case MEMBRE_HONORAIRE = 'Membre honoraire';
|
||||||
|
case PARTENAIRE = 'Partenaire';
|
||||||
|
case VISITEUR = 'Visiteur';
|
||||||
|
|
||||||
|
public function getHigherRoles(): array
|
||||||
|
{
|
||||||
|
$roles = [];
|
||||||
|
|
||||||
|
$isFound = false;
|
||||||
|
foreach (RoleEnum::cases() as $role) {
|
||||||
|
if ($role === $this) {
|
||||||
|
$isFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isFound) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$roles[] = $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInferiorRoles(): array
|
||||||
|
{
|
||||||
|
$roles = [];
|
||||||
|
|
||||||
|
$isFound = false;
|
||||||
|
foreach (RoleEnum::cases() as $role) {
|
||||||
|
if ($role === $this) {
|
||||||
|
$isFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isFound) {
|
||||||
|
$roles[] = $role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $roles;
|
||||||
|
}
|
||||||
|
}
|
67
src/Form/ChangePasswordFormType.php
Normal file
67
src/Form/ChangePasswordFormType.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\Length;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotCompromisedPassword;
|
||||||
|
use Symfony\Component\Validator\Constraints\PasswordStrength;
|
||||||
|
|
||||||
|
class ChangePasswordFormType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('plainPassword', RepeatedType::class, [
|
||||||
|
'type' => PasswordType::class,
|
||||||
|
'options' => [
|
||||||
|
'attr' => [
|
||||||
|
'autocomplete' => 'new-password',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'first_options' => [
|
||||||
|
'constraints' => [
|
||||||
|
new NotBlank([
|
||||||
|
'message' => 'Merci de renseigner un mot de passe',
|
||||||
|
]),
|
||||||
|
new Length([
|
||||||
|
'min' => 12,
|
||||||
|
'minMessage' => 'Votre mot de passe doit contenir au moins {{ limit }} caractères',
|
||||||
|
// max length allowed by Symfony for security reasons
|
||||||
|
'max' => 4096,
|
||||||
|
]),
|
||||||
|
new NotCompromisedPassword([
|
||||||
|
'message' => 'Ce mot de passe a été exposé lors d\'une fuite de données, il ne peut pas être utilisé.',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
'label' => 'Nouveau mot de passe',
|
||||||
|
],
|
||||||
|
'second_options' => [
|
||||||
|
'label' => 'Répéter le mot de passe',
|
||||||
|
],
|
||||||
|
'invalid_message' => 'Les mots de passe ne correspondent pas.',
|
||||||
|
// Instead of being set onto the object directly,
|
||||||
|
// this is read and encoded in the controller
|
||||||
|
'mapped' => false,
|
||||||
|
])
|
||||||
|
->add('submit', SubmitType::class, [
|
||||||
|
'label' => 'Mettre à jour',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'csrf_protection' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
49
src/Form/EmailFormType.php
Normal file
49
src/Form/EmailFormType.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\DTO\EmailDTO;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class EmailFormType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('email', EmailType::class, [
|
||||||
|
'label' => 'Nouvelle adresse email',
|
||||||
|
'attr' => [
|
||||||
|
'placeholder' => 'Nouvelle adresse email',
|
||||||
|
'autocomplete' => 'new-email',
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->add('password', PasswordType::class, [
|
||||||
|
'label' => 'Mot de passe actuel',
|
||||||
|
'attr' => [
|
||||||
|
'placeholder' => 'Mot de passe actuel',
|
||||||
|
'autocomplete' => 'new-password',
|
||||||
|
]
|
||||||
|
])
|
||||||
|
->add('submit', SubmitType::class, [
|
||||||
|
'label' => 'Enregistrer',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => EmailDTO::class,
|
||||||
|
'attr' => [
|
||||||
|
'autocomplete' => 'off',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
51
src/Form/FilePermissionType.php
Normal file
51
src/Form/FilePermissionType.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\ParentDirectory;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Enum\RoleEnum;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class FilePermissionType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('ownerRole', EnumType::class, [
|
||||||
|
'class' => RoleEnum::class,
|
||||||
|
'label' => 'Rôle minimum',
|
||||||
|
])
|
||||||
|
->add('isPublic', null, [
|
||||||
|
'label' => 'Dossier public',
|
||||||
|
])
|
||||||
|
->add('parentDirectoryPermissions', CollectionType::class, [
|
||||||
|
'entry_type' => ParentDirectoryPermissionType::class,
|
||||||
|
'entry_options' => [
|
||||||
|
'label' => false,
|
||||||
|
],
|
||||||
|
'allow_add' => true,
|
||||||
|
'allow_delete' => true,
|
||||||
|
'by_reference' => false,
|
||||||
|
])
|
||||||
|
->add('submit', SubmitType::class, [
|
||||||
|
'label' => 'Enregistrer',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => ParentDirectory::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
42
src/Form/ParentDirectoryPermissionType.php
Normal file
42
src/Form/ParentDirectoryPermissionType.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\Entity\ParentDirectory;
|
||||||
|
use App\Entity\ParentDirectoryPermission;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Enum\RoleEnum;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class ParentDirectoryPermissionType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('role', EnumType::class, [
|
||||||
|
'class' => RoleEnum::class,
|
||||||
|
'label' => 'Rôle',
|
||||||
|
])
|
||||||
|
->add('read', null, [
|
||||||
|
'label' => 'Lecture',
|
||||||
|
])
|
||||||
|
->add('write', null, [
|
||||||
|
'label' => 'Écriture',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => ParentDirectoryPermission::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
51
src/Form/PasswordFormType.php
Normal file
51
src/Form/PasswordFormType.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use App\DTO\PasswordDTO;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class PasswordFormType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('password', PasswordType::class, [
|
||||||
|
'label' => 'Mot de passe actuel',
|
||||||
|
'attr' => [
|
||||||
|
'placeholder' => 'Mot de passe actuel',
|
||||||
|
'autocomplete' => 'old-password',
|
||||||
|
],
|
||||||
|
'required' => true,
|
||||||
|
])
|
||||||
|
->add('newPassword', RepeatedType::class, [
|
||||||
|
'type' => PasswordType::class,
|
||||||
|
'first_options' => ['label' => 'Nouveau mot de passe'],
|
||||||
|
'attr' => [
|
||||||
|
'autocomplete' => 'new-password',
|
||||||
|
],
|
||||||
|
'second_options' => ['label' => 'Confirmer le mot de passe'],
|
||||||
|
'required' => true,
|
||||||
|
'invalid_message' => 'Les mots de passe ne correspondent pas.',
|
||||||
|
])
|
||||||
|
->add('submit', SubmitType::class, [
|
||||||
|
'label' => 'Enregistrer',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => PasswordDTO::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
39
src/Form/ResetPasswordRequestFormType.php
Normal file
39
src/Form/ResetPasswordRequestFormType.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Form;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
|
|
||||||
|
class ResetPasswordRequestFormType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('email', EmailType::class, [
|
||||||
|
'attr' => ['autocomplete' => 'email'],
|
||||||
|
'constraints' => [
|
||||||
|
new NotBlank([
|
||||||
|
'message' => 'Merci de renseigner votre adresse email',
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
->add('submit', SubmitType::class, [
|
||||||
|
'label' => 'Envoyer',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'csrf_protection' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
@ -14,7 +16,7 @@ class UploadType extends AbstractType
|
|||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('files', DropzoneType::class, [
|
->add('files', FileType::class, [
|
||||||
'label' => 'Fichiers à envoyer',
|
'label' => 'Fichiers à envoyer',
|
||||||
'attr' => [
|
'attr' => [
|
||||||
'placeholder' => 'Déposez vos fichiers ici',
|
'placeholder' => 'Déposez vos fichiers ici',
|
||||||
@ -22,7 +24,7 @@ class UploadType extends AbstractType
|
|||||||
'multiple' => true,
|
'multiple' => true,
|
||||||
])
|
])
|
||||||
->add('submit', SubmitType::class, [
|
->add('submit', SubmitType::class, [
|
||||||
'label' => 'Uploader',
|
'label' => 'Téléverser',
|
||||||
])
|
])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Form;
|
namespace App\Form;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
use App\Enum\RoleEnum;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
@ -16,9 +20,16 @@ class UserAdminType extends AbstractType
|
|||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
|
->add('fullname', null, [
|
||||||
|
'label' => 'Nom complet',
|
||||||
|
])
|
||||||
->add('email', EmailType::class, [
|
->add('email', EmailType::class, [
|
||||||
'label' => 'Adresse email',
|
'label' => 'Adresse email',
|
||||||
])
|
])
|
||||||
|
->add('folderRole', EnumType::class, [
|
||||||
|
'label' => 'Groupe',
|
||||||
|
'class' => RoleEnum::class,
|
||||||
|
])
|
||||||
->add('role', ChoiceType::class, [
|
->add('role', ChoiceType::class, [
|
||||||
'label' => 'Rôle',
|
'label' => 'Rôle',
|
||||||
'choices' => [
|
'choices' => [
|
||||||
@ -44,7 +55,6 @@ class UserAdminType extends AbstractType
|
|||||||
$builder->add('submit', SubmitType::class, [
|
$builder->add('submit', SubmitType::class, [
|
||||||
'label' => 'Enregistrer',
|
'label' => 'Enregistrer',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
public function configureOptions(OptionsResolver $resolver): void
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App;
|
namespace App;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||||
|
45
src/Repository/ParentDirectoryPermissionRepository.php
Normal file
45
src/Repository/ParentDirectoryPermissionRepository.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\ParentDirectoryPermission;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<ParentDirectoryPermission>
|
||||||
|
*/
|
||||||
|
class ParentDirectoryPermissionRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, ParentDirectoryPermission::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return ParentDirectoryPermission[] Returns an array of ParentDirectoryPermission objects
|
||||||
|
// */
|
||||||
|
// public function findByExampleField($value): array
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('p')
|
||||||
|
// ->andWhere('p.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->orderBy('p.id', 'ASC')
|
||||||
|
// ->setMaxResults(10)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function findOneBySomeField($value): ?ParentDirectoryPermission
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('p')
|
||||||
|
// ->andWhere('p.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getOneOrNullResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
}
|
45
src/Repository/ParentDirectoryRepository.php
Normal file
45
src/Repository/ParentDirectoryRepository.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\ParentDirectory;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<ParentDirectory>
|
||||||
|
*/
|
||||||
|
class ParentDirectoryRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, ParentDirectory::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @return ParentDirectory[] Returns an array of ParentDirectory objects
|
||||||
|
// */
|
||||||
|
// public function findByExampleField($value): array
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('p')
|
||||||
|
// ->andWhere('p.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->orderBy('p.id', 'ASC')
|
||||||
|
// ->setMaxResults(10)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public function findOneBySomeField($value): ?ParentDirectory
|
||||||
|
// {
|
||||||
|
// return $this->createQueryBuilder('p')
|
||||||
|
// ->andWhere('p.exampleField = :val')
|
||||||
|
// ->setParameter('val', $value)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getOneOrNullResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
}
|
34
src/Repository/ResetPasswordRequestRepository.php
Normal file
34
src/Repository/ResetPasswordRequestRepository.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
use App\Entity\ResetPasswordRequest;
|
||||||
|
use App\Entity\User;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordRequestInterface;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Persistence\Repository\ResetPasswordRequestRepositoryTrait;
|
||||||
|
use SymfonyCasts\Bundle\ResetPassword\Persistence\ResetPasswordRequestRepositoryInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends ServiceEntityRepository<ResetPasswordRequest>
|
||||||
|
*/
|
||||||
|
class ResetPasswordRequestRepository extends ServiceEntityRepository implements ResetPasswordRequestRepositoryInterface
|
||||||
|
{
|
||||||
|
use ResetPasswordRequestRepositoryTrait;
|
||||||
|
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, ResetPasswordRequest::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createResetPasswordRequest(object $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken): ResetPasswordRequestInterface
|
||||||
|
{
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
throw new \InvalidArgumentException('User must be an instance of ' . User::class);
|
||||||
|
}
|
||||||
|
return new ResetPasswordRequest($user, $expiresAt, $selector, $hashedToken);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Security\Authentication;
|
namespace App\Security\Authentication;
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
|
58
src/Security/Voter/FileVoter.php
Normal file
58
src/Security/Voter/FileVoter.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Security\Voter;
|
||||||
|
|
||||||
|
use App\Entity\ParentDirectory;
|
||||||
|
use App\Entity\ParentDirectoryPermission;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Enum\RoleEnum;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
|
|
||||||
|
final class FileVoter extends Voter
|
||||||
|
{
|
||||||
|
protected function supports(string $attribute, mixed $subject): bool
|
||||||
|
{
|
||||||
|
return $subject instanceof ParentDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ParentDirectory $realSubject
|
||||||
|
*/
|
||||||
|
$realSubject = $subject;
|
||||||
|
$user = $token->getUser();
|
||||||
|
|
||||||
|
$parentDirectoryPermissionsVisiteurRead = array_filter($realSubject->getParentDirectoryPermissions()->toArray(), static fn (ParentDirectoryPermission $parentDirectoryPermission) => RoleEnum::VISITEUR === $parentDirectoryPermission->getRole());
|
||||||
|
|
||||||
|
if ([] !== $parentDirectoryPermissionsVisiteurRead && !($user instanceof User)) {
|
||||||
|
return 'file_read' === $attribute && array_values($parentDirectoryPermissionsVisiteurRead)[0]->isRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$user instanceof User) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$parentDirectoryPermissions = array_filter($realSubject->getParentDirectoryPermissions()->toArray(), static fn (ParentDirectoryPermission $parentDirectoryPermission) => $parentDirectoryPermission->getRole() === $user->getFolderRole());
|
||||||
|
|
||||||
|
$parentDirectoryPermission = null;
|
||||||
|
|
||||||
|
if ([] !== $parentDirectoryPermissions) {
|
||||||
|
$parentDirectoryPermission = array_values($parentDirectoryPermissions)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkNeeded = false;
|
||||||
|
|
||||||
|
if (null !== $parentDirectoryPermission) {
|
||||||
|
$checkNeeded = 'file_read' === $attribute ? $parentDirectoryPermission->isRead() : $parentDirectoryPermission->isWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($realSubject->getUserCreated() === $user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return $realSubject->isPublic() && ($checkNeeded || $realSubject->getOwnerRole() === $user->getFolderRole() || in_array($user->getFolderRole(), $realSubject->getOwnerRole()->getHigherRoles(), true));
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Twig\Extension;
|
namespace App\Twig\Extension;
|
||||||
|
|
||||||
use App\Twig\Runtime\BasenameExtensionRuntime;
|
use App\Twig\Runtime\BasenameExtensionRuntime;
|
||||||
|
20
src/Twig/Extension/EntityExtension.php
Normal file
20
src/Twig/Extension/EntityExtension.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Twig\Extension;
|
||||||
|
|
||||||
|
use App\Twig\Runtime\EntityExtensionRuntime;
|
||||||
|
use Twig\Extension\AbstractExtension;
|
||||||
|
use Twig\TwigFilter;
|
||||||
|
use Twig\TwigFunction;
|
||||||
|
|
||||||
|
class EntityExtension extends AbstractExtension
|
||||||
|
{
|
||||||
|
public function getFunctions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new TwigFunction('get_parent_dir', [EntityExtensionRuntime::class, 'getParentDir']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
20
src/Twig/Extension/EnvironmentExtension.php
Normal file
20
src/Twig/Extension/EnvironmentExtension.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Twig\Extension;
|
||||||
|
|
||||||
|
use App\Twig\Runtime\EnvironmentExtensionRuntime;
|
||||||
|
use Twig\Extension\AbstractExtension;
|
||||||
|
use Twig\TwigFilter;
|
||||||
|
use Twig\TwigFunction;
|
||||||
|
|
||||||
|
class EnvironmentExtension extends AbstractExtension
|
||||||
|
{
|
||||||
|
public function getFunctions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new TwigFunction('get_env', [EnvironmentExtensionRuntime::class, 'getEnv']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
20
src/Twig/Extension/GravatarExtension.php
Normal file
20
src/Twig/Extension/GravatarExtension.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Twig\Extension;
|
||||||
|
|
||||||
|
use App\Twig\Runtime\GravatarExtensionRuntime;
|
||||||
|
use Twig\Extension\AbstractExtension;
|
||||||
|
use Twig\TwigFilter;
|
||||||
|
use Twig\TwigFunction;
|
||||||
|
|
||||||
|
class GravatarExtension extends AbstractExtension
|
||||||
|
{
|
||||||
|
public function getFunctions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new TwigFunction('get_gravatar', [GravatarExtensionRuntime::class, 'getGravatar']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Twig\Extension;
|
namespace App\Twig\Extension;
|
||||||
|
|
||||||
use App\Twig\Runtime\SizeExtensionRuntime;
|
use App\Twig\Runtime\SizeExtensionRuntime;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Twig\Extension;
|
namespace App\Twig\Extension;
|
||||||
|
|
||||||
use App\Twig\Runtime\TimeExtensionRuntime;
|
use App\Twig\Runtime\TimeExtensionRuntime;
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Twig\Runtime;
|
namespace App\Twig\Runtime;
|
||||||
|
|
||||||
use Twig\Extension\RuntimeExtensionInterface;
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
|
|
||||||
class BasenameExtensionRuntime implements RuntimeExtensionInterface
|
class BasenameExtensionRuntime implements RuntimeExtensionInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
public function basename($value)
|
public function basename($value)
|
||||||
{
|
{
|
||||||
return \basename($value);
|
return \basename((string) $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
src/Twig/Runtime/EntityExtensionRuntime.php
Normal file
22
src/Twig/Runtime/EntityExtensionRuntime.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Twig\Runtime;
|
||||||
|
|
||||||
|
use App\Entity\ParentDirectory;
|
||||||
|
use App\Repository\ParentDirectoryRepository;
|
||||||
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
|
|
||||||
|
class EntityExtensionRuntime implements RuntimeExtensionInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ParentDirectoryRepository $parentDirectoryRepository
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getParentDir(string $value): ParentDirectory
|
||||||
|
{
|
||||||
|
return $this->parentDirectoryRepository->findOneBy(['name' => $value]);
|
||||||
|
}
|
||||||
|
}
|
15
src/Twig/Runtime/EnvironmentExtensionRuntime.php
Normal file
15
src/Twig/Runtime/EnvironmentExtensionRuntime.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Twig\Runtime;
|
||||||
|
|
||||||
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
|
|
||||||
|
class EnvironmentExtensionRuntime implements RuntimeExtensionInterface
|
||||||
|
{
|
||||||
|
public function getEnv(string $value, string $default = ''): string
|
||||||
|
{
|
||||||
|
return $_ENV[$value] ?? $default;
|
||||||
|
}
|
||||||
|
}
|
21
src/Twig/Runtime/GravatarExtensionRuntime.php
Normal file
21
src/Twig/Runtime/GravatarExtensionRuntime.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Twig\Runtime;
|
||||||
|
|
||||||
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
|
|
||||||
|
class GravatarExtensionRuntime implements RuntimeExtensionInterface
|
||||||
|
{
|
||||||
|
public function __construct(private readonly string $defaultImage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGravatar(string $email): string
|
||||||
|
{
|
||||||
|
$hash = hash('sha256', strtolower(trim($email)));
|
||||||
|
|
||||||
|
return 'https://gravatar.com/avatar/' . $hash . '?s=2048&d=' . urlencode($this->defaultImage);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Twig\Runtime;
|
namespace App\Twig\Runtime;
|
||||||
|
|
||||||
use Twig\Extension\RuntimeExtensionInterface;
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
@ -15,8 +17,8 @@ class SizeExtensionRuntime implements RuntimeExtensionInterface
|
|||||||
{
|
{
|
||||||
$bytes = $value;
|
$bytes = $value;
|
||||||
$size = ['B', 'KB', 'MB', 'GB','TB'];
|
$size = ['B', 'KB', 'MB', 'GB','TB'];
|
||||||
$factor = floor((strlen($bytes) - 1) / 3);
|
$factor = floor((strlen((string) $bytes) - 1) / 3);
|
||||||
|
|
||||||
return sprintf('%.1f', $bytes / pow(1024, $factor)) . ' ' . @$size[$factor];
|
return sprintf('%.1f', $bytes / 1024 ** $factor) . ' ' . @$size[$factor];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Twig\Runtime;
|
namespace App\Twig\Runtime;
|
||||||
|
|
||||||
use Twig\Extension\RuntimeExtensionInterface;
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
|
36
symfony.lock
36
symfony.lock
@ -26,6 +26,18 @@
|
|||||||
"migrations/.gitignore"
|
"migrations/.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"friendsofphp/php-cs-fixer": {
|
||||||
|
"version": "3.68",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "3.0",
|
||||||
|
"ref": "be2103eb4a20942e28a6dd87736669b757132435"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
".php-cs-fixer.dist.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
"oneup/flysystem-bundle": {
|
"oneup/flysystem-bundle": {
|
||||||
"version": "4.12",
|
"version": "4.12",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
@ -38,6 +50,18 @@
|
|||||||
"config/packages/oneup_flysystem.yaml"
|
"config/packages/oneup_flysystem.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"phpstan/phpstan": {
|
||||||
|
"version": "2.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes-contrib",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"phpstan.dist.neon"
|
||||||
|
]
|
||||||
|
},
|
||||||
"phpunit/phpunit": {
|
"phpunit/phpunit": {
|
||||||
"version": "9.6",
|
"version": "9.6",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
@ -356,6 +380,18 @@
|
|||||||
"config/routes/web_profiler.yaml"
|
"config/routes/web_profiler.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfonycasts/reset-password-bundle": {
|
||||||
|
"version": "1.23",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "1.0",
|
||||||
|
"ref": "97c1627c0384534997ae1047b93be517ca16de43"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/reset_password.yaml"
|
||||||
|
]
|
||||||
|
},
|
||||||
"symfonycasts/tailwind-bundle": {
|
"symfonycasts/tailwind-bundle": {
|
||||||
"version": "v0.6.1"
|
"version": "v0.6.1"
|
||||||
},
|
},
|
||||||
|
5
tailwind.config.js
Executable file → Normal file
5
tailwind.config.js
Executable file → Normal file
@ -8,5 +8,8 @@ module.exports = {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [
|
||||||
|
require('flowbite/plugin')
|
||||||
|
],
|
||||||
|
darkMode: 'media',
|
||||||
}
|
}
|
@ -1,13 +1,13 @@
|
|||||||
{% extends 'base-admin.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Le cloud de Camélia-Studio{% endblock %}
|
{% block title %}Le disque nuagique de Camélia-Studio{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="min-h-96 flex items-center flex-col justify-center">
|
<div class="min-h-96 flex items-center flex-col justify-center">
|
||||||
<h1 class="text-4xl font-bold mb-2 text-gray-900 dark:text-white">Bienvenue sur l'administration</h1>
|
<h1 class="text-4xl font-bold mb-2 text-gray-900 dark:text-white">Bienvenue sur l'administration</h1>
|
||||||
<p class="text-xl text-gray-600 dark:text-gray-300 mb-8">Gérez facilement les accès des membres de Camélia-Studio à l'espace de stockage partagé Kumora. Ajoutez, modifiez ou retirez les utilisateurs en quelques clics.</p>
|
<p class="text-xl text-gray-600 dark:text-gray-300 mb-8">Gérez facilement les accès des membres de Camélia-Studio à l'espace de stockage partagé Kumora. Ajoutez, modifiez ou retirez les utilisateurs en quelques clics.</p>
|
||||||
<div class="mb-16">
|
<div class="mb-16">
|
||||||
<a href="{{ path('app_files_index') }}" class="text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 dark:bg-gray-800 dark:text-white dark:border-gray-600 font-medium rounded-lg px-8 py-3">
|
<a href="{{ path('app_admin_user_index') }}" class="text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 dark:bg-gray-800 dark:text-white dark:border-gray-600 font-medium rounded-lg px-8 py-3">
|
||||||
Gérer les utilisateurs
|
Gérer les utilisateurs
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{% extends 'base-admin.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container mx-auto px-16 mt-4">
|
<div class="container mx-auto px-16 mt-4">
|
||||||
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
|
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">
|
||||||
{% if not isNew %}
|
{% if not isNew %}
|
||||||
Edition de l'utilisateur {{ user.email }}
|
Edition de l'utilisateur {{ user.email }}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
{% extends 'base-admin.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container mx-auto px-16 mt-4">
|
<div class="container mx-auto px-16 mt-4">
|
||||||
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Liste des utilisateurs</h5>
|
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Liste des utilisateurs</h5>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<a href="{{ path('app_admin_user_create') }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Créer un utilisateur</a>
|
<a href="{{ path('app_admin_user_create') }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-hidden dark:focus:ring-blue-800">Créer un utilisateur</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
{{ include('partials/alerts.html.twig') }}
|
{{ include('partials/alerts.html.twig') }}
|
||||||
@ -24,13 +24,13 @@
|
|||||||
Rôle
|
Rôle
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" class="px-6 py-3">
|
<th scope="col" class="px-6 py-3">
|
||||||
Action
|
Actions
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700">
|
<tr class="odd:bg-white dark:odd:bg-gray-900 even:bg-gray-50 dark:even:bg-gray-800 border-b dark:border-gray-700">
|
||||||
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
<th scope="row" class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
|
||||||
{{ user.id }}
|
{{ user.id }}
|
||||||
</th>
|
</th>
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr" class="bg-white dark:bg-gray-800 dark:text-white">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Kumora - {% block title %}Accueil{% endblock %}</title>
|
|
||||||
<link rel="icon" href="{{ asset('images/favicon.ico') }}">
|
|
||||||
{% block stylesheets %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block javascripts %}
|
|
||||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
|
||||||
{% endblock %}
|
|
||||||
</head>
|
|
||||||
<body data-turbo="false">
|
|
||||||
{% include "partials/navbar-admin.html.twig" %}
|
|
||||||
{% block body %}{% endblock %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -11,8 +11,12 @@
|
|||||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body data-turbo="false">
|
<body data-turbo="false" class="flex flex-col min-h-screen">
|
||||||
{% include "partials/navbar.html.twig" %}
|
{% include "partials/navbar.html.twig" %}
|
||||||
|
<div class="flex-1">
|
||||||
|
{% include "partials/alerts.html.twig" %}
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% include "partials/footer.html.twig" %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container mx-auto px-16 mt-4">
|
<div class="container mx-auto px-16 mt-4">
|
||||||
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Créer un dossier dans /{{ basePath }}</h3>
|
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Créer un dossier dans /{{ basePath }}</h3>
|
||||||
{{ form(form) }}
|
{{ form(form) }}
|
||||||
</div>
|
</div>
|
||||||
|
37
templates/files/file_edit.html.twig
Normal file
37
templates/files/file_edit.html.twig
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mx-auto px-16 mt-4">
|
||||||
|
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Gérer les permissions du dossier {{ parentDir.name }}</h3>
|
||||||
|
{{ form_start(form) }}
|
||||||
|
{{ form_row(form.ownerRole) }}
|
||||||
|
{{ form_row(form.isPublic) }}
|
||||||
|
{{ form_label(form.parentDirectoryPermissions) }}
|
||||||
|
{{ form_widget(form.parentDirectoryPermissions) }}
|
||||||
|
<button type="button" id="add-parentDirectoryPermissions" class="py-2.5 px-5 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-hidden bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">Ajouter</button>
|
||||||
|
{{ form_end(form) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Gérer les permissions du dossier {{ parentDir.name }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
{{ parent() }}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('readystatechange', () => {
|
||||||
|
if (document.readyState === "complete") {
|
||||||
|
document.querySelector('#add-parentDirectoryPermissions').addEventListener('click', function() {
|
||||||
|
const container = document.querySelector('#file_permission_parentDirectoryPermissions');
|
||||||
|
const prototype = container.dataset.prototype;
|
||||||
|
const index = container.children.length;
|
||||||
|
|
||||||
|
container.insertAdjacentHTML('beforeend', prototype.replace(/__name__/g, index));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -8,14 +8,18 @@
|
|||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
{{ include('partials/alerts.html.twig') }}
|
{{ include('partials/alerts.html.twig') }}
|
||||||
</div>
|
</div>
|
||||||
|
{% if parentDir == null or (parentDir != null and is_granted('file_write', parentDir)) %}
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<a href="{{ path('app_files_create_directory', {
|
<a href="{{ path('app_files_create_directory', {
|
||||||
base: path
|
base: path
|
||||||
}) }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Créer un dossier</a>
|
}) }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-hidden dark:focus:ring-blue-800">Créer un dossier</a>
|
||||||
|
{% if path != '' %}
|
||||||
<a href="{{ path('app_files_upload', {
|
<a href="{{ path('app_files_upload', {
|
||||||
path: path
|
path: path
|
||||||
}) }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Ajouter des fichiers</a>
|
}) }}" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-hidden dark:focus:ring-blue-800">Ajouter des fichiers</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
{% include 'partials/breadbrumb.html.twig' %}
|
{% include 'partials/breadbrumb.html.twig' %}
|
||||||
</div>
|
</div>
|
||||||
@ -34,7 +38,10 @@
|
|||||||
Modifié le
|
Modifié le
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" class="px-6 py-3">
|
<th scope="col" class="px-6 py-3">
|
||||||
Action
|
Groupes d'accès
|
||||||
|
</th>
|
||||||
|
<th scope="col" class="px-6 py-3">
|
||||||
|
Actions
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -47,19 +54,36 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<twig:ux:icon name="line-md:folder-filled" class="w-6 h-6" />
|
<twig:ux:icon name="line-md:folder-filled" class="w-6 h-6" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="text-blue-700 dark:text-blue-500 hover:text-blue-900 dark:hover:text-blue-700" href="{{ file.url }}">{{ file.path|basename }}</a>
|
<a class="text-blue-700 dark:text-blue-500 hover:text-blue-900 dark:hover:text-blue-700" href="{{ file.previewUrl }}" {{ file.type == 'file' ? 'target="_blank"' : '' }}>{{ file.path|basename }}</a>
|
||||||
</th>
|
</th>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
{% if file.type == 'file' %}
|
|
||||||
{{ file.size|show_size }}
|
{{ file.size|show_size }}
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-4">
|
<td class="px-6 py-4">
|
||||||
{{ file.last_modified|time_diff }}
|
{{ file.last_modified|time_diff }}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<div class="flex gap-1">
|
||||||
|
{% if parentDir == null %}
|
||||||
|
{% set parentDirectory = get_parent_dir(file.path) %}
|
||||||
|
{% else %}
|
||||||
|
{% set parentDirectory = parentDir %}
|
||||||
|
{% endif %}
|
||||||
|
{% for parentRole in parentDirectory.ownerRole.getHigherRoles() %}
|
||||||
|
<img class="w-6 h-6" title="{{ parentRole.value }}" src="https://ui-avatars.com/api/?name={{ parentRole.value }}" alt="">
|
||||||
|
{% endfor %}
|
||||||
|
<img class="w-6 h-6" title="{{ parentDirectory.ownerRole.value }}" src="https://ui-avatars.com/api/?name={{ parentDirectory.ownerRole.value }}" alt="">
|
||||||
|
{% for permission in parentDirectory.parentDirectoryPermissions %}
|
||||||
|
<img class="w-6 h-6" title="{{ permission.role.value }}" src="https://ui-avatars.com/api/?name={{ permission.role.value }}" alt="">
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td class="px-6 py-4 flex gap-2 light:text-black">
|
<td class="px-6 py-4 flex gap-2 light:text-black">
|
||||||
{% if file.type == 'file' %}
|
{% if file.type == 'file' %}
|
||||||
|
<a title="Permet de voir le fichier" href="{{ file.previewUrl }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:eye" /></a>
|
||||||
|
<button title="Permet de copier le lien de prévisualisation du fichier" onclick="navigator.clipboard.writeText('{{ file.previewUrl }}'); alert('Lien copié avec succès !')" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6 cursor-pointer" name="fa6-solid:clipboard" /></button>
|
||||||
<a title="Permet de télécharger le fichier" href="{{ file.url }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:download" /></a>
|
<a title="Permet de télécharger le fichier" href="{{ file.url }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:download" /></a>
|
||||||
|
{% if is_granted('file_write', parentDir) %}
|
||||||
<a title="Permet de renommer le fichier" href="{{ path('app_files_rename', {
|
<a title="Permet de renommer le fichier" href="{{ path('app_files_rename', {
|
||||||
path: file.path
|
path: file.path
|
||||||
}) }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
|
}) }}" class="hover:text-blue-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
|
||||||
@ -68,7 +92,20 @@
|
|||||||
filename: file.path
|
filename: file.path
|
||||||
})
|
})
|
||||||
}}" class="hover:text-red-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
|
}}" class="hover:text-red-700 duration-300"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
{% if parentDir == null %}
|
||||||
|
{% set parentDirectory = get_parent_dir(file.path) %}
|
||||||
|
{% else %}
|
||||||
|
{% set parentDirectory = parentDir %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if parentDir == null and (parentDirectory.userCreated == app.user or (parentDirectory.isPublic and app.user.folderRole == enum('App\\Enum\\RoleEnum').CONSEIL_ADMINISTRATION)) %}
|
||||||
|
<a href="{{ path('app_files_file_edit_permission', {
|
||||||
|
parentDir: parentDirectory.name,
|
||||||
|
}) }}" class="hover:text-blue-700 duration-300" title="Permet de modifier les permissions du dossier"><twig:ux:icon class="w-6 h-6" name="fa6-solid:shield" /></a>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('file_write', parentDirectory) %}
|
||||||
<a href="{{ path('app_files_rename-directory', {
|
<a href="{{ path('app_files_rename-directory', {
|
||||||
path: file.path
|
path: file.path
|
||||||
}) }}" class="hover:text-blue-700 duration-300" title="Permet de renommer le dossier"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
|
}) }}" class="hover:text-blue-700 duration-300" title="Permet de renommer le dossier"><twig:ux:icon class="w-6 h-6" name="fa6-solid:pencil" /></a>
|
||||||
@ -76,6 +113,7 @@
|
|||||||
path: file.path
|
path: file.path
|
||||||
}) }}" class="hover:text-red-700 duration-300" title="Permet de supprimer le dossier"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
|
}) }}" class="hover:text-red-700 duration-300" title="Permet de supprimer le dossier"><twig:ux:icon class="w-6 h-6" name="fa6-solid:trash-can" /></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container mx-auto px-16 mt-4">
|
<div class="container mx-auto px-16 mt-4">
|
||||||
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Renommer le {{ type }} {{ filepath }}</h3>
|
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Renommer le {{ type }} {{ filepath }}</h3>
|
||||||
{{ form(form) }}
|
{{ form(form) }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container mx-auto px-16 mt-4">
|
<div class="container mx-auto px-16 mt-4">
|
||||||
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow dark:bg-gray-800 dark:border-gray-700">
|
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Ajouter des fichiers dans /{{ path }}</h3>
|
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Ajouter des fichiers dans /{{ path }}</h3>
|
||||||
{{ form(form) }}
|
{{ form(form) }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
{% extends 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
{% block title %}Le cloud de Camélia-Studio{% endblock %}
|
{% block title %}Le disque nuagique de Camélia-Studio{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="min-h-96 flex items-center flex-col justify-center">
|
<div class="min-h-96 flex items-center flex-col justify-center">
|
||||||
|
<img src="{{ get_env('DEFAULT_IMAGE') }}" alt="Mascotte Camélia Studio" class="max-w-96">
|
||||||
<h1 class="text-4xl font-bold mb-2 text-gray-900 dark:text-white">Bienvenue sur Kumora</h1>
|
<h1 class="text-4xl font-bold mb-2 text-gray-900 dark:text-white">Bienvenue sur Kumora</h1>
|
||||||
<p class="text-xl text-gray-600 dark:text-gray-300 mb-8">Notre espace de stockage en ligne, dédié aux membres de Camélia Studio, pour faciliter le partage de tous les fichiers et ressources de l'association.</p>
|
<p class="text-xl text-gray-600 dark:text-gray-300 mb-8">Notre espace de stockage en ligne, dédié aux membres de Camélia Studio, pour faciliter le partage de tous les fichiers et ressources de l'association.</p>
|
||||||
<div class="mb-16">
|
<div class="mb-16">
|
||||||
@ -11,5 +12,10 @@
|
|||||||
Accéder à mon espace
|
Accéder à mon espace
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-16 flex items-center justify-center gap-8">
|
||||||
|
<a target="_blank" href="{{ get_env('CAMELIA_URL') }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Camélia Studio</a>
|
||||||
|
<a target="_blank" href="{{ get_env('TSUBAKIMONO_URL') }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Tsubakimono</a>
|
||||||
|
<a target="_blank" href="{{ get_env('DISCORD_URL') }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Discord</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,3 +1,4 @@
|
|||||||
|
<div class="container mx-auto px-16 mt-4">
|
||||||
{% for label, messages in app.flashes %}
|
{% for label, messages in app.flashes %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
{% if label == 'success' %}
|
{% if label == 'success' %}
|
||||||
@ -19,3 +20,4 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
9
templates/partials/footer.html.twig
Normal file
9
templates/partials/footer.html.twig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
<footer class="bg-white border-gray-200 dark:bg-gray-900 p-4">
|
||||||
|
<div class="w-full flex justify-center gap-4">
|
||||||
|
<span class="text-gray-700 sm:text-center dark:text-gray-200">Site propulsé grâce à <a href="{{ get_env('GIT_URL') }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Kumora</a></span>
|
||||||
|
<span class="text-gray-500 sm:text-center dark:text-gray-400">
|
||||||
|
Version {{ get_env('APP_VERSION', '0.0.1') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
@ -1,10 +1,10 @@
|
|||||||
<nav class="bg-white border-gray-200 dark:bg-gray-900">
|
<nav class="bg-white border-gray-200 dark:bg-gray-900">
|
||||||
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
<div class="max-w-(--breakpoint-xl) flex flex-wrap items-center justify-between mx-auto p-4">
|
||||||
<a href="{{ path('app_home') }}" class="flex items-center space-x-3 rtl:space-x-reverse">
|
<a href="{{ path('app_home') }}" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||||
<img src="{{ asset('images/logo-kumora.png') }}" class="h-8" alt="Logo Camélia-Studio" />
|
<img src="{{ asset('images/logo-kumora.png') }}" class="h-8" alt="Logo Camélia-Studio" />
|
||||||
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Kumora</span>
|
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Kumora</span>
|
||||||
</a>
|
</a>
|
||||||
<button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-default" aria-expanded="false">
|
<button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-hidden focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-default" aria-expanded="false">
|
||||||
<span class="sr-only">Open main menu</span>
|
<span class="sr-only">Open main menu</span>
|
||||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
|
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"></path>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"></path>
|
||||||
@ -13,13 +13,13 @@
|
|||||||
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
|
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
|
||||||
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_admin_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Administration</a>
|
<a href="{{ path('app_admin_index') }}" class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Administration</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_admin_user_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Gestion des utilisateurs</a>
|
<a href="{{ path('app_admin_user_index') }}" class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Gestion des utilisateurs</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_home') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Retour à l'accueil</a>
|
<a href="{{ path('app_home') }}" class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Retour à l'accueil</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<nav class="bg-white border-gray-200 dark:bg-gray-900">
|
<nav class="bg-white border-gray-200 dark:bg-gray-900">
|
||||||
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
<div class="max-w-(--breakpoint-xl) flex flex-wrap items-center justify-between mx-auto p-4">
|
||||||
<a href="{{ path('app_home') }}" class="flex items-center space-x-3 rtl:space-x-reverse">
|
<a href="{{ path('app_home') }}" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||||
<img src="{{ asset('images/logo-kumora.png') }}" class="h-8" alt="Logo Camélia-Studio" />
|
<img src="{{ asset('images/logo-kumora.png') }}" class="h-8" alt="Logo Camélia-Studio" />
|
||||||
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Kumora</span>
|
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Kumora</span>
|
||||||
</a>
|
</a>
|
||||||
<button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-default" aria-expanded="false">
|
<button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-hidden focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-default" aria-expanded="false">
|
||||||
<span class="sr-only">Open main menu</span>
|
<span class="sr-only">Open main menu</span>
|
||||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
|
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"></path>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"></path>
|
||||||
@ -13,23 +13,39 @@
|
|||||||
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
|
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
|
||||||
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_home') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Accueil</a>
|
<a href="{{ path('app_home') }}" class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Accueil</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_files_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Liste des fichiers</a>
|
<a href="{{ path('app_files_index') }}" class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Liste des fichiers</a>
|
||||||
</li>
|
</li>
|
||||||
{% if not is_granted('IS_AUTHENTICATED_FULLY') %}
|
{% if not is_granted('IS_AUTHENTICATED_FULLY') %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_login') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Se connecter</a>
|
<a href="{{ path('app_login') }}" class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Se connecter</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if is_granted('ROLE_ADMIN') %}
|
{% if is_granted('ROLE_ADMIN') %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_admin_index') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Administration</a>
|
<button id="dropdownNavbarLink" data-dropdown-toggle="dropdownNavbar" class="flex items-center justify-between w-full py-2 px-3 text-gray-900 rounded-xs hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 md:w-auto dark:text-white md:dark:hover:text-blue-500 dark:focus:text-white dark:border-gray-700 dark:hover:bg-gray-700 md:dark:hover:bg-transparent">Administration <svg class="w-2.5 h-2.5 ms-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"></path>
|
||||||
|
</svg></button>
|
||||||
|
<!-- Dropdown menu -->
|
||||||
|
<div id="dropdownNavbar" class="z-10 hidden font-normal bg-white divide-y divide-gray-100 rounded-lg shadow-xs w-44 dark:bg-gray-700 dark:divide-gray-600">
|
||||||
|
<ul class="py-2 text-sm text-gray-700 dark:text-gray-400" aria-labelledby="dropdownLargeButton">
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('app_admin_index') }}" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Tableau de bord</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('app_admin_user_index') }}" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Gestion des utilisateurs</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('app_logout') }}" class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Se déconnecter</a>
|
<a href="{{ path('app_profile') }}" class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Profil</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('app_logout') }}" class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Se déconnecter</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
15
templates/profile/edit_email.html.twig
Normal file
15
templates/profile/edit_email.html.twig
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mx-auto px-16 mt-4">
|
||||||
|
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Modifier mon adresse email</h3>
|
||||||
|
{{ form(form) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Modifier mon email
|
||||||
|
{% endblock %}
|
||||||
|
|
15
templates/profile/edit_password.html.twig
Normal file
15
templates/profile/edit_password.html.twig
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mx-auto px-16 mt-4">
|
||||||
|
<div class="block p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<h3 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Modifier mon mot de passe</h3>
|
||||||
|
{{ form(form) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Modifier mon email
|
||||||
|
{% endblock %}
|
||||||
|
|
44
templates/profile/index.html.twig
Normal file
44
templates/profile/index.html.twig
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Le disque nuagique de Camélia-Studio{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mx-auto px-16">
|
||||||
|
<div class="grid grid-cols-4 gap-8 mt-4">
|
||||||
|
<div class="border border-gray-500 rounded-lg">
|
||||||
|
<div class="p-4">
|
||||||
|
<img src="{{ get_gravatar('toto@titi.fr') }}" alt="Avatar de l'utilisateur" class="rounded-lg">
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center pb-2">
|
||||||
|
{{ user.fullname }}
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-500 p-2 flex justify-between">
|
||||||
|
<div>
|
||||||
|
<span class="font-bold">
|
||||||
|
Email :
|
||||||
|
</span> {{ user.email }}
|
||||||
|
</div>
|
||||||
|
<a href="{{ path('app_profile_email_edit') }}" class="text-white hover:text-gray-200">
|
||||||
|
<twig:ux:icon name="line-md:pencil" class="w-6 h-6" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-500 p-2">
|
||||||
|
<span class="font-bold">
|
||||||
|
Rôle :
|
||||||
|
</span> {{ user.roles[0] == 'ROLE_ADMIN' ? 'Administrateur' : 'Utilisateur' }}
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-500 p-2">
|
||||||
|
<span class="font-bold">
|
||||||
|
Rôle de dossier :
|
||||||
|
</span>
|
||||||
|
{{ user.folderRole.value }}
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-500 py-2 pl-2">
|
||||||
|
<a href="{{ path('app_profile_password_edit') }}" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">Modifier mon mot de passe</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
15
templates/reset_password/check_email.html.twig
Normal file
15
templates/reset_password/check_email.html.twig
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Password Reset Email Sent{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<div class="container mx-auto px-16 mt-4">
|
||||||
|
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<p> Si un compte correspondant à votre adresse e-mail existe, un e-mail contenant un lien que vous pouvez utiliser pour réinitialiser votre mot de passe vient d'être envoyé. Ce lien expirera dans 1 heure.
|
||||||
|
</p>
|
||||||
|
<p>Si vous ne recevez pas d'e-mail, veuillez vérifier votre dossier spam ou <a class="font-medium text-blue-600 dark:text-blue-500 hover:underline" href="{{ path('app_forgot_password_request') }}">réessayer</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
10
templates/reset_password/email.html.twig
Normal file
10
templates/reset_password/email.html.twig
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<h1>Bonjour !</h1>
|
||||||
|
|
||||||
|
<p>Pour réinitialiser ton mot de passe, merci d'utiliser ce lien :</p>
|
||||||
|
|
||||||
|
<a href="{{ url('app_reset_password', {token: resetToken.token}) }}">{{ url('app_reset_password', {token: resetToken.token}) }}</a>
|
||||||
|
|
||||||
|
<p>Ce lien expire dans {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.</p>
|
||||||
|
|
||||||
|
<p>A bientôt</p>
|
||||||
|
<p>Camélia Studio - Kumora</p>
|
27
templates/reset_password/request.html.twig
Normal file
27
templates/reset_password/request.html.twig
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}Reset your password{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="container mx-auto px-16 mt-4">
|
||||||
|
<div class="p-6 bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700">
|
||||||
|
<h5 class="text-2xl font-bold tracking-tight text-gray-900 dark:text-white mb-4">Réinitialiser votre mot de passe</h5>
|
||||||
|
{% for flash_error in app.flashes('reset_password_error') %}
|
||||||
|
<div class="flex items-center p-4 mb-4 text-sm text-red-800 border border-red-300 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400 dark:border-red-800" role="alert">
|
||||||
|
<div>
|
||||||
|
{{ flash_error }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{{ form_start(requestForm) }}
|
||||||
|
{{ form_row(requestForm.email) }}
|
||||||
|
<div class="-mt-3 mb-4">
|
||||||
|
<small>
|
||||||
|
Entrez votre adresse email, et nous vous enverrons un lien pour réinitialiser votre mot de passe.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
{{ form_row(requestForm.submit) }}
|
||||||
|
{{ form_end(requestForm) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user