prefix . AEROMC_FRIENDS_CAPES_TABLE; } function aeromc_friends_cape_dir() { $uploads = wp_upload_dir(); return [ 'pfad' => trailingslashit($uploads['basedir']) . AEROMC_FRIENDS_CAPE_DIR, 'url' => trailingslashit($uploads['baseurl']) . AEROMC_FRIENDS_CAPE_DIR, ]; } /** Datei eines Umhangs löschen (nur Namen in unserem Schema). */ function aeromc_friends_cape_delete_file($datei) { if (!$datei || !preg_match('/^[a-f0-9]{32}-[a-f0-9]{8}\.png$/', (string) $datei)) { return; } $ort = aeromc_friends_cape_dir(); $voll = $ort['pfad'] . '/' . $datei; if (file_exists($voll)) { @unlink($voll); } } /** Zeile -> öffentliche Angaben */ function aeromc_friends_cape_info($r) { $ort = aeromc_friends_cape_dir(); return [ 'url' => $ort['url'] . '/' . $r->datei, 'width' => (int) $r->breite, 'height' => (int) $r->hoehe, 'hash' => $r->hash, 'updated' => $r->updated_at, ]; } /** * Prüft ein Umhang-Bild und gibt [daten, breite, hoehe] zurück oder WP_Error. */ function aeromc_friends_cape_check_image($base64) { $daten = base64_decode(preg_replace('/^data:image\/png;base64,/', '', (string) $base64), true); if ($daten === false || strlen($daten) < 60) { return new WP_Error('aeromc_invalid', 'Ungültiges Bild.', ['status' => 400]); } if (strlen($daten) > AEROMC_FRIENDS_CAPE_MAX_BYTES) { return new WP_Error('aeromc_too_big', 'Der Umhang ist zu groß (Datei).', ['status' => 413]); } if (strncmp($daten, "\x89PNG\r\n\x1A\n", 8) !== 0 || substr($daten, 12, 4) !== 'IHDR') { return new WP_Error('aeromc_invalid', 'Nur PNG-Bilder.', ['status' => 400]); } $kopf = unpack('Nbreite/Nhoehe', substr($daten, 16, 8)); $breite = (int) $kopf['breite']; $hoehe = (int) $kopf['hoehe']; if (!in_array($breite, [64, 128, 256, 512, 1024], true) || $breite !== 2 * $hoehe) { return new WP_Error('aeromc_invalid', 'Ein Umhang muss 64×32, 128×64, 256×128, 512×256 oder 1024×512 Pixel groß sein.', ['status' => 400]); } // neu kodieren: entfernt alles außer den Bildpunkten if (function_exists('imagecreatefromstring')) { $bild = @imagecreatefromstring($daten); if (!$bild || imagesx($bild) !== $breite || imagesy($bild) !== $hoehe) { return new WP_Error('aeromc_invalid', 'Das Bild ist beschädigt.', ['status' => 400]); } imagealphablending($bild, false); imagesavealpha($bild, true); ob_start(); imagepng($bild, null, 9); $daten = ob_get_clean(); imagedestroy($bild); } return [$daten, $breite, $hoehe]; } add_action('rest_api_init', function () { register_rest_route('aeromc/v1', '/cape', [ [ 'methods' => 'POST', 'callback' => 'aeromc_friends_route_cape_upload', 'permission_callback' => '__return_true', ], [ 'methods' => 'GET', 'callback' => 'aeromc_friends_route_cape_get', 'permission_callback' => '__return_true', ], ]); register_rest_route('aeromc/v1', '/cape/remove', [ 'methods' => 'POST', 'callback' => 'aeromc_friends_route_cape_remove', 'permission_callback' => '__return_true', ]); register_rest_route('aeromc/v1', '/capes', [ 'methods' => 'GET', 'callback' => 'aeromc_friends_route_capes', 'permission_callback' => '__return_true', ]); }); /** Anmeldung ist hier immer Pflicht – auch im Übergangsmodus (öffentlich sichtbare Bilder). */ function aeromc_friends_cape_auth($request, $uuid) { $sitzung = aeromc_friends_check_auth($request, $uuid); if (is_wp_error($sitzung)) { return $sitzung; } if (!$sitzung) { return new WP_Error('aeromc_auth', 'Anmeldung erforderlich.', ['status' => 401]); } return $sitzung; } function aeromc_friends_route_cape_upload($request) { global $wpdb; $uuid = aeromc_friends_normalize_uuid($request->get_param('uuid')); if (!$uuid) { return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]); } $sitzung = aeromc_friends_cape_auth($request, $uuid); if (is_wp_error($sitzung)) { return $sitzung; } $table = aeromc_friends_capes_table(); $alt = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE uuid = %s", $uuid)); if ($alt && $alt->status === 'gesperrt') { return new WP_Error('aeromc_blocked', 'Dein Umhang wurde vom Betreiber gesperrt.', ['status' => 403]); } if ($alt && time() - strtotime($alt->updated_at . ' UTC') < AEROMC_FRIENDS_CAPE_MIN_GAP) { return new WP_Error('aeromc_too_fast', 'Bitte einen Moment warten, bevor du den Umhang erneut hochlädst.', ['status' => 429]); } $geprueft = aeromc_friends_cape_check_image($request->get_param('image')); if (is_wp_error($geprueft)) { return $geprueft; } list($daten, $breite, $hoehe) = $geprueft; $hash = sha1($daten); $ort = aeromc_friends_cape_dir(); if (!wp_mkdir_p($ort['pfad'])) { return new WP_Error('aeromc_error', 'Umhang konnte nicht gespeichert werden.', ['status' => 500]); } if (!file_exists($ort['pfad'] . '/index.html')) { file_put_contents($ort['pfad'] . '/index.html', ''); } $datei = $uuid . '-' . substr($hash, 0, 8) . '.png'; if (file_put_contents($ort['pfad'] . '/' . $datei, $daten) === false) { return new WP_Error('aeromc_error', 'Umhang konnte nicht gespeichert werden.', ['status' => 500]); } if ($alt && $alt->datei !== $datei) { aeromc_friends_cape_delete_file($alt->datei); } $zeile = [ 'uuid' => $uuid, 'username' => $sitzung->username, 'datei' => $datei, 'breite' => $breite, 'hoehe' => $hoehe, 'hash' => $hash, 'status' => 'aktiv', 'updated_at' => current_time('mysql', true), ]; $wpdb->replace($table, $zeile); return ['ok' => true, 'cape' => aeromc_friends_cape_info((object) $zeile)]; } function aeromc_friends_route_cape_remove($request) { global $wpdb; $uuid = aeromc_friends_normalize_uuid($request->get_param('uuid')); if (!$uuid) { return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]); } $sitzung = aeromc_friends_cape_auth($request, $uuid); if (is_wp_error($sitzung)) { return $sitzung; } $table = aeromc_friends_capes_table(); $alt = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE uuid = %s", $uuid)); if ($alt) { aeromc_friends_cape_delete_file($alt->datei); if ($alt->status === 'gesperrt') { // Sperre bleibt bestehen, nur das Bild ist weg $wpdb->update($table, ['datei' => '', 'hash' => ''], ['uuid' => $uuid]); } else { $wpdb->delete($table, ['uuid' => $uuid]); } } return ['ok' => true]; } function aeromc_friends_route_cape_get($request) { global $wpdb; $uuid = aeromc_friends_normalize_uuid($request->get_param('uuid')); if (!$uuid) { return new WP_Error('aeromc_invalid', 'Ungültige uuid.', ['status' => 400]); } $table = aeromc_friends_capes_table(); $r = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE uuid = %s AND status = 'aktiv'", $uuid)); return ['ok' => true, 'cape' => $r ? aeromc_friends_cape_info($r) : null]; } function aeromc_friends_route_capes($request) { global $wpdb; $liste = array_filter(array_map('aeromc_friends_normalize_uuid', explode(',', (string) $request->get_param('uuids')))); $liste = array_slice(array_values(array_unique($liste)), 0, AEROMC_FRIENDS_CAPE_MAX_UUIDS); $ergebnis = []; if ($liste) { $table = aeromc_friends_capes_table(); $platz = implode(',', array_fill(0, count($liste), '%s')); $rows = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table WHERE status = 'aktiv' AND uuid IN ($platz)", $liste )); foreach ($rows as $r) { $ergebnis[$r->uuid] = aeromc_friends_cape_info($r); } } $antwort = new WP_REST_Response(['ok' => true, 'capes' => (object) $ergebnis]); // kurz zwischenspeichern lassen – der Mod fragt beim Betreten eines Servers viele auf einmal ab $antwort->header('Cache-Control', 'public, max-age=60'); return $antwort; } // ---------- Admin: Umhänge prüfen, löschen, sperren ---------- if (is_admin()) { add_action('admin_menu', function () { add_submenu_page('aeromc-reports', 'AeroMC Umhänge', 'Umhänge', 'manage_options', 'aeromc-capes', 'aeromc_friends_capes_page'); }, 20); } function aeromc_friends_capes_page() { global $wpdb; if (!current_user_can('manage_options')) { return; } $table = aeromc_friends_capes_table(); if (isset($_POST['aeromc_cape'], $_POST['aeromc_aktion']) && check_admin_referer('aeromc_capes')) { $uuid = aeromc_friends_normalize_uuid($_POST['aeromc_cape']); $r = $uuid ? $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE uuid = %s", $uuid)) : null; if ($r) { $aktion = (string) $_POST['aeromc_aktion']; if ($aktion === 'loeschen') { aeromc_friends_cape_delete_file($r->datei); $wpdb->delete($table, ['uuid' => $uuid]); echo '
Umhang von ' . esc_html($r->username) . ' gelöscht.
' . esc_html($r->username) . ' gesperrt – der Umhang ist entfernt, neue Uploads werden abgelehnt.
Sperre für ' . esc_html($r->username) . ' aufgehoben.
Eigene Umhänge, die Spieler im AeroMC-Skin-Editor hochgeladen haben. „Sperren“ entfernt den Umhang und verhindert neue Uploads dieses Spielers.
'; if (!$rows) { echo 'Noch keine Umhänge hochgeladen.
| Umhang | Spieler | Größe | Geändert (UTC) | Status | |
|---|---|---|---|---|---|
| ' . $bild . ' | ' . esc_html($r->username) . ' ' . esc_html($r->uuid) . ' | '
. '' . esc_html($r->breite . '×' . $r->hoehe) . ' | ' . esc_html($r->updated_at) . ' | ' . esc_html($r->status) . ' | '; echo ' |