Ajout de la gestion des rôles sur les dossiers parents + gestion des liens public si le dossier parent est relié au rôle Visiteur (Mais pas possible de lister les fichiers)
Some checks failed
Apply PHP CS Fixer / php-cs-fixer (push) Failing after 22s
CI / build-test (push) Failing after 1m24s
rector / Rector (push) Failing after 1m22s

This commit is contained in:
Melaine Gérard 2025-01-22 20:11:29 +01:00
parent 14c6ce39ab
commit 38f7355d42
15 changed files with 472 additions and 152 deletions

View File

@ -7,12 +7,12 @@
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/dbal": "^4.2.1",
"doctrine/doctrine-bundle": "^2.13.1",
"doctrine/doctrine-migrations-bundle": "^3.3.1",
"doctrine/dbal": "^4.2.2",
"doctrine/doctrine-bundle": "^2.13.2",
"doctrine/doctrine-migrations-bundle": "^3.4.0",
"doctrine/orm": "^3.3.1",
"league/flysystem": "^3.29.1",
"oneup/flysystem-bundle": "^4.12.3",
"oneup/flysystem-bundle": "^4.12.4",
"phpdocumentor/reflection-docblock": "^5.6.1",
"phpstan/phpdoc-parser": "^2.0",
"symfony/apache-pack": "^1.0.1",
@ -50,7 +50,7 @@
"symfony/validator": "7.2.*",
"symfony/web-link": "7.2.*",
"symfony/yaml": "7.2.*",
"symfonycasts/reset-password-bundle": "^1.23",
"symfonycasts/reset-password-bundle": "^1.23.1",
"symfonycasts/tailwind-bundle": "^0.6.1",
"tales-from-a-dev/flowbite-bundle": "^0.7.1",
"twig/extra-bundle": "^2.12|^3.18",
@ -109,16 +109,16 @@
}
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.68",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan-strict-rules": "^2.0",
"phpstan/phpstan-symfony": "^2.0",
"phpunit/phpunit": "^11.5.2",
"rector/rector": "^2.0",
"friendsofphp/php-cs-fixer": "^3.68.1",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan-strict-rules": "^2.0.3",
"phpstan/phpstan-symfony": "^2.0.2",
"phpunit/phpunit": "^11.5.3",
"rector/rector": "^2.0.7",
"symfony/browser-kit": "7.2.*",
"symfony/css-selector": "7.2.*",
"symfony/debug-bundle": "7.2.*",
"symfony/maker-bundle": "^1.61",
"symfony/maker-bundle": "^1.62.1",
"symfony/phpunit-bridge": "^7.2",
"symfony/stopwatch": "7.2.*",
"symfony/web-profiler-bundle": "7.2.*"

38
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d58d5009e09974e2358412fe10fa1005",
"content-hash": "6438b8b149fca5ad2f3c96aa06e93da2",
"packages": [
{
"name": "composer/semver",
@ -9371,16 +9371,16 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.1",
"version": "2.1.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7"
"reference": "7d08f569e582ade182a375c366cbd896eccadd3a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7",
"reference": "cd6e973e04b4c2b94c86e8612b5a65f0da0e08e7",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/7d08f569e582ade182a375c366cbd896eccadd3a",
"reference": "7d08f569e582ade182a375c366cbd896eccadd3a",
"shasum": ""
},
"require": {
@ -9425,20 +9425,20 @@
"type": "github"
}
],
"time": "2025-01-05T16:43:48+00:00"
"time": "2025-01-21T14:54:06+00:00"
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "2.0.2",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "02277ce4b0dd03d74f15282064f8f027d1dec9cc"
"reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/02277ce4b0dd03d74f15282064f8f027d1dec9cc",
"reference": "02277ce4b0dd03d74f15282064f8f027d1dec9cc",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/8b88b5f818bfa301e0c99154ab622dace071c3ba",
"reference": "8b88b5f818bfa301e0c99154ab622dace071c3ba",
"shasum": ""
},
"require": {
@ -9471,28 +9471,28 @@
"description": "Extra strict and opinionated rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.2"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.3"
},
"time": "2025-01-19T13:03:11+00:00"
"time": "2025-01-21T10:52:14+00:00"
},
{
"name": "phpstan/phpstan-symfony",
"version": "2.0.1",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-symfony.git",
"reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4"
"reference": "65f02c7e585f3c7372e42e14d3d87da034031553"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/c08cd8e54a08d651bc402d304cfa161c3c3766c4",
"reference": "c08cd8e54a08d651bc402d304cfa161c3c3766c4",
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/65f02c7e585f3c7372e42e14d3d87da034031553",
"reference": "65f02c7e585f3c7372e42e14d3d87da034031553",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"php": "^7.4 || ^8.0",
"phpstan/phpstan": "^2.0"
"phpstan/phpstan": "^2.1.2"
},
"conflict": {
"symfony/framework-bundle": "<3.0"
@ -9542,9 +9542,9 @@
"description": "Symfony Framework extensions and rules for PHPStan",
"support": {
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
"source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.1"
"source": "https://github.com/phpstan/phpstan-symfony/tree/2.0.2"
},
"time": "2025-01-04T13:58:15+00:00"
"time": "2025-01-21T18:57:07+00:00"
},
{
"name": "phpunit/php-code-coverage",

View File

@ -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');
}
}

View File

@ -1,64 +0,0 @@
<?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 Version20250116210156 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
$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 BLOB 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 TEMPORARY TABLE __temp__user AS SELECT id, email, roles, password FROM user');
$this->addSql('DROP TABLE user');
$this->addSql('CREATE TABLE user (id BLOB NOT NULL, email VARCHAR(255) NOT NULL, roles CLOB NOT NULL, password VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$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)');
$this->addSql('CREATE TEMPORARY TABLE __temp__messenger_messages AS SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM messenger_messages');
$this->addSql('DROP TABLE messenger_messages');
$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('INSERT INTO messenger_messages (id, body, headers, queue_name, created_at, available_at, delivered_at) SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM __temp__messenger_messages');
$this->addSql('DROP TABLE __temp__messenger_messages');
$this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
$this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
}
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('CREATE TEMPORARY TABLE __temp__messenger_messages AS SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM messenger_messages');
$this->addSql('DROP TABLE messenger_messages');
$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('INSERT INTO messenger_messages (id, body, headers, queue_name, created_at, available_at, delivered_at) SELECT id, body, headers, queue_name, created_at, available_at, delivered_at FROM __temp__messenger_messages');
$this->addSql('DROP TABLE __temp__messenger_messages');
$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)');
$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 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('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)');
}
}

View 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');
}
}

View 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');
}
}

View 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)');
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Command;
use App\Entity\User;
use App\Enum\RoleEnum;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
@ -36,6 +37,7 @@ class CreateUserCommand extends Command
$email = $io->ask('Email de l\'utilisateur');
$password = $io->askHidden('Mot de passe de l\'utilisateur');
$isAdmin = $io->confirm('Est-ce un administrateur ?');
$folderRole = $io->choice('Rôle du dossier', array_map(static fn ($role) => $role->value, RoleEnum::cases()), RoleEnum::VISITEUR->value);
try {
$user = $this->userRepository->findOneBy(['email' => $email]);
@ -43,19 +45,18 @@ class CreateUserCommand extends Command
$io->error('Un utilisateur existe déjà avec cet email');
return Command::FAILURE;
}
$user = new User();
$user->setEmail($email);
$user->setPassword($this->passwordHasher->hashPassword($user, $password));
$user->setRoles($isAdmin ? ['ROLE_ADMIN'] : ['ROLE_USER']);
$user->initId();
$user->setFolderRole(RoleEnum::from($folderRole));
$this->entityManager->persist($user);
$this->entityManager->flush();
$io->success('Utilisateur créé avec succès');
} catch (\Exception) {
$io->error('Une erreur est survenue lors de la création de l\'utilisateur');
} catch (\Exception $e) {
$io->error('Une erreur est survenue lors de la création de l\'utilisateur : ' . $e->getMessage());
}
return Command::SUCCESS;

View File

@ -59,7 +59,6 @@ class AdminController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
$role = $form->get('role')->getData();
$user->setRoles([$role]);
$user->initId();
if ($form->has('plainPassword')) {
$plainPassword = $form->get('plainPassword')->getData();

View File

@ -4,9 +4,14 @@ declare(strict_types=1);
namespace App\Controller;
use App\Entity\ParentDirectory;
use App\Entity\User;
use App\Enum\RoleEnum;
use App\Form\CreateDirectoryType;
use App\Form\RenameType;
use App\Form\UploadType;
use App\Repository\ParentDirectoryRepository;
use Doctrine\ORM\EntityManagerInterface;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -22,21 +27,39 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/files', 'app_files_')]
#[IsGranted('ROLE_USER')]
class FilesController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly ParentDirectoryRepository $parentDirectoryRepository
) {
}
/**
* @throws FilesystemException
*/
#[Route('/', name: 'index')]
#[IsGranted('ROLE_USER')]
public function index(Filesystem $defaultAdapter, UrlGeneratorInterface $urlGenerator, #[MapQueryParameter('path')] string $path = ''): Response
{
$path = $this->normalizePath($path);
/**
* @var User $user
*/
$user = $this->getUser();
if ('' !== $path && !$defaultAdapter->directoryExists($path)) {
if ('' !== $path) {
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $path]);
if (null === $parentDir || !$defaultAdapter->directoryExists($path)) {
throw $this->createNotFoundException("Ce dossier n'existe pas !");
}
if ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true)) {
throw $this->createNotFoundException("Vous n'avez pas le droit d'accéder à ce dossier !");
}
}
$files = $defaultAdapter->listContents('/' . $path);
$realFiles = [];
@ -44,6 +67,22 @@ class FilesController extends AbstractController
foreach ($files as $file) {
$filename = basename((string) $file['path']);
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('/', $file['path']);
if ('' !== $path) {
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $pathFile[0]]);
if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) {
continue;
}
} elseif ('file' !== $file['type']) {
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $filename]);
if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) {
continue;
}
}
$realFiles[] = [
'type' => $file['type'],
'path' => $file['path'],
@ -70,10 +109,34 @@ class FilesController extends AbstractController
]);
}
/**
* @throws FilesystemException
*/
#[Route('/file-proxy', name: 'app_file_proxy')]
public function fileProxy(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $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 (RoleEnum::VISITEUR !== $parentDir->getOwnerRole()) {
$this->denyAccessUnlessGranted('ROLE_USER');
/**
* @var User $user
*/
$user = $this->getUser();
if ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true)) {
throw $this->createNotFoundException("Vous n'avez pas le droit d'accéder à ce fichier !");
}
}
$mimetype = $defaultAdapter->mimeType($file);
if ('' === $mimetype) {
$mimetype = 'application/octet-stream';
@ -99,10 +162,25 @@ class FilesController extends AbstractController
* @throws FilesystemException
*/
#[Route('/file-delete', name: 'delete')]
#[IsGranted('ROLE_USER')]
public function fileDelete(Filesystem $defaultAdapter, #[MapQueryParameter('filename')] string $filename): RedirectResponse
{
/**
* @var User $user
*/
$user = $this->getUser();
$file = $this->normalizePath($filename);
$realPath = explode('/', $file);
if (count($realPath) > 1) {
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) {
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);
@ -120,9 +198,21 @@ class FilesController extends AbstractController
* @throws FilesystemException
*/
#[Route('/directory-delete', name: 'delete_directory')]
#[IsGranted('ROLE_USER')]
public function directoryDelete(Filesystem $defaultAdapter, #[MapQueryParameter('path')] string $path): RedirectResponse
{
$path = $this->normalizePath($path);
/**
* @var User $user
*/
$user = $this->getUser();
$realPath = explode('/', $path);
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) {
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);
@ -141,14 +231,29 @@ class FilesController extends AbstractController
* @throws FilesystemException
*/
#[Route('/rename', name: 'rename')]
#[IsGranted('ROLE_USER')]
public function rename(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response
{
$filepath = $this->normalizePath($filepath);
/**
* @var User $user
*/
$user = $this->getUser();
if ('' === $filepath || str_starts_with($filepath, '.') || !$defaultAdapter->fileExists($filepath)) {
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 || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) {
throw $this->createNotFoundException("Vous n'avez pas le droit de renommer ce fichier !");
}
}
$data = [
'newName' => pathinfo($filepath, PATHINFO_BASENAME),
];
@ -183,9 +288,22 @@ class FilesController extends AbstractController
* @throws FilesystemException
*/
#[Route('/create-directory', name: 'create_directory')]
#[IsGranted('ROLE_USER')]
public function createDirectory(Request $request, Filesystem $defaultAdapter, #[MapQueryParameter('base')] string $basePath): Response
{
$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 || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) {
throw $this->createNotFoundException("Vous n'avez pas le droit de créer de sous-dossier dans ce dossier !");
}
}
$form = $this->createForm(CreateDirectoryType::class);
$form->handleRequest($request);
@ -195,10 +313,36 @@ class FilesController extends AbstractController
$name = $data['name'];
if (explode('/', $name) > 1) {
$name = explode('/', $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->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());
$this->entityManager->persist($parentDirectory);
$this->entityManager->flush();
}
$this->addFlash('success', 'Le dossier a bien été créé.');
return $this->redirectToRoute('app_files_index', [
@ -216,9 +360,21 @@ class FilesController extends AbstractController
* @throws FilesystemException
*/
#[Route('/rename-directory', name: 'rename-directory')]
#[IsGranted('ROLE_USER')]
public function renameDirectory(#[MapQueryParameter('path')] string $filepath, Request $request, Filesystem $defaultAdapter): Response
{
$filepath = $this->normalizePath($filepath);
/**
* @var User $user
*/
$user = $this->getUser();
$realPath = explode('/', $filepath);
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) {
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 !");
@ -258,13 +414,30 @@ class FilesController extends AbstractController
* @throws FilesystemException
*/
#[Route('/upload', name: 'upload')]
#[IsGranted('ROLE_USER')]
public function upload(#[MapQueryParameter('path')] string $path, Request $request, Filesystem $defaultAdapter): Response
{
$path = $this->normalizePath($path);
/**
* @var User $user
*/
$user = $this->getUser();
if ('' === $path) {
throw $this->createNotFoundException("Vous ne pouvez pas uploader de fichier à la racine !");
}
$realPath = explode('/', $path);
$parentDir = $this->parentDirectoryRepository->findOneBy(['name' => $realPath[0]]);
if (null === $parentDir || ($parentDir->getOwnerRole() !== $user->getFolderRole() && !in_array($user->getFolderRole(), $parentDir->getOwnerRole()->getHigherRoles(), true))) {
throw $this->createNotFoundException("Vous n'avez pas le droit d'uploader des fichiers dans ce dossier !");
}
$form = $this->createForm(UploadType::class);
if ('' !== $path && !$defaultAdapter->directoryExists($path)) {
if (!$defaultAdapter->directoryExists($path)) {
throw $this->createNotFoundException("Ce dossier n'existe pas !");
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Entity;
use App\Enum\RoleEnum;
use App\Repository\ParentDirectoryRepository;
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;
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;
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Entity;
use App\Enum\RoleEnum;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
@ -18,8 +19,9 @@ use Symfony\Component\Uid\Uuid;
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\Column(type: 'uuid', length: 180)]
private ?Uuid $id = null;
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $email = null;
@ -36,16 +38,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[ORM\Column]
private ?string $password = null;
public function initId(): void
{
if ($this->id instanceof \Symfony\Component\Uid\Uuid) {
return;
}
#[ORM\Column(enumType: RoleEnum::class)]
private ?RoleEnum $folder_role = null;
$this->id = Uuid::v4();
}
public function getId(): ?Uuid
/**
* @return int|null
*/
public function getId(): ?int
{
return $this->id;
}
@ -119,4 +118,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this;
}
public function getFolderRole(): ?RoleEnum
{
return $this->folder_role;
}
public function setFolderRole(RoleEnum $folder_role): static
{
$this->folder_role = $folder_role;
return $this;
}
}

35
src/Enum/RoleEnum.php Normal file
View File

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

View File

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

View File

@ -12,9 +12,11 @@
<a href="{{ path('app_files_create_directory', {
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>
{% if path != '' %}
<a href="{{ path('app_files_upload', {
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>
{% endif %}
</div>
<div class="mt-4">
{% include 'partials/breadbrumb.html.twig' %}