Documents: Add support for URL-specific document variations - refs #5956
parent
c0930e7678
commit
130b5cc2ba
@ -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> |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,60 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* For licensing terms, see /license.txt */ |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Migrations\Schema\V200; |
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Migrations\AbstractMigrationChamilo; |
||||||
|
use Doctrine\DBAL\Schema\Schema; |
||||||
|
|
||||||
|
final class Version20241214083500 extends AbstractMigrationChamilo |
||||||
|
{ |
||||||
|
public function getDescription(): string |
||||||
|
{ |
||||||
|
return 'Add access_url_id field to resource_file table'; |
||||||
|
} |
||||||
|
|
||||||
|
public function up(Schema $schema): void |
||||||
|
{ |
||||||
|
if ($schema->hasTable('resource_file')) { |
||||||
|
$this->addSql( |
||||||
|
'ALTER TABLE resource_file ADD access_url_id INT DEFAULT NULL' |
||||||
|
); |
||||||
|
$this->addSql( |
||||||
|
'ALTER TABLE resource_file ADD CONSTRAINT FK_RESOURCE_FILE_ACCESS_URL FOREIGN KEY (access_url_id) REFERENCES access_url (id) ON DELETE SET NULL' |
||||||
|
); |
||||||
|
$this->addSql( |
||||||
|
'CREATE INDEX IDX_RESOURCE_FILE_ACCESS_URL ON resource_file (access_url_id)' |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
$result = $this->connection |
||||||
|
->executeQuery( |
||||||
|
"SELECT COUNT(1) FROM settings WHERE variable = 'access_url_specific_files' AND category = 'course'" |
||||||
|
) |
||||||
|
; |
||||||
|
$count = $result->fetchNumeric()[0]; |
||||||
|
if (empty($count)) { |
||||||
|
$this->addSql( |
||||||
|
"INSERT INTO settings (variable, category, selected_value, title, comment, scope, subkeytext, access_url_changeable) VALUES ('access_url_specific_files','course','false','Access Url Specific Files','','',NULL, 1)" |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function down(Schema $schema): void |
||||||
|
{ |
||||||
|
if ($schema->hasTable('resource_file')) { |
||||||
|
$this->addSql( |
||||||
|
'ALTER TABLE resource_file DROP FOREIGN KEY FK_RESOURCE_FILE_ACCESS_URL' |
||||||
|
); |
||||||
|
$this->addSql( |
||||||
|
'DROP INDEX IDX_RESOURCE_FILE_ACCESS_URL ON resource_file' |
||||||
|
); |
||||||
|
$this->addSql( |
||||||
|
'ALTER TABLE resource_file DROP COLUMN access_url_id' |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue