Glossary: Change from legacy to Vue interface and backend adaptation - refs #4768
parent
1492fcce16
commit
a93b06ef21
@ -0,0 +1,95 @@ |
|||||||
|
<template> |
||||||
|
<form @submit.prevent="submitForm" class="export-form"> |
||||||
|
<div class="form-field"> |
||||||
|
<label for="export-format">Export Format:</label> |
||||||
|
<select id="export-format" v-model="selectedFormat"> |
||||||
|
<option value="csv">CSV</option> |
||||||
|
<option value="xls">Excel</option> |
||||||
|
<option value="pdf">PDF</option> |
||||||
|
</select> |
||||||
|
</div> |
||||||
|
<button type="submit" class="btn btn--primary">Export</button> |
||||||
|
</form> |
||||||
|
</template> |
||||||
|
|
||||||
|
|
||||||
|
<script> |
||||||
|
import axios from "axios"; |
||||||
|
import { ENTRYPOINT } from "../../config/entrypoint"; |
||||||
|
import { RESOURCE_LINK_PUBLISHED } from "../resource_links/visibility"; |
||||||
|
import { useRoute, useRouter } from 'vue-router'; |
||||||
|
import { useI18n } from "vue-i18n"; |
||||||
|
import { ref, onMounted } from "vue"; |
||||||
|
|
||||||
|
export default { |
||||||
|
setup() { |
||||||
|
const route = useRoute(); |
||||||
|
const router = useRouter(); |
||||||
|
const { t } = useI18n(); |
||||||
|
|
||||||
|
const selectedFormat = ref('csv'); |
||||||
|
const parentResourceNodeId = ref(Number(route.params.node)); |
||||||
|
const resourceLinkList = ref( |
||||||
|
JSON.stringify([ |
||||||
|
{ |
||||||
|
sid: route.query.sid, |
||||||
|
cid: route.query.cid, |
||||||
|
visibility: RESOURCE_LINK_PUBLISHED, // visible by default |
||||||
|
}, |
||||||
|
]) |
||||||
|
); |
||||||
|
|
||||||
|
const submitForm = () => { |
||||||
|
const format = selectedFormat.value; |
||||||
|
|
||||||
|
const formData = new FormData(); |
||||||
|
formData.append('format', format); |
||||||
|
formData.append("sid", route.query.sid); |
||||||
|
formData.append("cid", route.query.cid); |
||||||
|
|
||||||
|
const endpoint = `${ENTRYPOINT}glossaries/export`; |
||||||
|
axios.post(endpoint, formData, { responseType: 'blob' }) |
||||||
|
.then(response => { |
||||||
|
const fileUrl = window.URL.createObjectURL(new Blob([response.data])); |
||||||
|
const link = document.createElement('a'); |
||||||
|
link.href = fileUrl; |
||||||
|
link.setAttribute('download', `glossary.${format}`); |
||||||
|
document.body.appendChild(link); |
||||||
|
link.click(); |
||||||
|
document.body.removeChild(link); |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
console.error('Error exporting glossary:', error); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
selectedFormat, |
||||||
|
submitForm, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.export-form { |
||||||
|
max-width: 400px; |
||||||
|
margin: 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
.form-field { |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
label { |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.btn--primary { |
||||||
|
background-color: #007bff; |
||||||
|
color: #ffffff; |
||||||
|
padding: 10px 20px; |
||||||
|
border: none; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,154 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<form @submit.prevent="submitGlossaryForm" name="glossary" id="glossary"> |
||||||
|
<div class="field"> |
||||||
|
<div class="p-float-label"> |
||||||
|
<input v-model="formData.name" id="glossary_title" name="name" type="text" class="p-inputtext p-component p-filled" /> |
||||||
|
|
||||||
|
<label for="glossary_title"> |
||||||
|
<span class="form_required">*</span> |
||||||
|
Term |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="field"> |
||||||
|
<div class="p-float-label"> |
||||||
|
<textarea v-model="formData.description" id="description" name="description"></textarea> |
||||||
|
|
||||||
|
<label for="description"> |
||||||
|
<span class="form_required">*</span> |
||||||
|
Term definition |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="field 2"> |
||||||
|
<div class="8"> |
||||||
|
|
||||||
|
<label for="glossary_SubmitGlossary" class="h-4 "> |
||||||
|
|
||||||
|
</label> |
||||||
|
|
||||||
|
<button class="btn btn--primary" name="SubmitGlossary" type="submit" id="glossary_SubmitGlossary"> |
||||||
|
<em class="mdi mdi-plus"></em> Save term |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="form-group"> |
||||||
|
<div class="col-sm-offset-2 col-sm-10"> |
||||||
|
<span class="form_required">*</span> |
||||||
|
<small>Required field</small> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<input name="_qf__glossary" type="hidden" value="" id="glossary__qf__glossary" /> |
||||||
|
<input name="sec_token" type="hidden" value="1e7d47c276bfdfe308a79e1b71d58089" id="glossary_sec_token" /> |
||||||
|
|
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import axios from "axios"; |
||||||
|
import { ENTRYPOINT } from "../../config/entrypoint"; |
||||||
|
import { useRoute, useRouter } from 'vue-router'; |
||||||
|
import { useI18n } from "vue-i18n"; |
||||||
|
import { ref, onMounted } from "vue"; |
||||||
|
import {RESOURCE_LINK_PUBLISHED} from "../resource_links/visibility"; |
||||||
|
|
||||||
|
export default { |
||||||
|
props: { |
||||||
|
termId: { |
||||||
|
type: Number, |
||||||
|
default: null |
||||||
|
} |
||||||
|
}, |
||||||
|
setup(props) { |
||||||
|
const route = useRoute(); |
||||||
|
const router = useRouter(); |
||||||
|
const { t } = useI18n(); |
||||||
|
|
||||||
|
const parentResourceNodeId = ref(Number(route.params.node)); |
||||||
|
|
||||||
|
const resourceLinkList = ref( |
||||||
|
JSON.stringify([ |
||||||
|
{ |
||||||
|
sid: route.query.sid, |
||||||
|
cid: route.query.cid, |
||||||
|
visibility: RESOURCE_LINK_PUBLISHED, // visible by default |
||||||
|
}, |
||||||
|
]) |
||||||
|
); |
||||||
|
|
||||||
|
const formData = ref({ |
||||||
|
name: '', |
||||||
|
description: '', |
||||||
|
}); |
||||||
|
|
||||||
|
const fetchTerm = () => { |
||||||
|
if (props.termId) { |
||||||
|
axios.get(ENTRYPOINT + 'glossaries/' + props.termId) |
||||||
|
.then(response => { |
||||||
|
const glossary = response.data; |
||||||
|
formData.value.name = glossary.name; |
||||||
|
formData.value.description = glossary.description; |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
console.error('Error fetching link:', error); |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
fetchTerm(); |
||||||
|
}); |
||||||
|
|
||||||
|
const submitGlossaryForm = () => { |
||||||
|
|
||||||
|
const postData = { |
||||||
|
name: formData.value.name, |
||||||
|
description: formData.value.description, |
||||||
|
parentResourceNodeId: parentResourceNodeId.value, |
||||||
|
resourceLinkList: resourceLinkList.value, |
||||||
|
sid: route.query.sid, |
||||||
|
cid: route.query.cid, |
||||||
|
}; |
||||||
|
|
||||||
|
if (props.termId) { |
||||||
|
const endpoint = `${ENTRYPOINT}glossaries/${props.termId}`; |
||||||
|
axios.put(endpoint, postData) |
||||||
|
.then(response => { |
||||||
|
console.log('Glossary updated:', response.data); |
||||||
|
|
||||||
|
router.push({ |
||||||
|
name: "GlossaryList", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
console.error('Error updating Glossary:', error); |
||||||
|
}); |
||||||
|
|
||||||
|
} else { |
||||||
|
const endpoint = `${ENTRYPOINT}glossaries`; |
||||||
|
axios.post(endpoint, postData) |
||||||
|
.then(response => { |
||||||
|
console.log('Glossary created:', response.data); |
||||||
|
|
||||||
|
router.push({ |
||||||
|
name: "GlossaryList", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
console.error('Error creating Glossary:', error); |
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
formData, |
||||||
|
submitGlossaryForm, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
@ -0,0 +1,127 @@ |
|||||||
|
<template> |
||||||
|
<form @submit.prevent="submitForm"> |
||||||
|
<div class="field 2"> |
||||||
|
<div class="8"> |
||||||
|
<label for="glossary_file" class="h-4"> |
||||||
|
File |
||||||
|
</label> |
||||||
|
<input class="mt-1" :ref="fileInputRef" name="file" type="file" id="glossary_file" /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="field"> |
||||||
|
<label>File type</label> |
||||||
|
<div class="field-radiobutton"> |
||||||
|
<input name="file_type" value="csv" type="radio" id="qf_85f94d" v-model="fileType" checked="checked" /> |
||||||
|
<label for="qf_85f94d" class="">CSV</label> |
||||||
|
</div> |
||||||
|
<div class="field-radiobutton"> |
||||||
|
<input name="file_type" value="xls" type="radio" id="qf_bff468" v-model="fileType" /> |
||||||
|
<label for="qf_bff468" class="">XLS</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="field 2"> |
||||||
|
<div class="8"> |
||||||
|
<div id="replace" class="field-checkbox"> |
||||||
|
<input class="appearance-none checked:bg-support-4 outline-none" name="replace" type="checkbox" value="1" id="qf_5b8df0" v-model="replace" /> |
||||||
|
<label for="qf_5b8df0" class=""> |
||||||
|
Delete all terms before import. |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="field 2"> |
||||||
|
<div class="8"> |
||||||
|
<div id="update" class="field-checkbox"> |
||||||
|
<input class="appearance-none checked:bg-support-4 outline-none" name="update" type="checkbox" value="1" id="qf_594e6e" v-model="update" /> |
||||||
|
<label for="qf_594e6e" class=""> |
||||||
|
Update existing terms. |
||||||
|
</label> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div class="field 2"> |
||||||
|
<div class="8"> |
||||||
|
<button class="btn btn--primary" name="SubmitImport" type="submit" id="glossary_SubmitImport"> |
||||||
|
<em class="mdi mdi-check"></em> Import |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</form> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import axios from 'axios'; |
||||||
|
import { ENTRYPOINT } from "../../config/entrypoint"; |
||||||
|
import { useRoute, useRouter } from "vue-router"; |
||||||
|
import { useI18n } from "vue-i18n"; |
||||||
|
import { ref } from "vue"; |
||||||
|
import { RESOURCE_LINK_PUBLISHED } from "../resource_links/visibility"; |
||||||
|
|
||||||
|
export default { |
||||||
|
setup() { |
||||||
|
const route = useRoute(); |
||||||
|
const router = useRouter(); |
||||||
|
const { t } = useI18n(); |
||||||
|
|
||||||
|
const fileInputRef = ref(null); |
||||||
|
const fileType = ref("csv"); |
||||||
|
const replace = ref(false); |
||||||
|
const update = ref(false); |
||||||
|
const parentResourceNodeId = ref(Number(route.params.node)); |
||||||
|
|
||||||
|
const resourceLinkList = ref( |
||||||
|
JSON.stringify([ |
||||||
|
{ |
||||||
|
sid: route.query.sid, |
||||||
|
cid: route.query.cid, |
||||||
|
visibility: RESOURCE_LINK_PUBLISHED, // visible by default |
||||||
|
}, |
||||||
|
]) |
||||||
|
); |
||||||
|
|
||||||
|
const submitForm = async () => { |
||||||
|
const fileInput = document.getElementById('glossary_file'); |
||||||
|
const file = fileInput.files[0]; |
||||||
|
const formData = new FormData(); |
||||||
|
formData.append("file", file); |
||||||
|
formData.append("file_type", fileType.value); |
||||||
|
formData.append("replace", replace.value); |
||||||
|
formData.append("update", update.value); |
||||||
|
formData.append("sid", route.query.sid); |
||||||
|
formData.append("cid", route.query.cid); |
||||||
|
formData.append("parentResourceNodeId", parentResourceNodeId.value); |
||||||
|
formData.append("resourceLinkList", resourceLinkList.value); |
||||||
|
|
||||||
|
console.log('formData', formData); |
||||||
|
|
||||||
|
console.log(ENTRYPOINT + 'glossaries/import'); |
||||||
|
try { |
||||||
|
// eslint-disable-next-line no-unused-vars |
||||||
|
const response = await axios.post(ENTRYPOINT + 'glossaries/import', formData, { |
||||||
|
headers: { |
||||||
|
'Content-Type': 'multipart/form-data' |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
router.push({ |
||||||
|
name: "GlossaryList", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
} catch (error) { |
||||||
|
fileInputRef.value = null; |
||||||
|
fileType.value = "csv"; |
||||||
|
replace.value = false; |
||||||
|
update.value = false; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
fileInputRef, |
||||||
|
fileType, |
||||||
|
replace, |
||||||
|
update, |
||||||
|
submitForm, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
@ -0,0 +1,9 @@ |
|||||||
|
<template> |
||||||
|
<router-view></router-view> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'GlossaryLayout' |
||||||
|
} |
||||||
|
</script> |
@ -0,0 +1,34 @@ |
|||||||
|
export default { |
||||||
|
path: '/resources/glossary/:node/', |
||||||
|
meta: { requiresAuth: true, showBreadcrumb: true }, |
||||||
|
name: 'glossary', |
||||||
|
component: () => import('../components/glossary/GlossaryLayout.vue'), |
||||||
|
redirect: { name: 'GlossaryList' }, |
||||||
|
children: [ |
||||||
|
{ |
||||||
|
name: 'GlossaryList', |
||||||
|
path: '', |
||||||
|
component: () => import('../views/glossary/GlossaryList.vue') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'CreateTerm', |
||||||
|
path: 'create', |
||||||
|
component: () => import('../views/glossary/CreateTerm.vue') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'UpdateTerm', |
||||||
|
path: 'edit/:id', |
||||||
|
component: () => import('../views/glossary/UpdateTerm.vue') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'ImportGlossary', |
||||||
|
path: '', |
||||||
|
component: () => import('../views/glossary/ImportGlossary.vue') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'ExportGlossary', |
||||||
|
path: '', |
||||||
|
component: () => import('../views/glossary/ExportGlossary.vue') |
||||||
|
}, |
||||||
|
] |
||||||
|
}; |
@ -0,0 +1,35 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<div class="mb-4"> |
||||||
|
<button class="btn btn--secondary" @click="goBack">Back</button> |
||||||
|
</div> |
||||||
|
<h1 class="text-h3 font-small text-gray-800 mb-4">Add new glossary term<hr /></h1> |
||||||
|
<GlossaryForm /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import GlossaryForm from "../../components/glossary/GlossaryForm.vue"; |
||||||
|
import { useRouter, useRoute } from 'vue-router'; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
GlossaryForm, |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
const router = useRouter(); |
||||||
|
const route = useRoute(); |
||||||
|
|
||||||
|
const goBack = () => { |
||||||
|
router.push({ |
||||||
|
name: "GlossaryList", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
goBack, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
@ -0,0 +1,41 @@ |
|||||||
|
<template> |
||||||
|
<div class="export-glossary"> |
||||||
|
<div class="mb-4"> |
||||||
|
<button class="btn btn--secondary" @click="goBack">Back</button> |
||||||
|
</div> |
||||||
|
<h2>Export Glossary</h2> |
||||||
|
<GlossaryExportForm /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import GlossaryExportForm from '../../components/glossary/GlossaryExportForm.vue'; |
||||||
|
import {useRoute, useRouter} from "vue-router"; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
GlossaryExportForm |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
const router = useRouter(); |
||||||
|
const route = useRoute(); |
||||||
|
|
||||||
|
const goBack = () => { |
||||||
|
router.push({ |
||||||
|
name: "GlossaryList", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
goBack, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.export-glossary { |
||||||
|
margin: 20px; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,258 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<input |
||||||
|
type="text" |
||||||
|
v-model="searchTerm" |
||||||
|
placeholder="Search term..." |
||||||
|
/> |
||||||
|
<ButtonToolbar v-if="isAuthenticated && isCurrentTeacher"> |
||||||
|
<BaseButton |
||||||
|
label="Add new glossary term" |
||||||
|
icon="new_glossary_term" |
||||||
|
class="mr-2 mb-2" |
||||||
|
type="black" |
||||||
|
@click="addNewTerm" |
||||||
|
/> |
||||||
|
<BaseButton |
||||||
|
label="Import glossary" |
||||||
|
icon="import" |
||||||
|
class="mr-2 mb-2" |
||||||
|
type="black" |
||||||
|
@click="importGlossary" |
||||||
|
/> |
||||||
|
<BaseButton |
||||||
|
label="Export" |
||||||
|
icon="save" |
||||||
|
class="mr-2 mb-2" |
||||||
|
type="black" |
||||||
|
@click="exportGlossary" |
||||||
|
/> |
||||||
|
<BaseButton |
||||||
|
:label="view === 'table' ? 'List view' : 'Table view'" |
||||||
|
icon="view_text" |
||||||
|
class="mr-2 mb-2" |
||||||
|
type="black" |
||||||
|
@click="changeView(view)" |
||||||
|
/> |
||||||
|
<BaseButton |
||||||
|
label="Export to Documents" |
||||||
|
icon="export_to_documents" |
||||||
|
class="mr-2 mb-2" |
||||||
|
type="black" |
||||||
|
@click="exportToDocuments" |
||||||
|
/> |
||||||
|
</ButtonToolbar> |
||||||
|
|
||||||
|
<div v-if="glossaries.length === 0"> |
||||||
|
<!-- Render the image and create button --> |
||||||
|
<EmptyState |
||||||
|
icon="mdi mdi-alphabetical" |
||||||
|
summary="Add your first term glossary to this course" |
||||||
|
> |
||||||
|
<BaseButton |
||||||
|
label="Add Glossary" |
||||||
|
class="mt-4" |
||||||
|
icon="plus" |
||||||
|
type="primary" |
||||||
|
@click="addNewTerm" |
||||||
|
/> |
||||||
|
</EmptyState> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div v-if="glossaries"> |
||||||
|
<div v-if="view === 'list'"> |
||||||
|
<ul> |
||||||
|
<li v-for="glossary in glossaries" :key="glossary.id"> |
||||||
|
<span>{{ glossary.name }} - {{ glossary.description }}</span> |
||||||
|
<BaseButton |
||||||
|
label="Edit" |
||||||
|
class="mr-2" |
||||||
|
icon="edit" |
||||||
|
type="black" |
||||||
|
@click="editTerm(glossary.iid)" |
||||||
|
/> |
||||||
|
<BaseButton |
||||||
|
label="Delete" |
||||||
|
class="mr-2" |
||||||
|
icon="delete" |
||||||
|
type="black" |
||||||
|
@click="deleteTerm(glossary.iid)" |
||||||
|
/> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
<table v-else> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>Title</th> |
||||||
|
<th>Description</th> |
||||||
|
<th>Actions</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
<tr v-for="glossary in glossaries" :key="glossary.id"> |
||||||
|
<td>{{ glossary.name }}</td> |
||||||
|
<td>{{ glossary.description }}</td> |
||||||
|
<td> |
||||||
|
<BaseButton |
||||||
|
label="Edit" |
||||||
|
class="mr-2" |
||||||
|
icon="edit" |
||||||
|
type="black" |
||||||
|
@click="editTerm(glossary.iid)" |
||||||
|
/> |
||||||
|
<BaseButton |
||||||
|
label="Delete" |
||||||
|
class="mr-2" |
||||||
|
icon="delete" |
||||||
|
type="black" |
||||||
|
@click="deleteTerm(glossary.iid)" |
||||||
|
/> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup> |
||||||
|
import EmptyState from "../../components/EmptyState.vue"; |
||||||
|
import BaseButton from "../../components/basecomponents/BaseButton.vue"; |
||||||
|
import ButtonToolbar from "../../components/basecomponents/ButtonToolbar.vue"; |
||||||
|
import {computed, onMounted, ref, watch} from "vue"; |
||||||
|
import { useStore } from "vuex"; |
||||||
|
import { useRoute, useRouter } from "vue-router"; |
||||||
|
import { useI18n } from "vue-i18n"; |
||||||
|
import axios from "axios"; |
||||||
|
import { ENTRYPOINT } from "../../config/entrypoint"; |
||||||
|
import {RESOURCE_LINK_PUBLISHED} from "../../components/resource_links/visibility"; |
||||||
|
|
||||||
|
const store = useStore(); |
||||||
|
const route = useRoute(); |
||||||
|
const router = useRouter(); |
||||||
|
|
||||||
|
const { t } = useI18n(); |
||||||
|
|
||||||
|
const searchTerm = ref(''); |
||||||
|
const parentResourceNodeId = ref(Number(route.params.node)); |
||||||
|
|
||||||
|
const resourceLinkList = ref( |
||||||
|
JSON.stringify([ |
||||||
|
{ |
||||||
|
sid: route.query.sid, |
||||||
|
cid: route.query.cid, |
||||||
|
visibility: RESOURCE_LINK_PUBLISHED, // visible by default |
||||||
|
}, |
||||||
|
]) |
||||||
|
); |
||||||
|
|
||||||
|
const isAuthenticated = computed(() => store.getters["security/isAuthenticated"]); |
||||||
|
const isCurrentTeacher = computed(() => store.getters["security/isCurrentTeacher"]); |
||||||
|
|
||||||
|
const glossaries = ref([]); |
||||||
|
const view = ref('list'); |
||||||
|
|
||||||
|
function addNewTerm() { |
||||||
|
router.push({ |
||||||
|
name: "CreateTerm", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function editTerm(termId) { |
||||||
|
|
||||||
|
console.log('termId ', termId); |
||||||
|
|
||||||
|
router.push({ |
||||||
|
name: 'UpdateTerm', |
||||||
|
params: { id: termId}, |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function deleteTerm(termId) { |
||||||
|
if (confirm('¿Delete?')) { |
||||||
|
axios |
||||||
|
.delete(`${ENTRYPOINT}glossaries/${termId}`) |
||||||
|
.then(response => { |
||||||
|
console.log('Term deleted:', response.data); |
||||||
|
fetchGlossaries(); |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
console.error('Error deleting term:', error); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function importGlossary() { |
||||||
|
router.push({ |
||||||
|
name: "ImportGlossary", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function exportGlossary() { |
||||||
|
router.push({ |
||||||
|
name: "ExportGlossary", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function changeView(newView) { |
||||||
|
// Handle changing the view (e.g., table view) |
||||||
|
view.value = newView === 'table' ? 'list' : 'table'; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function exportToDocuments() { |
||||||
|
|
||||||
|
const postData = { |
||||||
|
parentResourceNodeId: parentResourceNodeId.value, |
||||||
|
resourceLinkList: resourceLinkList.value, |
||||||
|
}; |
||||||
|
|
||||||
|
const endpoint = `${ENTRYPOINT}glossaries/export_to_documents`; |
||||||
|
|
||||||
|
axios.post(endpoint, postData) |
||||||
|
.then(response => { |
||||||
|
console.log(response.data); |
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
console.error(error); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function fetchGlossaries() { |
||||||
|
const params = { |
||||||
|
'resourceNode.parent': route.query.parent || null, |
||||||
|
'cid': route.query.cid || null, |
||||||
|
'sid': route.query.sid || null, |
||||||
|
'q': searchTerm.value |
||||||
|
}; |
||||||
|
|
||||||
|
axios |
||||||
|
.get(ENTRYPOINT + 'glossaries', { params }) |
||||||
|
.then(response => { |
||||||
|
|
||||||
|
console.log('responsedata:', response.data); |
||||||
|
glossaries.value = response.data; |
||||||
|
|
||||||
|
console.log('en fetch glossaries.value', glossaries.value); |
||||||
|
|
||||||
|
}) |
||||||
|
.catch(error => { |
||||||
|
console.error('Error fetching links:', error); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
watch(searchTerm, () => { |
||||||
|
fetchGlossaries(); |
||||||
|
}); |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
fetchGlossaries(); |
||||||
|
}); |
||||||
|
|
||||||
|
</script> |
@ -0,0 +1,35 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<div class="mb-4"> |
||||||
|
<button class="btn btn--secondary" @click="goBack">Back</button> |
||||||
|
</div> |
||||||
|
<h1 class="text-h3 font-small text-gray-800 mb-4">Import glossary<hr /></h1> |
||||||
|
<GlossaryImportForm /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import GlossaryImportForm from "../../components/glossary/GlossaryImportForm.vue"; |
||||||
|
import {useRoute, useRouter} from "vue-router"; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
GlossaryImportForm, |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
const router = useRouter(); |
||||||
|
const route = useRoute(); |
||||||
|
|
||||||
|
const goBack = () => { |
||||||
|
router.push({ |
||||||
|
name: "GlossaryList", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
goBack, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
@ -0,0 +1,40 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<div class="mb-4"> |
||||||
|
<button class="btn btn--secondary" @click="goBack">Back</button> |
||||||
|
</div> |
||||||
|
<h1>Edit term <hr /></h1> |
||||||
|
<GlossaryForm :termId="termId" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import GlossaryForm from "../../components/glossary/GlossaryForm.vue"; |
||||||
|
import {useRoute, useRouter} from "vue-router"; |
||||||
|
|
||||||
|
export default { |
||||||
|
components: { |
||||||
|
GlossaryForm, |
||||||
|
}, |
||||||
|
setup() { |
||||||
|
const router = useRouter(); |
||||||
|
const route = useRoute(); |
||||||
|
|
||||||
|
const goBack = () => { |
||||||
|
router.push({ |
||||||
|
name: "GlossaryList", |
||||||
|
query: route.query, |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
goBack, |
||||||
|
}; |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
termId() { |
||||||
|
return this.$route.params.id; |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
@ -0,0 +1,64 @@ |
|||||||
|
<?php |
||||||
|
/* For licensing terms, see /license.txt */ |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Controller\Api; |
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Entity\Course; |
||||||
|
use Chamilo\CoreBundle\Entity\Session; |
||||||
|
use Chamilo\CourseBundle\Entity\CGlossary; |
||||||
|
use Chamilo\CourseBundle\Repository\CGlossaryRepository; |
||||||
|
use DateTime; |
||||||
|
use Doctrine\ORM\EntityManager; |
||||||
|
use Exception; |
||||||
|
use Symfony\Component\HttpFoundation\Request; |
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
||||||
|
|
||||||
|
class CreateCGlossaryAction extends BaseResourceFileAction |
||||||
|
{ |
||||||
|
public function __invoke(Request $request, CGlossaryRepository $repo, EntityManager $em): CGlossary |
||||||
|
{ |
||||||
|
$data = json_decode($request->getContent(), true); |
||||||
|
$title = $data['name']; |
||||||
|
$description = $data['description']; |
||||||
|
$parentResourceNodeId = $data['parentResourceNodeId']; |
||||||
|
$resourceLinkList = json_decode($data['resourceLinkList'], true); |
||||||
|
$sid = (int) $data['sid']; |
||||||
|
$cid = (int) $data['cid']; |
||||||
|
|
||||||
|
$course = null; |
||||||
|
$session = null; |
||||||
|
if (0 !== $cid) { |
||||||
|
$course = $em->getRepository(Course::class)->find($cid); |
||||||
|
} |
||||||
|
if (0 !== $sid) { |
||||||
|
$session = $em->getRepository(Session::class)->find($sid); |
||||||
|
} |
||||||
|
|
||||||
|
// Check if the term already exists |
||||||
|
$qb = $repo->getResourcesByCourse($course, $session) |
||||||
|
->andWhere('resource.name = :name') |
||||||
|
->setParameter('name', $title); |
||||||
|
$existingGlossaryTerm = $qb->getQuery()->getOneOrNullResult(); |
||||||
|
if ($existingGlossaryTerm !== null) { |
||||||
|
throw new BadRequestHttpException('The glossary term already exists.'); |
||||||
|
} |
||||||
|
|
||||||
|
$glossary = (new CGlossary()) |
||||||
|
->setName($title) |
||||||
|
->setDescription($description) |
||||||
|
; |
||||||
|
|
||||||
|
|
||||||
|
if (!empty($parentResourceNodeId)) { |
||||||
|
$glossary->setParentResourceNode($parentResourceNodeId); |
||||||
|
} |
||||||
|
|
||||||
|
if (!empty($resourceLinkList)) { |
||||||
|
$glossary->setResourceLinkArray($resourceLinkList); |
||||||
|
} |
||||||
|
|
||||||
|
return $glossary; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,131 @@ |
|||||||
|
<?php |
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Controller\Api; |
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Entity\Course; |
||||||
|
use Chamilo\CoreBundle\Entity\Session; |
||||||
|
use Chamilo\CourseBundle\Entity\CGlossary; |
||||||
|
use Chamilo\CourseBundle\Repository\CGlossaryRepository; |
||||||
|
use Doctrine\ORM\EntityManager; |
||||||
|
use Doctrine\ORM\Exception\NotSupported; |
||||||
|
use Mpdf\Mpdf; |
||||||
|
use PhpOffice\PhpSpreadsheet\IOFactory; |
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet; |
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Tcpdf; |
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile; |
||||||
|
use Symfony\Component\HttpFoundation\Request; |
||||||
|
use Symfony\Component\HttpFoundation\Response; |
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
||||||
|
use Symfony\Component\HttpFoundation\File\File; |
||||||
|
use Symfony\Component\HttpKernel\KernelInterface; |
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface; |
||||||
|
|
||||||
|
class ExportCGlossaryAction |
||||||
|
{ |
||||||
|
|
||||||
|
public function __invoke(Request $request, CGlossaryRepository $repo, EntityManager $em, KernelInterface $kernel, TranslatorInterface $translator): Response |
||||||
|
{ |
||||||
|
$format = $request->get('format'); |
||||||
|
$cid = $request->request->get('cid'); |
||||||
|
$sid = $request->request->get('sid'); |
||||||
|
|
||||||
|
if (!in_array($format, ['csv', 'xls', 'pdf'])) { |
||||||
|
throw new BadRequestHttpException('Invalid export format'); |
||||||
|
} |
||||||
|
|
||||||
|
$exportPath = $kernel->getCacheDir(); |
||||||
|
$course = null; |
||||||
|
$session = null; |
||||||
|
if (0 !== $cid) { |
||||||
|
$course = $em->getRepository(Course::class)->find($cid); |
||||||
|
} |
||||||
|
if (0 !== $sid) { |
||||||
|
$session = $em->getRepository(Session::class)->find($sid); |
||||||
|
} |
||||||
|
|
||||||
|
$qb = $repo->getResourcesByCourse($course, $session); |
||||||
|
$glossaryItems = $qb->getQuery()->getResult(); |
||||||
|
|
||||||
|
$exportFilePath = $this->generateExportFile($glossaryItems, $format, $exportPath, $translator); |
||||||
|
|
||||||
|
$file = new File($exportFilePath); |
||||||
|
$response = new Response($file->getContent()); |
||||||
|
$response->headers->set('Content-Type', $file->getMimeType()); |
||||||
|
$response->headers->set('Content-Disposition', 'attachment; filename="glossary.'.$format.'"'); |
||||||
|
|
||||||
|
unlink($exportFilePath); |
||||||
|
|
||||||
|
return $response; |
||||||
|
} |
||||||
|
|
||||||
|
private function generateExportFile(array $glossaryItems, string $format, string $exportPath, TranslatorInterface $translator): string |
||||||
|
{ |
||||||
|
switch ($format) { |
||||||
|
case 'csv': |
||||||
|
return $this->generateCsvFile($glossaryItems, $exportPath); |
||||||
|
case 'xls': |
||||||
|
return $this->generateExcelFile($glossaryItems, $exportPath); |
||||||
|
case 'pdf': |
||||||
|
return $this->generatePdfFile($glossaryItems, $exportPath, $translator); |
||||||
|
default: |
||||||
|
throw new NotSupported('Export format not supported'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private function generateCsvFile(array $glossaryItems, string $exportPath): string |
||||||
|
{ |
||||||
|
$csvFilePath = $exportPath.'/glossary.csv'; |
||||||
|
$csvContent = ''; |
||||||
|
/* @var CGlossary $item */ |
||||||
|
foreach ($glossaryItems as $item) { |
||||||
|
$csvContent .= $item->getName() . ',' . $item->getDescription() . "\n"; |
||||||
|
} |
||||||
|
file_put_contents($csvFilePath, $csvContent); |
||||||
|
|
||||||
|
return $csvFilePath; |
||||||
|
} |
||||||
|
|
||||||
|
private function generateExcelFile(array $glossaryItems, string $exportPath): string |
||||||
|
{ |
||||||
|
$excelFilePath = $exportPath.'/glossary.xlsx'; |
||||||
|
$spreadsheet = new Spreadsheet(); |
||||||
|
$sheet = $spreadsheet->getActiveSheet(); |
||||||
|
/* @var CGlossary $item */ |
||||||
|
foreach ($glossaryItems as $index => $item) { |
||||||
|
$row = $index + 1; |
||||||
|
$sheet->setCellValue('A' . $row, $item->getName()); |
||||||
|
$sheet->setCellValue('B' . $row, $item->getDescription()); |
||||||
|
} |
||||||
|
|
||||||
|
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); |
||||||
|
$writer->save($excelFilePath); |
||||||
|
|
||||||
|
return $excelFilePath; |
||||||
|
} |
||||||
|
|
||||||
|
private function generatePdfFile(array $glossaryItems, string $exportPath, TranslatorInterface $translator): string |
||||||
|
{ |
||||||
|
$pdfFilePath = $exportPath . '/glossary.pdf'; |
||||||
|
|
||||||
|
$mpdf = new Mpdf(); |
||||||
|
|
||||||
|
$html = '<h1>'.$translator->trans('Glossary').'</h1>'; |
||||||
|
$html .= '<table>'; |
||||||
|
$html .= '<tr><th>'.$translator->trans('Term').'</th><th>'.$translator->trans('Definition').'</th></tr>'; |
||||||
|
/* @var CGlossary $item */ |
||||||
|
foreach ($glossaryItems as $item) { |
||||||
|
$html .= '<tr>'; |
||||||
|
$html .= '<td>' . $item->getName(). '</td>'; |
||||||
|
$html .= '<td>' . $item->getDescription() . '</td>'; |
||||||
|
$html .= '</tr>'; |
||||||
|
} |
||||||
|
$html .= '</table>'; |
||||||
|
|
||||||
|
$mpdf->WriteHTML($html); |
||||||
|
|
||||||
|
$mpdf->Output($pdfFilePath, 'F'); |
||||||
|
|
||||||
|
return $pdfFilePath; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
<?php |
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Controller\Api; |
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Entity\Course; |
||||||
|
use Chamilo\CoreBundle\Entity\Session; |
||||||
|
use Chamilo\CourseBundle\Entity\CDocument; |
||||||
|
use Chamilo\CourseBundle\Entity\CGlossary; |
||||||
|
use Chamilo\CourseBundle\Repository\CGlossaryRepository; |
||||||
|
use Doctrine\ORM\EntityManager; |
||||||
|
use Doctrine\ORM\Exception\NotSupported; |
||||||
|
use Mpdf\Mpdf; |
||||||
|
use PhpOffice\PhpSpreadsheet\IOFactory; |
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet; |
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Tcpdf; |
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile; |
||||||
|
use Symfony\Component\HttpFoundation\Request; |
||||||
|
use Symfony\Component\HttpFoundation\Response; |
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
||||||
|
use Symfony\Component\HttpFoundation\File\File; |
||||||
|
use Symfony\Component\HttpKernel\KernelInterface; |
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface; |
||||||
|
|
||||||
|
class ExportGlossaryToDocumentsAction |
||||||
|
{ |
||||||
|
|
||||||
|
public function __invoke(Request $request, CGlossaryRepository $repo, EntityManager $em, KernelInterface $kernel, TranslatorInterface $translator): String |
||||||
|
{ |
||||||
|
|
||||||
|
$data = json_decode($request->getContent(), true); |
||||||
|
$parentResourceNodeId = $data['parentResourceNodeId']; |
||||||
|
$resourceLinkList = json_decode($data['resourceLinkList'], true); |
||||||
|
|
||||||
|
$exportPath = $kernel->getCacheDir(); |
||||||
|
$glossaryItems = $repo->findAll(); |
||||||
|
|
||||||
|
$pdfFilePath = $this->generatePdfFile($glossaryItems, $exportPath, $translator); |
||||||
|
|
||||||
|
if ($pdfFilePath) { |
||||||
|
$fileName = basename($pdfFilePath); |
||||||
|
$uploadFile = new UploadedFile( |
||||||
|
$pdfFilePath, |
||||||
|
$fileName |
||||||
|
); |
||||||
|
|
||||||
|
$document = new CDocument(); |
||||||
|
$document->setTitle($fileName); |
||||||
|
$document->setUploadFile($uploadFile); |
||||||
|
$document->setFiletype('file'); |
||||||
|
|
||||||
|
if (!empty($parentResourceNodeId)) { |
||||||
|
$document->setParentResourceNode($parentResourceNodeId); |
||||||
|
} |
||||||
|
|
||||||
|
if (!empty($resourceLinkList)) { |
||||||
|
$document->setResourceLinkArray($resourceLinkList); |
||||||
|
} |
||||||
|
|
||||||
|
// Save the CDocument entity to the database |
||||||
|
$em->persist($document); |
||||||
|
$em->flush(); |
||||||
|
|
||||||
|
unlink($pdfFilePath); |
||||||
|
} |
||||||
|
|
||||||
|
return $pdfFilePath; |
||||||
|
} |
||||||
|
|
||||||
|
private function generatePdfFile(array $glossaryItems, string $exportPath, TranslatorInterface $translator): string |
||||||
|
{ |
||||||
|
|
||||||
|
$date = date('Y-m-d'); |
||||||
|
$pdfFileName = 'glossary_' . $date . '.pdf'; |
||||||
|
$pdfFilePath = $exportPath . '/' . $pdfFileName; |
||||||
|
|
||||||
|
$mpdf = new Mpdf(); |
||||||
|
|
||||||
|
$html = '<h1>'.$translator->trans('Glossary').'</h1>'; |
||||||
|
$html .= '<table>'; |
||||||
|
$html .= '<tr><th>'.$translator->trans('Term').'</th><th>'.$translator->trans('Definition').'</th></tr>'; |
||||||
|
/* @var CGlossary $item */ |
||||||
|
foreach ($glossaryItems as $item) { |
||||||
|
$html .= '<tr>'; |
||||||
|
$html .= '<td>' . $item->getName(). '</td>'; |
||||||
|
$html .= '<td>' . $item->getDescription() . '</td>'; |
||||||
|
$html .= '</tr>'; |
||||||
|
} |
||||||
|
$html .= '</table>'; |
||||||
|
|
||||||
|
$mpdf->WriteHTML($html); |
||||||
|
|
||||||
|
$mpdf->Output($pdfFilePath, 'F'); |
||||||
|
|
||||||
|
return $pdfFilePath; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,58 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* For licensing terms, see /license.txt */ |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Controller\Api; |
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Entity\Course; |
||||||
|
use Chamilo\CoreBundle\Entity\Session; |
||||||
|
use Chamilo\CourseBundle\Entity\CGlossary; |
||||||
|
use Chamilo\CourseBundle\Repository\CGlossaryRepository; |
||||||
|
use Doctrine\ORM\EntityManager; |
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse; |
||||||
|
use Symfony\Component\HttpFoundation\Request; |
||||||
|
use Symfony\Component\HttpFoundation\Response; |
||||||
|
|
||||||
|
class GetGlossaryCollectionController extends BaseResourceFileAction |
||||||
|
{ |
||||||
|
public function __invoke(Request $request, CGlossaryRepository $repo, EntityManager $em): Response |
||||||
|
{ |
||||||
|
$cid = $request->query->getInt('cid'); |
||||||
|
$sid = $request->query->getInt('sid'); |
||||||
|
$q = $request->query->get('q'); |
||||||
|
$course = null; |
||||||
|
$session = null; |
||||||
|
if ($cid) { |
||||||
|
$course = $em->getRepository(Course::class)->find($cid); |
||||||
|
} |
||||||
|
|
||||||
|
if ($sid) { |
||||||
|
$session = $em->getRepository(Session::class)->find($sid); |
||||||
|
} |
||||||
|
|
||||||
|
$qb = $repo->getResourcesByCourse($course, $session); |
||||||
|
if ($q) { |
||||||
|
$qb->andWhere($qb->expr()->like('resource.name', ':name')) |
||||||
|
->setParameter('name', '%' . $q . '%'); |
||||||
|
} |
||||||
|
$glossaries = $qb->getQuery()->getResult(); |
||||||
|
|
||||||
|
$dataResponse = []; |
||||||
|
if ($glossaries) { |
||||||
|
/* @var CGlossary $item */ |
||||||
|
foreach ($glossaries as $item) { |
||||||
|
$dataResponse[] = |
||||||
|
[ |
||||||
|
'iid' => $item->getIid(), |
||||||
|
'id' => $item->getIid(), |
||||||
|
'name' => $item->getName(), |
||||||
|
'description' => $item->getDescription(), |
||||||
|
]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return new JsonResponse($dataResponse); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,135 @@ |
|||||||
|
<?php |
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Controller\Api; |
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Entity\Course; |
||||||
|
use Chamilo\CoreBundle\Entity\Session; |
||||||
|
use Chamilo\CourseBundle\Entity\CGlossary; |
||||||
|
use Chamilo\CourseBundle\Repository\CGlossaryRepository; |
||||||
|
use Doctrine\ORM\EntityManager; |
||||||
|
use Doctrine\ORM\Exception\NotSupported; |
||||||
|
use PhpOffice\PhpSpreadsheet\IOFactory; |
||||||
|
use Symfony\Component\HttpFoundation\File\UploadedFile; |
||||||
|
use Symfony\Component\HttpFoundation\Request; |
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
||||||
|
use Symfony\Component\HttpFoundation\Response; |
||||||
|
|
||||||
|
|
||||||
|
class ImportCGlossaryAction |
||||||
|
{ |
||||||
|
|
||||||
|
public function __invoke(Request $request, CGlossaryRepository $repo, EntityManager $em): Response |
||||||
|
{ |
||||||
|
$file = $request->files->get('file'); |
||||||
|
$fileType = $request->request->get('file_type'); |
||||||
|
$replace = $request->request->get('replace'); |
||||||
|
$update = $request->request->get('update'); |
||||||
|
$cid = $request->request->get('cid'); |
||||||
|
$sid = $request->request->get('sid'); |
||||||
|
|
||||||
|
$course = null; |
||||||
|
$session = null; |
||||||
|
if (0 !== $cid) { |
||||||
|
$course = $em->getRepository(Course::class)->find($cid); |
||||||
|
} |
||||||
|
if (0 !== $sid) { |
||||||
|
$session = $em->getRepository(Session::class)->find($sid); |
||||||
|
} |
||||||
|
|
||||||
|
if (!$file instanceof UploadedFile || !$file->isValid()) { |
||||||
|
throw new BadRequestHttpException('Invalid file'); |
||||||
|
} |
||||||
|
|
||||||
|
$data = []; |
||||||
|
if ($fileType === 'csv') { |
||||||
|
if (($handle = fopen($file->getPathname(), 'r')) !== false) { |
||||||
|
$header = fgetcsv($handle, 0, ';'); |
||||||
|
while (($row = fgetcsv($handle, 0, ';')) !== false) { |
||||||
|
$term = isset($row[0]) ? trim($row[0]) : ''; |
||||||
|
$definition = isset($row[1]) ? trim($row[1]) : ''; |
||||||
|
$data[$term] = $definition; |
||||||
|
} |
||||||
|
fclose($handle); |
||||||
|
} |
||||||
|
} elseif ($fileType === 'xls') { |
||||||
|
$spreadsheet = IOFactory::load($file->getPathname()); |
||||||
|
$sheet = $spreadsheet->getActiveSheet(); |
||||||
|
$firstRow = true; |
||||||
|
foreach ($sheet->getRowIterator() as $row) { |
||||||
|
if ($firstRow) { |
||||||
|
$firstRow = false; |
||||||
|
continue; |
||||||
|
} |
||||||
|
$cellIterator = $row->getCellIterator(); |
||||||
|
$cellIterator->setIterateOnlyExistingCells(false); |
||||||
|
$rowData = []; |
||||||
|
foreach ($cellIterator as $cell) { |
||||||
|
$rowData[] = $cell->getValue(); |
||||||
|
} |
||||||
|
$term = isset($rowData[0]) ? utf8_decode(trim($rowData[0])) : ''; |
||||||
|
$definition = isset($rowData[1]) ? utf8_decode(trim($rowData[1])) : ''; |
||||||
|
$data[$term] = $definition; |
||||||
|
} |
||||||
|
} else { |
||||||
|
throw new BadRequestHttpException('Invalid file type'); |
||||||
|
} |
||||||
|
|
||||||
|
if (empty($data)) { |
||||||
|
throw new BadRequestHttpException('Invalid data'); |
||||||
|
} |
||||||
|
|
||||||
|
if ('true' === $replace) { |
||||||
|
$qb = $repo->getResourcesByCourse($course, $session); |
||||||
|
$allGlossaries = $qb->getQuery()->getResult(); |
||||||
|
if ($allGlossaries) { |
||||||
|
/* @var CGlossary $item */ |
||||||
|
foreach ($allGlossaries as $item) { |
||||||
|
$termToDelete = $repo->find($item->getIid()); |
||||||
|
if (null !== $termToDelete) { |
||||||
|
$repo->delete($termToDelete); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if ('true' === $update) { |
||||||
|
foreach ($data as $termToUpdate => $descriptionToUpdate) { |
||||||
|
// Check if the term already exists |
||||||
|
$qb = $repo->getResourcesByCourse($course, $session) |
||||||
|
->andWhere('resource.name = :name') |
||||||
|
->setParameter('name', $termToUpdate); |
||||||
|
/* @var CGlossary $existingGlossaryTerm */ |
||||||
|
$existingGlossaryTerm = $qb->getQuery()->getOneOrNullResult(); |
||||||
|
if ($existingGlossaryTerm !== null) { |
||||||
|
$existingGlossaryTerm->setDescription($descriptionToUpdate); |
||||||
|
$repo->update($existingGlossaryTerm); |
||||||
|
unset($data[$termToUpdate]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
foreach ($data as $term => $description) { |
||||||
|
$qb = $repo->getResourcesByCourse($course, $session) |
||||||
|
->andWhere('resource.name = :name') |
||||||
|
->setParameter('name', $term); |
||||||
|
/* @var CGlossary $existingNewGlossaryTerm */ |
||||||
|
$existingNewGlossaryTerm = $qb->getQuery()->getOneOrNullResult(); |
||||||
|
if (!$existingNewGlossaryTerm) { |
||||||
|
$newGlossary = (new CGlossary()) |
||||||
|
->setName($term) |
||||||
|
->setDescription($description) |
||||||
|
->setParent($course) |
||||||
|
->addCourseLink($course, $session) |
||||||
|
; |
||||||
|
$repo->create($newGlossary); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$response = new Response(json_encode($data), Response::HTTP_OK, ['Content-Type' => 'application/json']); |
||||||
|
|
||||||
|
return $response; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
/* For licensing terms, see /license.txt */ |
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Controller\Api; |
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Entity\Course; |
||||||
|
use Chamilo\CoreBundle\Entity\Session; |
||||||
|
use Chamilo\CourseBundle\Entity\CGlossary; |
||||||
|
use Chamilo\CourseBundle\Repository\CGlossaryRepository; |
||||||
|
use DateTime; |
||||||
|
use Doctrine\ORM\EntityManager; |
||||||
|
use Exception; |
||||||
|
use Symfony\Component\HttpFoundation\Request; |
||||||
|
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
||||||
|
|
||||||
|
class UpdateCGlossaryAction extends BaseResourceFileAction |
||||||
|
{ |
||||||
|
public function __invoke(CGlossary $glossary, Request $request, CGlossaryRepository $repo, EntityManager $em): CGlossary |
||||||
|
{ |
||||||
|
$data = json_decode($request->getContent(), true); |
||||||
|
$title = $data['name']; |
||||||
|
$description = $data['description']; |
||||||
|
$parentResourceNodeId = $data['parentResourceNodeId']; |
||||||
|
$resourceLinkList = json_decode($data['resourceLinkList'], true); |
||||||
|
$sid = (int) $data['sid']; |
||||||
|
$cid = (int) $data['cid']; |
||||||
|
|
||||||
|
$course = null; |
||||||
|
$session = null; |
||||||
|
if (0 !== $cid) { |
||||||
|
$course = $em->getRepository(Course::class)->find($cid); |
||||||
|
} |
||||||
|
if (0 !== $sid) { |
||||||
|
$session = $em->getRepository(Session::class)->find($sid); |
||||||
|
} |
||||||
|
|
||||||
|
// Check if the term already exists |
||||||
|
$qb = $repo->getResourcesByCourse($course, $session) |
||||||
|
->andWhere('resource.name = :name') |
||||||
|
->setParameter('name', $title); |
||||||
|
$existingGlossaryTerm = $qb->getQuery()->getOneOrNullResult(); |
||||||
|
if ($existingGlossaryTerm !== null && $existingGlossaryTerm->getIid() !== $glossary->getIid()) { |
||||||
|
throw new BadRequestHttpException('The glossary term already exists.'); |
||||||
|
} |
||||||
|
|
||||||
|
$glossary->setName($title); |
||||||
|
$glossary->setDescription($description); |
||||||
|
|
||||||
|
if (!empty($parentResourceNodeId)) { |
||||||
|
$glossary->setParentResourceNode($parentResourceNodeId); |
||||||
|
} |
||||||
|
|
||||||
|
if (!empty($resourceLinkList)) { |
||||||
|
$glossary->setResourceLinkArray($resourceLinkList); |
||||||
|
} |
||||||
|
|
||||||
|
return $glossary; |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue