diff --git a/application/controllers/api/frontend/v1/fotoHandling/Foto.php b/application/controllers/api/frontend/v1/fotoHandling/Foto.php
new file mode 100644
index 000000000..4945ddd85
--- /dev/null
+++ b/application/controllers/api/frontend/v1/fotoHandling/Foto.php
@@ -0,0 +1,237 @@
+ ['admin:r', 'assistenz:r'],
+ 'deleteFoto' => ['admin:r', 'assistenz:r'],
+ ]);
+
+ //Load Models and Libraries
+ $this->load->model('person/Person_model', 'PersonModel');
+ $this->load->model("crm/Akte_model", "AkteModel");
+ $this->load->model('person/Fotostatusperson_model', 'FotostatusPersonModel');
+
+ $this->loadPhrases([
+ 'ui',
+ 'header'
+ ]);
+ }
+
+ public function uploadFoto($person_id)
+ {
+ if(!$person_id)
+ {
+ return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Person_id']), self::ERROR_TYPE_GENERAL);
+ }
+
+ $data = json_decode(file_get_contents("php://input"), true);
+
+ if (!empty($data['image']))
+ {
+ $base64 = $data['image'];
+ $resizedImage1 = $this->_resize($base64, 827, 1063);
+
+ if (is_null($resizedImage1))
+ return $this->terminateWithError($this->p->t('header', 'error_fotoupload'), self::ERROR_TYPE_GENERAL);
+
+ $akte = $this->AkteModel->loadWhere(array('person_id' => $person_id, 'dokument_kurzbz' => 'Lichtbil'));
+
+ $akteUpdateData = array(
+ 'dokument_kurzbz' => 'Lichtbil',
+ 'person_id' => $person_id,
+ 'inhalt' => $resizedImage1,
+ 'mimetype' => 'image/jpg',
+ 'erstelltam' => date('c'),
+ 'gedruckt' => false,
+ 'titel' => 'Lichtbild_' . $person_id . '.jpg',
+ 'bezeichnung' => 'Lichtbild gross',
+ 'insertamum' => date('c'),
+ 'insertvon' => getAuthUID(),
+ );
+
+ if (hasData($akte)) {
+ $akte_id = getData($akte)[0]->akte_id;
+
+ $akteUpdateData['updateamum'] = date('c');
+ $akteUpdateData['updatevon'] = getAuthUID();
+ $akteResult = $this->AkteModel->update(array('akte_id' => $akte_id), $akteUpdateData);
+ } else {
+ $akteResult = $this->AkteModel->insert($akteUpdateData);
+ }
+
+ if (isError($akteResult)) {
+ return $this->terminateWithError(getError($akteResult), self::ERROR_TYPE_GENERAL);
+ }
+
+ $resizedImage2 = $this->_resize($base64, 101, 130);
+
+ if (is_null($resizedImage2))
+ return $this->terminateWithError($this->p->t('header', 'error_fotoupload'), self::ERROR_TYPE_GENERAL);
+
+ $result = $this->_updateFoto($person_id, $resizedImage2);
+
+ if (!isError($result)) {
+ $this->FotostatusPersonModel->insert(array(
+ 'person_id' => $person_id,
+ 'fotostatus_kurzbz' => 'hochgeladen',
+ 'datum' => date('Y-m-d'),
+ 'updateamum' => date('c'),
+ 'updatevon' => getAuthUID(),
+ 'insertamum' => date('c'),
+ 'insertvon' => getAuthUID(),
+ ));
+
+ return $this->terminateWithSuccess($base64);
+ }
+ }
+ else
+ {
+ $this->terminateWithError($this->p->t('header', 'error_noPhoto'), self::ERROR_TYPE_GENERAL);
+ }
+ }
+
+ public function deleteFoto($person_id)
+ {
+ if(!$person_id)
+ {
+ return $this->terminateWithError($this->p->t('ui', 'error_missingId', ['id'=> 'Person_id']), self::ERROR_TYPE_GENERAL);
+ }
+
+ $result = $this->_deleteFoto($person_id);
+
+ if (isError($result))
+ {
+ return $this->terminateWithError(getError($result), self::ERROR_TYPE_GENERAL);
+ }
+ return $this->terminateWithSuccess($result);
+ }
+
+ private function _resize($imageData, $maxwidth, $maxheight, $quality = 90)
+ {
+ $meta = getimagesize($imageData);
+ if (!$meta)
+ {
+ return null;
+ }
+
+ $src_width = $meta[0];
+ $src_height = $meta[1];
+ $mime = $meta['mime'];
+
+ switch ($mime) {
+ case 'image/jpeg':
+ case 'image/jpg':
+ $imagecreated = imagecreatefromjpeg($imageData);
+ break;
+ case 'image/png':
+ $imagecreated = imagecreatefrompng($imageData);
+ break;
+ case 'image/gif':
+ $imagecreated = imagecreatefromgif($imageData);
+ break;
+ default:
+ return null;
+ }
+
+
+ if (!$imagecreated)
+ {
+ return null;
+ }
+
+ $src_aspect_ratio = $src_width / $src_height;
+ $thu_aspect_ratio = $maxwidth / $maxheight;
+
+ if ($src_width <= $maxwidth && $src_height <= $maxheight)
+ {
+ $thu_width = $src_width;
+ $thu_height = $src_height;
+ }
+ elseif ($thu_aspect_ratio > $src_aspect_ratio)
+ {
+ $thu_width = (int) ($maxheight * $src_aspect_ratio);
+ $thu_height = $maxheight;
+ }
+ else
+ {
+ $thu_width = $maxwidth;
+ $thu_height = (int) ($maxwidth / $src_aspect_ratio);
+ }
+
+ $imageScaled = imagecreatetruecolor($thu_width, $thu_height);
+
+ if ($mime === 'image/png')
+ {
+ $background = imagecolorallocate($imageScaled , 0, 0, 0);
+ imagecolortransparent($imageScaled, $background);
+ imagealphablending($imageScaled, false);
+ imagesavealpha($imageScaled, true);
+ }
+
+ imagecopyresampled($imageScaled, $imagecreated, 0, 0, 0, 0, $thu_width, $thu_height, $src_width, $src_height);
+
+ if ($mime === "image/gif")
+ {
+ $background = imagecolorallocate($imageScaled, 0, 0, 0);
+ imagecolortransparent($imageScaled, $background);
+ }
+
+ if (!empty($imageScaled))
+ {
+ ob_start();
+
+ if ($mime == 'image/png')
+ imagepng($imageScaled, NULL);
+ else if ($mime === 'image/gif')
+ imagegif($imageScaled, NULL);
+ else
+ imagejpeg($imageScaled, NULL, $quality);
+
+ $resizedImageData = ob_get_contents();
+ ob_end_clean();
+ @imagedestroy($imagecreated);
+ @imagedestroy($imageScaled);
+
+
+ if (!empty($resizedImageData))
+ {
+ return base64_encode($resizedImageData);
+ }
+ return null;
+ }
+ return null;
+ }
+
+ private function _updateFoto($person_id, $foto)
+ {
+ $personJson['foto'] = $foto;
+ $result = $this->PersonModel->update($person_id, $personJson);
+
+ if (isError($result))
+ {
+ return error($result->msg, EXIT_ERROR);
+ }
+
+ return $result;
+ }
+
+ private function _deleteFoto($person_id)
+ {
+ $personJson['foto'] = null;
+ $result = $this->PersonModel->update($person_id, $personJson);
+
+ if (isError($result))
+ {
+ return error($result->msg, EXIT_ERROR);
+ }
+
+ return $result;
+ }
+}
diff --git a/application/models/person/Person_model.php b/application/models/person/Person_model.php
index 997048972..35a0f6144 100644
--- a/application/models/person/Person_model.php
+++ b/application/models/person/Person_model.php
@@ -423,4 +423,8 @@ class Person_model extends DB_Model
return success($result);
}
}
+
+
+
+
}
\ No newline at end of file
diff --git a/public/css/Studentenverwaltung.css b/public/css/Studentenverwaltung.css
index bb2588926..05b28d314 100644
--- a/public/css/Studentenverwaltung.css
+++ b/public/css/Studentenverwaltung.css
@@ -158,4 +158,8 @@ html {
.tiny-90 div.tox.tox-tinymce {
height: 90% !important;
+}
+
+.foto-container:hover .fotoedit {
+ opacity: 1 !important;
}
\ No newline at end of file
diff --git a/public/css/Vertragsverwaltung.css b/public/css/Vertragsverwaltung.css
index 7b2d71481..300ee5d55 100644
--- a/public/css/Vertragsverwaltung.css
+++ b/public/css/Vertragsverwaltung.css
@@ -18,3 +18,7 @@ html {
.vv {
margin-left: 0 !important;
}
+
+.foto-container:hover .fotoedit {
+ opacity: 1 !important;
+}
diff --git a/public/js/api/factory/fotoHandling.js b/public/js/api/factory/fotoHandling.js
new file mode 100644
index 000000000..8fffaef07
--- /dev/null
+++ b/public/js/api/factory/fotoHandling.js
@@ -0,0 +1,32 @@
+/**
+ * Copyright (C) 2025 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