Internal: Add "File info" to admin system block with file details - refs BT#21826

pull/5672/head
christianbeeznst 1 year ago
parent b11445c6a0
commit 9b00329b22
  1. 48
      assets/css/app.scss
  2. 87
      src/CoreBundle/Controller/Admin/AdminController.php
  3. 6
      src/CoreBundle/Controller/Admin/IndexBlocksController.php
  4. 152
      src/CoreBundle/Resources/views/Admin/files_info.html.twig

@ -742,6 +742,54 @@ form .field {
}
}
.files-info-page {
.form-control {
@apply w-48 p-1 border border-gray-300 rounded;
}
.btn-primary {
@apply bg-primary text-primary-button-text p-1 rounded;
}
.btn-primary:hover {
@apply bg-secondary-hover;
}
.modal {
@apply hidden fixed z-10 left-0 top-0 w-full h-full overflow-auto bg-black bg-opacity-50 pt-16;
}
.modal-content {
@apply bg-white m-auto p-5 border border-gray-300 w-1/3;
}
.close-button {
@apply text-fontdisabled float-right text-2xl font-bold;
}
.close-button:hover,
.close-button:focus {
@apply text-black no-underline cursor-pointer;
}
.pagination {
@apply flex justify-center my-5;
}
.pagination a,
.pagination span {
@apply mx-1 px-4 py-2 text-primary border border-gray-25;
}
.pagination a:hover {
@apply bg-gray-15;
}
.pagination span {
@apply bg-primary text-white border-primary;
}
}
//@import 'primevue-md-light-indigo/theme.css';
//@import '~primevue/resources/primevue.min.css';
//@import '~primeflex/primeflex.css';

@ -7,18 +7,27 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Controller\Admin;
use Chamilo\CoreBundle\Controller\BaseController;
use Chamilo\CoreBundle\Entity\ResourceFile;
use Chamilo\CoreBundle\Repository\ResourceNodeRepository;
use Chamilo\CoreBundle\ServiceHelper\AccessUrlHelper;
use Chamilo\CoreBundle\Settings\SettingsManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Vich\UploaderBundle\Storage\StorageInterface;
#[Route('/admin')]
class AdminController extends BaseController
{
private const ITEMS_PER_PAGE = 50;
public function __construct(
private readonly AccessUrlHelper $accessUrlHelper,
private EntityManagerInterface $entityManager,
private ResourceNodeRepository $resourceNodeRepository,
private StorageInterface $storage,
private AccessUrlHelper $accessUrlHelper
) {}
#[IsGranted('ROLE_ADMIN')]
@ -38,4 +47,78 @@ class AdminController extends BaseController
return new Response('', Response::HTTP_NO_CONTENT);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/files_info', name: 'admin_files_info', methods: ['GET'])]
public function listFilesInfo(Request $request): Response
{
$page = $request->query->getInt('page', 1);
$search = $request->query->get('search', '');
$offset = ($page - 1) * self::ITEMS_PER_PAGE;
$queryBuilder = $this->entityManager->getRepository(ResourceFile::class)->createQueryBuilder('rf')
->leftJoin('rf.resourceNode', 'rn')
->leftJoin('rn.resourceLinks', 'rl')
->leftJoin('rl.course', 'c')
->leftJoin('rl.user', 'u')
->addSelect('rn', 'rl', 'c', 'u');
if ($search) {
$queryBuilder->where('rf.title LIKE :search')
->orWhere('rf.originalName LIKE :search')
->orWhere('c.title LIKE :search')
->orWhere('u.username LIKE :search')
->orWhere('rn.uuid LIKE :search')
->setParameter('search', '%' . $search . '%');
}
$queryBuilder->orderBy('rf.id', 'DESC')
->setFirstResult($offset)
->setMaxResults(self::ITEMS_PER_PAGE);
$files = $queryBuilder->getQuery()->getResult();
$totalItemsQuery = $this->entityManager->getRepository(ResourceFile::class)
->createQueryBuilder('rf')
->leftJoin('rf.resourceNode', 'rn')
->leftJoin('rn.resourceLinks', 'rl')
->leftJoin('rl.course', 'c')
->leftJoin('rl.user', 'u')
->select('COUNT(rf.id)');
if ($search) {
$totalItemsQuery->where('rf.title LIKE :search')
->orWhere('rf.originalName LIKE :search')
->orWhere('c.title LIKE :search')
->orWhere('u.username LIKE :search')
->orWhere('rn.uuid LIKE :search')
->setParameter('search', '%' . $search . '%');
}
$totalItems = $totalItemsQuery->getQuery()->getSingleScalarResult();
$totalPages = ceil($totalItems / self::ITEMS_PER_PAGE);
$fileUrls = [];
$filePaths = [];
foreach ($files as $file) {
$resourceNode = $file->getResourceNode();
if ($resourceNode) {
$fileUrls[$file->getId()] = $this->resourceNodeRepository->getResourceFileUrl($resourceNode);
$creator = $resourceNode->getCreator();
} else {
$fileUrls[$file->getId()] = null;
$creator = null;
}
$filePaths[$file->getId()] = $this->resourceNodeRepository->getFilename($file);
}
return $this->render('@ChamiloCore/Admin/files_info.html.twig', [
'files' => $files,
'fileUrls' => $fileUrls,
'filePaths' => $filePaths,
'totalPages' => $totalPages,
'currentPage' => $page,
'search' => $search,
]);
}
}

@ -566,6 +566,12 @@ class IndexBlocksController extends BaseController
'label' => $this->translator->trans('Colors'),
];
$items[] = [
'class' => 'item-file-info',
'url' => '/admin/files_info',
'label' => $this->translator->trans('File info'),
];
return $items;
}

@ -0,0 +1,152 @@
{% extends "@ChamiloCore/Layout/layout_one_col.html.twig" %}
{% block content %}
<div class="files-info-page">
<h1>{{ 'File Information'|trans }}</h1>
<form method="get" action="{{ path('admin_files_info') }}" style="display: flex; justify-content: flex-end; margin-bottom: 20px;">
<input type="text" name="search" value="{{ search }}" placeholder="{{ 'Search...'|trans }}" class="form-control" style="margin-right: 10px;">
<button type="submit" class="btn btn--primary">{{ 'Search'|trans }}</button>
</form>
<table class="data_table">
<thead>
<tr>
<th>{{ 'Title'|trans }}</th>
<th>{{ 'Original Name'|trans }}</th>
<th>{{ 'Course'|trans }}</th>
<th>{{ 'User'|trans }}</th>
<th>{{ 'Actions'|trans }}</th>
</tr>
</thead>
<tbody>
{% for file in files %}
<tr>
<td>{{ file.title }}</td>
<td>{{ file.originalName }}</td>
<td>
{% if file.resourceNode and file.resourceNode.resourceLinks|length > 0 %}
{{ file.resourceNode.resourceLinks|first.course.title ?? 'N/A' }}
{% else %}
{{ 'N/A'|trans }}
{% endif %}
</td>
<td>
{% if file.resourceNode and file.resourceNode.resourceLinks|length > 0 %}
{{ file.resourceNode.resourceLinks|first.user.username ?? 'N/A' }}
{% elseif file.resourceNode and file.resourceNode.creator %}
{{ file.resourceNode.creator.username ?? 'N/A' }}
{% else %}
{{ 'N/A'|trans }}
{% endif %}
</td>
<td>
<a href="#" class="open-modal"
data-title="{{ file.title }}"
data-mime-type="{{ file.mimeType }}"
data-original-name="{{ file.originalName }}"
data-size="{{ file.size }}"
data-course="{% if file.resourceNode and file.resourceNode.resourceLinks|length > 0 %}{{ file.resourceNode.resourceLinks|first.course.title ?? 'N/A' }}{% else %}{{ 'N/A'|trans }}{% endif %}"
data-user="{% if file.resourceNode and file.resourceNode.resourceLinks|length > 0 %}{{ file.resourceNode.resourceLinks|first.user.username ?? 'N/A' }}{% elseif file.resourceNode and file.resourceNode.creator %}{{ file.resourceNode.creator.username ?? 'N/A' }}{% else %}{{ 'N/A'|trans }}{% endif %}"
data-file-url="{{ fileUrls[file.id] }}"
data-file-path="{{ filePaths[file.id] }}">
{{ 'View'|trans }}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
{% if currentPage > 1 %}
<a href="{{ path('admin_files_info', {'page': currentPage - 1, 'search': search}) }}">&laquo; {{ 'Previous'|trans }}</a>
{% endif %}
{% for i in max(1, currentPage - 2)..min(totalPages, currentPage + 2) %}
{% if i == currentPage %}
<span>{{ i }}</span>
{% else %}
<a href="{{ path('admin_files_info', {'page': i, 'search': search}) }}">{{ i }}</a>
{% endif %}
{% endfor %}
{% if currentPage < totalPages %}
<a href="{{ path('admin_files_info', {'page': currentPage + 1, 'search': search}) }}">{{ 'Next'|trans }} &raquo;</a>
{% endif %}
</div>
<!-- Modal -->
<div id="fileInfoModal" class="modal">
<div class="modal-content">
<span class="close-button">&times;</span>
<h2>{{ 'File Information'|trans }}</h2>
<p class="mt-3 mb-3"><strong>{{ 'Title:'|trans }}</strong> <span id="file-title"></span></p>
<p class="mb-3"><strong>{{ 'MIME Type:'|trans }}</strong> <span id="file-mime-type"></span></p>
<p class="mb-3"><strong>{{ 'Original Name:'|trans }}</strong> <span id="file-original-name"></span></p>
<p class="mb-3"><strong>{{ 'Size:'|trans }}</strong> <span id="file-size"></span></p>
<p class="mb-3"><strong>{{ 'Course:'|trans }}</strong> <span id="file-course"></span></p>
<p class="mb-3"><strong>{{ 'User:'|trans }}</strong> <span id="file-user"></span></p>
<p class="mb-3"><strong>{{ 'File Path:'|trans }}</strong> <span id="file-path"></span>&nbsp;&nbsp;&nbsp;<button id="copy-path" class="mdi mdi-content-copy"></button></p>
<p class="mb-3"><strong>{{ 'File Link:'|trans }}</strong> <a href="#" id="file-url" target="_blank">{{ 'Open File'|trans }}</a></p>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
var modal = document.getElementById("fileInfoModal");
var span = document.getElementsByClassName("close-button")[0];
var copyPathButton = document.getElementById('copy-path');
document.querySelectorAll('.open-modal').forEach(function(button) {
button.onclick = function(event) {
event.preventDefault();
var title = button.getAttribute('data-title');
var mimeType = button.getAttribute('data-mime-type');
var originalName = button.getAttribute('data-original-name');
var size = button.getAttribute('data-size');
var course = button.getAttribute('data-course');
var user = button.getAttribute('data-user');
var filePath = button.getAttribute('data-file-path');
var fileUrl = button.getAttribute('data-file-url');
document.getElementById('file-title').textContent = title;
document.getElementById('file-mime-type').textContent = mimeType;
document.getElementById('file-original-name').textContent = originalName;
document.getElementById('file-size').textContent = size + ' bytes';
document.getElementById('file-course').textContent = course;
document.getElementById('file-user').textContent = user;
document.getElementById('file-path').textContent = filePath;
document.getElementById('file-url').href = fileUrl;
modal.style.display = "block";
};
});
span.onclick = function() {
modal.style.display = "none";
};
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
};
copyPathButton.onclick = function() {
var filePath = document.getElementById('file-path').textContent;
navigator.clipboard.writeText(filePath).then(function() {
copyPathButton.classList.remove('mdi-content-copy');
copyPathButton.classList.add('mdi-check');
setTimeout(function() {
copyPathButton.classList.remove('mdi-check');
copyPathButton.classList.add('mdi-content-copy');
}, 2000);
}, function(err) {
alert('Failed to copy: ', err);
});
};
});
</script>
{% endblock %}
Loading…
Cancel
Save