From 2eb2c36d5bc90eea001a5d0a4e36d529822a19b9 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Tue, 26 May 2026 17:06:39 +0200 Subject: [PATCH] replace + in email helpers with spaces; configs for title edit, multi edit table & confetti on endupload; empty student projektarbeiten check; only show dropdown labels on large displays; new phrasen & phrasen fixes; --- application/config/abgabe.php | 8 ++ .../controllers/api/frontend/v1/Abgabe.php | 36 ++++++--- application/views/Cis/Abgabetool.php | 2 +- public/css/components/abgabetool/abgabe.css | 49 ++++++++++++ .../Cis/Abgabetool/AbgabeStudentDetail.js | 76 +++++++++++++++++-- .../Cis/Abgabetool/AbgabetoolAssistenz.js | 10 ++- .../Cis/Abgabetool/AbgabetoolStudent.js | 14 +++- public/js/helpers/EmailHelpers.js | 6 +- system/phrasesupdate.php | 46 ++++++++++- 9 files changed, 218 insertions(+), 29 deletions(-) diff --git a/application/config/abgabe.php b/application/config/abgabe.php index 90aedbd8b..3427046cd 100644 --- a/application/config/abgabe.php +++ b/application/config/abgabe.php @@ -43,3 +43,11 @@ $config['ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT'] = true; $config['ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER'] = true; $config['BETREUER_SAMMELMAIL_BUTTON_STUDENT'] = true; + +$config['MULTIEDIT_TABLE'] = true; + +$config['STUDENT_EDIT_PROJEKTARBEIT_TITLE'] = true; + +$config['CONFETTI_ON_ENDUPLOAD'] = true; + + diff --git a/application/controllers/api/frontend/v1/Abgabe.php b/application/controllers/api/frontend/v1/Abgabe.php index d6f3c29a8..5ba0ef172 100644 --- a/application/controllers/api/frontend/v1/Abgabe.php +++ b/application/controllers/api/frontend/v1/Abgabe.php @@ -93,6 +93,7 @@ class Abgabe extends FHCAPI_Controller $ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT = $this->config->item('ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT'); $ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER = $this->config->item('ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER'); $BETREUER_SAMMELMAIL_BUTTON_STUDENT = $this->config->item('BETREUER_SAMMELMAIL_BUTTON_STUDENT'); + $MULTIEDIT_TABLE = $this->config->item('MULTIEDIT_TABLE'); $ret = array( 'old_abgabe_beurteilung_link' => $old_abgabe_beurteilung_link, @@ -100,7 +101,7 @@ class Abgabe extends FHCAPI_Controller 'abgabetypenBetreuer' => $abgabetypenBetreuer, 'ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT' => $ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT, 'ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER' => $ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER, - 'BETREUER_SAMMELMAIL_BUTTON_STUDENT' => $BETREUER_SAMMELMAIL_BUTTON_STUDENT, + 'MULTIEDIT_TABLE' => $MULTIEDIT_TABLE, ); $this->terminateWithSuccess($ret); @@ -110,10 +111,14 @@ class Abgabe extends FHCAPI_Controller * loads config related to abgabetool for students to avoid handing out links reserved for employees */ public function getConfigStudent() { - $moodle_link =$this->config->item('STG_MOODLE_LINK'); + $moodle_link = $this->config->item('STG_MOODLE_LINK'); + $title_edit_allowed = $this->config->item('STUDENT_EDIT_PROJEKTARBEIT_TITLE'); + $confetti_on_endupload = $this->config->item('CONFETTI_ON_ENDUPLOAD'); $ret = array( 'moodle_link' => $moodle_link, + 'title_edit_allowed' => $title_edit_allowed, + 'confetti_on_endupload' => $confetti_on_endupload ); $this->terminateWithSuccess($ret); @@ -190,8 +195,8 @@ class Abgabe extends FHCAPI_Controller } else { $result = $this->ProjektarbeitModel->getStudentProjektarbeitenWithBetreuer(getAuthUID()); } - - $projektarbeiten = getData($result); + + $projektarbeiten = hasData($result) ? getData($result) : array(); if(count($projektarbeiten)) { foreach($projektarbeiten as $pa) { @@ -459,6 +464,10 @@ class Abgabe extends FHCAPI_Controller */ public function postStudentProjektarbeitTitel() { + if(!$this->config->item('STUDENT_EDIT_PROJEKTARBEIT_TITLE')) { + $this->terminateWithError($this->p->t('global', 'c4studentEditNotAllowed'), 'general'); + }; + $projektarbeit_id = $this->input->post('projektarbeit_id'); $titel = $this->input->post('titel'); @@ -467,6 +476,13 @@ class Abgabe extends FHCAPI_Controller $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); } + // strip all HTML tags to prevent XSS in mail bodies, table views and Projektarbeitsbenotung + $titel = trim(strip_tags($titel)); + if ($titel === '') { + $this->terminateWithError($this->p->t('global', 'wrongParameters'), 'general'); + } + + $this->checkProjektarbeitForFinishedStatus($projektarbeit_id); $this->load->model('education/Projektarbeit_model', 'ProjektarbeitModel'); @@ -484,6 +500,8 @@ class Abgabe extends FHCAPI_Controller $this->terminateWithError($this->p->t('abgabetool', 'c4noZuordnungBetreuerStudent'), 'general'); } + + $result = $this->ProjektarbeitModel->load($projektarbeit_id); $data = getData($result); @@ -492,7 +510,7 @@ class Abgabe extends FHCAPI_Controller $result = $this->ProjektarbeitModel->update( $projektarbeit_id, array( - 'titel' => trim($titel), + 'titel' => $titel, 'updatevon' => getAuthUID(), 'updateamum' => date('Y-m-d H:i:s') ) @@ -504,7 +522,7 @@ class Abgabe extends FHCAPI_Controller 'titelUpdate', array( 'projektarbeit_id' => $projektarbeit_id, - 'titel' => trim($titel), + 'titel' => $titel, 'updatevon' => getAuthUID(), 'updateamum' => date('Y-m-d H:i:s') ), @@ -514,7 +532,7 @@ class Abgabe extends FHCAPI_Controller $this->sendTitelChangedEmail( $projektarbeit_id, - trim($titel), + $titel, $oldTitle, $assignedStudentUid ); @@ -1533,7 +1551,7 @@ class Abgabe extends FHCAPI_Controller }; Events::trigger('projektarbeit_is_current', $projektarbeit_id, $returnFunc); if(!$projektarbeitIsCurrent) { - $this->terminateWithError($this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeit'), 'general'); + $this->terminateWithError($this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeitv2'), 'general'); } // Link to Abgabetool @@ -1739,7 +1757,7 @@ class Abgabe extends FHCAPI_Controller $data = getData($res)[0]; if($data->note !== NULL) { // hardcode this error msg cause phrasen arent reliable and people keep bugging why the cant edit old entries they definitely shouldnt update - $message = $this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeit'); + $message = $this->p->t('abgabetool','c4fehlerAktualitaetProjektarbeitv2'); if(strpos($message, "<<") === 0) { // phrase could not be loaded $this->terminateWithError('Die Projektarbeit wurde bereits benotet, Sie dürfen deshalb keine weiteren Termine anlegen oder bearbeiten.', 'general'); } else { diff --git a/application/views/Cis/Abgabetool.php b/application/views/Cis/Abgabetool.php index a0621b1f9..469bc8110 100644 --- a/application/views/Cis/Abgabetool.php +++ b/application/views/Cis/Abgabetool.php @@ -38,7 +38,7 @@ $includesArray = array( $this->load->view('templates/FHC-Header', $includesArray); ?> -
+
uid= student_uid_prop="" stg_kz_prop="" diff --git a/public/css/components/abgabetool/abgabe.css b/public/css/components/abgabetool/abgabe.css index 12e0e82ee..78a04e114 100644 --- a/public/css/components/abgabetool/abgabe.css +++ b/public/css/components/abgabetool/abgabe.css @@ -347,3 +347,52 @@ } +/*confetti celebration on endupload - impossible to miss*/ +#confetti-container { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 9999; + overflow: hidden; +} + +.confetti-piece { + position: absolute; + opacity: 0; + will-change: top, transform, opacity; +} + +/* Background Rain */ +@keyframes fallAndSpin { + 0% { + top: var(--start-y); + transform: translate3d(0, 0, 0) rotateX(0deg) rotateY(0deg); + opacity: 1; + } + 100% { + top: 105vh; + transform: translate3d(var(--drift), 0, 0) rotateX(720deg) rotateY(360deg); + opacity: 0.3; + } +} + +/* Corner Cannons*/ +@keyframes cannonBlast { + 0% { + transform: translate3d(0, 0, 0) scale(0.3) rotate(0deg); + opacity: 1; + animation-timing-function: cubic-bezier(0.1, 0.8, 0.2, 1); + } + 30% { + transform: translate3d(var(--blast-x), var(--blast-y), 0) scale(1.2) rotate(270deg); + opacity: 1; + animation-timing-function: linear; + } + 100% { + transform: translate3d(calc(var(--blast-x) * 1.4), 15vh, 0) scale(0.4) rotate(630deg); + opacity: 0; + } +} diff --git a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js index c9d5280cb..01673539f 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js @@ -21,7 +21,7 @@ export const AbgabeStudentDetail = { VueDatePicker, FhcOverlay }, - inject: ['notenOptions', 'isMobile', 'isViewMode', 'moodle_link'], + inject: ['notenOptions', 'isMobile', 'isViewMode', 'moodle_link', 'confetti_on_endupload', 'title_edit_allowed'], props: { projektarbeit: { type: Object, @@ -52,6 +52,67 @@ export const AbgabeStudentDetail = { } }, methods: { + confettiCannons() { + const container = document.getElementById('confetti-container'); + if (!container) return; + + const colors = ['#FFC107', '#FF5722', '#E91E63', '#00BCD4', '#4CAF50', '#9C27B0']; + const shapes = ['50%', '0%']; + const fragment = document.createDocumentFragment(); + + // Corner Cannons - Slowed Down) + const cannonCount = 150; + + for (let i = 0; i < cannonCount; i++) { + const leftConfetti = document.createElement('div'); + leftConfetti.classList.add('confetti-piece'); + leftConfetti.style.left = '0px'; + leftConfetti.style.top = '100%'; + + const rightConfetti = document.createElement('div'); + rightConfetti.classList.add('confetti-piece'); + rightConfetti.style.left = '100vw'; + rightConfetti.style.top = '100%'; + + const colorL = colors[Math.floor(Math.random() * colors.length)]; + const colorR = colors[Math.floor(Math.random() * colors.length)]; + const shapeL = shapes[Math.floor(Math.random() * shapes.length)]; + const shapeR = shapes[Math.floor(Math.random() * shapes.length)]; + + // Left Styles + leftConfetti.style.background = colorL; + leftConfetti.style.borderRadius = shapeL; + leftConfetti.style.width = `${Math.random() * 10 + 6}px`; + leftConfetti.style.height = `${Math.random() * 14 + 6}px`; + leftConfetti.style.setProperty('--blast-x', `${Math.random() * 50 + 10}vw`); + leftConfetti.style.setProperty('--blast-y', `-${Math.random() * 65 + 30}vh`); + + // Right Styles + rightConfetti.style.background = colorR; + rightConfetti.style.borderRadius = shapeR; + rightConfetti.style.width = `${Math.random() * 10 + 6}px`; + rightConfetti.style.height = `${Math.random() * 14 + 6}px`; + rightConfetti.style.setProperty('--blast-x', `-${Math.random() * 50 + 10}vw`); + rightConfetti.style.setProperty('--blast-y', `-${Math.random() * 65 + 30}vh`); + + // Increased durations to 3s - 5s for a floating gravity effect + const durationL = Math.random() * 2 + 3; + const durationR = Math.random() * 2 + 3; + const delayL = Math.random() * 0.2; + const delayR = Math.random() * 0.2; + + leftConfetti.style.animation = `cannonBlast ${durationL}s linear ${delayL}s both`; + rightConfetti.style.animation = `cannonBlast ${durationR}s linear ${delayR}s both`; + + fragment.appendChild(leftConfetti); + fragment.appendChild(rightConfetti); + + setTimeout(() => leftConfetti.remove(), (delayL + durationL) * 1000); + setTimeout(() => rightConfetti.remove(), (delayR + durationR) * 1000); + } + + container.appendChild(fragment); + }, openTitelEdit() { this.editingTitel = this.projektarbeit.titel ?? ''; this.$refs.modalTitelEdit.show(); @@ -155,6 +216,9 @@ export const AbgabeStudentDetail = { this.$api.call(ApiAbgabe.postStudentProjektarbeitEndupload(formData)) .then(res => { this.handleUploadRes(res, this.enduploadTermin) + if(this.confetti_on_endupload && res.meta.status == "success") { + this.confettiCannons() + } }).finally(()=> { this.loading = false }) @@ -274,8 +338,7 @@ export const AbgabeStudentDetail = { return qgatefound }, isTitelEditAllowed() { - // blocked once the projektarbeit has a note (finished) - mirrors backend guard - return !this.isViewMode && !this.projektarbeit?.note; + return this.title_edit_allowed && !this.isViewMode && !this.projektarbeit?.note; }, getTooltipVerspaetet() { return { value: this.$capitalize(this.$p.t('abgabetool/c4tooltipVerspaetet')), class: "custom-tooltip" } @@ -433,7 +496,7 @@ export const AbgabeStudentDetail = {
- +
{{$capitalize( $p.t('abgabetool/c4abgabetyp') )}}
@@ -544,6 +607,8 @@ export const AbgabeStudentDetail = {
+
+
{{ editingTitel.length }} / 1024
@@ -674,6 +739,7 @@ export const AbgabeStudentDetail = {
{{ $p.t('abgabetool/c4zusatzdatenausfuellen') }}
+ `, }; diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index fee321299..b962fc5ad 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -95,6 +95,7 @@ export const AbgabetoolAssistenz = { old_abgabe_beurteilung_link: null, ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT: null, ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER: null, + MULTIEDIT_TABLE: false, saving: false, loading: false, abgabeTypeOptions: null, @@ -2275,6 +2276,7 @@ export const AbgabetoolAssistenz = { this.old_abgabe_beurteilung_link = res.data?.old_abgabe_beurteilung_link; this.ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT = res.data?.ASSISTENZ_SAMMELMAIL_BUTTON_STUDENT; this.ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER = res.data?.ASSISTENZ_SAMMELMAIL_BUTTON_BETREUER; + this.MULTIEDIT_TABLE = res.data?.MULTIEDIT_TABLE; } // 2. Studiengänge @@ -2659,7 +2661,7 @@ export const AbgabetoolAssistenz = {

{{$p.t('abgabetool/abgabetoolTitleAdmin')}}

-
+
@@ -2676,10 +2678,10 @@ export const AbgabetoolAssistenz = {
-
+
-
+
- diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js index c855b6283..9ffcc1f94 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js @@ -18,7 +18,9 @@ export const AbgabetoolStudent = { return { notenOptions: Vue.computed(() => this.notenOptions), isViewMode: Vue.computed(() => this.isViewMode), - moodle_link: Vue.computed(() => this.moodle_link) + moodle_link: Vue.computed(() => this.moodle_link), + title_edit_allowed: Vue.computed(() => this.title_edit_allowed), + confetti_on_endupload: Vue.computed(() => this.confetti_on_endupload) } }, props: { @@ -46,6 +48,8 @@ export const AbgabetoolStudent = { projektarbeiten: null, selectedProjektarbeit: null, moodle_link: null, + title_edit_allowed: null, + confetti_on_endupload: null, editingTitel: '', editingProjektarbeit: null, }; @@ -335,6 +339,8 @@ export const AbgabetoolStudent = { this.$api.call(ApiAbgabe.getConfigStudent()).then(res => { this.moodle_link = res.data?.moodle_link + this.title_edit_allowed = res.data?.title_edit_allowed + this.confetti_on_endupload = res.data?.confetti_on_endupload }).catch(e => { this.loading = false }) @@ -381,7 +387,7 @@ export const AbgabetoolStudent = { rows="10" maxlength="1024" class="form-control w-100" - @keyup.enter="saveTitel" + @keydown.enter.prevent="saveTitel" />
{{ editingTitel.length }} / 1024
@@ -407,7 +413,7 @@ export const AbgabetoolStudent = {

{{$capitalize( $p.t('abgabetool/abgabetoolTitle') )}}


-
+
{{$capitalize( $p.t('abgabetool/c4abgabeStudentNoProjectsFound') )}}
@@ -497,7 +503,7 @@ export const AbgabetoolStudent = { style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" >{{ projektarbeit.titel }}