Editor: Integrate image upload with backend controller and fileManager option - refs #3795

pull/5241/head
christianbeeznst 2 years ago
parent 4bd237374e
commit 370f04dbaf
  1. 6
      assets/vue/App.vue
  2. 161
      assets/vue/components/basecomponents/BaseTinyEditor.vue
  3. 21
      assets/vue/components/filemanager/Layout.vue
  4. 19
      assets/vue/router/filemanager.js
  5. 2
      assets/vue/router/index.js
  6. 7
      assets/vue/router/page.js
  7. 496
      assets/vue/views/filemanager/List.vue
  8. 129
      assets/vue/views/filemanager/Upload.vue
  9. 24
      assets/vue/views/page/EditorDemo.vue
  10. 2
      src/CoreBundle/Controller/Api/BaseResourceFileAction.php
  11. 113
      src/CoreBundle/Controller/FileManagerController.php

@ -54,6 +54,7 @@ import { useLocale } from "./composables/locale"
import { useI18n } from "vue-i18n"
import { customVueTemplateEnabled } from "./config/env"
import CustomDashboardLayout from "../../var/vue_templates/components/layout/DashboardLayout.vue"
import EmptyLayout from "./components/layout/EmptyLayout.vue"
const apolloClient = new ApolloClient({
link: createHttpLink({
@ -69,6 +70,11 @@ const router = useRouter()
const i18n = useI18n()
const layout = computed(() => {
if (route.meta.emptyLayout) {
return EmptyLayout;
}
const queryParams = new URLSearchParams(window.location.search)
if (queryParams.has("lp") || (queryParams.has("origin") && "learnpath" === queryParams.get("origin"))) {

@ -0,0 +1,161 @@
<template>
<div class="base-tiny-editor">
<label v-if="title" :for="editorId">{{ title }}</label>
<TinyEditor
:id="editorId"
:model-value="modelValue"
:init="editorConfig"
:required="required"
@update:model-value="updateValue"
@input="updateValue"
/>
<p v-if="helpText" class="help-text">{{ helpText }}</p>
</div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import TinyEditor from '@tinymce/tinymce-vue'
import { useRoute, useRouter } from "vue-router"
import { useCidReqStore } from "../../store/cidReq"
import { storeToRefs } from "pinia"
import { useStore } from "vuex"
const props = defineProps({
editorId: String,
modelValue: String,
required: Boolean,
editorConfig: Object,
title: String,
helpText: String,
mode: { type: String, default: 'personal_files' },
useFileManager: { type: Boolean, default: false }
})
const emit = defineEmits(['update:modelValue'])
const router = useRouter()
const route = useRoute()
const parentResourceNodeId = ref(0)
const store = useStore()
const user = computed(() => store.getters["security/getUser"])
// Set the parent node ID based on the user's resource node ID or route parameter
parentResourceNodeId.value = user.value.resourceNode["id"]
if (route.params.node) {
parentResourceNodeId.value = Number(route.params.node)
}
const updateValue = (value) => {
emit('update:modelValue', value)
}
const defaultEditorConfig = {
skin_url: '/build/libs/tinymce/skins/ui/oxide',
content_css: '/build/libs/tinymce/skins/content/default/content.css',
branding: false,
relative_urls: false,
height: 280,
toolbar_mode: 'sliding',
autosave_ask_before_unload: true,
plugins: [
'advlist autolink lists link image charmap print preview anchor',
'searchreplace visualblocks code fullscreen',
'insertdatetime media table paste wordcount emoticons',
],
toolbar: 'undo redo | bold italic underline strikethrough | ...',
file_picker_callback: filePickerCallback
}
const editorConfig = computed(() => ({
...defaultEditorConfig,
...props.editorConfig
}))
function filePickerCallback(callback, value, meta) {
if (!props.useFileManager) {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.style.display = 'none';
input.onchange = () => {
const file = input.files[0];
const title = file.name;
const comment = '';
const fileType = 'file';
const resourceLinkList = [];
const formData = new FormData();
formData.append('uploadFile', file);
formData.append('title', title);
formData.append('comment', comment);
formData.append('parentResourceNodeId', parentResourceNodeId.value);
formData.append('filetype', fileType);
formData.append('resourceLinkList', resourceLinkList);
fetch('/file-manager/upload-image', {
method: 'POST',
body: formData,
})
.then(response => response.json())
.then(data => {
if (data.location) {
callback(data.location);
} else {
console.error('Failed to upload file');
}
})
.catch(error => console.error('Error uploading file:', error))
.finally(() => document.body.removeChild(input));
};
document.body.appendChild(input);
input.click();
return
}
let url;
if (props.mode === 'personal_files') {
url = '/resources/filemanager/personal_list/1477'
} else if (props.mode === 'documents') {
const cidReqStore = useCidReqStore();
const { course, session } = storeToRefs(cidReqStore);
let nodeId = course.value && course.value.resourceNode ? course.value.resourceNode.id : null;
if (!nodeId) {
console.error('Resource node ID is not available.');
return;
}
let folderParams = Object.entries(route.query).map(([key, value]) => `${key}=${value}`).join('&');
url = router.resolve({ name: "DocumentForHtmlEditor", params: { id: nodeId }, query: route.query }).href;
}
if (meta.filetype === 'image') {
url += "&type=images";
} else {
url += "&type=files";
}
window.addEventListener("message", function (event) {
var data = event.data;
if (data.url) {
url = data.url;
callback(url);
}
});
tinymce.activeEditor.windowManager.openUrl({
url: url,
title: "File manager",
onMessage: (api, message) => {
if (message.mceAction === 'fileSelected') {
const fileUrl = message.content;
callback(fileUrl);
api.close();
}
}
});
}
</script>

@ -0,0 +1,21 @@
<template>
<router-view></router-view>
</template>
<script setup>
import { useStore } from "vuex"
import { useRoute } from "vue-router"
import { onMounted, provide, readonly, ref, watch } from "vue"
import { useSocialInfo } from "../../composables/useSocialInfo"
const store = useStore()
const route = useRoute()
const { user, isCurrentUser, groupInfo, isGroup, loadUser } = useSocialInfo()
provide("social-user", user)
provide("is-current-user", isCurrentUser)
provide("group-info", groupInfo)
provide("is-group", isGroup)
onMounted(loadUser)
</script>

@ -0,0 +1,19 @@
export default {
path: '/resources/filemanager',
meta: { requiresAuth: true },
component: () => import('../components/filemanager/Layout.vue'),
children: [
{
path: 'personal_list/:node?',
name: 'FileManagerList',
component: () => import('../views/filemanager/List.vue'),
meta: { emptyLayout: true },
},
{
name: 'FileManagerUploadFile',
path: 'upload',
component: () => import('../views/filemanager/Upload.vue'),
meta: { emptyLayout: true },
},
],
};

@ -12,6 +12,7 @@ import toolIntroRoutes from "./ctoolintro"
import pageRoutes from "./page"
import socialNetworkRoutes from "./social"
import termsRoutes from "./terms"
import fileManagerRoutes from "./filemanager"
//import courseCategoryRoutes from './coursecategory';
import documents from "./documents"
@ -140,6 +141,7 @@ const router = createRouter({
component: MySessionListUpcoming,
meta: { requiresAuth: true },
},
fileManagerRoutes,
termsRoutes,
socialNetworkRoutes,
catalogueCourses,

@ -26,6 +26,11 @@ export default {
//path: ':id',
path: 'show',
component: () => import('../views/page/Show.vue')
}
},
{
name: 'PageEditorDemo',
path: 'editor-demo',
component: () => import('../views/page/EditorDemo.vue')
},
]
};

@ -0,0 +1,496 @@
<template>
<div
v-if="isAuthenticated"
class="q-card"
>
<div class="p-4 flex flex-row gap-1 mb-2">
<div class="flex flex-row gap-2">
<Button
class="btn btn--primary"
icon="fa fa-folder-plus"
label="New folder"
@click="openNew"
/>
<Button
class="btn btn--primary"
icon="fa fa-file-upload"
label="Upload"
@click="uploadDocumentHandler()"
/>
<Button
v-if="selectedItems.length"
class="btn btn--danger"
icon="pi pi-trash"
label="Delete"
@click="confirmDeleteMultiple"
/>
</div>
</div>
</div>
<DataTable
v-model:filters="filters"
v-model:selection="selectedItems"
:globalFilterFields="['resourceNode.title', 'resourceNode.updatedAt']"
:lazy="true"
:loading="isLoading"
:paginator="true"
:rows="10"
:rowsPerPageOptions="[5, 10, 20, 50]"
:totalRecords="totalItems"
:value="items"
class="p-datatable-sm"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords}"
dataKey="iid"
filterDisplay="menu"
paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown"
responsiveLayout="scroll"
@page="onPage($event)"
@sort="sortingChanged($event)"
>
<Column
:header="$t('Title')"
:sortable="true"
field="resourceNode.title"
>
<template #body="slotProps">
<div v-if="slotProps.data && slotProps.data.resourceNode && slotProps.data.resourceNode.resourceFile">
<ResourceFileLink :resource="slotProps.data" />
<v-icon
v-if="slotProps.data.resourceLinkListFromEntity && slotProps.data.resourceLinkListFromEntity.length > 0"
icon="mdi-link"
/>
</div>
<div v-else>
<a
v-if="slotProps.data"
class="cursor-pointer"
@click="handleClick(slotProps.data)"
>
<v-icon icon="mdi-folder" />
{{ slotProps.data.resourceNode.title }}
</a>
</div>
</template>
</Column>
<Column
:header="$t('Size')"
:sortable="true"
field="resourceNode.resourceFile.size"
>
<template #body="slotProps">
{{
slotProps.data.resourceNode.resourceFile
? prettyBytes(slotProps.data.resourceNode.resourceFile.size)
: ""
}}
</template>
</Column>
<Column
:header="$t('Modified')"
:sortable="true"
field="resourceNode.updatedAt"
>
<template #body="slotProps">
{{ relativeDatetime(slotProps.data.resourceNode.updatedAt) }}
</template>
</Column>
<Column :exportable="false">
<template #body="slotProps">
<div class="flex flex-row gap-2">
<Button
v-if="isAuthenticated"
class="btn btn--danger"
icon="pi pi-trash"
@click="confirmDeleteItem(slotProps.data)"
/>
</div>
</template>
</Column>
<Column :exportable="false">
<template #body="slotProps">
<div class="flex flex-row gap-2">
<Button
class="p-button-sm p-button p-mr-2"
label="Select"
@click="returnToEditor(slotProps.data)"
/>
</div>
</template>
</Column>
</DataTable>
<Dialog
v-model:visible="itemDialog"
:header="$t('New folder')"
:modal="true"
:style="{ width: '450px' }"
class="p-fluid"
>
<div class="p-field">
<label for="title">{{ $t("Name") }}</label>
<InputText
id="title"
v-model.trim="item.title"
:class="{ 'p-invalid': submitted && !item.title }"
autocomplete="off"
autofocus
required="true"
/>
<small
v-if="submitted && !item.title"
class="p-error"
>$t('Title is required')</small
>
</div>
<template #footer>
<Button
class="p-button-text"
icon="pi pi-times"
label="Cancel"
@click="hideDialog"
/>
<Button
class="p-button-text"
icon="pi pi-check"
label="Save"
@click="saveItem"
/>
</template>
</Dialog>
<Dialog v-model:visible="deleteItemDialog" :modal="true" :style="{ width: '450px' }" header="Confirm">
<div class="confirmation-content">
<i class="pi pi-exclamation-triangle p-mr-3" style="font-size: 2rem"></i>
<span>Are you sure you want to delete <b>{{ itemToDelete?.title }}</b>?</span>
</div>
<template #footer>
<Button class="p-button-text" icon="pi pi-times" label="No" @click="deleteItemDialog = false" />
<Button class="p-button-text" icon="pi pi-check" label="Yes" @click="deleteItemButton" />
</template>
</Dialog>
<Dialog
v-model:visible="deleteMultipleDialog"
:modal="true"
:style="{ width: '450px' }"
header="Confirm"
>
<div class="confirmation-content">
<i
class="pi pi-exclamation-triangle p-mr-3"
style="font-size: 2rem"
/>
<span v-if="item">{{ $t('Are you sure you want to delete the selected items?') }}</span>
</div>
<template #footer>
<Button
class="p-button-text"
icon="pi pi-times"
label="No"
@click="deleteMultipleDialog = false"
/>
<Button
class="p-button-text"
icon="pi pi-check"
label="Yes"
@click="deleteMultipleItems"
/>
</template>
</Dialog>
<Dialog v-model:visible="detailsDialogVisible" :header="selectedItem.title || 'Item Details'" :modal="true" :style="{ width: '50%' }">
<div v-if="Object.keys(selectedItem).length > 0">
<p><strong>Title:</strong> {{ selectedItem.resourceNode.title }}</p>
<p><strong>Modified:</strong> {{ relativeDatetime(selectedItem.resourceNode.updatedAt) }}</p>
<p><strong>Size:</strong> {{ prettyBytes(selectedItem.resourceNode.resourceFile.size) }}</p>
<p><strong>URL:</strong> <a :href="selectedItem.contentUrl" target="_blank">Open File</a></p>
</div>
<template #footer>
<Button class="p-button-text" label="Close" @click="closeDetailsDialog" />
</template>
</Dialog>
</template>
<script>
import { mapActions, mapGetters } from "vuex"
import { mapFields } from "vuex-map-fields"
import ListMixin from "../../mixins/ListMixin"
import ActionCell from "../../components/ActionCell.vue"
import ResourceIcon from "../../components/documents/ResourceIcon.vue"
import ResourceFileLink from "../../components/documents/ResourceFileLink.vue"
import DataFilter from "../../components/DataFilter"
import isEmpty from "lodash/isEmpty"
import { RESOURCE_LINK_PUBLISHED } from "../../components/resource_links/visibility"
import { useI18n } from "vue-i18n"
import { useFormatDate } from "../../composables/formatDate"
import prettyBytes from "pretty-bytes"
export default {
name: "FileManagerList",
servicePrefix: "FileManager",
components: {
ActionCell,
ResourceIcon,
ResourceFileLink,
DataFilter,
},
mixins: [ListMixin],
setup() {
const { t } = useI18n()
const { relativeDatetime } = useFormatDate()
const data = {
sortBy: "title",
sortDesc: false,
columnsQua: [
{ align: "left", name: "resourceNode.title", label: t("Title"), field: "resourceNode.title", sortable: true },
{
align: "left",
name: "resourceNode.updatedAt",
label: t("Modified"),
field: "resourceNode.updatedAt",
sortable: true,
},
{
name: "resourceNode.resourceFile.size",
label: t("Size"),
field: "resourceNode.resourceFile.size",
sortable: true,
},
{ name: "action", label: t("Actions"), field: "action", sortable: false },
],
columns: [
{ label: t("Title"), field: "title", name: "title", sortable: true },
{ label: t("Modified"), field: "resourceNode.updatedAt", name: "updatedAt", sortable: true },
{ label: t("Size"), field: "resourceNode.resourceFile.size", name: "size", sortable: true },
{ label: t("Actions"), name: "action", sortable: false },
],
pageOptions: [10, 20, 50, t("All")],
selected: [],
isBusy: false,
options: [],
selectedItems: [],
// prime vue
deleteMultipleDialog: false,
item: {},
filters: { shared: 0, loadNode: 1 },
submitted: false,
prettyBytes,
relativeDatetime,
t,
}
return data
},
created() {
this.resetList = true
this.onUpdateOptions(this.options)
this.isFromEditor = window.location.search.includes('editor=tinymce');
},
computed: {
// From crud.js list function
...mapGetters("resourcenode", {
resourceNode: "getResourceNode",
}),
...mapGetters({
isAuthenticated: "security/isAuthenticated",
isAdmin: "security/isAdmin",
currentUser: "security/getUser",
}),
...mapGetters("personalfile", {
items: "list",
}),
//...getters
// From ListMixin
...mapFields("personalfile", {
deletedResource: "deleted",
error: "error",
isLoading: "isLoading",
resetList: "resetList",
totalItems: "totalItems",
view: "view",
}),
},
data() {
return {
itemDialog: false,
detailsDialogVisible: false,
deleteItemDialog: false,
selectedItem: {},
itemToDelete: null,
isFromEditor: false,
};
},
methods: {
showHandler(item) {
this.selectedItem = item;
this.detailsDialogVisible = true;
},
closeDetailsDialog() {
this.detailsDialogVisible = false;
},
// prime
onPage(event) {
this.options.itemsPerPage = event.rows
this.options.page = event.page + 1
this.options.sortBy = event.sortField
this.options.sortDesc = event.sortOrder === -1
this.onUpdateOptions(this.options)
},
sortingChanged(event) {
console.log("sortingChanged")
console.log(event)
this.options.sortBy = event.sortField
this.options.sortDesc = event.sortOrder === -1
this.onUpdateOptions(this.options)
// ctx.sortBy ==> Field key for sorting by (or null for no sorting)
// ctx.sortDesc ==> true if sorting descending, false otherwise
},
openNew() {
this.item = {}
this.submitted = false
this.itemDialog = true
},
hideDialog() {
this.itemDialog = false
this.submitted = false
},
saveItem() {
this.submitted = true
if (this.item.title.trim()) {
if (this.item.id) {
} else {
let resourceNodeId = this.currentUser.resourceNode["id"]
if (!isEmpty(this.$route.params.node)) {
resourceNodeId = this.$route.params.node
}
this.item.filetype = "folder"
this.item.parentResourceNodeId = resourceNodeId
this.item.resourceLinkList = JSON.stringify([
{
gid: 0,
sid: 0,
cid: 0,
visibility: RESOURCE_LINK_PUBLISHED,
},
])
this.createWithFormData(this.item)
this.showMessage("Saved")
}
this.itemDialog = false
this.item = {}
}
},
editItem(item) {
this.item = { ...item }
this.itemDialog = true
},
confirmDeleteItem(item) {
console.log('confirmDeleteItem :::', item)
this.item = { ...item }
this.itemToDelete = { ...item }
this.deleteItemDialog = true
},
confirmDeleteMultiple() {
this.deleteMultipleDialog = true
},
deleteMultipleItems() {
console.log("deleteMultipleItems")
console.log(this.selectedItems)
this.deleteMultipleAction(this.selectedItems)
this.onRequest({
pagination: this.pagination,
})
this.deleteMultipleDialog = false
this.selectedItems = null
},
deleteItemButton() {
console.log("deleteItem", this.itemToDelete);
if (this.itemToDelete && this.itemToDelete.id) {
this.deleteItem(this.itemToDelete)
.then(() => {
this.$toast.add({ severity: 'success', summary: 'Success', detail: 'Item deleted successfully', life: 3000 });
this.deleteItemDialog = false;
this.itemToDelete = null;
this.onUpdateOptions(this.options);
})
.catch(error => {
console.error("Error deleting the item:", error);
this.$toast.add({ severity: 'error', summary: 'Error', detail: 'An error occurred while deleting the item', life: 3000 });
});
} else {
console.error("No item to delete or item ID is missing");
}
},
onRowSelected(items) {
this.selected = items
},
selectAllRows() {
this.$refs.selectableTable.selectAllRows()
},
clearSelected() {
this.$refs.selectableTable.clearSelected()
},
returnToEditor(item) {
const url = item.contentUrl
// Tiny mce.
window.parent.postMessage(
{
url: url,
},
"*",
)
if (parent.tinymce) {
parent.tinymce.activeEditor.windowManager.close()
}
// Ckeditor
function getUrlParam(paramName) {
var reParam = new RegExp("(?:[\?&]|&amp;)" + paramName + "=([^&]+)", "i")
var match = window.location.search.match(reParam)
return match && match.length > 1 ? match[1] : ""
}
var funcNum = getUrlParam("CKEditorFuncNum")
if (window.opener.CKEDITOR) {
window.opener.CKEDITOR.tools.callFunction(funcNum, url)
window.close()
}
},
async deleteSelected() {
console.log("deleteSelected")
this.deleteMultipleAction(this.selected)
this.onRequest({
pagination: this.pagination,
})
console.log("end -- deleteSelected")
},
...mapActions("personalfile", {
getPage: "fetchAll",
createWithFormData: "createWithFormData",
deleteItem: "del",
deleteMultipleAction: "delMultiple",
}),
...mapActions("resourcenode", {
findResourceNode: "findResourceNode",
}),
},
}
</script>

@ -0,0 +1,129 @@
<template>
<div>
<dashboard
:plugins="['Webcam', 'ImageEditor']"
:props="{
proudlyDisplayPoweredByUppy: false,
width: '100%',
}"
:uppy="uppy"
/>
</div>
</template>
<script>
import { mapActions, mapGetters, useStore } from "vuex"
import { createHelpers } from "vuex-map-fields"
import UploadMixin from "../../mixins/UploadMixin"
import { computed, ref } from "vue"
import isEmpty from "lodash/isEmpty"
import "@uppy/core/dist/style.css"
import "@uppy/dashboard/dist/style.css"
import "@uppy/image-editor/dist/style.css"
import Uppy from "@uppy/core"
import Webcam from "@uppy/webcam"
import { Dashboard } from "@uppy/vue"
import { useRoute, useRouter } from "vue-router"
import { ENTRYPOINT } from "../../config/entrypoint"
const XHRUpload = require("@uppy/xhr-upload")
const ImageEditor = require("@uppy/image-editor")
const servicePrefix = "FileManager"
const { mapFields } = createHelpers({
getterType: "personalfile/getField",
mutationType: "personalfile/updateField",
})
export default {
name: "FileManagerUploadFile",
servicePrefix,
components: {
Dashboard,
},
setup() {
const parentResourceNodeId = ref(null)
const route = useRoute()
const router = useRouter();
const store = useStore()
const user = computed(() => store.getters["security/getUser"])
parentResourceNodeId.value = user.value.resourceNode["id"]
if (route.params.node) {
parentResourceNodeId.value = Number(route.params.node)
}
let uppy = ref()
uppy.value = new Uppy()
.use(Webcam)
.use(ImageEditor, {
cropperOptions: {
viewMode: 1,
background: false,
autoCropArea: 1,
responsive: true,
},
actions: {
revert: true,
rotate: true,
granularRotate: true,
flip: true,
zoomIn: true,
zoomOut: true,
cropSquare: true,
cropWidescreen: true,
cropWidescreenVertical: true,
},
})
.use(XHRUpload, {
endpoint: ENTRYPOINT + "personal_files",
formData: true,
fieldName: "uploadFile",
})
uppy.value.setMeta({
filetype: "file",
parentResourceNodeId: parentResourceNodeId.value,
})
uppy.value.on("complete", (result) => {
router.push({ name: "FileManagerList" });
});
return {
uppy,
}
},
mixins: [UploadMixin],
data() {
return {
files: [],
parentResourceNodeId: 0,
}
},
computed: {
...mapFields(["error", "isLoading", "created", "violations"]),
...mapGetters({
isAuthenticated: "security/isAuthenticated",
isAdmin: "security/isAdmin",
currentUser: "security/getUser",
}),
},
created() {
let nodeId = this.$route.params.node
if (isEmpty(nodeId)) {
nodeId = this.currentUser.resourceNode["id"]
}
this.parentResourceNodeId = Number(nodeId)
},
methods: {
...mapActions("personalfile", ["uploadMany", "createFile"]),
},
}
</script>

@ -0,0 +1,24 @@
<template>
<div>
<h1>Editor Demo</h1>
<BaseTinyEditor
editor-id="demoEditor"
v-model="editorContent"
:mode="editorMode"
:use-file-manager="useFileManager"
title="Demo Editor"
help-text="Edit your content here in demo mode."
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import BaseTinyEditor from "../../components/basecomponents/BaseTinyEditor.vue"
const editorContent = ref('')
// Here you decide the mode, 'personal_files' or 'documents'
const editorMode = ref('personal_files')
// Decide if you want to use the file manager or not
const useFileManager = ref(true)
</script>

@ -185,7 +185,7 @@ class BaseResourceFileAction
/**
* Function loaded when creating a resource using the api, then the ResourceListener is executed.
*/
protected function handleCreateFileRequest(
public function handleCreateFileRequest(
AbstractResource $resource,
ResourceRepository $resourceRepository,
Request $request,

@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace Chamilo\CoreBundle\Controller;
use Chamilo\CoreBundle\Controller\Api\BaseResourceFileAction;
use Chamilo\CoreBundle\Entity\PersonalFile;
use Chamilo\CoreBundle\Repository\Node\PersonalFileRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
#[Route('/file-manager')]
class FileManagerController extends AbstractController
{
private BaseResourceFileAction $baseResourceFileAction;
private PersonalFileRepository $personalFileRepository;
private EntityManagerInterface $entityManager;
public function __construct(
BaseResourceFileAction $baseResourceFileAction,
PersonalFileRepository $personalFileRepository,
EntityManager $entityManager
) {
$this->baseResourceFileAction = $baseResourceFileAction;
$this->personalFileRepository = $personalFileRepository;
$this->entityManager = $entityManager;
}
#[Route('/list', name: 'file_manager_list', methods: ['GET'])]
public function list(): JsonResponse
{
// Implement logic to list files and folders
// This could be a call to your service or logic to retrieve files/folders
return $this->json(['files' => []]);
}
#[Route('/upload', name: 'file_manager_upload', methods: ['POST'])]
public function upload(Request $request): JsonResponse
{
// Implement logic to upload files
// This part will handle receiving and storing uploaded files
return $this->json(['message' => 'File(s) uploaded successfully']);
}
/**
* @throws Exception
*/
#[Route('/upload-image', name: 'file_manager_upload_image', methods: ['POST'])]
public function uploadImage(Request $request): JsonResponse
{
$resource = new PersonalFile();
$result = $this->baseResourceFileAction->handleCreateFileRequest(
$resource,
$this->personalFileRepository,
$request,
$this->entityManager,
'overwrite'
);
$this->entityManager->persist($resource);
$this->entityManager->flush();
if (!$result) {
return $this->json(['error' => 'File upload failed'], Response::HTTP_BAD_REQUEST);
}
return $this->json([
'message' => 'File uploaded successfully',
'data' => $result,
'location' => $this->personalFileRepository->getResourceFileUrl($resource),
]);
}
#[Route('/create-folder', name: 'file_manager_create_folder', methods: ['POST'])]
public function createFolder(Request $request): JsonResponse
{
// Implement logic to create new folders
return $this->json(['message' => 'Folder created successfully']);
}
#[Route('/rename', name: 'file_manager_rename', methods: ['POST'])]
public function rename(Request $request): JsonResponse
{
// Implement logic to rename files/folders
return $this->json(['message' => 'File/folder renamed successfully']);
}
#[Route('/delete', name: 'file_manager_delete', methods: ['DELETE'])]
public function delete(Request $request): JsonResponse
{
// Implement logic to delete files/folders
return $this->json(['message' => 'File/folder deleted successfully']);
}
#[Route('/download/{filename}', name: 'file_manager_download', methods: ['GET'])]
public function download(string $filename): Response
{
// Implement logic to download files
// Replace 'path/to/your/files' with the actual path where the files are stored
$filePath = 'path/to/your/files/' . $filename;
return new BinaryFileResponse($filePath);
}
}
Loading…
Cancel
Save