Skip to content

Commit

Permalink
Documents: Add replace document functionality - refs chamilo#5957
Browse files Browse the repository at this point in the history
  • Loading branch information
christianbeeznest committed Jan 10, 2025
1 parent ba7a75f commit ec85b88
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 0 deletions.
1 change: 1 addition & 0 deletions assets/vue/components/basecomponents/ChamiloIcons.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const chamiloIconToClass = {
"file-generic": "mdi mdi-file",
"file-image": "mdi mdi-file-image",
"file-pdf": "mdi mdi-file-pdf-box",
"file-swap": "mdi mdi-swap-horizontal",
"file-text": "mdi mdi-file-document",
"file-upload": "mdi mdi-file-upload",
"file-video": "mdi mdi-file-video",
Expand Down
62 changes: 62 additions & 0 deletions assets/vue/views/documents/DocumentsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@
type="secondary"
@click="openMoveDialog(slotProps.data)"
/>
<BaseButton
:disabled="slotProps.data.filetype !== 'file'"
:title="slotProps.data.filetype !== 'file' ? t('Replace (files only)') : t('Replace')"
icon="file-swap"
size="small"
type="secondary"
@click="slotProps.data.filetype === 'file' && openReplaceDialog(slotProps.data)"
/>
<BaseButton
:title="t('Information')"
icon="information"
Expand Down Expand Up @@ -352,6 +360,21 @@
</div>
</BaseDialogConfirmCancel>

<BaseDialogConfirmCancel
v-model:is-visible="isReplaceDialogVisible"
:title="t('Replace document')"
@confirm-clicked="replaceDocument"
@cancel-clicked="isReplaceDialogVisible = false"
>
<BaseFileUpload
id="replace-file"
:label="t('Select file to replace')"
accept="*/*"
model-value="selectedReplaceFile"
@file-selected="selectedReplaceFile = $event"
/>
</BaseDialogConfirmCancel>

<BaseDialog
v-model:is-visible="isFileUsageDialogVisible"
:style="{ width: '28rem' }"
Expand Down Expand Up @@ -519,6 +542,10 @@ const isSessionDocument = (item) => {
const isHtmlFile = (fileData) => isHtml(fileData)
const isReplaceDialogVisible = ref(false)
const selectedReplaceFile = ref(null)
const documentToReplace = ref(null)
onMounted(async () => {
isAllowedToEdit.value = await checkIsAllowedToEdit(true, true, true)
filters.value.loadNode = 1
Expand Down Expand Up @@ -784,6 +811,41 @@ function openMoveDialog(document) {
isMoveDialogVisible.value = true
}
function openReplaceDialog(document) {
documentToReplace.value = document
isReplaceDialogVisible.value = true
}
async function replaceDocument() {
if (!selectedReplaceFile.value) {
notification.showErrorNotification(t("No file selected."))
return
}
if (documentToReplace.value.filetype !== 'file') {
notification.showErrorNotification(t("Only files can be replaced."))
return
}
const formData = new FormData()
console.log(selectedReplaceFile.value)
formData.append('file', selectedReplaceFile.value)
try {
await axios.post(`/api/documents/${documentToReplace.value.iid}/replace`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
notification.showSuccessNotification(t("Document replaced successfully."))
isReplaceDialogVisible.value = false
onUpdateOptions(options.value)
} catch (error) {
notification.showErrorNotification(t("Error replacing document."))
console.error(error)
}
}
async function fetchFolders(nodeId = null, parentPath = "") {
const foldersList = [
{
Expand Down
99 changes: 99 additions & 0 deletions src/CoreBundle/Controller/Api/ReplaceDocumentFileAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

declare(strict_types=1);

/* For licensing terms, see /license.txt */

namespace Chamilo\CoreBundle\Controller\Api;

use Chamilo\CoreBundle\Repository\ResourceNodeRepository;
use Chamilo\CourseBundle\Entity\CDocument;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\File\Exception\FileException;

class ReplaceDocumentFileAction extends BaseResourceFileAction
{
private string $uploadBasePath;

public function __construct(KernelInterface $kernel)
{
$this->uploadBasePath = $kernel->getProjectDir() . '/var/upload/resource';
}

public function __invoke(
CDocument $document,
Request $request,
ResourceNodeRepository $resourceNodeRepository,
EntityManagerInterface $em
): Response {
$uploadedFile = $request->files->get('file');
if (!$uploadedFile) {
throw new BadRequestHttpException('"file" is required.');
}

$resourceNode = $document->getResourceNode();
if (!$resourceNode) {
throw new BadRequestHttpException('ResourceNode not found.');
}

$resourceFile = $resourceNode->getFirstResourceFile();
if (!$resourceFile) {
throw new BadRequestHttpException('No file found in the resource node.');
}

$filePath = $this->uploadBasePath . $resourceNodeRepository->getFilename($resourceFile);
if (!$filePath) {
throw new BadRequestHttpException('File path could not be resolved.');
}

$this->prepareDirectory($filePath);

try {
$uploadedFile->move(dirname($filePath), basename($filePath));
} catch (FileException $e) {
throw new BadRequestHttpException(sprintf('Failed to move the file: %s', $e->getMessage()));
}

$movedFilePath = $filePath;
if (!file_exists($movedFilePath)) {
throw new \RuntimeException('The moved file does not exist at the expected location.');
}
$fileSize = filesize($movedFilePath);
$resourceFile->setSize($fileSize);

$newFileName = $uploadedFile->getClientOriginalName();
$document->setTitle($newFileName);
$resourceFile->setOriginalName($newFileName);

$resourceNode->setUpdatedAt(new \DateTime());

$em->persist($document);
$em->persist($resourceFile);
$em->flush();

return new Response('Document replaced successfully.', Response::HTTP_OK);
}

/**
* Prepares the directory to ensure it exists and is writable.
*/
protected function prepareDirectory(string $filePath): void
{
$directory = dirname($filePath);

if (!is_dir($directory)) {
if (!mkdir($directory, 0775, true) && !is_dir($directory)) {
throw new \RuntimeException(sprintf('Unable to create directory "%s".', $directory));
}
}

if (!is_writable($directory)) {
throw new \RuntimeException(sprintf('Directory "%s" is not writable.', $directory));
}
}

}
26 changes: 26 additions & 0 deletions src/CourseBundle/Entity/CDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use ApiPlatform\Metadata\Put;
use ApiPlatform\Serializer\Filter\PropertyFilter;
use Chamilo\CoreBundle\Controller\Api\CreateDocumentFileAction;
use Chamilo\CoreBundle\Controller\Api\ReplaceDocumentFileAction;
use Chamilo\CoreBundle\Controller\Api\UpdateDocumentFileAction;
use Chamilo\CoreBundle\Controller\Api\UpdateVisibilityDocument;
use Chamilo\CoreBundle\Entity\AbstractResource;
Expand Down Expand Up @@ -60,6 +61,31 @@
security: "is_granted('EDIT', object.resourceNode)",
deserialize: true
),
new Post(
uriTemplate: '/documents/{iid}/replace',
controller: ReplaceDocumentFileAction::class,
openapiContext: [
'summary' => 'Replace a document file, maintaining the same IDs.',
'requestBody' => [
'content' => [
'multipart/form-data' => [
'schema' => [
'type' => 'object',
'properties' => [
'file' => [
'type' => 'string',
'format' => 'binary',
],
],
],
],
],
],
],
security: "is_granted('ROLE_CURRENT_COURSE_TEACHER') or is_granted('ROLE_CURRENT_COURSE_SESSION_TEACHER') or is_granted('ROLE_TEACHER')",
validationContext: ['groups' => ['Default', 'media_object_create', 'document:write']],
deserialize: false
),
new Get(security: "is_granted('VIEW', object.resourceNode)"),
new Delete(security: "is_granted('DELETE', object.resourceNode)"),
new Post(
Expand Down

0 comments on commit ec85b88

Please sign in to comment.