Skip to content

Commit

Permalink
Documents: Add support for URL-specific document variations - refs #5956
Browse files Browse the repository at this point in the history
  • Loading branch information
christianbeeznest committed Dec 16, 2024
1 parent c0930e7 commit 130b5cc
Show file tree
Hide file tree
Showing 11 changed files with 544 additions and 42 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 @@ -58,6 +58,7 @@ export const chamiloIconToClass = {
"file-text": "mdi mdi-file-document",
"file-upload": "mdi mdi-file-upload",
"file-video": "mdi mdi-file-video",
"file-replace": "mdi mdi-file-replace",
"fit-to-screen": "",
"folder-generic": "mdi mdi-folder",
"folder-multiple-plus": "mdi mdi-folder-multiple-plus",
Expand Down
5 changes: 5 additions & 0 deletions assets/vue/router/documents.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export default {
path: 'show',
component: () => import('../views/documents/DocumentShow.vue')
},
{
name: 'DocumentsAddVariation',
path: 'add_variation/:resourceFileId',
component: () => import('../views/documents/AddVariation.vue'),
},
{
name: 'DocumentForHtmlEditor',
path: 'manager',
Expand Down
189 changes: 189 additions & 0 deletions assets/vue/views/documents/AddVariation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
<template>
<div class="p-4 space-y-8">
<SectionHeader :title="t('Add File Variation')">
<BaseButton
:label="t('Back to Documents')"
icon="back"
type="gray"
@click="goBack"
/>
</SectionHeader>

<div v-if="originalFile" class="bg-gray-100 p-4 rounded-md shadow-md">
<h3 class="text-lg font-semibold">{{ t('Original File') }}</h3>
<p><strong>{{ t('Title:') }}</strong> {{ originalFile.originalName }}</p>
<p><strong>{{ t('Format:') }}</strong> {{ originalFile.mimeType }}</p>
<p><strong>{{ t('Size:') }}</strong> {{ prettyBytes(originalFile.size) }}</p>
</div>

<div class="space-y-6">
<h3 class="text-xl font-bold">{{ t('Upload New Variation') }}</h3>

<form @submit.prevent="uploadVariation" class="flex flex-col space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<BaseFileUpload
@file-selected="onFileSelected"
:label="t('Choose file')"
accept=".pdf,.html,.docx,.mp4"
required
class="w-full"
/>

<Dropdown
v-model="selectedAccessUrl"
:options="accessUrls"
optionLabel="url"
optionValue="id"
placeholder="Select a URL"
class="w-full"
/>
</div>

<div class="flex justify-end">
<BaseButton
:label="t('Upload')"
icon="file-upload"
type="success"
:disabled="!file"
@click="uploadVariant(file, originalFile?.resourceNode?.id, selectedAccessUrl)"
/>
</div>
</form>
</div>

<div>
<h3 class="text-xl font-bold mb-4">{{ t('Current Variations') }}</h3>
<DataTable :value="variations" class="w-full">
<Column field="title" :header="t('Title')" />
<Column field="mimeType" :header="t('Format')" />
<Column field="size" :header="t('Size')">
<template #body="slotProps">
{{ prettyBytes(slotProps.data.size) }}
</template>
</Column>
<Column field="updatedAt" :header="t('Updated At')" />
<Column field="url" :header="t('URL')">
<template #body="slotProps">
<a
:href="slotProps.data.path"
target="_blank"
class="text-blue-500 hover:underline"
>
{{ t('View') }}
</a>
</template>
</Column>
<Column field="creator" :header="t('Creator')" />
</DataTable>
</div>
</div>
</template>

<script setup>
import { ref, onMounted, computed } from "vue"
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import axios from 'axios'
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
import SectionHeader from "../../components/layout/SectionHeader.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue"
import prettyBytes from 'pretty-bytes'
import { useStore } from "vuex"
import { useCidReq } from "../../composables/cidReq"
const store = useStore()
const route = useRoute()
const router = useRouter()
const { t } = useI18n()
const { cid, sid, gid } = useCidReq()
const file = ref(null)
const variations = ref([])
const originalFile = ref(null)
const resourceFileId = route.params.resourceFileId;
const selectedAccessUrl = ref(null)
const accessUrls = ref([])
onMounted(async () => {
await fetchOriginalFile()
await fetchVariations()
await fetchAccessUrls()
})
async function fetchVariations() {
if (!originalFile.value?.resourceNode?.id) {
console.error('ResourceNodeId is undefined. Cannot fetch variations.')
return
}
try {
const resourceNodeId = originalFile.value.resourceNode.id
const response = await axios.get(`/r/resource_files/${resourceNodeId}/variants`)
variations.value = response.data
} catch (error) {
console.error('Error fetching variations:', error)
}
}
async function fetchAccessUrls() {
try {
const response = await axios.get('/api/access_urls')
if (Array.isArray(response.data['hydra:member'])) {
const currentAccessUrlId = window.access_url_id
accessUrls.value = response.data['hydra:member'].filter(
(url) => url.id !== currentAccessUrlId
)
} else {
accessUrls.value = []
}
} catch (error) {
console.error('Error fetching access URLs:', error)
accessUrls.value = []
}
}
async function fetchOriginalFile() {
try {
const response = await axios.get(`/api/resource_files/${resourceFileId}`)
originalFile.value = response.data
} catch (error) {
console.error('Error fetching original file:', error)
}
}
async function uploadVariant(file, resourceNodeId, accessUrlId) {
if (!resourceNodeId) {
console.error('ResourceNodeId is undefined. Check originalFile:', originalFile.value)
return
}
const formData = new FormData()
formData.append('file', file)
formData.append('resourceNodeId', resourceNodeId)
if (accessUrlId) {
formData.append('accessUrlId', accessUrlId)
}
try {
const response = await axios.post('/api/resource_files/add_variant', formData)
console.log('Variant uploaded or updated successfully:', response.data)
await fetchVariations()
file.value = null
selectedAccessUrl.value = null
} catch (error) {
console.error('Error uploading variant:', error)
}
}
function onFileSelected(selectedFile) {
file.value = selectedFile
}
function goBack() {
let queryParams = { cid, sid, gid }
router.push({ name: "DocumentsList", params: { node: parent.id }, query: queryParams })
}
</script>
24 changes: 23 additions & 1 deletion assets/vue/views/documents/DocumentsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@
@click="btnChangeVisibilityOnClick(slotProps.data)"
/>

<BaseButton
v-if="canEdit(slotProps.data) && allowAccessUrlFiles && isFile(slotProps.data)"
icon="file-replace"
size="small"
type="secondary"
:title="t('Add File Variation')"
@click="goToAddVariation(slotProps.data)"
/>

<BaseButton
v-if="canEdit(slotProps.data)"
icon="edit"
Expand Down Expand Up @@ -436,17 +445,21 @@ import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue"
import { useDocumentActionButtons } from "../../composables/document/documentActionButtons"
import SectionHeader from "../../components/layout/SectionHeader.vue"
import { checkIsAllowedToEdit } from "../../composables/userPermissions"
import { usePlatformConfig } from "../../store/platformConfig"
const store = useStore()
const route = useRoute()
const router = useRouter()
const securityStore = useSecurityStore()
const platformConfigStore = usePlatformConfig()
const allowAccessUrlFiles = computed(() => "false" !== platformConfigStore.getSetting("course.access_url_specific_files"))
const { t } = useI18n()
const { filters, options, onUpdateOptions, deleteItem } = useDatatableList("Documents")
const notification = useNotification()
const { cid, sid, gid } = useCidReq()
const { isImage, isHtml } = useFileUtils()
const { isImage, isHtml, isFile } = useFileUtils()
const { relativeDatetime } = useFormatDate()
const isAllowedToEdit = ref(false)
Expand Down Expand Up @@ -559,6 +572,15 @@ const showBackButtonIfNotRootFolder = computed(() => {
return resourceNode.value.resourceType.title !== "courses"
})
function goToAddVariation(item) {
const resourceFileId = item.resourceNode.firstResourceFile.id
router.push({
name: 'DocumentsAddVariation',
params: { resourceFileId, node: route.params.node },
query: { cid, sid, gid },
})
}
function back() {
if (!resourceNode.value) {
return
Expand Down
71 changes: 71 additions & 0 deletions src/CoreBundle/Controller/AddVariantResourceFileAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);

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

namespace Chamilo\CoreBundle\Controller;

use Chamilo\CoreBundle\Entity\ResourceFile;
use Chamilo\CoreBundle\Entity\ResourceNode;
use Chamilo\CoreBundle\Entity\AccessUrl;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class AddVariantResourceFileAction
{
public function __invoke(Request $request, EntityManagerInterface $em): ResourceFile
{
$uploadedFile = $request->files->get('file');
if (!$uploadedFile) {
throw new BadRequestHttpException('"file" is required');
}

$resourceNodeId = $request->get('resourceNodeId');
if (!$resourceNodeId) {
throw new BadRequestHttpException('"resourceNodeId" is required');
}

$resourceNode = $em->getRepository(ResourceNode::class)->find($resourceNodeId);
if (!$resourceNode) {
throw new NotFoundHttpException('ResourceNode not found');
}

$accessUrlId = $request->get('accessUrlId');
$accessUrl = null;
if ($accessUrlId) {
$accessUrl = $em->getRepository(AccessUrl::class)->find($accessUrlId);
if (!$accessUrl) {
throw new NotFoundHttpException('AccessUrl not found');
}
}

$existingResourceFile = $em->getRepository(ResourceFile::class)->findOneBy([
'resourceNode' => $resourceNode,
'accessUrl' => $accessUrl,
]);

if ($existingResourceFile) {
$existingResourceFile->setTitle($uploadedFile->getClientOriginalName());
$existingResourceFile->setFile($uploadedFile);
$existingResourceFile->setUpdatedAt(\DateTime::createFromImmutable(new \DateTimeImmutable()));
$resourceFile = $existingResourceFile;
} else {
$resourceFile = new ResourceFile();
$resourceFile->setTitle($uploadedFile->getClientOriginalName());
$resourceFile->setFile($uploadedFile);
$resourceFile->setResourceNode($resourceNode);

if ($accessUrl) {
$resourceFile->setAccessUrl($accessUrl);
}
}

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

return $resourceFile;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public function list(SettingsManager $settingsManager): Response
'social.hide_social_groups_block',
'course.show_course_duration',
'exercise.allow_exercise_auto_launch',
'course.access_url_specific_files',
];

$user = $this->userHelper->getCurrent();
Expand Down
Loading

0 comments on commit 130b5cc

Please sign in to comment.