svg
This commit is contained in:
@ -3,11 +3,18 @@
|
|||||||
"license": "proprietary",
|
"license": "proprietary",
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "vcs",
|
||||||
|
"url": "https://github.com/afornerot/bNine-FilesBundle"
|
||||||
|
}
|
||||||
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.2",
|
"php": ">=8.2",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"apereo/phpcas": "^1.6",
|
"apereo/phpcas": "^1.6",
|
||||||
|
"bnine/files-bundle": "^1.0",
|
||||||
"doctrine/dbal": "^3",
|
"doctrine/dbal": "^3",
|
||||||
"doctrine/doctrine-bundle": "^2.13",
|
"doctrine/doctrine-bundle": "^2.13",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.3",
|
"doctrine/doctrine-migrations-bundle": "^3.3",
|
||||||
|
36
composer.lock
generated
36
composer.lock
generated
@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "236661a47d3b0278e1198c4fb0940e3a",
|
"content-hash": "607d2ea75ad35d6ff9e6f5cd12d12c62",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "apereo/phpcas",
|
"name": "apereo/phpcas",
|
||||||
@ -77,6 +77,40 @@
|
|||||||
},
|
},
|
||||||
"time": "2023-02-19T19:52:35+00:00"
|
"time": "2023-02-19T19:52:35+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "bnine/files-bundle",
|
||||||
|
"version": "v1.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/afornerot/bNine-FilesBundle.git",
|
||||||
|
"reference": "f3a0b55f9b64930bb8cb9fa53dd7cb8799796014"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/afornerot/bNine-FilesBundle/zipball/f3a0b55f9b64930bb8cb9fa53dd7cb8799796014",
|
||||||
|
"reference": "f3a0b55f9b64930bb8cb9fa53dd7cb8799796014",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"symfony/framework-bundle": "^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"type": "symfony-bundle",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Bnine\\FilesBundle\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "Symfony bundle for file-browser",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/afornerot/bNine-FilesBundle/tree/v1.0.1",
|
||||||
|
"issues": "https://github.com/afornerot/bNine-FilesBundle/issues"
|
||||||
|
},
|
||||||
|
"time": "2025-07-30T20:35:03+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
"version": "0.13.1",
|
"version": "0.13.1",
|
||||||
|
@ -15,4 +15,5 @@ return [
|
|||||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||||
Oneup\UploaderBundle\OneupUploaderBundle::class => ['all' => true],
|
Oneup\UploaderBundle\OneupUploaderBundle::class => ['all' => true],
|
||||||
FOS\RestBundle\FOSRestBundle::class => ['all' => true],
|
FOS\RestBundle\FOSRestBundle::class => ['all' => true],
|
||||||
|
Bnine\FilesBundle\BnineFilesBundle::class => ['all' => true],
|
||||||
];
|
];
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Controller;
|
|
||||||
|
|
||||||
use App\Service\FileService;
|
|
||||||
use Oneup\UploaderBundle\Uploader\Response\ResponseInterface;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
|
||||||
|
|
||||||
class FileController extends AbstractController
|
|
||||||
{
|
|
||||||
private FileService $fileService;
|
|
||||||
|
|
||||||
public function __construct(FileService $fileService)
|
|
||||||
{
|
|
||||||
$this->fileService = $fileService;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/user/file/{domain}/{id}/{editable}', name: 'app_files', methods: ['GET'])]
|
|
||||||
public function browse(string $domain, int $id, int $editable, Request $request): Response
|
|
||||||
{
|
|
||||||
$relativePath = $request->query->get('path', '');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$files = $this->fileService->list($domain, (string) $id, $relativePath);
|
|
||||||
|
|
||||||
return $this->render('file/browse.html.twig', [
|
|
||||||
'domain' => $domain,
|
|
||||||
'id' => $id,
|
|
||||||
'files' => $files,
|
|
||||||
'path' => $relativePath,
|
|
||||||
'editable' => $editable,
|
|
||||||
]);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$this->addFlash('danger', $e->getMessage());
|
|
||||||
dd($e->getMessage());
|
|
||||||
|
|
||||||
return $this->redirectToRoute('app_files', [
|
|
||||||
'domain' => $domain,
|
|
||||||
'id' => $id,
|
|
||||||
'editable' => $editable,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/user/uploadmodal/{domain}/{id}', name: 'app_files_uploadmodal', methods: ['GET'])]
|
|
||||||
public function uploadmodal(string $domain, int $id, Request $request): Response
|
|
||||||
{
|
|
||||||
$relativePath = $request->query->get('path', '');
|
|
||||||
|
|
||||||
return $this->render('file\upload.html.twig', [
|
|
||||||
'useheader' => false,
|
|
||||||
'usemenu' => false,
|
|
||||||
'usesidebar' => false,
|
|
||||||
'endpoint' => 'file',
|
|
||||||
'domain' => $domain,
|
|
||||||
'id' => $id,
|
|
||||||
'path' => $relativePath,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/user/uploadfile', name: 'app_files_uploadfile', methods: ['POST'])]
|
|
||||||
public function upload(Request $request): Response|ResponseInterface
|
|
||||||
{
|
|
||||||
/** @var UploadedFile $file */
|
|
||||||
$file = $request->files->get('file');
|
|
||||||
$domain = $request->query->get('domain');
|
|
||||||
$id = $request->query->get('id');
|
|
||||||
$relativePath = $request->query->get('path', '');
|
|
||||||
|
|
||||||
if (!$file || !$domain || !$id) {
|
|
||||||
return new Response('Invalid parameters', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
$baseDir = $this->getParameter('kernel.project_dir').'/uploads/'.$domain.'/'.$id.'/'.ltrim($relativePath, '/');
|
|
||||||
|
|
||||||
if (!is_dir($baseDir)) {
|
|
||||||
mkdir($baseDir, 0775, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$originalName = $file->getClientOriginalName();
|
|
||||||
$file->move($baseDir, $originalName);
|
|
||||||
|
|
||||||
return new JsonResponse(['success' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/user/file/{domain}/{id}/delete', name: 'app_files_delete', methods: ['POST'])]
|
|
||||||
public function delete(string $domain, int $id, Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$data = json_decode($request->getContent(), true);
|
|
||||||
$relativePath = $data['path'] ?? null;
|
|
||||||
|
|
||||||
if (!$relativePath) {
|
|
||||||
return $this->json(['error' => 'Chemin non fourni.'], 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->fileService->delete($domain, (string) $id, $relativePath);
|
|
||||||
|
|
||||||
return $this->json(['success' => true]);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return $this->json(['error' => $e->getMessage()], 400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,7 +6,7 @@ use App\Entity\Project;
|
|||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Form\ProjectType;
|
use App\Form\ProjectType;
|
||||||
use App\Repository\ProjectRepository;
|
use App\Repository\ProjectRepository;
|
||||||
use App\Service\FileService;
|
use Bnine\FilesBundle\Service\FileService;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
@ -1,101 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Service;
|
|
||||||
|
|
||||||
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
|
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
|
||||||
use Symfony\Component\Finder\Finder;
|
|
||||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
|
||||||
|
|
||||||
class FileService
|
|
||||||
{
|
|
||||||
private string $basePath;
|
|
||||||
private Filesystem $filesystem;
|
|
||||||
|
|
||||||
public function __construct(KernelInterface $kernel)
|
|
||||||
{
|
|
||||||
$this->filesystem = new Filesystem();
|
|
||||||
$projectDir = $kernel->getProjectDir(); // chemin racine du projet
|
|
||||||
$this->basePath = $projectDir.'/uploads';
|
|
||||||
|
|
||||||
if (!is_dir($this->basePath)) {
|
|
||||||
// On crée le dossier uploads s'il n'existe pas
|
|
||||||
try {
|
|
||||||
$this->filesystem->mkdir($this->basePath, 0775);
|
|
||||||
} catch (IOExceptionInterface $e) {
|
|
||||||
throw new \RuntimeException('Impossible de créer le dossier /uploads : '.$e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise un répertoire pour une entité (ex: project/123)
|
|
||||||
*/
|
|
||||||
public function init(string $domain, string $id): void
|
|
||||||
{
|
|
||||||
$entityPath = $this->getEntityPath($domain, $id);
|
|
||||||
if (!is_dir($entityPath)) {
|
|
||||||
try {
|
|
||||||
$this->filesystem->mkdir($entityPath, 0775);
|
|
||||||
} catch (IOExceptionInterface $e) {
|
|
||||||
throw new \RuntimeException(sprintf('Impossible de créer le répertoire pour %s/%s : %s', $domain, $id, $e->getMessage()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Liste les fichiers d’un répertoire lié à une entité (ex: project/123)
|
|
||||||
*/
|
|
||||||
public function list(string $domain, string $id, string $relativePath = ''): array
|
|
||||||
{
|
|
||||||
$targetPath = $this->getEntityPath($domain, $id).'/'.ltrim($relativePath, '/');
|
|
||||||
$realPath = realpath($targetPath);
|
|
||||||
|
|
||||||
$baseEntityPath = $this->getEntityPath($domain, $id);
|
|
||||||
if (!$realPath || !str_starts_with($realPath, $baseEntityPath)) {
|
|
||||||
throw new NotFoundHttpException('Répertoire non autorisé ou inexistant.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$finder = new Finder();
|
|
||||||
$finder->depth('== 0')->in($realPath);
|
|
||||||
|
|
||||||
$results = [];
|
|
||||||
foreach ($finder as $file) {
|
|
||||||
$results[] = [
|
|
||||||
'name' => $file->getFilename(),
|
|
||||||
'isDirectory' => $file->isDir(),
|
|
||||||
'path' => ltrim(str_replace($baseEntityPath, '', $file->getRealPath()), '/'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supprime un fichier ou dossier (de façon sécurisée)
|
|
||||||
*/
|
|
||||||
public function delete(string $domain, string $id, string $relativePath): void
|
|
||||||
{
|
|
||||||
$baseEntityPath = $this->getEntityPath($domain, $id);
|
|
||||||
$targetPath = realpath($baseEntityPath.'/'.ltrim($relativePath, '/'));
|
|
||||||
|
|
||||||
if (!$targetPath || !str_starts_with($targetPath, $baseEntityPath)) {
|
|
||||||
throw new NotFoundHttpException('Fichier ou dossier non autorisé.');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->filesystem->remove($targetPath);
|
|
||||||
} catch (IOExceptionInterface $e) {
|
|
||||||
throw new \RuntimeException('Erreur lors de la suppression : '.$e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construit le chemin absolu d’un domaine/id
|
|
||||||
*/
|
|
||||||
private function getEntityPath(string $domain, string $id): string
|
|
||||||
{
|
|
||||||
return $this->basePath.'/'.$domain.'/'.$id;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"bnine/files-bundle": {
|
||||||
|
"version": "v1.0.1"
|
||||||
|
},
|
||||||
"doctrine/deprecations": {
|
"doctrine/deprecations": {
|
||||||
"version": "1.1",
|
"version": "1.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
@ -1,124 +0,0 @@
|
|||||||
<div id="file-browser-{{ domain }}-{{ id|e('html_attr') }}"
|
|
||||||
class="file-browser"
|
|
||||||
data-domain="{{ domain }}"
|
|
||||||
data-id="{{ id }}"
|
|
||||||
data-base-path="{{ path('app_files', { domain: domain, id: id, editable: editable }) }}"
|
|
||||||
data-current-path="{{ path }}">
|
|
||||||
|
|
||||||
<div class="card mt-3">
|
|
||||||
<div class="card-header">Fichiers</div>
|
|
||||||
<div class="card-body">
|
|
||||||
|
|
||||||
{% if editable %}
|
|
||||||
<div class="mb-3">
|
|
||||||
<a class="btn btn-info" style="max-width:100%; margin-bottom:15px;" data-bs-toggle="modal" data-bs-target="#mymodal" onClick="ModalLoad('mymodal','Upload','{{ path('app_files_uploadmodal',{domain:domain, id:id,path:path}) }}');" title='Upload'>Upload</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p><strong>Chemin :</strong> {{ path ?: '/' }}</p>
|
|
||||||
|
|
||||||
{% set parentPath = path|split('/')|slice(0, -1)|join('/') %}
|
|
||||||
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
{% if path %}
|
|
||||||
<li><a href="#" class="file-nav" data-path="{{ parentPath }}">⬅️ ..</a></li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for file in files %}
|
|
||||||
<li>
|
|
||||||
{% if file.isDirectory %}
|
|
||||||
📁 <a href="#" class="file-nav" data-path="{{ path ? path ~ '/' ~ file.name : file.name }}">{{ file.name }}/</a>
|
|
||||||
{% if editable %}
|
|
||||||
<button class="btn btn-sm btn-danger btn-delete ms-2" data-path="{{ file.path }}">🗑️</button>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
📄 {{ file.name }}
|
|
||||||
{% if editable %}
|
|
||||||
<button class="btn btn-sm btn-danger btn-delete ms-2" data-path="{{ file.path }}">🗑️</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li><em>Dossier vide</em></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
$(function () {
|
|
||||||
function refreshContainer(containerId, path) {
|
|
||||||
const $oldContainer = $('#' + containerId);
|
|
||||||
const base = $oldContainer.data('base-path');
|
|
||||||
|
|
||||||
$.get(base, { path: path }, function (html) {
|
|
||||||
console.log(html);
|
|
||||||
const $doc = $('<div>').html(html);
|
|
||||||
const $newContainer = $doc.find('#' + containerId);
|
|
||||||
console.log(containerId);
|
|
||||||
if ($newContainer.length) {
|
|
||||||
console.log("HHHHHHHHHHHHHHHHHHHH");
|
|
||||||
$oldContainer.replaceWith($newContainer);
|
|
||||||
initFileBrowser($newContainer); // rebind events
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initFileBrowser($container) {
|
|
||||||
const containerId = $container.attr('id');
|
|
||||||
|
|
||||||
// Clear any previous bindings (important!)
|
|
||||||
$container.off('click');
|
|
||||||
|
|
||||||
// Navigation dossier
|
|
||||||
$container.on('click', '.file-nav', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const path = $(this).data('path');
|
|
||||||
refreshContainer(containerId, path);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Suppression fichier ou dossier
|
|
||||||
$container.on('click', '.btn-delete', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!confirm('Supprimer ce fichier ?')) return;
|
|
||||||
|
|
||||||
const pathToDelete = $(this).data('path');
|
|
||||||
const currentPath = $container.data('current-path');
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: '/user/file/' + $container.data('domain') + '/' + $container.data('id') + '/delete',
|
|
||||||
method: 'POST',
|
|
||||||
contentType: 'application/json',
|
|
||||||
data: JSON.stringify({ path: pathToDelete }),
|
|
||||||
success: function (res) {
|
|
||||||
if (res.success) {
|
|
||||||
refreshContainer(containerId, currentPath);
|
|
||||||
} else {
|
|
||||||
alert('Erreur : ' + res.error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function (xhr) {
|
|
||||||
alert('Erreur lors de la suppression : ' + xhr.responseText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init navigateur fichiers
|
|
||||||
const containerId = 'file-browser-{{ domain }}-{{ id|e('html_attr') }}';
|
|
||||||
const $browser = $('#' + containerId);
|
|
||||||
initFileBrowser($browser);
|
|
||||||
|
|
||||||
// Rafraîchir après fermeture modale
|
|
||||||
$('#mymodal').on('hidden.bs.modal', function () {
|
|
||||||
const $browser = $('#' + containerId);
|
|
||||||
const currentPath = $browser.data('current-path') || '';
|
|
||||||
refreshContainer(containerId, currentPath);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
@ -1,45 +0,0 @@
|
|||||||
{% extends 'base.html.twig' %}
|
|
||||||
|
|
||||||
{% block localstyle %}
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<a class="btn btn-secondary" onClick="closeModal();">Annuler</a>
|
|
||||||
<form action="{{ path('app_files_uploadfile', {
|
|
||||||
domain: domain,
|
|
||||||
id: id,
|
|
||||||
path: path
|
|
||||||
}) }}"
|
|
||||||
class="dropzone" id="myDropzone" style="margin-top:10px"></form>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block localscript %}
|
|
||||||
<script>
|
|
||||||
Dropzone.options.myDropzone = {
|
|
||||||
paramName: "{{endpoint}}",
|
|
||||||
maxFilesize: 20, // MB
|
|
||||||
parallelUploads: 5,
|
|
||||||
uploadMultiple: false,
|
|
||||||
dictDefaultMessage: "Déposez vos fichiers ici pour les téléverser",
|
|
||||||
successmultiple: function (files, response) {
|
|
||||||
console.log("multi uploaded", files);
|
|
||||||
},
|
|
||||||
queuecomplete: function () {
|
|
||||||
// Quand tous les fichiers sont uploadés, on ferme la modale et rafraîchit le navigateur
|
|
||||||
window.parent.$("#mymodal").modal('hide');
|
|
||||||
if (typeof window.parent.refreshFileBrowser === 'function') {
|
|
||||||
window.parent.refreshFileBrowser(); // à définir côté parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
window.parent.$("#mymodal").modal('hide');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if mode=="update" %}
|
{% if mode=="update" %}
|
||||||
{{ render(path("app_files",{domain:'project',id:project.id, editable:1})) }}
|
{{ render(path("bninefiles_files",{domain:'project',id:project.id, editable:1})) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user