diff --git a/application/controllers/api/frontend/v1/Udf.php b/application/controllers/api/frontend/v1/Udf.php
new file mode 100644
index 000000000..99ed2a10d
--- /dev/null
+++ b/application/controllers/api/frontend/v1/Udf.php
@@ -0,0 +1,142 @@
+.
+ */
+
+if (! defined('BASEPATH')) exit('No direct script access allowed');
+
+/**
+ * This controller operates between (interface) the JS (GUI) and the UDFLib (back-end)
+ * Provides data to the ajax get calls about the Udf component
+ * Listens to ajax post calls to change the Udf data
+ * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON
+ */
+class Udf extends FHCAPI_Controller
+{
+ /**
+ * Calls the parent's constructor and prepares the UDFLib
+ */
+ public function __construct()
+ {
+ // TODO(chris): permissions
+ // NOTE: UdfLib has its own permissions checks
+ parent::__construct([
+ 'load' => 'admin:r',#self::PERM_LOGGED,
+ 'save' => 'admin:r'#self::PERM_LOGGED
+ ]);
+
+ // Libraries
+ $this->load->library('form_validation');
+ $this->load->library('UDFLib');
+
+ // Models
+ $this->load->model($this->getTargetModelPath(), 'TargetModel');
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ /**
+ * Load all UDFs for a dataset
+ *
+ * @return void
+ */
+ public function load()
+ {
+ $pks = $this->TargetModel->getPks();
+ foreach ($pks as $id)
+ $this->form_validation->set_rules($id, $id, 'required');
+
+
+ if (!$this->form_validation->run())
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+
+ $id = [];
+ foreach ($pks as $pk)
+ $id[$pk] = $this->input->post($pk);
+ if (!is_array($this->TargetModel->getPk()))
+ $id = current($id);
+
+ $result = $this->udflib->getFieldArray($this->TargetModel, $id);
+
+ #$fields = $this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result));
+ $fields = $result->retval;
+
+ $this->terminateWithSuccess($fields);
+ }
+
+ /**
+ * Saves UDFs to a dataset
+ *
+ * @return void
+ */
+ public function save()
+ {
+ $pks = $this->TargetModel->getPks();
+ foreach ($pks as $id)
+ $this->form_validation->set_rules($id, $id, 'required');
+
+ $result = $this->udflib->getCiValidations($this->TargetModel, $this->input->post());
+
+ #$fieldValidations = $this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result));
+ $fieldvalidations = $result->retval;
+
+ $this->form_validation->set_rules($fieldvalidations);
+
+
+ if (!$this->form_validation->run())
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+
+ $id = [];
+ $fields = $this->input->post();
+ foreach ($pks as $pk) {
+ $id[$pk] = $fields[$pk];
+ unset($fields[$pk]);
+ }
+ if (!is_array($this->TargetModel->getPk()))
+ $id = current($id);
+
+ $result = $this->TargetModel->update($id, $fields);
+
+ #$this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+
+ $this->terminateWithSuccess(array_fill_keys(array_keys($fields), ''));
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Private methods
+
+ /**
+ * Get the path to the target model from the url
+ *
+ * @return string
+ */
+ private function getTargetModelPath()
+ {
+ $ci_model_path = array_slice($this->uri->rsegments, 2);
+ if ($ci_model_path)
+ $ci_model_path[] = ucfirst(array_pop($ci_model_path)) . '_model';
+ return implode(DIRECTORY_SEPARATOR, $ci_model_path);
+ }
+}
\ No newline at end of file
diff --git a/application/controllers/api/frontend/v1/stv/Student.php b/application/controllers/api/frontend/v1/stv/Student.php
new file mode 100644
index 000000000..101d34863
--- /dev/null
+++ b/application/controllers/api/frontend/v1/stv/Student.php
@@ -0,0 +1,588 @@
+.
+ */
+
+if (! defined('BASEPATH')) exit('No direct script access allowed');
+
+use \DateTime as DateTime;
+
+/**
+ * This controller operates between (interface) the JS (GUI) and the back-end
+ * Provides data to the ajax get calls about a Student
+ * Listens to ajax post calls to change the Student data
+ * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON
+ */
+class Student extends FHCAPI_Controller
+{
+ /**
+ * Calls the parent's constructor and prepares libraries and phrases
+ */
+ public function __construct()
+ {
+ // TODO(chris): permissions
+ parent::__construct([
+ 'get' => ['admin:r', 'assistenz:r'],
+ 'save' => ['admin:w', 'assistenz:w'],
+ 'check' => ['admin:w', 'assistenz:w'],
+ 'add' => ['admin:w', 'assistenz:w']
+ ]);
+
+ // Load Libraries
+ $this->load->library('VariableLib', ['uid' => getAuthUID()]);
+
+ // Load language phrases
+ $this->loadPhrases([
+ 'ui'
+ ]);
+ }
+
+ //------------------------------------------------------------------------------------------------------------------
+ // Public methods
+
+ /**
+ * Get details for a prestudent
+ *
+ * @param string $prestudent_id
+ * @return void
+ */
+ public function get($prestudent_id)
+ {
+ $studiensemester_kurzbz = $this->variablelib->getVar('semester_aktuell');
+
+ $this->load->model('crm/Prestudent_model', 'PrestudentModel');
+
+ $this->PrestudentModel->addSelect('p.*');
+ $this->PrestudentModel->addSelect('s.student_uid');
+ $this->PrestudentModel->addSelect('matrikelnr');
+ $this->PrestudentModel->addSelect('b.aktiv');
+ $this->PrestudentModel->addSelect('v.semester');
+ $this->PrestudentModel->addSelect('v.verband');
+ $this->PrestudentModel->addSelect('v.gruppe');
+ $this->PrestudentModel->addSelect('b.alias');
+
+ if (defined('ACTIVE_ADDONS') && strpos(ACTIVE_ADDONS, 'bewerbung') !== false) {
+ $this->PrestudentModel->addSelect(
+ "(
+ SELECT kontakt
+ FROM public.tbl_kontakt
+ WHERE kontakttyp='email'
+ AND person_id=p.person_id
+ AND zustellung
+ ORDER BY kontakt_id
+ LIMIT 1
+ ) AS email_privat",
+ false
+ );
+ }
+
+ $this->PrestudentModel->addJoin('public.tbl_student s', 'prestudent_id', 'LEFT');
+ $this->PrestudentModel->addJoin('public.tbl_benutzer b', 'student_uid = uid', 'LEFT');
+ $this->PrestudentModel->addJoin(
+ 'public.tbl_studentlehrverband v',
+ 'b.uid = v.student_uid AND v.studiensemester_kurzbz = ' . $this->PrestudentModel->escape($studiensemester_kurzbz),
+ 'LEFT'
+ );
+ $this->PrestudentModel->addJoin('public.tbl_person p', 'p.person_id = tbl_prestudent.person_id');
+
+ $result = $this->PrestudentModel->loadWhere(['prestudent_id' => $prestudent_id]);
+
+ #$student = $this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ $student = $result->retval;
+
+ if (!$student)
+ return show_404();
+
+ $this->terminateWithSuccess(current($student));
+ }
+
+ /**
+ * Saves data to a prestudent
+ *
+ * @param string $prestudent_id
+ * @return void
+ */
+ public function save($prestudent_id)
+ {
+ $studiensemester_kurzbz = $this->variablelib->getVar('semester_aktuell');
+
+ $this->load->model('person/Person_model', 'PersonModel');
+ $this->load->model('crm/Student_model', 'StudentModel');
+ $this->load->model('crm/Prestudent_model', 'PrestudentModel');
+ $this->load->model('education/Studentlehrverband_model', 'StudentlehrverbandModel');
+
+ $this->load->library('form_validation');
+
+ $this->form_validation->set_rules('gebdatum', 'Geburtsdatum', 'is_valid_date');
+
+ $this->form_validation->set_rules('semester', 'Semester', 'integer');
+
+ $this->load->library('UDFLib');
+
+ $result = $this->udflib->getCiValidations($this->PersonModel, $this->input->post());
+
+ #$fieldValidations = $this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result));
+ $fieldvalidations = $result->retval;
+
+ $this->form_validation->set_rules($fieldvalidations);
+
+ if (!$this->form_validation->run())
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $result = $this->StudentModel->loadWhere(['prestudent_id' => $prestudent_id]);
+
+ #$student = $this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ $student = $result->retval;
+
+ $uid = $student ? current($student)->student_uid : null;
+
+ $result = $this->PrestudentModel->loadWhere(['prestudent_id' => $prestudent_id]);
+
+ #$person = $this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result));
+ $person = $result->retval;
+
+ $person_id = $person ? current($person)->person_id : null;
+
+
+ $array_allowed_props_lehrverband = ['verband', 'semester', 'gruppe'];
+ $update_lehrverband = array();
+ foreach ($array_allowed_props_lehrverband as $prop) {
+ $val = $this->input->post($prop);
+ if ($val !== null) {
+ $update_lehrverband[$prop] = $val;
+ }
+ }
+
+ $array_allowed_props_person = [
+ 'anrede',
+ 'bpk',
+ 'titelpre',
+ 'titelpost',
+ 'nachname',
+ 'vorname',
+ 'vornamen',
+ 'wahlname',
+ 'gebdatum',
+ 'gebort',
+ 'geburtsnation',
+ 'svnr',
+ 'ersatzkennzeichen',
+ 'staatsbuergerschaft',
+ 'matr_nr',
+ 'sprache',
+ 'geschlecht',
+ 'familienstand',
+ 'foto',
+ 'anmerkung',
+ 'homepage'
+ ];
+
+ // add UDFs
+ $result = $this->udflib->getDefinitionForModel($this->PersonModel);
+
+ #$definitions = $this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ $definitions = $result->retval;
+
+ foreach ($definitions as $def)
+ $array_allowed_props_person[] = $def['name'];
+
+ $update_person = array();
+ foreach ($array_allowed_props_person as $prop) {
+ $val = $this->input->post($prop);
+ if ($val !== null) {
+ $update_person[$prop] = $val;
+ }
+ }
+
+ $array_allowed_props_student = ['matrikelnr'];
+ $update_student = array();
+ foreach ($array_allowed_props_student as $prop) {
+ $val = $this->input->post($prop);
+ if ($val !== null) {
+ $update_student[$prop] = $val;
+ }
+ }
+
+ // Check PKs
+ if (count($update_lehrverband) + count($update_student) && $uid === null) {
+ // TODO(chris): phrase
+ $this->terminateWithValidationErrors(['' => "Kein/e StudentIn vorhanden!"]);
+ }
+ if (count($update_person) && $person_id === null) {
+ // TODO(chris): phrase
+ $this->terminateWithValidationErrors(['' => "Keine Person vorhanden!"]);
+ }
+
+ // Do Updates
+ if (count($update_lehrverband)) {
+ $result = $this->StudentlehrverbandModel->update([
+ 'studiensemester_kurzbz' => $studiensemester_kurzbz,
+ 'student_uid' => $uid
+ ], $update_lehrverband);
+ #$this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+
+ if (count($update_person)) {
+ $result = $this->PersonModel->update(
+ $person_id,
+ $update_person
+ );
+ #$this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+
+
+ if (count($update_student)) {
+ $result = $this->StudentModel->update(
+ [$uid],
+ $update_student
+ );
+ #$this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+
+ $this->terminateWithSuccess(array_fill_keys(array_merge(
+ array_keys($update_lehrverband),
+ array_keys($update_person),
+ array_keys($update_student)
+ ), ''));
+ }
+
+ public function check()
+ {
+ $this->load->library('form_validation');
+
+ $this->form_validation->set_rules('gebdatum', 'Geburtsdatum', 'callback_isValidDate', [
+ 'isValidDate' => $this->p->t('ui', 'error_invalid_date')
+ ]);
+
+ if (!$this->form_validation->run())
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ $vorname = $this->input->post('vorname');
+ $nachname = $this->input->post('nachname');
+ $gebdatum = $this->input->post('gebdatum');
+
+ if (!$vorname && !$nachname && !$gebdatum)
+ $this->terminateWithValidationErrors(['At least one of vorname, nachname or gebdatum must be set']);
+
+ $this->load->model('person/Person_model', 'PersonModel');
+
+ if ($gebdatum)
+ $this->PersonModel->db->where('gebdatum', (new DateTime($gebdatum))->format('Y-m-d'));
+ if ($vorname && $nachname) {
+ $this->PersonModel->db->or_group_start();
+ $this->PersonModel->db->where('LOWER(nachname)', 'LOWER(' . $this->PersonModel->db->escape($nachname) . ')', false);
+ $this->PersonModel->db->where('LOWER(vorname)', 'LOWER(' . $this->PersonModel->db->escape($vorname) . ')', false);
+ $this->PersonModel->db->group_end();
+ } elseif ($nachname) {
+ $this->PersonModel->db->or_where('LOWER(nachname)', 'LOWER(' . $this->PersonModel->escape($nachname) . ')', false);
+ }
+
+ $result = $this->PersonModel->load();
+
+ #$data = $this->getDataOrTerminateWithError($result);
+ if (isError($result))
+ $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ $data = $result->retval;
+
+ $this->terminateWithSuccess($data);
+ }
+
+ public function add()
+ {
+ if (!$this->input->post('person_id')) {
+ if (!isset($_POST['address']) || !is_array($_POST['address']))
+ $_POST['address'] = [];
+ $_POST['address']['func'] = 1;
+ }
+ if ($this->input->post('incoming')) {
+ $_POST['ausbildungssemester'] = 0;
+ }
+
+ $this->load->library('form_validation');
+
+ $this->form_validation->set_rules('nachname', 'Nachname', 'callback_requiredIfNotPersonId', [
+ 'requiredIfNotPersonId' => $this->p->t('ui', 'error_required')
+ ]);
+ $this->form_validation->set_rules('geschlecht', 'Geschlecht', 'callback_requiredIfNotPersonId', [
+ 'requiredIfNotPersonId' => $this->p->t('ui', 'error_required')
+ ]);
+ $this->form_validation->set_rules('gebdatum', 'Geburtsdatum', 'callback_isValidDate', [
+ 'isValidDate' => $this->p->t('ui', 'error_invalid_date')
+ ]);
+ $this->form_validation->set_rules('address[func]', 'Address', 'required|integer|less_than[2]|greater_than[-2]');
+ $this->form_validation->set_rules('address[plz]', 'PLZ', 'callback_requiredIfAddressFunc', [
+ 'requiredIfAddressFunc' => $this->p->t('ui', 'error_required')
+ ]);
+ $this->form_validation->set_rules('address[gemeinde]', 'Gemeinde', 'callback_requiredIfAddressFunc', [
+ 'requiredIfAddressFunc' => $this->p->t('ui', 'error_required')
+ ]);
+ $this->form_validation->set_rules('address[ort]', 'Ort', 'callback_requiredIfAddressFunc', [
+ 'requiredIfAddressFunc' => $this->p->t('ui', 'error_required')
+ ]);
+ $this->form_validation->set_rules('address[address]', 'Adresse', 'callback_requiredIfAddressFunc', [
+ 'requiredIfAddressFunc' => $this->p->t('ui', 'error_required')
+ ]);
+ $this->form_validation->set_rules('email', 'E-Mail', 'valid_email');
+ $this->form_validation->set_rules('studiengang_kz', 'Studiengang', 'required');
+ $this->form_validation->set_rules('studiensemester_kurzbz', 'Studiensemester', 'required');
+ $this->form_validation->set_rules('ausbildungssemester', 'Ausbildungssemester', 'required|integer|less_than[9]|greater_than[-1]');
+ // TODO(chris): validate studienplan with studiengang, semester and orgform?
+ // TODO(chris): validate person_id, studiengang_kz, studiensemester_kurzbz, orgform_kurzbz, nation, gemeinde, ort, geschlecht?
+
+ if (!$this->form_validation->run())
+ $this->terminateWithValidationErrors($this->form_validation->error_array());
+
+ // TODO(chris): This should be in a library
+ $this->load->model('crm/Prestudent_model', 'PrestudentModel');
+ $this->load->model('crm/Prestudentstatus_model', 'PrestudentstatusModel');
+
+ $this->db->trans_start();
+
+ $result = $this->addInteressent();
+
+ $this->db->trans_complete();
+
+ if ($this->db->trans_status() === FALSE)
+ $this->terminateWithError('TODO(chris): TEXT', self::ERROR_TYPE_GENERAL);
+
+ $this->terminateWithSuccess($result);
+ }
+
+ protected function addInteressent()
+ {
+ // Person anlegen wenn nötig
+ $person_id = $this->input->post('person_id');
+ if (!$person_id) {
+ $this->load->model('person/Person_model', 'PersonModel');
+
+ $data = [
+ 'nachname' => $this->input->post('nachname'),
+ 'insertamum' => date('c'),
+ 'insertvon' => getAuthUID(),
+ 'zugangscode' => uniqid(),
+ 'aktiv' => true
+ ];
+ if ($this->input->post('anrede'))
+ $data['anrede'] = $this->input->post('anrede');
+ if ($this->input->post('titelpre'))
+ $data['titelpre'] = $this->input->post('titelpre');
+ if ($this->input->post('titelpost'))
+ $data['titelpost'] = $this->input->post('titelpost');
+ if ($this->input->post('vorname'))
+ $data['vorname'] = $this->input->post('vorname');
+ if ($this->input->post('vornamen'))
+ $data['vornamen'] = $this->input->post('vornamen');
+ if ($this->input->post('wahlname'))
+ $data['wahlname'] = $this->input->post('wahlname');
+ if ($this->input->post('geschlecht'))
+ $data['geschlecht'] = $this->input->post('geschlecht');
+ if ($this->input->post('gebdatum'))
+ $data['gebdatum'] = (new DateTime($this->input->post('datum_obj')))->format('Y-m-d');
+ if ($this->input->post('geburtsnation'))
+ $data['geburtsnation'] = $this->input->post('geburtsnation');
+ if ($this->input->post('staatsbuergerschaft'))
+ $data['staatsbuergerschaft'] = $this->input->post('staatsbuergerschaft');
+
+ $result = $this->PersonModel->insert($data);
+ if (isError($result))
+ return $result;
+ $person_id = getData($result);
+ }
+
+ // Addresse anlegen
+ $anlegen = $this->input->post('address[func]');
+ if ($anlegen) {
+ $this->load->model('person/Adresse_model', 'AdresseModel');
+
+ $data = [
+ 'nation' => $this->input->post('address[nation]'),
+ 'strasse' => $this->input->post('address[address]'),
+ 'plz' => $this->input->post('address[plz]'),
+ 'ort' => $this->input->post('address[ort]'),
+ 'gemeinde' => $this->input->post('address[gemeinde]'),
+ 'typ' => 'h',
+ 'zustelladresse' => true,
+ ];
+ if ($anlegen < 0) { // Überschreiben
+ $this->AdresseModel->addOrder('zustelladresse', 'DESC');
+ $this->AdresseModel->addOrder('sort');
+ $result = $this->AdresseModel->loadWhere([
+ 'person_id' => $person_id
+ ]);
+ if (isError($result))
+ return $result;
+ if (hasData($result)) {
+ $address = current(getData($result));
+
+ $data['updateamum'] = date('c');
+ $data['updatevon'] = getAuthUID();
+
+ $result = $this->AdresseModel->update($address->adresse_id, $data);
+ if (isError($result))
+ return $result;
+ } else {
+ //Wenn keine Adrese vorhanden ist dann eine neue Anlegen
+ $anlegen = 1;
+ $data['heimatadresse'] = true;
+ }
+ }
+ if ($anlegen > 0) {
+ $data['person_id'] = $person_id;
+ $data['insertamum'] = date('c');
+ $data['insertvon'] = getAuthUID();
+ if (!isset($data['heimatadresse']))
+ $data['heimatadresse'] = !$this->input->post('person_id');
+
+ $result = $this->AdresseModel->insert($data);
+ if (isError($result))
+ return $result;
+ }
+ }
+
+ // Kontaktdaten
+ $kontaktdaten = [];
+ foreach (['email', 'telefon', 'mobil'] as $k) {
+ $v = $this->input->post($k);
+ if ($v)
+ $kontaktdaten[$k] = $v;
+ }
+ if (count($kontaktdaten)) {
+ $this->load->model('person/Kontakt_model', 'KontaktModel');
+
+ foreach ($kontaktdaten as $typ => $kontakt) {
+ $data = [
+ 'person_id' => $person_id,
+ 'kontakttyp' => $typ,
+ 'kontakt' => $kontakt,
+ 'zustellung' => true,
+ 'insertamum' => date('c'),
+ 'insertvon' => getAuthUID()
+ ];
+ $result = $this->KontaktModel->insert($data);
+ if (isError($result))
+ return $result;
+ }
+ }
+
+ // Prestudent anlegen
+ $data = [
+ 'aufmerksamdurch_kurzbz' => 'k.A.',
+ 'person_id' => $person_id,
+ 'studiengang_kz' => $this->input->post('studiengang_kz'),
+ 'ausbildungcode' => $this->input->post('letzteausbildung'),
+ 'anmerkung' => $this->input->post('anmerkungen'),
+ 'reihungstestangetreten' => false,
+ 'bismelden' => true
+ ];
+ $ausbildungsart = $this->input->post('ausbildungsart');
+ if ($ausbildungsart)
+ $data['anmerkung'] .= ' Ausbildungsart:' . $ausbildungsart;
+ // Incomings und ausserordentliche sind bei Meldung nicht förderrelevant
+ $incoming = $this->input->post('incoming');
+ if ($incoming || substr($data['studiengang_kz'], 0, 1) == '9')
+ $data['foerderrelevant'] = false;
+ // Wenn die Person schon im System erfasst ist, dann die ZGV des Datensatzes uebernehmen
+ $this->PrestudentModel->addOrder('zgvmas_code');
+ $this->PrestudentModel->addOrder('zgv_code', 'DESC');
+ $this->PrestudentModel->addLimit(1);
+ $result = $this->PrestudentModel->loadWhere([
+ 'person_id' => $person_id
+ ]);
+ if (isError($result))
+ return $result;
+ if (hasData($result)) {
+ $prestudent = current(getData($result));
+ if ($prestudent->zgv_code) {
+ $data['zgv_code'] = $prestudent->zgv_code;
+ $data['zgvort'] = $prestudent->zgvort;
+ $data['zgvdatum'] = $prestudent->zgvdatum;
+
+ $data['zgvmas_code'] = $prestudent->zgvmas_code;
+ $data['zgvmaort'] = $prestudent->zgvmaort;
+ $data['zgvmadatum'] = $prestudent->zgvmadatum;
+ }
+ }
+ // Prestudent speichern
+ $result = $this->PrestudentModel->insert($data);
+ if (isError($result))
+ return $result;
+ $prestudent_id = getData($result);
+
+ // Prestudent Rolle Anlegen
+ $data = [
+ 'prestudent_id' => $prestudent_id,
+ 'status_kurzbz' => $incoming ? 'Incoming' : 'Interessent',
+ 'studiensemester_kurzbz' => $this->input->post('studiensemester_kurzbz'),
+ 'ausbildungssemester' => $this->input->post('ausbildungssemester') ?: 0,
+ 'orgform_kurzbz' => $this->input->post('orgform_kurzbz') ?: null,
+ 'studienplan_id' => $this->input->post('studienplan_id') ?: null,
+ 'datum' => date('Y-m-d'),
+ 'insertamum' => date('c'),
+ 'insertvon' => getAuthUID()
+ ];
+ $result = $this->PrestudentstatusModel->insert($data);
+ if (isError($result))
+ return $result;
+
+ if ($incoming) {
+ // TODO(chris): IMPLEMENT!
+ //Matrikelnummer und UID generieren
+ //Benutzerdatensatz anlegen
+ //Studentendatensatz anlegen
+ //StudentLehrverband anlegen
+ }
+
+ // TODO(chris): DEBUG
+ /*$result = $this->PrestudentModel->loadWhere([
+ 'pestudent_id' => 1
+ ]);
+ if (isError($result)) {
+ return $result;
+ }*/
+
+ return success(true);
+ }
+
+ public function requiredIfNotPersonId($value)
+ {
+ if (isset($_POST['person_id']))
+ return true;
+ return !!$value;
+ }
+
+ public function requiredIfAddressFunc($value)
+ {
+ if (!$_POST['address']['func'])
+ return true;
+ return !!$value;
+ }
+}
diff --git a/application/core/DB_Model.php b/application/core/DB_Model.php
index 79040dc66..c799de516 100644
--- a/application/core/DB_Model.php
+++ b/application/core/DB_Model.php
@@ -827,6 +827,23 @@ class DB_Model extends CI_Model
return $result;
}
+ public function getDbTable()
+ {
+ return $this->dbTable;
+ }
+
+ public function getPk()
+ {
+ return $this->pk;
+ }
+
+ public function getPks()
+ {
+ if (is_array($this->pk))
+ return $this->pk;
+ return [$this->pk];
+ }
+
// ------------------------------------------------------------------------------------------
// Protected methods
diff --git a/application/helpers/hlp_common_helper.php b/application/helpers/hlp_common_helper.php
index 3e682e56c..688fae965 100644
--- a/application/helpers/hlp_common_helper.php
+++ b/application/helpers/hlp_common_helper.php
@@ -422,3 +422,41 @@ function isValidDate($dateString)
return false;
}
}
+
+
+// ------------------------------------------------------------------------
+// Collection of utility functions for form validation purposes
+// ------------------------------------------------------------------------
+
+/**
+ * check if string can be converted to a date
+ */
+function is_valid_date($dateString)
+{
+ try
+ {
+ return (new DateTime($dateString)) !== false;
+ }
+ catch(Exception $e)
+ {
+ return false;
+ }
+}
+
+/**
+ * check if given permissions are met
+ */
+function has_write_permissions($value, $permissions = '')
+{
+ if (!$permissions)
+ $permissions = $value; // TODO(chris): ??
+ $permissions = explode(',', $permissions);
+
+ $CI =& get_instance();
+ $CI->load->library('PermissionLib');
+ return $CI->permissionlib->hasAtLeastOne(
+ $permissions,
+ 'sometable',
+ 'w'
+ );
+}
diff --git a/application/language/english/form_validation_lang.php b/application/language/english/form_validation_lang.php
new file mode 100644
index 000000000..9e942f8e5
--- /dev/null
+++ b/application/language/english/form_validation_lang.php
@@ -0,0 +1,42 @@
+getDbTable();
+ if (!isset($this->_definition_cache[$dbTable])) {
+ $this->_ci->load->model('system/UDF_model', 'UDFModel');
+ list($schema, $table) = explode('.', $dbTable);
+ $result = $this->_ci->UDFModel->loadWhere([
+ 'schema' => $schema,
+ 'table' => $table
+ ]);
+ if (isError($result))
+ return $result;
+
+ $this->_definition_cache[$dbTable] = json_decode(current($result->retval)->jsons, true);
+ }
+ return success($this->_definition_cache[$dbTable]);
+ }
+
+ /**
+ * Gets the UDFs for db entry with translated params and resolved listValues for dropdowns
+ *
+ * @param DB_Model $targetModel
+ * @param mixed $id
+ *
+ * @return stdClass
+ */
+ public function getFieldArray($targetModel, $id)
+ {
+ // Load Libraries
+ $this->_ci->load->library('PhrasesLib');
+ $this->_ci->load->library('PermissionLib');
+
+ $result = $this->getDefinitionForModel($targetModel);
+ if (isError($result))
+ return $result;
+ $definitions = getData($result);
+
+ usort($definitions, function ($a, $b) {
+ return $a[self::SORT] - $b[self::SORT];
+ });
+
+ $values = $targetModel->getUDFs($id);
+
+ $fields = [];
+ foreach ($definitions as $field) {
+ // check read permissions
+ if (!$this->_ci->permissionlib->hasAtLeastOne(
+ $field[self::REQUIRED_PERMISSIONS_PARAMETER],
+ self::PERMISSION_TABLE_METHOD,
+ self::PERMISSION_TYPE_READ
+ ))
+ continue;
+
+ // set value
+ if (isset($values[$field[self::NAME]])) {
+ $field['value'] = $values[$field[self::NAME]];
+ } elseif (isset($field['defaultValue'])) {
+ $field['value'] = $field['defaultValue'];
+ } elseif (isset($field[self::TYPE]) && $field[self::TYPE] == 'checkbox') {
+ $field['value'] = false;
+ } else {
+ $field['value'] = '';
+ }
+
+ // translate params
+ foreach ([self::LABEL, self::TITLE, self::PLACEHOLDER] as $key) {
+ if (isset($field[$key])) {
+ $res = $this->_ci->phraseslib->getPhrases(self::PHRASES_APP_NAME, getUserLanguage(), $field[$key], null, null, 'no');
+ if (hasData($res))
+ $field[$key] = current(getData($res))->text;
+ }
+ }
+
+ // check write permissions
+ $field['disabled'] = !$this->_ci->permissionlib->hasAtLeastOne(
+ $field[self::REQUIRED_PERMISSIONS_PARAMETER],
+ self::PERMISSION_TABLE_METHOD,
+ self::PERMISSION_TYPE_WRITE
+ );
+
+ // set listValues for dropdowns
+ if (isset($field[self::LIST_VALUES])) {
+ if (isset($field[self::LIST_VALUES]['enum'])) {
+ $field['options'] = $field[self::LIST_VALUES]['enum'];
+ } elseif (isset($field[self::LIST_VALUES]['sql'])) {
+ $res = $this->_ci->UDFModel->execReadOnlyQuery($field[self::LIST_VALUES]['sql']);
+ $field['options'] = hasData($res) ? getData($res) : [];
+ }
+ }
+
+ // add to array
+ $fields[] = $field;
+ }
+ return success($fields);
+ }
+
+ /**
+ * Gets a validation config array for CI form_validation
+ *
+ * @param DB_Model $targetModel
+ * @param array (optional) $filter
+ *
+ * @return stdClass
+ */
+ public function getCiValidations($targetModel, $filter = null)
+ {
+ $result = $this->getDefinitionForModel($targetModel);
+ if (isError($result))
+ return $result;
+ $definitions = getData($result);
+
+ $result = [];
+ foreach ($definitions as $def) {
+ if ($filter && !isset($filter[$def['name']]))
+ continue;
+ $validations = [];
+ if (isset($def['requiredPermissions']))
+ $validations[] = 'has_write_permissions[' . implode(',', $def['requiredPermissions']) . ']';
+ if (isset($def['required']))
+ $validations[] = 'required';
+ if (isset($def['validation'])) {
+ if (isset($def['validation']['max-value']))
+ $validations[] = 'less_than_equal_to[' . $def['validation']['max-value'] . ']';
+ if (isset($def['validation']['min-value']))
+ $validations[] = 'greater_than_equal_to[' . $def['validation']['min-value'] . ']';
+ if (isset($def['validation']['max-length']))
+ $validations[] = 'max_length[' . $def['validation']['max-length'] . ']';
+ if (isset($def['validation']['min-length']))
+ $validations[] = 'min_length[' . $def['validation']['min-length'] . ']';
+ if (isset($def['validation']['regex']) && is_array($def['validation']['regex'])) {
+ foreach ($def['validation']['regex'] as $regex) {
+ if ($regex['language'] == 'php') {
+ $validations[] = 'regex_match[' . $regex['expression'] . ']';
+ }
+ }
+ }
+ }
+ if ($validations)
+ $result[] = [
+ 'field' => $def['name'],
+ 'label' => $def['title'],
+ 'rules' => $validations
+ ];
+ }
+ return success($result);
+ }
+
// -------------------------------------------------------------------------------------------------
// Private methods
//
diff --git a/public/js/components/Fetch.js b/public/js/components/Fetch.js
index 9a34e1a3f..f683c0655 100644
--- a/public/js/components/Fetch.js
+++ b/public/js/components/Fetch.js
@@ -99,7 +99,7 @@ export const CoreFetchCmpt = {
*
*/
errorHandler: function(error) {
- if (error.response.data.retval)
+ if (error.response?.data?.retval)
this.setError(error.response.data.retval);
else
this.setError(error.message);
diff --git a/public/js/components/Stv/Studentenverwaltung/Details/Details.js b/public/js/components/Stv/Studentenverwaltung/Details/Details.js
index 0c988179f..7866f1c97 100644
--- a/public/js/components/Stv/Studentenverwaltung/Details/Details.js
+++ b/public/js/components/Stv/Studentenverwaltung/Details/Details.js
@@ -1,14 +1,17 @@
import {CoreRESTClient} from '../../../../RESTClient.js';
-import FormForm from '../../../Form/Form.js';
+import CoreForm from '../../../Form/Form.js';
import FormInput from '../../../Form/Input.js';
import FormUploadImage from '../../../Form/Upload/Image.js';
+import CoreUdf from '../../../Udf/Udf.js';
+
export default {
components: {
- FormForm,
+ CoreForm,
FormInput,
- FormUploadImage
+ FormUploadImage,
+ CoreUdf
},
inject: {
showBpk: {
@@ -39,6 +42,7 @@ export default {
},
data() {
return {
+ test: {udf_viaf: 'TEST'},
familienstaende: {
"": "--keine Auswahl--",
"g": "geschieden",
@@ -49,6 +53,7 @@ export default {
original: null,
data: null,
changed: {},
+ udfChanges: false,
studentIn: null,
gebDatumIsValid: false,
gebDatumIsInvalid: false
@@ -90,25 +95,33 @@ export default {
},
methods: {
updateStudent(n) {
- CoreRESTClient
- .get('components/stv/Student/get/' + n.prestudent_id)
- .then(result => result.data)
+ // TODO(chris): move to fhcapi.factory
+ this.$fhcApi
+ .get('api/frontend/v1/stv/student/get/' + n.prestudent_id)
.then(result => {
- this.data = result;
+ this.data = result.data;
if (!this.data.familienstand)
this.data.familienstand = '';
- this.original = {...this.data};
+ this.original = {...(this.original || {}), ...this.data};
})
.catch(this.$fhcAlert.handleSystemError);
},
save() {
+ if (!this.changedLength)
+ return;
+
+ this.$refs.form.clearValidation();
this.$refs.form
- .send(CoreRESTClient.post('components/stv/Student/save/' + this.modelValue.prestudent_id, this.changed))
+ .post('api/frontend/v1/stv/student/save/' + this.modelValue.prestudent_id, this.changed)
.then(result => {
this.original = {...this.data};
this.changed = {};
+ this.$refs.form.setFeedback(true, result.data);
})
- .catch(this.$fhcAlert.handleSystemError);
+ .catch(this.$fhcAlert.handleSystemError)
+ },
+ udfsLoaded(udfs) {
+ this.original = {...(this.original || {}), ...udfs};
}
},
created() {
@@ -117,7 +130,7 @@ export default {
//TODO(chris): Phrasen
//TODO(chris): Geburtszeit? Anzahl der Kinder?
template: `
-