Merge pull request #4783 from christianbeeznest/GH-4768
Glossary: Change from legacy to Vue interface and backend adaptation - refs #4768pull/4788/head
commit
b5437404e0
@ -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