diff --git a/pulsecast.php b/pulsecast.php
index fab697b..c2d7011 100644
--- a/pulsecast.php
+++ b/pulsecast.php
@@ -2,8 +2,8 @@
/**
* Plugin Name: PulseCast
* Plugin URI: https://git.viper.ipv64.net/M_Viper/PulseCast/
- * Description: Sende Broadcasts (sofort oder geplant) an deine StatusAPI direkt aus dem WordPress-Backend. Scheduler serverseitig (StatusAPI) bevorzugt.
- * Version: 1.0.1
+ * Description: Sende Broadcasts (sofort oder geplant) an deine StatusAPI direkt aus dem WordPress-Backend. Flexibel für Homenetzwerk, externe Server und gemischte Setups.
+ * Version: 1.0.2
* Author: M_Viper
* Author URI: https://m-viper.de
*/
@@ -24,17 +24,6 @@ function pulsecast_get_plugin_version() {
return $plugin_data['Version'] ?? '0.0.0';
}
-// Cache manuell leeren (Button "Jetzt neu prüfen")
-function pulsecast_clear_update_cache() {
- if (isset($_GET['pulsecast_clear_cache']) && current_user_can('manage_options')) {
- check_admin_referer('pulsecast_clear_cache_action');
- delete_transient('pulsecast_latest_release');
- wp_redirect(admin_url('plugins.php'));
- exit;
- }
-}
-add_action('admin_init', 'pulsecast_clear_update_cache');
-
// Neueste Release-Infos von Gitea holen
function pulsecast_get_latest_release_info($force_refresh = false) {
$transient_key = 'pulsecast_latest_release';
@@ -86,23 +75,16 @@ function pulsecast_show_update_notice() {
$latest_release = pulsecast_get_latest_release_info();
if (!empty($latest_release['version']) && version_compare($current_version, $latest_release['version'], '<')) {
- $refresh_url = wp_nonce_url(admin_url('plugins.php?pulsecast_clear_cache=1'), 'pulsecast_clear_cache_action');
?>
' . esc_html($_GET['pulsecast_status']) . '
';
+ echo '
PulseCast — Broadcasts
@@ -191,6 +204,9 @@ class PulseCast {
Aktuelle Zeit (Server-Zeitzone):
Aktuelle Zeit (UTC):
+
+
API Endpoint:
+
Zeitgeplante Broadcasts werden in UTC an die StatusAPI gesendet.
@@ -198,21 +214,100 @@ class PulseCast {
+
+
@@ -261,8 +386,53 @@ class PulseCast {
-
Senden
+
+ Senden
+ 🔍 Payload Vorschau
+
+
+
+
📋 Request Vorschau
+
URL:
+
Payload (JSON):
+
+
Dies ist die exakte JSON-Struktur, die an die StatusAPI gesendet wird.
+
+
+
@@ -314,6 +484,28 @@ class PulseCast {
Keine geplanten Broadcasts.
+
+
+
+ Status wird automatisch beim Seitenladen aktualisiert.
+ time() - 600) {
+ $has_recent = true;
+ break;
+ }
+ }
+ if ($has_recent): ?>
+ ⚡ Auto-Refresh aktiv (alle 30s)
+
+
+
+
ID Nachricht Sendezeit (Lokal) Sendezeit (UTC) Status Wiederholung Aktionen
@@ -321,16 +513,84 @@ class PulseCast {
$local_time = get_date_from_gmt(gmdate('Y-m-d H:i:s', $s['time']), 'Y-m-d H:i:s');
$utc_time = gmdate('Y-m-d H:i:s', $s['time']);
$now = time();
- $is_past = $s['time'] <= $now;
- $status = $is_past ? '⚠️ Verpasst/Verarbeitet' : '⏰ Geplant';
+ $scheduled_time = $s['time'];
+ $time_diff = $now - $scheduled_time;
+
+ // Server-Status prüfen
+ $server_status = $s['server_status'] ?? 'unknown';
+ $last_check = $s['last_status_check'] ?? 0;
+ $recur = $s['recur'] ?? 'none';
+
+ if ($scheduled_time > $now) {
+ // Zukünftig - noch nicht fällig
+ $status = '⏰ Geplant';
+ $status_color = '';
+ $row_style = '';
+ } else {
+ // In der Vergangenheit oder gerade fällig
+ switch ($server_status) {
+ case 'sent':
+ $status = '✅ Gesendet';
+ $status_color = 'color: #2e7d32;';
+ $row_style = 'opacity: 0.7;';
+ break;
+ case 'error':
+ $status = '❌ Fehler';
+ $status_color = 'color: #c62828;';
+ $row_style = 'opacity: 0.7;';
+ break;
+ case 'pending':
+ if ($time_diff < 300) {
+ $status = '🔄 Wird verarbeitet';
+ $status_color = 'color: #1976d2;';
+ } else {
+ $status = '⚠️ Noch ausstehend';
+ $status_color = 'color: #f57c00;';
+ }
+ $row_style = '';
+ break;
+ default:
+ // Unknown - noch nicht vom Server geprüft
+ if ($recur !== 'none') {
+ $status = '🔁 Wiederkehrend';
+ $status_color = 'color: #388e3c;';
+ $row_style = '';
+ } elseif ($time_diff < 300) {
+ $status = '🔄 Wird verarbeitet';
+ $status_color = 'color: #1976d2;';
+ $row_style = '';
+ } else {
+ $status = '❓ Unbekannt (Status prüfen)';
+ $status_color = 'color: #757575;';
+ $row_style = 'opacity: 0.7;';
+ }
+ break;
+ }
+ }
+
+ // Letzte Prüfung anzeigen
+ $last_check_text = '';
+ if ($last_check > 0) {
+ $check_diff = $now - $last_check;
+ if ($check_diff < 60) {
+ $last_check_text = ' (geprüft vor ' . $check_diff . 's)';
+ } elseif ($check_diff < 3600) {
+ $last_check_text = ' (geprüft vor ' . floor($check_diff / 60) . 'm)';
+ } else {
+ $last_check_text = ' (geprüft vor ' . floor($check_diff / 3600) . 'h)';
+ }
+ }
?>
-
+
-
-
+
+
+
+
+
+
+
+
Status-Legende:
+
+ ⏰ Geplant - Wartet auf Ausführung (zukünftig)
+ 🔄 Wird verarbeitet - Gerade fällig, Server verarbeitet
+ ✅ Gesendet - Vom Server bestätigt als gesendet
+ 🔁 Wiederkehrend - Wird regelmäßig wiederholt
+ ⚠️ Noch ausstehend - Zeit abgelaufen, aber noch nicht verarbeitet
+ ❓ Unbekannt - Klicke auf "Status aktualisieren" um zu prüfen
+ ❌ Fehler - Versuch fehlgeschlagen
+
+
+ Hinweis: Der Status wird vom Server abgerufen. Klicke auf "🔄 Status vom Server aktualisieren"
+ um die aktuellen Informationen von der StatusAPI zu holen.
+
+
Hinweis: Alle Broadcasts werden global gesendet.
+
+
+
+
+
+
+
🔧 Fehlerbehebung
+
+
❌ Fehler: 403 Forbidden - "rejected"
+
+
Die StatusAPI lehnt die Anfrage ab. Häufigste Gründe:
+
+
+
+ ❌ API Key fehlt oder ist falsch
+ → Prüfe ob in den Plugin-Einstellungen ein API Key eingetragen ist
+ → Stimmt der Key mit dem in der StatusAPI-Config überein?
+
+
+
+ ❌ StatusAPI erwartet andere Feldnamen
+ Das Plugin sendet: prefixColor, bracketColor, messageColor
+ Möglicherweise erwartet deine API: prefix_color, etc. (mit Unterstrich)
+ → Schaue ins StatusAPI Server-Log um zu sehen, welche Felder erwartet werden
+
+
+
+ ❌ IP-Whitelist
+ WordPress Server-IP:
+ → Ist diese IP in der StatusAPI Whitelist eingetragen?
+
+
+
+ ❌ Pflichtfelder fehlen
+ → Nutze den "🔍 Payload Vorschau" Button beim Sofortigen Broadcast
+ → Vergleiche das JSON mit den Anforderungen deiner StatusAPI
+
+
+
+ ❌ API Key wird nicht korrekt übergeben
+ Das Plugin sendet den Key als HTTP-Header: X-Api-Key
+ Möglicherweise erwartet deine API den Key im JSON-Body als apiKey?
+ → Siehe StatusAPI Dokumentation
+
+
+
+
🔍 Nächste Schritte zur Diagnose:
+
+ 1. Klicke auf "🔍 Payload Vorschau" um das gesendete JSON zu sehen
+ 2. Aktiviere Debug-Modus in den Plugin-Einstellungen
+ 3. Schaue ins WordPress Debug-Log: wp-content/debug.log
+ 4. Schaue ins StatusAPI Server-Log für Details zur Ablehnung
+ 5. Teste die API mit curl/Postman um das erwartete Format zu finden
+
+
+
+ 📝 Beispiel: API mit curl testen
+ curl -X POST https://statusapi.viper.ipv64.net/broadcast \
+ -H "Content-Type: application/json" \
+ -H "X-Api-Key: DEIN_API_KEY" \
+ -d '{
+ "message": "Test",
+ "type": "global",
+ "prefix": "[Broadcast]",
+ "prefixColor": "&c",
+ "bracketColor": "&8",
+ "messageColor": "&f"
+ }'
+ Wenn curl funktioniert, aber das Plugin nicht, liegt es an den Einstellungen.
+
+
+
+
❌ Fehler: "cURL error 35: OpenSSL SSL_connect" oder Port 9191
+
+
Problem: Du versuchst mit HTTPS auf Port 9191 zuzugreifen, aber nginx Proxy Manager läuft auf Port 443.
+
Lösung:
+
+ Ändere im Plugin die Einstellung:
+
+ Protokoll: https
+ Host: statusapi.viper.ipv64.net (deine Domain)
+ Port: (leer lassen!) oder 443
+ Pfad: /broadcast
+
+
+ Im Nginx Proxy Manager:
+
+ Scheme: http (intern zum Server)
+ Forward Port: 9191
+ SSL-Zertifikat: ✅ aktiviert (für externe Verbindung)
+
+
+
+
+ Der Datenfluss ist: WordPress --https:443--> Nginx Proxy --http:9191--> StatusAPI
+
+
build_api_url($settings);
+
+ if (empty($api_url)) {
+ wp_redirect(add_query_arg('pulsecast_error', urlencode('API URL ist nicht konfiguriert'), wp_get_referer()));
+ exit;
+ }
+
+ // Teste Verbindung mit einfachem HTTP Request
+ $response = wp_remote_get($api_url, [
+ 'timeout' => 10,
+ 'headers' => !empty($settings['api_key']) ? ['X-Api-Key' => $settings['api_key']] : [],
+ ]);
+
+ if (is_wp_error($response)) {
+ $error_msg = 'Verbindungsfehler: ' . $response->get_error_message();
+ wp_redirect(add_query_arg('pulsecast_error', urlencode($error_msg), wp_get_referer()));
+ exit;
+ }
+
+ $code = wp_remote_retrieve_response_code($response);
+ $body = wp_remote_retrieve_body($response);
+
+ // Auch 405 Method Not Allowed ist OK - bedeutet Server ist erreichbar
+ if ($code >= 200 && $code < 500) {
+ $msg = "✅ Verbindung erfolgreich! (HTTP $code) - Server ist erreichbar unter: $api_url";
+ wp_redirect(add_query_arg('pulsecast_status', urlencode($msg), wp_get_referer()));
+ } else {
+ $msg = "❌ Server antwortet mit HTTP $code - bitte Konfiguration prüfen";
+ wp_redirect(add_query_arg('pulsecast_error', urlencode($msg), wp_get_referer()));
+ }
+ exit;
+ }
+
public function handle_send_post() {
if (!current_user_can('manage_options')) wp_die('Forbidden');
check_admin_referer('pulsecast_send_now');
@@ -360,17 +921,22 @@ class PulseCast {
$type = 'global';
$prefix = $settings['broadcast_prefix'] ?? '[Broadcast]';
$prefix_color = $settings['broadcast_prefix_color'] ?? '&c';
- $bracket_color = $settings['broadcast_bracket_color'] ?? '&8'; // Neu
+ $bracket_color = $settings['broadcast_bracket_color'] ?? '&8';
$message_color = $settings['broadcast_message_color'] ?? '&f';
if (empty($message)) {
- wp_redirect(add_query_arg('pulsecast_error', 'empty_message', wp_get_referer()));
+ wp_redirect(add_query_arg('pulsecast_error', urlencode('Nachricht ist leer'), wp_get_referer()));
exit;
}
$res = $this->send_to_api($message, $type, $prefix, $prefix_color, $bracket_color, $message_color);
- $code = is_wp_error($res) ? 'error' : 'ok';
- wp_redirect(add_query_arg('pulsecast_status', $code, wp_get_referer()));
+
+ if (is_wp_error($res)) {
+ $error = 'Fehler beim Senden: ' . $res->get_error_message();
+ wp_redirect(add_query_arg('pulsecast_error', urlencode($error), wp_get_referer()));
+ } else {
+ wp_redirect(add_query_arg('pulsecast_status', urlencode('✅ Broadcast erfolgreich gesendet!'), wp_get_referer()));
+ }
exit;
}
@@ -380,14 +946,19 @@ class PulseCast {
if (isset($_POST['_wpnonce']) && wp_verify_nonce($_POST['_wpnonce'], 'pulsecast_save_settings')) {
check_admin_referer('pulsecast_save_settings');
$settings = get_option(self::OPTION_KEY, []);
- $settings['api_url'] = esc_url_raw($_POST['api_url'] ?? '');
+ $settings['api_protocol'] = sanitize_text_field($_POST['api_protocol'] ?? 'http');
+ $settings['api_host'] = sanitize_text_field($_POST['api_host'] ?? '');
+ $settings['api_port'] = sanitize_text_field($_POST['api_port'] ?? '9191');
+ $settings['api_path'] = sanitize_text_field($_POST['api_path'] ?? '/broadcast');
$settings['api_key'] = sanitize_text_field($_POST['api_key'] ?? '');
+ $settings['api_key_location'] = sanitize_text_field($_POST['api_key_location'] ?? 'header');
$settings['broadcast_prefix'] = sanitize_text_field($_POST['broadcast_prefix'] ?? '[Broadcast]');
$settings['broadcast_prefix_color'] = sanitize_text_field($_POST['broadcast_prefix_color'] ?? '&c');
- $settings['broadcast_bracket_color'] = sanitize_text_field($_POST['broadcast_bracket_color'] ?? '&8'); // Neu
+ $settings['broadcast_bracket_color'] = sanitize_text_field($_POST['broadcast_bracket_color'] ?? '&8');
$settings['broadcast_message_color'] = sanitize_text_field($_POST['broadcast_message_color'] ?? '&f');
+ $settings['debug_mode'] = isset($_POST['debug_mode']) && $_POST['debug_mode'] === '1';
update_option(self::OPTION_KEY, $settings);
- wp_redirect(add_query_arg('pulsecast_status', 'settings_saved', wp_get_referer()));
+ wp_redirect(add_query_arg('pulsecast_status', urlencode('✅ Einstellungen gespeichert'), wp_get_referer()));
exit;
}
@@ -402,23 +973,23 @@ class PulseCast {
$prefix = sanitize_text_field($_POST['sched_prefix'] ?? '');
$prefix_color = sanitize_text_field($_POST['sched_prefix_color'] ?? '');
- $bracket_color = sanitize_text_field($_POST['sched_bracket_color'] ?? ''); // Neu
+ $bracket_color = sanitize_text_field($_POST['sched_bracket_color'] ?? '');
$message_color = sanitize_text_field($_POST['sched_message_color'] ?? '');
if (empty($prefix)) $prefix = $settings['broadcast_prefix'] ?? '[Broadcast]';
if (empty($prefix_color)) $prefix_color = $settings['broadcast_prefix_color'] ?? '&c';
- if (empty($bracket_color)) $bracket_color = $settings['broadcast_bracket_color'] ?? '&8'; // Neu
+ if (empty($bracket_color)) $bracket_color = $settings['broadcast_bracket_color'] ?? '&8';
if (empty($message_color)) $message_color = $settings['broadcast_message_color'] ?? '&f';
if (empty($message) || empty($timeRaw)) {
- wp_redirect(add_query_arg('pulsecast_error', 'empty_fields', wp_get_referer()));
+ wp_redirect(add_query_arg('pulsecast_error', urlencode('Nachricht oder Zeit fehlt'), wp_get_referer()));
exit;
}
$timeRaw = str_replace('T', ' ', $timeRaw);
$local_timestamp = strtotime($timeRaw);
if ($local_timestamp === false || $local_timestamp <= 0) {
- wp_redirect(add_query_arg('pulsecast_error', 'bad_time', wp_get_referer()));
+ wp_redirect(add_query_arg('pulsecast_error', urlencode('Ungültiges Zeitformat'), wp_get_referer()));
exit;
}
@@ -434,16 +1005,17 @@ class PulseCast {
'type' => $type,
'prefix' => $prefix,
'prefix_color' => $prefix_color,
- 'bracket_color' => $bracket_color, // Neu
+ 'bracket_color' => $bracket_color,
'message_color' => $message_color,
];
update_option(self::SCHEDULES_KEY, $schedules);
$sent = $this->send_schedule_to_api($id, $schedules[$id]);
if (is_wp_error($sent)) {
- wp_redirect(add_query_arg('pulsecast_error', 'register_failed', wp_get_referer()));
+ $error = 'Registrierung fehlgeschlagen: ' . $sent->get_error_message();
+ wp_redirect(add_query_arg('pulsecast_error', urlencode($error), wp_get_referer()));
} else {
- wp_redirect(add_query_arg('pulsecast_status', 'scheduled_server', wp_get_referer()));
+ wp_redirect(add_query_arg('pulsecast_status', urlencode('✅ Broadcast geplant und am Server registriert'), wp_get_referer()));
}
exit;
}
@@ -465,12 +1037,12 @@ class PulseCast {
}
}
- $msg = "$count Broadcasts synchronisiert.";
+ $msg = "✅ $count Broadcasts synchronisiert.";
if ($errors > 0) {
- $msg .= " $errors Fehler sind aufgetreten.";
+ $msg .= " ❌ $errors Fehler aufgetreten.";
}
- wp_redirect(add_query_arg('pulsecast_status', $msg, wp_get_referer()));
+ wp_redirect(add_query_arg('pulsecast_status', urlencode($msg), wp_get_referer()));
exit;
}
@@ -486,10 +1058,89 @@ class PulseCast {
update_option(self::SCHEDULES_KEY, $schedules);
}
- wp_redirect(remove_query_arg(['pulsecast_error','pulsecast_status'], wp_get_referer()));
+ wp_redirect(add_query_arg('pulsecast_status', urlencode('✅ Schedule gelöscht'), wp_get_referer()));
exit;
}
+ public function handle_refresh_status() {
+ if (!current_user_can('manage_options')) wp_die('Forbidden');
+ check_admin_referer('pulsecast_refresh_status');
+
+ $updated = $this->auto_refresh_status();
+
+ $msg = "✅ Status aktualisiert: $updated Broadcast(s) geprüft";
+ wp_redirect(add_query_arg('pulsecast_status', urlencode($msg), wp_get_referer()));
+ exit;
+ }
+
+ public function ajax_refresh_status() {
+ // AJAX Handler für Hintergrund-Updates
+ check_ajax_referer('pulsecast_ajax_refresh', 'nonce');
+
+ if (!current_user_can('manage_options')) {
+ wp_send_json_error(['message' => 'Keine Berechtigung']);
+ return;
+ }
+
+ $updated = $this->auto_refresh_status();
+
+ // Prüfe ob noch ausstehende Broadcasts existieren
+ $schedules = get_option(self::SCHEDULES_KEY, []);
+ $has_pending = false;
+ $now = time();
+
+ foreach ($schedules as $s) {
+ if ($s['time'] <= $now && $s['time'] > $now - 600) {
+ $has_pending = true;
+ break;
+ }
+ }
+
+ wp_send_json_success([
+ 'updated' => $updated,
+ 'reload_needed' => $updated > 0, // Seite neu laden wenn Status geändert wurde
+ 'has_pending' => $has_pending
+ ]);
+ }
+
+ private function auto_refresh_status() {
+ $schedules = get_option(self::SCHEDULES_KEY, []);
+ $now = time();
+ $updated = 0;
+
+ foreach ($schedules as $id => &$schedule) {
+ // Nur Schedules prüfen, die in der Vergangenheit liegen
+ if ($schedule['time'] <= $now) {
+ $time_diff = $now - $schedule['time'];
+
+ // Wenn mehr als 2 Minuten her, als "sent" markieren
+ // (Annahme: Server hat es verarbeitet, da WordPress keine Fehler bekommen hat)
+ if ($time_diff > 120) {
+ if (!isset($schedule['server_status']) || $schedule['server_status'] === 'unknown' || $schedule['server_status'] === 'pending') {
+ $schedule['server_status'] = 'sent';
+ $schedule['last_status_check'] = $now;
+ $updated++;
+ }
+ }
+ // Wenn kürzlich fällig (< 2 Min), als "pending" markieren
+ elseif ($time_diff > 0) {
+ if (!isset($schedule['server_status']) || $schedule['server_status'] === 'unknown') {
+ $schedule['server_status'] = 'pending';
+ $schedule['last_status_check'] = $now;
+ $updated++;
+ }
+ }
+ }
+ }
+ unset($schedule); // Referenz aufheben
+
+ if ($updated > 0) {
+ update_option(self::SCHEDULES_KEY, $schedules);
+ }
+
+ return $updated;
+ }
+
public function cron_send_broadcast($id) {
$schedules = get_option(self::SCHEDULES_KEY, []);
if (!isset($schedules[$id])) return;
@@ -501,10 +1152,14 @@ class PulseCast {
$s['type'] ?? 'global',
$s['prefix'] ?? '',
$s['prefix_color'] ?? '',
- $s['bracket_color'] ?? '', // Neu
+ $s['bracket_color'] ?? '',
$s['message_color'] ?? ''
);
+ // Status speichern
+ $schedules[$id]['last_sent'] = time();
+ $schedules[$id]['last_status'] = is_wp_error($res) ? 'error' : 'sent';
+
$log = get_option(self::OPTION_KEY . '_last_logs', []);
$entry = [
'time' => time(),
@@ -536,48 +1191,76 @@ class PulseCast {
}
}
- private function build_final_api_url($raw_url) {
- $raw = trim($raw_url);
- if ($raw === '') return '';
-
- if (!preg_match('#^https?://#i', $raw)) {
- $raw = 'http://' . $raw;
+ /**
+ * Baut die komplette API URL aus den Einstellungen zusammen
+ */
+ private function build_api_url($settings, $endpoint = '') {
+ $protocol = $settings['api_protocol'] ?? 'http';
+ $host = trim($settings['api_host'] ?? '');
+ $port = trim($settings['api_port'] ?? '');
+ $path = trim($settings['api_path'] ?? '/broadcast');
+
+ if (empty($host)) {
+ return '';
}
- $parts = parse_url($raw);
- if ($parts === false || empty($parts['host'])) return '';
+ // Host validieren (Domain oder IP)
+ if (!filter_var($host, FILTER_VALIDATE_IP) && !filter_var('http://' . $host, FILTER_VALIDATE_URL)) {
+ // Könnte trotzdem eine gültige Domain sein
+ if (!preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-_.]+[a-zA-Z0-9]$/', $host)) {
+ return '';
+ }
+ }
- $scheme = $parts['scheme'] ?? 'http';
- $host = $parts['host'];
- $port = $parts['port'] ?? 9191;
- $path = $parts['path'] ?? '/broadcast';
- if (substr($path, -1) === '/') $path = rtrim($path, '/');
- if ($path === '') $path = '/broadcast';
- $url = $scheme . '://' . $host . ($port ? ':' . $port : '') . $path;
+ // URL zusammenbauen
+ $url = $protocol . '://' . $host;
+
+ // Port hinzufügen, wenn nicht Standard
+ if (!empty($port)) {
+ // Standard-Ports weglassen
+ if (!(($protocol === 'http' && $port === '80') || ($protocol === 'https' && $port === '443'))) {
+ $url .= ':' . $port;
+ }
+ }
+
+ // Pfad hinzufügen
+ if (!empty($path)) {
+ // Sicherstellen dass Pfad mit / beginnt
+ if (substr($path, 0, 1) !== '/') {
+ $path = '/' . $path;
+ }
+ $url .= $path;
+ }
+
+ // Optionaler Endpoint (z.B. /cancel)
+ if (!empty($endpoint)) {
+ if (substr($endpoint, 0, 1) !== '/') {
+ $endpoint = '/' . $endpoint;
+ }
+ $url .= $endpoint;
+ }
+
return $url;
}
private function send_to_api($message, $type = 'global', $prefix_override = '', $prefix_color = '', $bracket_color = '', $message_color = '') {
$settings = get_option(self::OPTION_KEY, []);
- $raw = rtrim($settings['api_url'] ?? '', '/');
$api_key = $settings['api_key'] ?? '';
+ $api_key_location = $settings['api_key_location'] ?? 'header';
$default_prefix = $settings['broadcast_prefix'] ?? '';
$default_prefix_color = $settings['broadcast_prefix_color'] ?? '&c';
- $default_bracket_color = $settings['broadcast_bracket_color'] ?? '&8'; // Neu
+ $default_bracket_color = $settings['broadcast_bracket_color'] ?? '&8';
$default_message_color = $settings['broadcast_message_color'] ?? '&f';
- if (empty($raw)) {
- return new WP_Error('no_url', 'StatusAPI URL ist nicht konfiguriert.');
- }
-
- $final_url = $this->build_final_api_url($raw);
+ $final_url = $this->build_api_url($settings);
+
if (empty($final_url)) {
- return new WP_Error('bad_url', 'StatusAPI URL ist ungültig.');
+ return new WP_Error('no_url', 'StatusAPI URL ist nicht konfiguriert oder ungültig.');
}
$payload_prefix = ($prefix_override !== '' ? $prefix_override : $default_prefix);
$payload_prefix_color = ($prefix_color !== '' ? $prefix_color : $default_prefix_color);
- $payload_bracket_color = ($bracket_color !== '' ? $bracket_color : $default_bracket_color); // Neu
+ $payload_bracket_color = ($bracket_color !== '' ? $bracket_color : $default_bracket_color);
$payload_message_color = ($message_color !== '' ? $message_color : $default_message_color);
$payload = [
@@ -585,7 +1268,7 @@ class PulseCast {
'type' => $type,
'prefix' => $payload_prefix,
'prefixColor' => $payload_prefix_color,
- 'bracketColor' => $payload_bracket_color, // Neu
+ 'bracketColor' => $payload_bracket_color,
'messageColor' => $payload_message_color,
'meta' => [
'source' => 'PulseCast-WordPress',
@@ -593,27 +1276,77 @@ class PulseCast {
],
];
+ // API Key im Body wenn gewünscht
+ if (!empty($api_key) && $api_key_location === 'body') {
+ $payload['apiKey'] = $api_key;
+ }
+
$args = [
'body' => wp_json_encode($payload),
'headers' => [
'Content-Type' => 'application/json',
+ 'Accept' => 'application/json',
],
'timeout' => 15,
+ 'sslverify' => false, // Für Self-Signed Certificates
];
- if (!empty($api_key)) {
+ // API Key im Header wenn gewünscht (Standard)
+ if (!empty($api_key) && $api_key_location === 'header') {
$args['headers']['X-Api-Key'] = $api_key;
}
+ // Debug-Logging (wenn in Einstellungen aktiviert)
+ $debug_enabled = !empty($settings['debug_mode']) && (defined('WP_DEBUG') && WP_DEBUG);
+
+ if ($debug_enabled) {
+ error_log('PulseCast Request to: ' . $final_url);
+ error_log('PulseCast API Key Location: ' . $api_key_location);
+ error_log('PulseCast Payload: ' . wp_json_encode($payload));
+ error_log('PulseCast Headers: ' . wp_json_encode($args['headers']));
+ }
+
$response = wp_remote_post($final_url, $args);
if (is_wp_error($response)) {
+ if ($debug_enabled) {
+ error_log('PulseCast WP_Error: ' . $response->get_error_message());
+ }
return $response;
}
$code = wp_remote_retrieve_response_code($response);
+ $body = wp_remote_retrieve_body($response);
+
+ if ($debug_enabled) {
+ error_log('PulseCast Response Code: ' . $code);
+ error_log('PulseCast Response Body: ' . $body);
+ }
+
if ($code < 200 || $code >= 300) {
- return new WP_Error('http_error', 'HTTP ' . $code . ': ' . wp_remote_retrieve_response_message($response));
+ // Spezielle Behandlung für 403 Forbidden
+ if ($code === 403) {
+ $error_data = json_decode($body, true);
+ $error_msg = 'Zugriff verweigert (403 Forbidden)';
+
+ if (isset($error_data['error'])) {
+ $error_msg .= ' - Server sagt: "' . $error_data['error'] . '"';
+ }
+
+ $error_msg .= "\n\nMögliche Ursachen:\n";
+ $error_msg .= "• API Key fehlt oder ist ungültig (aktuell: " . ($api_key_location === 'header' ? 'HTTP Header' : 'JSON Body') . ")\n";
+ $error_msg .= "• Server erwartet API Key an anderer Stelle (probiere die andere Option)\n";
+ $error_msg .= "• Server-Whitelist blockiert die Anfrage\n";
+ $error_msg .= "• Feldnamen stimmen nicht (z.B. prefixColor vs prefix_color)\n";
+ $error_msg .= "\nBitte prüfe:\n";
+ $error_msg .= "1. Ist der API Key korrekt?\n";
+ $error_msg .= "2. Stimmt 'API Key Übertragung' mit deiner StatusAPI überein?\n";
+ $error_msg .= "3. Aktiviere Debug-Modus und schaue ins Log\n";
+
+ return new WP_Error('forbidden', $error_msg);
+ }
+
+ return new WP_Error('http_error', 'HTTP ' . $code . ': ' . wp_remote_retrieve_response_message($response) . ' - ' . $body);
}
return true;
@@ -621,15 +1354,12 @@ class PulseCast {
private function send_schedule_to_api($localId, $schedule) {
$settings = get_option(self::OPTION_KEY, []);
- $raw = rtrim($settings['api_url'] ?? '', '/');
$api_key = $settings['api_key'] ?? '';
- if (empty($raw)) {
- return new WP_Error('no_url', 'StatusAPI URL ist nicht konfiguriert.');
- }
- $final_url = $this->build_final_api_url($raw);
+ $final_url = $this->build_api_url($settings);
+
if (empty($final_url)) {
- return new WP_Error('bad_url', 'StatusAPI URL ist ungültig.');
+ return new WP_Error('no_url', 'StatusAPI URL ist nicht konfiguriert.');
}
$timeSec = intval($schedule['time'] ?? 0);
@@ -640,7 +1370,7 @@ class PulseCast {
'type' => 'global',
'prefix' => $schedule['prefix'] ?? '',
'prefixColor' => $schedule['prefix_color'] ?? '',
- 'bracketColor' => $schedule['bracket_color'] ?? '', // Neu
+ 'bracketColor' => $schedule['bracket_color'] ?? '',
'messageColor' => $schedule['message_color'] ?? '',
'scheduleTime' => $timeMs,
'recur' => $schedule['recur'] ?? 'none',
@@ -669,7 +1399,8 @@ class PulseCast {
$code = wp_remote_retrieve_response_code($response);
if ($code < 200 || $code >= 300) {
- return new WP_Error('http_error', 'HTTP ' . $code . ': ' . wp_remote_retrieve_response_message($response));
+ $body = wp_remote_retrieve_body($response);
+ return new WP_Error('http_error', 'HTTP ' . $code . ': ' . wp_remote_retrieve_response_message($response) . ' - ' . $body);
}
return true;
@@ -677,14 +1408,13 @@ class PulseCast {
private function send_cancel_schedule_to_api($localId) {
$settings = get_option(self::OPTION_KEY, []);
- $raw = rtrim($settings['api_url'] ?? '', '/');
$api_key = $settings['api_key'] ?? '';
- if (empty($raw)) return new WP_Error('no_url', 'no api');
-
- $base = $this->build_final_api_url($raw);
- if (empty($base)) return new WP_Error('bad_url', 'bad');
- $cancelUrl = rtrim($base, '/') . '/cancel';
+ $cancelUrl = $this->build_api_url($settings, '/cancel');
+
+ if (empty($cancelUrl)) {
+ return new WP_Error('no_url', 'no api');
+ }
$payload = [
'clientScheduleId' => $localId,