From c0e2cec5568bfa01bee49a39bbb7a610483c5869 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Wed, 14 Aug 2024 15:51:27 +0200 Subject: [PATCH 0001/1216] s&d --- application/libraries/AntragLib.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/application/libraries/AntragLib.php b/application/libraries/AntragLib.php index c1649587d..1a8b28ed3 100644 --- a/application/libraries/AntragLib.php +++ b/application/libraries/AntragLib.php @@ -77,7 +77,9 @@ class AntragLib 'studiensemester_kurzbz'=>$prestudentstatus->studiensemester_kurzbz, 'ausbildungssemester'=>$prestudentstatus->ausbildungssemester ], [ - 'statusgrund_id' => null + 'statusgrund_id' => null, + 'updateamum' => date('c'), + 'updatevon' => $insertvon ]); } } @@ -335,7 +337,10 @@ class AntragLib 'status_kurzbz'=>$prestudentstatus->status_kurzbz, 'studiensemester_kurzbz'=>$prestudentstatus->studiensemester_kurzbz, 'ausbildungssemester'=>$prestudentstatus->ausbildungssemester - ], []); + ], [ + 'updateamum' => $insertam, + 'updatevon' => $insertvon + ]); if (isError($result)) { $errors[] = getError($result); From d6d6d0ac48a2c5c5fea4bc16790c66ea134e31d4 Mon Sep 17 00:00:00 2001 From: Cris Date: Mon, 16 Sep 2024 18:22:28 +0200 Subject: [PATCH 0002/1216] Added phrases for softwarebereitstellung app --- system/phrasesupdate.php | 64 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index a6fbf4f4d..9a99b21ec 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -28826,13 +28826,13 @@ array( 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Softwareanforderung und Lizenzmanagement für die Lehre', + 'text' => 'Softwareanforderung für die Lehre', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'Software Request and License management for Education', + 'text' => 'Software Request for Education', 'description' => '', 'insertvon' => 'system' ) @@ -30605,6 +30605,66 @@ array( ) ) ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'userAnzahl', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'User-Anzahl', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'User Number', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'userAnzahlAendern', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'User-Anzahl ändern', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Change User Number', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'userAnzahlNeu', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'User-Anzahl NEU', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'NEW User Number', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), ); From 6c08741450589e3b3a82465b9c90f79358de1810 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Fri, 20 Sep 2024 14:31:25 +0200 Subject: [PATCH 0003/1216] Fist Draft --- application/config/search.php | 664 +++++++++ application/config/searchfunctions.php | 31 + .../controllers/api/frontend/v1/Searchbar.php | 13 +- application/core/FHCAPI_Controller.php | 9 +- application/helpers/hlp_common_helper.php | 17 + application/libraries/SearchBarLib.php | 1307 ++++++++++++----- public/css/components/searchbar.css | 38 + public/js/api/search.js | 4 +- .../components/searchbar/result/employee.js | 59 + .../searchbar/result/mergedperson.js | 166 +++ .../searchbar/result/mergedstudent.js | 35 + .../searchbar/result/organisationunit.js | 63 + .../js/components/searchbar/result/person.js | 46 + .../components/searchbar/result/prestudent.js | 70 + public/js/components/searchbar/result/room.js | 74 + .../js/components/searchbar/result/student.js | 58 + .../searchbar/result/template/action.js | 28 + .../searchbar/result/template/actions.js | 26 + .../searchbar/result/template/frame.js | 59 + public/js/components/searchbar/searchbar.js | 438 ++++-- public/js/plugin/FhcApi.js | 2 + system/dbupdate_3.4.php | 1 + system/dbupdate_3.4/40128_search.php | 171 +++ 23 files changed, 2838 insertions(+), 541 deletions(-) create mode 100644 application/config/search.php create mode 100644 application/config/searchfunctions.php create mode 100644 public/js/components/searchbar/result/employee.js create mode 100644 public/js/components/searchbar/result/mergedperson.js create mode 100644 public/js/components/searchbar/result/mergedstudent.js create mode 100644 public/js/components/searchbar/result/organisationunit.js create mode 100644 public/js/components/searchbar/result/person.js create mode 100644 public/js/components/searchbar/result/prestudent.js create mode 100644 public/js/components/searchbar/result/room.js create mode 100644 public/js/components/searchbar/result/student.js create mode 100644 public/js/components/searchbar/result/template/action.js create mode 100644 public/js/components/searchbar/result/template/actions.js create mode 100644 public/js/components/searchbar/result/template/frame.js create mode 100644 system/dbupdate_3.4/40128_search.php diff --git a/application/config/search.php b/application/config/search.php new file mode 100644 index 000000000..ac6189527 --- /dev/null +++ b/application/config/search.php @@ -0,0 +1,664 @@ + 'person_id', + 'table' => 'public.tbl_person', + 'searchfields' => [ + 'uid' => [ + 'comparison' => 'equals', + 'field' => 'uid', + 'join' => [ + 'table' => "public.tbl_benutzer", + 'using' => "person_id" + ], + '1-n' => true + ], + 'vorname' => [ + 'alias' => ['firstname'], + 'comparison' => 'similar', + 'field' => 'vorname' + ], + 'nachname' => [ + 'alias' => ['lastname', 'surename'], + 'comparison' => 'similar', + 'field' => 'nachname' + ], + 'name' => [ + 'comparison' => 'similar', + 'field' => "(vorname || ' ' || nachname)" + ], + 'email' => [ + 'comparison' => 'similar', + 'field' => 'kontakt', + 'join' => [ + 'table' => "public.tbl_kontakt", + 'on' => "kontakttyp = 'email' AND tbl_kontakt.person_id = tbl_person.person_id" + ], + "1-n" => true + ], + 'tel' => [ + 'alias' => ['phone', 'telefon'], + 'comparison' => 'similar', + 'field' => 'kontakt', + 'join' => [ + 'table' => "public.tbl_kontakt", + 'on' => "kontakttyp IN ('telefon', 'so.tel', 'mobil') AND tbl_kontakt.person_id = tbl_person.person_id" + ], + "1-n" => true + ], + 'preid' => [ + 'alias' => ['prestudent_id'], + 'comparison' => 'equal-int', + 'field' => 'prestudent_id', + 'join' => [ + 'table' => "public.tbl_prestudent", + 'using' => "person_id" + ], + '1-n' => true + ], + 'pid' => [ + 'alias' => ['person_id'], + 'comparison' => 'equal-int', + 'field' => 'person_id' + ] + ], + 'resultfields' => [ + "b.uid", + "p.person_id", + "(p.vorname || ' ' || p.nachname) AS name", + "ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email", + "p.foto" + ], + 'resultjoin' => " + JOIN public.tbl_person p USING (person_id) + LEFT JOIN public.tbl_benutzer b USING (person_id)" +]; + +$config['student'] = [ + 'primarykey' => 'student_uid', + 'table' => 'public.tbl_student', + 'searchfields' => [ + 'uid' => [ + 'comparison' => 'equals', + 'field' => 'student_uid' + ], + 'vorname' => [ + 'alias' => ['firstname'], + 'comparison' => 'similar', + 'field' => 'vorname', + 'join' => [ + [ + 'table' => "public.tbl_prestudent", + 'using' => "prestudent_id" + ], + [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ] + ], + 'nachname' => [ + 'alias' => ['lastname', 'surename'], + 'comparison' => 'similar', + 'field' => 'nachname', + 'join' => [ + [ + 'table' => "public.tbl_prestudent", + 'using' => "prestudent_id" + ], + [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ] + ], + 'name' => [ + 'comparison' => 'similar', + 'field' => "(vorname || ' ' || nachname)", + 'join' => [ + [ + 'table' => "public.tbl_prestudent", + 'using' => "prestudent_id" + ], + [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ] + ], + 'email' => [ + 'comparison' => 'similar', + 'field' => 'kontakt', + 'join' => [ + [ + 'table' => "public.tbl_prestudent", + 'using' => "prestudent_id" + ], + [ + 'table' => "public.tbl_kontakt", + 'on' => "kontakttyp = 'email' AND tbl_kontakt.person_id = tbl_prestudent.person_id" + ] + ], + "1-n" => true + ], + 'tel' => [ + 'alias' => ['phone', 'telefon'], + 'comparison' => 'similar', + 'field' => 'kontakt', + 'join' => [ + [ + 'table' => "public.tbl_prestudent", + 'using' => "prestudent_id" + ], + [ + 'table' => "public.tbl_kontakt", + 'on' => "kontakttyp IN ('telefon', 'so.tel', 'mobil') AND tbl_kontakt.person_id = tbl_prestudent.person_id" + ] + ], + "1-n" => true + ], + 'stg' => [ + 'alias' => ['studiengang'], + 'comparison' => 'equals', + 'field' => "typ || kurzbz", + 'join' => [ + [ + 'table' => "public.tbl_prestudent", + 'using' => "prestudent_id" + ], + [ + 'table' => "public.tbl_studiengang", + 'on' => "tbl_studiengang.studiengang_kz = tbl_prestudent.studiengang_kz" + ] + ] + ], + 'preid' => [ + 'alias' => ['prestudent_id'], + 'comparison' => 'equal-int', + 'field' => 'prestudent_id' + ], + 'pid' => [ + 'alias' => ['person_id'], + 'comparison' => 'equal-int', + 'field' => 'person_id', + 'join' => [ + 'table' => "public.tbl_prestudent", + 'using' => "prestudent_id" + ] + ] + ], + 'resultfields' => [ + "s.student_uid AS uid", + "s.matrikelnr", + "p.person_id", + "(p.vorname || ' ' || p.nachname) AS name", + "ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email", + "p.foto" + ], + 'resultjoin' => " + JOIN public.tbl_student s USING (student_uid) + JOIN public.tbl_benutzer b ON(b.uid = s.student_uid) + JOIN public.tbl_person p USING(person_id)" +]; + +// TODO(chris): "ref" +$config['prestudent'] = [ + 'primarykey' => 'prestudent_id', + 'table' => 'public.tbl_prestudent', + 'searchfields' => [ + 'uid' => [ + 'comparison' => 'equals', + 'field' => 'student_uid', + 'join' => [ + 'table' => "public.tbl_student", + 'using' => "prestudent_id" + ] + ], + 'vorname' => [ + 'alias' => ['firstname'], + 'comparison' => 'similar', + 'field' => 'vorname', + 'join' => [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ], + 'nachname' => [ + 'alias' => ['lastname', 'surename'], + 'comparison' => 'similar', + 'field' => 'nachname', + 'join' => [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ], + 'name' => [ + 'comparison' => 'similar', + 'field' => "(vorname || ' ' || nachname)", + 'join' => [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ], + 'email' => [ + 'comparison' => 'similar', + 'field' => 'kontakt', + 'join' => [ + 'table' => "public.tbl_kontakt", + 'on' => "kontakttyp = 'email' AND tbl_kontakt.person_id = tbl_prestudent.person_id" + ], + "1-n" => true + ], + 'tel' => [ + 'alias' => ['phone', 'telefon'], + 'comparison' => 'similar', + 'field' => 'kontakt', + 'join' => [ + 'table' => "public.tbl_kontakt", + 'on' => "kontakttyp IN ('telefon', 'so.tel', 'mobil') AND tbl_kontakt.person_id = tbl_prestudent.person_id" + ], + "1-n" => true + ], + 'stg' => [ + 'alias' => ['studiengang'], + 'comparison' => 'equals', + 'field' => "typ || kurzbz", + 'join' => [ + 'table' => "public.tbl_studiengang", + 'using' => "studiengang_kz" + ] + ], + 'preid' => [ + 'alias' => ['prestudent_id'], + 'comparison' => 'equal-int', + 'field' => 'prestudent_id' + ], + 'pid' => [ + 'alias' => ['person_id'], + 'comparison' => 'equal-int', + 'field' => 'person_id', + 'join' => [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ] + ], + 'resultfields' => [ + "ps.prestudent_id", + "ps.studiengang_kz", + "s.matrikelnr", + "p.person_id", + "b.uid", + "(p.vorname || ' ' || p.nachname) AS name", + "ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email", + "p.foto", + "UPPER(sg.typ || sg.kurzbz) AS stg_kuerzel", + "sg.bezeichnung", + "( + SELECT bezeichnung_mehrsprachig[(TABLE lang)] + FROM public.tbl_status + WHERE status_kurzbz = public.get_rolle_prestudent(ps.prestudent_id, NULL) + LIMIT 1 + ) as status" + ], + 'resultjoin' => " + LEFT JOIN public.tbl_prestudent ps USING (prestudent_id) + LEFT JOIN public.tbl_student s ON (ps.prestudent_id = s.prestudent_id) + LEFT JOIN public.tbl_benutzer b ON (b.uid = s.student_uid) + JOIN public.tbl_person p ON (p.person_id = ps.person_id) + LEFT JOIN public.tbl_studiengang sg ON (sg.studiengang_kz = ps.studiengang_kz)" +]; + +$config['employee'] = [ + 'alias' => ['ma', 'mitarbeiter'], + 'primarykey' => 'mitarbeiter_uid', + 'table' => 'public.tbl_mitarbeiter', + 'searchfields' => [ + 'uid' => [ + 'alias' => ['mitarbeiter_uid'], + 'comparison' => 'equals', + 'field' => "mitarbeiter_uid" + ], + 'vorname' => [ + 'alias' => ['firstname'], + 'comparison' => 'similar', + 'field' => "vorname", + 'join' => [ + [ + 'table' => "public.tbl_benutzer", + 'on' => "uid = mitarbeiter_uid" + ], + [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ] + ], + 'nachname' => [ + 'alias' => ['lastname', 'surename'], + 'comparison' => 'similar', + 'field' => "nachname", + 'join' => [ + [ + 'table' => "public.tbl_benutzer", + 'on' => "uid = mitarbeiter_uid" + ], + [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ] + ], + 'name' => [ + 'comparison' => 'similar', + 'field' => "(vorname || ' ' || nachname)", + 'join' => [ + [ + 'table' => "public.tbl_benutzer", + 'on' => "uid = mitarbeiter_uid" + ], + [ + 'table' => "public.tbl_person", + 'using' => "person_id" + ] + ] + ], + 'email' => [ + 'comparison' => 'similar', + 'field' => "COALESCE(alias, uid) || '" . '@' . DOMAIN . "'", + 'join' => [ + 'table' => "public.tbl_benutzer", + 'on' => "uid = mitarbeiter_uid" + ] + ], + 'tel' => [ + 'alias' => ['phone', 'telefon'], + 'comparison' => 'similar', + 'field' => "TRIM(COALESCE(kontakt, '') || ' ' || COALESCE(telefonklappe, ''))", + 'join' => [ + 'table' => "public.tbl_kontakt", + 'on' => "kontakttyp = 'telefon' AND tbl_kontakt.standort_id = tbl_mitarbeiter.standort_id" + ], + "1-n" => true + ], + 'pid' => [ + 'alias' => ['person_id'], + 'comparison' => 'equal-int', + 'field' => "person_id" + ], + 'oe' => [ + 'alias' => ['ou', 'organisationseinheit', 'organisationunit'], + 'comparison' => 'vector', + 'field' => "fts_bezeichnung", + 'join' => [ + [ + 'table' => "public.tbl_benutzerfunktion", + 'on' => "mitarbeiter_uid = uid + AND funktion_kurzbz = 'oezuordnung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW())" + ], + [ + 'table' => "public.tbl_organisationseinheit", + 'using' => "oe_kurzbz" + ] + ], + '1-n' => true + ], + 'kst' => [ + 'comparison' => 'vector', + 'field' => "fts_bezeichnung", + 'join' => [ + [ + 'table' => "public.tbl_benutzerfunktion", + 'on' => "mitarbeiter_uid = uid + AND funktion_kurzbz = 'kstzuordnung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW())" + ], + [ + 'table' => "public.tbl_organisationseinheit", + 'using' => "oe_kurzbz" + ] + ], + '1-n' => true + ] + ], + 'resultfields' => [ + "b.uid", + "p.person_id", + "(p.vorname || ' ' || p.nachname) AS name", + "ARRAY( + SELECT + '[' || ot.bezeichnung || '] ' || o.bezeichnung AS bezeichnung + FROM public.tbl_benutzerfunktion bf + JOIN public.tbl_organisationseinheit o USING(oe_kurzbz) + JOIN public.tbl_organisationseinheittyp ot USING(organisationseinheittyp_kurzbz) + WHERE bf.funktion_kurzbz = 'oezuordnung' + AND (bf.datum_von IS NULL OR bf.datum_von <= NOW()) + AND (bf.datum_bis IS NULL OR bf.datum_bis >= NOW()) + AND bf.uid = b.uid + GROUP BY o.bezeichnung, ot.bezeichnung + ) AS organisationunit_name", + "COALESCE(b.alias, b.uid) || '" . '@' . DOMAIN . "' AS email", + "TRIM(COALESCE(k.kontakt, '') || ' ' || COALESCE(m.telefonklappe, '')) AS phone", + "'" . base_url("/cis/public/bild.php?src=person&person_id=") . "' || p.person_id AS photo_url", + "ARRAY( + SELECT + '[' || ot.bezeichnung || '] ' || o.bezeichnung AS bezeichnung + FROM public.tbl_benutzerfunktion bf + JOIN public.tbl_organisationseinheit o USING(oe_kurzbz) + JOIN public.tbl_organisationseinheittyp ot USING(organisationseinheittyp_kurzbz) + WHERE bf.funktion_kurzbz = 'kstzuordnung' + AND (bf.datum_von IS NULL OR bf.datum_von <= NOW()) + AND (bf.datum_bis IS NULL OR bf.datum_bis >= NOW()) + AND bf.uid = b.uid + GROUP BY o.bezeichnung, ot.bezeichnung + ) AS standardkostenstelle" + ], + 'resultjoin' => " + JOIN public.tbl_mitarbeiter m USING (mitarbeiter_uid) + JOIN public.tbl_benutzer b ON (b.uid = m.mitarbeiter_uid) + JOIN public.tbl_person p USING(person_id) + LEFT JOIN ( + SELECT kontakt, standort_id + FROM public.tbl_kontakt + WHERE kontakttyp = 'telefon' + ) k ON (k.standort_id = m.standort_id)" +]; + +$config['unassigned_employee'] = $config['employee']; +$config['unassigned_employee']['alias'] = ['mitarbeiter_ohne_zuordnung']; +$config['unassigned_employee']['prepare'] = "unassigned_employee AS ( + SELECT tbl_mitarbeiter.* + FROM public.tbl_mitarbeiter + LEFT JOIN public.tbl_benutzerfunktion ON ( + uid = mitarbeiter_uid + AND funktion_kurzbz = 'kstzuordnung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW()) + ) + WHERE tbl_benutzerfunktion.bezeichnung IS NULL + UNION + SELECT tbl_mitarbeiter.* + FROM public.tbl_mitarbeiter + LEFT JOIN public.tbl_benutzerfunktion ON ( + uid = mitarbeiter_uid + AND funktion_kurzbz = 'oezuordnung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW()) + ) + WHERE tbl_benutzerfunktion.bezeichnung IS NULL +)"; +$config['unassigned_employee']['table'] = "unassigned_employee"; +$config['unassigned_employee']['searchfields']['tel']['join']['on'] = "kontakttyp = 'telefon' AND tbl_kontakt.standort_id = unassigned_employee.standort_id"; + +$config['organisationunit'] = [ + 'alias' => ['ou', 'organisationseinheit', 'oe'], + 'primarykey' => 'oe_kurzbz', + 'table' => 'public.tbl_organisationseinheit', + 'searchfields' => [ + 'uid' => [ + 'comparison' => 'equals', + 'field' => 'uid', + 'prepare' => "organisationunit_leader(oe_kurzbz, uid, vorname, nachname) AS ( + SELECT oe_kurzbz, vorname, nachname, uid + FROM public.tbl_benutzerfunktion + JOIN public.tbl_benutzer USING (uid) + JOIN public.tbl_person USING (person_id) + WHERE funktion_kurzbz = 'Leitung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW()) + AND tbl_benutzer.aktiv = TRUE + )", + 'join' => [ + 'table' => "organisationunit_leader", + 'using' => "oe_kurzbz" + ], + '1-n' => true + ], + 'vorname' => [ + 'alias' => ['firstname'], + 'comparison' => 'similar', + 'field' => 'vorname', + 'prepare' => "organisationunit_leader(oe_kurzbz, uid, vorname, nachname) AS ( + SELECT oe_kurzbz, vorname, nachname, uid + FROM public.tbl_benutzerfunktion + JOIN public.tbl_benutzer USING (uid) + JOIN public.tbl_person USING (person_id) + WHERE funktion_kurzbz = 'Leitung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW()) + AND tbl_benutzer.aktiv = TRUE + )", + 'join' => [ + 'table' => "organisationunit_leader", + 'using' => "oe_kurzbz" + ], + '1-n' => true + ], + 'nachname' => [ + 'alias' => ['lastname', 'surename'], + 'comparison' => 'similar', + 'field' => 'nachname', + 'prepare' => "organisationunit_leader(oe_kurzbz, uid, vorname, nachname) AS ( + SELECT oe_kurzbz, vorname, nachname, uid + FROM public.tbl_benutzerfunktion + JOIN public.tbl_benutzer USING (uid) + JOIN public.tbl_person USING (person_id) + WHERE funktion_kurzbz = 'Leitung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW()) + AND tbl_benutzer.aktiv = TRUE + )", + 'join' => [ + 'table' => "organisationunit_leader", + 'using' => "oe_kurzbz" + ], + '1-n' => true + ], + 'name' => [ + 'comparison' => 'similar', + 'field' => "(vorname || ' ' || nachname)", + 'prepare' => "organisationunit_leader(oe_kurzbz, uid, vorname, nachname) AS ( + SELECT oe_kurzbz, vorname, nachname, uid + FROM public.tbl_benutzerfunktion + JOIN public.tbl_benutzer USING (uid) + JOIN public.tbl_person USING (person_id) + WHERE funktion_kurzbz = 'Leitung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW()) + AND tbl_benutzer.aktiv = TRUE + )", + 'join' => [ + 'table' => "organisationunit_leader", + 'using' => "oe_kurzbz" + ], + '1-n' => true + ], + 'oe' => [ + 'alias' => ['ou', 'organisationseinheit', 'organisationunit'], + 'comparison' => 'vector', + 'field' => "fts_bezeichnung" + ], + 'kurzbz' => [ + 'alias' => ['oe_kurzbz'], + 'comparison' => 'equals', + 'field' => "oe_kurzbz" + ] + ], + 'resultfields' => [ + "oe.oe_kurzbz", + "('[' || type.bezeichnung || '] ' || oe.bezeichnung) AS name", + "oe_parent.oe_kurzbz AS parentoe_kurzbz", + "(CASE WHEN oe_parent.bezeichnung IS NOT NULL THEN '[' || type_parent.bezeichnung || '] ' || oe_parent.bezeichnung END) AS parentoe_name", + "ARRAY( + SELECT JSON_BUILD_OBJECT('uid', b.uid, 'vorname', p.vorname, 'nachname', p.nachname, 'name', (p.vorname || ' ' || p.nachname)) + FROM public.tbl_benutzerfunktion bf + JOIN public.tbl_benutzer b USING (uid) + JOIN public.tbl_person p USING (person_id) + WHERE funktion_kurzbz = 'Leitung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW()) + AND b.aktiv = TRUE + AND oe_kurzbz = oe.oe_kurzbz + ) AS leaders", + "( + SELECT COUNT(*) + FROM public.tbl_benutzerfunktion + WHERE funktion_kurzbz = 'oezuordnung' + AND (datum_von IS NULL OR datum_von <= NOW()) + AND (datum_bis IS NULL OR datum_bis >= NOW()) + AND oe_kurzbz = oe.oe_kurzbz + ) AS number_of_people", + "(CASE WHEN oe.mailverteiler THEN oe.oe_kurzbz || '" . '@' . DOMAIN . "' END) AS mailgroup" + ], + 'resultjoin' => " + JOIN public.tbl_organisationseinheit oe + USING (oe_kurzbz) + JOIN public.tbl_organisationseinheittyp type + USING (organisationseinheittyp_kurzbz) + LEFT JOIN public.tbl_organisationseinheit oe_parent + ON (oe_parent.oe_kurzbz = oe.oe_parent_kurzbz) + LEFT JOIN public.tbl_organisationseinheittyp type_parent + ON (oe_parent.organisationseinheittyp_kurzbz = type_parent.organisationseinheittyp_kurzbz)" +]; + +$config['room'] = [ + 'alias' => ['raum'], + 'primarykey' => 'ort_kurzbz', + 'table' => 'public.tbl_ort', + 'searchfields' => [ + 'name' => [ + 'comparison' => 'similar', + 'field' => 'ort_kurzbz' + ] + ], + 'resultfields' => [ + "ort.ort_kurzbz", + "ort.gebteil AS building", + "ort.ausstattung AS equipment", + "ort.stockwerk AS floor", + "ort.dislozierung AS room_number", + "ort.content_id", + "address.ort AS city", + "address.plz AS zip", + "address.strasse AS street", + "ort.max_person", + "ort.arbeitsplaetze AS workplaces" + ], + 'resultjoin' => " + JOIN public.tbl_ort ort + USING (ort_kurzbz) + LEFT JOIN public.tbl_standort + USING (standort_id) + LEFT JOIN public.tbl_adresse address + USING (adresse_id)" +]; diff --git a/application/config/searchfunctions.php b/application/config/searchfunctions.php new file mode 100644 index 000000000..c8244e9a3 --- /dev/null +++ b/application/config/searchfunctions.php @@ -0,0 +1,31 @@ + 4, + 'rank' => "0", + 'compare' => "{field} = {word}", + 'force_integer' => true +]; + +$config['equals'] = [ + 'priority' => 3, + 'rank' => "0", + 'compare' => "LOWER({field}) = {word}" +]; + +$config['similar'] = [ + 'priority' => 2, + 'rank' => "(COALESCE({field}, '') <->> {word})", + 'compare' => "COALESCE({field}, '') %> {word}", + 'compare_boolean' => "COALESCE({field}, '') ILIKE {like:word}" +]; + +$config['vector'] = [ + 'priority' => 1, + 'rank' => "ts_rank_cd({field}, to_tsquery('simple', {word}))", + 'compare' => "to_tsquery('simple', {word}) @@ {field}" +]; + diff --git a/application/controllers/api/frontend/v1/Searchbar.php b/application/controllers/api/frontend/v1/Searchbar.php index 8b383e042..2d5b06063 100644 --- a/application/controllers/api/frontend/v1/Searchbar.php +++ b/application/controllers/api/frontend/v1/Searchbar.php @@ -50,6 +50,7 @@ class Searchbar extends FHCAPI_Controller */ public function search() { + #$searchstrings = ['pid:71995', 'email:eder.iris', 'schnabl', 'schnabl thomas', 'schnabl thomas sarim', 'schnabl -thomas', 'nachname:schnabl -vorname:thomas', 'schnabl thomas -sarim', 'schnabl or hacker', '-ali', '-ali -baba', '-ali -baba -raub', '-ali -honig', '-ali -baba ali', 'hofer martin']; $this->load->library('form_validation'); // Checks if the searchstr and the types parameters are in the POSTed JSON @@ -57,13 +58,17 @@ class Searchbar extends FHCAPI_Controller $this->form_validation->set_rules(self::TYPES_PARAM . '[]', null, 'required'); if (!$this->form_validation->run()) - $this->terminateWithError(SearchBarLib::ERROR_WRONG_JSON, self::ERROR_TYPE_GENERAL); + $this->terminateWithValidationErrors($this->form_validation->error_array()); // Convert to json the result from searchbarlib->search $result = $this->searchbarlib->search($this->input->post(self::SEARCHSTR_PARAM), $this->input->post(self::TYPES_PARAM)); - if (property_exists($result, 'error')) - $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - $this->terminateWithSuccess($result); + + $data = $this->getDataOrTerminateWithError($result); + + $this->addMeta('time', $result->meta['time']); + $this->addMeta('searchstring', $result->meta['searchstring']); + + $this->terminateWithSuccess($data); } } diff --git a/application/core/FHCAPI_Controller.php b/application/core/FHCAPI_Controller.php index 36388e271..197f4757f 100644 --- a/application/core/FHCAPI_Controller.php +++ b/application/core/FHCAPI_Controller.php @@ -106,10 +106,15 @@ class FHCAPI_Controller extends Auth_Controller $error = []; if (is_array($data)) { - if ($type == self::ERROR_TYPE_VALIDATION) + if ($type == self::ERROR_TYPE_VALIDATION) { $error['messages'] = $data; - else + } elseif (array_is_list($data)) { + foreach ($data as $d) + $this->addError($d, $type); + return; + } else { $error = $data; + } } elseif (is_object($data)) { $error = (array)$data; } else { diff --git a/application/helpers/hlp_common_helper.php b/application/helpers/hlp_common_helper.php index 40aed007c..00c0a1b93 100644 --- a/application/helpers/hlp_common_helper.php +++ b/application/helpers/hlp_common_helper.php @@ -424,6 +424,23 @@ function isValidDate($dateString) } +// ------------------------------------------------------------------------ +// PHP functions that don't exist in older versions +// ------------------------------------------------------------------------ + +/** + * Returns true if the given array is sequential + */ +if (!function_exists('array_is_list')) { + function array_is_list(array $arr) + { + if ($arr === []) { + return true; + } + return array_keys($arr) === range(0, count($arr) - 1); + } +} + // ------------------------------------------------------------------------ // Collection of utility functions for form validation purposes // ------------------------------------------------------------------------ diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index b725f6e90..6547cd107 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -39,6 +39,10 @@ class SearchBarLib private $_ci; // Code igniter instance + private $_searchfunction_priorities = []; + private $_numeric_searchfunctions = []; + private $_allowed_searchfunctions = []; + /** * Gets the CI instance and loads model */ @@ -46,8 +50,22 @@ class SearchBarLib { $this->_ci =& get_instance(); // get code igniter instance - // It is loaded only to have the DB_Model available + // It is loaded only to have the DB functions available $this->_ci->load->model('person/Benutzer_model', 'BenutzerModel'); + + // Load Config + $this->_ci->load->config('search', true); + $this->_ci->load->config('searchfunctions', true); + + $this->_ci->load->library('PhrasesLib', [['search'], null], 'search_phrases'); + + // Precompute helper arrays + foreach ($this->_ci->config->item('searchfunctions') as $key => $arr) { + $this->_searchfunction_priorities[$key] = $arr['priority']; + if ($arr['force_integer'] ?? false) + $this->_numeric_searchfunctions[] = $key; + $this->_allowed_searchfunctions[] = $key; + } } //------------------------------------------------------------------------------------------------------------------ @@ -55,384 +73,612 @@ class SearchBarLib /** * It performes the search of the given search string using the specified search types + * TODO(chris): permissions + * + * @param string $searchstring + * @param array $types (optional) + * + * @return stdClass containing an array with the result on index 0 + * and the overall query time on index 1. */ - public function search($searchstr, $types) + public function search($searchstring, $types = []) { - // Checks if the given parameters are fine - $search = $this->_checkParameters($searchstr, $types); + if (!$types) { + $types = $this->_ci->config->item('search'); + } else { + $tmp = []; + $missing = []; + foreach ($types as $type) { + $typeconfig = $this->_ci->config->item($type, 'search'); + if (!$typeconfig) { + $missing[] = $type; + } else { + $tmp[$type] = $typeconfig; + } + } + if ($missing) { + $p = $this->_ci->search_phrases; + return error(array_map(function ($type) use ($p) { + return $p->t('search', 'error_missing_config', [ + 'type' => $type + ]); // TODO(chris): phrase + }, $missing)); + } + $types = $tmp; + } - // If the check was successful then perform the search - if (isSuccess($search)) $search = $this->_search($searchstr, $types); - return $search; // return the result + // Convert searchstring into array + list($searchArray, $searchstring) = $this->_convertQuery($searchstring, $types); + + + $sql = $this->getDynamicSearchSqls($searchArray, array_keys($types)); + if (isError($sql)) + return $sql; + if (!hasData($sql)) { + $retval = success([]); + $retval->meta = ['time' => 0, 'searchstring' => $searchstring]; + return $retval; + } + + $msc = microtime(true); + $result = $this->_ci->BenutzerModel->execReadOnlyQuery(getData($sql)); + $msc = microtime(true) - $msc; + + if (isError($result)) + return $result; + + $retval = success($result->retval); + $retval->meta = [ + 'time' => $msc, + 'searchstring' => $searchstring + ]; + + return $retval; + } + + /** + * Generates the search query for the given search string and the + * specified search type. + * + * @param array $searchArray + * @param string $table + * + * @return stdClass containing the query string. + */ + public function getDynamicSearchSql($searchArray, $table) + { + $res = $this->checkConfig($table); + if (isError($res)) + return $res; + $table_config = getData($res); + + $sql_with = []; + + $sql_select = $this->prepareDynamicSearchSql($sql_with, $searchArray, $table); + + if (!$sql_select) + return success(""); + + $lang = getUserLanguage(); + + $output = " + WITH lang (index) AS ( + SELECT index + FROM public.tbl_sprache + WHERE sprache=" . $this->_ci->db->escape($lang) . " + LIMIT 1 + )"; + + if ($sql_with) { + $sql_with = array_unique($sql_with); + $output .= ", " . implode(", ", $sql_with); + } + + $other_selects = ""; + if (isset($table_config['resultfields'])) + $other_selects = implode(", ", $table_config['resultfields']); + if ($other_selects) + $other_selects = ", " . $other_selects; + + $output .= " + , q (" . $table_config['primarykey'] . ", rank) AS ( + SELECT " . $table_config['primarykey'] . ", MAX(rank) + FROM (" . implode(" UNION ", $sql_select) . ") q + GROUP BY " . $table_config['primarykey'] . " + ) + SELECT + " . $this->_ci->db->escape($table) . " AS type, + q.rank + " . $other_selects . " + FROM q + " . ($table_config['resultjoin'] ?? "") . " + ORDER BY rank DESC + "; + + return success($output); + } + + /** + * Generates the search query for the given search string and the + * specified search types. + * + * @param array $searchArray + * @param array $types + * + * @return stdClass containing the query string. + */ + public function getDynamicSearchSqls($searchArray, $types) + { + $with = []; + $selects = []; + foreach ($types as $type) { + $res = $this->checkConfig($type); + if (isError($res)) + return $res; + $table_config = getData($res); + + $select = $this->prepareDynamicSearchSql($with, $searchArray, $type); + if (!$select) + continue; + + $with[] = "final_" . $type . " (" . $table_config['primarykey'] . ", rank) AS ( + SELECT " . $table_config['primarykey'] . ", MAX(rank) + FROM (" . implode(" UNION ", $select) . ") q + GROUP BY " . $table_config['primarykey'] . " + )"; + + $other_selects = + $selects[] = " + SELECT + " . $this->_ci->db->escape($type) . " AS type, + rank, + TO_JSONB((SELECT x FROM (SELECT " . implode(", ", $table_config['resultfields'] ?? ['*']) . ") x)) AS data + FROM final_" . $type . ($table_config['resultjoin'] ?? ""); + } + + if (!$selects) + return success(""); + + $with = array_unique($with); + + $lang = getUserLanguage(); + array_unshift($with, "lang (index) AS ( + SELECT index + FROM public.tbl_sprache + WHERE sprache=" . $this->_ci->db->escape($lang) . " + LIMIT 1 + )"); + + return success(" + WITH " . implode(", ", $with) . " + SELECT * + FROM (" . implode(" UNION ", $selects) . ") q + ORDER BY rank DESC + LIMIT 100 + "); + } + + //------------------------------------------------------------------------------------------------------------------ + // Protected methods + + /** + * Check config + * + * @param string $name + * + * @return stdClass + */ + protected function checkConfig($name) + { + $table_config = $this->_ci->config->item($name, 'search'); + + if (!$table_config) + return error($this->_ci->search_phrases->t('search', 'error_missing_config', [ + 'type' => $name + ])); // TODO(chris): phrase + + $errors = []; + if (!isset($table_config['table']) + || !is_string($table_config['table']) + || !$table_config['table'] + ) { + $errors[] = $this->_ci->search_phrases->t('search', 'error_invalid_config', [ + 'type' => $name, + 'field' => 'table' + ]); // TODO(chris): phrase + } + if (!isset($table_config['primarykey']) + || !is_string($table_config['primarykey']) + || !$table_config['primarykey'] + ) { + $errors[] = $this->_ci->search_phrases->t('search', 'error_invalid_config', [ + 'type' => $name, + 'field' => 'primarykey' + ]); + } + if (!isset($table_config['resultfields']) + || !is_array($table_config['resultfields']) + || !$table_config['resultfields'] + ) { + $errors[] = $this->_ci->search_phrases->t('search', 'error_invalid_config', [ + 'type' => $name, + 'field' => 'resultfields' + ]); + } + if (!isset($table_config['searchfields']) + || !is_array($table_config['searchfields']) + || !$table_config['searchfields'] + ) { + $errors[] = $this->_ci->search_phrases->t('search', 'error_invalid_config', [ + 'type' => $name, + 'field' => 'searchfields' + ]); + } else { + foreach ($table_config['searchfields'] as $searchfield => $config) { + if (!isset($config['field']) + || !is_string($config['field']) + || !$config['field'] + ) { + $errors[] = $this->_ci->search_phrases->t('search', 'error_invalid_config_searchfield', [ + 'type' => $name, + 'searchfield' => $searchfield, + 'field' => 'field' + ]); // TODO(chris): phrase + } + if (!isset($config['comparison']) + || !is_string($config['comparison']) + || !in_array($config['comparison'], $this->_allowed_searchfunctions) + ) { + $errors[] = $this->_ci->search_phrases->t('search', 'error_invalid_config_searchfield', [ + 'type' => $name, + 'searchfield' => $searchfield, + 'field' => 'comparison' + ]); + } + } + } + + if ($errors) + return error($errors); + + return success($table_config); + } + + /** + * Generates the with statements for the given search string and the + * specified search type. + * + * @param array &$sqlWith + * @param array $searchArray + * @param string $table + * + * @return string a query string or the name of the prepared select. + */ + protected function prepareDynamicSearchSql(&$sqlWith, $searchArray, $table) + { + $table_config = $this->_ci->config->item($table, 'search'); + + $id_offset = count($sqlWith); + + + $allowed_codes_w_order = ['' => 0, '!' => -1]; + $max = max($this->_searchfunction_priorities); + foreach ($table_config['searchfields'] as $code => $config) { + $allowed_codes_w_order[$code] = $this->_searchfunction_priorities[$config['comparison']]; + $allowed_codes_w_order['!' . $code] = $this->_searchfunction_priorities[$config['comparison']] - $max - 2; + } + + $check_order = $this->_searchfunction_priorities; + uasort($table_config['searchfields'], function ($a, $b) use ($check_order) { + return $check_order[$b['comparison']] - $check_order[$a['comparison']]; + }); + + $integer_functions = $this->_numeric_searchfunctions; + $integer_fields = array_keys(array_filter($table_config['searchfields'], function ($a) use ($integer_functions) { + return in_array($a['comparison'], $integer_functions); + })); + + $only_integer_fields = count($integer_fields) == count($table_config['searchfields']); + + $aliases = []; + foreach ($table_config['searchfields'] as $field => $config) { + if (isset($config['alias'])) { + foreach ($config['alias'] as $alias) { + $aliases[$alias] = $field; + $aliases['!' . $alias] = '!' . $field; + } + } + } + + $sql_select = []; + + if (isset($table_config['prepare'])) { + if (is_array($table_config['prepare'])) + $sqlWith = $table_config['prepare']; + else + $sqlWith[] = $table_config['prepare']; + } + + foreach ($searchArray as $or_search) { + if (isset($or_search['-filter']) && !in_array($table, $or_search['-filter'])) + continue; + unset($or_search['-filter']); + + foreach ($aliases as $alias => $field) { + if (isset($or_search[$alias])) { + $or_search[$field] = array_merge($or_search[$alias], $or_search[$field] ?? []); + unset($or_search[$alias]); + } + } + + // NOTE(chris): early out if not allowed fields are in the search array + $used_codes = array_keys($or_search); + if (count(array_intersect($used_codes, array_keys($allowed_codes_w_order))) != count($used_codes)) + continue; + + // NOTE(chris): expand general excludes to all fields + if (isset($or_search['!'])) { + $not = $or_search['!']; + unset($or_search['!']); + foreach ($table_config['searchfields'] as $code => $config) { + if (isset($or_search['!' . $code])) + $or_search['!' . $code] = array_unique(array_merge($or_search['!' . $code], $not)); + else + $or_search['!' . $code] = $not; + } + } + + // NOTE(chris): early out if all searchfields require an integer and at least one searchword is not a number + if ($only_integer_fields + && isset($or_search[""]) + && $this->_hasAtLeastOneNaN($or_search[""]) + ) { + continue; + } + + $skip = false; + foreach ($integer_fields as $code) { + // NOTE(chris): filter non integer for integer fields + if (isset($or_search['!' . $code])) { + $or_search['!' . $code] = array_filter($or_search['!' . $code], function ($a) { + return is_numeric($a); + }); + if (!$or_search['!' . $code]) + unset($or_search['!' . $code]); + } + // NOTE(chris): early out if a searchword that is not a number is compared to a searchfield that requires an integer + if (isset($or_search[$code]) + && $this->_hasAtLeastOneNaN($or_search[$code]) + ) { + $skip = true; + break; + } + } + if ($skip) + continue; + + // NOTE(chris): sort for performance reasons + uksort($or_search, function ($a, $b) use ($allowed_codes_w_order) { + return $allowed_codes_w_order[$b] - $allowed_codes_w_order[$a]; + }); + + $or_with = []; + $or_select = []; + $or_prepare = []; + + if (substr(key($or_search), 0, 1) == '!') { + // NOTE(chris): only negative searchwords + $sql = []; + foreach ($or_search as $code => $words) { + $code = substr($code, 1); + // NOTE(chris): sort for performance reasons + usort($words, function ($a, $b) { + return strlen($b) - strlen($a); + }); + $field_config = $table_config['searchfields'][$code]; + + if (isset($field_config['prepare'])) { + if (is_array($field_config['prepare'])) + $or_with = array_merge($or_with, $field_config['prepare']); + else + $or_with[] = $field_config['prepare']; + $or_prepare[$code] = $field_config['prepare']; + unset($table_config['searchfields'][$code]['prepare']); + unset($field_config['prepare']); + } + $field_sql = " + SELECT + " . $table_config['table'] . "." . $table_config['primarykey'] . " + FROM " . $table_config['table'] . " + " . $this->_makeJoin($field_config['join']) . " + WHERE "; + // TODO(chris): equals and equal-int could be IN () statement??? + foreach ($words as $word) { + $sql[] = $field_sql . $this->_makeCompareBool($field_config['comparison'], $field_config['field'], $word); + } + } + + $or_select[] = " + SELECT + " . $table_config['primarykey'] . ", + 1.0 AS rank + FROM " . $table_config['table'] . " + WHERE prestudent_id NOT IN (" . implode(" UNION ", $sql) . ")"; + } else { + $current_select = false; + $count = 0; + $skip = false; + foreach ($or_search as $code => $words) { + // NOTE(chris): sort for performance reasons + if ($code && substr($code, 0, 1) == '!') { + usort($words, function ($a, $b) { + return strlen($a) - strlen($b); + }); + } else { + usort($words, function ($a, $b) { + return strlen($b) - strlen($a); + }); + } + if ($code == '') { + foreach ($words as $i => $word) { + $field_sql = []; + foreach ($table_config['searchfields'] as $c => $field_config) { + if (in_array($field_config['comparison'], $integer_functions) && !is_numeric($word)) + continue; + + $word_from = $table_config['table']; + $word_join = ""; + $word_rank = "0"; + if ($current_select) { + $word_from = $current_select; + if ($field_config['field'] != $table_config['primarykey']) { + $word_join .= " " . $this->_makeJoin($table_config); + } + $word_rank = "rank"; + } + if (isset($field_config['prepare'])) { + if (is_array($field_config['prepare'])) + $or_with = array_merge($or_with, $field_config['prepare']); + else + $or_with[] = $field_config['prepare']; + $or_prepare[$c] = $field_config['prepare']; + unset($table_config['searchfields'][$c]['prepare']); + unset($field_config['prepare']); + } + if (isset($field_config['join'])) { + $word_join .= " " . $this->_makeJoin($field_config['join']); + } + $field_sql[] = " + SELECT + " . $word_from . "." . $table_config['primarykey'] . ", + " . $word_rank . " AS w_rank, + " . $this->_makeRank($field_config['comparison'], $field_config['field'], $word) . " AS rank + FROM " . $word_from . " + " . $word_join . " + WHERE " . $this->_makeCompare($field_config['comparison'], $field_config['field'], $word); + } + // NOTE(chris): skip because the word is not numeric but all searchfields require integers + if (!$field_sql) { + $or_with = []; + $or_select = []; + $count = 0; + $skip = true; + foreach ($or_prepare as $k => $v) + $table_config['searchfields'][$k]['prepare'] = $v; + break; + } + + $id = "w" . ($id_offset + count($or_with)); + $or_with[] = " + " . $id . " (" . $table_config['primarykey'] . ", rank) AS ( + SELECT + " . $table_config['primarykey'] . ", + (w_rank + 1.0 - CASE " . + "WHEN MIN(rank) = 0 THEN 0 " . + "ELSE EXP(SUM(LN(CASE WHEN rank = 0 THEN 1 ELSE rank " . + "END))) END) AS rank + FROM (" . implode(' UNION ALL ', $field_sql) . ") " . $id . " + GROUP BY " . $table_config['primarykey'] . ", w_rank + )"; + $current_select = $id; + } + } else { + foreach ($words as $i => $word) { + $where = ""; + $rank = ""; + $jointype = ""; + if (substr($code, 0, 1) == '!') { + $c = substr($code, 1); + $field_config = $table_config['searchfields'][$c]; + + $rank = "1"; + + $jointype = "LEFT"; + + $where = $field_config['field'] . + " IS NULL OR NOT (" . + $this->_makeCompareBool( + $field_config['comparison'], + $field_config['field'], + $word + ) . + ")"; + if ($field_config['1-n'] ?? false) { + $where = "GROUP BY " . + $table_config['primarykey'] . + ", rank HAVING MIN(CASE WHEN " . + $where . + " THEN 1 ELSE 0 END) = 1"; + } else { + $where = "WHERE " . $where; + } + } else { + $field_config = $table_config['searchfields'][$code]; + + $rank = $this->_makeRank($field_config['comparison'], $field_config['field'], $word); + + $where = $this->_makeCompare($field_config['comparison'], $field_config['field'], $word); + $where = "WHERE " . $where; + } + $word_from = $table_config['table']; + $word_join = ""; + $word_rank = ""; + if ($current_select) { + $word_from = $current_select; + if ($field_config['field'] != $table_config['primarykey']) { + $word_join .= " " . $this->_makeJoin($table_config); + } + $word_rank = "rank + "; + } + if (isset($field_config['prepare'])) { + if (is_array($field_config['prepare'])) + $or_with = array_merge($or_with, $field_config['prepare']); + else + $or_with[] = $field_config['prepare']; + $or_prepare[$code] = $field_config['prepare']; + unset($table_config['searchfields'][$code]['prepare']); + unset($field_config['prepare']); + } + if (isset($field_config['join'])) { + $word_join .= " " . $this->_makeJoin($field_config['join'], $jointype); + } + + $id = "w" . ($id_offset + count($or_with)); + $or_with[] = " + " . $id . " (" . $table_config['primarykey'] . ", rank) AS ( + SELECT + " . $word_from . "." . $table_config['primarykey'] . ", + " . $word_rank . $rank . " AS rank + FROM " . $word_from . " + " . $word_join . " + " . $where . " + )"; + $current_select = $id; + } + } + if ($skip) + break; + $count += count($words); + } + + if (!$count || !$current_select) + continue; + + $or_select[] = " + SELECT " . $table_config['primarykey'] . ", rank / " . $count . " AS rank FROM " . $current_select; + } + + $sqlWith = array_merge($sqlWith, $or_with); + $sql_select = array_merge($sql_select, $or_select); + } + + return $sql_select; } //------------------------------------------------------------------------------------------------------------------ // Private methods - /** - * Checks: - * - The given searchstr is a not empty string - * - The given types is a not empty array and contains allowed search types - */ - private function _checkParameters($searchstr, $types) - { - // If searchstr is empty - if (isEmptyString($searchstr)) return error(self::ERROR_WRONG_SEARCHSTR); - - // If types is not an array or it is empty - if (isEmptyArray($types)) return error(self::ERROR_NO_TYPES); - - // If all the elements in types are allowed search types - if (!isEmptyArray(array_diff($types, self::ALLOWED_TYPES))) return error(self::ERROR_WRONG_TYPES); - - return success(); // The check is fine! - } - - /** - * Loops on types and perform the search of that type using searchstr - * Then it collects all the returned data into an array as property of an object - */ - private function _search($searchstr, $types) - { - // Object to be returned - $result = new stdClass(); - $result->data = array(); - - // For each search type - foreach ($types as $type) - { - // Perform the search and then add the result to data - $result->data = array_merge($result->data, $this->{'_'.$type}($searchstr, $type)); - } - - return $result; - } - - private function _mitarbeiter_ohne_zuordnung($searchstr, $type) - { - $dbModel = new DB_Model(); - - $sql = ' - SELECT - \''.$type.'\' AS type, - b.uid AS uid, - p.person_id AS person_id, - p.vorname || \' \' || p.nachname AS name, - ARRAY_AGG(DISTINCT(org.bezeichnung)) AS organisationunit_name, - COALESCE(b.alias, b.uid) || \''.'@'.DOMAIN.'\' AS email, - TRIM(COALESCE(k.kontakt, \'\') || \' \' || COALESCE(m.telefonklappe, \'\')) AS phone, - \''.base_url(self::PHOTO_IMG_URL).'\' || p.person_id AS photo_url, - ARRAY_AGG(DISTINCT(stdkst.bezeichnung)) AS standardkostenstelle - FROM public.tbl_mitarbeiter m - JOIN public.tbl_benutzer b ON(b.uid = m.mitarbeiter_uid) - LEFT JOIN ( - SELECT \'[\' || ot.bezeichnung || \'] \' || o.bezeichnung AS bezeichnung, bf.uid - FROM public.tbl_benutzerfunktion bf - JOIN public.tbl_organisationseinheit o USING(oe_kurzbz) - JOIN public.tbl_organisationseinheittyp ot USING(organisationseinheittyp_kurzbz) - WHERE bf.funktion_kurzbz = \'kstzuordnung\' - AND (bf.datum_von IS NULL OR bf.datum_von <= NOW()) - AND (bf.datum_bis IS NULL OR bf.datum_bis >= NOW()) - GROUP BY o.bezeichnung, ot.bezeichnung, bf.uid - ) stdkst ON stdkst.uid = b.uid - JOIN public.tbl_person p USING(person_id) - LEFT JOIN ( - SELECT \'[\' || ot.bezeichnung || \'] \' || o.bezeichnung AS bezeichnung, bf.uid - FROM public.tbl_benutzerfunktion bf - JOIN public.tbl_organisationseinheit o USING(oe_kurzbz) - JOIN public.tbl_organisationseinheittyp ot USING(organisationseinheittyp_kurzbz) - WHERE bf.funktion_kurzbz = \'oezuordnung\' - AND (bf.datum_von IS NULL OR bf.datum_von <= NOW()) - AND (bf.datum_bis IS NULL OR bf.datum_bis >= NOW()) - GROUP BY o.bezeichnung, ot.bezeichnung, bf.uid - ) org ON org.uid = b.uid - LEFT JOIN ( - SELECT kontakt, standort_id - FROM public.tbl_kontakt - WHERE kontakttyp = \'telefon\' - ) k ON(k.standort_id = m.standort_id) - WHERE - (stdkst.bezeichnung IS NULL - OR org.bezeichnung IS NULL) - AND ( - ' . - $this->buildSearchClause( - $dbModel, - array('b.uid', 'p.vorname', 'p.nachname'), - $searchstr - ) . - ' - ) - GROUP BY type, b.uid, p.person_id, name, email, m.telefonklappe, phone - '; - - $employees = $dbModel->execReadOnlyQuery($sql); - - // If something has been found then return it - if (hasData($employees)) return getData($employees); - - // Otherwise return an empty array - return array(); - } - - protected function buildSearchClause(DB_Model $dbModel, array $columns, $searchstr) - { - $document = implode(' || \' \' || ', $columns); - $query = '\'' . implode(':* & ', explode(' ', trim($searchstr))) . ':*\''; - $reversequery = '\'*:' . implode(' & *:', explode(' ', trim($searchstr))) . '\''; - $nospacequery = '\'' . implode('', explode(' ', trim($searchstr))) . ':*\''; - - $searchclause = <<execReadOnlyQuery(' - SELECT - \''.$type.'\' AS type, - b.uid AS uid, - p.person_id AS person_id, - p.vorname || \' \' || p.nachname AS name, - ARRAY_AGG(DISTINCT(org.bezeichnung)) AS organisationunit_name, - COALESCE(b.alias, b.uid) || \''.'@'.DOMAIN.'\' AS email, - TRIM(COALESCE(k.kontakt, \'\') || \' \' || COALESCE(m.telefonklappe, \'\')) AS phone, - \''.base_url(self::PHOTO_IMG_URL).'\' || p.person_id AS photo_url, - ARRAY_AGG(DISTINCT(stdkst.bezeichnung)) AS standardkostenstelle - FROM public.tbl_mitarbeiter m - JOIN public.tbl_benutzer b ON(b.uid = m.mitarbeiter_uid) - JOIN ( - SELECT \'[\' || ot.bezeichnung || \'] \' || o.bezeichnung AS bezeichnung, bf.uid - FROM public.tbl_benutzerfunktion bf - JOIN public.tbl_organisationseinheit o USING(oe_kurzbz) - JOIN public.tbl_organisationseinheittyp ot USING(organisationseinheittyp_kurzbz) - WHERE bf.funktion_kurzbz = \'kstzuordnung\' - AND (bf.datum_von IS NULL OR bf.datum_von <= NOW()) - AND (bf.datum_bis IS NULL OR bf.datum_bis >= NOW()) - GROUP BY o.bezeichnung, ot.bezeichnung, bf.uid - ) stdkst ON stdkst.uid = b.uid - JOIN public.tbl_person p USING(person_id) - JOIN ( - SELECT \'[\' || ot.bezeichnung || \'] \' || o.bezeichnung AS bezeichnung, bf.uid - FROM public.tbl_benutzerfunktion bf - JOIN public.tbl_organisationseinheit o USING(oe_kurzbz) - JOIN public.tbl_organisationseinheittyp ot USING(organisationseinheittyp_kurzbz) - WHERE bf.funktion_kurzbz = \'oezuordnung\' - AND (bf.datum_von IS NULL OR bf.datum_von <= NOW()) - AND (bf.datum_bis IS NULL OR bf.datum_bis >= NOW()) - GROUP BY o.bezeichnung, ot.bezeichnung, bf.uid - ) org ON org.uid = b.uid - LEFT JOIN ( - SELECT kontakt, standort_id - FROM public.tbl_kontakt - WHERE kontakttyp = \'telefon\' - ) k ON(k.standort_id = m.standort_id) - WHERE ' . - $this->buildSearchClause( - $dbModel, - array('b.uid', 'p.vorname', 'p.nachname', 'org.bezeichnung', 'stdkst.bezeichnung'), - $searchstr - ) . - ' - GROUP BY type, b.uid, p.person_id, name, email, m.telefonklappe, phone - '); - - // If something has been found then return it - if (hasData($employees)) return getData($employees); - - // Otherwise return an empty array - return array(); - } - - /** - * Seach for organisation units - */ - private function _organisationunit($searchstr, $type) - { - $dbModel = new DB_Model(); - - $ous = $dbModel->execReadOnlyQuery(' - SELECT - \''.$type.'\' AS type, - o.oe_kurzbz AS oe_kurzbz, - \'[\' || ot.bezeichnung || \'] \' || o.bezeichnung AS name, - oParent.oe_kurzbz AS parentoe_kurzbz, - (CASE WHEN oParent.bezeichnung IS NOT NULL THEN \'[\' || otParent.bezeichnung || \'] \' || oParent.bezeichnung END) AS parentoe_name, - ARRAY_AGG(DISTINCT(bfLeader.uid)) AS leader_uid, - ARRAY_AGG(DISTINCT(bfLeader.vorname || \' \' || bfLeader.nachname)) AS leader_name, - COUNT(bfCount.benutzerfunktion_id) AS number_of_people, - (CASE WHEN o.mailverteiler = TRUE THEN o.oe_kurzbz || \''.'@'.DOMAIN.'\' END) AS mailgroup - FROM public.tbl_organisationseinheit o - JOIN public.tbl_organisationseinheittyp ot USING(organisationseinheittyp_kurzbz) - LEFT JOIN public.tbl_organisationseinheit oParent ON(oParent.oe_kurzbz = o.oe_parent_kurzbz) - LEFT JOIN public.tbl_organisationseinheittyp otParent ON(oParent.organisationseinheittyp_kurzbz = otParent.organisationseinheittyp_kurzbz) - LEFT JOIN ( - SELECT benutzerfunktion_id, oe_kurzbz - FROM public.tbl_benutzerfunktion - WHERE funktion_kurzbz = \'oezuordnung\' - AND (datum_von IS NULL OR datum_von <= NOW()) - AND (datum_bis IS NULL OR datum_bis >= NOW()) - ) bfCount ON(bfCount.oe_kurzbz = o.oe_kurzbz) - LEFT JOIN ( - SELECT bf.oe_kurzbz, bf.uid, p.vorname, p.nachname - FROM public.tbl_benutzerfunktion bf - JOIN public.tbl_benutzer b USING(uid) - JOIN public.tbl_person p USING(person_id) - WHERE funktion_kurzbz = \'Leitung\' - AND (datum_von IS NULL OR datum_von <= NOW()) - AND (datum_bis IS NULL OR datum_bis >= NOW()) - AND b.aktiv = TRUE - ) bfLeader ON(bfLeader.oe_kurzbz = o.oe_kurzbz) - WHERE ' . - $this->buildSearchClause( - $dbModel, - array('o.oe_kurzbz', 'o.bezeichnung', 'ot.bezeichnung'), - $searchstr - ) . - ' - GROUP BY type, o.oe_kurzbz, o.bezeichnung, ot.bezeichnung, oParent.oe_kurzbz, oParent.bezeichnung, otParent.bezeichnung - '); - - // If something has been found - if (hasData($ous)) - { - // Loop through the returned dataset - foreach (getData($ous) as $ou) - { - // Create the new property leaders as an empty array - $ou->leaders = array(); - - // Loop through the found leaders for this organisation unit - for ($i = 0; $i < count($ou->leader_uid); $i++) - { - // If a leader exists for this organisationunit and has a name :D - if (!isEmptyString($ou->leader_uid[$i]) && !isEmptyString($ou->leader_name[$i])) - { - // Empty object that will contains the leader uid and name - $leader = new stdClass(); - // Set the properties name and uid - $leader->uid = $ou->leader_uid[$i]; - $leader->name = $ou->leader_name[$i]; - // Add the leader object to the leaders array - $ou->leaders[] = $leader; - } - } - - // Remove the not needed properties leader_uid and leader_name - unset($ou->leader_uid); - unset($ou->leader_name); - } - - // Returns the changed dataset - return getData($ous); - } - - // Otherwise return an empty array - return array(); - } - - /** - * Search for persons - */ - private function _person($searchstr, $type) - { - return array(); - } - - /** - * Search for students - */ - private function _student($searchstr, $type) - { - $dbModel = new DB_Model(); - - $students = $dbModel->execReadOnlyQuery(' - SELECT - \''.$type.'\' AS type, - s.student_uid AS uid, - s.matrikelnr, - p.person_id AS person_id, - p.vorname || \' \' || p.nachname AS name, - k.kontakt as email , - p.foto - FROM public.tbl_student s - JOIN public.tbl_benutzer b ON(b.uid = s.student_uid) - JOIN public.tbl_person p USING(person_id) - LEFT JOIN ( - SELECT kontakt, person_id - FROM public.tbl_kontakt - WHERE kontakttyp = \'email\' - ) as k USING(person_id) - WHERE b.uid ILIKE \'%'.$dbModel->escapeLike($searchstr).'%\' - OR p.vorname ILIKE \'%'.$dbModel->escapeLike($searchstr).'%\' - OR p.nachname ILIKE \'%'.$dbModel->escapeLike($searchstr).'%\' - GROUP BY type, s.student_uid, s.matrikelnr, p.person_id, name, email, p.foto - '); - - // If something has been found then return it - if (hasData($students)) return getData($students); - - // Otherwise return an empty array - return array(); - } - - /** - * Search for prestudents - */ - private function _prestudent($searchstr, $type) - { - $dbModel = new DB_Model(); - - $prestudent = $dbModel->execReadOnlyQuery(' - SELECT - \''.$type.'\' AS type, - ps.prestudent_id, - ps.studiengang_kz, - p.person_id AS person_id, - b.uid, - p.vorname || \' \' || p.nachname AS name, - ( - SELECT kontakt - FROM public.tbl_kontakt - WHERE kontakttyp = \'email\' - AND person_id = p.person_id - LIMIT 1 - ) as email, - p.foto, - sg.bezeichnung - FROM public.tbl_prestudent ps - LEFT JOIN public.tbl_student s USING (prestudent_id) - LEFT JOIN public.tbl_benutzer b ON (b.uid = s.student_uid) - JOIN public.tbl_person p ON (p.person_id = ps.person_id) - LEFT JOIN public.tbl_studiengang sg ON (sg.studiengang_kz = ps.studiengang_kz) - WHERE b.uid ILIKE \'%'.$dbModel->escapeLike($searchstr).'%\' - OR p.vorname ILIKE \'%'.$dbModel->escapeLike($searchstr).'%\' - OR p.nachname ILIKE \'%'.$dbModel->escapeLike($searchstr).'%\' - or cast(ps.prestudent_id as text) ILIKE \'%'.$dbModel->escapeLIKE($searchstr).'%\' - GROUP BY type, b.uid, ps.prestudent_id, ps.studiengang_kz, sg.bezeichnung, s.student_uid, s.matrikelnr, p.person_id, name, email, p.foto - '); - - // If something has been found then return it - if (hasData($prestudent)) return getData($prestudent); - - // Otherwise return an empty array - return array(); - } - /** * Search for documents */ @@ -456,5 +702,324 @@ EOSC; { return array(); } -} + /** + * Checks if an array has at least on non numeric value. + * + * @param array $arr + * + * @return boolean + */ + private function _hasAtLeastOneNaN($arr) + { + foreach ($arr as $value) + if (!is_numeric($value)) + return true; + return false; + } + + /** + * Helper function for getDynamicSearchSql + * + * @param array $join + * @param string $prefix + * + * @return string + */ + private function _makeJoin($join, $prefix = "") + { + if (!is_array($join)) + return ""; + if (!isset($join['table'])) { + $output = []; + foreach ($join as $j) + $output[] = trim($this->_makeJoin($j, $prefix)); + return implode(" ", $output); + } + if (!isset($join['on']) && !isset($join['using']) && !isset($join['primarykey'])) + return ""; + $output = $prefix . " JOIN " . $join['table']; + + if (isset($join['using'])) + return $output . " USING (" . $join['using'] . ")"; + + if (isset($join['primarykey'])) + return $output . " USING (" . $join['primarykey'] . ")"; + + return $output . " ON (" . $join['on'] . ")"; + } + + /** + * Helper function for _makeRank, _makeCompare and _makeCompareBool + * + * @param string $function + * @param string $mode + * @param string $field + * @param string $word + * + * @return string + */ + private function _makeFunction($function, $mode, $field, $word) + { + $searchfunction = $this->_ci->config->item($mode, 'searchfunctions'); + + if (!$searchfunction) + return ""; + $tpl = $searchfunction[$function] ?? ""; + + if (strstr($tpl, '{field}')) + $tpl = str_replace('{field}', $field, $tpl); + + if (strstr($tpl, '{word}')) + $tpl = str_replace('{word}', $this->_ci->db->escape($word), $tpl); + if (strstr($tpl, '{like:word}')) + $tpl = str_replace('{like:word}', "'%" . $this->_ci->db->escapeLike($word) . "%'", $tpl); + + return $tpl; + } + + /** + * Helper function for getDynamicSearchSql + * + * @param string $mode + * @param string $field + * @param string $word + * + * @return string + */ + private function _makeRank($mode, $field, $word) + { + return $this->_makeFunction('rank', $mode, $field, $word); + } + + /** + * Helper function for getDynamicSearchSql + * + * @param string $mode + * @param string $field + * @param string $word + * + * @return string + */ + private function _makeCompare($mode, $field, $word) + { + return $this->_makeFunction('compare', $mode, $field, $word); + } + + /** + * Helper function for getDynamicSearchSql + * + * @param string $mode + * @param string $field + * @param string $word + * + * @return string + */ + private function _makeCompareBool($mode, $field, $word) + { + $searchfunction = $this->_ci->config->item($mode, 'searchfunctions'); + + if (!$searchfunction) + return ""; + $function = isset($searchfunction['compare_boolean']) ? 'compare_boolean' : 'compare'; + return $this->_makeFunction($function, $mode, $field, $word); + } + + /** + * Converts the search string to an array. + * First level should be joined with an OR. + * Second level should be joined with an AND or AND NOT. + * It is an associative array where the key is a code for the field + * which the words should be compared with and the value is the array + * of words. + * Use AND NOT if the first letter in the key is "!". + * Use AND if the first letter in the key is not "!". + * E.g: + * If the key is: + * "": the words should be compared to all fields with AND. + * "!": the words should be compared to all fields with AND NOT. + * "somefield": the words should be compared to the field somefield with + * AND. + * "!somefield": the words should be compared to the field somefield with + * AND NOT. + * + * @param string $searchstring + * @param array $types + * + * @return array + */ + private function _convertQuery($searchstring, $types) + { + $searchAllTypes = count($types) == count($this->_ci->config->item('search')); + $allowedTypes = array_keys($types); + + $currentArray = []; + $outputArray = []; + $cleanStrings = []; + $cleanSearchstring = ''; + $filter = ['+' => [], '-' => []]; + $typeAliases = []; + + $tmp = explode(' ', strtolower($searchstring)); + while ($tmp) { + $chunk = trim(array_shift($tmp)); + if ($chunk == '') + continue; + + if (strpos($chunk, '"') !== false) { + $test = explode('"', $chunk); + if (count($test) > 2) { + $rest = implode('"', array_slice($test, 2)); + if ($rest) { + array_unshift($tmp, $rest); + $chunk = implode('"', array_slice($test, 0, 2)) . '"'; + } + } + if (count($test) == 2) { + while ($tmp && strpos($test[1], '"') === false) { + $test[1] .= ' ' . trim(array_shift($tmp)); + } + if (strpos($test[1], '"') === false) { + $chunk = implode('"', $test) . '"'; + } else { + $test2 = explode('"', $test[1], 2); + $chunk = $test[0] . '"' . $test2[0] . '"'; + if ($test2[1]) { + array_unshift($tmp, $test2[1]); + } + } + } + if (strpos($chunk, ' ') === false) { + $chunk = str_replace('"', '', $chunk); + } + } + + if ($chunk == 'or') { + $this->_convertQueryCleanupOr($currentArray, $cleanStrings, $filter, $searchAllTypes, $allowedTypes); + $filter = ['+' => [], '-' => []]; + if ($currentArray) { + $cleanSearchstring .= ($cleanSearchstring ? ' or ' : '') . implode(' ', $cleanStrings); + $cleanStrings = []; + $outputArray[] = $currentArray; + $currentArray = []; + } + continue; + } + + if ($chunk == ':' || $chunk == '-' || substr($chunk, -1) == ':') + continue; + + if ($chunk[0] == ':' || ($chunk[0] == '-' && $chunk[1] == ':')) { + if (!$typeAliases) { + foreach ($types as $type => $config) { + $typeAliases[$type] = $type; + if (isset($config['alias'])) { + foreach ($config['alias'] as $alias) { + if (!isset($typeAliases[$alias])) + $typeAliases[$alias] = $type; + } + } + } + } + + $test = explode(':', $chunk, 2); + if (isset($typeAliases[$test[1]])) + $chunk = $test[0] . ':' . $typeAliases[$test[1]]; + elseif ($test[0] == '-') + continue; + } + + if (in_array($chunk, $cleanStrings)) + continue; + + $cleanStrings[] = $chunk; + + $chunk = str_replace('"', '', $chunk); + $code = ''; + + if ($chunk[0] == '-') { + $code = '!'; + $chunk = substr($chunk, 1); + } + if (strpos($chunk, ':') !== false) { + $chunk = explode(':', $chunk, 2); + if (!$chunk[0]) { + $filter[$code ? '-' : '+'][] = $chunk[1]; + continue; + } + $code .= $chunk[0]; + $chunk = $chunk[1]; + } + + if (!isset($currentArray[$code])) + $currentArray[$code] = []; + + $currentArray[$code][] = $chunk; + } + + $this->_convertQueryCleanupOr($currentArray, $cleanStrings, $filter, $searchAllTypes, $allowedTypes); + if ($currentArray) { + $cleanSearchstring .= ($cleanSearchstring ? ' or ' : '') . implode(' ', $cleanStrings); + $outputArray[] = $currentArray; + } + return [$outputArray, $cleanSearchstring]; + } + + private function _convertQueryCleanupOr(&$currentArray, &$cleanStrings, $filter, $searchAllTypes, $allowedTypes) + { + if ($filter['+'] && $filter['-']) { + $double = array_intersect($filter['+'], $filter['-']); + if ($double) { + foreach ($double as $type) { + array_splice($cleanStrings, array_search(':' . $type, $cleanStrings), 1); + array_splice($cleanStrings, array_search('-:' . $type, $cleanStrings), 1); + } + $filter['+'] = array_diff($filter['+'], $double); + $filter['-'] = array_diff($filter['-'], $double); + } + if (!$filter['+'] && !$filter['-']) { + // All filters cancel each other out + $currentArray = []; + $cleanStrings = []; + return; + } + if ($filter['+']) { + foreach ($filter['-'] as $type) { + array_splice($cleanStrings, array_search('-:' . $type, $cleanStrings), 1); + } + $filter['-'] = []; + } + } + if ($filter['+']) { + $cleanFilter = array_intersect($allowedTypes, $filter['+']); + if (!$cleanFilter) { + // All filters are forbidden + $currentArray = []; + $cleanStrings = []; + return; + } + $forbiddenFilter = array_diff($cleanFilter, $filter['+']); + foreach ($forbiddenFilter as $type) { + array_splice($cleanStrings, array_search(':' . $type, $cleanStrings), 1); + } + $filter['+'] = $cleanFilter; + } elseif ($filter['-']) { + $filter['+'] = array_diff($allowedTypes, $filter['-']); + if (!$searchAllTypes) { + foreach ($filter['+'] as $type) + $cleanStrings[] = ':' . $type; + foreach ($filter['-'] as $type) + array_splice($cleanStrings, array_search('-:' . $type, $cleanStrings), 1); + } + } else { + if (!$searchAllTypes) { + foreach ($allowedTypes as $type) + $cleanStrings[] = ':' . $type; + } + } + + if ($filter['+']) { + $currentArray['-filter'] = $filter['+']; + } + } +} diff --git a/public/css/components/searchbar.css b/public/css/components/searchbar.css index 2bbaf9d67..61032d227 100644 --- a/public/css/components/searchbar.css +++ b/public/css/components/searchbar.css @@ -92,4 +92,42 @@ .searchbar_inline_ul li { list-style: none; +} + +/* new variant with template/frame */ + +.searchbar-result { + border-bottom: 1px solid lightgrey; + margin-bottom: 1rem; + padding-bottom: 1rem; +} + +.searchbar-actions { + display: flex; + flex-wrap: wrap; + gap: .5rem; + padding: 0; + margin: 1rem 0 0; +} + +.searchbar-square-image { + position: relative; + display: block; + height: 0; + padding-bottom: 100%; +} + +.searchbar-square-image > * { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; +} +.searchbar-square-image img { + object-fit: cover; +} + +.no-margin-paragraphs p { + margin: 0; } \ No newline at end of file diff --git a/public/js/api/search.js b/public/js/api/search.js index 4655d8fa8..c6a5d77a5 100644 --- a/public/js/api/search.js +++ b/public/js/api/search.js @@ -16,9 +16,9 @@ */ export default { - search(searchsettings) { + search(searchsettings, config) { const url = '/api/frontend/v1/searchbar/search'; - return this.$fhcApi.post(url, searchsettings); + return this.$fhcApi.post(url, searchsettings, config); }, searchdummy(searchsettings) { const url = 'public/js/apps/api/dummyapi.php/Search'; diff --git a/public/js/components/searchbar/result/employee.js b/public/js/components/searchbar/result/employee.js new file mode 100644 index 000000000..1c4da075a --- /dev/null +++ b/public/js/components/searchbar/result/employee.js @@ -0,0 +1,59 @@ +import TemplateFrame from "./template/frame.js"; + +export default { + components: { + TemplateFrame + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + template: ` + +
+
+
Standard-Kostenstelle
+
+
    +
  • {{ stdkst }}
  • +
+ keine +
+
+
+
Organisations-Einheit
+
+
    +
  • {{ oe }}
  • +
+ keine +
+
+
+
EMails
+ +
+
+
Telefon
+ +
+
+
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/mergedperson.js b/public/js/components/searchbar/result/mergedperson.js new file mode 100644 index 000000000..f6b07f3d2 --- /dev/null +++ b/public/js/components/searchbar/result/mergedperson.js @@ -0,0 +1,166 @@ +import TemplateFrame from "./template/frame.js"; +import TemplateAction from "./template/action.js"; + +export default { + components: { + TemplateFrame, + TemplateAction + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + computed: { + telurl() { + return 'tel:' + this.employee?.phone; + }, + person() { + const person = this.res.list.filter(item => item.type == 'person'); + if (person.length) + return person.pop(); + + const { person_id, name, foto, photo_url, email } = this.res.list[0]; + return { person_id, name, foto, photo_url, email }; + }, + employee() { + const ma = this.res.list.filter(item => [ + 'employee', + 'unassigned_employee' + ].includes(item.type)); + return ma.length ? ma.pop() : null; + }, + students() { + const students = this.res.list.filter(item => item.type == 'prestudent'); + return students.length ? students : null; + }, + foto() { + if (this.person.foto) + return 'data:image/jpeg;base64,' + this.person.foto; + return this.person.photo_url; + }, + emails() { + if (Array.isArray(this.person.email)) + return this.person.email; + return [this.person.email]; + } + }, + template: ` + +
+
+
Person ID
+
+ {{ person.person_id }} +
+
+
+
EMails
+ +
+ + + + +
+
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/mergedstudent.js b/public/js/components/searchbar/result/mergedstudent.js new file mode 100644 index 000000000..aa5d7739f --- /dev/null +++ b/public/js/components/searchbar/result/mergedstudent.js @@ -0,0 +1,35 @@ +import ResultPrestudent from "./prestudent.js"; +import ResultStudent from "./student.js"; + +export default { + components: { + ResultPrestudent, + ResultStudent + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + computed: { + prestudent() { + const prestudent = this.res.list.filter(item => item.type == 'prestudent'); + return prestudent.pop(); + } + }, + template: ` + + ` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/organisationunit.js b/public/js/components/searchbar/result/organisationunit.js new file mode 100644 index 000000000..f9b6870e0 --- /dev/null +++ b/public/js/components/searchbar/result/organisationunit.js @@ -0,0 +1,63 @@ +import TemplateFrame from "./template/frame.js"; + +export default { + components: { + TemplateFrame + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + computed: { + foto() { + if (this.res.foto) + return 'data:image/jpeg;base64,' + this.res.foto; + return null; + } + }, + template: ` + +
+
+
übergeordnete OrgEinheit
+
+ {{ res.parentoe_name }} +
+
+ +
+
Gruppen-EMail
+ +
+ +
+
Leiter
+
+
    +
  • {{ leader.name }}
  • +
+ N.N. +
+
+ +
+
Mitarbeiter-Anzahl
+
+ {{ res.number_of_people }} +
+
+
+
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/person.js b/public/js/components/searchbar/result/person.js new file mode 100644 index 000000000..f8adee56e --- /dev/null +++ b/public/js/components/searchbar/result/person.js @@ -0,0 +1,46 @@ +import TemplateFrame from "./template/frame.js"; + +export default { + components: { + TemplateFrame + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + computed: { + foto() { + if (this.res.foto) + return 'data:image/jpeg;base64,' + this.res.foto; + return null; + } + }, + template: ` + +
+
+
Person ID
+
+ {{ res.person_id }} +
+
+
+
EMails
+ +
+
+
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/prestudent.js b/public/js/components/searchbar/result/prestudent.js new file mode 100644 index 000000000..8c174d3c0 --- /dev/null +++ b/public/js/components/searchbar/result/prestudent.js @@ -0,0 +1,70 @@ +import TemplateFrame from "./template/frame.js"; + +export default { + components: { + TemplateFrame + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + computed: { + foto() { + if (this.res.foto) + return 'data:image/jpeg;base64,' + this.res.foto; + return null; + } + }, + template: ` + +
+
+
Person ID
+
+ {{ res.person_id }} +
+
+
+
EMails
+ +
+
+
Student UID
+
+ {{ res.uid }} +
+
+
+
Matrikelnummer
+
+ {{ res.matrikelnr }} +
+
+
+
Prestudent ID
+
+ {{ res.prestudent_id }} +
+
+
+
Studiengang
+
+ {{ res.bezeichnung }} +
+
+
+
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/room.js b/public/js/components/searchbar/result/room.js new file mode 100644 index 000000000..6da1e970a --- /dev/null +++ b/public/js/components/searchbar/result/room.js @@ -0,0 +1,74 @@ +import TemplateFrame from "./template/frame.js"; + +export default { + components: { + TemplateFrame + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + computed: { + equipment() { + if (!this.res.equipment) + return ""; + return this.res.equipment.replace(new RegExp('
', 'ig'), ''); + }, + address() { + let address = this.res.zip || ''; + if (this.res.city) + address += (address ? ' ' : '') + this.res.city; + if (this.res.street) + address += (address ? ', ' : '') + this.res.street; + if (this.res.floor) + address += (address ? ' / ' : '') + this.res.floor + ' Stockwerk'; + + return address || 'N/A'; + } + }, + template: ` + +
+
+
Standort
+
+ {{ address }} +
+
+ +
+
Sitzplätze
+
+ + +
+
+ +
+
Gebäude
+
+ {{ res.building }} +
+
+ +
+
Zusatz Informationen
+
+
+
+
+
+
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/student.js b/public/js/components/searchbar/result/student.js new file mode 100644 index 000000000..8d8c4894b --- /dev/null +++ b/public/js/components/searchbar/result/student.js @@ -0,0 +1,58 @@ +import TemplateFrame from "./template/frame.js"; + +export default { + components: { + TemplateFrame + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + computed: { + foto() { + if (this.res.foto) + return 'data:image/jpeg;base64,' + this.res.foto; + return null; + } + }, + template: ` + +
+
+
Student UID
+
+ {{ res.uid }} +
+
+
+
Person ID
+
+ {{ res.person_id }} +
+
+
+
Matrikelnummer
+
+ {{ res.matrikelnr }} +
+
+
+
EMails
+ +
+
+
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/template/action.js b/public/js/components/searchbar/result/template/action.js new file mode 100644 index 000000000..a7654df59 --- /dev/null +++ b/public/js/components/searchbar/result/template/action.js @@ -0,0 +1,28 @@ +export default { + emits: [ 'actionexecuted' ], + props: { + res: Object, + action: Object + }, + computed: { + actionHref() { + if (this.action.type !== 'link') + return 'javascript:void(0);'; + return typeof this.action.action === 'function' + ? this.action.action(this.res) + : this.action.action; + } + }, + methods: { + actionFunc() { + if (this.action.type !== 'function') + return; + this.action.action(this.res); + this.$emit('actionexecuted'); + } + }, + template: ` + + Action + ` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/template/actions.js b/public/js/components/searchbar/result/template/actions.js new file mode 100644 index 000000000..5849594ed --- /dev/null +++ b/public/js/components/searchbar/result/template/actions.js @@ -0,0 +1,26 @@ +import ResultAction from "./action.js"; + +export default { + components: { + ResultAction + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Array + }, + template: ` +
+ + + {{ action.label }} + +
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/template/frame.js b/public/js/components/searchbar/result/template/frame.js new file mode 100644 index 000000000..87078a166 --- /dev/null +++ b/public/js/components/searchbar/result/template/frame.js @@ -0,0 +1,59 @@ +import ResultAction from "./action.js"; +import ResultActions from "./actions.js"; + +export default { + components: { + ResultAction, + ResultActions + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object, + title: String, + image: String, + imageFallback: String + }, + template: ` +
+
+
+ + +
+ +
+
+
+ +
+ + {{ title }} + + + + + + +
+
+
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/searchbar.js b/public/js/components/searchbar/searchbar.js index d23ab2226..54049679e 100644 --- a/public/js/components/searchbar/searchbar.js +++ b/public/js/components/searchbar/searchbar.js @@ -1,168 +1,282 @@ -import person from "./person.js"; -import raum from "./raum.js"; -import employee from "./employee.js"; -import organisationunit from "./organisationunit.js"; -import student from "./student.js"; -import prestudent from "./prestudent.js"; +import ResultPerson from "./result/person.js"; +import ResultStudent from "./result/student.js"; +import ResultPrestudent from "./result/prestudent.js"; +import ResultEmployee from "./result/employee.js"; +import ResultOrganisationunit from "./result/organisationunit.js"; +import ResultRoom from "./result/room.js"; +import ResultMergedperson from "./result/mergedperson.js"; +import ResultMergedstudent from "./result/mergedstudent.js"; + +// TODO(chris): arrays in results export default { - props: [ "searchoptions", "searchfunction" ], - data: function() { - return { - searchtimer: null, - hidetimer: null, - showsettings: false, - searchsettings: { - searchstr: '', - types: [] - }, - showresult: false, - searchresult: [], - searching: false, - error: null - }; - }, - components: { - person: person, - raum: raum, - employee: employee, - organisationunit: organisationunit, - student: student, - prestudent: prestudent - }, - template: ` -
-
- - -
- -
-
- -
-
{{ this.error }}
-
Es wurden keine Ergebnisse gefunden.
- -
-
-
- -
-
- -
- -
- `, - beforeMount: function() { - this.updateSearchOptions(); - }, - methods: { - updateSearchOptions: function() { - this.searchsettings.types = []; - for( const idx in this.searchoptions.types ) { - this.searchsettings.types.push(this.searchoptions.types[idx]); - } - }, - calcSearchResultExtent: function() { - var rect = this.$refs.searchbox.getBoundingClientRect(); + components: { + ResultPerson, + ResultStudent, + ResultPrestudent, + ResultEmployee, + ResultOrganisationunit, + ResultRoom, + ResultMergedperson, + ResultMergedstudent + }, + props: [ "searchoptions", "searchfunction" ], + data() { + return { + searchtimer: null, + hidetimer: null, + showsettings: false, + searchsettings: { + searchstr: '', + types: [] + }, + showresult: false, + searchresult: [], + searching: false, + error: null, + abortController: null, + retry: 5 + }; + }, + beforeMount() { + this.updateSearchOptions(); + }, + methods: { + updateSearchOptions() { + this.searchsettings.types = []; + for (const idx in this.searchoptions.types) { + this.searchsettings.types.push(this.searchoptions.types[idx]); + } + }, + calcSearchResultExtent() { + var rect = this.$refs.searchbox.getBoundingClientRect(); //console.log(window.innerWidth + ' ' + window.innerHeight + ' ' + JSON.stringify(rect)); - this.$refs.result.style.height = Math.floor(window.innerHeight * 0.80) + 'px'; - }, - search: function() { - if( this.searchtimer !== null ) { - clearTimeout(this.searchtimer); - } - if( this.searchsettings.searchstr.length >= 2 ) { - this.calcSearchResultExtent(); - this.searchtimer = setTimeout( - this.callsearchapi, - 500 - ); - } else { - this.showresult = false; - } - }, - callsearchapi: function() { - var that = this; - this.error = null; - this.searchresult = []; - this.searching = true; - this.showsearchresult(); - this.searchfunction(this.searchsettings) - .then(function(response) { - if( response.data?.error === 1 ) { - that.error = 'Bei der Suche ist ein Fehler aufgetreten.'; - } else { - that.searchresult = response.data.data; - } - }) - .catch(function(error) { - that.error = 'Bei der Suche ist ein Fehler aufgetreten.' - + ' ' + error.message; - }) - .finally(function() { - that.searching = false; - }); - }, - refreshsearch: function() { - this.search(); - this.togglesettings(); - }, - calcSearchSettingsExtent: function() { - var rect = this.$refs.settingsbutton.getBoundingClientRect(); + this.$refs.result.style.height = Math.floor(window.innerHeight * 0.80) + 'px'; + }, + search() { + if (this.searchtimer !== null) { + clearTimeout(this.searchtimer); + } + if (this.abortController) { + this.abortController.abort(); + this.abortController = null; + } + if (this.searchsettings.searchstr.length >= 2) { + this.calcSearchResultExtent(); + this.searchtimer = setTimeout( + this.callsearchapi, + 500 + ); + } else { + this.showresult = false; + } + }, + callsearchapi() { + this.error = null; + this.searchresult = []; + this.searching = true; + this.showsearchresult(); + + if (this.abortController) + this.abortController.abort(); + this.abortController = new AbortController(); + + this + .searchfunction(this.searchsettings, { signal: this.abortController.signal }) + .then(response => { + if (!response.data) { + this.error = 'Bei der Suche ist ein Fehler aufgetreten.'; + } else { + let res = response.data.map(el => ({...el, ...JSON.parse(el.data)})); + if (this.searchoptions.mergeResults) { + let counter = 0; + let mergeTypes = []; + let mergedType = 'merged'; + let mergeKey = ''; + + switch (this.searchoptions.mergeResults) { + case 'student': + mergeTypes = ['student', 'prestudent']; + mergedType += this.searchoptions.mergeResults; + mergeKey = 'uid'; + break; + case 'person': + mergeTypes = ['person', 'employee', 'unassigned_employee', 'mitarbeiter', 'mitarbeiter_ohne_zuordnung', 'student', 'prestudent']; + mergedType += this.searchoptions.mergeResults; + mergeKey = 'person_id'; + break; + } + + if (mergeTypes.length) { + res = Object.values(res.reduce((a, c) => { + if (!mergeTypes.includes(c.type)) { + a['nomerge' + counter++] = c; + } else if (c[mergeKey] === null) { + a['nomerge' + counter++] = c; + } else if (a[c[mergeKey]] === undefined) { + a[c[mergeKey]] = { + rank: c.rank, + type: mergedType, + list: [c] + }; + } else { + a[c[mergeKey]].list.push(c); + if (c.rank > a[c[mergeKey]].rank) + a[c[mergeKey]].rank = c.rank; + } + return a; + }, {})).sort((a, b) => b.rank - a.rank); + } + } + this.searchresult = res; + } + this.searching = false; + this.retry = 5; + }) + .catch(error => { + if (error.code == "ERR_CANCELED") { + return this.retry = 5; + } + if (error.code == "ECONNABORTED" && this.retry) { + this.retry--; + return this.callsearchapi(); + } + + this.error = 'Bei der Suche ist ein Fehler aufgetreten.' + ' ' + error.message; + this.searching = false; + this.retry = 5; + }); + }, + refreshsearch() { + this.search(); + this.togglesettings(); + }, + calcSearchSettingsExtent() { + var rect = this.$refs.settingsbutton.getBoundingClientRect(); //console.log(window.innerWidth + ' ' + window.innerHeight + ' ' + JSON.stringify(rect)); - this.$refs.settings.style.top = Math.floor(rect.bottom + 3) + 'px'; - this.$refs.settings.style.right = Math.floor(window.innerWidth - rect.right) + 'px'; - this.$refs.settings.style.width = Math.floor(window.innerWidth * 0.5) + 'px'; + this.$refs.settings.style.top = Math.floor(rect.bottom + 3) + 'px'; + this.$refs.settings.style.right = Math.floor(window.innerWidth - rect.right) + 'px'; + this.$refs.settings.style.width = Math.floor(window.innerWidth * 0.5) + 'px'; //this.$refs.settings.style.height = Math.floor(window.innerHeight * 0.5) + 'px'; - }, - togglesettings: function() { - this.showsettings = !this.showsettings; - this.calcSearchSettingsExtent(); - }, - hideresult: function() { - this.showresult = false; - window.removeEventListener('resize', this.calcSearchResultExtent); - }, - showsearchresult: function() { - if( this.searchsettings.searchstr.length >= 3 ) { - this.showresult = true; - window.addEventListener('resize', this.calcSearchResultExtent); - } - }, - searchfocusin: function(e) { - e.preventDefault(); - e.stopPropagation(); - if( this.hidetimer !== null ) { - clearTimeout(this.hidetimer); - } - }, - searchfocusout: function(e) { - e.preventDefault(); - e.stopPropagation(); - this.hidetimer = setTimeout( - this.hideresult, - 100 - ); - } - } + }, + togglesettings() { + this.showsettings = !this.showsettings; + this.calcSearchSettingsExtent(); + }, + hideresult() { + this.showresult = false; + window.removeEventListener('resize', this.calcSearchResultExtent); + }, + showsearchresult() { + if (this.searchsettings.searchstr.length >= 3) { + this.showresult = true; + window.addEventListener('resize', this.calcSearchResultExtent); + } + }, + searchfocusin(e) { + e.preventDefault(); + e.stopPropagation(); + if (this.hidetimer !== null) { + clearTimeout(this.hidetimer); + } + }, + searchfocusout(e) { + e.preventDefault(); + e.stopPropagation(); + this.hidetimer = setTimeout( + this.hideresult, + 100 + ); + } + }, + template: ` +
+
+ + +
+ +
+
+ +
+
{{ error }}
+
Es wurden keine Ergebnisse gefunden.
+ +
+
+
+ +
+
+ +
+
` }; diff --git a/public/js/plugin/FhcApi.js b/public/js/plugin/FhcApi.js index 7be1a5b9f..dbf5069b8 100644 --- a/public/js/plugin/FhcApi.js +++ b/public/js/plugin/FhcApi.js @@ -40,6 +40,8 @@ export default { function _clean_return_value(response) { const result = response.data; delete response.data; + if (!result) + return {meta: {response}, data: null}; if (!result.meta) result.meta = {response}; else diff --git a/system/dbupdate_3.4.php b/system/dbupdate_3.4.php index 11880fd55..9bdccecf8 100644 --- a/system/dbupdate_3.4.php +++ b/system/dbupdate_3.4.php @@ -58,6 +58,7 @@ require_once('dbupdate_3.4/17513_Entwicklungsteam.php'); require_once('dbupdate_3.4/28575_softwarebereitstellung.php'); require_once('dbupdate_3.4/41150_oe-pfad_db_view.php'); require_once('dbupdate_3.4/44031_stv_favorites.php'); +require_once('dbupdate_3.4/40128_search.php'); // *** Pruefung und hinzufuegen der neuen Attribute und Tabellen echo '

Pruefe Tabellen und Attribute!

'; diff --git a/system/dbupdate_3.4/40128_search.php b/system/dbupdate_3.4/40128_search.php new file mode 100644 index 000000000..2ef8f8b4a --- /dev/null +++ b/system/dbupdate_3.4/40128_search.php @@ -0,0 +1,171 @@ +db_num_rows(@$db->db_query("SELECT 1 +FROM pg_extension WHERE extname = 'pg_trgm' LIMIT 1;"))) +{ + $qry = "CREATE extension pg_trgm;"; + + if (!$db->db_query($qry)) + echo 'Module pg_trgm ' . $db->db_last_error() . '
'; + else + echo 'Module pg_trgm: activated
'; +} + + +// Add additional computed columns +// Add column fts_bezeichnung to public.tbl_organisationseinheit +if (!@$db->db_query("SELECT fts_bezeichnung FROM public.tbl_organisationseinheit LIMIT 1")) +{ + $qry = "ALTER TABLE public.tbl_organisationseinheit ADD COLUMN fts_bezeichnung tsvector;"; + $qry .= "COMMENT ON COLUMN public.tbl_organisationseinheit.fts_bezeichnung IS 'used for search - auto generated w triggers';"; + + if (!$db->db_query($qry)) + echo ' public.tbl_organisationseinheit ' . $db->db_last_error() . '
'; + else + echo 'public.tbl_organisationseinheit: new column "fts_bezeichnung" added
'; +} + +// Add function tr_update_tbl_organisationseinheit_fts_bezeichnung to public +if (!$db->db_num_rows(@$db->db_query("SELECT 1 FROM pg_proc WHERE proname = 'tr_update_tbl_organisationseinheit_fts_bezeichnung' LIMIT 1;"))) +{ + $qry = "CREATE FUNCTION tr_update_tbl_organisationseinheit_fts_bezeichnung() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS + $$ + BEGIN + IF TG_TABLE_NAME = 'tbl_organisationseinheit' THEN + NEW.fts_bezeichnung := to_tsvector('simple', COALESCE((SELECT bezeichnung FROM public.tbl_organisationseinheittyp WHERE organisationseinheittyp_kurzbz = NEW.organisationseinheittyp_kurzbz), '') || ' ' || COALESCE(NEW.bezeichnung, '')); + ELSIF TG_TABLE_NAME = 'tbl_organisationseinheittyp' THEN + UPDATE public.tbl_organisationseinheit SET fts_bezeichnung = to_tsvector('simple', COALESCE(NEW.bezeichnung, '') || ' ' || COALESCE(bezeichnung, '')) WHERE organisationseinheittyp_kurzbz = NEW.organisationseinheittyp_kurzbz; + END IF; + RETURN NEW; + END; + $$"; + + if (!$db->db_query($qry)) + echo ' public.tr_update_tbl_organisationseinheit_fts_bezeichnung ' . $db->db_last_error() . '
'; + else + echo 'public.tr_update_tbl_organisationseinheit_fts_bezeichnung: function created
'; +} + +$update_column = false; +// Add trigger tr_organisationseinheit_update_organisationseinheittyp_kurzbz to public.tbl_organisationseinheit +if (!$db->db_num_rows(@$db->db_query("SELECT 1 FROM information_schema.triggers WHERE event_object_table ='tbl_organisationseinheit' AND trigger_name = 'tr_organisationseinheit_update_organisationseinheittyp_kurzbz' LIMIT 1;"))) +{ + $qry = "CREATE TRIGGER tr_organisationseinheit_update_organisationseinheittyp_kurzbz + BEFORE UPDATE OF organisationseinheittyp_kurzbz OR INSERT + ON public.tbl_organisationseinheit + FOR EACH ROW + EXECUTE FUNCTION tr_update_tbl_organisationseinheit_fts_bezeichnung();"; + + if (!$db->db_query($qry)) + echo ' public.tbl_organisationseinheit ' . $db->db_last_error() . '
'; + else { + echo 'public.tbl_organisationseinheit: trigger "tr_organisationseinheit_update_organisationseinheittyp_kurzbz" created
'; + $update_column = true; + } +} + +// Add trigger tr_organisationseinheittyp_update_bezeichnung to public.tbl_organisationseinheittyp +if (!$db->db_num_rows(@$db->db_query("SELECT 1 FROM information_schema.triggers WHERE event_object_table ='tbl_organisationseinheittyp' AND trigger_name = 'tr_organisationseinheittyp_update_bezeichnung' LIMIT 1;"))) +{ + $qry = "CREATE TRIGGER tr_organisationseinheittyp_update_bezeichnung + BEFORE UPDATE OF bezeichnung + ON public.tbl_organisationseinheittyp + FOR EACH ROW + EXECUTE FUNCTION tr_update_tbl_organisationseinheit_fts_bezeichnung();"; + + if (!$db->db_query($qry)) + echo ' public.tbl_organisationseinheittyp ' . $db->db_last_error() . '
'; + else { + echo 'public.tbl_organisationseinheittyp: trigger "tr_organisationseinheittyp_update_bezeichnung" created
'; + $update_column = true; + } +} + +// Update fts_bezeichnung on tbl_organisationseinheit with new triggers +if ($update_column) +{ + $qry = "UPDATE public.tbl_organisationseinheittyp SET bezeichnung = bezeichnung;"; + + if (!$db->db_query($qry)) + echo ' public.tbl_organisationseinheit ' . $db->db_last_error() . '
'; + else + echo 'public.tbl_organisationseinheit: column "fts_bezeichnung" updated
'; +} + + +// Add Trigram Indexes +// Add index for kontakt to public.tbl_kontakt +if ($result = @$db->db_query("SELECT 1 +FROM pg_indexes WHERE indexname = 'idx_tbl_kontakt_kontakt_trgm';")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = "CREATE INDEX idx_tbl_kontakt_kontakt_trgm ON public.tbl_kontakt USING GIN (COALESCE(kontakt, '') gin_trgm_ops);"; + + if (!$db->db_query($qry)) + echo 'public.tbl_kontakt ' . $db->db_last_error() . '
'; + else + echo 'public.tbl_kontakt: added index "idx_tbl_kontakt_kontakt_trgm"
'; + } +} +// Add index for vorname to public.tbl_person +if ($result = @$db->db_query("SELECT 1 +FROM pg_indexes WHERE indexname = 'idx_tbl_person_vorname_trgm';")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = "CREATE INDEX idx_tbl_person_vorname_trgm ON public.tbl_person USING GIN (COALESCE(vorname, '') gin_trgm_ops);"; + + if (!$db->db_query($qry)) + echo 'public.tbl_person ' . $db->db_last_error() . '
'; + else + echo 'public.tbl_person: added index "idx_tbl_person_vorname_trgm"
'; + } +} +// Add index for nachname to public.tbl_person +if ($result = @$db->db_query("SELECT 1 +FROM pg_indexes WHERE indexname = 'idx_tbl_person_nachname_trgm';")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = "CREATE INDEX idx_tbl_person_nachname_trgm ON public.tbl_person USING GIN (COALESCE(nachname, '') gin_trgm_ops);"; + + if (!$db->db_query($qry)) + echo 'public.tbl_person ' . $db->db_last_error() . '
'; + else + echo 'public.tbl_person: added index "idx_tbl_person_nachname_trgm"
'; + } +} +// Add index for vorname || ' ' || nachname to public.tbl_person +if ($result = @$db->db_query("SELECT 1 +FROM pg_indexes WHERE indexname = 'idx_tbl_person_name_trgm';")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = "CREATE INDEX idx_tbl_person_name_trgm ON public.tbl_person USING GIN (COALESCE((vorname || ' ' || nachname), '') gin_trgm_ops);"; + + if (!$db->db_query($qry)) + echo 'public.tbl_person ' . $db->db_last_error() . '
'; + else + echo 'public.tbl_person: added index "idx_tbl_person_name_trgm"
'; + } +} + + +// Add Vector Indexes +// Add index for fts_bezeichnung to public.tbl_organisationseinheit +if (!$db->db_num_rows(@$db->db_query("SELECT 1 +FROM pg_indexes WHERE indexname = 'idx_tbl_organisationseinheit_fts_bezeichnung_vector' LIMIT 1;"))) +{ + $qry = "CREATE INDEX idx_tbl_organisationseinheit_fts_bezeichnung_vector ON public.tbl_organisationseinheit USING GIN (fts_bezeichnung);"; + + if (!$db->db_query($qry)) + echo 'public.tbl_organisationseinheit ' . $db->db_last_error() . '
'; + else + echo 'public.tbl_organisationseinheit: added index "idx_tbl_organisationseinheit_fts_bezeichnung_vector"
'; +} From 7174f9cbe08950efb65cf2acd295b41f808f374f Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Fri, 20 Sep 2024 14:38:33 +0200 Subject: [PATCH 0004/1216] Use Merged Person --- public/js/components/Stv/Studentenverwaltung.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/public/js/components/Stv/Studentenverwaltung.js b/public/js/components/Stv/Studentenverwaltung.js index e2ca8e349..57dd82081 100644 --- a/public/js/components/Stv/Studentenverwaltung.js +++ b/public/js/components/Stv/Studentenverwaltung.js @@ -91,8 +91,20 @@ export default { }, childactions: [ ] + }, + mergedperson: { + defaultaction: { + type: "link", + action: data => this.$fhcApi.getUri('/studentenverwaltung/person/' + data.person_id) + }, + defaultactionstudent: { + type: "link", + action: data => this.$fhcApi.getUri('/studentenverwaltung/prestudent/' + data.prestudent_id) + }, + childactions: [] } - } + }, + mergeResults: 'person' }, studiengangKz: undefined, studiensemesterKurzbz: this.defaultSemester, From 11b0e770a2aae6ec0d64629ce3859b48976cdcf3 Mon Sep 17 00:00:00 2001 From: Cris Date: Mon, 7 Oct 2024 16:29:53 +0200 Subject: [PATCH 0005/1216] Added filter lehrtyp_kurzbz to getAutocompleteSuggestions in Lehrveranstaltung_model.php --- .../education/Lehrveranstaltung_model.php | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index de8ebc5c9..f10ba5423 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -20,19 +20,45 @@ class Lehrveranstaltung_model extends DB_Model * @param $eventQuery String * @param string $studiensemester_kurzbz Filter by Studiensemester * @param array $oes Filter by Organisationseinheiten + * @param null $lehrtyp_kurzbz Filter by Lehrtyp 'lv' or 'modul' * @return array */ - public function getAutocompleteSuggestions($eventQuery, $studiensemester_kurzbz = null, $oes = null) + public function getAutocompleteSuggestions($eventQuery, $studiensemester_kurzbz = null, $oes = null, $lehrtyp_kurzbz = null) { - $subQry = $this->_getQryLvsByStudienplan($studiensemester_kurzbz, $oes); + // Subquery + $subQry = $this->_getQryLvsByStudienplan(); $params = []; - /* filter by input string */ - if (is_string($eventQuery)) { + if (isset($studiensemester_kurzbz) && is_string($studiensemester_kurzbz)) + { + /* filter by studiensemester */ + $subQry.= ' AND stplsem.studiensemester_kurzbz = ?'; + $params[] = $studiensemester_kurzbz; + } + + if (isset($oes) && is_array($oes)) + { + /* filter by organisationseinheit */ + $subQry.= ' AND lv.oe_kurzbz IN ?'; + $params[]= $oes; + } + + if (isset($lehrtyp_kurzbz) && is_string($lehrtyp_kurzbz)) + { + /* filter by lehrtyp_kurzbz */ + $subQry .= ' AND lehrtyp_kurzbz = ?'; + $params[] = $lehrtyp_kurzbz; + } + + + if (is_string($eventQuery)) + { + /* filter by input string */ $subQry.= ' AND lv.bezeichnung ILIKE ?'; $params[] = '%' . $eventQuery . '%'; } + // Final Query $qry = 'SELECT DISTINCT ON (lehrveranstaltung_id) * FROM ('. $subQry. ') AS tmp'; return $this->execQuery($qry, $params); From 8744a00ce7e42afe54806d260136302525503731 Mon Sep 17 00:00:00 2001 From: Cris Date: Mon, 7 Oct 2024 16:34:41 +0200 Subject: [PATCH 0006/1216] Added filter lehrtyp_kurzbz and $oe_column to getLvsByStudienplan in Lehrveranstaltung_model.php .lehrtyp_kurzbz filters by Lehrtyp 'lv' or 'modul' (default no filter) .oe_column is used when filtering $oes: Filter by lv.oe_kurzbz or stg.oe_kurzbz (the stg joined to lv) --- .../education/Lehrveranstaltung_model.php | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index f10ba5423..1fb949d7b 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -67,15 +67,50 @@ class Lehrveranstaltung_model extends DB_Model /** * Get Lehrveranstaltungen with its Stg, OE and OE-type. * Filter by Studiensemester and Organisationseinheiten if necessary. - * @param $eventQuery String - * @param string $studiensemester_kurzbz Filter by Studiensemester - * @param array $oes Filter by Organisationseinheiten - * @param array $lv_ids Filter by Lehrveranstaltung-Ids + * @param null|string $studiensemester_kurzbz Filter by Studiensemester + * @param null|array $oes Filter by Organisationseinheiten + * @param null|string $lehrtyp_kurzbz Filter by Lehrtyp 'lv' or 'modul' + * @param null|array $lv_ids Filter by Lehrveranstaltung-Ids + * @param string $oe_column 'lv'|'stg' Used when filtering $oes: Filter by lv.oe_kurzbz or stg.oe_kurzbz (the stg joined to lv) * @return array */ - public function getLvsByStudienplan($studiensemester_kurzbz = null, $oes = null, $lv_ids = null) + public function getLvsByStudienplan($studiensemester_kurzbz = null, $oes = null, $lehrtyp_kurzbz = null, $lv_ids = null, $oe_column = 'lv') { - $subQry = $this->_getQryLvsByStudienplan($studiensemester_kurzbz, $oes); + // Subquery LVs + $subQry = $this->_getQryLvsByStudienplan(); + $params = []; + + if (isset($studiensemester_kurzbz) && is_string($studiensemester_kurzbz)) + { + /* filter by studiensemester */ + $subQry.= ' AND stplsem.studiensemester_kurzbz = ?'; + $params[] = $studiensemester_kurzbz; + } + + if (isset($oes) && is_array($oes)) + { + if ($oe_column === 'lv') + { + /* filter by lv organisationseinheit (Standard behaviour) */ + $subQry.= ' AND lv.oe_kurzbz IN ?'; + } + elseif ($oe_column === 'stg') + { + /* filter by lv studiengangs organisationseinheit () */ + $subQry.= ' AND stg.oe_kurzbz IN ?'; + } + + $params[]= $oes; + } + + if (isset($lehrtyp_kurzbz) && is_string($lehrtyp_kurzbz)) + { + /* filter by lehrtyp_kurzbz */ + $subQry .= ' AND lehrtyp_kurzbz = ?'; + $params[] = $lehrtyp_kurzbz; + } + + // Final Query $qry = 'SELECT * FROM ('. $subQry. ') AS tmp'; if (isset($lv_ids) && is_array($lv_ids)) @@ -87,7 +122,7 @@ class Lehrveranstaltung_model extends DB_Model $qry.= ' ORDER BY stg_typ_kurzbz, orgform_kurzbz DESC'; - return $this->execQuery($qry); + return $this->execQuery($qry, $params); } /** From b08e01f72f8589c0790856b1d656d6ac9df0f252 Mon Sep 17 00:00:00 2001 From: Cris Date: Mon, 7 Oct 2024 16:37:07 +0200 Subject: [PATCH 0007/1216] Added columns to and moved where-clauses from _getQryLvsByStudienplan to calling methods in Lehrveranstaltung_model.php .added cols lehrtyp_kurzbz and lehrveranstaltung_template_id --- .../education/Lehrveranstaltung_model.php | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 1fb949d7b..1c601a291 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -130,7 +130,7 @@ class Lehrveranstaltung_model extends DB_Model * * @return string */ - private function _getQryLvsByStudienplan($studiensemester_kurzbz = null, $oes = null, $lehrtyp_kurzbz = 'lv') + private function _getQryLvsByStudienplan() { $qry = ' SELECT @@ -152,6 +152,8 @@ class Lehrveranstaltung_model extends DB_Model lv.lehrveranstaltung_id, lv.semester, lv.bezeichnung AS lv_bezeichnung, + lv.lehrtyp_kurzbz, + lv.lehrveranstaltung_template_id, ( -- comma seperated string of all lehreinheitgruppen SELECT string_agg(bezeichnung, \', \') AS lehreinheitgruppe_bezeichnung @@ -186,23 +188,8 @@ class Lehrveranstaltung_model extends DB_Model JOIN public.tbl_organisationseinheit oe USING (oe_kurzbz) JOIN public.tbl_studiengang stg ON stg.studiengang_kz = sto.studiengang_kz JOIN public.tbl_studiengangstyp stgtyp ON stgtyp.typ = stg.typ - /* filter by lehrtyp_kurzbz, default is lvs only */ - WHERE - lehrtyp_kurzbz = '. $this->db->escape($lehrtyp_kurzbz); - - if (isset($studiensemester_kurzbz) && is_string($studiensemester_kurzbz)) - { - /* filter by studiensemester */ - $qry.= ' AND stplsem.studiensemester_kurzbz = '. $this->db->escape($studiensemester_kurzbz); - - } - - if (isset($oes) && is_array($oes)) - { - /* filter by organisationseinheit */ - $implodedOes = "'". implode("', '", $oes). "'"; - $qry.= ' AND lv.oe_kurzbz IN ('. $implodedOes. ')'; - } + WHERE 1 = 1 + '; return $qry; } From 7096bba958ed8db567532d6812764bda07ccfa48 Mon Sep 17 00:00:00 2001 From: Cris Date: Mon, 7 Oct 2024 16:37:45 +0200 Subject: [PATCH 0008/1216] Added phrases for Softwarebereitstellung --- system/phrasesupdate.php | 80 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 9a99b21ec..89dc427c3 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -30665,6 +30665,86 @@ array( ) ) ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'standardLvTemplate', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Standard LV-Template', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Standard Course Template', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'anforderungNachStandardLvTemplate', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Anforderung nach Standard LV-Template', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Request by Standard Course Template', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'swAnforderungUeberAuswahlVonStandardisiertenLvTemplates', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Softwareanforderung über die Auswahl von standardisierten LV-Templates (Quellkurse)", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Software Requirements based on the Selection of Standardized Course-Templates", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'swFuerLvAnfordern', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Software für LV anfordern", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Request software for courses", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), ); From bf1bc8d1a97dbb52c2d1bd30a295a3909058da22 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Sun, 13 Oct 2024 13:32:15 +0200 Subject: [PATCH 0009/1216] added new contact type email unverifiziert and adresse type meldeadresse (mainly for electronic onboarding) --- system/dbupdate_3.4.php | 1 + ...14_electronic_onboarding_anbindung_ida.php | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 system/dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php diff --git a/system/dbupdate_3.4.php b/system/dbupdate_3.4.php index 11880fd55..d3b7b454d 100644 --- a/system/dbupdate_3.4.php +++ b/system/dbupdate_3.4.php @@ -58,6 +58,7 @@ require_once('dbupdate_3.4/17513_Entwicklungsteam.php'); require_once('dbupdate_3.4/28575_softwarebereitstellung.php'); require_once('dbupdate_3.4/41150_oe-pfad_db_view.php'); require_once('dbupdate_3.4/44031_stv_favorites.php'); +require_once('dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php'); // *** Pruefung und hinzufuegen der neuen Attribute und Tabellen echo '

Pruefe Tabellen und Attribute!

'; diff --git a/system/dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php b/system/dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php new file mode 100644 index 000000000..ccc0a5827 --- /dev/null +++ b/system/dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php @@ -0,0 +1,30 @@ +db_query("SELECT 1 FROM public.tbl_kontakttyp WHERE kontakttyp='email_unverifiziert'")) +{ + if($db->db_num_rows($result)==0) + { + $qry = "INSERT INTO public.tbl_kontakttyp(kontakttyp, beschreibung, bezeichnung_mehrsprachig) VALUES('email_unverifiziert', 'Unverifizierte E-Mail', '{\"Unverifizierte E-Mail\", \"Unverified email\"}');"; + + if(!$db->db_query($qry)) + echo 'Kontakttyp: '.$db->db_last_error().'
'; + else + echo '
Neuen Kontakttyp E-Mail unverifiziert in public.tbl_kontakttyp hinzugefügt'; + } +} + +// public.tbl_adressentyp: add type Meldeadresse +if($result = $db->db_query("SELECT 1 FROM public.tbl_adressentyp WHERE adressentyp_kurzbz='m'")) +{ + if($db->db_num_rows($result)==0) + { + $qry = "INSERT INTO public.tbl_adressentyp(adressentyp_kurzbz, bezeichnung, bezeichnung_mehrsprachig, sort) VALUES('m', 'Meldeadresse', '{\"Meldeadresse\", \"Registered adress\"}', 6);"; + + if(!$db->db_query($qry)) + echo 'Adressentyp: '.$db->db_last_error().'
'; + else + echo '
Neue Adressentyp Meldeadresse in public.tbl_adressentyp hinzugefügt'; + } +} From 322544c7fb0746c85934e21d232845f7f38971e6 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Mon, 14 Oct 2024 11:20:46 +0200 Subject: [PATCH 0010/1216] dynamic image url for searchresults --- application/config/search.php | 15 ++++++++++++--- application/libraries/SearchBarLib.php | 1 - .../components/searchbar/result/mergedperson.js | 12 ++++-------- public/js/components/searchbar/result/person.js | 9 +-------- .../js/components/searchbar/result/prestudent.js | 9 +-------- public/js/components/searchbar/result/student.js | 9 +-------- 6 files changed, 19 insertions(+), 36 deletions(-) diff --git a/application/config/search.php b/application/config/search.php index ac6189527..14b6e89d8 100644 --- a/application/config/search.php +++ b/application/config/search.php @@ -74,7 +74,10 @@ $config['person'] = [ "p.person_id", "(p.vorname || ' ' || p.nachname) AS name", "ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email", - "p.foto" + "CASE + WHEN p.foto IS NOT NULL THEN 'data:image/jpeg' || CONVERT_FROM(DECODE('3b','hex'), 'UTF8') || 'base64,' || p.foto + ELSE NULL END + AS photo_url" ], 'resultjoin' => " JOIN public.tbl_person p USING (person_id) @@ -200,7 +203,10 @@ $config['student'] = [ "p.person_id", "(p.vorname || ' ' || p.nachname) AS name", "ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email", - "p.foto" + "CASE + WHEN p.foto IS NOT NULL THEN 'data:image/jpeg' || CONVERT_FROM(DECODE('3b','hex'), 'UTF8') || 'base64,' || p.foto + ELSE NULL END + AS photo_url" ], 'resultjoin' => " JOIN public.tbl_student s USING (student_uid) @@ -298,7 +304,10 @@ $config['prestudent'] = [ "b.uid", "(p.vorname || ' ' || p.nachname) AS name", "ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email", - "p.foto", + "CASE + WHEN p.foto IS NOT NULL THEN 'data:image/jpeg' || CONVERT_FROM(DECODE('3b','hex'), 'UTF8') || 'base64,' || p.foto + ELSE NULL END + AS photo_url", "UPPER(sg.typ || sg.kurzbz) AS stg_kuerzel", "sg.bezeichnung", "( diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index 6547cd107..c3528978d 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -228,7 +228,6 @@ class SearchBarLib GROUP BY " . $table_config['primarykey'] . " )"; - $other_selects = $selects[] = " SELECT " . $this->_ci->db->escape($type) . " AS type, diff --git a/public/js/components/searchbar/result/mergedperson.js b/public/js/components/searchbar/result/mergedperson.js index f6b07f3d2..09b967b2f 100644 --- a/public/js/components/searchbar/result/mergedperson.js +++ b/public/js/components/searchbar/result/mergedperson.js @@ -20,8 +20,9 @@ export default { if (person.length) return person.pop(); - const { person_id, name, foto, photo_url, email } = this.res.list[0]; - return { person_id, name, foto, photo_url, email }; + // TODO(chris): first one might have not one of these but a later one + const { person_id, name, photo_url, email } = this.res.list[0]; + return { person_id, name, photo_url, email }; }, employee() { const ma = this.res.list.filter(item => [ @@ -34,11 +35,6 @@ export default { const students = this.res.list.filter(item => item.type == 'prestudent'); return students.length ? students : null; }, - foto() { - if (this.person.foto) - return 'data:image/jpeg;base64,' + this.person.foto; - return this.person.photo_url; - }, emails() { if (Array.isArray(this.person.email)) return this.person.email; @@ -51,7 +47,7 @@ export default { :res="person" :actions="actions" :title="person.name" - :image="foto" + :image="this.person.photo_url" image-fallback="fas fa-user-circle fa-7x" @actionexecuted="$emit('actionexecuted')" > diff --git a/public/js/components/searchbar/result/person.js b/public/js/components/searchbar/result/person.js index f8adee56e..65155a95f 100644 --- a/public/js/components/searchbar/result/person.js +++ b/public/js/components/searchbar/result/person.js @@ -9,20 +9,13 @@ export default { res: Object, actions: Object }, - computed: { - foto() { - if (this.res.foto) - return 'data:image/jpeg;base64,' + this.res.foto; - return null; - } - }, template: ` diff --git a/public/js/components/searchbar/result/prestudent.js b/public/js/components/searchbar/result/prestudent.js index 8c174d3c0..662069b75 100644 --- a/public/js/components/searchbar/result/prestudent.js +++ b/public/js/components/searchbar/result/prestudent.js @@ -9,20 +9,13 @@ export default { res: Object, actions: Object }, - computed: { - foto() { - if (this.res.foto) - return 'data:image/jpeg;base64,' + this.res.foto; - return null; - } - }, template: ` diff --git a/public/js/components/searchbar/result/student.js b/public/js/components/searchbar/result/student.js index 8d8c4894b..93033d08b 100644 --- a/public/js/components/searchbar/result/student.js +++ b/public/js/components/searchbar/result/student.js @@ -9,20 +9,13 @@ export default { res: Object, actions: Object }, - computed: { - foto() { - if (this.res.foto) - return 'data:image/jpeg;base64,' + this.res.foto; - return null; - } - }, template: ` From 123f29a75062d5008fbfdd8b55370f09404ba93e Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Mon, 14 Oct 2024 11:22:20 +0200 Subject: [PATCH 0011/1216] CMS Search --- application/config/search.php | 94 +++++++++++++++++++ application/config/searchfunctions.php | 2 +- .../controllers/api/frontend/v1/Language.php | 47 ++++++++++ application/libraries/SearchBarLib.php | 6 ++ public/js/api/fhcapifactory.js | 4 +- public/js/api/language.js | 22 +++++ public/js/components/searchbar/result/cms.js | 81 ++++++++++++++++ public/js/components/searchbar/searchbar.js | 34 ++++++- system/dbupdate_3.4/40128_search.php | 15 +++ 9 files changed, 298 insertions(+), 7 deletions(-) create mode 100644 application/controllers/api/frontend/v1/Language.php create mode 100644 public/js/api/language.js create mode 100644 public/js/components/searchbar/result/cms.js diff --git a/application/config/search.php b/application/config/search.php index 14b6e89d8..f52d755dd 100644 --- a/application/config/search.php +++ b/application/config/search.php @@ -671,3 +671,97 @@ $config['room'] = [ LEFT JOIN public.tbl_adresse address USING (adresse_id)" ]; + +$config['cms'] = [ + 'primarykey' => 'contentsprache_id', + 'table' => 'cms', + 'prepare' => " + cms_auth (content_id) AS ( + SELECT content_id + FROM campus.tbl_content c + WHERE NOT EXISTS (SELECT 1 FROM campus.tbl_contentgruppe g WHERE g.content_id=c.content_id) + UNION + SELECT content_id + FROM public.vw_gruppen g + JOIN campus.tbl_contentgruppe c USING (gruppe_kurzbz) + WHERE uid = (TABLE auth) + ), + cms_active (content_id, template_kurzbz) AS ( + SELECT content_id, template_kurzbz + FROM cms_auth + JOIN campus.tbl_content USING (content_id) + WHERE aktiv = TRUE + ), + cms_active_redirect (content_id) AS ( + SELECT content_id + FROM cms_active + WHERE template_kurzbz = 'redirect' + ), + cms_active_redirect_linked (content_id) AS ( + SELECT content_id + FROM cms_active_redirect + JOIN campus.tbl_contentsprache USING (content_id) + WHERE LEFT((xpath('string(/content/url)', content))[1]::text, 1) <> '#' + ), + cms_active_others (content_id) AS ( + SELECT content_id + FROM cms_active + WHERE template_kurzbz IN ('contentmittitel', 'contentohnetitel', 'contentmittitel_filterwidget') + ), + cms (contentsprache_id) AS ( + SELECT contentsprache_id + FROM campus.tbl_contentsprache + WHERE content_id IN ( + SELECT content_id + FROM cms_active_redirect_linked + UNION + SELECT content_id + FROM cms_active_others + ) + AND version = campus.get_highest_content_version(content_id) + ) + ", + 'searchfields' => [ + 'content' => [ + 'alias' => ['inhalt'], + 'comparison' => "vector", + 'field' => "(setweight(to_tsvector('simple', COALESCE(titel, '')), 'A') || setweight(to_tsvector('simple', COALESCE(content, '')::text), 'B'))", + 'join' => [ + 'table' => "campus.tbl_contentsprache", + 'using' => "contentsprache_id" + ] + ], + 'content_id' => [ + 'alias' => ['id'], + 'comparison' => "equal-int", + 'field' => "content_id", + 'join' => [ + 'table' => "campus.tbl_contentsprache", + 'using' => "contentsprache_id" + ] + ], + 'lang' => [ + 'alias' => ['language', 'sprache'], + 'comparison' => "equals", + 'field' => "sprache", + 'join' => [ + 'table' => "campus.tbl_contentsprache", + 'using' => "contentsprache_id" + ] + ] + ], + 'resultfields' => [ + "contentsprache.content_id", + "content.template_kurzbz", + "contentsprache.version", + "contentsprache.sprache AS language", + "contentsprache.titel AS title", + "contentsprache.content", + "(xpath('string(/content/url)', contentsprache.content))[1] AS content_url" + ], + 'resultjoin' => " + JOIN campus.tbl_contentsprache contentsprache + USING (contentsprache_id) + JOIN campus.tbl_content content + USING (content_id)" +]; diff --git a/application/config/searchfunctions.php b/application/config/searchfunctions.php index c8244e9a3..898069aed 100644 --- a/application/config/searchfunctions.php +++ b/application/config/searchfunctions.php @@ -25,7 +25,7 @@ $config['similar'] = [ $config['vector'] = [ 'priority' => 1, - 'rank' => "ts_rank_cd({field}, to_tsquery('simple', {word}))", + 'rank' => "ts_rank({field}, to_tsquery('simple', {word}))", 'compare' => "to_tsquery('simple', {word}) @@ {field}" ]; diff --git a/application/controllers/api/frontend/v1/Language.php b/application/controllers/api/frontend/v1/Language.php new file mode 100644 index 000000000..797af4804 --- /dev/null +++ b/application/controllers/api/frontend/v1/Language.php @@ -0,0 +1,47 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the back-end + * Provides data to the ajax get calls about languages + * This controller works with JSON calls on the HTTP GET and the output is always JSON + */ +class Language extends FHCAPI_Controller +{ + public function __construct() + { + parent::__construct([ + 'get' => self::PERM_LOGGED + ]); + + // Load models + $this->load->model('system/Sprache_model', 'SpracheModel'); + } + + public function get() + { + $this->SpracheModel->addOrder('sprache'); + + $result = $this->SpracheModel->load(); + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } +} diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index c3528978d..782e30843 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -168,6 +168,9 @@ class SearchBarLib FROM public.tbl_sprache WHERE sprache=" . $this->_ci->db->escape($lang) . " LIMIT 1 + ), + auth (uid) AS ( + SELECT " . $this->_ci->db->escape(getAuthUID()) . " AS uid )"; if ($sql_with) { @@ -248,6 +251,9 @@ class SearchBarLib WHERE sprache=" . $this->_ci->db->escape($lang) . " LIMIT 1 )"); + array_unshift($with, "auth (uid) AS ( + SELECT " . $this->_ci->db->escape(getAuthUID()) . " AS uid + )"); return success(" WITH " . implode(", ", $with) . " diff --git a/public/js/api/fhcapifactory.js b/public/js/api/fhcapifactory.js index 655bfa409..26ed8847b 100644 --- a/public/js/api/fhcapifactory.js +++ b/public/js/api/fhcapifactory.js @@ -23,6 +23,7 @@ import studstatus from "./studstatus.js"; import stv from "./stv.js"; import notiz from "./notiz.js"; import betriebsmittel from "./betriebsmittel.js"; +import language from "./language.js"; export default { search, @@ -32,5 +33,6 @@ export default { studstatus, stv, notiz, - betriebsmittel + betriebsmittel, + language }; diff --git a/public/js/api/language.js b/public/js/api/language.js new file mode 100644 index 000000000..6a11d193b --- /dev/null +++ b/public/js/api/language.js @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2024 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export default { + getAll() { + return this.$fhcApi.get('/api/frontend/v1/language/get'); + } +}; \ No newline at end of file diff --git a/public/js/components/searchbar/result/cms.js b/public/js/components/searchbar/result/cms.js new file mode 100644 index 000000000..af2d6b0bb --- /dev/null +++ b/public/js/components/searchbar/result/cms.js @@ -0,0 +1,81 @@ +import TemplateFrame from "./template/frame.js"; + +export default { + components: { + TemplateFrame + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + inject: [ + 'languages', + 'query' + ], + computed: { + preview() { + if (this.res.template_kurzbz != 'redirect') { + let text = this.res.content.replace(//ig, '').replace(/<[^>]+>/ig, '').replace(/^\s+|\s+$/g, ''); + + if (text.length > 1000) { + // NOTE(chris): focus on searched text! + let lower = text.toLowerCase(); + let firstOccurence = Math.min(this.query.split(' ').reduce((a, c) => { + // NOTE(chris): filter query for words that affects the content field and get the lowest index of them + if (c == 'or') + return a; + let i = c.indexOf(':'); + if (i < 0 || (i > 0 && ['content', 'inhalt'].includes(c.split(':')[0]))) { + let posInText = lower.indexOf(c); + if (posInText >= 0) + a.push(posInText); + } + return a; + }, [])); + + if (firstOccurence) { + if (firstOccurence + 997 >= text.length) { + firstOccurence = text.length - 997; + if (firstOccurence > 0) + return '...' + text.substr(firstOccurence, 997); + } else { + return '...' + text.substr(firstOccurence, 994) + '...'; + } + } + + text = text.substr(0, 997) + '...'; + } + + return text; + } + + let url = this.res.content_url; + if (url.substr(0, 16) == '../index.ci.php/') + url = this.$fhcApi.getUri(url.substr(16)); + else if (url.substr(0, 3) == '../') + url = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/\/+$/, '') + url.substr(2); + return '' + url + ''; + }, + flag() { + if (!this.languages || !this.languages[this.res.language]) + return ""; + return "data:image/jpeg;base64," + this.languages[this.res.language].flagge; + } + }, + template: ` + + +
+
+ No Content +
+
` +}; \ No newline at end of file diff --git a/public/js/components/searchbar/searchbar.js b/public/js/components/searchbar/searchbar.js index 54049679e..8ac64e856 100644 --- a/public/js/components/searchbar/searchbar.js +++ b/public/js/components/searchbar/searchbar.js @@ -4,6 +4,7 @@ import ResultPrestudent from "./result/prestudent.js"; import ResultEmployee from "./result/employee.js"; import ResultOrganisationunit from "./result/organisationunit.js"; import ResultRoom from "./result/room.js"; +import ResultCms from "./result/cms.js"; import ResultMergedperson from "./result/mergedperson.js"; import ResultMergedstudent from "./result/mergedstudent.js"; @@ -17,10 +18,17 @@ export default { ResultEmployee, ResultOrganisationunit, ResultRoom, + ResultCms, ResultMergedperson, ResultMergedstudent }, props: [ "searchoptions", "searchfunction" ], + provide() { + return { + languages: Vue.computed(() => this.languages), + query: Vue.computed(() => this.lastQuery) + }; + }, data() { return { searchtimer: null, @@ -35,9 +43,22 @@ export default { searching: false, error: null, abortController: null, - retry: 5 + retry: 0, + languages: null, + lastQuery: '' }; }, + created() { + this.$fhcApi.factory + .language.getAll() + .then(result => { + this.languages = result.data.reduce((a, c) => { + a[c.sprache] = c; + return a; + }, {}); + }) + .catch(this.$fhcAlert.handleSystemError); + }, beforeMount() { this.updateSearchOptions(); }, @@ -82,12 +103,14 @@ export default { this.abortController = new AbortController(); this - .searchfunction(this.searchsettings, { signal: this.abortController.signal }) + .searchfunction(this.searchsettings, { timeout: 50000, signal: this.abortController.signal }) .then(response => { if (!response.data) { this.error = 'Bei der Suche ist ein Fehler aufgetreten.'; } else { let res = response.data.map(el => ({...el, ...JSON.parse(el.data)})); + this.lastQuery = response.meta.searchstring; + if (this.searchoptions.mergeResults) { let counter = 0; let mergeTypes = []; @@ -131,11 +154,11 @@ export default { this.searchresult = res; } this.searching = false; - this.retry = 5; + this.retry = 0; }) .catch(error => { if (error.code == "ERR_CANCELED") { - return this.retry = 5; + return this.retry = 0; } if (error.code == "ECONNABORTED" && this.retry) { this.retry--; @@ -144,7 +167,7 @@ export default { this.error = 'Bei der Suche ist ein Fehler aufgetreten.' + ' ' + error.message; this.searching = false; - this.retry = 5; + this.retry = 0; }); }, refreshsearch() { @@ -239,6 +262,7 @@ export default { +
Unbekannter Ergebnistyp: '{{ res.type }}'.
diff --git a/system/dbupdate_3.4/40128_search.php b/system/dbupdate_3.4/40128_search.php index 2ef8f8b4a..92e1a10fd 100644 --- a/system/dbupdate_3.4/40128_search.php +++ b/system/dbupdate_3.4/40128_search.php @@ -169,3 +169,18 @@ FROM pg_indexes WHERE indexname = 'idx_tbl_organisationseinheit_fts_bezeichnung_ else echo 'public.tbl_organisationseinheit: added index "idx_tbl_organisationseinheit_fts_bezeichnung_vector"
'; } +// Add index for titel || ' ' || content to campus.tbl_contentsprache +if (!$db->db_num_rows(@$db->db_query("SELECT 1 +FROM pg_indexes WHERE indexname = 'idx_tbl_contentsprache_fts_titel_content_vector' LIMIT 1;"))) +{ + $qry = " + CREATE INDEX idx_tbl_contentsprache_fts_titel_content_vector + ON campus.tbl_contentsprache + USING GIN ((setweight(to_tsvector('simple', COALESCE(titel, '')), 'A') || setweight(to_tsvector('simple', COALESCE(content, '')::text), 'B'))); + "; + + if (!$db->db_query($qry)) + echo 'campus.tbl_contentsprache ' . $db->db_last_error() . '
'; + else + echo 'campus.tbl_contentsprache: added index "idx_tbl_contentsprache_fts_titel_content_vector"
'; +} From 5cee80ed124e28205145d6ec723234764609ba3f Mon Sep 17 00:00:00 2001 From: ma0048 Date: Fri, 18 Oct 2024 13:24:39 +0200 Subject: [PATCH 0012/1216] - rtliste automatisierte messages --- public/js/plugin/FhcAlert.js | 26 ++++++++++++++++++++ system/dbupdate_3.4.php | 2 ++ system/dbupdate_3.4/37620_reihungslisten.php | 13 ++++++++++ vilesci/stammdaten/auswertung_fhtw.php | 18 ++++++++++++-- 4 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 system/dbupdate_3.4/37620_reihungslisten.php diff --git a/public/js/plugin/FhcAlert.js b/public/js/plugin/FhcAlert.js index b079bf98e..aa8c7a3cc 100644 --- a/public/js/plugin/FhcAlert.js +++ b/public/js/plugin/FhcAlert.js @@ -60,6 +60,13 @@ * Displays a confirmation dialog and returns a Promise which resolves * with true or false depending und the pressed button. * @return Promise + * + * confirm + * ------------ + * Displays a confirmation dialog and returns a Promise which resolves + * with true or false depending und the pressed button. + * @param string message + * @return Promise * * alertDefault * ------------ @@ -226,6 +233,25 @@ export default { }); }); }, + confirm(message) { + return new Promise((resolve, reject) => { + helperAppInstance.$confirm.require({ + group: 'fhcAlertConfirm', + header: 'Achtung', + message: message, + acceptLabel: 'Ja', + acceptClass: 'btn btn-success', + rejectLabel: 'Abbrechen', + rejectClass: 'btn btn-outline-secondary', + accept() { + resolve(true); + }, + reject() { + resolve(false); + }, + }); + }); + }, alertDefault(severity, title, message, sticky = false) { let options = { severity: severity, summary: title, detail: message}; diff --git a/system/dbupdate_3.4.php b/system/dbupdate_3.4.php index 11880fd55..ca8b42db9 100644 --- a/system/dbupdate_3.4.php +++ b/system/dbupdate_3.4.php @@ -58,6 +58,8 @@ require_once('dbupdate_3.4/17513_Entwicklungsteam.php'); require_once('dbupdate_3.4/28575_softwarebereitstellung.php'); require_once('dbupdate_3.4/41150_oe-pfad_db_view.php'); require_once('dbupdate_3.4/44031_stv_favorites.php'); +require_once('dbupdate_3.4/37620_reihungslisten.php'); + // *** Pruefung und hinzufuegen der neuen Attribute und Tabellen echo '

Pruefe Tabellen und Attribute!

'; diff --git a/system/dbupdate_3.4/37620_reihungslisten.php b/system/dbupdate_3.4/37620_reihungslisten.php new file mode 100644 index 000000000..950d6af0e --- /dev/null +++ b/system/dbupdate_3.4/37620_reihungslisten.php @@ -0,0 +1,13 @@ +db_query("SELECT 1 FROM public.tbl_buchungstyp WHERE buchungstyp_kurzbz = 'KautionDrittStaat';")) +{ + if ($db->db_num_rows($result) == 0) + { + $qry = "INSERT INTO public.tbl_buchungstyp (buchungstyp_kurzbz, beschreibung, standardtext, standardbetrag) VALUES ('KautionDrittStaat', 'Kaution', 'Deposit for application, third countries', '-250');"; + if (!$db->db_query($qry)) + echo 'public.tbl_buchungstyp '.$db->db_last_error().'
'; + else + echo ' public.tbl_buchungstyp: Added buchungstyp "KautionDrittStaat"
'; + } +} \ No newline at end of file diff --git a/vilesci/stammdaten/auswertung_fhtw.php b/vilesci/stammdaten/auswertung_fhtw.php index aa39889cb..10809dc4f 100644 --- a/vilesci/stammdaten/auswertung_fhtw.php +++ b/vilesci/stammdaten/auswertung_fhtw.php @@ -1220,6 +1220,7 @@ $reihungstest = isset($_REQUEST['reihungstest']) ? $_REQUEST['reihungstest'] : ' $studiengang = isset($_REQUEST['studiengang']) ? $_REQUEST['studiengang'] : ''; $semester = isset($_REQUEST['semester']) ? $_REQUEST['semester'] : ''; $prestudent_id = isset($_REQUEST['prestudent_id']) ? $_REQUEST['prestudent_id'] : ''; +$prestudent_ids = isset($_REQUEST['prestudent_ids']) ? $_REQUEST['prestudent_ids'] : ''; $orgform_kurzbz = isset($_REQUEST['orgform_kurzbz']) ? $_REQUEST['orgform_kurzbz'] : ''; $format = (isset($_REQUEST['format']) ? $_REQUEST['format'] : ''); $stgtyp = (isset($_REQUEST['stgtyp']) ? $_REQUEST['stgtyp'] : ''); @@ -1259,7 +1260,7 @@ if ($prestudent_id != '' && !is_numeric($prestudent_id)) { die('PrestudentID ist ungueltig'); } -if (isset($_POST['rtauswsubmit']) && $reihungstest == '' && $studiengang == '' && $semester == '' && $prestudent_id == '' && $datum_von == '' && $datum_bis == '') +if (isset($_POST['rtauswsubmit']) && $reihungstest == '' && $studiengang == '' && $semester == '' && $prestudent_id == '' && $datum_von == '' && $datum_bis == '' && $prestudent_ids == '') { die('Waehlen Sie bitte mindestens eine der Optionen aus'); } @@ -1378,6 +1379,10 @@ if (isset($_REQUEST['reihungstest']) || isset($_POST['rtauswsubmit'])) { $query .= " AND ps.prestudent_id=" . $db->db_add_param($prestudent_id, FHC_INTEGER); } + if ($prestudent_ids != '') + { + $query .= " AND ps.prestudent_id IN (" .$db->db_implode4SQL(explode(',', $prestudent_ids)) . ")"; + } if ($orgform_kurzbz != '' && $studiengang != '') { $query .= " AND (tbl_ablauf.studienplan_id=( @@ -1611,6 +1616,10 @@ if (isset($_REQUEST['reihungstest']) || isset($_POST['rtauswsubmit'])) { $query .= " AND ps.prestudent_id=" . $db->db_add_param($prestudent_id, FHC_INTEGER); } + if ($prestudent_ids != '') + { + $query .= " AND ps.prestudent_id IN (" .$db->db_implode4SQL(explode(',',$prestudent_ids)) . ")"; + } if ($orgform_kurzbz != '') { //$query .= " AND tbl_prestudentstatus.orgform_kurzbz=" . $db->db_add_param($orgform_kurzbz); @@ -2890,7 +2899,7 @@ else $selectedrtstr = ''; $checkbxstr = ''; $first = true; - $noparamsselected = $prestudent_id == '' && $reihungstest == '' && $datum_von == '' && $datum_bis == '' && $studiengang == '' && $semester == ''; + $noparamsselected = $prestudent_id == '' && $reihungstest == '' && $datum_von == '' && $datum_bis == '' && $studiengang == '' && $semester == '' && $prestudent_ids == ''; //$maxeachline = 1; foreach ($rtest as $rt) { @@ -3006,6 +3015,9 @@ else echo ''; echo ''; echo 'PrestudentIn: '; + echo ''; + echo ''; + echo 'PrestudentIn (Mehrfachauswahl): '; echo ' '; echo '

'; @@ -3014,6 +3026,7 @@ else &datum_von=' . $datum_von . ' &datum_bis=' . $datum_bis . ' &prestudent_id=' . $prestudent_id . ' + &' . http_build_query(array('prestudent_ids' => $prestudent_ids)) . ' &' . http_build_query(array('reihungstest' => $reihungstest)) . ' &orgform_kurzbz=' . $orgform_kurzbz . ' &stgtyp=' . $stgtyp . ' @@ -3067,6 +3080,7 @@ else datum_von=' . $datum_von . '& datum_bis=' . $datum_bis . '& prestudent_id=' . $prestudent_id . '& + &' . http_build_query(array('prestudent_ids' => $prestudent_ids)) . ' &' . http_build_query(array('reihungstest' => $reihungstest)) . '">
'; echo '
'; From b8ff37eb8edfd28c897119d032c517533663574f Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Sat, 19 Oct 2024 17:25:09 +0200 Subject: [PATCH 0013/1216] added kontakt verifikation table for saving of kontakt verifikation data, added model for the table --- .../person/Kontaktverifikation_model.php | 42 ++++++++++++++++ ...14_electronic_onboarding_anbindung_ida.php | 48 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 application/models/person/Kontaktverifikation_model.php diff --git a/application/models/person/Kontaktverifikation_model.php b/application/models/person/Kontaktverifikation_model.php new file mode 100644 index 000000000..b15439edb --- /dev/null +++ b/application/models/person/Kontaktverifikation_model.php @@ -0,0 +1,42 @@ +dbTable = 'public.tbl_kontakt_verifikation'; + $this->pk = 'kontakt_verifikation_id'; + } + + /** + * Gets contact verification for a person and a verification code + * @param person_id + * @param kontakttyp + * @param verifikation_code + * @param expiration_days number of days after which verifikation code expires + * @return object success or error + */ + public function getKontaktVerifikation($person_id, $kontakttyp, $verifikation_code, $expiration_days = 1) + { + $qry = " + SELECT + kt.kontakt_id, + kv.verifikation_code + FROM + public.tbl_kontakt_verifikation kv + JOIN public.tbl_kontakt kt USING(kontakt_id) + WHERE kt.person_id = ? + AND kt.kontakttyp = ? + AND kv.verifikation_code = ? + AND kv.erstelldatum >= NOW() - INTERVAL '".$this->escape($expiration_days)." days' + ORDER BY + kt.kontakt_id DESC + LIMIT 1"; + + return $this->execQuery($qry, array($person_id, $kontakttyp, $verifikation_code)); + } +} diff --git a/system/dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php b/system/dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php index ccc0a5827..b204d45aa 100644 --- a/system/dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php +++ b/system/dbupdate_3.4/40314_electronic_onboarding_anbindung_ida.php @@ -28,3 +28,51 @@ if($result = $db->db_query("SELECT 1 FROM public.tbl_adressentyp WHERE adressent echo '
Neue Adressentyp Meldeadresse in public.tbl_adressentyp hinzugefügt'; } } + +if (!$result = @$db->db_query('SELECT 1 FROM public.tbl_kontakt_verifikation LIMIT 1')) +{ + $qry = "CREATE SEQUENCE public.tbl_kontakt_verifikation_kontakt_verifikation_id_seq + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + START WITH 1 + CACHE 1 + NO CYCLE; + + CREATE TABLE public.tbl_kontakt_verifikation + ( + kontakt_verifikation_id integer DEFAULT nextval('public.tbl_kontakt_verifikation_kontakt_verifikation_id_seq'::regclass), + kontakt_id integer UNIQUE NOT NULL, + verifikation_code varchar(32) UNIQUE NOT NULL, + erstelldatum timestamp without time zone, + verifikation_datum timestamp without time zone, + app varchar(32), + CONSTRAINT pk_tbl_kontakt_verifikation_id PRIMARY KEY (kontakt_verifikation_id) + ); + + ALTER TABLE public.tbl_kontakt_verifikation ADD CONSTRAINT fk_tbl_kontakt_verifikation_kontakt_id FOREIGN KEY (kontakt_id) + REFERENCES public.tbl_kontakt (kontakt_id) + ON DELETE CASCADE ON UPDATE CASCADE; + + ALTER TABLE public.tbl_kontakt_verifikation ADD CONSTRAINT fk_tbl_kontakt_verifikation_app FOREIGN KEY (app) + REFERENCES system.tbl_app (app) + ON DELETE RESTRICT ON UPDATE CASCADE; + + COMMENT ON TABLE public.tbl_kontakt_verifikation IS 'Contact verification'; + COMMENT ON COLUMN public.tbl_kontakt_verifikation.kontakt_id IS 'Contact to verify'; + COMMENT ON COLUMN public.tbl_kontakt_verifikation.verifikation_code IS 'Code generated for verification'; + COMMENT ON COLUMN public.tbl_kontakt_verifikation.erstelldatum IS 'Time when verification code was generated'; + COMMENT ON COLUMN public.tbl_kontakt_verifikation.verifikation_datum IS 'Time when contact was verified'; + COMMENT ON COLUMN public.tbl_kontakt_verifikation.app IS 'App where contact was verified'; + + GRANT SELECT, UPDATE, INSERT, DELETE ON public.tbl_kontakt_verifikation TO web; + GRANT SELECT, UPDATE, INSERT, DELETE ON public.tbl_kontakt_verifikation TO vilesci; + GRANT SELECT, UPDATE ON public.tbl_kontakt_verifikation_kontakt_verifikation_id_seq TO vilesci; + GRANT SELECT, UPDATE ON public.tbl_kontakt_verifikation_kontakt_verifikation_id_seq TO web; + "; + + if(!$db->db_query($qry)) + echo 'public.tbl_kontakt_verifikation: '.$db->db_last_error().'
'; + else + echo ' public.tbl_kontakt_verifikation: Tabelle hinzugefuegt
'; +} From dcb24415fcb75f75f920ad0bffb1f61135f41559 Mon Sep 17 00:00:00 2001 From: Cris Date: Mon, 21 Oct 2024 14:02:25 +0200 Subject: [PATCH 0014/1216] Added phrases 'errorConfigFehlt' and 'errorUnbekannteUrl' --- system/phrasesupdate.php | 42 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 59c4a2246..a8d80a4af 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -31165,7 +31165,47 @@ array( 'insertvon' => 'system' ) ) - ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'errorConfigFehlt', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Config-Eintrag fehlt", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Missing config entry", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'errorUnbekannteUrl', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Unbekannte URL. Seite bzw. Link kann nicht geöffnet werden.", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Unknown URL. Cannot open to site or link", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), ); From a9a18d1cd43f2ba49b041117f03f6f67aa972df5 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Mon, 28 Oct 2024 11:16:02 +0100 Subject: [PATCH 0015/1216] Remove deprecated components --- public/js/components/searchbar/action.js | 31 ------- public/js/components/searchbar/actions.js | 28 ------ public/js/components/searchbar/employee.js | 89 ------------------- .../components/searchbar/organisationunit.js | 82 ----------------- public/js/components/searchbar/person.js | 55 ------------ public/js/components/searchbar/prestudent.js | 88 ------------------ public/js/components/searchbar/raum.js | 51 ----------- public/js/components/searchbar/student.js | 80 ----------------- 8 files changed, 504 deletions(-) delete mode 100644 public/js/components/searchbar/action.js delete mode 100644 public/js/components/searchbar/actions.js delete mode 100644 public/js/components/searchbar/employee.js delete mode 100644 public/js/components/searchbar/organisationunit.js delete mode 100644 public/js/components/searchbar/person.js delete mode 100644 public/js/components/searchbar/prestudent.js delete mode 100644 public/js/components/searchbar/raum.js delete mode 100644 public/js/components/searchbar/student.js diff --git a/public/js/components/searchbar/action.js b/public/js/components/searchbar/action.js deleted file mode 100644 index 699d5f8c7..000000000 --- a/public/js/components/searchbar/action.js +++ /dev/null @@ -1,31 +0,0 @@ -export default { - props: { - res: { - type: Object - }, - action: { - type: Object - }, - cssclass: { - type: String, - default: '' - } - }, - emits: [ 'actionexecuted' ], - template: ` - - Action - - `, - methods: { - getactionhref: function() { - return (this.action.type === 'link') ? this.action.action(this.res) - : 'javascript:void(0);'; - }, - execaction: function() { - this.action.action(this.res); - this.$emit('actionexecuted'); - } - } -}; \ No newline at end of file diff --git a/public/js/components/searchbar/actions.js b/public/js/components/searchbar/actions.js deleted file mode 100644 index ff9e25e12..000000000 --- a/public/js/components/searchbar/actions.js +++ /dev/null @@ -1,28 +0,0 @@ -import action from "./action.js"; - -export default { - props: [ "res", "actions" ], - components: { - action: action - }, - emits: [ 'actionexecuted' ], - template: ` -
    -
  • - - - {{ action.label }} - -
  • -
-
- `, - methods: { - hasicon: function(index) { - return (typeof this.actions[index].icon !== "undefined"); - }, - geticonclass: function(index) { - return this.actions[index].icon; - } - } -}; \ No newline at end of file diff --git a/public/js/components/searchbar/employee.js b/public/js/components/searchbar/employee.js deleted file mode 100644 index afb947874..000000000 --- a/public/js/components/searchbar/employee.js +++ /dev/null @@ -1,89 +0,0 @@ -import action from "./action.js"; -import actions from "./actions.js"; - -export default { - props: [ "res", "actions" ], - components: { - action: action, - actions: actions - }, - emits: [ 'actionexecuted' ], - template: ` -
- -
-
- - - - -
- -
- - {{ res.name }} - - -
- -
- -
-
Standard-Kostenstelle
-
-
    -
  • {{ stdkst }}
  • -
- keine -
-
- -
-
Organisations-Einheit
-
-
    -
  • {{ oe }}
  • -
- keine -
-
- -
-
EMail
- -
- -
-
Telefon
- -
- -
- - - -
-
- -
- `, - methods: { - }, - computed: { - mailtourl: function() { - return 'mailto:' + this.res.email; - }, - telurl: function() { - return 'tel:' + this.res.phone; - } - } -}; \ No newline at end of file diff --git a/public/js/components/searchbar/organisationunit.js b/public/js/components/searchbar/organisationunit.js deleted file mode 100644 index e7b5861e8..000000000 --- a/public/js/components/searchbar/organisationunit.js +++ /dev/null @@ -1,82 +0,0 @@ -import action from "./action.js"; -import actions from "./actions.js"; - -export default { - props: [ "res", "actions" ], - components: { - action: action, - actions: actions - }, - emits: [ 'actionexecuted' ], - template: ` -
- -
-
- - - -
- -
- - {{ res.name }} - - -
- -
- -
-
übergeordnete OrgEinheit
-
- {{ res.parentoe_name }} -
-
- -
-
Gruppen-EMail
- -
- -
-
Leiter
-
-
    -
  • {{ leader.name }}
  • -
- N.N. -
-
- -
-
Mitarbeiter-Anzahl
-
- {{ res.number_of_people }} -
-
- -
- - - -
-
- -
- `, - methods: { - }, - computed: { - mailtourl: function() { - return 'mailto:' + this.res.mailgroup; - }, - telurl: function() { - return 'tel:' + this.res.phone; - } - } -}; \ No newline at end of file diff --git a/public/js/components/searchbar/person.js b/public/js/components/searchbar/person.js deleted file mode 100644 index 4f267cbd4..000000000 --- a/public/js/components/searchbar/person.js +++ /dev/null @@ -1,55 +0,0 @@ -import action from "./action.js"; -import actions from "./actions.js"; - -export default { - props: [ "res", "actions" ], - components: { - action: action, - actions: actions - }, - emits: [ 'actionexecuted' ], - template: ` -
- -
-
- - - - -
- -
- - {{ res.gn }} {{ res.sn }} - - -
- -
-
-
EMail
- -
-
- - - -
-
- -
- `, - methods: { - }, - computed: { - mailtourl: function() { - return 'mailto:' + this.res.mail; - } - } -}; \ No newline at end of file diff --git a/public/js/components/searchbar/prestudent.js b/public/js/components/searchbar/prestudent.js deleted file mode 100644 index 0f7429075..000000000 --- a/public/js/components/searchbar/prestudent.js +++ /dev/null @@ -1,88 +0,0 @@ -import action from "./action.js"; -import actions from "./actions.js"; - -export default { - props: [ "res", "actions" ], - components: { - action: action, - actions: actions - }, - emits: [ 'actionexecuted' ], - template: ` -
- -
-
- - - - -
- -
- - {{ res.name }} - - -
- -
- -
-
Prestudent_id
-
- {{ res.prestudent_id }} -
-
- -
-
Student_uid
-
- {{ res.uid }} -
-
- -
-
Person_id
-
- {{ res.person_id }} -
-
- -
-
Studiengang
-
- {{ res.bezeichnung }} -
-
- -
-
EMail
- -
- - -
- - - -
-
- -
- `, - methods: { - }, - computed: { - mailtourl: function() { - return 'mailto:' + this.res.email; - } - } -}; \ No newline at end of file diff --git a/public/js/components/searchbar/raum.js b/public/js/components/searchbar/raum.js deleted file mode 100644 index 887820d6c..000000000 --- a/public/js/components/searchbar/raum.js +++ /dev/null @@ -1,51 +0,0 @@ -import action from "./action.js"; -import actions from "./actions.js"; - -export default { - props: [ "res", "actions" ], - components: { - action: action, - actions: actions - }, - emits: [ 'actionexecuted' ], - template: ` -
- -
-
- - - -
-
- - {{ res.r }} - - -
- -
-
-
Gebäude
-
{{ res.g }}
-
-
-
Stockwerk
-
{{ res.s }}
-
-
-
Raumnummer
-
{{ res.rn }}
-
-
- - - -
-
- -
- `, - methods: { - } -}; \ No newline at end of file diff --git a/public/js/components/searchbar/student.js b/public/js/components/searchbar/student.js deleted file mode 100644 index 73253237c..000000000 --- a/public/js/components/searchbar/student.js +++ /dev/null @@ -1,80 +0,0 @@ -import action from "./action.js"; -import actions from "./actions.js"; - -export default { - props: [ "res", "actions" ], - components: { - action: action, - actions: actions - }, - emits: [ 'actionexecuted' ], - template: ` -
- -
-
- - - - -
- -
- - {{ res.name }} - - -
- -
- -
-
Student_uid
-
- {{ res.uid }} -
-
- -
-
Person_id
-
- {{ res.person_id }} -
-
- -
-
Matrikelnummer
-
- {{ res.matrikelnr }} -
-
- -
-
EMail
- -
- -
- - - -
-
- -
- `, - methods: { - }, - computed: { - mailtourl: function() { - return 'mailto:' + this.res.email; - } - } -}; \ No newline at end of file From 084a13316d128ad9a0330473d7cddf301ac10532 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Mon, 28 Oct 2024 11:17:03 +0100 Subject: [PATCH 0016/1216] Remove duplicate emails --- public/js/components/searchbar/result/mergedperson.js | 2 +- public/js/components/searchbar/result/person.js | 7 ++++++- public/js/components/searchbar/result/prestudent.js | 7 ++++++- public/js/components/searchbar/result/student.js | 7 ++++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/public/js/components/searchbar/result/mergedperson.js b/public/js/components/searchbar/result/mergedperson.js index 09b967b2f..8f12540be 100644 --- a/public/js/components/searchbar/result/mergedperson.js +++ b/public/js/components/searchbar/result/mergedperson.js @@ -37,7 +37,7 @@ export default { }, emails() { if (Array.isArray(this.person.email)) - return this.person.email; + return new Set(this.person.email); return [this.person.email]; } }, diff --git a/public/js/components/searchbar/result/person.js b/public/js/components/searchbar/result/person.js index 65155a95f..daa968e72 100644 --- a/public/js/components/searchbar/result/person.js +++ b/public/js/components/searchbar/result/person.js @@ -9,6 +9,11 @@ export default { res: Object, actions: Object }, + computed: { + emails() { + return new Set(this.res.email); + } + }, template: `
EMails
diff --git a/public/js/components/searchbar/result/prestudent.js b/public/js/components/searchbar/result/prestudent.js index 662069b75..4a11ac941 100644 --- a/public/js/components/searchbar/result/prestudent.js +++ b/public/js/components/searchbar/result/prestudent.js @@ -9,6 +9,11 @@ export default { res: Object, actions: Object }, + computed: { + emails() { + return new Set(this.res.email); + } + }, template: `
EMails
diff --git a/public/js/components/searchbar/result/student.js b/public/js/components/searchbar/result/student.js index 93033d08b..3eb401d97 100644 --- a/public/js/components/searchbar/result/student.js +++ b/public/js/components/searchbar/result/student.js @@ -9,6 +9,11 @@ export default { res: Object, actions: Object }, + computed: { + emails() { + return new Set(this.res.email); + } + }, template: `
EMails
From b1f4e5487d0fdd1977000bb91fc0b82fe5eacb3d Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Mon, 28 Oct 2024 11:18:25 +0100 Subject: [PATCH 0017/1216] Bug: wrong function name --- application/libraries/SearchBarLib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index 782e30843..6d817c20f 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -778,7 +778,7 @@ class SearchBarLib if (strstr($tpl, '{word}')) $tpl = str_replace('{word}', $this->_ci->db->escape($word), $tpl); if (strstr($tpl, '{like:word}')) - $tpl = str_replace('{like:word}', "'%" . $this->_ci->db->escapeLike($word) . "%'", $tpl); + $tpl = str_replace('{like:word}', "'%" . $this->_ci->db->escape_like_str($word) . "%'", $tpl); return $tpl; } From 963391ec99a061dd96de350afe80da7cb12f9ce2 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Mon, 28 Oct 2024 11:20:51 +0100 Subject: [PATCH 0018/1216] Bug: counting "with" statements + allowing multi primarykey + allowing "recursive" statement in prepare config --- application/libraries/SearchBarLib.php | 142 +++++++++++++++++-------- 1 file changed, 96 insertions(+), 46 deletions(-) diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index 6d817c20f..54591b11f 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -162,8 +162,14 @@ class SearchBarLib $lang = getUserLanguage(); - $output = " - WITH lang (index) AS ( + $output = "WITH"; + if ($sql_with && $sql_with[0] === 'RECURSIVE') { + $output .= " RECURSIVE"; + array_shift($sql_with); + } + + $output .= " + lang (index) AS ( SELECT index FROM public.tbl_sprache WHERE sprache=" . $this->_ci->db->escape($lang) . " @@ -185,10 +191,10 @@ class SearchBarLib $other_selects = ", " . $other_selects; $output .= " - , q (" . $table_config['primarykey'] . ", rank) AS ( - SELECT " . $table_config['primarykey'] . ", MAX(rank) + , q (" . $this->_formatPrimarykeys($table_config['primarykey']) . ", rank) AS ( + SELECT " . $this->_formatPrimarykeys($table_config['primarykey']) . ", MAX(rank) FROM (" . implode(" UNION ", $sql_select) . ") q - GROUP BY " . $table_config['primarykey'] . " + GROUP BY " . $this->_formatPrimarykeys($table_config['primarykey']) . " ) SELECT " . $this->_ci->db->escape($table) . " AS type, @@ -225,10 +231,10 @@ class SearchBarLib if (!$select) continue; - $with[] = "final_" . $type . " (" . $table_config['primarykey'] . ", rank) AS ( - SELECT " . $table_config['primarykey'] . ", MAX(rank) + $with[] = "final_" . $type . " (" . $this->_formatPrimarykeys($table_config['primarykey']) . ", rank) AS ( + SELECT " . $this->_formatPrimarykeys($table_config['primarykey']) . ", MAX(rank) FROM (" . implode(" UNION ", $select) . ") q - GROUP BY " . $table_config['primarykey'] . " + GROUP BY " . $this->_formatPrimarykeys($table_config['primarykey']) . " )"; $selects[] = " @@ -242,6 +248,12 @@ class SearchBarLib if (!$selects) return success(""); + $recursive = ""; + if ($with && $with[0] === "RECURSIVE") { + $recursive = "RECURSIVE "; + array_shift($with); + } + $with = array_unique($with); $lang = getUserLanguage(); @@ -256,7 +268,7 @@ class SearchBarLib )"); return success(" - WITH " . implode(", ", $with) . " + WITH " . $recursive . implode(", ", $with) . " SELECT * FROM (" . implode(" UNION ", $selects) . ") q ORDER BY rank DESC @@ -399,10 +411,7 @@ class SearchBarLib $sql_select = []; if (isset($table_config['prepare'])) { - if (is_array($table_config['prepare'])) - $sqlWith = $table_config['prepare']; - else - $sqlWith[] = $table_config['prepare']; + $this->_addPreparesToSqlWith($sqlWith, $table_config['prepare']); } foreach ($searchArray as $or_search) { @@ -484,17 +493,14 @@ class SearchBarLib $field_config = $table_config['searchfields'][$code]; if (isset($field_config['prepare'])) { - if (is_array($field_config['prepare'])) - $or_with = array_merge($or_with, $field_config['prepare']); - else - $or_with[] = $field_config['prepare']; + $this->_addPreparesToSqlWith($or_with, $field_config['prepare']); $or_prepare[$code] = $field_config['prepare']; unset($table_config['searchfields'][$code]['prepare']); unset($field_config['prepare']); } $field_sql = " SELECT - " . $table_config['table'] . "." . $table_config['primarykey'] . " + " . $this->_formatPrimarykeys($table_config['primarykey'], $table_config['table']) . " FROM " . $table_config['table'] . " " . $this->_makeJoin($field_config['join']) . " WHERE "; @@ -506,7 +512,7 @@ class SearchBarLib $or_select[] = " SELECT - " . $table_config['primarykey'] . ", + " . $table_config['table']($table_config['primarykey']) . ", 1.0 AS rank FROM " . $table_config['table'] . " WHERE prestudent_id NOT IN (" . implode(" UNION ", $sql) . ")"; @@ -537,16 +543,13 @@ class SearchBarLib $word_rank = "0"; if ($current_select) { $word_from = $current_select; - if ($field_config['field'] != $table_config['primarykey']) { + if ($this->_needBasicTableJoin($field_config['field'], $table_config['primarykey'])) { $word_join .= " " . $this->_makeJoin($table_config); } $word_rank = "rank"; } if (isset($field_config['prepare'])) { - if (is_array($field_config['prepare'])) - $or_with = array_merge($or_with, $field_config['prepare']); - else - $or_with[] = $field_config['prepare']; + $this->_addPreparesToSqlWith($or_with, $field_config['prepare']); $or_prepare[$c] = $field_config['prepare']; unset($table_config['searchfields'][$c]['prepare']); unset($field_config['prepare']); @@ -556,7 +559,7 @@ class SearchBarLib } $field_sql[] = " SELECT - " . $word_from . "." . $table_config['primarykey'] . ", + " . $this->_formatPrimarykeys($table_config['primarykey'], $word_from) . ", " . $word_rank . " AS w_rank, " . $this->_makeRank($field_config['comparison'], $field_config['field'], $word) . " AS rank FROM " . $word_from . " @@ -576,15 +579,15 @@ class SearchBarLib $id = "w" . ($id_offset + count($or_with)); $or_with[] = " - " . $id . " (" . $table_config['primarykey'] . ", rank) AS ( + " . $id . " (" . $this->_formatPrimarykeys($table_config['primarykey']) . ", rank) AS ( SELECT - " . $table_config['primarykey'] . ", + " . $this->_formatPrimarykeys($table_config['primarykey']) . ", (w_rank + 1.0 - CASE " . "WHEN MIN(rank) = 0 THEN 0 " . "ELSE EXP(SUM(LN(CASE WHEN rank = 0 THEN 1 ELSE rank " . "END))) END) AS rank FROM (" . implode(' UNION ALL ', $field_sql) . ") " . $id . " - GROUP BY " . $table_config['primarykey'] . ", w_rank + GROUP BY " . $this->_formatPrimarykeys($table_config['primarykey']) . ", w_rank )"; $current_select = $id; } @@ -611,7 +614,7 @@ class SearchBarLib ")"; if ($field_config['1-n'] ?? false) { $where = "GROUP BY " . - $table_config['primarykey'] . + $this->_formatPrimarykeys($table_config['primarykey'], $current_select ?: $table_config['table']) . ", rank HAVING MIN(CASE WHEN " . $where . " THEN 1 ELSE 0 END) = 1"; @@ -631,16 +634,13 @@ class SearchBarLib $word_rank = ""; if ($current_select) { $word_from = $current_select; - if ($field_config['field'] != $table_config['primarykey']) { + if ($this->_needBasicTableJoin($field_config['field'], $table_config['primarykey'])) { $word_join .= " " . $this->_makeJoin($table_config); } $word_rank = "rank + "; } if (isset($field_config['prepare'])) { - if (is_array($field_config['prepare'])) - $or_with = array_merge($or_with, $field_config['prepare']); - else - $or_with[] = $field_config['prepare']; + $this->_addPreparesToSqlWith($or_with, $field_config['prepare']); $or_prepare[$code] = $field_config['prepare']; unset($table_config['searchfields'][$code]['prepare']); unset($field_config['prepare']); @@ -651,9 +651,9 @@ class SearchBarLib $id = "w" . ($id_offset + count($or_with)); $or_with[] = " - " . $id . " (" . $table_config['primarykey'] . ", rank) AS ( + " . $id . " (" . $this->_formatPrimarykeys($table_config['primarykey']) . ", rank) AS ( SELECT - " . $word_from . "." . $table_config['primarykey'] . ", + " . $this->_formatPrimarykeys($table_config['primarykey'], $word_from) . ", " . $word_rank . $rank . " AS rank FROM " . $word_from . " " . $word_join . " @@ -671,11 +671,12 @@ class SearchBarLib continue; $or_select[] = " - SELECT " . $table_config['primarykey'] . ", rank / " . $count . " AS rank FROM " . $current_select; + SELECT " . $this->_formatPrimarykeys($table_config['primarykey']) . ", rank / " . $count . " AS rank FROM " . $current_select; } $sqlWith = array_merge($sqlWith, $or_with); $sql_select = array_merge($sql_select, $or_select); + $id_offset += count($or_with); } return $sql_select; @@ -685,27 +686,76 @@ class SearchBarLib // Private methods /** - * Search for documents + * Checks if the field is not one of the primarykeys. + * + * @param string $field + * @param array|string $primarykeys + * + * @return boolean */ - private function _document($searchstr, $type) + private function _needBasicTableJoin($field, $primarykeys) { - return array(); + if (!is_array($primarykeys) && strpos($primarykeys, ",") !== false) { + return $field != $primarykeys; + } + if (!is_array($primarykeys)) + $primarykeys = explode(",", $primarykeys); + + foreach ($primarykeys as $key) { + if ($field == trim($key)) + return false; + } + return true; } /** - * Search for CMSs + * Returns comma separated primarykeys. Optionally with table prefix + * + * @param array|string $primarykeys + * @param string $prefix + * + * @return string */ - private function _cms($searchstr, $type) + private function _formatPrimarykeys($primarykeys, $prefix = "") { - return array(); + if (is_array($primarykeys)) { + if ($prefix) + $prefix .= "."; + return $prefix . implode(", " . $prefix, $primarykeys); + } + if (!$prefix) + return $primarykeys; + + return $prefix . "." . implode(", " . $prefix . ".", explode(",", $primarykeys)); } /** - * Search for rooms + * Adds the prepare statement to the sqlWith stack and handles the + * "RECURSIVE" modifier + * + * @param array &$sqlWith + * @param array $prepares + * + * @return void */ - private function _raum($searchstr, $type) + private function _addPreparesToSqlWith(&$sqlWith, $prepares) { - return array(); + $recursive = $sqlWith[0] ?? "" === "RECURSIVE"; + if (!is_array($prepares)) + $prepares = [$prepares]; + + foreach ($prepares as $prep) { + $prep = trim($prep); + if (strtoupper(substr($prep, 0, 10)) === "RECURSIVE ") { + $recursive = true; + $sqlWith[] = substr($prep, 10); + } else { + $sqlWith[] = $prep; + } + } + if ($recursive && $sqlWith[0] !== "RECURSIVE") { + array_unshift($sqlWith, "RECURSIVE"); + } } /** From 8d00a9b4c9b5fa160bbbd7db391a2c45b6399b02 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 31 Oct 2024 14:22:31 +0100 Subject: [PATCH 0019/1216] Bugfix --- application/config/search.php | 6 +++++- application/libraries/SearchBarLib.php | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/application/config/search.php b/application/config/search.php index f52d755dd..aa99c17e0 100644 --- a/application/config/search.php +++ b/application/config/search.php @@ -400,7 +400,11 @@ $config['employee'] = [ 'pid' => [ 'alias' => ['person_id'], 'comparison' => 'equal-int', - 'field' => "person_id" + 'field' => "person_id", + 'join' => [ + 'table' => "public.tbl_benutzer", + 'on' => "uid = mitarbeiter_uid" + ] ], 'oe' => [ 'alias' => ['ou', 'organisationseinheit', 'organisationunit'], diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index 54591b11f..91abeaebb 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -242,7 +242,8 @@ class SearchBarLib " . $this->_ci->db->escape($type) . " AS type, rank, TO_JSONB((SELECT x FROM (SELECT " . implode(", ", $table_config['resultfields'] ?? ['*']) . ") x)) AS data - FROM final_" . $type . ($table_config['resultjoin'] ?? ""); + FROM final_" . $type . " + " . ($table_config['resultjoin'] ?? ""); } if (!$selects) From 0916d3476744fb3504c20a6c92081ed82cb46b77 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 31 Oct 2024 14:25:27 +0100 Subject: [PATCH 0020/1216] DMS Search --- application/config/search.php | 135 +++++++++++++++----- public/js/components/searchbar/searchbar.js | 3 + system/dbupdate_3.4/40128_search.php | 15 +++ 3 files changed, 119 insertions(+), 34 deletions(-) diff --git a/application/config/search.php b/application/config/search.php index aa99c17e0..488a1ab3b 100644 --- a/application/config/search.php +++ b/application/config/search.php @@ -3,10 +3,6 @@ if (! defined('BASEPATH')) exit('No direct script access allowed'); -// TODO(chris): permissions -// TODO(chris): foto - - $config['person'] = [ 'primarykey' => 'person_id', 'table' => 'public.tbl_person', @@ -70,7 +66,7 @@ $config['person'] = [ ] ], 'resultfields' => [ - "b.uid", + "b.uid", // TODO(chris): multiple? "p.person_id", "(p.vorname || ' ' || p.nachname) AS name", "ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email", @@ -678,7 +674,7 @@ $config['room'] = [ $config['cms'] = [ 'primarykey' => 'contentsprache_id', - 'table' => 'cms', + 'table' => 'campus.tbl_contentsprache', 'prepare' => " cms_auth (content_id) AS ( SELECT content_id @@ -711,47 +707,23 @@ $config['cms'] = [ SELECT content_id FROM cms_active WHERE template_kurzbz IN ('contentmittitel', 'contentohnetitel', 'contentmittitel_filterwidget') - ), - cms (contentsprache_id) AS ( - SELECT contentsprache_id - FROM campus.tbl_contentsprache - WHERE content_id IN ( - SELECT content_id - FROM cms_active_redirect_linked - UNION - SELECT content_id - FROM cms_active_others - ) - AND version = campus.get_highest_content_version(content_id) ) ", 'searchfields' => [ 'content' => [ 'alias' => ['inhalt'], 'comparison' => "vector", - 'field' => "(setweight(to_tsvector('simple', COALESCE(titel, '')), 'A') || setweight(to_tsvector('simple', COALESCE(content, '')::text), 'B'))", - 'join' => [ - 'table' => "campus.tbl_contentsprache", - 'using' => "contentsprache_id" - ] + 'field' => "(setweight(to_tsvector('simple', COALESCE(titel, '')), 'A') || setweight(to_tsvector('simple', COALESCE(content, '')::text), 'B'))" ], 'content_id' => [ 'alias' => ['id'], 'comparison' => "equal-int", - 'field' => "content_id", - 'join' => [ - 'table' => "campus.tbl_contentsprache", - 'using' => "contentsprache_id" - ] + 'field' => "content_id" ], 'lang' => [ 'alias' => ['language', 'sprache'], 'comparison' => "equals", - 'field' => "sprache", - 'join' => [ - 'table' => "campus.tbl_contentsprache", - 'using' => "contentsprache_id" - ] + 'field' => "sprache" ] ], 'resultfields' => [ @@ -767,5 +739,100 @@ $config['cms'] = [ JOIN campus.tbl_contentsprache contentsprache USING (contentsprache_id) JOIN campus.tbl_content content - USING (content_id)" + USING (content_id) + WHERE content_id IN ( + SELECT content_id + FROM cms_active_redirect_linked + UNION + SELECT content_id + FROM cms_active_others + ) + AND version = campus.get_highest_content_version(content_id)" +]; + +$config['dms'] = [ + // TODO(chris): IMPLEMENT + // TODO(chris): TEST + // TODO(chris): project? + 'primarykey' => 'dms_id, version', + 'table' => 'campus.tbl_dms_version', + 'searchfields' => [ + 'keywords' => [ + 'alias' => ['keyword', 'keywords', 'schlagwort', 'schlagworte'], + 'comparison' => "vector", + 'field' => "(to_tsvector('simple', COALESCE(schlagworte, '')))" + ] + ], + 'resultfields' => [ + "v.dms_id", + "v.version", + "v.filename", + "v.mimetype", + "v.name", + "v.beschreibung AS description", + "v.schlagworte AS keywords" + ], + 'resultjoin' => " + JOIN campus.tbl_dms_version v + USING (dms_id, version) + WHERE cis_suche = TRUE + AND version=(SELECT MAX(version) FROM campus.tbl_dms_version WHERE dms_id=v.dms_id) + AND NOT EXISTS ( + SELECT + 1 + FROM + fue.tbl_projekt_dokument p + WHERE p.dms_id = v.dms_id + ) AND ( + NOT EXISTS ( + WITH RECURSIVE categories (kategorie_kurzbz) AS ( + SELECT + kategorie_kurzbz + FROM + campus.tbl_dms c + WHERE c.dms_id = v.dms_id + UNION ALL + SELECT + cat.parent_kategorie_kurzbz AS kategorie_kurzbz + FROM + categories + JOIN campus.tbl_dms_kategorie cat USING (kategorie_kurzbz) + ) + SELECT + 1 + FROM + categories + JOIN campus.tbl_dms_kategorie_gruppe USING (kategorie_kurzbz) + UNION + SELECT + 1 + FROM + categories + JOIN campus.tbl_dms_kategorie USING (kategorie_kurzbz) + WHERE + berechtigung_kurzbz IS NOT NULL + ) OR EXISTS ( + WITH RECURSIVE categories (kategorie_kurzbz) AS ( + SELECT + kategorie_kurzbz + FROM + campus.tbl_dms c + WHERE c.dms_id = v.dms_id + UNION ALL + SELECT + cat.parent_kategorie_kurzbz AS kategorie_kurzbz + FROM + categories + JOIN campus.tbl_dms_kategorie cat USING (kategorie_kurzbz) + ) + SELECT + 1 + FROM + categories + JOIN campus.tbl_dms_kategorie_gruppe USING (kategorie_kurzbz) + JOIN public.tbl_benutzergruppe USING(gruppe_kurzbz) + WHERE + uid = (TABLE auth) + ) + )" ]; diff --git a/public/js/components/searchbar/searchbar.js b/public/js/components/searchbar/searchbar.js index 8ac64e856..7589cfbef 100644 --- a/public/js/components/searchbar/searchbar.js +++ b/public/js/components/searchbar/searchbar.js @@ -5,6 +5,7 @@ import ResultEmployee from "./result/employee.js"; import ResultOrganisationunit from "./result/organisationunit.js"; import ResultRoom from "./result/room.js"; import ResultCms from "./result/cms.js"; +import ResultDms from "./result/dms.js"; import ResultMergedperson from "./result/mergedperson.js"; import ResultMergedstudent from "./result/mergedstudent.js"; @@ -19,6 +20,7 @@ export default { ResultOrganisationunit, ResultRoom, ResultCms, + ResultDms, ResultMergedperson, ResultMergedstudent }, @@ -263,6 +265,7 @@ export default { +
Unbekannter Ergebnistyp: '{{ res.type }}'.
diff --git a/system/dbupdate_3.4/40128_search.php b/system/dbupdate_3.4/40128_search.php index 92e1a10fd..8ca0fd246 100644 --- a/system/dbupdate_3.4/40128_search.php +++ b/system/dbupdate_3.4/40128_search.php @@ -184,3 +184,18 @@ FROM pg_indexes WHERE indexname = 'idx_tbl_contentsprache_fts_titel_content_vect else echo 'campus.tbl_contentsprache: added index "idx_tbl_contentsprache_fts_titel_content_vector"
'; } +// Add index for schlagworte to campus.tbl_dms_version +if (!$db->db_num_rows(@$db->db_query("SELECT 1 +FROM pg_indexes WHERE indexname = 'idx_tbl_dms_version_fts_schlagworte_vector' LIMIT 1;"))) +{ + $qry = " + CREATE INDEX idx_tbl_dms_version_fts_schlagworte_vector + ON campus.tbl_dms_version + USING GIN ((to_tsvector('simple', COALESCE(schlagworte, '')))); + "; + + if (!$db->db_query($qry)) + echo 'campus.tbl_dms_version ' . $db->db_last_error() . '
'; + else + echo 'campus.tbl_contentsprache: added index "idx_tbl_dms_version_fts_schlagworte_vector"
'; +} From b27564ea0f22d6cf414bfa334e154e13ff325560 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Tue, 5 Nov 2024 09:01:18 +0100 Subject: [PATCH 0021/1216] Multiple uids in person search --- application/config/search.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/application/config/search.php b/application/config/search.php index 488a1ab3b..8c601e046 100644 --- a/application/config/search.php +++ b/application/config/search.php @@ -66,7 +66,7 @@ $config['person'] = [ ] ], 'resultfields' => [ - "b.uid", // TODO(chris): multiple? + "ARRAY( SELECT uid FROM public.tbl_benutzer WHERE person_id = p.person_id ) AS uids", "p.person_id", "(p.vorname || ' ' || p.nachname) AS name", "ARRAY( SELECT kontakt FROM public.tbl_kontakt WHERE kontakttyp = 'email' AND person_id=p.person_id ) AS email", @@ -76,8 +76,7 @@ $config['person'] = [ AS photo_url" ], 'resultjoin' => " - JOIN public.tbl_person p USING (person_id) - LEFT JOIN public.tbl_benutzer b USING (person_id)" + JOIN public.tbl_person p USING (person_id)" ]; $config['student'] = [ @@ -210,7 +209,6 @@ $config['student'] = [ JOIN public.tbl_person p USING(person_id)" ]; -// TODO(chris): "ref" $config['prestudent'] = [ 'primarykey' => 'prestudent_id', 'table' => 'public.tbl_prestudent', @@ -751,9 +749,6 @@ $config['cms'] = [ ]; $config['dms'] = [ - // TODO(chris): IMPLEMENT - // TODO(chris): TEST - // TODO(chris): project? 'primarykey' => 'dms_id, version', 'table' => 'campus.tbl_dms_version', 'searchfields' => [ From 2ff7c9f5823484b6ae929e1fdc6e8ba778ff8862 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Tue, 5 Nov 2024 09:02:09 +0100 Subject: [PATCH 0022/1216] Dms search result component --- public/js/components/searchbar/result/dms.js | 77 ++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 public/js/components/searchbar/result/dms.js diff --git a/public/js/components/searchbar/result/dms.js b/public/js/components/searchbar/result/dms.js new file mode 100644 index 000000000..74fc9440b --- /dev/null +++ b/public/js/components/searchbar/result/dms.js @@ -0,0 +1,77 @@ +import TemplateFrame from "./template/frame.js"; + +export default { + components: { + TemplateFrame + }, + emits: [ 'actionexecuted' ], + props: { + res: Object, + actions: Object + }, + inject: [ + 'query' + ], + computed: { + icon() { + switch (this.res.mimetype) { + case 'application/pdf': + return 'file-pdf'; + case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': + case 'application/msword': + return 'file-word'; + case 'application/vnd.openxmlformats-officedocument.presentationml.presentation': + case 'application/mspowerpoint': + return 'file-powerpoint'; + case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': + case 'application/vnd.ms-excel': + return 'file-excel'; + case 'application/x-zip': + case 'application/zip': + return 'file-zipper'; + case 'image/jpeg': + case 'image/gif': + case 'image/png': + return 'file-image'; + default: + return 'file'; + } + } + }, + template: ` + +
+
+
DMS ID
+
+ {{ res.dms_id }} +
+
+
+
Version
+
+ {{ res.version }} +
+
+
+
Keywords
+
+ {{ res.keywords }} +
+
+
+
Description
+
+ {{ res.description }} +
+
+
+
` +}; \ No newline at end of file From 3a13226298323f8bf5bd60e4e903755ad40cf0f9 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Tue, 5 Nov 2024 11:08:07 +0100 Subject: [PATCH 0023/1216] Bug: merge "RECURSIVE"-keyword --- application/libraries/SearchBarLib.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index 91abeaebb..d563a9c79 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -675,6 +675,12 @@ class SearchBarLib SELECT " . $this->_formatPrimarykeys($table_config['primarykey']) . ", rank / " . $count . " AS rank FROM " . $current_select; } + if ($or_with[0] === "RECURSIVE") { + if ($sqlWith[0] !== "RECURSIVE") + array_unshift($sqlWith, "RECURSIVE"); + array_shift($or_with); + } + $sqlWith = array_merge($sqlWith, $or_with); $sql_select = array_merge($sql_select, $or_select); $id_offset += count($or_with); From d0cf86585a174ef777accb102d71f0fcb6668169 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Tue, 5 Nov 2024 11:42:18 +0100 Subject: [PATCH 0024/1216] Visual update for search icons --- public/css/components/searchbar.css | 74 +++++++++++++------ public/js/components/searchbar/result/cms.js | 2 +- public/js/components/searchbar/result/dms.js | 2 +- .../components/searchbar/result/employee.js | 2 +- .../searchbar/result/mergedperson.js | 2 +- .../searchbar/result/organisationunit.js | 2 +- .../js/components/searchbar/result/person.js | 2 +- .../components/searchbar/result/prestudent.js | 2 +- public/js/components/searchbar/result/room.js | 2 +- .../js/components/searchbar/result/student.js | 2 +- .../searchbar/result/template/frame.js | 5 +- 11 files changed, 64 insertions(+), 33 deletions(-) diff --git a/public/css/components/searchbar.css b/public/css/components/searchbar.css index 61032d227..37eeaef83 100644 --- a/public/css/components/searchbar.css +++ b/public/css/components/searchbar.css @@ -97,37 +97,69 @@ /* new variant with template/frame */ .searchbar-result { - border-bottom: 1px solid lightgrey; - margin-bottom: 1rem; - padding-bottom: 1rem; + border-bottom: 1px solid lightgrey; + margin-bottom: 1rem; + padding-bottom: 1rem; } .searchbar-actions { - display: flex; - flex-wrap: wrap; - gap: .5rem; - padding: 0; - margin: 1rem 0 0; + display: flex; + flex-wrap: wrap; + gap: .5rem; + padding: 0; + margin: 1rem 0 0; } .searchbar-square-image { - position: relative; - display: block; - height: 0; - padding-bottom: 100%; + position: relative; + display: block; + height: 0; + padding-bottom: 100%; +} +.searchbar-square-image > * { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; +} +.searchbar-square-image > img { + object-fit: cover; +} +.searchbar-square-image > div { + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + color: rgba(var(--bs-white-rgb),1); + background-color: rgba(var(--bs-primary-rgb),var(--bs-bg-opacity)); +} +.searchbar-square-image.rounded-circle > img, +.searchbar-square-image.rounded-circle > div { + border-radius: 50%; } -.searchbar-square-image > * { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; +.searchbar-rounded-image { + display: block; + width: 100%; } -.searchbar-square-image img { - object-fit: cover; +.searchbar-rounded-image > img { + width: 100%; +} +.searchbar-rounded-image > div { + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + color: rgba(var(--bs-white-rgb),1); + background-color: rgba(var(--bs-primary-rgb),var(--bs-bg-opacity)); + height: calc((100px - .75em) * 1.3); +} +.searchbar-rounded-image > img, +.searchbar-rounded-image > div { + border-radius: .25rem; } .no-margin-paragraphs p { - margin: 0; + margin: 0; } \ No newline at end of file diff --git a/public/js/components/searchbar/result/cms.js b/public/js/components/searchbar/result/cms.js index af2d6b0bb..5befdb413 100644 --- a/public/js/components/searchbar/result/cms.js +++ b/public/js/components/searchbar/result/cms.js @@ -69,7 +69,7 @@ export default { :res="res" :actions="actions" :title="res.title" - image-fallback="fas fa-newspaper fa-4x p-4 text-white bg-primary" + image-fallback="fas fa-newspaper fa-4x" @actionexecuted="$emit('actionexecuted')" > diff --git a/public/js/components/searchbar/result/dms.js b/public/js/components/searchbar/result/dms.js index 74fc9440b..ad770f1c6 100644 --- a/public/js/components/searchbar/result/dms.js +++ b/public/js/components/searchbar/result/dms.js @@ -44,7 +44,7 @@ export default { :res="res" :actions="actions" :title="res.name" - :image-fallback="'fas fa-' + icon + ' fa-4x p-5 text-white bg-primary'" + :image-fallback="'fas fa-' + icon + ' fa-4x'" @actionexecuted="$emit('actionexecuted')" >
diff --git a/public/js/components/searchbar/result/employee.js b/public/js/components/searchbar/result/employee.js index 1c4da075a..2fa07bf5a 100644 --- a/public/js/components/searchbar/result/employee.js +++ b/public/js/components/searchbar/result/employee.js @@ -16,7 +16,7 @@ export default { :actions="actions" :title="res.name" :image="res.photo_url" - image-fallback="fas fa-user-circle fa-7x" + image-fallback="fas fa-user fa-4x" @actionexecuted="$emit('actionexecuted')" >
diff --git a/public/js/components/searchbar/result/mergedperson.js b/public/js/components/searchbar/result/mergedperson.js index 8f12540be..cfb7fe5a6 100644 --- a/public/js/components/searchbar/result/mergedperson.js +++ b/public/js/components/searchbar/result/mergedperson.js @@ -48,7 +48,7 @@ export default { :actions="actions" :title="person.name" :image="this.person.photo_url" - image-fallback="fas fa-user-circle fa-7x" + image-fallback="fas fa-user fa-4x" @actionexecuted="$emit('actionexecuted')" >
diff --git a/public/js/components/searchbar/result/organisationunit.js b/public/js/components/searchbar/result/organisationunit.js index f9b6870e0..129479277 100644 --- a/public/js/components/searchbar/result/organisationunit.js +++ b/public/js/components/searchbar/result/organisationunit.js @@ -22,7 +22,7 @@ export default { :res="res" :actions="actions" :title="res.name" - image-fallback="fas fa-sitemap fa-4x p-4 text-white bg-primary" + image-fallback="fas fa-sitemap fa-4x" @actionexecuted="$emit('actionexecuted')" >
diff --git a/public/js/components/searchbar/result/person.js b/public/js/components/searchbar/result/person.js index daa968e72..bc4e75ee8 100644 --- a/public/js/components/searchbar/result/person.js +++ b/public/js/components/searchbar/result/person.js @@ -21,7 +21,7 @@ export default { :actions="actions" :title="res.name" :image="res.photo_url" - image-fallback="fas fa-user-circle fa-7x" + image-fallback="fas fa-user fa-4x" @actionexecuted="$emit('actionexecuted')" >
diff --git a/public/js/components/searchbar/result/prestudent.js b/public/js/components/searchbar/result/prestudent.js index 4a11ac941..f3df3e8ca 100644 --- a/public/js/components/searchbar/result/prestudent.js +++ b/public/js/components/searchbar/result/prestudent.js @@ -21,7 +21,7 @@ export default { :actions="actions" :title="res.name + ' (' + res.status + ' ' + res.stg_kuerzel + ')'" :image="res.photo_url" - image-fallback="fas fa-user-circle fa-7x" + image-fallback="fas fa-user fa-4x" @actionexecuted="$emit('actionexecuted')" >
diff --git a/public/js/components/searchbar/result/room.js b/public/js/components/searchbar/result/room.js index 6da1e970a..23ce6b463 100644 --- a/public/js/components/searchbar/result/room.js +++ b/public/js/components/searchbar/result/room.js @@ -33,7 +33,7 @@ export default { :res="res" :actions="actions" :title="res.ort_kurzbz" - image-fallback="fas fa-door-open fa-4x p-4 text-white bg-primary" + image-fallback="fas fa-door-open fa-4x" @actionexecuted="$emit('actionexecuted')" >
diff --git a/public/js/components/searchbar/result/student.js b/public/js/components/searchbar/result/student.js index 3eb401d97..bb3512e3a 100644 --- a/public/js/components/searchbar/result/student.js +++ b/public/js/components/searchbar/result/student.js @@ -21,7 +21,7 @@ export default { :actions="actions" :title="res.name" :image="res.photo_url" - image-fallback="fas fa-user-circle fa-7x" + image-fallback="fas fa-user fa-4x" @actionexecuted="$emit('actionexecuted')" >
diff --git a/public/js/components/searchbar/result/template/frame.js b/public/js/components/searchbar/result/template/frame.js index 87078a166..336c2d9f9 100644 --- a/public/js/components/searchbar/result/template/frame.js +++ b/public/js/components/searchbar/result/template/frame.js @@ -22,14 +22,13 @@ export default { :res="res" :action="actions.defaultaction" @actionexecuted="$emit('actionexecuted')" - class="searchbar-square-image" + class="searchbar-rounded-image" > -
+
From e3119ee48ce91d3a6e2785d234582d8c0e9479d5 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Tue, 5 Nov 2024 15:35:00 +0100 Subject: [PATCH 0025/1216] cleanup --- application/controllers/api/frontend/v1/Searchbar.php | 1 - application/libraries/SearchBarLib.php | 5 ----- 2 files changed, 6 deletions(-) diff --git a/application/controllers/api/frontend/v1/Searchbar.php b/application/controllers/api/frontend/v1/Searchbar.php index 2d5b06063..d630d3afd 100644 --- a/application/controllers/api/frontend/v1/Searchbar.php +++ b/application/controllers/api/frontend/v1/Searchbar.php @@ -50,7 +50,6 @@ class Searchbar extends FHCAPI_Controller */ public function search() { - #$searchstrings = ['pid:71995', 'email:eder.iris', 'schnabl', 'schnabl thomas', 'schnabl thomas sarim', 'schnabl -thomas', 'nachname:schnabl -vorname:thomas', 'schnabl thomas -sarim', 'schnabl or hacker', '-ali', '-ali -baba', '-ali -baba -raub', '-ali -honig', '-ali -baba ali', 'hofer martin']; $this->load->library('form_validation'); // Checks if the searchstr and the types parameters are in the POSTed JSON diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index d563a9c79..630f31ecf 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -32,11 +32,6 @@ class SearchBarLib const ERROR_WRONG_TYPES = 'ERR004'; const ERROR_NOT_AUTH = 'ERR005'; - // List of allowed types of search - const ALLOWED_TYPES = ['mitarbeiter', 'mitarbeiter_ohne_zuordnung', 'organisationunit', 'raum', 'person', 'student', 'prestudent', 'document', 'cms']; - - const PHOTO_IMG_URL = '/cis/public/bild.php?src=person&person_id='; - private $_ci; // Code igniter instance private $_searchfunction_priorities = []; From 8c689418f7fffe9c54d77aeb5406c1fe55576570 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 7 Nov 2024 09:57:26 +0100 Subject: [PATCH 0026/1216] Bugfixes --- application/libraries/SearchBarLib.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index 630f31ecf..b9c71b16b 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -68,7 +68,6 @@ class SearchBarLib /** * It performes the search of the given search string using the specified search types - * TODO(chris): permissions * * @param string $searchstring * @param array $types (optional) @@ -498,9 +497,8 @@ class SearchBarLib SELECT " . $this->_formatPrimarykeys($table_config['primarykey'], $table_config['table']) . " FROM " . $table_config['table'] . " - " . $this->_makeJoin($field_config['join']) . " + " . $this->_makeJoin($field_config['join'] ?? '') . " WHERE "; - // TODO(chris): equals and equal-int could be IN () statement??? foreach ($words as $word) { $sql[] = $field_sql . $this->_makeCompareBool($field_config['comparison'], $field_config['field'], $word); } @@ -508,10 +506,10 @@ class SearchBarLib $or_select[] = " SELECT - " . $table_config['table']($table_config['primarykey']) . ", + " . $this->_formatPrimarykeys($table_config['primarykey'], $table_config['table']) . ", 1.0 AS rank FROM " . $table_config['table'] . " - WHERE prestudent_id NOT IN (" . implode(" UNION ", $sql) . ")"; + WHERE " . $table_config['primarykey'] . " NOT IN (" . implode(" UNION ", $sql) . ")"; } else { $current_select = false; $count = 0; From f82ed6928aaae0a6d1a648a4ea5d572b2ee6f11b Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 7 Nov 2024 11:47:35 +0100 Subject: [PATCH 0027/1216] Phrasen --- application/libraries/SearchBarLib.php | 8 +- public/js/components/searchbar/result/cms.js | 2 +- public/js/components/searchbar/result/dms.js | 11 +- .../components/searchbar/result/employee.js | 12 +- .../searchbar/result/mergedperson.js | 26 +- .../searchbar/result/organisationunit.js | 17 +- .../js/components/searchbar/result/person.js | 4 +- .../components/searchbar/result/prestudent.js | 12 +- public/js/components/searchbar/result/room.js | 16 +- .../js/components/searchbar/result/student.js | 8 +- .../searchbar/result/template/action.js | 2 +- public/js/components/searchbar/searchbar.js | 18 +- system/phrasesupdate.php | 682 +++++++++++++++++- 13 files changed, 744 insertions(+), 74 deletions(-) diff --git a/application/libraries/SearchBarLib.php b/application/libraries/SearchBarLib.php index b9c71b16b..a14319bba 100644 --- a/application/libraries/SearchBarLib.php +++ b/application/libraries/SearchBarLib.php @@ -95,7 +95,7 @@ class SearchBarLib return error(array_map(function ($type) use ($p) { return $p->t('search', 'error_missing_config', [ 'type' => $type - ]); // TODO(chris): phrase + ]); }, $missing)); } $types = $tmp; @@ -288,7 +288,7 @@ class SearchBarLib if (!$table_config) return error($this->_ci->search_phrases->t('search', 'error_missing_config', [ 'type' => $name - ])); // TODO(chris): phrase + ])); $errors = []; if (!isset($table_config['table']) @@ -298,7 +298,7 @@ class SearchBarLib $errors[] = $this->_ci->search_phrases->t('search', 'error_invalid_config', [ 'type' => $name, 'field' => 'table' - ]); // TODO(chris): phrase + ]); } if (!isset($table_config['primarykey']) || !is_string($table_config['primarykey']) @@ -336,7 +336,7 @@ class SearchBarLib 'type' => $name, 'searchfield' => $searchfield, 'field' => 'field' - ]); // TODO(chris): phrase + ]); } if (!isset($config['comparison']) || !is_string($config['comparison']) diff --git a/public/js/components/searchbar/result/cms.js b/public/js/components/searchbar/result/cms.js index 5befdb413..591c7e79c 100644 --- a/public/js/components/searchbar/result/cms.js +++ b/public/js/components/searchbar/result/cms.js @@ -75,7 +75,7 @@ export default {
- No Content + {{ $p.t('search/result_content_none') }}
` }; \ No newline at end of file diff --git a/public/js/components/searchbar/result/dms.js b/public/js/components/searchbar/result/dms.js index ad770f1c6..75b281541 100644 --- a/public/js/components/searchbar/result/dms.js +++ b/public/js/components/searchbar/result/dms.js @@ -9,9 +9,6 @@ export default { res: Object, actions: Object }, - inject: [ - 'query' - ], computed: { icon() { switch (this.res.mimetype) { @@ -49,25 +46,25 @@ export default { >
-
DMS ID
+
{{ $p.t('search/result_dms_id') }}
{{ res.dms_id }}
-
Version
+
{{ $p.t('search/result_version') }}
{{ res.version }}
-
Keywords
+
{{ $p.t('search/result_keywords') }}
{{ res.keywords }}
-
Description
+
{{ $p.t('global/beschreibung') }}
{{ res.description }}
diff --git a/public/js/components/searchbar/result/employee.js b/public/js/components/searchbar/result/employee.js index 2fa07bf5a..a86fc6cbf 100644 --- a/public/js/components/searchbar/result/employee.js +++ b/public/js/components/searchbar/result/employee.js @@ -21,25 +21,25 @@ export default { >
-
Standard-Kostenstelle
+
{{ $p.t('search/result_stdkst') }}
  • {{ stdkst }}
- keine + {{ $p.t('search/result_stdkst_none') }}
-
Organisations-Einheit
+
{{ $p.t('lehre/organisationseinheit') }}
  • {{ oe }}
- keine + {{ $p.t('search/result_oe_none') }}
-
EMails
+
{{ $p.t('search/result_emails') }}
-
Telefon
+
{{ $p.t('person/telefon') }}
{{ res.phone }} diff --git a/public/js/components/searchbar/result/mergedperson.js b/public/js/components/searchbar/result/mergedperson.js index cfb7fe5a6..c0349c2e3 100644 --- a/public/js/components/searchbar/result/mergedperson.js +++ b/public/js/components/searchbar/result/mergedperson.js @@ -47,19 +47,19 @@ export default { :res="person" :actions="actions" :title="person.name" - :image="this.person.photo_url" + :image="person.photo_url" image-fallback="fas fa-user fa-4x" @actionexecuted="$emit('actionexecuted')" >
-
Person ID
+
{{ $p.t('person/person_id') }}
{{ person.person_id }}
-
Standard-Kostenstelle
+
{{ $p.t('search/result_stdkst') }}
- keine + {{ $p.t('search/result_stdkst_none') }}
-
Organisations-Einheit
+
{{ $p.t('lehre/organisationseinheit') }}
- keine + {{ $p.t('search/result_oe_none') }}
-
Studiengang
+
{{ $p.t('lehre/studiengang') }}
{{ student.bezeichnung }}
-
Prestudent ID
+
{{ $p.t('search/result_prestudent_id') }}
{{ student.prestudent_id }}
-
Student UID
+
{{ $p.t('search/result_student_uid') }}
{{ student.uid }}
-
Matrikelnummer
+
{{ $p.t('person/matrikelnummer') }}
{{ student.matrikelnr }}
diff --git a/public/js/components/searchbar/result/organisationunit.js b/public/js/components/searchbar/result/organisationunit.js index 129479277..fe5d6512a 100644 --- a/public/js/components/searchbar/result/organisationunit.js +++ b/public/js/components/searchbar/result/organisationunit.js @@ -9,13 +9,6 @@ export default { res: Object, actions: Object }, - computed: { - foto() { - if (this.res.foto) - return 'data:image/jpeg;base64,' + this.res.foto; - return null; - } - }, template: `
-
übergeordnete OrgEinheit
+
{{ $p.t('search/result_parent_oe') }}
{{ res.parentoe_name }}
-
Gruppen-EMail
+
{{ $p.t('search/result_group_emails') }}
-
Leiter
+
{{ $p.t('search/result_leader') }}
  • {{ leader.name }}
- N.N. + {{ $p.t('search/result_leader_none') }}
-
Mitarbeiter-Anzahl
+
{{ $p.t('search/result_number_of_employees') }}
{{ res.number_of_people }}
diff --git a/public/js/components/searchbar/result/person.js b/public/js/components/searchbar/result/person.js index bc4e75ee8..f6ce0a5f0 100644 --- a/public/js/components/searchbar/result/person.js +++ b/public/js/components/searchbar/result/person.js @@ -26,13 +26,13 @@ export default { >
-
Person ID
+
{{ $p.t('person/person_id') }}
{{ res.person_id }}
-
EMails
+
{{ $p.t('search/result_emails') }}
{{ email }} diff --git a/public/js/components/searchbar/result/prestudent.js b/public/js/components/searchbar/result/prestudent.js index f3df3e8ca..51b4ccd9b 100644 --- a/public/js/components/searchbar/result/prestudent.js +++ b/public/js/components/searchbar/result/prestudent.js @@ -26,13 +26,13 @@ export default { >
-
Person ID
+
{{ $p.t('person/person_id') }}
{{ res.person_id }}
-
Student UID
+
{{ $p.t('search/result_student_uid') }}
{{ res.uid }}
-
Matrikelnummer
+
{{ $p.t('person/matrikelnummer') }}
{{ res.matrikelnr }}
-
Prestudent ID
+
{{ $p.t('search/result_prestudent_id') }}
{{ res.prestudent_id }}
-
Studiengang
+
{{ $p.t('lehre/studiengang') }}
{{ res.bezeichnung }}
diff --git a/public/js/components/searchbar/result/room.js b/public/js/components/searchbar/result/room.js index 23ce6b463..46fce1c71 100644 --- a/public/js/components/searchbar/result/room.js +++ b/public/js/components/searchbar/result/room.js @@ -22,9 +22,9 @@ export default { if (this.res.street) address += (address ? ', ' : '') + this.res.street; if (this.res.floor) - address += (address ? ' / ' : '') + this.res.floor + ' Stockwerk'; + address += (address ? ' / ' : '') + this.$p.t('search/result_address_floor', this.res); - return address || 'N/A'; + return address || this.$p.t('search/result_address_none'); } }, template: ` @@ -38,33 +38,33 @@ export default { >
-
Standort
+
{{ $p.t('search/result_room_address') }}
{{ address }}
-
Sitzplätze
+
{{ $p.t('search/result_workplaces') }}
-
Gebäude
+
{{ $p.t('search/result_building') }}
{{ res.building }}
-
Zusatz Informationen
+
{{ $p.t('search/result_equipment') }}
diff --git a/public/js/components/searchbar/result/student.js b/public/js/components/searchbar/result/student.js index bb3512e3a..e85716a94 100644 --- a/public/js/components/searchbar/result/student.js +++ b/public/js/components/searchbar/result/student.js @@ -26,25 +26,25 @@ export default { >
-
Student UID
+
{{ $p.t('search/result_student_uid') }}
{{ res.uid }}
-
Person ID
+
{{ $p.t('person/person_id') }}
{{ res.person_id }}
-
Matrikelnummer
+
{{ $p.t('person/matrikelnummer') }}
{{ res.matrikelnr }}
-
EMails
+
{{ $p.t('search/result_emails') }}
{{ email }} diff --git a/public/js/components/searchbar/result/template/action.js b/public/js/components/searchbar/result/template/action.js index a7654df59..f41d9925f 100644 --- a/public/js/components/searchbar/result/template/action.js +++ b/public/js/components/searchbar/result/template/action.js @@ -23,6 +23,6 @@ export default { }, template: ` - Action + {{ $p.t('search/action_default_label') }} ` }; \ No newline at end of file diff --git a/public/js/components/searchbar/searchbar.js b/public/js/components/searchbar/searchbar.js index 7589cfbef..034cf842a 100644 --- a/public/js/components/searchbar/searchbar.js +++ b/public/js/components/searchbar/searchbar.js @@ -9,8 +9,6 @@ import ResultDms from "./result/dms.js"; import ResultMergedperson from "./result/mergedperson.js"; import ResultMergedstudent from "./result/mergedstudent.js"; -// TODO(chris): arrays in results - export default { components: { ResultPerson, @@ -108,7 +106,7 @@ export default { .searchfunction(this.searchsettings, { timeout: 50000, signal: this.abortController.signal }) .then(response => { if (!response.data) { - this.error = 'Bei der Suche ist ein Fehler aufgetreten.'; + this.error = this.$p.t('search/error_general'); } else { let res = response.data.map(el => ({...el, ...JSON.parse(el.data)})); this.lastQuery = response.meta.searchstring; @@ -167,7 +165,7 @@ export default { return this.callsearchapi(); } - this.error = 'Bei der Suche ist ein Fehler aufgetreten.' + ' ' + error.message; + this.error = this.$p.t('search/error_general', error); this.searching = false; this.retry = 0; }); @@ -230,8 +228,8 @@ export default { v-model="searchsettings.searchstr" class="form-control" type="search" - placeholder="Suche..." - aria-label="Search" + :placeholder="$p.t('search/input_search_label')" + :aria-label="$p.t('search/input_search_label')" > @@ -254,7 +254,7 @@ export default {
{{ error }}
-
Es wurden keine Ergebnisse gefunden.
+
{{ $p.t('search/error_no_results') }}
@@ -302,7 +302,7 @@ export default { class="btn btn-primary" type="button" > - Übernehmen + {{ $p.t('search/button_applyfilter_label') }}
` diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index d9a72eaac..76059451d 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -31165,8 +31165,688 @@ array( 'insertvon' => 'system' ) ) + ), + //**************************** CORE/search + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'input_search_label', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Suche...', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Search...', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'button_filter_label', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Filter', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Filter', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'button_applyfilter_label', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Übernehmen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Apply', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'action_default_label', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Standard Aktion', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Default Action', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_student_uid', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Student UID', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Student UID', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_prestudent_id', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Prestudent ID', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Prestudent ID', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_emails', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Emails', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Emails', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_group_emails', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Gruppen-Email', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Group-Email', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_employee', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'MitarbeiterIn', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Employee', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_stdkst', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Standard-Kostenstelle', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Standard cost center', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_stdkst_none', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'keine', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'none', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_parent_oe', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Übergeordnete OE', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Parent OU', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_oe_none', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'keine', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'none', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_room_address', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Standort', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Site', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_address_floor', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => '{floor} Stockwerk', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '{floor} floor', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_address_none', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'N/A', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'N/A', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_workplaces', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sitzplätze', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Seats', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_workplaces_pc', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => '{max_person}, davon {workplaces} PC-Plätze', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '{max_person}, of which {workplaces} PC-Workstations', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_workplaces_none', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'N/A', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'N/A', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_building', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Gebäude', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Building', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_equipment', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Zusatz Informationen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Additional information', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_leader', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Leiter', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Leader', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_leader_none', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'N.N.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'N/A', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_number_of_employees', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Mitarbeiter-Anzahl', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Number of employees', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_dms_id', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'DMS ID', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'DMS ID', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_version', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Version', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Version', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_keywords', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Schlagwörter', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Keywords', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'result_content_none', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Kein Inhalt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'No Content', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'error_no_results', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Es wurden keine Ergebnisse gefunden.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'No results were found.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'error_unknown_type', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Unbekannter Ergebnistyp: '{type}'.", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Unknown resulttype: '{type}'.", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'error_general', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bei der Suche ist ein Fehler aufgetreten. {message}', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'An error occurred while searching. {message}', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'error_missing_config', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Such-Konfiguration $config["{type}"] nicht gefunden.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Search config $config["{type}"] not found.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'error_invalid_config', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Such-Konfiguration für $config["{type}"] ist ungültig: Feld "{field}" fehlt, ist leer oder hat einen ungültigen Typ.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Search config for $config["{type}"] is invalid: field {field} is missing, empty or has an invalid type.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'search', + 'phrase' => 'error_invalid_config_searchfield', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Such-Konfiguration $config["{type}"]["searchfields"]["{searchfield}"] ist ungültig: Feld "{field}" fehlt oder ist unglültig.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Search config $config["{type}"]["searchfields"]["{searchfield}"] is invalid: field {field} is missing or invalid.', + 'description' => '', + 'insertvon' => 'system' + ) + ) ) - ); From 72c992569c5c79a49f7b1c8325db494533f612d4 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 7 Nov 2024 13:33:51 +0100 Subject: [PATCH 0028/1216] Improvements Mergedperson Component --- .../searchbar/result/mergedperson.js | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/public/js/components/searchbar/result/mergedperson.js b/public/js/components/searchbar/result/mergedperson.js index c0349c2e3..e6fc5833b 100644 --- a/public/js/components/searchbar/result/mergedperson.js +++ b/public/js/components/searchbar/result/mergedperson.js @@ -12,33 +12,38 @@ export default { actions: Object }, computed: { - telurl() { - return 'tel:' + this.employee?.phone; - }, person() { - const person = this.res.list.filter(item => item.type == 'person'); - if (person.length) - return person.pop(); + // Cummulate all emails + const email = this.res.list.reduce((a, c) => [...a, ...(Array.isArray(c.email) ? c.email : [c.email])], []); + + // Use person entry if available (with cummulated emails) + const person = this.res.list.find(item => item.type == 'person'); + if (person) + return {...person, email}; + + // Those properties should be the same in all entries + const { person_id, name } = this.res.list[0]; + // Get first photo (prefer student photo if available) + const photo_url = ((this.students ? this.students.find(el => el.photo_url) : null) || this.employee)?.photo_url; - // TODO(chris): first one might have not one of these but a later one - const { person_id, name, photo_url, email } = this.res.list[0]; return { person_id, name, photo_url, email }; }, employee() { - const ma = this.res.list.filter(item => [ + return this.res.list.find(item => [ 'employee', 'unassigned_employee' - ].includes(item.type)); - return ma.length ? ma.pop() : null; + ].includes(item.type)) || null; }, students() { const students = this.res.list.filter(item => item.type == 'prestudent'); return students.length ? students : null; }, emails() { - if (Array.isArray(this.person.email)) - return new Set(this.person.email); - return [this.person.email]; + // Remove duplicates + return new Set(this.person.email); + }, + telurl() { + return 'tel:' + this.employee?.phone; } }, template: ` From 2ed5df8c1c18f644bac7de3ff0aad41c9103f7dc Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 7 Nov 2024 17:01:51 +0100 Subject: [PATCH 0029/1216] Orgform of Stg in searchresults --- application/config/search.php | 13 ++++++++++++- .../js/components/searchbar/result/mergedperson.js | 2 +- public/js/components/searchbar/result/prestudent.js | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/application/config/search.php b/application/config/search.php index 8c601e046..3a2c27a18 100644 --- a/application/config/search.php +++ b/application/config/search.php @@ -309,7 +309,18 @@ $config['prestudent'] = [ FROM public.tbl_status WHERE status_kurzbz = public.get_rolle_prestudent(ps.prestudent_id, NULL) LIMIT 1 - ) as status" + ) AS status", + "COALESCE( + ( + SELECT COALESCE(plan.orgform_kurzbz, pss.orgform_kurzbz) + FROM public.tbl_prestudentstatus pss + LEFT JOIN lehre.tbl_studienplan plan USING (studienplan_id) + WHERE pss.prestudent_id=ps.prestudent_id + ORDER BY pss.datum DESC, pss.insertamum DESC, pss.ext_id DESC + LIMIT 1 + ), + sg.orgform_kurzbz + ) AS orgform" ], 'resultjoin' => " LEFT JOIN public.tbl_prestudent ps USING (prestudent_id) diff --git a/public/js/components/searchbar/result/mergedperson.js b/public/js/components/searchbar/result/mergedperson.js index e6fc5833b..be9d1e735 100644 --- a/public/js/components/searchbar/result/mergedperson.js +++ b/public/js/components/searchbar/result/mergedperson.js @@ -139,7 +139,7 @@ export default {
{{ $p.t('lehre/studiengang') }}
- {{ student.bezeichnung }} + {{ student.bezeichnung }} {{ student.orgform ? '(' + student.orgform + ')' : '' }}
diff --git a/public/js/components/searchbar/result/prestudent.js b/public/js/components/searchbar/result/prestudent.js index 51b4ccd9b..c33877bf0 100644 --- a/public/js/components/searchbar/result/prestudent.js +++ b/public/js/components/searchbar/result/prestudent.js @@ -60,7 +60,7 @@ export default {
{{ $p.t('lehre/studiengang') }}
- {{ res.bezeichnung }} + {{ res.bezeichnung }} {{ res.orgform ? '(' + res.orgform + ')' : '' }}
From add9263d35ff2a1883c5336a5329144714118882 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Mon, 11 Nov 2024 17:17:33 +0100 Subject: [PATCH 0030/1216] added Electronic Onboarding phrases --- system/phrasesupdate.php | 366 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 364 insertions(+), 2 deletions(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 7ef0269c9..29d5d6132 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -31025,8 +31025,370 @@ array( 'insertvon' => 'system' ) ) - ) - + ), + //**************************** FHC-Core-ElectronicOnboarding + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'emailFehlt', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Email fehlt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Email is missing', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'emailUngueltig', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Email ist ungültig', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'The email is not valid', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'emailRegistriert', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Diese Email ist bereits registriert. Bitte verwenden sie eine andere Email oder loggen sie sich mit einer anderen Methode ein.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'The email is already registered. Please choose a different email or use a different login method.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungZugangEmailBetreff', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Zugang zu Ihrer Bewerbung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Access to your application', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungZugangEmailAnredeWeiblich', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sehr geehrte Frau', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Dear Ms', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungZugangEmailAnredeMaennlich', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sehr geehrter Herr', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Dear Mr', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungZugangEmailAnredeNeutral', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sehr geehrte/r', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Dear', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'registrierungVerifzierenFuer', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Registrierung verifizieren für', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Verify registration for', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'registrierungVerifzieren', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Registrierung verifizieren', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Verify registration', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'vorname', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Vorname', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'First name', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'nachname', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Nachname', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Last name', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'geburtsdatum', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Geburtsdatum', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Birth date', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'emailAdresse', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'E-Mail Adresse', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'E-mail address', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'emailGesendet', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Die E-Mail mit dem Link zu Ihrer Bewerbung wurde erfolgreich an {0} verschickt.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'The email with the link to your application has been successfully sent to {0}.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'emailGesendetHinweis', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'In der Regel erhalten Sie das Mail in wenigen Minuten. Wenn Sie nach 24 Stunden noch kein Mail erhalten haben, + kontaktieren Sie bitte unsere {0}Studienberatung{1}', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'You should receive an e-mail within a few minutes. If you receive no e-mail within 24 hours please contact + our {0}student counselling team{1}.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'fehlerBeiRegistrierung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Fehler bei der Registrierung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Error when registering', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'fehlerBeiRegistrierungText', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Da ist etwas schief gelaufen. Wir bitten um Entschuldigung.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Sorry, something went wrong.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'fehlerBeiRegistrierungNochmalVersuchen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Nochmals versuchen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Try again', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), ); From 8614e4301fba5444c2f1bd36c0ada99aa8ddb890 Mon Sep 17 00:00:00 2001 From: Cris Date: Wed, 20 Nov 2024 13:54:05 +0100 Subject: [PATCH 0031/1216] Added getStudienjahrByStudiensemester method to Studiensemester API Controller --- .../v1/organisation/Studiensemester.php | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/application/controllers/api/frontend/v1/organisation/Studiensemester.php b/application/controllers/api/frontend/v1/organisation/Studiensemester.php index 72a449aaa..bb56ea71a 100644 --- a/application/controllers/api/frontend/v1/organisation/Studiensemester.php +++ b/application/controllers/api/frontend/v1/organisation/Studiensemester.php @@ -24,7 +24,8 @@ class Studiensemester extends FHCAPI_Controller parent::__construct( array( 'getAll' => self::PERM_LOGGED, - 'getAktNext' => self::PERM_LOGGED + 'getAktNext' => self::PERM_LOGGED, + 'getStudienjahrByStudiensemester' => self::PERM_LOGGED ) ); // Load model StudiensemesterModel @@ -115,4 +116,40 @@ class Studiensemester extends FHCAPI_Controller $this->terminateWithSuccess((getData($result) ?: '')); } + + /** + * Get Studienjahr by Studiensemester. + * input param semester: studiensemester_kurzbz + */ + public function getStudienjahrByStudiensemester() + { + $semester = $this->input->get('semester'); + + $studienjahrObj = null; + + if (!is_numeric($semester)) + { + $this->StudiensemesterModel->addSelect('studienjahr_kurzbz'); + $result = $this->StudiensemesterModel->loadWhere(array('studiensemester_kurzbz =' => $semester)); + } + + if (hasData($result)) + { + $studienjahr = getData($result)[0]->studienjahr_kurzbz; + $startstudienjahr = substr($studienjahr, 0, 4); + $endstudienjahr = substr($studienjahr, 0, 2) . substr($studienjahr, -2); + + $studienjahrObj = new StdClass(); + + $studienjahrObj->studienjahr_kurzbz = $studienjahr; + $studienjahrObj->startstudienjahr = $startstudienjahr; + $studienjahrObj->endstudienjahr= $endstudienjahr; + } + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_DB); + } + + $this->terminateWithSuccess((getData(success($studienjahrObj)))); + } } From 933af943448aa4e05da36d3e83e042e95f08a0e3 Mon Sep 17 00:00:00 2001 From: Cris Date: Wed, 20 Nov 2024 13:55:11 +0100 Subject: [PATCH 0032/1216] Added checkIsTemplate method to Lehrveranstaltung Model Checks if given LV is a template (Quellkurs) --- .../education/Lehrveranstaltung_model.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 1c601a291..f7860f470 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -716,6 +716,28 @@ class Lehrveranstaltung_model extends DB_Model return $this->execQuery($qry); } + /** + * Check if given LV is a template (Quellkurs) + * + * @param $lehrveranstaltung_id + * @return array|stdClass|void + */ + public function checkIsTemplate($lehrveranstaltung_id) + { + $this->addSelect('lehrtyp_kurzbz, lehrveranstaltung_template_id'); + $result = $this->load($lehrveranstaltung_id); + + if (isError($result)) + return error(getError($result)); + + if (hasData($result)) + { + return success( + getData($result)[0]->lehrtyp_kurzbz === 'tpl' && + getData($result)[0]->lehrveranstaltung_template_id === null + ); + } + } /** * Get ECTS Summe pro angerechnetes Quereinstiegssemester. From ae94daa4a66cca8a04fcedbc73bed7abce3fdc5c Mon Sep 17 00:00:00 2001 From: Cris Date: Wed, 20 Nov 2024 13:57:30 +0100 Subject: [PATCH 0033/1216] Added getStudienjahrByStudiensemester method to Studiensemester Model --- .../organisation/Studiensemester_model.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/application/models/organisation/Studiensemester_model.php b/application/models/organisation/Studiensemester_model.php index c59c9d343..93cc6c014 100644 --- a/application/models/organisation/Studiensemester_model.php +++ b/application/models/organisation/Studiensemester_model.php @@ -253,4 +253,40 @@ class Studiensemester_model extends DB_Model if (is_numeric($studienjahrNumber) && mb_substr($studiensemester_kurzbz, 0, 2) == 'SS') (int)$studienjahrNumber -= 1; return $studienjahrNumber; } + + /** + * Get Studienjahr by Studiensemester. + * + * @param $studiensemester_kurzbz + * @return array|stdClass + */ + public function getStudienjahrByStudiensemester($studiensemester_kurzbz) + { + $studienjahrObj = null; + + if (!is_numeric($studiensemester_kurzbz)) + { + $this->StudiensemesterModel->addSelect('studienjahr_kurzbz'); + $result = $this->StudiensemesterModel->loadWhere(array('studiensemester_kurzbz =' => $studiensemester_kurzbz)); + } + + if (hasData($result)) + { + $studienjahr = getData($result)[0]->studienjahr_kurzbz; + $startstudienjahr = substr($studienjahr, 0, 4); + $endstudienjahr = substr($studienjahr, 0, 2) . substr($studienjahr, -2); + + $studienjahrObj = new StdClass(); + + $studienjahrObj->studienjahr_kurzbz = $studienjahr; + $studienjahrObj->startstudienjahr = $startstudienjahr; + $studienjahrObj->endstudienjahr= $endstudienjahr; + } + + if (isError($result)) { + return error(getError($result)); + } + + return success($studienjahrObj); + } } From e94e9ceff212f604199ac59e3d3f242ba7a590b6 Mon Sep 17 00:00:00 2001 From: Cris Date: Wed, 20 Nov 2024 14:07:43 +0100 Subject: [PATCH 0034/1216] Added phrases 'zuordnungExistiertBereits' and 'swFuerLvAendern' --- system/phrasesupdate.php | 41 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 925f88bba..2f80e27aa 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -31346,7 +31346,46 @@ array( ) ) ), - + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'zuordnungExistiertBereits', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Zuordnung existiert bereits.", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Assignment already exists.", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'swFuerLvAendern', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Software für LV ändern", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Change Software for courses", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ) ); From b63146a8d89cdc4f08213350170346b1956cd560 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Wed, 20 Nov 2024 15:03:59 +0100 Subject: [PATCH 0035/1216] added MeldezettelJob for accepting Meldezettel of students with Meldeadresse --- .../controllers/jobs/MeldezettelJob.php | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 application/controllers/jobs/MeldezettelJob.php diff --git a/application/controllers/jobs/MeldezettelJob.php b/application/controllers/jobs/MeldezettelJob.php new file mode 100644 index 000000000..329597985 --- /dev/null +++ b/application/controllers/jobs/MeldezettelJob.php @@ -0,0 +1,86 @@ +_ci =& get_instance(); + + $this->_ci->load->model('crm/Dokumentprestudent_model', 'DokumentprestudentModel'); + } + + /** + * Sets Meldezettel to "accepted" for all students with Meldeadresse. + */ + public function acceptMeldezettel() + { + $this->logInfo('Start Meldezettel Job'); + + $params = array(self::DOKUMENT_KURZBZ); + + $qry = " + -- get all prestudents with meldeadresse, but no accepted Meldezettel + SELECT + DISTINCT prestudent_id + FROM + public.tbl_adresse + JOIN public.tbl_person USING (person_id) + JOIN public.tbl_prestudent ps USING (person_id) + WHERE + typ = 'm' + AND NOT EXISTS ( + SELECT + 1 + FROM + public.tbl_dokumentprestudent + WHERE + prestudent_id = ps.prestudent_id + AND dokument_kurzbz = ? + )"; + + // get all prestudents with Meldeadresse and no accpeted Meldezettel + $result = $this->_ci->DokumentprestudentModel->execReadOnlyQuery($qry, $params); + + if (isError($result)) + { + $this->logError(getError($result)); + } + + $count = 0; + + if (hasData($result)) + { + $prestudents = getData($result); + + foreach ($prestudents as $prestudent) + { + // set Meldezettel to accepted + $result = $this->_ci->DokumentprestudentModel->insert( + array( + 'prestudent_id' => $prestudent->prestudent_id, + 'dokument_kurzbz' => self::DOKUMENT_KURZBZ, + 'datum' => date('Y-m-d'), + 'insertamum' => strftime('%Y-%m-%d %H:%M'), + 'insertvon' => self::INSERT_VON + ) + ); + + if (isError($result)) + $this->logError(getError($result)); + else + $count++; + } + } + + $this->logInfo('End Meldezettel Job', array('Number of changes ' => $count)); + } +} From 1159b0dad5d3d62fa3d2ffbff897c571d603f629 Mon Sep 17 00:00:00 2001 From: Cris Date: Wed, 27 Nov 2024 17:28:54 +0100 Subject: [PATCH 0036/1216] Refactored getLvs method in Lehrveranstaltung_model.php --- .../education/Lehrveranstaltung_model.php | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index f7860f470..28b44a177 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -66,18 +66,21 @@ class Lehrveranstaltung_model extends DB_Model /** * Get Lehrveranstaltungen with its Stg, OE and OE-type. - * Filter by Studiensemester and Organisationseinheiten if necessary. + * Can be filtered by Studienesemester, lv oes or lv's stg oes, and also by specific lvs. * @param null|string $studiensemester_kurzbz Filter by Studiensemester - * @param null|array $oes Filter by Organisationseinheiten - * @param null|string $lehrtyp_kurzbz Filter by Lehrtyp 'lv' or 'modul' - * @param null|array $lv_ids Filter by Lehrveranstaltung-Ids - * @param string $oe_column 'lv'|'stg' Used when filtering $oes: Filter by lv.oe_kurzbz or stg.oe_kurzbz (the stg joined to lv) + * @param null $lv_oes Filter oes by lv oe (default behaviour) + * @param null $stg_oes Filter oes by lv's stg oe + * @param null|array $lv_ids Filter by Lehrveranstaltungen * @return array */ - public function getLvsByStudienplan($studiensemester_kurzbz = null, $oes = null, $lehrtyp_kurzbz = null, $lv_ids = null, $oe_column = 'lv') + public function getLvs($studiensemester_kurzbz = null, $lv_oes = null, $stg_oes = null, $lv_ids = null) { // Subquery LVs $subQry = $this->_getQryLvsByStudienplan(); + + /* filter by lehrtyp_kurzbz 'lv' */ + $subQry .= ' AND lehrtyp_kurzbz = \'lv\''; + $params = []; if (isset($studiensemester_kurzbz) && is_string($studiensemester_kurzbz)) @@ -87,27 +90,18 @@ class Lehrveranstaltung_model extends DB_Model $params[] = $studiensemester_kurzbz; } - if (isset($oes) && is_array($oes)) + if (isset($lv_oes) && is_array($lv_oes)) { - if ($oe_column === 'lv') - { - /* filter by lv organisationseinheit (Standard behaviour) */ - $subQry.= ' AND lv.oe_kurzbz IN ?'; - } - elseif ($oe_column === 'stg') - { - /* filter by lv studiengangs organisationseinheit () */ - $subQry.= ' AND stg.oe_kurzbz IN ?'; - } - - $params[]= $oes; + /* filter by lv organisationseinheit */ + $subQry.= ' AND lv.oe_kurzbz IN ?'; + $params[]= $lv_oes; } - if (isset($lehrtyp_kurzbz) && is_string($lehrtyp_kurzbz)) + if (isset($stg_oes) && is_array($stg_oes)) { - /* filter by lehrtyp_kurzbz */ - $subQry .= ' AND lehrtyp_kurzbz = ?'; - $params[] = $lehrtyp_kurzbz; + /* filter by lv studiengangs organisationseinheit */ + $subQry.= ' AND stg.oe_kurzbz IN ?'; + $params[]= $stg_oes; } // Final Query From c94a2d2d0caa3481b27bbb3a257a254cb2d30972 Mon Sep 17 00:00:00 2001 From: Cris Date: Wed, 27 Nov 2024 17:31:02 +0100 Subject: [PATCH 0037/1216] Added param lehrveranstaltung_id to getTemplateLvTree method in Lehrveranstaltung_model.php and enhanced order Now ordering extra templates to appear first and directly after the assigned lvs --- .../education/Lehrveranstaltung_model.php | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/application/models/education/Lehrveranstaltung_model.php b/application/models/education/Lehrveranstaltung_model.php index 28b44a177..aa68f7f69 100644 --- a/application/models/education/Lehrveranstaltung_model.php +++ b/application/models/education/Lehrveranstaltung_model.php @@ -189,14 +189,15 @@ class Lehrveranstaltung_model extends DB_Model } /** - * Get all Templates and union with all Lehrveranstaltungen of given Studiensemester and Oes, that are assigned to - * a template. This data structure can be used for nested tabulator data tree. + * Get all Templates and its assigned Lehrveranstaltungen of given Studiensemester and Oes. + * Lvs are queried via actual Studienordnung and Studienplan. * * @param null|string $studiensemester_kurzbz * @param null|array $oes + * @param null $lehrveranstaltung_id Queries certain LV only * @return array|stdClass|null */ - public function getTemplateLvTree($studiensemester_kurzbz = null, $oes = null){ + public function getTemplateLvTree($studiensemester_kurzbz = null, $oes = null, $lehrveranstaltung_id = null){ $params = []; $qry = ' WITH @@ -223,6 +224,13 @@ class Lehrveranstaltung_model extends DB_Model -- filter lvs assigned to template (= standardisierte lv) AND lehrveranstaltung_template_id IS NOT NULL'; + if (is_numeric($lehrveranstaltung_id)) + { + /* filter by studiensemester */ + $params[]= $lehrveranstaltung_id; + $qry.= ' AND lv.lehrveranstaltung_template_id = ? '; + } + if (is_string($studiensemester_kurzbz)) { /* filter by studiensemester */ @@ -266,6 +274,13 @@ class Lehrveranstaltung_model extends DB_Model WHERE std.lehrveranstaltung_template_id = lv.lehrveranstaltung_id )'; + if (is_numeric($lehrveranstaltung_id)) + { + /* filter by studiensemester */ + $params[]= $lehrveranstaltung_id; + $qry.= ' AND lv.lehrveranstaltung_id = ? '; + } + if (is_array($oes)) { /* filter by organisationseinheit */ @@ -342,7 +357,17 @@ class Lehrveranstaltung_model extends DB_Model JOIN public.tbl_studiengangstyp stgtyp ON stgtyp.typ = stg.typ JOIN public.tbl_organisationseinheit oe ON oe.oe_kurzbz = lv.oe_kurzbz ORDER BY - oe.bezeichnung, lv.semester, lv.bezeichnung + -- Sort by organisationseinheit, semester, and lv.bezeichnung + oe.bezeichnung, + lv.semester, + lv.bezeichnung, + -- Within each group, ensure templates appear first + CASE + WHEN lv.lehrtyp_kurzbz = \'tpl\' THEN 0 + ELSE 1 + END, + -- Ensure assigend lvs follow their template, grouped by lehrveranstaltung_template_id + COALESCE(lv.lehrveranstaltung_template_id, lv.lehrveranstaltung_id) '; return $this->execQuery($qry, $params); From 3ec748e6fbbbb501d795515ec32b71d8c50d7195 Mon Sep 17 00:00:00 2001 From: Cris Date: Wed, 27 Nov 2024 17:31:48 +0100 Subject: [PATCH 0038/1216] Added new phrases for Softwarebereitstellung --- system/phrasesupdate.php | 86 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 2f80e27aa..b1e80f803 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -29361,18 +29361,18 @@ array( array( 'app' => 'softwarebereitstellung', 'category' => 'global', - 'phrase' => 'softwareanforderungSubtitle', + 'phrase' => 'softwarebereitstellungSubtitle', 'insertvon' => 'system', 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Softwareanforderung und Lizenzmanagement für die Lehre', + 'text' => 'Softwarebereitstellung für die Lehre', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'Software Request and License management for Education', + 'text' => 'Software delivery for Education', 'description' => '', 'insertvon' => 'system' ) @@ -31385,6 +31385,86 @@ array( 'insertvon' => 'system' ) ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'anforderungNachQuellkurs', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Anforderung nach Quellkurs', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Request by Course-Template', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'swAnforderungFuerQuellkurs', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Softwareanforderung für Quellkurse", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Software Requirements for Course-Templates", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'swAnforderungFuerEinzelneLvs', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Softwareanforderung für einzelne Lehrveranstaltungen", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Software Requirements for individual Courses", + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'softwarebereitstellung', + 'category' => 'global', + 'phrase' => 'softwarebereitstellung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => "Softwarebereitstellung", + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => "Software Delivery", + 'description' => '', + 'insertvon' => 'system' + ) + ) ) ); From 0a97b39efbb877ff3f7a07ca0d531434b54cb3ca Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Tue, 24 Dec 2024 19:14:04 +0100 Subject: [PATCH 0039/1216] added phrases for electronic onboarding verification page --- .../person/Kontaktverifikation_model.php | 2 +- system/phrasesupdate.php | 137 +++++++++++++++++- 2 files changed, 132 insertions(+), 7 deletions(-) diff --git a/application/models/person/Kontaktverifikation_model.php b/application/models/person/Kontaktverifikation_model.php index b15439edb..17bcb1c35 100644 --- a/application/models/person/Kontaktverifikation_model.php +++ b/application/models/person/Kontaktverifikation_model.php @@ -29,7 +29,7 @@ class Kontaktverifikation_model extends DB_Model FROM public.tbl_kontakt_verifikation kv JOIN public.tbl_kontakt kt USING(kontakt_id) - WHERE kt.person_id = ? + WHERE kt.person_id = ? AND kt.kontakttyp = ? AND kv.verifikation_code = ? AND kv.erstelldatum >= NOW() - INTERVAL '".$this->escape($expiration_days)." days' diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 8d7bdd939..843a055a1 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -31310,18 +31310,18 @@ array( array( 'app' => 'core', 'category' => 'onboarding', - 'phrase' => 'registrierungVerifzierenFuer', + 'phrase' => 'bewerbungVerifizierung', 'insertvon' => 'system', 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Registrierung verifizieren für', + 'text' => 'Verifizierung Ihrer Bewerbung', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'Verify registration for', + 'text' => 'Application verification', 'description' => '', 'insertvon' => 'system' ) @@ -31330,18 +31330,143 @@ array( array( 'app' => 'core', 'category' => 'onboarding', - 'phrase' => 'registrierungVerifzieren', + 'phrase' => 'bewerbungVerifzieren', 'insertvon' => 'system', 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Registrierung verifizieren', + 'text' => 'Bewerbung verifizieren', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'Verify registration', + 'text' => 'Verify application', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungVerifizierungEinleitung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Wenn Ihre Daten stimmen, geben Sie bitte Ihre E-Mail Adresse ein und drücken Sie auf "Bewerbung verfifizieren". +Danach erhalten Sie eine E-Mail mit dem Link zu Ihrer Bewerbung an die angegebene Adresse. +Dort können Sie Studienrichtungen hinzufügen, Ihre Daten vervollständigen, und sich unverbindlich bewerben.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'If your data is correct, please enter your email and click "Verify application". +We will then send you a link via e-mail to the address specified. There, you can add personal information or degree programs and submit non-binding applications.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungVerifizierungKontakthinweis', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Wenn Sie mehr Informationen benötigen, steht Ihnen unsere Studienberatung gerne persönlich, telefonisch, per E-Mail oder WhatsApp zur Verfügung.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Should you require any additional information, please do not hesitate to contact our student counselling team in person, by phone, or via e-mail or WhatsApp.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungVerifizierungDatenschutzhinweis', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Datenschutz-Hinweis', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Privacy information', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungVerifizierungDatenschutzhinweisText', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Die uns von Ihnen zum Zwecke der Bewerbung bekanntgegebenen Daten werden von uns ausschließlich zur Abwicklung der Bewerbung auf der Grundlage von vor- bzw vertraglichen Zwecken verarbeitet und mit der unten beschriebenen Ausnahme bei Unklarheiten betreffend die Zugangsvoraussetzungen nicht an Dritte weitergegeben. +Kommt es zu keinem weiteren Kontakt bzw zu keiner Aufnahme, löschen wir Ihre Daten nach drei Jahren.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'The data communicated to us by you for the purpose of the application will be used by us exclusively for the processing of the application on the basis of pre-contractual or contractual purposes and will not be passed on to third parties with the exception described below in case of uncertainties regarding the entry requirements. +If there is no further contact or enrolment, your data will be deleted after three years.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungVerifizierungInformationenDatenschutzGrundverordnung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Informationen zu Ihren Betroffenenrechten finden Sie hier:', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Information on your data subject rights can be found here:', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bewerbungVerifizierungDatenschutzFragen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bei Fragen stehen wir Ihnen jederzeit unter folgender Mail zur Verfügung: ', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'If you have any questions, please contact us at ', 'description' => '', 'insertvon' => 'system' ) From 115b35ad7b40f3b27a161c5a8859ca019f1924e5 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Wed, 8 Jan 2025 14:52:32 +0100 Subject: [PATCH 0040/1216] electronic onboarding: changed phrase for error display --- system/phrasesupdate.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 843a055a1..4de9cbb03 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -31622,13 +31622,13 @@ If there is no further contact or enrolment, your data will be deleted after thr 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Da ist etwas schief gelaufen. Wir bitten um Entschuldigung.', + 'text' => 'Es ist ein Fehler bei der Registrierung aufgetreten.', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'Sorry, something went wrong.', + 'text' => 'An error occured during registration.', 'description' => '', 'insertvon' => 'system' ) From 08cb3d3809b34b43eb87558a02a5ae702e4c8681 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Thu, 16 Jan 2025 21:02:01 +0100 Subject: [PATCH 0041/1216] electronic onboarding phrase typo fix --- system/phrasesupdate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 4de9cbb03..7f030e62b 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -31355,7 +31355,7 @@ array( 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Wenn Ihre Daten stimmen, geben Sie bitte Ihre E-Mail Adresse ein und drücken Sie auf "Bewerbung verfifizieren". + 'text' => 'Wenn Ihre Daten stimmen, geben Sie bitte Ihre E-Mail Adresse ein und drücken Sie auf "Bewerbung verifizieren". Danach erhalten Sie eine E-Mail mit dem Link zu Ihrer Bewerbung an die angegebene Adresse. Dort können Sie Studienrichtungen hinzufügen, Ihre Daten vervollständigen, und sich unverbindlich bewerben.', 'description' => '', From 39ada202774fbb77811a2cd54bd96f2a046917aa Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Wed, 22 Jan 2025 15:26:25 +0100 Subject: [PATCH 0042/1216] Electronic Onboarding: added phrases --- system/phrasesupdate.php | 102 +++++++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 20 deletions(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 7f030e62b..e177274ec 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -31307,26 +31307,6 @@ array( ) ) ), - array( - 'app' => 'core', - 'category' => 'onboarding', - 'phrase' => 'bewerbungVerifizierung', - 'insertvon' => 'system', - 'phrases' => array( - array( - 'sprache' => 'German', - 'text' => 'Verifizierung Ihrer Bewerbung', - 'description' => '', - 'insertvon' => 'system' - ), - array( - 'sprache' => 'English', - 'text' => 'Application verification', - 'description' => '', - 'insertvon' => 'system' - ) - ) - ), array( 'app' => 'core', 'category' => 'onboarding', @@ -31654,6 +31634,88 @@ If there is no further contact or enrolment, your data will be deleted after thr ) ) ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'zustimmungDatenuebermittlung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Können in Ausnahmefällen die Zugangsvoraussetzungen von der FH Technikum Wien nicht abschließend abgeklärt werden, erteile ich die Zustimmung, dass die FH Technikum Wien die Dokumente zur Überprüfung an die zuständigen Behörden weiterleiten kann.
+Ich wurde darüber informiert, dass ich nicht verpflichtet bin, der Übermittlung meiner Daten zuzustimmen. Diese Zustimmung ist allerdings notwendig, um die Bewerbung berücksichtigen zu können.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'If in exceptional cases the admission requirements can not be finally clarified by the UAS Technikum Wien, I give my consent that the UAS Technikum Wien can forward the documents to the competent authorities for verification.
+I have been informed that I am under no obligation to consent to the transmission of my data. However, this consent is necessary in order for the application to be considered.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'zustimmungDatenschutzerklaerung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ich habe die Datenschutzerklärung zu Kenntnis genommen.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'I have taken note of the privacy policy.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bitteDatenuebermittlungZustimmen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sie müssen der Datenübermittlung zustimmen, um Ihre Bewerbung abschicken zu können.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'You have to consent the transmission of your data to send the application.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'onboarding', + 'phrase' => 'bitteDatenschutzerklaerungZustimmen', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sie müssen der Datenschutzerklärung zustimmen, um Ihre Bewerbung abschicken zu können.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'You have to consent to the privacy statement to send the application.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ) ); From b079d22e8db60db783aa5e39bad24fbd130f89a2 Mon Sep 17 00:00:00 2001 From: ma0048 Date: Mon, 27 Jan 2025 09:37:02 +0100 Subject: [PATCH 0043/1216] - filter hinzugefuegt - db index hinzugefuegt - sqls optimiert - diplomand wird nun auch angezeigt --- system/phrasesupdate.php | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 746c24da3..2eb88bc9c 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -18288,6 +18288,26 @@ array( ) ) ), + array( + 'app' => 'international', + 'category' => 'international', + 'phrase' => 'studentstatus', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Studentstatus', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Student status', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'international', 'category' => 'international', @@ -19423,6 +19443,26 @@ array( ) ) ), + array( + 'app' => 'international', + 'category' => 'international', + 'phrase' => 'stgtodo', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'Nur offene Massnahmen', + 'text' => 'O', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'only open measures', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( 'app' => 'international', 'category' => 'international', From 7fbff20dd97d66a15cd0242214c96616530fe924 Mon Sep 17 00:00:00 2001 From: ma0068 Date: Tue, 4 Feb 2025 14:49:41 +0100 Subject: [PATCH 0044/1216] new Tab Gruppen --- .../api/frontend/v1/stv/Config.php | 4 + .../api/frontend/v1/stv/Favorites.php | 2 +- .../api/frontend/v1/stv/Gruppen.php | 81 ++++++++ public/js/api/stv.js | 2 + public/js/api/stv/group.js | 8 + .../js/components/Stv/Studentenverwaltung.js | 1 + .../Studentenverwaltung/Details/Gruppen.js | 19 ++ .../Details/Gruppen/Gruppen.js | 182 ++++++++++++++++++ system/phrasesupdate.php | 83 ++++++++ 9 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 application/controllers/api/frontend/v1/stv/Gruppen.php create mode 100644 public/js/api/stv/group.js create mode 100644 public/js/components/Stv/Studentenverwaltung/Details/Gruppen.js create mode 100644 public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js diff --git a/application/controllers/api/frontend/v1/stv/Config.php b/application/controllers/api/frontend/v1/stv/Config.php index c28c49485..12cd77048 100644 --- a/application/controllers/api/frontend/v1/stv/Config.php +++ b/application/controllers/api/frontend/v1/stv/Config.php @@ -91,6 +91,10 @@ class Config extends FHCAPI_Controller 'title' => $this->p->t('stv', 'tab_resources'), 'component' => './Stv/Studentenverwaltung/Details/Betriebsmittel.js' ]; + $result['groups'] = [ + 'title' => $this->p->t('stv', 'tab_groups'), + 'component' => './Stv/Studentenverwaltung/Details/Gruppen.js' + ]; /* TODO(chris): Ausgeblendet für Testing $result['grades'] = [ 'title' => $this->p->t('stv', 'tab_grades'), diff --git a/application/controllers/api/frontend/v1/stv/Favorites.php b/application/controllers/api/frontend/v1/stv/Favorites.php index 8d7a6cd14..b8fe6f3d7 100644 --- a/application/controllers/api/frontend/v1/stv/Favorites.php +++ b/application/controllers/api/frontend/v1/stv/Favorites.php @@ -48,7 +48,7 @@ class Favorites extends FHCAPI_Controller if (!$data) $this->terminateWithSuccess(null); else - $this->terminateWithSuccess($data['stv_favorites']); + $this->terminateWithSuccess($data['stv_favorites'] ?? null); } public function set() diff --git a/application/controllers/api/frontend/v1/stv/Gruppen.php b/application/controllers/api/frontend/v1/stv/Gruppen.php new file mode 100644 index 000000000..39c5efe21 --- /dev/null +++ b/application/controllers/api/frontend/v1/stv/Gruppen.php @@ -0,0 +1,81 @@ + ['admin:r', 'assistenz:r'], + 'deleteGruppe' => ['admin:rw', 'assistenz:rw'], + ]); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + + // Load language phrases + $this->loadPhrases([ + 'ui', 'gruppenmanagement' + ]); + + // Load models + $this->load->model('person/Benutzergruppe_model', 'BenutzergruppeModel'); + $this->load->model('organisation/Gruppe_model', 'GruppeModel'); + } + + public function getGruppen($student_uid) + { + $this->BenutzergruppeModel ->addSelect('gruppe_kurzbz'); + $this->BenutzergruppeModel ->addSelect('bezeichnung'); + $this->BenutzergruppeModel ->addSelect('generiert'); + $this->BenutzergruppeModel ->addSelect('uid'); + $this->BenutzergruppeModel ->addSelect('studiensemester_kurzbz'); + $this->BenutzergruppeModel ->addSelect('public.tbl_benutzergruppe.insertvon'); + $this->BenutzergruppeModel ->addJoin('public.tbl_gruppe', 'gruppe_kurzbz'); + $this->BenutzergruppeModel-> addOrder('bezeichnung', 'ASC'); + + $result = $this->BenutzergruppeModel->loadWhere( + array( + 'uid' => $student_uid + ) + ); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function deleteGruppe() + { + $student_uid = $this->input->post('id'); + $gruppe_kurzbz = $this->input->post('gruppe_kurzbz'); + + //Validate if automatic group generation + $result = $this->GruppeModel-> loadWhere( + array( + 'gruppe_kurzbz' => $gruppe_kurzbz + ) + ); + $data = $this->getDataOrTerminateWithError($result); + $generation = current($data); + + if($generation->generiert) + { + $this->terminateWithError($this->p->t('gruppenmanagement', 'error_deleteGeneratedGroups'), self::ERROR_TYPE_GENERAL); + } + + $result = $this->BenutzergruppeModel->delete( + array( + 'gruppe_kurzbz' => $gruppe_kurzbz, + 'uid' => $student_uid + ) + ); + + $data = $this->getDataOrTerminateWithError($result); + + return $this->terminateWithSuccess($data); + } +} diff --git a/public/js/api/stv.js b/public/js/api/stv.js index 14fcc6661..8b14beb14 100644 --- a/public/js/api/stv.js +++ b/public/js/api/stv.js @@ -2,12 +2,14 @@ import verband from './stv/verband.js'; import students from './stv/students.js'; import filter from './stv/filter.js'; import konto from './stv/konto.js'; +import group from './stv/group.js'; export default { verband, students, filter, konto, + group, configStudent() { return this.$fhcApi.get('api/frontend/v1/stv/config/student'); }, diff --git a/public/js/api/stv/group.js b/public/js/api/stv/group.js new file mode 100644 index 000000000..af6e6e122 --- /dev/null +++ b/public/js/api/stv/group.js @@ -0,0 +1,8 @@ +export default { + getGruppen(url, config, params) { + return this.$fhcApi.get('api/frontend/v1/stv/Gruppen/getGruppen/' + params.id); + }, + deleteGroup(params) { + return this.$fhcApi.post('api/frontend/v1/stv/Gruppen/deleteGruppe/', params); + } +} \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung.js b/public/js/components/Stv/Studentenverwaltung.js index 8779e4bf1..eaa253c2c 100644 --- a/public/js/components/Stv/Studentenverwaltung.js +++ b/public/js/components/Stv/Studentenverwaltung.js @@ -57,6 +57,7 @@ export default { hasPermissionToSkipStatusCheck: this.permissions['student/keine_studstatuspruefung'], hasPermissionRtAufsicht: this.permissions['lehre/reihungstestAufsicht'], lists: this.lists, + currentSemester: Vue.computed(() => this.studiensemesterKurzbz), defaultSemester: this.defaultSemester, $reloadList: () => { this.$refs.stvList.reload(); diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Gruppen.js b/public/js/components/Stv/Studentenverwaltung/Details/Gruppen.js new file mode 100644 index 000000000..1c7c47d6b --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Gruppen.js @@ -0,0 +1,19 @@ +import GruppenList from './Gruppen/Gruppen.js'; + +export default { + components: { + GruppenList + }, + props: { + modelValue: Object + }, + methods: { + reload() { + this.$refs.gruppen.$refs.table.reloadTable(); + } + }, + template: ` +
+ +
` +}; \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js b/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js new file mode 100644 index 000000000..6a657a793 --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js @@ -0,0 +1,182 @@ +import {CoreFilterCmpt} from "../../../../filter/Filter.js"; + +export default { + components: { + CoreFilterCmpt, + }, + inject: { + currentSemester: { + from: 'currentSemester', + }, + }, + props: { + student: Object + }, + data() { + return { + tabulatorOptions: { + ajaxURL: 'dummy', + ajaxRequestFunc: this.$fhcApi.factory.stv.group.getGruppen, + ajaxParams: () => { + return { + id: this.student.uid + }; + }, + ajaxResponse: (url, params, response) => response.data, + initialFilter: [ + {field: "uid", type: "=", value: this.student.uid}, + [ + {field: "studiensemester_kurzbz", type: "=", value: this.currentSemester}, + {field: "insertvon", type: "=", value: "mlists_generate"} + ] + ], + columns: [ + {title: "Gruppe", field: "gruppe_kurzbz"}, + {title: "Bezeichnung", field: "bezeichnung"}, + {title: "Semester", field: "studiensemester_kurzbz"}, + { + title: "automatisch generiert", + field: "generiert", + formatter: "tickCross", + hozAlign: "center", + formatterParams: { + tickElement: '', + crossElement: '' + } + }, + {title: "UID", field: "uid"}, + {title: "InsertVon", field: "insertvon", visible: false}, + { + title: 'Aktionen', field: 'actions', + minWidth: 150, // Ensures Action-buttons will be always fully displayed + formatter: (cell, formatterParams, onRendered) => { + const container = document.createElement('div'); + container.className = "d-flex gap-2"; + + const data = cell.getData(); + + const button = document.createElement('button'); + button.className = 'btn btn-outline-secondary btn-action'; + button.innerHTML = ''; + button.title = this.$p.t('ui', 'loeschen'); + button.addEventListener('click', () => + this.actionDeleteGroup(data.gruppe_kurzbz) + ); + if (data.generiert) + button.disabled = true; + container.append(button); + + return container; + }, + frozen: true + }, + ], + layout: 'fitDataFill', + height: 'auto', + selectable: true, + index: 'group_id', + persistenceID: 'stv-details-gruppe' + }, + tabulatorEvents: [ + { + event: 'tableBuilt', + handler: async () => { + + await this.$p.loadCategory(['global', 'person', 'stv', 'ui', 'gruppenmanagement']); + + let cm = this.$refs.table.tabulator.columnManager; + + cm.getColumnByField('gruppe_kurzbz').component.updateDefinition({ + title: this.$p.t('gruppenmanagement', 'gruppe') + }); + + cm.getColumnByField('bezeichnung').component.updateDefinition({ + title: this.$p.t('ui', 'bezeichnung') + }); + + cm.getColumnByField('generiert').component.updateDefinition({ + title: this.$p.t('gruppenmanagement', 'automatisch_generiert') + }); + + cm.getColumnByField('uid').component.updateDefinition({ + title: this.$p.t('ui', 'student_uid') + }); + + //Interference with Filter if not commented out + /* + cm.getColumnByField('studiensemester_kurzbz').component.updateDefinition({ + title: this.$p.t('lehre', 'studiensemester') + });*/ + + } + } + ], + } + }, + methods: { + actionDeleteGroup(gruppe_kurzbz) { + this.$fhcAlert + .confirmDelete() + .then(result => result + ? gruppe_kurzbz + : Promise.reject({handled: true})) + .then(this.deleteGroup) + .catch(this.$fhcAlert.handleSystemError); + + }, + deleteGroup(gruppe_kurzbz) { + const group_id = { + id: this.student.uid, + gruppe_kurzbz: gruppe_kurzbz + }; + + return this.$fhcApi.factory.stv.group.deleteGroup(group_id) + .then(response => { + this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete')); + }).catch(this.$fhcAlert.handleSystemError) + .finally(() => { + window.scrollTo(0, 0); + this.reload(); + }); + }, + reload() { + this.$refs.table.reloadTable(); + }, + }, + watch: { + currentSemester(newVal) { + if (newVal) { + + this.$refs.table.tabulator.clearFilter(); // Clear old filters + + this.$refs.table.tabulator.setFilter([ + {field: "uid", type: "=", value: this.student.uid}, + [ + {field: "studiensemester_kurzbz", type: "=", value: newVal}, + {field: "insertvon", type: "=", value: "mlists_generate"} + ] + ]); + + + } + }, + student() { + this.$refs.table.reloadTable(); + } + }, + template: ` +
+
{{$p.t('stv', 'tab_groups')}}
+ + + +
+ ` +} \ No newline at end of file diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 746c24da3..160db2853 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -37297,7 +37297,90 @@ array( 'insertvon' => 'system' ) ) + ), + //////////// FHC4 Phrases Gruppen Start //////////// + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'tab_groups', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Gruppen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Groups', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'gruppenmanagement', + 'phrase' => 'gruppe', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Gruppe', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Group', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'gruppenmanagement', + 'phrase' => 'automatisch_generiert', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'automatisch generiert', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'automatically generated', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'gruppenmanagement', + 'phrase' => 'error_deleteGeneratedGroups', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Automatisch generierte Gruppenzuordnungen können nicht gelöscht werden.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Automatically generated group assignments cannot be deleted.', + 'description' => '', + 'insertvon' => 'system' + ) + ) ) + //////////// FHC4 Phrases Gruppen End //////////// + ); From 6eb9c2ac223e34fe6bd5e89310615e3822e58bf2 Mon Sep 17 00:00:00 2001 From: ma0068 Date: Tue, 4 Feb 2025 14:57:35 +0100 Subject: [PATCH 0045/1216] update filtering --- application/controllers/api/frontend/v1/stv/Gruppen.php | 1 - .../Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/application/controllers/api/frontend/v1/stv/Gruppen.php b/application/controllers/api/frontend/v1/stv/Gruppen.php index 39c5efe21..c30816f2a 100644 --- a/application/controllers/api/frontend/v1/stv/Gruppen.php +++ b/application/controllers/api/frontend/v1/stv/Gruppen.php @@ -33,7 +33,6 @@ class Gruppen extends FHCAPI_Controller $this->BenutzergruppeModel ->addSelect('generiert'); $this->BenutzergruppeModel ->addSelect('uid'); $this->BenutzergruppeModel ->addSelect('studiensemester_kurzbz'); - $this->BenutzergruppeModel ->addSelect('public.tbl_benutzergruppe.insertvon'); $this->BenutzergruppeModel ->addJoin('public.tbl_gruppe', 'gruppe_kurzbz'); $this->BenutzergruppeModel-> addOrder('bezeichnung', 'ASC'); diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js b/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js index 6a657a793..63a9b24e1 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Gruppen/Gruppen.js @@ -27,7 +27,7 @@ export default { {field: "uid", type: "=", value: this.student.uid}, [ {field: "studiensemester_kurzbz", type: "=", value: this.currentSemester}, - {field: "insertvon", type: "=", value: "mlists_generate"} + {field: "studiensemester_kurzbz", type: "=", value: null} ] ], columns: [ @@ -45,7 +45,6 @@ export default { } }, {title: "UID", field: "uid"}, - {title: "InsertVon", field: "insertvon", visible: false}, { title: 'Aktionen', field: 'actions', minWidth: 150, // Ensures Action-buttons will be always fully displayed @@ -153,7 +152,7 @@ export default { {field: "uid", type: "=", value: this.student.uid}, [ {field: "studiensemester_kurzbz", type: "=", value: newVal}, - {field: "insertvon", type: "=", value: "mlists_generate"} + {field: "studiensemester_kurzbz", type: "=", value: null} ] ]); From 018220594a82590cffbcbc612f0f767a314387d7 Mon Sep 17 00:00:00 2001 From: ma0068 Date: Thu, 6 Feb 2025 08:34:34 +0100 Subject: [PATCH 0046/1216] prepare basic framework --- .../api/frontend/v1/messages/Messages.php | 45 +++++++ .../api/frontend/v1/stv/Config.php | 4 + .../api/frontend/v1/stv/Favorites.php | 2 +- application/models/system/Message_model.php | 9 +- public/js/api/fhcapifactory.js | 4 +- public/js/api/messages.js | 5 + public/js/api/messages/person.js | 6 + .../components/Messages/Details/NewMessage.js | 13 ++ .../Messages/Details/TableMessages.js | 117 ++++++++++++++++++ public/js/components/Messages/Messages.js | 48 +++++++ .../Studentenverwaltung/Details/Messages.js | 25 ++++ system/phrasesupdate.php | 25 +++- 12 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 application/controllers/api/frontend/v1/messages/Messages.php create mode 100644 public/js/api/messages.js create mode 100644 public/js/api/messages/person.js create mode 100644 public/js/components/Messages/Details/NewMessage.js create mode 100644 public/js/components/Messages/Details/TableMessages.js create mode 100644 public/js/components/Messages/Messages.js create mode 100644 public/js/components/Stv/Studentenverwaltung/Details/Messages.js diff --git a/application/controllers/api/frontend/v1/messages/Messages.php b/application/controllers/api/frontend/v1/messages/Messages.php new file mode 100644 index 000000000..f81e85e9a --- /dev/null +++ b/application/controllers/api/frontend/v1/messages/Messages.php @@ -0,0 +1,45 @@ + ['admin:r', 'assistenz:r'], + ]); + + //Load Models + $this->load->model('system/Message_model', 'MessageModel'); + + // Additional Permission Checks + //TODO(manu) check permissions + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + $this->load->library('form_validation'); + + // Load language phrases + $this->loadPhrases([ + 'ui' + ]); + } + + public function getMessages($id, $type_id) + { + //$this->terminateWithError("in backend " . $type_id . ": " . $id, self::ERROR_TYPE_GENERAL); + + if ($type_id != "person_id") + { + $this->terminateWithError("logic for type_id " . $type_id . " not defined yet", self::ERROR_TYPE_GENERAL); + } + + $result = $this->MessageModel->getMessagesOfPerson($id); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/stv/Config.php b/application/controllers/api/frontend/v1/stv/Config.php index c28c49485..9dec3015f 100644 --- a/application/controllers/api/frontend/v1/stv/Config.php +++ b/application/controllers/api/frontend/v1/stv/Config.php @@ -91,6 +91,10 @@ class Config extends FHCAPI_Controller 'title' => $this->p->t('stv', 'tab_resources'), 'component' => './Stv/Studentenverwaltung/Details/Betriebsmittel.js' ]; + $result['messages'] = [ + 'title' => $this->p->t('stv', 'tab_messages'), + 'component' => './Stv/Studentenverwaltung/Details/Messages.js' + ]; /* TODO(chris): Ausgeblendet für Testing $result['grades'] = [ 'title' => $this->p->t('stv', 'tab_grades'), diff --git a/application/controllers/api/frontend/v1/stv/Favorites.php b/application/controllers/api/frontend/v1/stv/Favorites.php index 8d7a6cd14..b8fe6f3d7 100644 --- a/application/controllers/api/frontend/v1/stv/Favorites.php +++ b/application/controllers/api/frontend/v1/stv/Favorites.php @@ -48,7 +48,7 @@ class Favorites extends FHCAPI_Controller if (!$data) $this->terminateWithSuccess(null); else - $this->terminateWithSuccess($data['stv_favorites']); + $this->terminateWithSuccess($data['stv_favorites'] ?? null); } public function set() diff --git a/application/models/system/Message_model.php b/application/models/system/Message_model.php index d9f8585ed..6288f54f3 100644 --- a/application/models/system/Message_model.php +++ b/application/models/system/Message_model.php @@ -85,7 +85,7 @@ class Message_model extends DB_Model */ public function getMessagesOfPerson($person_id, $status = null) { - $sql = 'SELECT m.message_id, + $sql = "SELECT m.message_id, m.person_id, m.subject, m.body, @@ -109,7 +109,10 @@ class Message_model extends DB_Model re.vornamen AS revornamen, s.status, s.statusinfo, - s.insertamum AS statusamum + s.insertamum AS statusamum, + CONCAT(se.titelpre, ' ', se.vorname, ' ', se.nachname, ' ', se.titelpost) as sender, + CONCAT(re.titelpre, ' ', re.vorname, ' ', re.nachname, ' ', re.titelpost ) as recipient, + TO_CHAR(s.insertamum::timestamp, 'DD.MM.YYYY HH24:MI') AS format_insertamum FROM public.tbl_msg_message m JOIN public.tbl_msg_recipient r ON m.message_id = r.message_id JOIN public.tbl_person se ON (m.person_id = se.person_id) @@ -122,7 +125,7 @@ class Message_model extends DB_Model ) s ON (m.message_id = s.message_id AND re.person_id = s.person_id) WHERE se.person_id = ? OR re.person_id = ? - '; + "; if (is_numeric($status)) { diff --git a/public/js/api/fhcapifactory.js b/public/js/api/fhcapifactory.js index c4106c3f6..7bc510b6e 100644 --- a/public/js/api/fhcapifactory.js +++ b/public/js/api/fhcapifactory.js @@ -33,6 +33,7 @@ import ort from "./ort.js"; import cms from "./cms.js"; import lehre from "./lehre.js"; import addons from "./addons.js"; +import messages from "./messages.js"; export default { search, @@ -52,5 +53,6 @@ export default { ort, cms, lehre, - addons + addons, + messages }; diff --git a/public/js/api/messages.js b/public/js/api/messages.js new file mode 100644 index 000000000..f1378c69d --- /dev/null +++ b/public/js/api/messages.js @@ -0,0 +1,5 @@ +import person from "./messages/person.js"; + +export default { + person +} \ No newline at end of file diff --git a/public/js/api/messages/person.js b/public/js/api/messages/person.js new file mode 100644 index 000000000..bfbdaf123 --- /dev/null +++ b/public/js/api/messages/person.js @@ -0,0 +1,6 @@ +export default { + getMessages(url, config, params){ + console.log("in api", params); + return this.$fhcApi.get('api/frontend/v1/messages/messages/getMessages/' + params.id + '/' + params.type); + }, +} \ No newline at end of file diff --git a/public/js/components/Messages/Details/NewMessage.js b/public/js/components/Messages/Details/NewMessage.js new file mode 100644 index 000000000..bf60842d2 --- /dev/null +++ b/public/js/components/Messages/Details/NewMessage.js @@ -0,0 +1,13 @@ +export default { + data(){ + return { + + } + }, + template: ` +
+

New Message

+
+ ` + +} \ No newline at end of file diff --git a/public/js/components/Messages/Details/TableMessages.js b/public/js/components/Messages/Details/TableMessages.js new file mode 100644 index 000000000..bc7e6a02a --- /dev/null +++ b/public/js/components/Messages/Details/TableMessages.js @@ -0,0 +1,117 @@ +import {CoreFilterCmpt} from "../../filter/Filter.js"; + +export default { + components: { + CoreFilterCmpt, + }, + props: { + endpoint: { + type: String, + required: true + }, + typeId: String, + id: { + type: [Number, String], + required: true + }, + }, + //TODO(Manu) endpoint macht Probleme + data(){ + return { + tabulatorOptions: { + ajaxURL: 'dummy', + ajaxRequestFunc: this.$fhcApi.factory.messages.person.getMessages, + ajaxParams: () => { + return { + id: this.id, + type: this.typeId + }; + }, + ajaxResponse: (url, params, response) => response.data, + columns: [ + {title: "subject", field: "subject"}, + {title: "body", field: "body", visible: false}, + {title: "message_id", field: "message_id", visible: false}, + {title: "datum", field: "format_insertamum"}, + {title: "sender", field: "sender"}, + {title: "recipient", field: "recipient"}, + {title: "sepersonid", field: "sepersonid"}, + {title: "repersonid", field: "repersonid"}, + {title: "status", field: "status"}, + { + title: 'Aktionen', field: 'actions', + width: 100, + formatter: (cell, formatterParams, onRendered) => { + let container = document.createElement('div'); + container.className = "d-flex gap-2"; + + let button = document.createElement('button'); + button.className = 'btn btn-outline-secondary btn-action'; + button.title = this.$p.t('ui', 'notiz_edit'); + button.innerHTML = ''; + button.addEventListener( + 'click', + (event) => + this.actionEditNotiz(cell.getData().notiz_id) + ); + container.append(button); + + button = document.createElement('button'); + button.className = 'btn btn-outline-secondary btn-action'; + button.title = this.$p.t('notiz', 'notiz_delete'); + button.innerHTML = ''; + button.addEventListener( + 'click', + () => + this.actionDeleteNotiz(cell.getData().notiz_id) + ); + container.append(button); + + return container; + }, + frozen: true + }], + layout: 'fitColumns', + layoutColumnsOnNewData: false, + height: '250', + selectableRangeMode: 'click', + selectable: true, + index: 'message_id', + persistenceID: 'core-message' + }, + } + }, +/* computed: { + statusText(){ + 0: this.$p.t('messsages', 'unread'), + 1: this.$p.t('messsages', 'read'), + 2: this.$p.t('messsages', 'archived'), + 0: this.$p.t('messsages', 'unread'), + 3: this.$p.t('person', 'deleted'), + } + },*/ + template: ` +
+

Table Messages

+

type_id: {{typeId}}

+

id: {{id}}

+

endpoint: {{endpoint}}

+ + + + + + +
+ ` + +} \ No newline at end of file diff --git a/public/js/components/Messages/Messages.js b/public/js/components/Messages/Messages.js new file mode 100644 index 000000000..6811a3aed --- /dev/null +++ b/public/js/components/Messages/Messages.js @@ -0,0 +1,48 @@ +import TableMessages from "./Details/TableMessages.js"; +import NewMessage from "./Details/NewMessage.js"; + +export default { + components: { + TableMessages, + NewMessage + }, + props: { + endpoint: { + type: String, + required: true + }, + typeId: String, + id: { + type: [Number, String], + required: true + }, + showNew: Boolean, + showTable: Boolean + }, + data() { + return {} + }, + template: ` +
+

endpoint: {{endpoint}}

+
+ + +
+ +
+ + + +
+ +
+ ` + +} \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Messages.js b/public/js/components/Stv/Studentenverwaltung/Details/Messages.js new file mode 100644 index 000000000..7bd6d4b4d --- /dev/null +++ b/public/js/components/Stv/Studentenverwaltung/Details/Messages.js @@ -0,0 +1,25 @@ +import CoreMessages from "../../../Messages/Messages.js"; + +export default { + components: { + CoreMessages + }, + props: { + modelValue: Object + }, + template: ` +
+ + + + + +
+ ` +}; \ No newline at end of file diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index cfaf85ec7..f25cc72ec 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -37297,7 +37297,30 @@ array( 'insertvon' => 'system' ) ) - ) + ), + /////////// FHC4 Phrases Messages START /////////// + array( + 'app' => 'core', + 'category' => 'stv', + 'phrase' => 'tab_messages', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Nachrichten', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Messages', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + /////////// FHC4 Phrases Messages END /////////// + ); From bc484453041935f163a2b029a556aa7bffd98693 Mon Sep 17 00:00:00 2001 From: ma0048 Date: Mon, 10 Feb 2025 13:05:44 +0100 Subject: [PATCH 0047/1216] - infocenter filter fuer electronic onboarding hinzugefuegt --- .../system/infocenter/InfoCenter.php | 44 +++++++ .../views/system/infocenter/onboarding.php | 47 +++++++ .../system/infocenter/onboardingData.php | 116 ++++++++++++++++++ system/filtersupdate.php | 23 ++++ 4 files changed, 230 insertions(+) create mode 100644 application/views/system/infocenter/onboarding.php create mode 100644 application/views/system/infocenter/onboardingData.php diff --git a/application/controllers/system/infocenter/InfoCenter.php b/application/controllers/system/infocenter/InfoCenter.php index f6e41d2e6..9024088f8 100644 --- a/application/controllers/system/infocenter/InfoCenter.php +++ b/application/controllers/system/infocenter/InfoCenter.php @@ -22,6 +22,7 @@ class InfoCenter extends Auth_Controller const REIHUNGSTESTABSOLVIERT_PAGE = 'reihungstestAbsolviert'; const ABGEWIESEN_PAGE = 'abgewiesen'; const AUFGENOMMEN_PAGE = 'aufgenommen'; + const ONBOARDING_PAGE = 'onboarding'; const SHOW_DETAILS_PAGE = 'showDetails'; const SHOW_ZGV_DETAILS_PAGE = 'showZGVDetails'; const ZGV_UBERPRUEFUNG_PAGE = 'ZGVUeberpruefung'; @@ -116,6 +117,7 @@ class InfoCenter extends Auth_Controller 'index' => 'infocenter:r', 'freigegeben' => 'infocenter:r', 'abgewiesen' => 'infocenter:r', + 'onboarding' => 'infocenter:r', 'aufgenommen' => 'infocenter:r', 'reihungstestAbsolviert' => 'infocenter:r', 'showDetails' => 'infocenter:r', @@ -230,6 +232,13 @@ class InfoCenter extends Auth_Controller $this->load->view('system/infocenter/infocenterAbgewiesen.php'); } + + public function onboarding() + { + $this->_setNavigationMenu(self::ONBOARDING_PAGE); // define the navigation menu for this page + + $this->load->view('system/infocenter/onboarding.php'); + } /** * Aufgenommene page of the InfoCenter tool @@ -1552,6 +1561,7 @@ class InfoCenter extends Auth_Controller $reihungstestAbsolviertLink = site_url(self::INFOCENTER_URI.'/'.self::REIHUNGSTESTABSOLVIERT_PAGE); $abgewiesenLink = site_url(self::INFOCENTER_URI.'/'.self::ABGEWIESEN_PAGE); $aufgenommenLink = site_url(self::INFOCENTER_URI.'/'.self::AUFGENOMMEN_PAGE); + $onboardingLink = site_url(self::INFOCENTER_URI.'/'.self::ONBOARDING_PAGE); $currentFilterId = $this->input->get(self::FILTER_ID); if (isset($currentFilterId)) @@ -1560,6 +1570,7 @@ class InfoCenter extends Auth_Controller $reihungstestAbsolviertLink .= '?'.self::PREV_FILTER_ID.'='.$currentFilterId; $abgewiesenLink .= '?'.self::PREV_FILTER_ID.'='.$currentFilterId; $aufgenommenLink .= '?'.self::PREV_FILTER_ID.'='.$currentFilterId; + $onboardingLink .= '?'.self::PREV_FILTER_ID.'='.$currentFilterId; } $this->navigationlib->setSessionMenu( @@ -1623,6 +1634,18 @@ class InfoCenter extends Auth_Controller '', // target 40 // sort ), + 'ohnePrestudent' => $this->navigationlib->oneLevel( + 'Electronic Onboarding', // description + $onboardingLink, // link + null, // children + 'users', // icon + null, // subscriptDescription + false, // expand + null, // subscriptLinkClass + null, // subscriptLinkValue + '', // target + 50 // sort + ), ) ); } @@ -1649,6 +1672,8 @@ class InfoCenter extends Auth_Controller $link = site_url(self::ZGV_UEBERPRUEFUNG_URI); if ($origin_page === self::ABGEWIESEN_PAGE) $link = site_url(self::INFOCENTER_URI.'/'.self::ABGEWIESEN_PAGE); + if ($origin_page === self::ONBOARDING_PAGE) + $link = site_url(self::INFOCENTER_URI.'/'.self::ONBOARDING_PAGE); if ($origin_page === self::AUFGENOMMEN_PAGE) $link = site_url(self::INFOCENTER_URI.'/'.self::AUFGENOMMEN_PAGE); @@ -1690,6 +1715,7 @@ class InfoCenter extends Auth_Controller $freigegebenLink = site_url(self::INFOCENTER_URI.'/'.self::FREIGEGEBEN_PAGE); $absolviertLink = site_url(self::INFOCENTER_URI.'/'.self::REIHUNGSTESTABSOLVIERT_PAGE); $abgewiesenLink = site_url(self::INFOCENTER_URI.'/'.self::ABGEWIESEN_PAGE); + $onboardingLink = site_url(self::INFOCENTER_URI.'/'.self::ONBOARDING_PAGE); $prevFilterId = $this->input->get(self::PREV_FILTER_ID); if (isset($prevFilterId)) { @@ -1766,6 +1792,24 @@ class InfoCenter extends Auth_Controller ) ); } + if($page == self::ONBOARDING_PAGE) + { + $this->navigationlib->setSessionElementMenu( + 'onboarding', + $this->navigationlib->oneLevel( + 'Electronic Onboarding', // description + $onboardingLink, // link + null, // children + 'users', // icon + null, // subscriptDescription + false, // expand + null, // subscriptLinkClass + null, // subscriptLinkValue + '', // target + 50 // sort + ) + ); + } } /** diff --git a/application/views/system/infocenter/onboarding.php b/application/views/system/infocenter/onboarding.php new file mode 100644 index 000000000..a600364d9 --- /dev/null +++ b/application/views/system/infocenter/onboarding.php @@ -0,0 +1,47 @@ +load->view( + 'templates/FHC-Header', + array( + 'title' => 'Info Center', + 'jquery3' => true, + 'jqueryui1' => true, + 'jquerycheckboxes1' => true, + 'bootstrap3' => true, + 'fontawesome4' => true, + 'sbadmintemplate3' => true, + 'tablesorter2' => true, + 'ajaxlib' => true, + 'filterwidget' => true, + 'navigationwidget' => true, + 'dialoglib' => true, + 'phrases' => array( + 'person' => array('vorname', 'nachname'), + 'ui' => array('bitteEintragWaehlen') + ), + 'customCSSs' => array('public/css/sbadmin2/tablesort_bootstrap.css', 'public/css/infocenter/infocenterPersonDataset.css'), + 'customJSs' => array('public/js/bootstrapper.js', 'public/js/infocenter/infocenterPersonDataset.js') + ) + ); +?> + +
+ + widgetlib->widget('NavigationWidget'); ?> + +
+
+
+
+ +
+
+
+ load->view('system/infocenter/onboardingData.php'); ?> +
+
+
+
+ +load->view('templates/FHC-Footer'); ?> diff --git a/application/views/system/infocenter/onboardingData.php b/application/views/system/infocenter/onboardingData.php new file mode 100644 index 000000000..5ee66fdde --- /dev/null +++ b/application/views/system/infocenter/onboardingData.php @@ -0,0 +1,116 @@ +>0 as bezeichnung + FROM public.tbl_rueckstellung + JOIN public.tbl_rueckstellung_status USING(status_kurzbz) + JOIN public.tbl_person sp ON tbl_rueckstellung.person_id = sp.person_id + WHERE tbl_rueckstellung.rueckstellung_id = + ( + SELECT srueck.rueckstellung_id + FROM public.tbl_rueckstellung srueck + WHERE srueck.person_id = tbl_rueckstellung.person_id + AND datum_bis >= NOW() + ORDER BY srueck.datum_bis DESC LIMIT 1 + ) + ) rueck ON rueck.person_id = p.person_id + WHERE p.person_id NOT IN (SELECT person_id FROM public.tbl_prestudent)'; + + $filterWidgetArray = array( + 'query' => $query, + 'app' => InfoCenter::APP, + 'datasetName' => 'onboarding', + 'filter_id' => $this->input->get('filter_id'), + 'requiredPermissions' => 'infocenter', + 'datasetRepresentation' => 'tablesorter', + 'checkboxes' => 'PersonId', + 'additionalColumns' => array('Details'), + 'columnsAliases' => array( + 'PersonId', + ucfirst($this->p->t('person', 'vorname')) , + ucfirst($this->p->t('person', 'nachname')), + ucfirst($this->p->t('global', 'sperrdatum')), + ucfirst($this->p->t('global', 'gesperrtVon')), + ucfirst($this->p->t('infocenter', 'rueckstelldatum')), + ucfirst($this->p->t('infocenter', 'rueckstellgrund')), + ), + + 'formatRow' => function($datasetRaw) { + /* NOTE: Dont use $this here for PHP Version compatibility */ + $datasetRaw->{'Details'} = sprintf( + 'Details', + site_url('system/infocenter/InfoCenter/showDetails'), + $datasetRaw->{'PersonId'}, + 'onboarding', + (isset($_GET['fhc_controller_id']) ? $_GET['fhc_controller_id'] : ''), + (isset($_GET['filter_id']) ? $_GET['filter_id'] : '') + ); + + if ($datasetRaw->{'LockDate'} == null) + { + $datasetRaw->{'LockDate'} = '-'; + } + + if ($datasetRaw->{'LockUser'} == null) + { + $datasetRaw->{'LockUser'} = '-'; + } + + if ($datasetRaw->{'HoldDate'} == null) + { + $datasetRaw->{'HoldDate'} = '-'; + } + else + { + $datasetRaw->{'HoldDate'} = date_format(date_create($datasetRaw->{'HoldDate'}), 'Y-m-d H:i'); + } + + if ($datasetRaw->{'Rueckstellgrund'} === null) + { + $datasetRaw->{'Rueckstellgrund'} = '-'; + } + + return $datasetRaw; + }, + + 'markRow' => function($datasetRaw) { + + if ($datasetRaw->LockDate != null) + { + return FilterWidget::DEFAULT_MARK_ROW_CLASS; + } + } + + + + ); + + echo $this->widgetlib->widget('FilterWidget', $filterWidgetArray); +?> diff --git a/system/filtersupdate.php b/system/filtersupdate.php index 51dd86314..cfcceb0f6 100644 --- a/system/filtersupdate.php +++ b/system/filtersupdate.php @@ -529,6 +529,29 @@ $filters = array( ', 'oe_kurzbz' => null, ), + array( + 'app' => 'infocenter', + 'dataset_name' => 'onboarding', + 'filter_kurzbz' => 'InfoCenterOnboarding', + 'description' => '{Alle}', + 'sort' => 1, + 'default_filter' => true, + 'filter' => ' + { + "name": "Electronic Onboarding - Alle", + "columns": [ + {"name": "PersonId"}, + {"name": "Vorname"}, + {"name": "Nachname"}, + {"name": "LockUser"}, + {"name": "HoldDate"}, + {"name": "Rueckstellgrund"} + ], + "filters": [] + } + ', + 'oe_kurzbz' => null, + ), array( 'app' => 'infocenter', 'dataset_name' => 'aufgenommen', From 4ccbdecc48ba3eaad48528e798b886dc72f879b7 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Mon, 10 Feb 2025 13:37:53 +0100 Subject: [PATCH 0048/1216] added phrases for new Beurteilungsformular 2025 --- system/phrasesupdate.php | 309 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index cfaf85ec7..f8a2763e3 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -37297,7 +37297,316 @@ array( 'insertvon' => 'system' ) ) + ), + // PROJEKTARBEITSBEURTEILUNG SS2025 PHRASEN --------------------------------------------------------------------------- + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'maxPunkte', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Max. Punkte', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Max. points', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'bewertung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Bewertung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Score', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'problemstellungZieldefinition', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Problemstellung und Zieldefinition', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'problemstellungZieldefinitionText', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Die Problemstellung ist klar und präzise definiert und in einen wissenschaftlichen Kontext eingebettet. +Die Zielsetzung sowie eventuelle Messgrößen sind eindeutig formuliert.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'methodikLoesungsansatz', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Methodik und Lösungsansatz', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'methodikLoesungsansatzText', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Das methodische Vorgehen ist logisch und nachvollziehbar strukturiert, passend zur Zielsetzung, +und die angewandten Methoden sind korrekt und fundiert umgesetzt. +Die Methodik ist fachspezifisch angemessen, literaturbasiert begründet und wissenschaftlich vertretbar.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'ergebnisseDiskussion', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Ergebnisse und Diskussion', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'ergebnisseDiskussionText', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Die Ergebnisse werden fundiert analysiert und in Bezug auf die Zielsetzung schlüssig interpretiert. +Die Diskussion reflektiert die Relevanz und Grenzen der Ergebnisse kritisch und ist logisch strukturiert.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'strukturAufbau', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Struktur und Aufbau', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'strukturAufbauText', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Die Arbeit folgt einer logischen, klaren Gliederung und einem konsistenten roten Faden. +Verzeichnisse, Grafiken, Tabellen und der Text sind gemäß den aktuell gültigen wissenschaftlichen Richtlinien der FH Technikum Wien aufbereitet.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'stilAusdruck', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Stil und Ausdruck', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'stilAusdruckText', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Der sprachliche Ausdruck ist präzise, +fachlich korrekt und erfüllt die Anforderungen an gendergerechte Sprache gemäß den geltenden Richtlinien der FH Technikum Wien.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'zitierregelnQuellenangaben', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Zitierregeln und Quellenangaben', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'zitierregelnQuellenangabenText', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Umfang, Qualität und Aktualität der verarbeiteten Quellen sind angemessen +und repräsentieren den aktuellen Stand der Forschung zum Thema. Die Zitierregeln (IEEE oder Harvard) werden konsequent und korrekt angewendet.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => '', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'notenschluesselHinweisGewichtungEinzeln', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Falls ein Kriterium negativ bewertet wird, ist die {0} insgesamt als negativ zu beurteilen.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'If a criterion has a negative score, the {0} is to be assessed as negative overall.', + 'description' => '', + 'insertvon' => 'system' + ) + ) ) + // PROJEKTARBEITSBEURTEILUNG SS2025 ENDE --------------------------------------------------------------------------- ); From ae3072be924940441968f5d65a352245af043afc Mon Sep 17 00:00:00 2001 From: ma0068 Date: Tue, 11 Feb 2025 09:02:56 +0100 Subject: [PATCH 0049/1216] listTables with preview in two layouts --- .../api/frontend/v1/messages/Messages.php | 2 +- application/models/system/Message_model.php | 57 +++- public/css/Studentenverwaltung.css | 1 + public/css/components/Messages.css | 4 + public/js/api/messages/person.js | 1 - .../Messages/Details/TableMessages.js | 272 +++++++++++++++--- public/js/components/Messages/Messages.js | 15 +- .../Studentenverwaltung/Details/Messages.js | 3 +- system/phrasesupdate.php | 200 +++++++++++++ 9 files changed, 508 insertions(+), 47 deletions(-) create mode 100644 public/css/components/Messages.css diff --git a/application/controllers/api/frontend/v1/messages/Messages.php b/application/controllers/api/frontend/v1/messages/Messages.php index f81e85e9a..adf1d52e9 100644 --- a/application/controllers/api/frontend/v1/messages/Messages.php +++ b/application/controllers/api/frontend/v1/messages/Messages.php @@ -36,7 +36,7 @@ class Messages extends FHCAPI_Controller $this->terminateWithError("logic for type_id " . $type_id . " not defined yet", self::ERROR_TYPE_GENERAL); } - $result = $this->MessageModel->getMessagesOfPerson($id); + $result = $this->MessageModel->getMessagesForTable($id); $data = $this->getDataOrTerminateWithError($result); diff --git a/application/models/system/Message_model.php b/application/models/system/Message_model.php index 6288f54f3..1b201fc1b 100644 --- a/application/models/system/Message_model.php +++ b/application/models/system/Message_model.php @@ -109,10 +109,7 @@ class Message_model extends DB_Model re.vornamen AS revornamen, s.status, s.statusinfo, - s.insertamum AS statusamum, - CONCAT(se.titelpre, ' ', se.vorname, ' ', se.nachname, ' ', se.titelpost) as sender, - CONCAT(re.titelpre, ' ', re.vorname, ' ', re.nachname, ' ', re.titelpost ) as recipient, - TO_CHAR(s.insertamum::timestamp, 'DD.MM.YYYY HH24:MI') AS format_insertamum + s.insertamum AS statusamum FROM public.tbl_msg_message m JOIN public.tbl_msg_recipient r ON m.message_id = r.message_id JOIN public.tbl_person se ON (m.person_id = se.person_id) @@ -233,4 +230,56 @@ class Message_model extends DB_Model return $this->execQuery($query, $params); } + + /** + * Gets messages for a person for tableMessages. + * @param $person_id + * @param null $status message status. by default, latest status is returned + * @return array|null + */ + public function getMessagesForTable($person_id, $status = null) + { + $sql = " + SELECT + m.message_id AS message_id, + m.subject AS subject, + m.body AS body, + m.insertamum AS insertamum, + m.relationmessage_id AS relationmessage_id, + (SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = m.person_id) as sender, + (SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = r.person_id) as recipient, + m.person_id as sender_id, + r.person_id as recipient_id, + MAX(ss.status) as status, + MAX(ss.insertamum) as statusdatum + FROM public.tbl_msg_message m + JOIN public.tbl_msg_recipient r USING(message_id) + JOIN public.tbl_msg_status ss ON(r.message_id = ss.message_id AND ss.person_id = r.person_id) + WHERE m.person_id = ? + GROUP BY m.message_id, m.subject, m.body, m.insertamum, m.relationmessage_id, sender, recipient, sender_id, recipient_id + UNION ALL + SELECT + m.message_id AS message_id, + m.subject AS subject, + m.body AS body, + m.insertamum AS insertamum, + m.relationmessage_id AS relationmessage_id, + (SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = m.person_id) as sender, + (SELECT COALESCE(titelpre,'') || ' ' || COALESCE(vorname,'') || ' ' || COALESCE(nachname,'') || ' ' || COALESCE(titelpost,'') FROM public.tbl_person WHERE person_id = r.person_id) as recipient, + m.person_id as sender_id, + r.person_id as recipient_id, + MAX(ss.status) as status, + MAX(ss.insertamum) as statusdatum + FROM public.tbl_msg_recipient r + JOIN public.tbl_msg_status ss USING(message_id, person_id) + JOIN public.tbl_msg_message m USING(message_id) + WHERE r.person_id = ? + GROUP BY m.message_id, m.subject, m.body, m.insertamum, m.relationmessage_id, sender, recipient, sender_id, recipient_id + ORDER BY insertamum + "; + + $parametersArray = array($person_id, $person_id); + + return $this->execQuery($sql, $parametersArray); + } } diff --git a/public/css/Studentenverwaltung.css b/public/css/Studentenverwaltung.css index f179c3667..cc2eff51d 100644 --- a/public/css/Studentenverwaltung.css +++ b/public/css/Studentenverwaltung.css @@ -4,6 +4,7 @@ @import './components/FilterComponent.css'; @import './components/Tabs.css'; @import './components/Notiz.css'; +@import './components/Messages.css'; html { font-size: .875em; diff --git a/public/css/components/Messages.css b/public/css/components/Messages.css new file mode 100644 index 000000000..1c004f6b0 --- /dev/null +++ b/public/css/components/Messages.css @@ -0,0 +1,4 @@ +.twoColumns { + height: 400px; + overflow-y: auto; +} diff --git a/public/js/api/messages/person.js b/public/js/api/messages/person.js index bfbdaf123..baf8bd39f 100644 --- a/public/js/api/messages/person.js +++ b/public/js/api/messages/person.js @@ -1,6 +1,5 @@ export default { getMessages(url, config, params){ - console.log("in api", params); return this.$fhcApi.get('api/frontend/v1/messages/messages/getMessages/' + params.id + '/' + params.type); }, } \ No newline at end of file diff --git a/public/js/components/Messages/Details/TableMessages.js b/public/js/components/Messages/Details/TableMessages.js index bc7e6a02a..c4fd4d4d7 100644 --- a/public/js/components/Messages/Details/TableMessages.js +++ b/public/js/components/Messages/Details/TableMessages.js @@ -1,8 +1,12 @@ import {CoreFilterCmpt} from "../../filter/Filter.js"; +import FormForm from '../../Form/Form.js'; +//import FormInput from '../../Form/Input.js'; export default { components: { CoreFilterCmpt, + FormForm, + // FormInput }, props: { endpoint: { @@ -14,6 +18,7 @@ export default { type: [Number, String], required: true }, + messageLayout: String, }, //TODO(Manu) endpoint macht Probleme data(){ @@ -32,12 +37,65 @@ export default { {title: "subject", field: "subject"}, {title: "body", field: "body", visible: false}, {title: "message_id", field: "message_id", visible: false}, - {title: "datum", field: "format_insertamum"}, + { + title: "Datum", + field: "insertamum", + formatter: function (cell) { + const dateStr = cell.getValue(); + const date = new Date(dateStr); // Convert to Date object + return date.toLocaleString("de-DE", { + day: "2-digit", + month: "2-digit", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: false + }); + } + }, {title: "sender", field: "sender"}, {title: "recipient", field: "recipient"}, - {title: "sepersonid", field: "sepersonid"}, - {title: "repersonid", field: "repersonid"}, - {title: "status", field: "status"}, + {title: "senderId", field: "sender_id"}, + {title: "recipientId", field: "recipient_id"}, + { + title: "status", + field: "status", + formatter: function (cell) { + //TODO(Manu) get phrases in this context to work? + + /* const statusMap = { + 0: this.$p.t('messsages', 'unread'), + 1: this.$p.t('messsages', 'read'), + 2: this.$p.t('messsages', 'archived'), + 3: this.$p.t('messsages', 'deleted') + };*/ + const statusMap = { + 0: 'unread', + 1: 'read', + 2: 'archived', + 3: 'deleted' + }; + return statusMap[cell.getValue()]; + // return this.$p.t('messsages', 'deleted'); + } + + }, + { + title: "letzte Änderung", + field: "statusdatum", + formatter: function (cell) { + const dateStr = cell.getValue(); + const date = new Date(dateStr); // Convert to Date object + return date.toLocaleString("de-DE", { + day: "2-digit", + month: "2-digit", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + hour12: false + }); + } + }, { title: 'Aktionen', field: 'actions', width: 100, @@ -47,23 +105,23 @@ export default { let button = document.createElement('button'); button.className = 'btn btn-outline-secondary btn-action'; - button.title = this.$p.t('ui', 'notiz_edit'); - button.innerHTML = ''; + button.title = this.$p.t('global', 'reply'); + button.innerHTML = ''; button.addEventListener( 'click', (event) => - this.actionEditNotiz(cell.getData().notiz_id) + this.reply(cell.getData().message_id) ); container.append(button); button = document.createElement('button'); button.className = 'btn btn-outline-secondary btn-action'; - button.title = this.$p.t('notiz', 'notiz_delete'); + button.title = this.$p.t('ui', 'loeschen'); button.innerHTML = ''; button.addEventListener( 'click', () => - this.actionDeleteNotiz(cell.getData().notiz_id) + this.deleteMessage(cell.getData().message_id) ); container.append(button); @@ -71,46 +129,186 @@ export default { }, frozen: true }], - layout: 'fitColumns', - layoutColumnsOnNewData: false, - height: '250', + layout: 'fitDataFill', + layoutColumnsOnNewData: false, + // height: 'auto', + height: '400', + selectable: true, + selectableRangeMode: 'click', +/* layoutColumnsOnNewData: false, + selectableRangeMode: 'click', selectable: true, index: 'message_id', - persistenceID: 'core-message' + persistenceID: 'core-message'*/ }, + tabulatorEvents: [ + { + event: 'dataLoaded', + handler: data => this.tabulatorData = data.map(item => { + return item; + }), + }, + { + event: 'tableBuilt', + handler: async() => { + await this.$p.loadCategory(['global', 'person', 'stv', 'messages', 'ui', 'notiz']); + + + let cm = this.$refs.table.tabulator.columnManager; + + cm.getColumnByField('subject').component.updateDefinition({ + title: this.$p.t('global', 'betreff') + }); + cm.getColumnByField('body').component.updateDefinition({ + title: this.$p.t('messages', 'body') + }); + cm.getColumnByField('message_id').component.updateDefinition({ + title: this.$p.t('messages', 'message_id') + }); + cm.getColumnByField('insertamum').component.updateDefinition({ + title: this.$p.t('global', 'datum') + }); + cm.getColumnByField('sender').component.updateDefinition({ + title: this.$p.t('messages', 'sender') + }); + cm.getColumnByField('recipient').component.updateDefinition({ + title: this.$p.t('messages', 'recipient') + }); + cm.getColumnByField('sender_id').component.updateDefinition({ + title: this.$p.t('messages', 'senderId') + }); + cm.getColumnByField('recipient_id').component.updateDefinition({ + title: this.$p.t('messages', 'recipientId') + }); + cm.getColumnByField('statusdatum').component.updateDefinition({ + title: this.$p.t('notiz', 'letzte_aenderung') + }); + /* + cm.getColumnByField('actions').component.updateDefinition({ + title: this.$p.t('global', 'aktionen') + }); + */ + } + }, + { + event: 'rowClick', + handler: (e, row) => { + const selectedMessage = row.getData().message_id; + const body = row.getData().body; + this.previewBody = body; + console.log(selectedMessage); + } + }, + ], + tabulatorData: [], + previewBody: "" } }, -/* computed: { + methods: { + reply(message_id){ + console.log("in reply " + message_id); + }, + deleteMessage(message_id){ + console.log("deleteMessage " + message_id); + }, + actionNewMessage(){ + console.log("action new message"); + }, + }, + computed: { statusText(){ - 0: this.$p.t('messsages', 'unread'), - 1: this.$p.t('messsages', 'read'), - 2: this.$p.t('messsages', 'archived'), - 0: this.$p.t('messsages', 'unread'), - 3: this.$p.t('person', 'deleted'), + return { + 0: this.$p.t('messsages', 'unread'), + 1: this.$p.t('messsages', 'read'), + 2: this.$p.t('messsages', 'archived'), + 3: this.$p.t('messsages', 'deleted') + } } - },*/ + }, + mounted() { + // change to target="_blank" +/* this.$nextTick(() => { + const links = document.querySelectorAll('.preview a'); + links.forEach(link => { + link.setAttribute('target', '_blank'); + link.setAttribute('rel', 'noopener noreferrer'); // Sicherheitsmaßnahme + }); + });*/ + }, template: `
-

Table Messages

-

type_id: {{typeId}}

-

id: {{id}}

endpoint: {{endpoint}}

+

{{messageLayout}}

+ + + + + + +
- - - - - +
+ +
+ + +
+ + +
+



+ +
+
+
+ +
+ +
+
+ + +
+ + +
+ + +
+ + +
+ +
+
+
+ +
+
+
` diff --git a/public/js/components/Messages/Messages.js b/public/js/components/Messages/Messages.js index 6811a3aed..3fd6e2a02 100644 --- a/public/js/components/Messages/Messages.js +++ b/public/js/components/Messages/Messages.js @@ -17,14 +17,24 @@ export default { required: true }, showNew: Boolean, - showTable: Boolean + showTable: Boolean, + messageLayout: { + type: String, + default: 'twoColumnsTableLeft', + validator(value) { + return [ + 'twoColumnsTableLeft', + 'listTableTop' + ].includes(value) + } + }, }, data() { return {} }, template: `
-

endpoint: {{endpoint}}

+

endpoint Messages.js: {{endpoint}}

diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Messages.js b/public/js/components/Stv/Studentenverwaltung/Details/Messages.js index 7bd6d4b4d..e9f542945 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Messages.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Messages.js @@ -14,12 +14,11 @@ export default { endpoint="$fhcApi.factory.messages.person" type-id="person_id" :id="modelValue.person_id" + messageLayout="listTableTop" show-table > - -
` }; \ No newline at end of file diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index f25cc72ec..95a505a23 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -37319,6 +37319,206 @@ array( ) ) ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'unread', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'ungelesen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'unread', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'read', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'gelesen', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'read', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'archived', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'archiviert', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'archived', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'deleted', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'gelöscht', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'deleted', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'body', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Nachrichtentext', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Body', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'message_id', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Message ID', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Message ID', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'sender', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'SenderIn', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Sender', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'recipient', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'EmpfängerIn', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Recipient', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'senderId', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'SenderIn ID', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Sender ID', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'messages', + 'phrase' => 'recipientId', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'EmpfängerIn ID', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Recipient ID', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), /////////// FHC4 Phrases Messages END /////////// ); From 92f3f0d89c71c1b6eb89287cef39d5adc6b9f995 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Wed, 12 Feb 2025 19:15:43 +0100 Subject: [PATCH 0050/1216] changed Projektarbeitsbeurteilung phrases --- system/phrasesupdate.php | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index f8a2763e3..59da9ab39 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -37327,13 +37327,13 @@ array( 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Bewertung', + 'text' => 'Bewertung (Prozent)', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'Score', + 'text' => 'Score (percentage)', 'description' => '', 'insertvon' => 'system' ) @@ -37353,7 +37353,7 @@ array( ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'Problem Definition and Objective Setting', 'description' => '', 'insertvon' => 'system' ) @@ -37374,7 +37374,8 @@ Die Zielsetzung sowie eventuelle Messgrößen sind eindeutig formuliert.', ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'The problem is clearly and precisely defined and embedded in a scientific context. +The objective, along with any potential metrics, is clearly formulated.', 'description' => '', 'insertvon' => 'system' ) @@ -37394,7 +37395,7 @@ Die Zielsetzung sowie eventuelle Messgrößen sind eindeutig formuliert.', ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'Methodology and Approach', 'description' => '', 'insertvon' => 'system' ) @@ -37416,7 +37417,9 @@ Die Methodik ist fachspezifisch angemessen, literaturbasiert begründet und wiss ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'The methodological approach is logically and comprehensively structured, +aligned with the objective, and the applied methods are implemented correctly and soundly. +The methodology is appropriate to the field, justified based on literature, and scientifically valid.', 'description' => '', 'insertvon' => 'system' ) @@ -37436,7 +37439,7 @@ Die Methodik ist fachspezifisch angemessen, literaturbasiert begründet und wiss ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'Results and Discussion', 'description' => '', 'insertvon' => 'system' ) @@ -37457,7 +37460,9 @@ Die Diskussion reflektiert die Relevanz und Grenzen der Ergebnisse kritisch und ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'The quality of the solution sufficiently meets the objective. +The results are thoroughly analyzed and coherently interpreted with respect to the objective. +The discussion critically reflects on the relevance and limitations of the results and is logically structured.', 'description' => '', 'insertvon' => 'system' ) @@ -37477,7 +37482,7 @@ Die Diskussion reflektiert die Relevanz und Grenzen der Ergebnisse kritisch und ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'Structure and Organization', 'description' => '', 'insertvon' => 'system' ) @@ -37498,7 +37503,8 @@ Verzeichnisse, Grafiken, Tabellen und der Text sind gemäß den aktuell gültige ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'The work follows a logical and clear outline with a consistent narrative thread. +Directories, graphics, tables, and text are prepared in accordance with the currently valid scientific guidelines of FH Technikum Wien.', 'description' => '', 'insertvon' => 'system' ) @@ -37518,7 +37524,7 @@ Verzeichnisse, Grafiken, Tabellen und der Text sind gemäß den aktuell gültige ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'Style and Expression', 'description' => '', 'insertvon' => 'system' ) @@ -37539,7 +37545,8 @@ fachlich korrekt und erfüllt die Anforderungen an gendergerechte Sprache gemä ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'The linguistic expression is precise, professionally accurate, +and meets the requirements of gender-sensitive language as per the applicable guidelines of FH Technikum Wien.', 'description' => '', 'insertvon' => 'system' ) @@ -37559,7 +37566,7 @@ fachlich korrekt und erfüllt die Anforderungen an gendergerechte Sprache gemä ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'Citation Rules and References', 'description' => '', 'insertvon' => 'system' ) @@ -37580,7 +37587,8 @@ und repräsentieren den aktuellen Stand der Forschung zum Thema. Die Zitierregel ), array( 'sprache' => 'English', - 'text' => '', + 'text' => 'The scope, quality, and timeliness of the sources processed are appropriate +and represent the current state of research on the topic. The prescribed citation rules are consistently and correctly applied.', 'description' => '', 'insertvon' => 'system' ) @@ -37594,13 +37602,13 @@ und repräsentieren den aktuellen Stand der Forschung zum Thema. Die Zitierregel 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Falls ein Kriterium negativ bewertet wird, ist die {0} insgesamt als negativ zu beurteilen.', + 'text' => 'Wenn ein Kriterium mit unter 50% bewertet wird, ist die Arbeit insgesamt als negativ zu beurteilen.', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'If a criterion has a negative score, the {0} is to be assessed as negative overall.', + 'text' => 'Each criterion must receive a minimum score of 50%; otherwise, the entire work is rated negatively.', 'description' => '', 'insertvon' => 'system' ) From de895362be5d3a8f520864be8daa7ef43fcaf8a2 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Wed, 12 Feb 2025 19:28:27 +0100 Subject: [PATCH 0051/1216] changed Projektarbeitsbeurteilung phrases even more --- system/phrasesupdate.php | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 59da9ab39..51399bbf2 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -37613,6 +37613,46 @@ and represent the current state of research on the topic. The prescribed citatio 'insertvon' => 'system' ) ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'begruendung', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Begründung', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Overall comment', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'begruendungVerpflichtend', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Eine Begründung der Bewertung ist notwendig', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'An overall comment is mandatory and must be completed', + 'description' => '', + 'insertvon' => 'system' + ) + ) ) // PROJEKTARBEITSBEURTEILUNG SS2025 ENDE --------------------------------------------------------------------------- ); From 7083955702e871fcd4c14b44a150e2cb6b831360 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Fri, 14 Feb 2025 16:44:33 +0100 Subject: [PATCH 0052/1216] changed Proektarbeitsbeurteilung phrases --- system/phrasesupdate.php | 41 +++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 51399bbf2..85373ff28 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -37327,13 +37327,35 @@ array( 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Bewertung (Prozent)', + 'text' => 'Erfüllungsgrad (Prozent) zum Ausfüllen', 'description' => '', 'insertvon' => 'system' ), array( 'sprache' => 'English', - 'text' => 'Score (percentage)', + 'text' => 'Degree of Fulfilment (Percentage) to Fill In', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'projektarbeitsbeurteilung', + 'category' => 'projektarbeitsbeurteilung', + 'phrase' => 'details', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Details (angemessener und korrekter Einsatz von +Werkzeugen und Technologien in jedem Schritt)', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Details +(appropriate and correct use of tools and technologies at each step)', 'description' => '', 'insertvon' => 'system' ) @@ -37453,8 +37475,9 @@ The methodology is appropriate to the field, justified based on literature, and 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Die Ergebnisse werden fundiert analysiert und in Bezug auf die Zielsetzung schlüssig interpretiert. -Die Diskussion reflektiert die Relevanz und Grenzen der Ergebnisse kritisch und ist logisch strukturiert.', + 'text' => 'Die Qualität der Lösung ist bezogen auf die Zielsetzung ausreichend. + Die Ergebnisse werden fundiert analysiert und in Bezug auf die Zielsetzung schlüssig interpretiert. + Die Diskussion reflektiert die Relevanz und Grenzen der Ergebnisse kritisch und ist logisch strukturiert.', 'description' => '', 'insertvon' => 'system' ), @@ -37602,7 +37625,7 @@ and represent the current state of research on the topic. The prescribed citatio 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Wenn ein Kriterium mit unter 50% bewertet wird, ist die Arbeit insgesamt als negativ zu beurteilen.', + 'text' => 'Jedes Kriterium muss mit mind. 50% bewertet werden, sonst ist die gesamte Arbeit negativ.', 'description' => '', 'insertvon' => 'system' ), @@ -37617,12 +37640,12 @@ and represent the current state of research on the topic. The prescribed citatio array( 'app' => 'projektarbeitsbeurteilung', 'category' => 'projektarbeitsbeurteilung', - 'phrase' => 'begruendung', + 'phrase' => 'gesamtkommentar', 'insertvon' => 'system', 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Begründung', + 'text' => 'Gesamtkommentar', 'description' => '', 'insertvon' => 'system' ), @@ -37637,12 +37660,12 @@ and represent the current state of research on the topic. The prescribed citatio array( 'app' => 'projektarbeitsbeurteilung', 'category' => 'projektarbeitsbeurteilung', - 'phrase' => 'begruendungVerpflichtend', + 'phrase' => 'gesamtkommentarVerpflichtend', 'insertvon' => 'system', 'phrases' => array( array( 'sprache' => 'German', - 'text' => 'Eine Begründung der Bewertung ist notwendig', + 'text' => 'Gesamtkommentar verpflichtend auszufüllen', 'description' => '', 'insertvon' => 'system' ), From 57eec9b940dc15af0289ca29decf5cdd3e827fee Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Fri, 14 Feb 2025 23:47:19 +0100 Subject: [PATCH 0053/1216] adapted document export to new Projektarbeit version (percent assessment) --- cis/private/lehre/abgabe_lektor_details.php | 8 +- cis/private/lehre/abgabe_student.php | 4 +- cis/private/lehre/abgabe_student_details.php | 4 +- .../projektbeurteilungDocumentExport.php | 45 ++++ cis/private/pdfExport.php | 54 ++-- include/projektarbeit.class.php | 248 +++++++++++------- 6 files changed, 238 insertions(+), 125 deletions(-) create mode 100644 cis/private/lehre/projektbeurteilungDocumentExport.php diff --git a/cis/private/lehre/abgabe_lektor_details.php b/cis/private/lehre/abgabe_lektor_details.php index a8705e7f9..74722607f 100644 --- a/cis/private/lehre/abgabe_lektor_details.php +++ b/cis/private/lehre/abgabe_lektor_details.php @@ -125,7 +125,7 @@ $projekttyp_kurzbz = $projektarbeit_obj->projekttyp_kurzbz; // paarbeit sollte nur ab bestimmten Zeitpunkt online bewertet werden $paIsCurrent = $projektarbeit_obj->projektarbeitIsCurrent($projektarbeit_id); -if(!is_numeric($paIsCurrent) || $paIsCurrent < 0) +if(!is_bool($paIsCurrent)) { echo "".$p->t('abgabetool/fehlerAktualitaetProjektarbeit')."
 "; } @@ -166,7 +166,7 @@ if(in_array($betreuerart, array('Erstbegutachter', 'Senatsvorsitz'))) } // Mail mit Token an Zweitbegutachter senden - if (count($zweitbetreuerArr) > 0 && $paIsCurrent >= 1 && isset($_GET['zweitbegutachtertoken']) && isset($_GET['zweitbetreuer_person_id'])) + if (count($zweitbetreuerArr) > 0 && $paIsCurrent === true && isset($_GET['zweitbegutachtertoken']) && isset($_GET['zweitbetreuer_person_id'])) { $qry_std="SELECT * FROM campus.vw_benutzer where uid=".$db->db_add_param($uid); if(!$result_std=$db->db_query($qry_std)) @@ -482,7 +482,7 @@ $htmlstr .= "\n"; $htmlstr .= ""; $htmlstr .= ""; } diff --git a/cis/private/lehre/abgabe_student_details.php b/cis/private/lehre/abgabe_student_details.php index 860eb7579..f040ed034 100644 --- a/cis/private/lehre/abgabe_student_details.php +++ b/cis/private/lehre/abgabe_student_details.php @@ -472,8 +472,8 @@ if($command=="update" && $error!=true) else { // paarbeit sollte nur ab bestimmten Zeitpunkt online bewertet werden - $num_rows_sem = $projektarbeit_obj->projektarbeitIsCurrent($projektarbeit_id); - if(!is_numeric($num_rows_sem) || $num_rows_sem < 0) + $paIsCurrent = $projektarbeit_obj->projektarbeitIsCurrent($projektarbeit_id); + if(!is_bool($paIsCurrent)) { echo "".$p->t('abgabetool/fehlerAktualitaetProjektarbeit')."
 "; } diff --git a/cis/private/lehre/projektbeurteilungDocumentExport.php b/cis/private/lehre/projektbeurteilungDocumentExport.php new file mode 100644 index 000000000..195650a24 --- /dev/null +++ b/cis/private/lehre/projektbeurteilungDocumentExport.php @@ -0,0 +1,45 @@ +getBerechtigungen($user); + +$projektarbeit = new projektarbeit(); +$projektarbeit->load($_GET['projektarbeit_id']); + +$betreuer = new person(); +$betreuer->getPersonFromBenutzer($user); + +//Überprüft ob es der Betreuer oder der Student ist +if ($betreuer->person_id !== $_GET['person_id'] && $projektarbeit->student_uid !== $user && !$rechte->isBerechtigt('assistenz')) + die("

Sie haben keine Berechtigung für diese Aktion.

"); + +$projektarbeitVorlage = new projektarbeit(); + +// passende Vorlage holen +$vorlage = $projektarbeitVorlage->getVorlage($_GET['projektarbeit_id'], $_GET['betreuerart_kurzbz']); + + +if ($vorlage == null) + die("

".$projektarbeitVorlage->errormsg."

"); + +// weiterleiten auf Dokumentexport +header('Location: ' . APP_ROOT . '/cis/private/pdfExport.php?xml=projektarbeitsbeurteilung.xml.php' + .'&xsl='.$vorlage.'&betreuerart_kurzbz=' . $_GET['betreuerart_kurzbz'] + . '&projektarbeit_id=' . $_GET['projektarbeit_id'] . '&person_id=' . $_GET['person_id']. '&uid=' . $user +); +die(); diff --git a/cis/private/pdfExport.php b/cis/private/pdfExport.php index ad2bb1fae..7393a084c 100644 --- a/cis/private/pdfExport.php +++ b/cis/private/pdfExport.php @@ -196,41 +196,41 @@ if (isset($_GET['output']) && $_GET['output'] != 'pdf') else $output = 'pdf'; -if (isset($_GET['xsl']) && ($_GET['xsl'] === 'Projektbeurteilung')) -{ - if (!isset($_GET['betreuerart_kurzbz']) || !isset($_GET['person_id']) || !isset($_GET['projektarbeit_id'])) - die('Fehlerhafte Parameteruebergabe'); +//~ if (isset($_GET['xsl']) && ($_GET['xsl'] === 'Projektbeurteilung')) +//~ { + //~ if (!isset($_GET['betreuerart_kurzbz']) || !isset($_GET['person_id']) || !isset($_GET['projektarbeit_id'])) + //~ die('Fehlerhafte Parameteruebergabe'); - $projektarbeit = new projektarbeit(); - $projektarbeit->load($_GET['projektarbeit_id']); + //~ $projektarbeit = new projektarbeit(); + //~ $projektarbeit->load($_GET['projektarbeit_id']); - $betreuer = new person(); - $betreuer->getPersonFromBenutzer($user); + //~ $betreuer = new person(); + //~ $betreuer->getPersonFromBenutzer($user); - //Überprüft ob es der Betreuer oder der Student ist - if ($betreuer->person_id !== $_GET['person_id'] && $projektarbeit->student_uid !== $user && !$rechte->isBerechtigt('assistenz')) - die("

Sie haben keine Berechtigung für diese Aktion.

"); + //~ //Überprüft ob es der Betreuer oder der Student ist + //~ if ($betreuer->person_id !== $_GET['person_id'] && $projektarbeit->student_uid !== $user && !$rechte->isBerechtigt('assistenz')) + //~ die("

Sie haben keine Berechtigung für diese Aktion.

"); - switch ($_GET['betreuerart_kurzbz']) - { - case 'Begutachter' : - case 'Senatsvorsitz' : - $xsl = 'ProjektBeurteilungBA'; - break; - case 'Erstbegutachter' : - $xsl = 'ProjektBeurteilungMAErst'; - break; - case 'Zweitbegutachter' : - $xsl = 'ProjektBeurteilungMAZweit'; - break; - } + //~ switch ($_GET['betreuerart_kurzbz']) + //~ { + //~ case 'Begutachter' : + //~ case 'Senatsvorsitz' : + //~ $xsl = 'ProjektBeurteilungBA'; + //~ break; + //~ case 'Erstbegutachter' : + //~ $xsl = 'ProjektBeurteilungMAErst'; + //~ break; + //~ case 'Zweitbegutachter' : + //~ $xsl = 'ProjektBeurteilungMAZweit'; + //~ break; + //~ } - $allowed = true; -} + //~ $allowed = true; +//~ } $konto = new konto(); -if ((((isset($_GET["uid"]) && $user == $_GET["uid"])) || $rechte->isBerechtigt('admin')) || (isset($allowed) && $allowed === true)) +if (((isset($_GET["uid"]) && $user == $_GET["uid"])) || $rechte->isBerechtigt('admin')) { $buchungstypen = array(); if (defined("CIS_DOKUMENTE_STUDIENBEITRAG_TYPEN")) diff --git a/include/projektarbeit.class.php b/include/projektarbeit.class.php index 783a0670b..5f07cf69d 100644 --- a/include/projektarbeit.class.php +++ b/include/projektarbeit.class.php @@ -16,8 +16,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * Authors: Christian Paminger , - * Andreas Oesterreicher and - * Rudolf Hangl . + * Andreas Oesterreicher and + * Rudolf Hangl . */ /** * Klasse projektarbeit @@ -27,7 +27,7 @@ require_once(dirname(__FILE__).'/basis_db.class.php'); class projektarbeit extends basis_db { - public $new; // boolean + public $new; // boolean public $result = array(); // adresse Objekt //Tabellenspalten @@ -59,6 +59,37 @@ class projektarbeit extends basis_db public $abgabedatum; + // Welche Version der Projektarbeit wird in welchem Semester verwendet + private $_versions = array( + 'Diplom' => array( + 'SS2025' => 3, + 'SS2023' => 2, + 'SS2022' => 1 + ), + 'Others' => array( + 'SS2025' => 2, + 'SS2022' => 1 + ) + ); + + // welche Vorlagen werden für welche Projekarbeitsversion verwendet (beginnend mit 0) + private $_projektarbeitVorlageMappings = array( + 'Begutachter' => array( + 2 => 'ProjektBeurteilungBAProzent', + 0 => 'ProjektBeurteilungBA' + ), + 'Senatsvorsitz' => array( + 2 => 'ProjektBeurteilungBAProzent', + 0 => 'ProjektBeurteilungBA' + ), + 'Erstbegutachter' => array( + 3 => 'ProjektBeurteilungMAProzent', + 0 => 'ProjektBeurteilungMAErst' + ), + 'Zweitbegutachter' => array( + 0 => 'ProjektBeurteilungMAZweit' + ) + ); /** * Konstruktor @@ -233,25 +264,25 @@ class projektarbeit extends basis_db $qry='BEGIN; INSERT INTO lehre.tbl_projektarbeit (projekttyp_kurzbz, titel, lehreinheit_id, student_uid, firma_id, note, punkte, beginn, ende, faktor, freigegeben, gesperrtbis, stundensatz, gesamtstunden, themenbereich, anmerkung, insertamum, insertvon, updateamum, updatevon, titel_english, final) VALUES('. - $this->db_add_param($this->projekttyp_kurzbz).', '. - $this->db_add_param($this->titel).', '. - $this->db_add_param($this->lehreinheit_id, FHC_INTEGER).', '. - $this->db_add_param($this->student_uid).', '. - $this->db_add_param($this->firma_id, FHC_INTEGER).', '. - $this->db_add_param($this->note).', '. - $this->db_add_param($this->punkte).', '. - $this->db_add_param($this->beginn).', '. - $this->db_add_param($this->ende).', '. - $this->db_add_param($this->faktor).', '. - $this->db_add_param($this->freigegeben, FHC_BOOLEAN).', '. - $this->db_add_param($this->gesperrtbis).', '. - $this->db_add_param($this->stundensatz).', '. - $this->db_add_param($this->gesamtstunden).', '. - $this->db_add_param($this->themenbereich).', '. - $this->db_add_param($this->anmerkung).', now(), '. - $this->db_add_param($this->insertvon).', now(), '. - $this->db_add_param($this->updatevon).','. - $this->db_add_param($this->titel_english).','. + $this->db_add_param($this->projekttyp_kurzbz).', '. + $this->db_add_param($this->titel).', '. + $this->db_add_param($this->lehreinheit_id, FHC_INTEGER).', '. + $this->db_add_param($this->student_uid).', '. + $this->db_add_param($this->firma_id, FHC_INTEGER).', '. + $this->db_add_param($this->note).', '. + $this->db_add_param($this->punkte).', '. + $this->db_add_param($this->beginn).', '. + $this->db_add_param($this->ende).', '. + $this->db_add_param($this->faktor).', '. + $this->db_add_param($this->freigegeben, FHC_BOOLEAN).', '. + $this->db_add_param($this->gesperrtbis).', '. + $this->db_add_param($this->stundensatz).', '. + $this->db_add_param($this->gesamtstunden).', '. + $this->db_add_param($this->themenbereich).', '. + $this->db_add_param($this->anmerkung).', now(), '. + $this->db_add_param($this->insertvon).', now(), '. + $this->db_add_param($this->updatevon).','. + $this->db_add_param($this->titel_english).','. $this->db_add_param($this->final, FHC_BOOLEAN).');'; } else @@ -471,92 +502,129 @@ class projektarbeit extends basis_db } /** - * Prüft ob Projektarbeit aktuell ist (ab bestimmtem Semester). - * Masterarbeiten sind ab der Änderung zur Gewichtung der Punkte aktuell, - * Bachelorarbeiten schon ab dem Umstieg auf das Online Beurteilungsformular. + * Prüft ob Projektarbeit aktuell ist (also zurzeit online bewertet wird). * @param $projektarbeit_id - * @return int -1 wenn Fehler, 0 wenn nicht aktuell, 1 wenn aktuell + * @return boolean */ public function projektarbeitIsCurrent($projektarbeit_id) { + $version = $this->getVersion($projektarbeit_id); // paarbeit sollte nur ab einem Studiensemester online bewertet werden - $qry="SELECT 1 - FROM lehre.tbl_projektarbeit - JOIN lehre.tbl_lehreinheit USING(lehreinheit_id) - JOIN public.tbl_studiensemester USING(studiensemester_kurzbz) - WHERE projektarbeit_id=".$this->db_add_param($projektarbeit_id, FHC_INTEGER)." - AND - ( - ( - projekttyp_kurzbz = 'Diplom' - AND tbl_studiensemester.start::date >= ( - SELECT start - FROM public.tbl_studiensemester - WHERE studiensemester_kurzbz = 'SS2023' - )::date - ) - OR - ( - projekttyp_kurzbz <> 'Diplom' - AND tbl_studiensemester.start::date >= ( - SELECT start - FROM public.tbl_studiensemester - WHERE studiensemester_kurzbz = 'SS2022' - )::date - ) - ) - LIMIT 1"; - - $result_sem=$this->db_query($qry); - - if (!$result_sem) - { - $this->errormsg = "Fehler beim Ermitteln der Projektarbeit Aktualität"; - return -1; - } - - $num_rows = $this->db_num_rows($result_sem); - - if ($num_rows < 0) - { - $this->errormsg = "Fehler beim Ermitteln der Anzahl der aktuellen Projektarbeiten"; - } - - return $num_rows; + return $version === null ? null : $version->isCurrent; } /** - * Prüft ob Projektarbeit aktuell ist (ab bestimmtem Semester), vor der Änderung zur Gewichtung der Punkte. + * Holt sich Version der Projektarbeit. + * Liefert auch mit, ob die Version die aktuellste ist. + * z.B.: Masterarbeiten waren ab der Änderung zur Gewichtung der Punkte aktuell, + * Bachelorarbeiten waren ab dem Umstieg auf das Online Beurteilungsformular aktuell. * @param $projektarbeit_id - * @return int -1 wenn Fehler, 0 wenn nicht aktuell, 1 wenn aktuell + * @return objekt mit Versionsinfo, null im Fehlerfall */ - public function projektarbeitIsCurrentBeforeWeightening($projektarbeit_id) + public function getVersion($projektarbeit_id) { // paarbeit sollte nur ab einem Studiensemester online bewertet werden - $qry="SELECT 1 - FROM lehre.tbl_projektarbeit + $qry=" + SELECT + CASE + WHEN semesters_diplom.studiensemester_kurzbz IS NOT NULL + THEN semesters_diplom.studiensemester_kurzbz + ELSE semesters.studiensemester_kurzbz + END AS version_studiensemester_kurzbz, + pa.projekttyp_kurzbz + FROM + lehre.tbl_projektarbeit pa JOIN lehre.tbl_lehreinheit USING(lehreinheit_id) - JOIN public.tbl_studiensemester USING(studiensemester_kurzbz) - WHERE projektarbeit_id=".$this->db_add_param($projektarbeit_id, FHC_INTEGER)." - AND tbl_studiensemester.start::date >= (SELECT start FROM public.tbl_studiensemester WHERE studiensemester_kurzbz = 'SS2022')::date - LIMIT 1"; + JOIN public.tbl_studiensemester sem USING(studiensemester_kurzbz) + LEFT JOIN ( + SELECT + start, studiensemester_kurzbz + FROM + public.tbl_studiensemester + WHERE + studiensemester_kurzbz IN (".$this->db_implode4SQL(array_keys($this->_versions['Others'])).") + ) semesters ON sem.start >= semesters.start AND pa.projekttyp_kurzbz <> 'Diplom' + LEFT JOIN ( + SELECT + start, studiensemester_kurzbz + FROM + public.tbl_studiensemester + WHERE + studiensemester_kurzbz IN (".$this->db_implode4SQL(array_keys($this->_versions['Diplom'])).") + ) semesters_diplom ON sem.start >= semesters_diplom.start AND pa.projekttyp_kurzbz = 'Diplom' + WHERE + projektarbeit_id=".$this->db_add_param($projektarbeit_id, FHC_INTEGER)." + ORDER BY + semesters.start DESC, semesters_diplom.start DESC + LIMIT 1"; - $result_sem=$this->db_query($qry); + $errormsg = "Fehler beim Ermitteln der Projektarbeit Version"; - if (!$result_sem) + if ($this->db_query($qry)) { - $this->errormsg = "Fehler beim Ermitteln der Projektarbeit Aktualität"; - return -1; + if ($row = $this->db_fetch_object()) + { + // known project types + if (isset($this->_versions[$row->projekttyp_kurzbz][$row->version_studiensemester_kurzbz])) + { + $row->versionNumber = $this->_versions[$row->projekttyp_kurzbz][$row->version_studiensemester_kurzbz]; + $row->isCurrent = + $this->_versions[$row->projekttyp_kurzbz][$row->version_studiensemester_kurzbz] + == max($this->_versions[$row->projekttyp_kurzbz]); + + } + elseif (isset($this->_versions['Others'][$row->version_studiensemester_kurzbz])) + { + $row->versionNumber = $this->_versions['Others'][$row->version_studiensemester_kurzbz]; + $row->isCurrent = + $this->_versions['Others'][$row->version_studiensemester_kurzbz] + == max($this->_versions['Others']); + } + else + { + $row->isCurrent = false; + $row->versionNumber = 0; + } + return $row; + } + else + { + $this->errormsg = $errormsg; + return null; + } + } + else + { + $this->errormsg = $errormsg; + return null; + } + } + + /** + * Holt Version einer Projektarbeit für eine Betreuerart. + * @param $projektarbeit_id + * @param $betreuerart_kurzbz + * @return string Vorlagenname + */ + public function getVorlage($projektarbeit_id, $betreuerart_kurzbz) + { + $version = $this->getVersion($projektarbeit_id); + + if ($version == null) return null; + + $key = 0; + if (isset($this->_projektarbeitVorlageMappings[$betreuerart_kurzbz])) + { + foreach ($this->_projektarbeitVorlageMappings[$betreuerart_kurzbz] as $versionNumber => $vorlage) + { + if ($versionNumber <= $version->versionNumber && $versionNumber > $key) $key = $versionNumber; + } } - $num_rows = $this->db_num_rows($result_sem); - - if ($num_rows < 0) - { - $this->errormsg = "Fehler beim Ermitteln der Anzahl der aktuellen Projektarbeiten"; - } - - return $num_rows; + return + isset($this->_projektarbeitVorlageMappings[$betreuerart_kurzbz][$key]) + ? $this->_projektarbeitVorlageMappings[$betreuerart_kurzbz][$key] + : ''; } } ?> From c960b4832d110794842de25d9c8caa5274dfff0a Mon Sep 17 00:00:00 2001 From: SimonGschnell Date: Mon, 17 Feb 2025 14:40:24 +0100 Subject: [PATCH 0054/1216] test --- public/js/api/stundenplan.js | 11 +++++++++++ public/js/apps/Dashboard/Fhc.js | 16 ++++++++-------- public/js/components/Calendar/Week/Page.js | 4 +++- public/js/components/Cis/Cms/Content.js | 2 +- public/js/components/Cis/Cms/News.js | 2 +- .../StudiengangInformation.js | 4 ++-- public/js/components/Cis/Mylv/LvUebersicht.js | 4 ++-- .../js/components/Cis/Stundenplan/Stundenplan.js | 5 +++++ 8 files changed, 33 insertions(+), 15 deletions(-) diff --git a/public/js/api/stundenplan.js b/public/js/api/stundenplan.js index 9ddbeff54..0f5de934e 100644 --- a/public/js/api/stundenplan.js +++ b/public/js/api/stundenplan.js @@ -42,4 +42,15 @@ export default { {} ); }, + getMoodleEventsByUserid(username, timestart, timeend) { + return this.$fhcApi.get( + FHC_JS_DATA_STORAGE_OBJECT.app_root + + `addons/moodle/cis/get_events_by_userid.php`, + { + username: username, + timestart: timestart, + timeend: timeend, + } + ); + }, }; \ No newline at end of file diff --git a/public/js/apps/Dashboard/Fhc.js b/public/js/apps/Dashboard/Fhc.js index 9f77d4b9b..19f6b3a2b 100644 --- a/public/js/apps/Dashboard/Fhc.js +++ b/public/js/apps/Dashboard/Fhc.js @@ -2,14 +2,14 @@ import FhcDashboard from '../../components/Dashboard/Dashboard.js'; import FhcApi from '../../plugin/FhcApi.js'; import Phrasen from '../../plugin/Phrasen.js'; import contrast from '../../directives/contrast.js'; -import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers"; -import Stundenplan from "../../components/Cis/Stundenplan/Stundenplan"; -import MylvStudent from "../../components/Cis/Mylv/Student"; -import Profil from "../../components/Cis/Profil/Profil"; -import CmsNews from "../../components/Cis/Cms/News"; -import CmsContent from "../../components/Cis/Cms/Content"; -import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info"; -import RoomInformation from "../../components/Cis/Mylv/RoomInformation"; +import {setScrollbarWidth} from "../../helpers/CssVarCalcHelpers.js"; +import Stundenplan from "../../components/Cis/Stundenplan/Stundenplan.js"; +import MylvStudent from "../../components/Cis/Mylv/Student.js"; +import Profil from "../../components/Cis/Profil/Profil.js"; +import CmsNews from "../../components/Cis/Cms/News.js"; +import CmsContent from "../../components/Cis/Cms/Content.js"; +import Info from "../../components/Cis/Mylv/Semester/Studiengang/Lv/Info.js"; +import RoomInformation from "../../components/Cis/Mylv/RoomInformation.js"; const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router; diff --git a/public/js/components/Calendar/Week/Page.js b/public/js/components/Calendar/Week/Page.js index 10ff6c495..b029b854a 100644 --- a/public/js/components/Calendar/Week/Page.js +++ b/public/js/components/Calendar/Week/Page.js @@ -197,7 +197,9 @@ export default { 'grid-template-columns': 'repeat(' + day.lanes + ', 1fr)', 'grid-template-rows': 'repeat(' + (this.hours.length * 60 / this.smallestTimeFrame) + ', 1fr)', } - + console.log(this.smallestTimeFrame,"this is the smallest timeframe") + console.log(this.hours.length,"this is the length of the hours") + console.log(this.hours.length/60, "this is the length of the hours multiplied by 60 resulting in minutes") if(day.isPast) { styleObj['background-color'] = '#F5E9D7' styleObj['border-color'] = '#E8E8E8'; diff --git a/public/js/components/Cis/Cms/Content.js b/public/js/components/Cis/Cms/Content.js index ed87e1aae..d657322ba 100644 --- a/public/js/components/Cis/Cms/Content.js +++ b/public/js/components/Cis/Cms/Content.js @@ -1,6 +1,6 @@ import raum_contentmittitel from './Content_types/Raum_contentmittitel.js' import general from './Content_types/General.js' -import BsConfirm from "../../Bootstrap/Confirm"; +import BsConfirm from "../../Bootstrap/Confirm.js"; export default { name: "ContentComponent", diff --git a/public/js/components/Cis/Cms/News.js b/public/js/components/Cis/Cms/News.js index 89bed18d7..1567cbef4 100644 --- a/public/js/components/Cis/Cms/News.js +++ b/public/js/components/Cis/Cms/News.js @@ -1,6 +1,6 @@ import Pagination from "../../Pagination/Pagination.js"; import StudiengangInformation from "./StudiengangInformation/StudiengangInformation.js"; -import BsConfirm from "../../Bootstrap/Confirm"; +import BsConfirm from "../../Bootstrap/Confirm.js"; export default { name: "NewsComponent", diff --git a/public/js/components/Cis/Cms/StudiengangInformation/StudiengangInformation.js b/public/js/components/Cis/Cms/StudiengangInformation/StudiengangInformation.js index 06743ae5d..92e303801 100644 --- a/public/js/components/Cis/Cms/StudiengangInformation/StudiengangInformation.js +++ b/public/js/components/Cis/Cms/StudiengangInformation/StudiengangInformation.js @@ -1,5 +1,5 @@ -import StudiengangPerson from "./StudiengangPerson"; -import StudiengangVertretung from "./StudiengangVertretung"; +import StudiengangPerson from "./StudiengangPerson.js"; +import StudiengangVertretung from "./StudiengangVertretung.js"; export default { data(){ diff --git a/public/js/components/Cis/Mylv/LvUebersicht.js b/public/js/components/Cis/Mylv/LvUebersicht.js index 6670a0c76..13972af5e 100644 --- a/public/js/components/Cis/Mylv/LvUebersicht.js +++ b/public/js/components/Cis/Mylv/LvUebersicht.js @@ -1,5 +1,5 @@ -import BsModal from "../../Bootstrap/Modal"; -import LvMenu from "./LvMenu"; +import BsModal from "../../Bootstrap/Modal.js"; +import LvMenu from "./LvMenu.js"; export default { props:{ diff --git a/public/js/components/Cis/Stundenplan/Stundenplan.js b/public/js/components/Cis/Stundenplan/Stundenplan.js index d35c9c536..ba3a3c649 100644 --- a/public/js/components/Cis/Stundenplan/Stundenplan.js +++ b/public/js/components/Cis/Stundenplan/Stundenplan.js @@ -150,6 +150,11 @@ export const Stundenplan = { }, created() { + let time_start = Math.floor(this.eventCalendarDate.firstDayOfCalendarMonth.getTime() / 1000); + let time_end = Math.floor(this.eventCalendarDate.lastDayOfCalendarMonth.getTime() / 1000); + this.$fhcApi.factory.stundenplan.getMoodleEventsByUserid('io23m005', time_start, time_end).then((response) => { + console.log(response); + }) this.$fhcApi.factory.authinfo.getAuthUID().then((res) => res.data) .then(data=>{ this.uid = data.uid; From 9fb2ea4ae31b16a30120212924111581c2d4c4d0 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Mon, 17 Feb 2025 19:03:53 +0100 Subject: [PATCH 0055/1216] Projektarbeitsbeurteilung: added authorization (Berechtigungen) check to document export --- .../projektbeurteilungDocumentExport.php | 23 +-------- cis/private/pdfExport.php | 50 ++++++++----------- include/projektarbeit.class.php | 19 ++++++- 3 files changed, 41 insertions(+), 51 deletions(-) diff --git a/cis/private/lehre/projektbeurteilungDocumentExport.php b/cis/private/lehre/projektbeurteilungDocumentExport.php index 195650a24..08f7127ee 100644 --- a/cis/private/lehre/projektbeurteilungDocumentExport.php +++ b/cis/private/lehre/projektbeurteilungDocumentExport.php @@ -6,40 +6,21 @@ * daraus ein PDF */ require_once('../../../config/cis.config.inc.php'); -require_once('../../../include/functions.inc.php'); require_once('../../../include/projektarbeit.class.php'); -require_once('../../../include/person.class.php'); if (!isset($_GET['betreuerart_kurzbz']) || !isset($_GET['person_id']) || !isset($_GET['projektarbeit_id'])) die('Fehlerhafte Parameteruebergabe'); -$user = get_uid(); - -$rechte = new benutzerberechtigung(); -$rechte->getBerechtigungen($user); - -$projektarbeit = new projektarbeit(); -$projektarbeit->load($_GET['projektarbeit_id']); - -$betreuer = new person(); -$betreuer->getPersonFromBenutzer($user); - -//Überprüft ob es der Betreuer oder der Student ist -if ($betreuer->person_id !== $_GET['person_id'] && $projektarbeit->student_uid !== $user && !$rechte->isBerechtigt('assistenz')) - die("

Sie haben keine Berechtigung für diese Aktion.

"); - -$projektarbeitVorlage = new projektarbeit(); - // passende Vorlage holen +$projektarbeitVorlage = new projektarbeit(); $vorlage = $projektarbeitVorlage->getVorlage($_GET['projektarbeit_id'], $_GET['betreuerart_kurzbz']); - if ($vorlage == null) die("

".$projektarbeitVorlage->errormsg."

"); // weiterleiten auf Dokumentexport header('Location: ' . APP_ROOT . '/cis/private/pdfExport.php?xml=projektarbeitsbeurteilung.xml.php' .'&xsl='.$vorlage.'&betreuerart_kurzbz=' . $_GET['betreuerart_kurzbz'] - . '&projektarbeit_id=' . $_GET['projektarbeit_id'] . '&person_id=' . $_GET['person_id']. '&uid=' . $user + . '&projektarbeit_id=' . $_GET['projektarbeit_id'] . '&person_id=' . $_GET['person_id'] ); die(); diff --git a/cis/private/pdfExport.php b/cis/private/pdfExport.php index 7393a084c..d4638d1bd 100644 --- a/cis/private/pdfExport.php +++ b/cis/private/pdfExport.php @@ -196,41 +196,35 @@ if (isset($_GET['output']) && $_GET['output'] != 'pdf') else $output = 'pdf'; -//~ if (isset($_GET['xsl']) && ($_GET['xsl'] === 'Projektbeurteilung')) -//~ { - //~ if (!isset($_GET['betreuerart_kurzbz']) || !isset($_GET['person_id']) || !isset($_GET['projektarbeit_id'])) - //~ die('Fehlerhafte Parameteruebergabe'); +// Berechtigungprüfung Projektarbeit +if (isset($_GET['projektarbeit_id'])) +{ + $projektarbeitVorlage = new projektarbeit(); + $allePaVorlagen = $projektarbeitVorlage->getAllVorlagen(); - //~ $projektarbeit = new projektarbeit(); - //~ $projektarbeit->load($_GET['projektarbeit_id']); + if (!is_array($allePaVorlagen)) + die("

Fehler beim Holen der Projektarbeit Vorlagen

"); - //~ $betreuer = new person(); - //~ $betreuer->getPersonFromBenutzer($user); + if (in_array($xsl, $allePaVorlagen)) + { + $rechte = new benutzerberechtigung(); + $rechte->getBerechtigungen($user); - //~ //Überprüft ob es der Betreuer oder der Student ist - //~ if ($betreuer->person_id !== $_GET['person_id'] && $projektarbeit->student_uid !== $user && !$rechte->isBerechtigt('assistenz')) - //~ die("

Sie haben keine Berechtigung für diese Aktion.

"); + $projektarbeit = new projektarbeit(); + $projektarbeit->load($_GET['projektarbeit_id']); - //~ switch ($_GET['betreuerart_kurzbz']) - //~ { - //~ case 'Begutachter' : - //~ case 'Senatsvorsitz' : - //~ $xsl = 'ProjektBeurteilungBA'; - //~ break; - //~ case 'Erstbegutachter' : - //~ $xsl = 'ProjektBeurteilungMAErst'; - //~ break; - //~ case 'Zweitbegutachter' : - //~ $xsl = 'ProjektBeurteilungMAZweit'; - //~ break; - //~ } - - //~ $allowed = true; -//~ } + $betreuer = new person(); + $betreuer->getPersonFromBenutzer($user); + //Überprüft ob es der Betreuer oder der Student ist + if ($betreuer->person_id !== $_GET['person_id'] && $projektarbeit->student_uid !== $user && !$rechte->isBerechtigt('assistenz')) + die("

Sie haben keine Berechtigung für diese Aktion.

"); + $paBerechtigt = true; + } +} $konto = new konto(); -if (((isset($_GET["uid"]) && $user == $_GET["uid"])) || $rechte->isBerechtigt('admin')) +if (((isset($_GET["uid"]) && $user == $_GET["uid"])) || $rechte->isBerechtigt('admin') || (isset($paBerechtigt) && $paBerechtigt === true)) { $buchungstypen = array(); if (defined("CIS_DOKUMENTE_STUDIENBEITRAG_TYPEN")) diff --git a/include/projektarbeit.class.php b/include/projektarbeit.class.php index 5f07cf69d..c191550b7 100644 --- a/include/projektarbeit.class.php +++ b/include/projektarbeit.class.php @@ -59,7 +59,7 @@ class projektarbeit extends basis_db public $abgabedatum; - // Welche Version der Projektarbeit wird in welchem Semester verwendet + // Welche Version der Projektarbeit wird ab welchem Semester verwendet private $_versions = array( 'Diplom' => array( 'SS2025' => 3, @@ -72,7 +72,7 @@ class projektarbeit extends basis_db ) ); - // welche Vorlagen werden für welche Projekarbeitsversion verwendet (beginnend mit 0) + // welche Vorlagen werden ab welcher Projekarbeitsversion verwendet (0 - erste "default" Vorlage) private $_projektarbeitVorlageMappings = array( 'Begutachter' => array( 2 => 'ProjektBeurteilungBAProzent', @@ -626,5 +626,20 @@ class projektarbeit extends basis_db ? $this->_projektarbeitVorlageMappings[$betreuerart_kurzbz][$key] : ''; } + + /** + * Holt alle möglichen, jemals verwendeten Projektarbeits-Vorlagen + * @return array mit Vorlagennamen + */ + public function getAllVorlagen() + { + $vorlagen = array(); + foreach ($this->_projektarbeitVorlageMappings as $mappings) + { + $vorlagen = array_unique(array_merge($vorlagen, $mappings)); + } + + return $vorlagen; + } } ?> From e086da2274ce66bc477d8ba5b6c8308b38eba720 Mon Sep 17 00:00:00 2001 From: SimonGschnell Date: Tue, 18 Feb 2025 14:23:56 +0100 Subject: [PATCH 0056/1216] feature(Calendar Moodle Events): displays moodle events as allDayEvents in the Calendar --- public/js/components/Calendar/Week/Page.js | 75 ++++++++++++++----- public/js/components/Cis/Mylv/LvModal.js | 4 +- .../components/Cis/Stundenplan/Stundenplan.js | 74 +++++++++++++----- 3 files changed, 116 insertions(+), 37 deletions(-) diff --git a/public/js/components/Calendar/Week/Page.js b/public/js/components/Calendar/Week/Page.js index b029b854a..504293d07 100644 --- a/public/js/components/Calendar/Week/Page.js +++ b/public/js/components/Calendar/Week/Page.js @@ -10,7 +10,7 @@ export default { hourPosition:null, hourPositionTime:null, resizeObserver: null, - width: 0 + width: 0, } }, inject: [ @@ -36,6 +36,16 @@ export default { 'input', ], computed: { + allDayEvents(){ + let allDayEvents = {}; + for(let day in this.events){ + const filteredAllDayEvents = this.events[day].filter(event=>event.allDayEvent); + if (filteredAllDayEvents.length > 0){ + allDayEvents[day]=filteredAllDayEvents; + } + }; + return allDayEvents; + }, getGridStyle() { return { 'min-height': '100px', @@ -126,6 +136,7 @@ export default { d.isToday = nextDay.getFullYear() === this.todayDate.getFullYear() && nextDay.getMonth() === this.todayDate.getMonth() && nextDay.getDate() === this.todayDate.getDate() if (this.events[key]) { this.events[key].forEach(evt => { + if (evt.allDayEvent) return; let event = {orig:evt,lane:1,maxLane:1,start: evt.start < day ? day : evt.start, end: evt.end > nextDay ? nextDay : evt.end,shared:[],setSharedMaxRecursive(doneItems) { this.maxLane = Math.max(doneItems[0].maxLane, this.maxLane); doneItems.push(this); @@ -197,31 +208,44 @@ export default { 'grid-template-columns': 'repeat(' + day.lanes + ', 1fr)', 'grid-template-rows': 'repeat(' + (this.hours.length * 60 / this.smallestTimeFrame) + ', 1fr)', } - console.log(this.smallestTimeFrame,"this is the smallest timeframe") - console.log(this.hours.length,"this is the length of the hours") - console.log(this.hours.length/60, "this is the length of the hours multiplied by 60 resulting in minutes") if(day.isPast) { styleObj['background-color'] = '#F5E9D7' styleObj['border-color'] = '#E8E8E8'; styleObj.opacity = 0.5; } else if (day.isToday) { - // styleObj['backgroundImage'] = 'linear-gradient(to bottom, #F5E9D7 '+this.getDayTimePercent+'%, #FFFFFF '+this.getDayTimePercent+'%)' - // styleObj['border-color'] = '#E8E8E8'; - // styleObj.opacity = 0.5; + styleObj['backgroundImage'] = 'linear-gradient(to bottom, #F5E9D7 '+this.getDayTimePercent+'%, #FFFFFF '+this.getDayTimePercent+'%)' + styleObj['border-color'] = '#E8E8E8'; + styleObj.opacity = 0.5; } return styleObj }, eventGridStyle(day, event) { - return { - 'z-index': 1, - 'grid-column-start': 1 + (event.lane - 1) * day.lanes / event.maxLane, - 'grid-column-end': 1 + event.lane * day.lanes / event.maxLane, - 'grid-row-start': this.dateToMinutesOfDay(event.start), - 'grid-row-end': this.dateToMinutesOfDay(event.end), - 'background-color': event.orig.color, - 'max-height': '75px' + if (event.orig.allDayEvent) + { + return; + return { + 'z-index': '2', + 'grid-column': '1 / -1', + 'background-color': 'rgb(204, 204, 204)', + 'max-height': '75px', + color: 'black', + position: 'sticky', + top: '44px', + }; + } + else + { + return { + 'z-index': 1, + 'grid-column-start': 1 + (event.lane - 1) * day.lanes / event.maxLane, + 'grid-column-end': 1 + event.lane * day.lanes / event.maxLane, + 'grid-row-start': this.dateToMinutesOfDay(event.start), + 'grid-row-end': this.dateToMinutesOfDay(event.end), + 'background-color': event.orig.color, + 'max-height': '75px' + }; } }, calcHourPosition(event) { @@ -333,12 +357,27 @@ export default { {{hourPositionTime}} - +
+
{{hour}}:00
-
+
+
+
+
+ +

this is a placeholder which means that no template was passed to the Calendar Page slot

+
+
+
+
{{curTime}} @@ -350,7 +389,7 @@ export default { :style="eventGridStyle(day,event)" class="mx-2 small rounded overflow-hidden fhc-entry " v-contrast > - +

this is a placeholder which means that no template was passed to the Calendar Page slot

diff --git a/public/js/components/Cis/Mylv/LvModal.js b/public/js/components/Cis/Mylv/LvModal.js index e873dd5f8..acb2a7693 100644 --- a/public/js/components/Cis/Mylv/LvModal.js +++ b/public/js/components/Cis/Mylv/LvModal.js @@ -35,7 +35,7 @@ export default { }, data() { return { - menu: [], + menu: null, result: false, info: null, }; @@ -76,7 +76,7 @@ export default {
".$p->t('abgabetool/student').": ".$db->convert_html_chars($studentenname).""; -$semester_benotbar = $paIsCurrent >= 1; +$semester_benotbar = $paIsCurrent === true; $endupload_vorhanden = $num_rows_endupload >= 1; if ($semester_benotbar && $endupload_vorhanden) @@ -544,7 +544,7 @@ if (isset($zweitbetreuerArr) && is_array($zweitbetreuerArr)) // wenn es Zweitbet $htmlstr .= "  " . $p->t("; // Token senden button wenn Zweitbegutachter extern ist und Projektarbeit nicht für altes Semester ist - if (isset($zweitbetreuer->email) && !isset($zweitbetreuer->uid) && $paIsCurrent >= 1) + if (isset($zweitbetreuer->email) && !isset($zweitbetreuer->uid) && $paIsCurrent === true) { $htmlstr .= "
\n"; $htmlstr .= ""; diff --git a/cis/private/lehre/abgabe_student.php b/cis/private/lehre/abgabe_student.php index 2512d9831..0a0385970 100644 --- a/cis/private/lehre/abgabe_student.php +++ b/cis/private/lehre/abgabe_student.php @@ -195,13 +195,13 @@ else $htmlstr .= "
"; if (!is_null($row->babgeschickt)) - $htmlstr .= "".$p->t('abgabetool/projektbeurteilungErstDownload').""; + $htmlstr .= "".$p->t('abgabetool/projektbeurteilungErstDownload').""; if (!is_null($row->babgeschickt) && !is_null($row->zweitbetreuer_abgeschickt)) $htmlstr .= "/"; if (!is_null($row->zweitbetreuer_abgeschickt)) - $htmlstr .= "".$p->t('abgabetool/projektbeurteilungZweitDownload').""; + $htmlstr .= "".$p->t('abgabetool/projektbeurteilungZweitDownload').""; $htmlstr .= "