From 6e9ee7cf814f7d8cba1e95b481abc6dbf7c9af89 Mon Sep 17 00:00:00 2001 From: ma0068 Date: Fri, 3 May 2024 09:43:49 +0200 Subject: [PATCH 0001/1357] Notizcomponent: Api Controller for Notizzuordnungs_ids --- .../api/frontend/v1/notiz/Notiz.php | 384 ------------------ .../api/frontend/v1/notiz/NotizAnrechnung.php | 44 ++ .../api/frontend/v1/notiz/NotizBestellung.php | 43 ++ .../frontend/v1/notiz/NotizLehreinheit.php | 44 ++ .../frontend/v1/notiz/NotizMitarbeiter.php | 44 ++ .../api/frontend/v1/notiz/NotizPerson.php | 33 ++ .../api/frontend/v1/notiz/NotizPrestudent.php | 44 ++ .../api/frontend/v1/notiz/NotizProjekt.php | 32 ++ .../frontend/v1/notiz/NotizProjektphase.php | 32 ++ .../frontend/v1/notiz/NotizProjekttask.php | 32 ++ application/core/Notiz_Controller.php | 79 ++-- public/js/api/notiz.js | 22 +- public/js/api/notiz/anrechnung.js | 38 ++ public/js/api/notiz/bestellung.js | 38 ++ public/js/api/notiz/lehreinheit.js | 38 ++ public/js/api/notiz/mitarbeiter.js | 38 ++ public/js/api/notiz/person.js | 19 +- public/js/api/notiz/prestudent.js | 38 ++ public/js/api/notiz/projekt.js | 38 ++ public/js/api/notiz/projektphase.js | 38 ++ public/js/api/notiz/projekttask.js | 38 ++ .../Studentenverwaltung/Details/Notizen.js | 72 ++-- system/phrasesupdate.php | 22 + 23 files changed, 786 insertions(+), 464 deletions(-) delete mode 100644 application/controllers/api/frontend/v1/notiz/Notiz.php create mode 100644 application/controllers/api/frontend/v1/notiz/NotizAnrechnung.php create mode 100644 application/controllers/api/frontend/v1/notiz/NotizBestellung.php create mode 100644 application/controllers/api/frontend/v1/notiz/NotizLehreinheit.php create mode 100644 application/controllers/api/frontend/v1/notiz/NotizMitarbeiter.php create mode 100644 application/controllers/api/frontend/v1/notiz/NotizPerson.php create mode 100644 application/controllers/api/frontend/v1/notiz/NotizPrestudent.php create mode 100644 application/controllers/api/frontend/v1/notiz/NotizProjekt.php create mode 100644 application/controllers/api/frontend/v1/notiz/NotizProjektphase.php create mode 100644 application/controllers/api/frontend/v1/notiz/NotizProjekttask.php create mode 100644 public/js/api/notiz/anrechnung.js create mode 100644 public/js/api/notiz/bestellung.js create mode 100644 public/js/api/notiz/lehreinheit.js create mode 100644 public/js/api/notiz/mitarbeiter.js create mode 100644 public/js/api/notiz/prestudent.js create mode 100644 public/js/api/notiz/projekt.js create mode 100644 public/js/api/notiz/projektphase.js create mode 100644 public/js/api/notiz/projekttask.js diff --git a/application/controllers/api/frontend/v1/notiz/Notiz.php b/application/controllers/api/frontend/v1/notiz/Notiz.php deleted file mode 100644 index 2288766c4..000000000 --- a/application/controllers/api/frontend/v1/notiz/Notiz.php +++ /dev/null @@ -1,384 +0,0 @@ - ['admin:r', 'assistenz:r'], - 'getNotizen' => ['admin:r', 'assistenz:r'], - 'loadNotiz' => 'assistenz:r', // TODO(manu): self::PERM_LOGGED - 'addNewNotiz' => 'assistenz:r', // TODO(manu): self::PERM_LOGGED - 'updateNotiz' => 'assistenz:r', // TODO(manu): self::PERM_LOGGED - 'deleteNotiz' => ['admin:r', 'assistenz:r'], - 'loadDokumente' => ['admin:r', 'assistenz:r'], - 'getMitarbeiter' => ['admin:r', 'assistenz:r'] - ]); - - //Load Models - $this->load->model('person/Notiz_model', 'NotizModel'); - $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); - - // Load Libraries - $this->load->library('VariableLib', ['uid' => getAuthUID()]); - - // Load language phrases - $this->loadPhrases([ - 'ui' - ]); - } - - /* public function getUid() - { - $this->terminateWithSuccess(getAuthUID()); - }*/ - - - public function getNotizen($id, $type) - { - - //check if valid type - $result = $this->NotizzuordnungModel->isValidType($type); - if(isError($result)) - $this->terminateWithError($result->retval, self::ERROR_TYPE_GENERAL); - - //$this->terminateWithError(" after check type not valid", self::ERROR_TYPE_GENERAL); - $result = $this->NotizModel->getNotizWithDocEntries($id, $type); - - if (isError($result)) { - $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - return $this->terminateWithSuccess(getData($result) ?: []); - - // return $this->terminateWithError("type not valid", self::ERROR_TYPE_GENERAL); - - } - - /* public function loadNotiz() - { - $_POST = json_decode(utf8_encode($this->input->raw_input_stream), true); - - $notiz_id = $this->input->post('notiz_id'); - - //$this->load->model('person/Notiz_model', 'NotizModel'); - $this->NotizModel->addJoin('public.tbl_notiz_dokument', 'notiz_id', 'LEFT'); - $this->NotizModel->addSelect('*'); - $this->NotizModel->addSelect("TO_CHAR(CASE WHEN public.tbl_notiz.updateamum >= public.tbl_notiz.insertamum - THEN public.tbl_notiz.updateamum ELSE public.tbl_notiz.insertamum END::timestamp, 'DD.MM.YYYY HH24:MI:SS') AS lastUpdate"); - $this->NotizModel->addLimit(1); - - $result = $this->NotizModel->loadWhere( - array('notiz_id' => $notiz_id) - ); - if (isError($result)) - { - $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); - } - elseif (!hasData($result)) - { - $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=>'Notiz_id']), self::ERROR_TYPE_GENERAL); - } - else - { - $this->terminateWithSuccess(current(getData($result))); - } - } - - - public function updateNotiz() - { - $this->load->library('form_validation'); - $this->load->library('DmsLib'); - - if (isset($_POST['data'])) - { - $data = json_decode($_POST['data']); - unset($_POST['data']); - foreach ($data as $k => $v) { - $_POST[$k] = $v; - } - } - - $notiz_id = $this->input->post('notiz_id'); - - if(!$notiz_id) - { - $this->terminateWithError($this->p->t('ui','error_missingId',['id'=>'Notiz_id']), self::ERROR_TYPE_GENERAL); - } - - //Form Validation - $this->form_validation->set_rules('titel', 'Titel', 'required', [ - 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Titel']) - ]); - - $this->form_validation->set_rules('text', 'Text', 'required', [ - 'required' => $this->p->t('ui', 'error_fieldRequired', ['field' => 'Text']) - ]); - - if ($this->form_validation->run() == false) - { - $this->terminateWithValidationErrors($this->form_validation->error_array()); - } - - //update Notiz - $uid = getAuthUID(); - $titel = $this->input->post('titel'); - $text = $this->input->post('text'); - $verfasser_uid = $this->input->post('verfasser'); - $bearbeiter_uid = isset($_POST['bearbeiter']) ? $_POST['bearbeiter'] : $uid; - $erledigt = $this->input->post('erledigt'); - $start = $this->input->post('start'); - $ende = $this->input->post('ende'); - - $result = $this->NotizModel->update( - [ - 'notiz_id' => $notiz_id - ], - [ - 'titel' => $titel, - 'updatevon' => $uid, - 'updateamum' => date('c'), - 'text' => $text, - 'verfasser_uid' => $verfasser_uid, - 'bearbeiter_uid' => $bearbeiter_uid, - 'start' => $start, - 'ende' => $ende, - 'erledigt' => $erledigt - ] - ); - if (isError($result)) - { - return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - - //update(1) laden aller bereits mit dieser notiz_id verknüpften DMS-Einträge - $this->load->model('person/Notizdokument_model', 'NotizdokumentModel'); - $this->NotizdokumentModel->addJoin('campus.tbl_dms_version', 'dms_id'); - $dms_uploaded = null; - - $result = $this->NotizdokumentModel->loadWhere(array('notiz_id' => $notiz_id)); - if (isError($result)) - { - $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - elseif (!hasData($result)) - { - $dms_id_arr = null; - } - else - { - $result = getData($result); - foreach($result as $doc) { - $dms_id_arr[] = array( - 'name' => $doc->name, - 'dms_id' => $doc->dms_id - ); - } - } - - foreach ($_FILES as $k => $file) - { - //update(2) alle neuen files (alle außer type application/x.fhc-dms+json) anhängen - if($file["type"] == 'application/x.fhc-dms+json') - { - $dms_uploaded[] = array( - 'name' => $file["name"] - ); - } - else - { - $dms = array( - 'kategorie_kurzbz' => 'notiz', - 'version' => 0, - 'name' => $file["name"], - 'mimetype' => $file["type"], - 'insertamum' => date('c'), - 'insertvon' => $uid - ); - - //Todo(manu) check if filetypes weiter eingeschränkt werden sollen - //Todo(manu)check name files: nicht gleiches file 2mal hochladen - $result = $this->dmslib->upload($dms, $k, array('*')); - - if (isError($result)) - { - return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - $dms_id = $result->retval['dms_id']; - - $result = $this->NotizdokumentModel->insert(array('notiz_id' => $notiz_id, 'dms_id' => $dms_id)); - if (isError($result)) - { - return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - } - } - - //update(3) check if Dateien gelöscht wurden - if(count($dms_uploaded) != count($dms_id_arr)) - { - if (count($dms_uploaded) == 0) - { - $filesDeleted = $dms_id_arr; - } - else - { - $upload_new_names = array_column($dms_uploaded, "name"); - - $filesDeleted = array_filter($dms_id_arr, function ($file) use ($upload_new_names) { - return !in_array($file["name"], $upload_new_names); - }); - } - - foreach ($filesDeleted as $file) - { - $result = $this->dmslib->removeAll($file['dms_id']); - - if (isError($result)) - { - return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - else - $this->outputJson($result); - } - } - return $this->terminateWithSuccess($result); - }*/ - - - /* public function deleteNotiz() - { - $_POST = json_decode(utf8_encode($this->input->raw_input_stream), true); - $notiz_id = $this->input->post('notiz_id'); - $type = $this->input->post('type_id'); - $id = $this->input->post('id'); - - //dms_id auslesen aus notizdokument wenn vorhanden - $dms_id_arr = []; - $this->load->model('person/Notizdokument_model', 'NotizdokumentModel'); - - $result = $this->NotizdokumentModel->loadWhere(array('notiz_id' => $notiz_id)); - - if (isError($result)) - { - return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - - if(hasData($result)) - { - $result = getData($result); - foreach ($result as $doc) { - $dms_id_arr[] = $doc->dms_id; - } - } - - if($dms_id_arr) - { - $this->load->library('DmsLib'); - foreach($dms_id_arr as $dms_id) - { - $result = $this->dmslib->removeAll($dms_id); - - if (isError($result)) - { - return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); - } - - $this->outputJson($result); - } - } - - //delete Notizzuordnung - if($type == "software_id") - { - // Loads extension Model - $this->load->model('extensions/FHC-Core-Softwarebereitstellung/Softwarenotizzuordnung_model', 'ExtensionnotizzuordnungModel'); - $result = $this->ExtensionnotizzuordnungModel->delete([ - 'notiz_id' => $notiz_id, - 'id' => strval($id) - ], - [ - 'type_id' => $type - ]); - - } - else - { - //notizzuordnungsid! - $result = $this->NotizzuordnungModel->delete(['notiz_id' => $notiz_id, $type => $id]); - } - - - $this->load->model('person/Notiz_model', 'NotizModel'); - //$this->NotizModel->addJoin('public.tbl_notizzuordnung', 'notiz_id'); - - //TODO (erweitern um Type_id) für Extensions, damit auch Notizzuordnung gelöscht werden kann - - //Löschen von Notiz - $result = $this->NotizModel->delete( - array('notiz_id' => $notiz_id) - ); - - if (isError($result)) - { - return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); - } - if(!hasData($result)) - { - return $this->terminateWithError($this->p->t('ui','error_missingId', ['id'=> 'Notiz_id']), self::ERROR_TYPE_GENERAL); - } - - return $this->terminateWithSuccess(current(getData($result))); - }*/ - - /* public function loadDokumente() - { - $_POST = json_decode(utf8_encode($this->input->raw_input_stream), true); - $notiz_id = $this->input->post('notiz_id'); - - $this->NotizModel->addSelect('campus.tbl_dms_version.*'); - - $this->NotizModel->addJoin('public.tbl_notiz_dokument', 'ON (public.tbl_notiz_dokument.notiz_id = public.tbl_notiz.notiz_id)'); - $this->NotizModel->addJoin('campus.tbl_dms_version', 'ON (public.tbl_notiz_dokument.dms_id = campus.tbl_dms_version.dms_id)'); - - $result = $this->NotizModel->loadWhere( - array('public.tbl_notiz.notiz_id' => $notiz_id) - ); - if (isError($result)) { - return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); - } - - if(!hasData($result)) - { - return $this->terminateWithError($this->p->t('ui','error_missingId', ['id'=> 'Notiz_id']), self::ERROR_TYPE_GENERAL); - } - return $this->terminateWithSuccess(getData($result)); - }*/ - - /* public function getMitarbeiter($searchString) - { - $this->load->model('ressource/Mitarbeiter_model', 'MitarbeiterModel'); - $result = $this->MitarbeiterModel->searchMitarbeiter($searchString); - if (isError($result)) { - $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); - } - return $this->terminateWithSuccess($result); - }*/ - - public function isBerechtigt($id, $typeId) - { - if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) - { - $result = $this->p->t('lehre','error_keineSchreibrechte'); - - return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); - } - return success("berechtigt in überschreibender Funktion"); - /* return $this->terminateWithError('keine Berechtigung bro', self::ERROR_TYPE_GENERAL);*/ - } - -} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/notiz/NotizAnrechnung.php b/application/controllers/api/frontend/v1/notiz/NotizAnrechnung.php new file mode 100644 index 000000000..b58020507 --- /dev/null +++ b/application/controllers/api/frontend/v1/notiz/NotizAnrechnung.php @@ -0,0 +1,44 @@ + ['admin:r', 'assistenz:r'], + ]); + + //Load Models + $this->load->model('person/Notiz_model', 'NotizModel'); + $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + + // Load language phrases + $this->loadPhrases([ + 'ui' + ]); + } + + public function isBerechtigt($id, $typeId) + { + if($typeId != "anrechnung_id") + { + return $this->terminateWithError($this->p->t('ui','error_typeNotizIdIncorrect'), self::ERROR_TYPE_GENERAL); + } + + //TODO define permission + if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) + { + $result = $this->p->t('lehre','error_keineSchreibrechte'); + + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + return $this->terminateWithSuccess("berechtigt in überschreibender Funktion"); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/notiz/NotizBestellung.php b/application/controllers/api/frontend/v1/notiz/NotizBestellung.php new file mode 100644 index 000000000..483efd619 --- /dev/null +++ b/application/controllers/api/frontend/v1/notiz/NotizBestellung.php @@ -0,0 +1,43 @@ + ['admin:r', 'assistenz:r'], + ]); + + //Load Models + $this->load->model('person/Notiz_model', 'NotizModel'); + $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + + // Load language phrases + $this->loadPhrases([ + 'ui' + ]); + } + + public function isBerechtigt($id, $typeId) + { + if($typeId != "bestellung_id") + { + return $this->terminateWithError($this->p->t('ui','error_typeNotizIdIncorrect'), self::ERROR_TYPE_GENERAL); + } + //TODO define permission + if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) + { + $result = $this->p->t('lehre','error_keineSchreibrechte'); + + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + return $this->terminateWithSuccess("berechtigt in überschreibender Funktion"); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/notiz/NotizLehreinheit.php b/application/controllers/api/frontend/v1/notiz/NotizLehreinheit.php new file mode 100644 index 000000000..5c40af368 --- /dev/null +++ b/application/controllers/api/frontend/v1/notiz/NotizLehreinheit.php @@ -0,0 +1,44 @@ + ['admin:r', 'assistenz:r'], + ]); + + //Load Models + $this->load->model('person/Notiz_model', 'NotizModel'); + $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + + // Load language phrases + $this->loadPhrases([ + 'ui' + ]); + } + + public function isBerechtigt($id, $typeId) + { + if($typeId != "lehreinheit_id") + { + return $this->terminateWithError($this->p->t('ui','error_typeNotizIdIncorrect'), self::ERROR_TYPE_GENERAL); + } + + //TODO define permission + if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) + { + $result = $this->p->t('lehre','error_keineSchreibrechte'); + + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + return $this->terminateWithSuccess("berechtigt in überschreibender Funktion"); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/notiz/NotizMitarbeiter.php b/application/controllers/api/frontend/v1/notiz/NotizMitarbeiter.php new file mode 100644 index 000000000..fd8ee3bbb --- /dev/null +++ b/application/controllers/api/frontend/v1/notiz/NotizMitarbeiter.php @@ -0,0 +1,44 @@ + ['admin:r', 'assistenz:r'], + ]); + + //Load Models + $this->load->model('person/Notiz_model', 'NotizModel'); + $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + + // Load language phrases + $this->loadPhrases([ + 'ui' + ]); + } + + public function isBerechtigt($id, $typeId) + { + if($typeId != "mitarbeiter_uid") + { + return $this->terminateWithError($this->p->t('ui','error_typeNotizIdIncorrect'), self::ERROR_TYPE_GENERAL); + } + + //TODO define permission + if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) + { + $result = $this->p->t('lehre','error_keineSchreibrechte'); + + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + return $this->terminateWithSuccess("berechtigt in überschreibender Funktion"); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/notiz/NotizPerson.php b/application/controllers/api/frontend/v1/notiz/NotizPerson.php new file mode 100644 index 000000000..2cf1f2519 --- /dev/null +++ b/application/controllers/api/frontend/v1/notiz/NotizPerson.php @@ -0,0 +1,33 @@ + ['admin:r', 'assistenz:r'], + ]); + } + + public function isBerechtigt($id, $typeId) + { + if($typeId != "person_id") + { + return $this->terminateWithError($this->p->t('ui','error_typeNotizIdIncorrect'), self::ERROR_TYPE_GENERAL); + } + + //TODO define permission + if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) + { + $result = $this->p->t('lehre','error_keineSchreibrechte'); + + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + + return $this->outputJsonSuccess(true); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/notiz/NotizPrestudent.php b/application/controllers/api/frontend/v1/notiz/NotizPrestudent.php new file mode 100644 index 000000000..d4f50e69c --- /dev/null +++ b/application/controllers/api/frontend/v1/notiz/NotizPrestudent.php @@ -0,0 +1,44 @@ + ['admin:r', 'assistenz:r'], + ]); + + //Load Models + $this->load->model('person/Notiz_model', 'NotizModel'); + $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); + + // Load Libraries + $this->load->library('VariableLib', ['uid' => getAuthUID()]); + + // Load language phrases + $this->loadPhrases([ + 'ui' + ]); + } + + public function isBerechtigt($id, $typeId) + { + if($typeId != "prestudent_id") + { + return $this->terminateWithError($this->p->t('ui','error_typeNotizIdIncorrect'), self::ERROR_TYPE_GENERAL); + } + + //TODO define permission + if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) + { + $result = $this->p->t('lehre','error_keineSchreibrechte'); + + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + return $this->terminateWithSuccess("berechtigt in überschreibender Funktion"); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/notiz/NotizProjekt.php b/application/controllers/api/frontend/v1/notiz/NotizProjekt.php new file mode 100644 index 000000000..180f58b1b --- /dev/null +++ b/application/controllers/api/frontend/v1/notiz/NotizProjekt.php @@ -0,0 +1,32 @@ + ['admin:r', 'assistenz:r'], + ]); + } + + public function isBerechtigt($id, $typeId) + { + if($typeId != "projekt_kurzbz") + { + return $this->terminateWithError($this->p->t('ui','error_typeNotizIdIncorrect'), self::ERROR_TYPE_GENERAL); + } + + //TODO define permission + if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) + { + $result = $this->p->t('lehre','error_keineSchreibrechte'); + + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + return $this->terminateWithSuccess("berechtigt in überschreibender Funktion"); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/notiz/NotizProjektphase.php b/application/controllers/api/frontend/v1/notiz/NotizProjektphase.php new file mode 100644 index 000000000..071aae6a5 --- /dev/null +++ b/application/controllers/api/frontend/v1/notiz/NotizProjektphase.php @@ -0,0 +1,32 @@ + ['admin:r', 'assistenz:r'], + ]); + } + + public function isBerechtigt($id, $typeId) + { + if($typeId != "projektphase_id") + { + return $this->terminateWithError($this->p->t('ui','error_typeNotizIdIncorrect'), self::ERROR_TYPE_GENERAL); + } + + //TODO define permission + if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) + { + $result = $this->p->t('lehre','error_keineSchreibrechte'); + + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + return $this->terminateWithSuccess("berechtigt in überschreibender Funktion"); + } +} \ No newline at end of file diff --git a/application/controllers/api/frontend/v1/notiz/NotizProjekttask.php b/application/controllers/api/frontend/v1/notiz/NotizProjekttask.php new file mode 100644 index 000000000..4d407051a --- /dev/null +++ b/application/controllers/api/frontend/v1/notiz/NotizProjekttask.php @@ -0,0 +1,32 @@ + ['admin:r', 'assistenz:r'], + ]); + } + + public function isBerechtigt($id, $typeId) + { + if($typeId != "projekttask_id") + { + return $this->terminateWithError($this->p->t('ui','error_typeNotizIdIncorrect'), self::ERROR_TYPE_GENERAL); + } + + //TODO define permission + if(!$this->permissionlib->isBerechtigt('admin', 'suid') && !$this->permissionlib->isBerechtigt('assistenz', 'suid')) + { + $result = $this->p->t('lehre','error_keineSchreibrechte'); + + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + return $this->terminateWithSuccess("berechtigt in überschreibender Funktion"); + } +} \ No newline at end of file diff --git a/application/core/Notiz_Controller.php b/application/core/Notiz_Controller.php index 51d6854b7..8a93c4f0f 100644 --- a/application/core/Notiz_Controller.php +++ b/application/core/Notiz_Controller.php @@ -10,19 +10,20 @@ abstract class Notiz_Controller extends FHCAPI_Controller public function __construct() { parent::__construct([ - 'getUid' => ['admin:r', 'assistenz:r'], - 'getNotizen' => ['admin:r', 'assistenz:r'], - 'loadNotiz' => 'assistenz:r', // TODO(manu): self::PERM_LOGGED - 'addNewNotiz' => 'assistenz:r', // TODO(manu): self::PERM_LOGGED - 'updateNotiz' => 'assistenz:r', // TODO(manu): self::PERM_LOGGED - 'deleteNotiz' => ['admin:r', 'assistenz:r'], - 'loadDokumente' => ['admin:r', 'assistenz:r'], - 'getMitarbeiter' => ['admin:r', 'assistenz:r'], - 'isBerechtigt' => ['admin:r', 'assistenz:r'] + 'getUid' => self::PERM_LOGGED, + 'getNotizen' => self::PERM_LOGGED, + 'loadNotiz' => self::PERM_LOGGED, + 'addNewNotiz' => self::PERM_LOGGED, + 'updateNotiz' => self::PERM_LOGGED, + 'deleteNotiz' => ['admin:w', 'assistenz:w'], + 'loadDokumente' => ['admin:w', 'assistenz:w'], + 'getMitarbeiter' => self::PERM_LOGGED, + 'isBerechtigt' => self::PERM_LOGGED, ]); //Load Models $this->load->model('person/Notiz_model', 'NotizModel'); + $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); // Load Libraries $this->load->library('VariableLib', ['uid' => getAuthUID()]); @@ -39,7 +40,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller } - //Standardfall: für extensions überschreiben: speichern der Zuordnung in eigene Extensiontabelle angeben + //Override function for extensions protected function assignNotiz($notiz_id, $id, $type) { $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); @@ -55,7 +56,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller return success(getData($result)); } - //Standardfall: für extensions überschreiben: speichern der Zuordnung in eigene Extensiontabelle angeben + //Override function for extensions protected function deleteNotizzuordnung($notiz_id, $id, $type) { $this->load->model('person/Notizzuordnung_model', 'NotizzuordnungModel'); @@ -72,11 +73,23 @@ abstract class Notiz_Controller extends FHCAPI_Controller } + //Override function for extensions + public function getNotizen($id, $type) + { + $result = $this->NotizzuordnungModel->isValidType($type); + if(isError($result)) + $this->terminateWithError($result->retval, self::ERROR_TYPE_GENERAL); - //TODO(manu) abstract - protected function getNotizen($id, $type) {} + $result = $this->NotizModel->getNotizWithDocEntries($id, $type); - //TODO(manu) to abstract + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + return $this->terminateWithSuccess(getData($result) ?: []); + } + + + //Override function protected function isBerechtigt($id, $typeId){ return $this->terminateWithError("in abstract function: define right in extension", self::ERROR_TYPE_GENERAL); } @@ -144,7 +157,8 @@ abstract class Notiz_Controller extends FHCAPI_Controller $titel = $this->input->post('titel'); $text = $this->input->post('text'); $erledigt = $this->input->post('erledigt'); - $verfasser_uid = isset($_POST['verfasser_uid']) ? $_POST['verfasser_uid'] : $uid; + $verfasser_uid = isset($_POST['verfasser']) ? $_POST['verfasser'] : $uid; + //$verfasser_uid = isset($_POST['verfasser_uid']) ? $_POST['verfasser_uid'] : $uid; $bearbeiter_uid = isset($_POST['bearbeiter_uid']) ? $_POST['bearbeiter_uid'] : null; $type = $this->input->post('typeId'); $start = $this->input->post('start'); @@ -153,7 +167,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller // Start DB transaction $this->db->trans_start(); - //Speichern der Notiz + //Save note $result = $this->NotizModel->insert(array('titel' => $titel, 'text' => $text, 'erledigt' => $erledigt, 'verfasser_uid' => $verfasser_uid, "insertvon" => $verfasser_uid, 'start' => $start, 'ende' => $ende, 'bearbeiter_uid' => $bearbeiter_uid)); @@ -165,7 +179,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller $notiz_id = $result->retval; - //Speichern der Notizzuordnung + //save Notizzuordnung $result = $this->assignNotiz($notiz_id, $id, $type); if (isError($result)) @@ -174,7 +188,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); } - //Speichern der Dokumente + //save Documents $dms_id_arr = []; foreach ($_FILES as $k => $file) { @@ -189,6 +203,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller //Todo(manu) check if filetypes weiter eingeschränkt werden sollen //Todo(manu)check name files: nicht gleiches file 2mal hochladen + //Todo define in dms component: readFile, downloadFile $result = $this->dmslib->upload($dms, $k, ['*']); /* $result = $this->dmslib->upload($dms, $k, ['application/pdf','application/x.fhc-dms+json']);*/ if (isError($result)) @@ -199,10 +214,9 @@ abstract class Notiz_Controller extends FHCAPI_Controller $dms_id_arr[] = $result->retval['dms_id']; } - //Eintrag in Notizdokument speichern + //save entry in Notizdokument if($dms_id_arr) { - // Loads model Notizdokument_model $this->load->model('person/Notizdokument_model', 'NotizdokumentModel'); foreach($dms_id_arr as $dms_id) { @@ -257,8 +271,8 @@ abstract class Notiz_Controller extends FHCAPI_Controller $uid = getAuthUID(); $titel = $this->input->post('titel'); $text = $this->input->post('text'); - $verfasser_uid = $this->input->post('verfasser_uid'); - $bearbeiter_uid = isset($_POST['bearbeiter_uid']) ? $_POST['bearbeiter_uid'] : $uid; + $verfasser_uid = isset($_POST['verfasser']) ? $_POST['verfasser'] : $uid; + $bearbeiter_uid = isset($_POST['bearbeiter']) ? $_POST['bearbeiter'] : $uid; $erledigt = $this->input->post('erledigt'); $start = $this->input->post('start'); $ende = $this->input->post('ende'); @@ -284,7 +298,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); } - //update(1) laden aller bereits mit dieser notiz_id verknüpften DMS-Einträge + //update(1) loading all dms-entries with this notiz_id $this->load->model('person/Notizdokument_model', 'NotizdokumentModel'); $this->NotizdokumentModel->addJoin('campus.tbl_dms_version', 'dms_id'); $dms_uploaded = null; @@ -311,7 +325,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller foreach ($_FILES as $k => $file) { - //update(2) alle neuen files (alle außer type application/x.fhc-dms+json) anhängen + //update(2) attach all new files (except type application/x.fhc-dms+json) if($file["type"] == 'application/x.fhc-dms+json') { $dms_uploaded[] = array( @@ -331,6 +345,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller //Todo(manu) check if filetypes weiter eingeschränkt werden sollen //Todo(manu)check name files: nicht gleiches file 2mal hochladen + //Todo define in dms component: readFile, downloadFile $result = $this->dmslib->upload($dms, $k, array('*')); if (isError($result)) @@ -347,7 +362,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller } } - //update(3) check if Dateien gelöscht wurden + //update(3) check if all files have been deleted if(count($dms_uploaded) != count($dms_id_arr)) { if (count($dms_uploaded) == 0) @@ -387,12 +402,9 @@ abstract class Notiz_Controller extends FHCAPI_Controller $typeId = $this->input->post('type_id'); $id = $this->input->post('id'); - if(!$this->isBerechtigt($id, $typeId)) - { - $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), self::ERROR_TYPE_GENERAL); - } + //TODO(manu): define Permissions for deletion document if filecomponent finished - //dms_id auslesen aus notizdokument wenn vorhanden + //get dms_id from notizdokument $dms_id_arr = []; $this->load->model('person/Notizdokument_model', 'NotizdokumentModel'); @@ -416,7 +428,6 @@ abstract class Notiz_Controller extends FHCAPI_Controller // Start DB transaction $this->db->trans_start(); - //return $this->terminateWithError("dms_id kommt: " . $notiz_id, self::ERROR_TYPE_GENERAL); $this->load->library('DmsLib'); @@ -444,7 +455,7 @@ abstract class Notiz_Controller extends FHCAPI_Controller $this->load->model('person/Notiz_model', 'NotizModel'); - //Löschen von Notiz + //Delete Note $result = $this->NotizModel->delete( array('notiz_id' => $notiz_id) ); @@ -465,10 +476,6 @@ abstract class Notiz_Controller extends FHCAPI_Controller public function loadDokumente() { - if(!$this->isBerechtigt('$id', '$typeId')) - { - $this->terminateWithError($this->p->t('ui', 'keineBerechtigung'), self::ERROR_TYPE_GENERAL); - } $_POST = json_decode(utf8_encode($this->input->raw_input_stream), true); $notiz_id = $this->input->post('notiz_id'); diff --git a/public/js/api/notiz.js b/public/js/api/notiz.js index 9d210a8f9..b0a01e989 100644 --- a/public/js/api/notiz.js +++ b/public/js/api/notiz.js @@ -1,13 +1,29 @@ -//TODO(Manu) refactor with require or async +//TODO(Manu) refactor for extensions with require or async //sonst Error wenn extension file nicht vorhanden import person from "./notiz/person.js"; -import softwarenotiz from "../../extensions/FHC-Core-Softwarebereitstellung/js/api/softwarenotiz.js"; +import prestudent from "./notiz/prestudent.js"; +import mitarbeiter from "./notiz/mitarbeiter.js"; +import projekt from "./notiz/projekt.js"; +import anrechnung from "./notiz/anrechnung.js"; +import bestellung from "./notiz/bestellung.js"; +import lehreinheit from "./notiz/lehreinheit.js"; +import projektphase from "./notiz/projektphase.js"; +import projekttask from "./notiz/projekttask.js"; +//import softwarenotiz from "../../extensions/FHC-Core-Softwarebereitstellung/js/api/softwarenotiz.js"; //import pppnotiz from "../../extensions/FHC-Core-PEP/js/api/pppnotiz.js"; export default { person, - softwarenotiz, + prestudent, + mitarbeiter, + anrechnung, + bestellung, + lehreinheit, + projekt, + projektphase, + projekttask, +// softwarenotiz, // pppnotiz } \ No newline at end of file diff --git a/public/js/api/notiz/anrechnung.js b/public/js/api/notiz/anrechnung.js new file mode 100644 index 000000000..843017687 --- /dev/null +++ b/public/js/api/notiz/anrechnung.js @@ -0,0 +1,38 @@ +export default { + getNotizen(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizAnrechnung/getNotizen/' + params.id + '/' + params.type); + }, + getUid(){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizAnrechnung/getUid/'); + }, + addNewNotiz(id, formData) { + return this.$fhcApi.post('api/frontend/v1/notiz/notizAnrechnung/addNewNotiz/' + id, + formData + ); + }, + loadNotiz(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizAnrechnung/loadNotiz/', { + notiz_id + }); + }, + loadDokumente(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizAnrechnung/loadDokumente/', { + notiz_id + }); + }, + deleteNotiz(notiz_id, type_id, id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizAnrechnung/deleteNotiz/', { + notiz_id, + type_id, + id + }); + }, + updateNotiz(notiz_id, formData){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizAnrechnung/updateNotiz/' + notiz_id, + formData + ); + }, + getMitarbeiter(event){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizAnrechnung/getMitarbeiter/' + event); + } +} \ No newline at end of file diff --git a/public/js/api/notiz/bestellung.js b/public/js/api/notiz/bestellung.js new file mode 100644 index 000000000..3f0f5268c --- /dev/null +++ b/public/js/api/notiz/bestellung.js @@ -0,0 +1,38 @@ +export default { + getNotizen(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizBestellung/getNotizen/' + params.id + '/' + params.type); + }, + getUid(){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizBestellung/getUid/'); + }, + addNewNotiz(id, formData) { + return this.$fhcApi.post('api/frontend/v1/notiz/notizBestellung/addNewNotiz/' + id, + formData + ); + }, + loadNotiz(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizBestellung/loadNotiz/', { + notiz_id + }); + }, + loadDokumente(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizBestellung/loadDokumente/', { + notiz_id + }); + }, + deleteNotiz(notiz_id, type_id, id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizBestellung/deleteNotiz/', { + notiz_id, + type_id, + id + }); + }, + updateNotiz(notiz_id, formData){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizBestellung/updateNotiz/' + notiz_id, + formData + ); + }, + getMitarbeiter(event){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizBestellung/getMitarbeiter/' + event); + } +} \ No newline at end of file diff --git a/public/js/api/notiz/lehreinheit.js b/public/js/api/notiz/lehreinheit.js new file mode 100644 index 000000000..85b3187ae --- /dev/null +++ b/public/js/api/notiz/lehreinheit.js @@ -0,0 +1,38 @@ +export default { + getNotizen(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizLehreinheit/getNotizen/' + params.id + '/' + params.type); + }, + getUid(){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizLehreinheit/getUid/'); + }, + addNewNotiz(id, formData) { + return this.$fhcApi.post('api/frontend/v1/notiz/notizLehreinheit/addNewNotiz/' + id, + formData + ); + }, + loadNotiz(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizLehreinheit/loadNotiz/', { + notiz_id + }); + }, + loadDokumente(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizLehreinheit/loadDokumente/', { + notiz_id + }); + }, + deleteNotiz(notiz_id, type_id, id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizLehreinheit/deleteNotiz/', { + notiz_id, + type_id, + id + }); + }, + updateNotiz(notiz_id, formData){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizLehreinheit/updateNotiz/' + notiz_id, + formData + ); + }, + getMitarbeiter(event){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizLehreinheit/getMitarbeiter/' + event); + } +} \ No newline at end of file diff --git a/public/js/api/notiz/mitarbeiter.js b/public/js/api/notiz/mitarbeiter.js new file mode 100644 index 000000000..ada9b0ae8 --- /dev/null +++ b/public/js/api/notiz/mitarbeiter.js @@ -0,0 +1,38 @@ +export default { + getNotizen(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizMitarbeiter/getNotizen/' + params.id + '/' + params.type); + }, + getUid(){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizMitarbeiter/getUid/'); + }, + addNewNotiz(id, formData) { + return this.$fhcApi.post('api/frontend/v1/notiz/notizMitarbeiter/addNewNotiz/' + id, + formData + ); + }, + loadNotiz(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizMitarbeiter/loadNotiz/', { + notiz_id + }); + }, + loadDokumente(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizMitarbeiter/loadDokumente/', { + notiz_id + }); + }, + deleteNotiz(notiz_id, type_id, id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizMitarbeiter/deleteNotiz/', { + notiz_id, + type_id, + id + }); + }, + updateNotiz(notiz_id, formData){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizMitarbeiter/updateNotiz/' + notiz_id, + formData + ); + }, + getMitarbeiter(event){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizMitarbeiter/getMitarbeiter/' + event); + } +} \ No newline at end of file diff --git a/public/js/api/notiz/person.js b/public/js/api/notiz/person.js index be19b4ed9..62a3640f0 100644 --- a/public/js/api/notiz/person.js +++ b/public/js/api/notiz/person.js @@ -1,38 +1,41 @@ export default { getNotizen(url, config, params){ - return this.$fhcApi.get('api/frontend/v1/notiz/notiz/getNotizen/' + params.id + '/' + params.type); + return this.$fhcApi.get('api/frontend/v1/notiz/notizPerson/getNotizen/' + params.id + '/' + params.type); }, getUid(){ - return this.$fhcApi.get('api/frontend/v1/notiz/notiz/getUid/'); + return this.$fhcApi.get('api/frontend/v1/notiz/notizPerson/getUid/'); }, addNewNotiz(id, formData) { - return this.$fhcApi.post('api/frontend/v1/notiz/notiz/addNewNotiz/' + id, + return this.$fhcApi.post('api/frontend/v1/notiz/notizPerson/addNewNotiz/' + id, formData ); }, loadNotiz(notiz_id){ - return this.$fhcApi.post('api/frontend/v1/notiz/notiz/loadNotiz/', { + return this.$fhcApi.post('api/frontend/v1/notiz/notizPerson/loadNotiz/', { notiz_id }); }, loadDokumente(notiz_id){ - return this.$fhcApi.post('api/frontend/v1/notiz/notiz/loadDokumente/', { + return this.$fhcApi.post('api/frontend/v1/notiz/notizPerson/loadDokumente/', { notiz_id }); }, deleteNotiz(notiz_id, type_id, id){ - return this.$fhcApi.post('api/frontend/v1/notiz/notiz/deleteNotiz/', { + return this.$fhcApi.post('api/frontend/v1/notiz/notizPerson/deleteNotiz/', { notiz_id, type_id, id }); }, updateNotiz(notiz_id, formData){ - return this.$fhcApi.post('api/frontend/v1/notiz/notiz/updateNotiz/' + notiz_id, + return this.$fhcApi.post('api/frontend/v1/notiz/notizPerson/updateNotiz/' + notiz_id, formData ); }, getMitarbeiter(event){ - return this.$fhcApi.get('api/frontend/v1/notiz/notiz/getMitarbeiter/' + event); + return this.$fhcApi.get('api/frontend/v1/notiz/notizPerson/getMitarbeiter/' + event); + }, + isBerechtigt(id, type_id){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizPerson/isBerechtigt/'); } } \ No newline at end of file diff --git a/public/js/api/notiz/prestudent.js b/public/js/api/notiz/prestudent.js new file mode 100644 index 000000000..c529ea44e --- /dev/null +++ b/public/js/api/notiz/prestudent.js @@ -0,0 +1,38 @@ +export default { + getNotizen(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizPrestudent/getNotizen/' + params.id + '/' + params.type); + }, + getUid(){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizPrestudent/getUid/'); + }, + addNewNotiz(id, formData) { + return this.$fhcApi.post('api/frontend/v1/notiz/notizPrestudent/addNewNotiz/' + id, + formData + ); + }, + loadNotiz(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizPrestudent/loadNotiz/', { + notiz_id + }); + }, + loadDokumente(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizPrestudent/loadDokumente/', { + notiz_id + }); + }, + deleteNotiz(notiz_id, type_id, id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizPrestudent/deleteNotiz/', { + notiz_id, + type_id, + id + }); + }, + updateNotiz(notiz_id, formData){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizPrestudent/updateNotiz/' + notiz_id, + formData + ); + }, + getMitarbeiter(event){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizPrestudent/getMitarbeiter/' + event); + } +} \ No newline at end of file diff --git a/public/js/api/notiz/projekt.js b/public/js/api/notiz/projekt.js new file mode 100644 index 000000000..ac8b71623 --- /dev/null +++ b/public/js/api/notiz/projekt.js @@ -0,0 +1,38 @@ +export default { + getNotizen(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/notiz/NotizProjekt/getNotizen/' + params.id + '/' + params.type); + }, + getUid(){ + return this.$fhcApi.get('api/frontend/v1/notiz/NotizProjekt/getUid/'); + }, + addNewNotiz(id, formData) { + return this.$fhcApi.post('api/frontend/v1/notiz/NotizProjekt/addNewNotiz/' + id, + formData + ); + }, + loadNotiz(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/NotizProjekt/loadNotiz/', { + notiz_id + }); + }, + loadDokumente(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/NotizProjekt/loadDokumente/', { + notiz_id + }); + }, + deleteNotiz(notiz_id, type_id, id){ + return this.$fhcApi.post('api/frontend/v1/notiz/NotizProjekt/deleteNotiz/', { + notiz_id, + type_id, + id + }); + }, + updateNotiz(notiz_id, formData){ + return this.$fhcApi.post('api/frontend/v1/notiz/NotizProjekt/updateNotiz/' + notiz_id, + formData + ); + }, + getMitarbeiter(event){ + return this.$fhcApi.get('api/frontend/v1/notiz/NotizProjekt/getMitarbeiter/' + event); + } +} \ No newline at end of file diff --git a/public/js/api/notiz/projektphase.js b/public/js/api/notiz/projektphase.js new file mode 100644 index 000000000..8b85106a3 --- /dev/null +++ b/public/js/api/notiz/projektphase.js @@ -0,0 +1,38 @@ +export default { + getNotizen(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizProjektphase/getNotizen/' + params.id + '/' + params.type); + }, + getUid(){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizProjektphase/getUid/'); + }, + addNewNotiz(id, formData) { + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjektphase/addNewNotiz/' + id, + formData + ); + }, + loadNotiz(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjektphase/loadNotiz/', { + notiz_id + }); + }, + loadDokumente(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjektphase/loadDokumente/', { + notiz_id + }); + }, + deleteNotiz(notiz_id, type_id, id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjektphase/deleteNotiz/', { + notiz_id, + type_id, + id + }); + }, + updateNotiz(notiz_id, formData){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjektphase/updateNotiz/' + notiz_id, + formData + ); + }, + getMitarbeiter(event){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizProjektphase/getMitarbeiter/' + event); + } +} \ No newline at end of file diff --git a/public/js/api/notiz/projekttask.js b/public/js/api/notiz/projekttask.js new file mode 100644 index 000000000..6b6db3c77 --- /dev/null +++ b/public/js/api/notiz/projekttask.js @@ -0,0 +1,38 @@ +export default { + getNotizen(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizProjekttask/getNotizen/' + params.id + '/' + params.type); + }, + getUid(){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizProjekttask/getUid/'); + }, + addNewNotiz(id, formData) { + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjekttask/addNewNotiz/' + id, + formData + ); + }, + loadNotiz(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjekttask/loadNotiz/', { + notiz_id + }); + }, + loadDokumente(notiz_id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjekttask/loadDokumente/', { + notiz_id + }); + }, + deleteNotiz(notiz_id, type_id, id){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjekttask/deleteNotiz/', { + notiz_id, + type_id, + id + }); + }, + updateNotiz(notiz_id, formData){ + return this.$fhcApi.post('api/frontend/v1/notiz/notizProjekttask/updateNotiz/' + notiz_id, + formData + ); + }, + getMitarbeiter(event){ + return this.$fhcApi.get('api/frontend/v1/notiz/notizProjekttask/getMitarbeiter/' + event); + } +} \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Notizen.js b/public/js/components/Stv/Studentenverwaltung/Details/Notizen.js index 4c128bfea..185b8d0d5 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Notizen.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Notizen.js @@ -11,20 +11,6 @@ export default {

Notizen

- - - + - - diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index ba4dcfa01..e4d6d1870 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -27781,6 +27781,28 @@ array( ) ) ), + + //**************************** FHC-Core-Notizcomponent + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'error_typeNotizIdIncorrect', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Id-Typ der Notiz ist nicht korrekt', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Id type of note is incorrect', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), ); From d542cf7720b9c0651d8510a18a73916e3200a6ae Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Wed, 14 Aug 2024 16:20:47 +0200 Subject: [PATCH 0002/1357] s&d --- application/libraries/AntragLib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/libraries/AntragLib.php b/application/libraries/AntragLib.php index c1649587d..1a96eadf1 100644 --- a/application/libraries/AntragLib.php +++ b/application/libraries/AntragLib.php @@ -126,7 +126,7 @@ class AntragLib return $this->_ci->StudierendenantragstatusModel->resumeAntraegeForAbmeldungStgl($antrag_id); } // NOTE(chris): get last status that is not pause - $this->_ci->StudierendenantragstatusModel->addOrder('insertamum'); + $this->_ci->StudierendenantragstatusModel->addOrder('insertamum', 'DESC'); $this->_ci->StudierendenantragstatusModel->addLimit(1); $result = $this->_ci->StudierendenantragstatusModel->loadWhere([ 'studierendenantrag_id' => $antrag_id, From 6c08741450589e3b3a82465b9c90f79358de1810 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Fri, 20 Sep 2024 14:31:25 +0200 Subject: [PATCH 0003/1357] 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/1357] 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 bf1bc8d1a97dbb52c2d1bd30a295a3909058da22 Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Sun, 13 Oct 2024 13:32:15 +0200 Subject: [PATCH 0005/1357] 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 0006/1357] 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 0007/1357] 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 b8ff37eb8edfd28c897119d032c517533663574f Mon Sep 17 00:00:00 2001 From: Alexei Karpenko Date: Sat, 19 Oct 2024 17:25:09 +0200 Subject: [PATCH 0008/1357] 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 a9a18d1cd43f2ba49b041117f03f6f67aa972df5 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Mon, 28 Oct 2024 11:16:02 +0100 Subject: [PATCH 0009/1357] 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 0010/1357] 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 0011/1357] 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 0012/1357] 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 0013/1357] 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 0014/1357] 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 b1452698f842aefb7030d9c529a4415a023b616a Mon Sep 17 00:00:00 2001 From: ma0068 Date: Mon, 4 Nov 2024 12:54:34 +0100 Subject: [PATCH 0015/1357] Start View Karteireiter, Components Unassigned, Details and Stati --- .../api/frontend/v1/vertraege/Vertraege.php | 224 ++++++++++ .../models/accounting/Vertrag_model.php | 159 +++++++ .../models/accounting/Vertragstyp_model.php | 1 + .../Vertragvertragsstatus_model.php | 2 + public/js/api/fhcapifactory.js | 4 +- public/js/api/vertraege.js | 5 + public/js/api/vertraege/person.js | 36 ++ .../Stv/Studentenverwaltung/Details.js | 1 + .../Details/Prestudent/MultiStatus.js | 5 + .../js/components/Vertraege/List/Details.js | 113 +++++ public/js/components/Vertraege/List/Status.js | 75 ++++ .../components/Vertraege/List/Unassigned.js | 113 +++++ public/js/components/Vertraege/Vertraege.js | 389 ++++++++++++++++++ 13 files changed, 1126 insertions(+), 1 deletion(-) create mode 100644 application/controllers/api/frontend/v1/vertraege/Vertraege.php create mode 100644 public/js/api/vertraege.js create mode 100644 public/js/api/vertraege/person.js create mode 100644 public/js/components/Vertraege/List/Details.js create mode 100644 public/js/components/Vertraege/List/Status.js create mode 100644 public/js/components/Vertraege/List/Unassigned.js create mode 100644 public/js/components/Vertraege/Vertraege.js diff --git a/application/controllers/api/frontend/v1/vertraege/Vertraege.php b/application/controllers/api/frontend/v1/vertraege/Vertraege.php new file mode 100644 index 000000000..eaf3062f5 --- /dev/null +++ b/application/controllers/api/frontend/v1/vertraege/Vertraege.php @@ -0,0 +1,224 @@ + ['admin:r', 'assistenz:r'], + 'getAllContractsNotAssigned' => ['admin:r', 'assistenz:r'], + 'getAllContractsAssigned' => ['admin:r', 'assistenz:r'], + 'getAllContractTypes' => self::PERM_LOGGED, + 'getStatiOfContract' => self::PERM_LOGGED, + 'loadContract' => ['admin:r', 'assistenz:r'], + 'updateContract' => ['admin:r', 'assistenz:r'], + 'addNewContract' => ['admin:r', 'assistenz:r'], + 'deleteContract' => ['admin:r', 'assistenz:r'], + + ]); + + //Load Models + $this->load->model('accounting/Vertrag_model', 'VertragModel'); + $this->load->model('accounting/Vertragsstatus_model', 'VertragsstatusModel'); + $this->load->model('accounting/Vertragstyp_model', 'VertragstypModel'); + $this->load->model('accounting/Vertragvertragsstatus_model', 'VertragvertragsstatusModel'); + } + + public function getAllVertraege($person_id) + { + $result = $this->VertragModel->loadContractsOfPerson($person_id); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getAllContractsNotAssigned($person_id) + { + $result = $this->VertragModel->loadContractsOfPersonNotAssigned($person_id); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getAllContractsAssigned($person_id, $vertrag_id) + { + $result = $this->VertragModel->loadContractsOfPersonAssigned($person_id, $vertrag_id); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getStatiOfContract($vertrag_id) + { + $result = $this->VertragModel->getStatiOfContract($vertrag_id); + + if (isError($result)) { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + + $this->terminateWithSuccess((getData($result) ?: [])); + } + + public function getAllContractTypes() + { + $result = $this->VertragstypModel->load(); + if (isError($result)) + { + $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL); + } + $this->terminateWithSuccess(getData($result) ?: []); + } + + public function addNewContract($person_id) + { + //$this->validateNewOrUpdate(); + //TODO(Manu) check validations + + //$person_id = $this->input->post('person_id'); + $vertragsdatum = $this->input->post('vertragsdatum'); + $bezeichnung = $this->input->post('bezeichnung'); + $vertragstyp_kurzbz = $this->input->post('vertragstyp_kurzbz'); + $betrag = $this->input->post('betrag'); + $vertragsstunden = $this->input->post('vertragsstunden'); + $vertragsstunden_studiensemester_kurzbz = $this->input->post('vertragsstunden_studiensemester_kurzbz'); + $anmerkung = $this->input->post('anmerkung'); + + $this->db->trans_start(); + + $result = $this->VertragModel->insert( [ + 'person_id' => $person_id, + 'vertragsdatum' => $vertragsdatum, + 'bezeichnung' => $bezeichnung, + 'vertragstyp_kurzbz' => $vertragstyp_kurzbz, + 'betrag' => $betrag, + 'vertragsstunden' => $vertragsstunden, + 'vertragsstunden_studiensemester_kurzbz' => $vertragsstunden_studiensemester_kurzbz, + 'anmerkung' => $anmerkung, + 'insertamum' => date('c'), + 'insertvon' => getAuthUID() + ]); + + //add entry with status neu in lehre.tbl_vertrag_vertragsstatus + //TODO(Manu) validation, dass status nicht schon bereits vorhanden + $status_result = $this->VertragvertragsstatusModel->insert([ + 'vertrag_id' => $result->retval, + 'uid' => getAuthUID(), + 'vertragsstatus_kurzbz' => 'neu', + 'insertamum' => date('c'), + 'insertvon' => getAuthUID(), + 'datum' => date('c') + ]); + + if (!$status_result) { + $this->db->trans_rollback(); + $this->terminateWithError('Fehler beim Hinzufügen des Vertragsstatus.'); + } + + $this->db->trans_complete(); + + $this->terminateWithSuccess(true); + } + + public function loadContract($vertrag_id) + { + $result = $this->VertragModel->load($vertrag_id); + + if (isError($result)) { + $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + + if (!hasData($result)) { + $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id' => 'Vertrag_id']), self::ERROR_TYPE_GENERAL); + } + + $this->terminateWithSuccess(current(getData($result))); + } + + public function updateContract($vertrag_id) + { + //$this->validateNewOrUpdate(); + //TODO(Manu)validations + + + $vertrag_id = $this->input->post('vertrag_id'); + $person_id = $this->input->post('person_id'); + $vertragsdatum = $this->input->post('vertragsdatum'); + $bezeichnung = $this->input->post('bezeichnung'); + $vertragstyp_kurzbz = $this->input->post('vertragstyp_kurzbz'); + $betrag = $this->input->post('betrag'); + $vertragsstunden = $this->input->post('vertragsstunden'); + $vertragsstunden_studiensemester_kurzbz = $this->input->post('vertragsstunden_studiensemester_kurzbz'); + $anmerkung = $this->input->post('anmerkung'); + + $this->db->trans_start(); + + $result = $this->VertragModel->update($vertrag_id, [ + 'person_id' => $person_id, + 'vertragsdatum' => $vertragsdatum, + 'bezeichnung' => $bezeichnung, + 'vertragstyp_kurzbz' => $vertragstyp_kurzbz, + 'betrag' => $betrag, + 'vertragsstunden' => $vertragsstunden, + 'vertragsstunden_studiensemester_kurzbz' => $vertragsstunden_studiensemester_kurzbz, + 'anmerkung' => $anmerkung, + 'vertragsstunden' => $vertragsstunden, + 'updateamum' => date('c'), + 'updatevon' => getAuthUID() + ]); + + $this->getDataOrTerminateWithError($result); + + $this->db->trans_complete(); + + $this->terminateWithSuccess(true); + } + + public function deleteContract($vertrag_id) + { + //TODO(Manu) validations, + $result = $this->VertragvertragsstatusModel->load([ + 'vertrag_id' => $vertrag_id + ]); + + if(hasData($result)){ + $data = getData($result); + foreach ($data as $item) { + //delete all entries in lehre.tbl_vertrag_vertragsstatus + $result = $this->VertragvertragsstatusModel->delete([ + 'vertrag_id' => $vertrag_id, + 'vertragsstatus_kurzbz' => $item->vertragsstatus_kurzbz, + 'uid' => $item->uid + ]); + if(isError($result)) + $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + } + + //delete Contract + $result = $this->VertragModel->delete( + array('vertrag_id' => $vertrag_id, + ) + ); + + if (isError($result)) { + return $this->terminateWithError($result, self::ERROR_TYPE_GENERAL); + } + if (!hasData($result)) { + return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id' => 'Vertrag_id']), self::ERROR_TYPE_GENERAL); + } + return $this->outputJsonSuccess(current(getData($result))); + } +} \ No newline at end of file diff --git a/application/models/accounting/Vertrag_model.php b/application/models/accounting/Vertrag_model.php index 8725cd98d..62b109922 100644 --- a/application/models/accounting/Vertrag_model.php +++ b/application/models/accounting/Vertrag_model.php @@ -330,4 +330,163 @@ class Vertrag_model extends DB_Model return $bezeichnung; } + + /** + * Loads all Contracts of a Person + * @param $person_id + * @return array of objects + */ + public function loadContractsOfPerson($person_id) + { + $query = " + SELECT + *, + tbl_vertrag.bezeichnung as bezeichnung, + tbl_vertragstyp.bezeichnung as vertragstyp_bezeichnung, + TO_CHAR(tbl_vertrag.vertragsdatum::timestamp, 'DD.MM.YYYY') AS format_vertragsdatum, + (SELECT bezeichnung FROM lehre.tbl_vertragsstatus + JOIN lehre.tbl_vertrag_vertragsstatus USING(vertragsstatus_kurzbz) + WHERE vertrag_id=tbl_vertrag.vertrag_id ORDER BY datum desc limit 1) as status, anmerkung + FROM + lehre.tbl_vertrag + LEFT JOIN lehre.tbl_vertragstyp USING(vertragstyp_kurzbz) + WHERE person_id= ?"; + + + return $this->execQuery($query, array($person_id)); + } + + /** + * Loads all Contracts of a Person that are not assigned yet + * @param $person_id + * @return array of objects + */ + public function loadContractsOfPersonNotAssigned($person_id) + { + $query = " +SELECT + 'Lehrauftrag' as type, + lehreinheit_id, + mitarbeiter_uid, + null as pruefung_id, + null as projektarbeit_id, + (tbl_lehreinheitmitarbeiter.semesterstunden*tbl_lehreinheitmitarbeiter.stundensatz) as betrag, + tbl_lehreinheit.studiensemester_kurzbz, + null as betreuerart_kurzbz, + ( SELECT + upper(tbl_studiengang.typ || tbl_studiengang.kurzbz) || tbl_lehrveranstaltung.semester || '-' || tbl_lehrveranstaltung.kurzbz || '-' || tbl_lehreinheit.lehrform_kurzbz + FROM + lehre.tbl_lehrveranstaltung + JOIN public.tbl_studiengang USING(studiengang_kz) + WHERE + lehrveranstaltung_id=tbl_lehreinheit.lehrveranstaltung_id) + as bezeichnung + FROM + lehre.tbl_lehreinheitmitarbeiter + JOIN lehre.tbl_lehreinheit USING(lehreinheit_id) + WHERE + mitarbeiter_uid IN (SELECT uid FROM public.tbl_benutzer WHERE person_id=?) + AND vertrag_id IS NULL + UNION + SELECT + 'Betreuung' as type, + tbl_projektarbeit.lehreinheit_id as lehreinheit_id, + null as mitarbeiter_uid, + null::integer as pruefung_id, + projektarbeit_id, + (tbl_projektbetreuer.stunden*tbl_projektbetreuer.stundensatz) as betrag, + tbl_lehreinheit.studiensemester_kurzbz, + tbl_projektbetreuer.betreuerart_kurzbz, + (SELECT nachname || ' ' || vorname FROM public.tbl_person JOIN public.tbl_benutzer USING(person_id) WHERE uid=tbl_projektarbeit.student_uid) + as bezeichnung + FROM + lehre.tbl_projektbetreuer + JOIN lehre.tbl_projektarbeit USING(projektarbeit_id) + JOIN lehre.tbl_lehreinheit USING(lehreinheit_id) + WHERE + tbl_projektbetreuer.person_id=? + AND vertrag_id IS NULL + "; + + return $this->execQuery($query, array($person_id, $person_id)); + } + + /** + * Loads all Contracts of a Person that are assigned yet + * @param $person_id, $vertrag_id + * @return array of objects + */ + //TODO(Manu) vereinfachen! + public function loadContractsOfPersonAssigned($person_id, $vertrag_id) + { + $query = " + SELECT + 'Lehrauftrag' as type, + lehreinheit_id, + mitarbeiter_uid, + null as pruefung_id, + null as projektarbeit_id, + (tbl_lehreinheitmitarbeiter.semesterstunden * tbl_lehreinheitmitarbeiter.stundensatz) as betrag, + tbl_lehreinheit.studiensemester_kurzbz, + null as betreuerart_kurzbz, + ( SELECT + upper(tbl_studiengang.typ || tbl_studiengang.kurzbz) || tbl_lehrveranstaltung.semester || '-' || tbl_lehrveranstaltung.kurzbz || '-' || tbl_lehreinheit.lehrform_kurzbz + FROM + lehre.tbl_lehrveranstaltung + JOIN public.tbl_studiengang USING(studiengang_kz) + WHERE + lehrveranstaltung_id=tbl_lehreinheit.lehrveranstaltung_id) + as bezeichnung + FROM + lehre.tbl_lehreinheitmitarbeiter + JOIN lehre.tbl_lehreinheit USING(lehreinheit_id) + WHERE + mitarbeiter_uid IN (SELECT uid FROM public.tbl_benutzer WHERE person_id=?) + AND vertrag_id = ? + UNION + SELECT + 'Betreuung' as type, + tbl_projektarbeit.lehreinheit_id as lehreinheit_id, + null as mitarbeiter_uid, + null::integer as pruefung_id, + projektarbeit_id, + (tbl_projektbetreuer.stunden * tbl_projektbetreuer.stundensatz) as betrag, + tbl_lehreinheit.studiensemester_kurzbz, + tbl_projektbetreuer.betreuerart_kurzbz, + (SELECT nachname || ' ' || vorname FROM public.tbl_person JOIN public.tbl_benutzer USING(person_id) WHERE uid=tbl_projektarbeit.student_uid) + as bezeichnung + FROM + lehre.tbl_projektbetreuer + JOIN lehre.tbl_projektarbeit USING(projektarbeit_id) + JOIN lehre.tbl_lehreinheit USING(lehreinheit_id) + WHERE + tbl_projektbetreuer.person_id=? + AND vertrag_id = ? + "; + + return $this->execQuery($query, array($person_id, $vertrag_id, $person_id, $vertrag_id)); + } + + /** + * Returns all stati of a contract + * + * @param $vertrag_id + * @return array + */ + public function getStatiOfContract($vertrag_id) + { + $query = " + SELECT + * + FROM + lehre.tbl_vertrag_vertragsstatus + JOIN lehre.tbl_vertragsstatus USING(vertragsstatus_kurzbz) + WHERE + tbl_vertrag_vertragsstatus.vertrag_id = ? + ORDER BY datum DESC"; + + return $this->execQuery($query, array($vertrag_id)); + } + + } diff --git a/application/models/accounting/Vertragstyp_model.php b/application/models/accounting/Vertragstyp_model.php index 42d248217..a3275af6e 100644 --- a/application/models/accounting/Vertragstyp_model.php +++ b/application/models/accounting/Vertragstyp_model.php @@ -11,4 +11,5 @@ class Vertragstyp_model extends DB_Model $this->dbTable = 'lehre.tbl_vertragstyp'; $this->pk = 'vertragstyp_kurzbz'; } + } diff --git a/application/models/accounting/Vertragvertragsstatus_model.php b/application/models/accounting/Vertragvertragsstatus_model.php index 78a065540..75b0794cc 100644 --- a/application/models/accounting/Vertragvertragsstatus_model.php +++ b/application/models/accounting/Vertragvertragsstatus_model.php @@ -190,4 +190,6 @@ class Vertragvertragsstatus_model extends DB_Model return $this->loadWhere($condition); } + + } diff --git a/public/js/api/fhcapifactory.js b/public/js/api/fhcapifactory.js index e30772ca7..45a172302 100644 --- a/public/js/api/fhcapifactory.js +++ b/public/js/api/fhcapifactory.js @@ -24,6 +24,7 @@ import stv from "./stv.js"; import notiz from "./notiz.js"; import betriebsmittel from "./betriebsmittel.js"; import checkperson from "./checkperson.js"; +import vertraege from "./vertraege.js"; export default { search, @@ -34,5 +35,6 @@ export default { stv, notiz, betriebsmittel, - checkperson + checkperson, + vertraege }; \ No newline at end of file diff --git a/public/js/api/vertraege.js b/public/js/api/vertraege.js new file mode 100644 index 000000000..1c23316ef --- /dev/null +++ b/public/js/api/vertraege.js @@ -0,0 +1,5 @@ +import person from "./vertraege/person.js"; + +export default { + person +} \ No newline at end of file diff --git a/public/js/api/vertraege/person.js b/public/js/api/vertraege/person.js new file mode 100644 index 000000000..cff6ea87d --- /dev/null +++ b/public/js/api/vertraege/person.js @@ -0,0 +1,36 @@ +export default { + getAllVertraege(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/vertraege/vertraege/getAllVertraege/' + params.person_id); + }, + getAllContractsNotAssigned(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/vertraege/vertraege/getAllContractsNotAssigned/' + params.person_id); + }, + getAllContractsAssigned(url, config, params){ + console.log(params.person_id, params.vertrag_id); + return this.$fhcApi.get('api/frontend/v1/vertraege/vertraege/getAllContractsAssigned/' + params.person_id + '/' + params.vertrag_id); + }, + getAllContractsNotAssigned2(person_id){ + return this.$fhcApi.get('api/frontend/v1/vertraege/vertraege/getAllContractsNotAssigned/' + person_id); + }, + getStatiOfContract(url, config, params){ + return this.$fhcApi.get('api/frontend/v1/vertraege/vertraege/getStatiOfContract/' + params.vertrag_id); + }, + getAllContractTypes(){ + return this.$fhcApi.get('api/frontend/v1/vertraege/vertraege/getAllContractTypes/'); + }, + addNewContract(person_id, formData) { + return this.$fhcApi.post('api/frontend/v1/vertraege/vertraege/addNewContract/' + + person_id, formData + ); + }, + loadContract(vertrag_id){ + return this.$fhcApi.post('api/frontend/v1/vertraege/vertraege/loadContract/' + vertrag_id); + }, + updateContract(vertrag_id, formData) { + return this.$fhcApi.post( 'api/frontend/v1/vertraege/vertraege/updateContract/' + vertrag_id, + formData); + }, + deleteContract(vertrag_id){ + return this.$fhcApi.post('api/frontend/v1/vertraege/vertraege/deleteContract/' + vertrag_id); + }, +} \ No newline at end of file diff --git a/public/js/components/Stv/Studentenverwaltung/Details.js b/public/js/components/Stv/Studentenverwaltung/Details.js index 4df148063..dfa7e92ed 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details.js +++ b/public/js/components/Stv/Studentenverwaltung/Details.js @@ -43,6 +43,7 @@ export default { }, template: `
+
Bitte StudentIn auswählen!
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Prestudent/MultiStatus.js b/public/js/components/Stv/Studentenverwaltung/Details/Prestudent/MultiStatus.js index ac4a2dd9e..4cf826bb3 100644 --- a/public/js/components/Stv/Studentenverwaltung/Details/Prestudent/MultiStatus.js +++ b/public/js/components/Stv/Studentenverwaltung/Details/Prestudent/MultiStatus.js @@ -318,6 +318,9 @@ export default{ @saved="reload" > + TEST1: Anzahl Students: + {{modelValue.length}} + + TEST 2 +