');
- body.append(loader);
+ click = 0;
- container.html('');
+ let container = $('#modal-container');
+ const body = $('body')
- const url = $(e.target).attr('data-modal');
- $(container).modal('show');
+ if (!container.length) {
+ container = $('
');
- container.load(url, function() {
- loader.remove()
- });
+ body.append(container);
+ }
+
+ const loader = $('
');
+ loader.html('
Loading...
');
+ body.append(loader);
+
+ container.html('');
+
+ let url = $(e.target).attr('data-modal');
+
+ if (!url) {
+ url = $(e.target).parents('*[data-modal]').first().attr('data-modal');
+ }
+
+ $(container).modal('show');
+
+ container.load(url, function() {
+ loader.remove()
+ });
+ }, 250)
});
const urlParams = new URLSearchParams(window.location.search)
diff --git a/composer.json b/composer.json
index 84f54e6..0848a6f 100644
--- a/composer.json
+++ b/composer.json
@@ -25,6 +25,7 @@
"scheb/2fa-qr-code": "^5.7",
"sensio/framework-extra-bundle": "^6.1",
"sensiolabs/ansi-to-html": "^1.2",
+ "spe/filesize-extension-bundle": "~2.0.0",
"stof/doctrine-extensions-bundle": "^1.6",
"symfony/apache-pack": "^1.0",
"symfony/asset": "5.2.*",
diff --git a/config/bundles.php b/config/bundles.php
index 5318aa9..096b755 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -23,4 +23,5 @@ return [
Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true],
FOS\JsRoutingBundle\FOSJsRoutingBundle::class => ['all' => true],
Knp\DoctrineBehaviors\DoctrineBehaviorsBundle::class => ['all' => true],
+ SPE\FilesizeExtensionBundle\SPEFilesizeExtensionBundle::class => ['all' => true],
];
diff --git a/config/packages/app.yaml b/config/packages/app.yaml
index 9485b08..50ae286 100644
--- a/config/packages/app.yaml
+++ b/config/packages/app.yaml
@@ -41,6 +41,8 @@ core:
templates:
- {name: "Par défaut", file: "page/text/default.txt.twig"}
+ file_manager:
+
fos_js_routing:
routes_to_expose:
- blog_tech_form_without_javascript
diff --git a/config/packages/security.yaml b/config/packages/security.yaml
index 0c39122..6b62234 100644
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -46,5 +46,6 @@ security:
- { path: ^/resetting, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
- { path: ^/admin/user, roles: ROLE_ADMIN }
+ - { path: ^/admin/file_manager, roles: ROLE_WRITER }
- { path: ^/admin, roles: ROLE_USER }
- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY }
diff --git a/core/Controller/FileManager/FileManagerAdminController.php b/core/Controller/FileManager/FileManagerAdminController.php
new file mode 100644
index 0000000..e6f3626
--- /dev/null
+++ b/core/Controller/FileManager/FileManagerAdminController.php
@@ -0,0 +1,232 @@
+render('@Core/file_manager/index.html.twig');
+ }
+
+ /**
+ * @Route("/api/directory", name="admin_file_manager_api_directory", options={"expose"=true})
+ */
+ public function directory(FsFileManager $manager, Request $request): Response
+ {
+ $files = $manager->list($request->query->get('directory', '/'));
+
+ return $this->json($files);
+ }
+
+ /**
+ * @Route("/info", name="admin_file_manager_info", options={"expose"=true})
+ */
+ public function info(FsFileManager $manager, Request $request): Response
+ {
+ $info = $manager->info($request->query->get('file'));
+
+ if (!$info) {
+ throw $this->createNotFoundException();
+ }
+
+ $path = $manager->getPathUri().'/'.$info->getRelativePathname();
+
+ return $this->render('@Core/file_manager/info.html.twig', [
+ 'info' => $info,
+ 'path' => $path,
+ 'isLocked' => $manager->isLocked($info->getRelativePathname()),
+ ]);
+ }
+
+ /**
+ * @Route("/directory/new", name="admin_file_manager_directory_new", options={"expose"=true}, methods={"GET", "POST"})
+ */
+ public function directoryNew(FsFileManager $manager, Request $request): Response
+ {
+ $info = $manager->info($request->query->get('file'));
+
+ if (!$info) {
+ throw $this->createNotFoundException();
+ }
+
+ if (!$info->isDir()) {
+ throw $this->createNotFoundException();
+ }
+
+ if ($manager->isLocked($request->query->get('file'))) {
+ return $this->render('@Core/file_manager/directory_new.html.twig', [
+ 'locked' => true,
+ ]);
+ }
+ $form = $this->createForm(DirectoryCreateType::class);
+
+ if ($request->isMethod('POST')) {
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $status = $manager->createDirectory($form->get('name')->getData(), $request->query->get('file'));
+
+ if (true === $status) {
+ $this->addFlash('success', 'Directory created.');
+ } else {
+ $this->addFlash('warning', 'Directory not created.');
+ }
+ } else {
+ $this->addFlash('warning', 'Unauthorized char(s).');
+ }
+
+ return $this->redirectToRoute('admin_file_manager_index', [
+ 'path' => $info->getRelativePath(),
+ ]);
+ }
+
+ return $this->render('@Core/file_manager/directory_new.html.twig', [
+ 'form' => $form->createView(),
+ 'file' => $request->query->get('file'),
+ 'locked' => false,
+ ]);
+ }
+
+ /**
+ * @Route("/directory/rename", name="admin_file_manager_directory_rename", methods={"GET", "POST"})
+ */
+ public function directoryRename(FsFileManager $manager, Request $request): Response
+ {
+ $info = $manager->info($request->query->get('file'));
+
+ if (!$info) {
+ throw $this->createNotFoundException();
+ }
+
+ if (!$info->isDir()) {
+ throw $this->createNotFoundException();
+ }
+
+ if ($manager->isLocked($request->query->get('file'))) {
+ return $this->render('@Core/file_manager/directory_rename.html.twig', [
+ 'locked' => true,
+ ]);
+ }
+
+ $form = $this->createForm(DirectoryRenameType::class);
+
+ if ($request->isMethod('POST')) {
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $status = $manager->renameDirectory($form->get('name')->getData(), $request->query->get('file'));
+
+ if (true === $status) {
+ $this->addFlash('success', 'Directory renamed.');
+ } else {
+ $this->addFlash('warning', 'Directory not renamed.');
+ }
+ } else {
+ $this->addFlash('warning', 'Unauthorized char(s).');
+ }
+
+ return $this->redirectToRoute('admin_file_manager_index', [
+ 'path' => $info->getRelativePath(),
+ ]);
+ }
+
+ return $this->render('@Core/file_manager/directory_rename.html.twig', [
+ 'form' => $form->createView(),
+ 'file' => $request->query->get('file'),
+ 'locked' => false,
+ ]);
+ }
+
+ /**
+ * @Route("/upload", name="admin_file_manager_upload", options={"expose"=true}, methods={"GET", "POST"})
+ */
+ public function upload(FsFileManager $manager, Request $request): Response
+ {
+ $info = $manager->info($request->query->get('file'));
+
+ if (!$info) {
+ throw $this->createNotFoundException();
+ }
+
+ if (!$info->isDir()) {
+ throw $this->createAccessDeniedException();
+ }
+
+ if ($manager->isLocked($request->query->get('file'))) {
+ return $this->render('@Core/file_manager/upload.html.twig', [
+ 'locked' => true,
+ ]);
+ }
+ $form = $this->createForm(FileUploadType::class, null, [
+ 'mimes' => $manager->getMimes(),
+ ]);
+
+ if ($request->isMethod('POST')) {
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $manager->upload($form->get('files')->getData(), $request->query->get('file'));
+
+ $this->addFlash('success', 'Files uploaded.');
+ } else {
+ $this->addFlash('warning', 'Unauthorized file type(s).');
+ }
+
+ return $this->redirectToRoute('admin_file_manager_index', [
+ 'path' => $info->getRelativePath(),
+ ]);
+ }
+
+ return $this->render('@Core/file_manager/upload.html.twig', [
+ 'form' => $form->createView(),
+ 'file' => $request->query->get('file'),
+ 'locked' => false,
+ ]);
+ }
+
+ /**
+ * @Route("/delete", name="admin_file_manager_delete", methods={"DELETE"})
+ */
+ public function delete(FsFileManager $manager, Request $request): Response
+ {
+ $path = $request->request->get('file');
+ $info = $manager->info($request->request->get('file'));
+
+ if (!$info) {
+ throw $this->createNotFoundException();
+ }
+
+ if ($this->isCsrfTokenValid('delete', $request->request->get('_token'))) {
+ if ($manager->delete($path)) {
+ $this->addFlash('success', 'The data has been removed.');
+ } else {
+ $this->addFlash('warning', 'The data has not been removed.');
+ }
+ }
+
+ return $this->redirectToRoute('admin_file_manager_index', [
+ 'path' => $info->getRelativePath(),
+ ]);
+ }
+
+ protected function getSection(): string
+ {
+ return 'file_manager';
+ }
+}
diff --git a/core/DependencyInjection/Configuration.php b/core/DependencyInjection/Configuration.php
index 62a1e83..acc89f0 100644
--- a/core/DependencyInjection/Configuration.php
+++ b/core/DependencyInjection/Configuration.php
@@ -9,6 +9,30 @@ class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
+ $defaultMimetypes = [
+ 'image/png',
+ 'image/jpg',
+ 'image/jpeg',
+ 'image/gif',
+ 'application/pdf',
+ 'application/ogg',
+ 'video/mp4',
+ 'application/zip',
+ 'multipart/x-zip',
+ 'application/rar',
+ 'application/x-rar-compressed',
+ 'application/x-zip-compressed',
+ 'application/tar',
+ 'application/x-tar',
+ 'text/plain',
+ 'text/x-asm',
+ 'application/octet-stream'
+ ];
+
+ $defaultLocked = [
+ '%kernel.project_dir%/public/uploads',
+ ];
+
$treeBuilder = new TreeBuilder('core');
$treeBuilder->getRootNode()
@@ -16,45 +40,70 @@ class Configuration implements ConfigurationInterface
->arrayNode('site')
->children()
->scalarNode('name')
+ ->defaultValue('Murph')
->isRequired()
->cannotBeEmpty()
->end()
->scalarNode('logo')
+ ->defaultValue('build/images/core/logo.svg')
->isRequired()
->cannotBeEmpty()
->end()
->arrayNode('controllers')
->prototype('array')
- ->children()
- ->scalarNode('name')
- ->cannotBeEmpty()
+ ->children()
+ ->scalarNode('name')
+ ->cannotBeEmpty()
+ ->end()
+ ->scalarNode('action')
+ ->cannotBeEmpty()
+ ->end()
->end()
- ->scalarNode('action')
- ->cannotBeEmpty()
- ->end()
- ->end()
->end()
->end()
->arrayNode('pages')
->prototype('array')
- ->children()
- ->scalarNode('name')
- ->isRequired()
- ->cannotBeEmpty()
- ->end()
- ->arrayNode('templates')
- ->prototype('array')
- ->children()
- ->scalarNode('name')
- ->cannotBeEmpty()
- ->end()
- ->scalarNode('file')
- ->cannotBeEmpty()
+ ->children()
+ ->scalarNode('name')
+ ->isRequired()
+ ->cannotBeEmpty()
+ ->end()
+ ->arrayNode('templates')
+ ->prototype('array')
+ ->children()
+ ->scalarNode('name')
+ ->cannotBeEmpty()
+ ->end()
+ ->scalarNode('file')
+ ->cannotBeEmpty()
+ ->end()
+ ->end()
->end()
->end()
->end()
->end()
+ ->end()
+ ->end()
+ ->end()
+ ->arrayNode('file_manager')
+ ->children()
+ ->arrayNode('mimes')
+ ->scalarPrototype()
->end()
+ ->defaultValue($defaultMimetypes)
+ ->end()
+ ->scalarNode('path')
+ ->defaultValue('%kernel.project_dir%/public/uploads')
+ ->cannotBeEmpty()
+ ->end()
+ ->scalarNode('path_uri')
+ ->defaultValue('/uploads')
+ ->cannotBeEmpty()
+ ->end()
+ ->arrayNode('path_locked')
+ ->scalarPrototype()
+ ->end()
+ ->defaultValue($defaultLocked)
->end()
->end()
->end()
diff --git a/core/FileManager/FsFileManager.php b/core/FileManager/FsFileManager.php
new file mode 100644
index 0000000..ed86029
--- /dev/null
+++ b/core/FileManager/FsFileManager.php
@@ -0,0 +1,230 @@
+
+ */
+class FsFileManager
+{
+ protected array $mimes;
+ protected string $path;
+ protected string $pathUri;
+ protected array $pathLocked;
+ protected FileUploadHandler $uploadHandler;
+
+ public function __construct(ParameterBagInterface $params, FileUploadHandler $uploadHandler)
+ {
+ $config = $params->get('core')['file_manager'];
+
+ $this->uploadHandler = $uploadHandler;
+ $this->mimes = $config['mimes'];
+ $this->path = $config['path'];
+ $this->pathUri = $this->normalizePath($config['path_uri']);
+
+ foreach ($config['path_locked'] as $k => $v) {
+ $config['path_locked'][$k] = sprintf('/%s/', $this->normalizePath($v));
+ }
+
+ $this->pathLocked = $config['path_locked'];
+ }
+
+ public function list(string $directory): array
+ {
+ $directory = $this->normalizePath($directory);
+
+ $breadcrumb = ['/'];
+
+ if ($directory) {
+ $breadcrumb = array_merge(
+ $breadcrumb,
+ explode('/', $directory)
+ );
+ }
+
+ $data = [
+ 'breadcrumb' => $breadcrumb,
+ 'parent' => dirname($directory),
+ 'directories' => [],
+ 'files' => [],
+ ];
+
+ $finder = new Finder();
+ $finder->directories()->depth('== 0')->in($this->path.'/'.$directory);
+
+ foreach ($finder as $file) {
+ $data['directories'][] = [
+ 'basename' => $file->getBasename(),
+ 'path' => $directory.'/'.$file->getBasename(),
+ 'locked' => $this->isLocked($directory.'/'.$file->getBasename()),
+ 'mime' => null,
+ ];
+ }
+
+ $finder = new Finder();
+ $finder->files()->depth('== 0')->in($this->path.'/'.$directory);
+
+ foreach ($finder as $file) {
+ $data['files'][] = [
+ 'basename' => $file->getBasename(),
+ 'path' => $directory,
+ 'locked' => $this->isLocked($directory.'/'.$file->getBasename()),
+ 'mime' => mime_content_type($file->getRealPath()),
+ ];
+ }
+
+ return $data;
+ }
+
+ public function info(string $path): ?SplFileInfo
+ {
+ $path = $this->normalizePath($path);
+
+ if ('' === $path) {
+ return new SplFileInfo($this->path, '', '');
+ }
+
+ $finder = new Finder();
+ $finder->in($this->path)
+ ->name(basename($path))
+ ;
+
+ $dirname = dirname($path);
+
+ if ('.' === $dirname) {
+ $dirname = '';
+ }
+
+ foreach ($finder as $file) {
+ if ($file->getRelativePath() === $dirname) {
+ return $file;
+ }
+ }
+
+ return null;
+ }
+
+ public function createDirectory(string $name, string $path): bool
+ {
+ $file = $this->info($path);
+
+ if (!$file || $this->isLocked($path)) {
+ return false;
+ }
+
+ $filesystem = new Filesystem();
+ $path = $file->getPathname().'/'.$this->normalizePath($name);
+
+ if ($filesystem->exists($path)) {
+ return false;
+ }
+
+ $filesystem->mkdir($path, 0700);
+
+ return true;
+ }
+
+ public function renameDirectory(string $name, string $path): bool
+ {
+ $file = $this->info($path);
+
+ if (!$file || $this->isLocked($path)) {
+ return false;
+ }
+
+ $filesystem = new Filesystem();
+ $newPath = $file->getPath().'/'.$this->normalizePath($name);
+
+ if ($filesystem->exists($newPath)) {
+ return false;
+ }
+
+ $filesystem->rename($file->getPathName(), $newPath);
+
+ return true;
+ }
+
+ public function upload($files, string $path)
+ {
+ if ($files instanceof UploadedFile) {
+ $files = [$files];
+ }
+
+ foreach ($files as $file) {
+ $this->uploadHandler->handleForm($file, $this->path.'/'.$path, null, true);
+ }
+ }
+
+ public function delete(string $path): bool
+ {
+ $file = $this->info($path);
+
+ if ($this->isLocked($file)) {
+ return false;
+ }
+
+ if ($file) {
+ $filesystem = new Filesystem();
+ $filesystem->remove($file);
+ }
+
+ return false;
+ }
+
+ public function isLocked($path): bool
+ {
+ $file = $this->info($path);
+
+ if (!$file) {
+ return false;
+ }
+
+ foreach ($this->pathLocked as $lock) {
+ if (u($file->getPathName().'/')->startsWith($lock)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+ public function getPathUri(): string
+ {
+ return $this->pathUri;
+ }
+
+ public function getMimes(): array
+ {
+ return $this->mimes;
+ }
+
+ public function getPathLocked(): array
+ {
+ return $this->pathLocked;
+ }
+
+ protected function normalizePath(string $path): string
+ {
+ return (string) u($path)
+ ->replace('..', '.')
+ ->replaceMatches('#/{2,}#', '/')
+ ->replaceMatches('#^.$#', '')
+ ->trim('/')
+ ->trim()
+ ;
+ }
+}
diff --git a/core/Form/FileManager/DirectoryCreateType.php b/core/Form/FileManager/DirectoryCreateType.php
new file mode 100644
index 0000000..61971d8
--- /dev/null
+++ b/core/Form/FileManager/DirectoryCreateType.php
@@ -0,0 +1,43 @@
+add(
+ 'name',
+ TextType::class,
+ [
+ 'label' => 'Name',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new Regex([
+ 'pattern' => '#['.preg_quote('\\/?%*:|"<>').'\t\n\r]+#',
+ 'match' => false,
+ ]),
+ new NotBlank(),
+ ],
+ ]
+ );
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ ]);
+ }
+}
diff --git a/core/Form/FileManager/DirectoryRenameType.php b/core/Form/FileManager/DirectoryRenameType.php
new file mode 100644
index 0000000..fb70e8a
--- /dev/null
+++ b/core/Form/FileManager/DirectoryRenameType.php
@@ -0,0 +1,43 @@
+add(
+ 'name',
+ TextType::class,
+ [
+ 'label' => 'Name',
+ 'required' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new Regex([
+ 'pattern' => '#['.preg_quote('\\/?%*:|"<>').'\t\n\r]+#',
+ 'match' => false,
+ ]),
+ new NotBlank(),
+ ],
+ ]
+ );
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ ]);
+ }
+}
diff --git a/core/Form/FileManager/FileUploadType.php b/core/Form/FileManager/FileUploadType.php
new file mode 100644
index 0000000..6c53287
--- /dev/null
+++ b/core/Form/FileManager/FileUploadType.php
@@ -0,0 +1,42 @@
+add(
+ 'files',
+ FileType::class,
+ [
+ 'label' => 'Files',
+ 'required' => true,
+ 'multiple' => true,
+ 'attr' => [
+ ],
+ 'constraints' => [
+ new All([
+ new File([
+ 'mimeTypes' => $options['mimes'],
+ ]),
+ ]),
+ ],
+ ]
+ );
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'mimes' => [],
+ ]);
+ }
+}
diff --git a/core/Form/FileUploadHandler.php b/core/Form/FileUploadHandler.php
index 1378b32..68b9b90 100644
--- a/core/Form/FileUploadHandler.php
+++ b/core/Form/FileUploadHandler.php
@@ -11,18 +11,25 @@ use Symfony\Component\HttpFoundation\File\UploadedFile;
*/
class FileUploadHandler
{
- public function handleForm(?UploadedFile $uploadedFile, string $path, callable $afterUploadCallback): void
+ public function handleForm(?UploadedFile $uploadedFile, string $path, ?callable $afterUploadCallback = null, bool $keepOriginalFilename = false): void
{
if (null === $uploadedFile) {
return;
}
$originalFilename = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
- $safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
- $filename = date('Ymd-his').$safeFilename.'.'.$uploadedFile->guessExtension();
+
+ if ($keepOriginalFilename) {
+ $filename = $originalFilename.'.'.$uploadedFile->guessExtension();
+ } else {
+ $safeFilename = transliterator_transliterate('Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()', $originalFilename);
+ $filename = date('Ymd-his').$safeFilename.'.'.$uploadedFile->guessExtension();
+ }
$uploadedFile->move($path, $filename);
- $afterUploadCallback($filename);
+ if ($afterUploadCallback) {
+ $afterUploadCallback($filename);
+ }
}
}
diff --git a/core/Resources/translations/messages.fr.yaml b/core/Resources/translations/messages.fr.yaml
index 1380aad..66ab7b9 100644
--- a/core/Resources/translations/messages.fr.yaml
+++ b/core/Resources/translations/messages.fr.yaml
@@ -12,6 +12,7 @@
"Error": "Erreur"
"The data has been saved.": "Les données ont été sauvegardées."
"The data has been removed.": "Les données ont été supprimées."
+"The data has not been removed.": "Les données n'ont pas été supprimées."
"You must add a navigation.": "Vous devez ajouter une navigation."
"E-mail sent.": "E-mail envoyé"
"name": "Nom"
@@ -165,3 +166,22 @@
"For selection": "Pour la sélection"
"For all items": "Pour tous les éléments"
"Run": "Lancer"
+"Download": "Télécharger"
+"Absolute URL": "URL absolue"
+"Relative URL": "URL relative"
+"Creation date": "Date de création"
+"Modification date": "Date de modification"
+"File size": "Poids du fichier"
+"File manager": "Gestionnaire de fichiers"
+"File(s) uploaded.": "Fichier(s) déposé(s)."
+"Unauthorized file type(s).": "Type(s) de fichier non autorisé(s)"
+"Directory locked.": "Répertoire verrouillé."
+"File locked.": "Fichier verrouillé."
+"Upload files": "Envoyer des fichiers"
+"Files": "Fichiers"
+"Directory created.": "Répertoire créé."
+"Directory not created.": "Répertoire non créé."
+"Directory renamed.": "Répertoire renommé."
+"Directory not renamed.": "Répertoire non renommé."
+"Rename": "Renommer"
+"Rename directory": "Renommer le répertoire"
diff --git a/core/Resources/views/admin/module/menu.html.twig b/core/Resources/views/admin/module/menu.html.twig
index 6e8004f..2c05f7f 100644
--- a/core/Resources/views/admin/module/menu.html.twig
+++ b/core/Resources/views/admin/module/menu.html.twig
@@ -47,6 +47,16 @@
+
+
+
+
{% endif %}
diff --git a/core/Resources/views/file_manager/_form.html.twig b/core/Resources/views/file_manager/_form.html.twig
new file mode 100644
index 0000000..d5e483e
--- /dev/null
+++ b/core/Resources/views/file_manager/_form.html.twig
@@ -0,0 +1 @@
+{{ form_widget(form) }}
diff --git a/core/Resources/views/file_manager/directory_new.html.twig b/core/Resources/views/file_manager/directory_new.html.twig
new file mode 100644
index 0000000..8b6e6dd
--- /dev/null
+++ b/core/Resources/views/file_manager/directory_new.html.twig
@@ -0,0 +1,33 @@
+
+
+
+
+ {% if locked %}
+
+
+
+
+
+ {{ 'Directory locked.'|trans }}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
diff --git a/core/Resources/views/file_manager/directory_rename.html.twig b/core/Resources/views/file_manager/directory_rename.html.twig
new file mode 100644
index 0000000..15fc731
--- /dev/null
+++ b/core/Resources/views/file_manager/directory_rename.html.twig
@@ -0,0 +1,33 @@
+
+
+
+
+ {% if locked %}
+
+
+
+
+
+ {{ 'Directory locked.'|trans }}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
diff --git a/core/Resources/views/file_manager/index.html.twig b/core/Resources/views/file_manager/index.html.twig
new file mode 100644
index 0000000..cc4e0f2
--- /dev/null
+++ b/core/Resources/views/file_manager/index.html.twig
@@ -0,0 +1,16 @@
+{% extends '@Core/admin/layout.html.twig' %}
+
+{% block body %}
+
+
+
+
+ {{ 'File manager'|trans }}
+
+
+
+
+
+
+
+{% endblock %}
diff --git a/core/Resources/views/file_manager/info.html.twig b/core/Resources/views/file_manager/info.html.twig
new file mode 100644
index 0000000..8ea00d7
--- /dev/null
+++ b/core/Resources/views/file_manager/info.html.twig
@@ -0,0 +1,93 @@
+
+
+
+
+ {% if info.type == 'file' %}
+
+
+
+
+
+
+
+
+
+ {% endif %}
+
+
+ {% if info.type == 'file' %}
+ -
+ {{ 'File size'|trans }}
+
+
+
+ {% endif %}
+
+ -
+ {{ 'Creation date'|trans }}
+
+
+
+ -
+ {{ 'Modification date'|trans }}
+
+
+
+
+
+ {% if info.extension in ['jpeg', 'jpg', 'gif', 'png', 'svg'] %}
+
+ {% endif %}
+
+
+
+
+
+{% if not isLocked %}
+
+{% endif %}
diff --git a/core/Resources/views/file_manager/upload.html.twig b/core/Resources/views/file_manager/upload.html.twig
new file mode 100644
index 0000000..15c40ff
--- /dev/null
+++ b/core/Resources/views/file_manager/upload.html.twig
@@ -0,0 +1,33 @@
+
+
+
+
+ {% if locked %}
+
+
+
+
+
+ {{ 'Directory locked.'|trans }}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
diff --git a/core/Resources/views/form/bootstrap_4_form_theme.html.twig b/core/Resources/views/form/bootstrap_4_form_theme.html.twig
index ad956d5..ef80b40 100644
--- a/core/Resources/views/form/bootstrap_4_form_theme.html.twig
+++ b/core/Resources/views/form/bootstrap_4_form_theme.html.twig
@@ -24,7 +24,7 @@
diff --git a/package.json b/package.json
index 5cb5929..d529b8a 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,8 @@
"regenerator-runtime": "^0.13.2",
"sass-loader": "^11.0.0",
"stimulus": "^2.0.0",
+ "vue-loader": "^15.9.5",
+ "vue-template-compiler": "^2.6.14",
"webpack-notifier": "^1.6.0"
},
"license": "UNLICENSED",
@@ -20,6 +22,7 @@
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.11.2",
+ "axios": "^0.21.1",
"bootstrap": "^4.3.1",
"choices.js": "^9.0.1",
"flag-icon-css": "^3.5.0",
@@ -35,6 +38,7 @@
"sortablejs": "^1.13.0",
"tinymce": "^5.7.1",
"vanillajs-datepicker": "^1.1.4",
+ "vue": "^2.6.14",
"wire.css": "^1.2.5",
"zxcvbn": "^4.4.2"
}
diff --git a/public/js/fos_js_routes.json b/public/js/fos_js_routes.json
index f2bc483..67abbc6 100644
--- a/public/js/fos_js_routes.json
+++ b/public/js/fos_js_routes.json
@@ -1 +1 @@
-{"base_url":"","routes":{"api_blog_comment_preview":{"tokens":[["text","\/api\/blog\/comment\/preview"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"blog_tech_form_without_javascript":{"tokens":[["text","\/nojs"]],"defaults":[],"requirements":[],"hosttokens":[["text","local.deblan"]],"methods":[],"schemes":[]}},"prefix":"","host":"localhost","port":"","scheme":"http","locale":[]}
\ No newline at end of file
+{"base_url":"","routes":{"api_blog_comment_preview":{"tokens":[["text","\/api\/blog\/comment\/preview"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"admin_file_manager_api_directory":{"tokens":[["text","\/admin\/file_manager\/api\/directory"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"admin_file_manager_info":{"tokens":[["text","\/admin\/file_manager\/info"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":[],"schemes":[]},"admin_file_manager_directory_new":{"tokens":[["text","\/admin\/file_manager\/directory\/new"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":["GET","POST"],"schemes":[]},"admin_file_manager_upload":{"tokens":[["text","\/admin\/file_manager\/upload"]],"defaults":[],"requirements":[],"hosttokens":[],"methods":["GET","POST"],"schemes":[]},"blog_tech_form_without_javascript":{"tokens":[["text","\/nojs"]],"defaults":[],"requirements":{"_locale":"fr"},"hosttokens":[["text","local.deblan"]],"methods":[],"schemes":[]}},"prefix":"","host":"localhost","port":"","scheme":"http","locale":[]}
\ No newline at end of file
diff --git a/symfony.lock b/symfony.lock
index 4cc1c58..ece326b 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -247,6 +247,9 @@
"config/packages/ansi_to_html.yaml"
]
},
+ "spe/filesize-extension-bundle": {
+ "version": "2.0.0"
+ },
"spomky-labs/otphp": {
"version": "v10.0.1"
},
diff --git a/webpack.config.js b/webpack.config.js
index 9fb2e2b..a75a871 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -42,6 +42,7 @@ Encore
.enableSourceMaps(!Encore.isProduction())
// enables hashed filenames (e.g. app.abc123.css)
.enableVersioning(Encore.isProduction())
+ .enableVueLoader()
.configureBabel((config) => {
config.plugins.push('@babel/plugin-proposal-class-properties');
diff --git a/yarn.lock b/yarn.lock
index 6697753..5d0f13a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1028,6 +1028,22 @@
"@types/webpack-sources" "*"
source-map "^0.6.0"
+"@vue/component-compiler-utils@^3.1.0":
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.2.2.tgz#2f7ed5feed82ff7f0284acc11d525ee7eff22460"
+ integrity sha512-rAYMLmgMuqJFWAOb3Awjqqv5X3Q3hVr4jH/kgrFJpiU0j3a90tnNBplqbj+snzrgZhC9W128z+dtgMifOiMfJg==
+ dependencies:
+ consolidate "^0.15.1"
+ hash-sum "^1.0.2"
+ lru-cache "^4.1.2"
+ merge-source-map "^1.1.0"
+ postcss "^7.0.36"
+ postcss-selector-parser "^6.0.2"
+ source-map "~0.6.1"
+ vue-template-es2015-compiler "^1.9.0"
+ optionalDependencies:
+ prettier "^1.18.2"
+
"@webassemblyjs/ast@1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f"
@@ -1414,6 +1430,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
+axios@^0.21.1:
+ version "0.21.1"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
+ integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
+ dependencies:
+ follow-redirects "^1.10.0"
+
babel-loader@^8.0.0:
version "8.2.2"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81"
@@ -1489,6 +1512,11 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
+bluebird@^3.1.1:
+ version "3.7.2"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
+ integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
+
body-parser@1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
@@ -1905,6 +1933,13 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+consolidate@^0.15.1:
+ version "0.15.1"
+ resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
+ integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==
+ dependencies:
+ bluebird "^3.1.1"
+
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
@@ -2178,6 +2213,11 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
+de-indent@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
+ integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
+
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -2788,6 +2828,11 @@ follow-redirects@^1.0.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
+follow-redirects@^1.10.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
+ integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
+
foreach@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
@@ -3061,6 +3106,16 @@ has@^1.0.0, has@^1.0.3:
dependencies:
function-bind "^1.1.1"
+hash-sum@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
+ integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
+
+he@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
hex-color-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
@@ -3700,7 +3755,7 @@ loader-utils@1.2.3:
emojis-list "^2.0.0"
json5 "^1.0.1"
-loader-utils@^1.4.0:
+loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
@@ -3773,7 +3828,7 @@ lozad@^1.16.0:
resolved "https://registry.yarnpkg.com/lozad/-/lozad-1.16.0.tgz#86ce732c64c69926ccdebb81c8c90bb3735948b4"
integrity sha512-JBr9WjvEFeKoyim3svo/gsQPTkgG/mOHJmDctZ/+U9H3ymUuvEkqpn8bdQMFsvTMcyRJrdJkLv0bXqGm0sP72w==
-lru-cache@^4.0.1:
+lru-cache@^4.0.1, lru-cache@^4.1.2:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
@@ -3863,6 +3918,13 @@ merge-descriptors@1.0.1:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+merge-source-map@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
+ integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
+ dependencies:
+ source-map "^0.6.1"
+
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -4830,6 +4892,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.27:
source-map "^0.6.1"
supports-color "^6.1.0"
+postcss@^7.0.36:
+ version "7.0.36"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.36.tgz#056f8cffa939662a8f5905950c07d5285644dfcb"
+ integrity sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==
+ dependencies:
+ chalk "^2.4.2"
+ source-map "^0.6.1"
+ supports-color "^6.1.0"
+
postcss@^8.2.8:
version "8.2.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece"
@@ -4839,6 +4910,11 @@ postcss@^8.2.8:
nanoid "^3.1.20"
source-map "^0.6.1"
+prettier@^1.18.2:
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
+ integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
+
pretty-error@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-3.0.3.tgz#57f4b16aebcadbd681db2c002ae0f95bf87b24cf"
@@ -6143,6 +6219,48 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
+vue-hot-reload-api@^2.3.0:
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
+ integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
+
+vue-loader@^15.9.5:
+ version "15.9.7"
+ resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.7.tgz#15b05775c3e0c38407679393c2ce6df673b01044"
+ integrity sha512-qzlsbLV1HKEMf19IqCJqdNvFJRCI58WNbS6XbPqK13MrLz65es75w392MSQ5TsARAfIjUw+ATm3vlCXUJSOH9Q==
+ dependencies:
+ "@vue/component-compiler-utils" "^3.1.0"
+ hash-sum "^1.0.2"
+ loader-utils "^1.1.0"
+ vue-hot-reload-api "^2.3.0"
+ vue-style-loader "^4.1.0"
+
+vue-style-loader@^4.1.0:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
+ integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==
+ dependencies:
+ hash-sum "^1.0.2"
+ loader-utils "^1.0.2"
+
+vue-template-compiler@^2.6.14:
+ version "2.6.14"
+ resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763"
+ integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==
+ dependencies:
+ de-indent "^1.0.2"
+ he "^1.1.0"
+
+vue-template-es2015-compiler@^1.9.0:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
+ integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
+
+vue@^2.6.14:
+ version "2.6.14"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.14.tgz#e51aa5250250d569a3fbad3a8a5a687d6036e235"
+ integrity sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ==
+
watchpack@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7"