529 lines
18 KiB
PHP
529 lines
18 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
ini_set('display_errors', '1');
|
|
error_reporting(E_ALL);
|
|
session_start();
|
|
|
|
if (!isset($_SESSION['user_id'])) {
|
|
header('Location: ../login.php');
|
|
exit;
|
|
}
|
|
|
|
require_once __DIR__ . '/../auth_helpers.php';
|
|
|
|
$config = require __DIR__ . '/../../config/database.php';
|
|
$telegramConfig = require __DIR__ . '/../../config/telegram.php';
|
|
|
|
$dsn = sprintf(
|
|
'mysql:host=%s;dbname=%s;charset=%s',
|
|
$config['host'],
|
|
$config['dbname'],
|
|
$config['charset'] ?? 'utf8mb4'
|
|
);
|
|
|
|
$pdo = new PDO(
|
|
$dsn,
|
|
$config['user'],
|
|
$config['pass'],
|
|
[
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
]
|
|
);
|
|
|
|
$currentUserId = (int) $_SESSION['user_id'];
|
|
$loggedIn = true;
|
|
$publicBasePath = '../';
|
|
$pageTitle = 'Telegram-Testversand - AstroTools';
|
|
$headerIntroPre = 'Administration';
|
|
$headerIntroTitle = 'Telegram-Testversand';
|
|
$headerIntroSub = 'Testnachricht an alle verknuepften Telegram-Konten senden.';
|
|
$errors = [];
|
|
$successMessage = '';
|
|
$resultRows = [];
|
|
$messageText = "Testnachricht von SkyView\n\nDer Telegram-Bot ist jetzt erfolgreich verbunden.";
|
|
$sendMode = 'self';
|
|
$selectedRecipientIds = [];
|
|
|
|
function h(?string $value): string
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function textLength(string $value): int
|
|
{
|
|
if (function_exists('mb_strlen')) {
|
|
return mb_strlen($value);
|
|
}
|
|
|
|
return strlen($value);
|
|
}
|
|
|
|
function telegramHttpPostJson(string $url, array $payload): array
|
|
{
|
|
$jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
if ($jsonPayload === false) {
|
|
return [
|
|
'ok' => false,
|
|
'status_code' => 0,
|
|
'body' => null,
|
|
'error' => 'Die Telegram-Anfrage konnte nicht als JSON kodiert werden.',
|
|
];
|
|
}
|
|
|
|
if (DIRECTORY_SEPARATOR === '\\' && function_exists('shell_exec')) {
|
|
$urlBase64 = base64_encode($url);
|
|
$payloadBase64 = base64_encode($jsonPayload);
|
|
$powerShellScript = str_replace(
|
|
['__URL_BASE64__', '__PAYLOAD_BASE64__'],
|
|
[$urlBase64, $payloadBase64],
|
|
<<<'PS'
|
|
$ErrorActionPreference = 'Stop'
|
|
$url = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('__URL_BASE64__'))
|
|
$body = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('__PAYLOAD_BASE64__'))
|
|
try {
|
|
$response = Invoke-RestMethod -Method Post -Uri $url -ContentType 'application/json' -Body $body
|
|
[PSCustomObject]@{
|
|
ok = $true
|
|
body = ($response | ConvertTo-Json -Depth 20 -Compress)
|
|
error = ''
|
|
} | ConvertTo-Json -Compress
|
|
} catch {
|
|
$stream = $null
|
|
$responseBody = ''
|
|
if ($_.Exception.Response) {
|
|
try {
|
|
$stream = $_.Exception.Response.GetResponseStream()
|
|
if ($stream) {
|
|
$reader = New-Object System.IO.StreamReader($stream)
|
|
$responseBody = $reader.ReadToEnd()
|
|
}
|
|
} catch {
|
|
} finally {
|
|
if ($stream) {
|
|
$stream.Dispose()
|
|
}
|
|
}
|
|
}
|
|
[PSCustomObject]@{
|
|
ok = $false
|
|
body = $responseBody
|
|
error = $_.Exception.Message
|
|
} | ConvertTo-Json -Compress
|
|
}
|
|
PS
|
|
);
|
|
$encodedCommandBinary = function_exists('iconv')
|
|
? iconv('UTF-8', 'UTF-16LE', $powerShellScript)
|
|
: $powerShellScript;
|
|
$encodedCommand = base64_encode($encodedCommandBinary === false ? $powerShellScript : $encodedCommandBinary);
|
|
$command = 'powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand ' . $encodedCommand;
|
|
$rawOutput = shell_exec($command);
|
|
|
|
if ($rawOutput !== null && trim($rawOutput) !== '') {
|
|
$decodedShell = json_decode(trim($rawOutput), true);
|
|
if (is_array($decodedShell)) {
|
|
return [
|
|
'ok' => (bool) ($decodedShell['ok'] ?? false),
|
|
'status_code' => 0,
|
|
'body' => $decodedShell['body'] ?? null,
|
|
'error' => $decodedShell['error'] ?? null,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (function_exists('curl_init')) {
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Content-Type: application/json',
|
|
'Content-Length: ' . strlen($jsonPayload),
|
|
],
|
|
CURLOPT_POSTFIELDS => $jsonPayload,
|
|
CURLOPT_TIMEOUT => 20,
|
|
]);
|
|
|
|
$body = curl_exec($ch);
|
|
$curlError = curl_error($ch);
|
|
$statusCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($body === false) {
|
|
return [
|
|
'ok' => false,
|
|
'status_code' => $statusCode,
|
|
'body' => null,
|
|
'error' => $curlError !== '' ? $curlError : 'Die Telegram-Anfrage ist fehlgeschlagen.',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status_code' => $statusCode,
|
|
'body' => $body,
|
|
'error' => null,
|
|
];
|
|
}
|
|
|
|
$context = stream_context_create([
|
|
'http' => [
|
|
'method' => 'POST',
|
|
'header' => implode("\r\n", [
|
|
'Content-Type: application/json',
|
|
'Content-Length: ' . strlen($jsonPayload),
|
|
]),
|
|
'content' => $jsonPayload,
|
|
'timeout' => 20,
|
|
'ignore_errors' => true,
|
|
],
|
|
]);
|
|
|
|
$body = @file_get_contents($url, false, $context);
|
|
$statusCode = 0;
|
|
|
|
if (isset($http_response_header) && is_array($http_response_header)) {
|
|
foreach ($http_response_header as $headerLine) {
|
|
if (preg_match('/^HTTP\/\S+\s+(\d{3})\b/', $headerLine, $matches) === 1) {
|
|
$statusCode = (int) $matches[1];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($body === false) {
|
|
return [
|
|
'ok' => false,
|
|
'status_code' => $statusCode,
|
|
'body' => null,
|
|
'error' => 'Die Telegram-Anfrage ist fehlgeschlagen. Auf diesem Server ist moeglicherweise weder cURL noch der HTTPS-Stream-Wrapper verfuegbar.',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status_code' => $statusCode,
|
|
'body' => $body,
|
|
'error' => null,
|
|
];
|
|
}
|
|
|
|
function sendTelegramMessage(string $botToken, string $chatId, string $messageText): array
|
|
{
|
|
$response = telegramHttpPostJson(
|
|
'https://api.telegram.org/bot' . $botToken . '/sendMessage',
|
|
[
|
|
'chat_id' => $chatId,
|
|
'text' => $messageText,
|
|
]
|
|
);
|
|
|
|
if (!$response['ok']) {
|
|
return [
|
|
'ok' => false,
|
|
'description' => (string) ($response['error'] ?? 'Telegram konnte nicht erreicht werden.'),
|
|
];
|
|
}
|
|
|
|
$decoded = json_decode((string) $response['body'], true);
|
|
if (!is_array($decoded)) {
|
|
return [
|
|
'ok' => false,
|
|
'description' => 'Telegram hat keine gueltige JSON-Antwort geliefert.',
|
|
];
|
|
}
|
|
|
|
return [
|
|
'ok' => (bool) ($decoded['ok'] ?? false),
|
|
'description' => (string) ($decoded['description'] ?? ''),
|
|
];
|
|
}
|
|
|
|
if (empty($_SESSION['telegram_broadcast_csrf'])) {
|
|
$_SESSION['telegram_broadcast_csrf'] = bin2hex(random_bytes(16));
|
|
}
|
|
|
|
$stmtCurrentUser = $pdo->prepare("
|
|
SELECT `id`, `username`, `display_name`, `role`, `is_active`, `telegram_chat_id`
|
|
FROM `app_users`
|
|
WHERE `id` = :id
|
|
LIMIT 1
|
|
");
|
|
$stmtCurrentUser->execute([':id' => $currentUserId]);
|
|
$currentUser = $stmtCurrentUser->fetch();
|
|
|
|
if (
|
|
!$currentUser ||
|
|
!isAdminLikeRole((string) $currentUser['role']) ||
|
|
(int) $currentUser['is_active'] !== 1
|
|
) {
|
|
$_SESSION = [];
|
|
session_destroy();
|
|
header('Location: ../login.php');
|
|
exit;
|
|
}
|
|
|
|
$stmtRecipients = $pdo->query("
|
|
SELECT
|
|
`id`,
|
|
`username`,
|
|
`display_name`,
|
|
`telegram_username`,
|
|
`telegram_chat_id`,
|
|
`telegram_connected_at`
|
|
FROM `app_users`
|
|
WHERE `is_active` = 1
|
|
AND `telegram_chat_id` IS NOT NULL
|
|
AND `telegram_chat_id` <> ''
|
|
ORDER BY COALESCE(`display_name`, `username`) ASC, `id` ASC
|
|
");
|
|
$recipients = $stmtRecipients->fetchAll();
|
|
$recipientIndex = [];
|
|
foreach ($recipients as $recipient) {
|
|
$recipientIndex[(int) $recipient['id']] = $recipient;
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = (string) ($_POST['action'] ?? '');
|
|
$csrfToken = (string) ($_POST['csrf_token'] ?? '');
|
|
$messageText = trim((string) ($_POST['message_text'] ?? ''));
|
|
$sendMode = (string) ($_POST['send_mode'] ?? 'self');
|
|
$selectedRecipientIds = array_values(array_unique(array_map(
|
|
static fn ($value): int => (int) $value,
|
|
is_array($_POST['recipient_ids'] ?? null) ? $_POST['recipient_ids'] : []
|
|
)));
|
|
|
|
if (!hash_equals($_SESSION['telegram_broadcast_csrf'], $csrfToken)) {
|
|
$errors[] = 'Die Sitzung ist abgelaufen. Bitte die Seite neu laden.';
|
|
}
|
|
|
|
if ($action !== 'send_broadcast') {
|
|
$errors[] = 'Die angeforderte Aktion ist ungueltig.';
|
|
}
|
|
|
|
if ($messageText === '') {
|
|
$errors[] = 'Bitte eine Nachricht eingeben.';
|
|
} elseif (textLength($messageText) > 4000) {
|
|
$errors[] = 'Die Nachricht ist zu lang. Bitte auf maximal 4000 Zeichen kuerzen.';
|
|
}
|
|
|
|
$botToken = trim((string) ($telegramConfig['bot_token'] ?? ''));
|
|
if ($botToken === '') {
|
|
$errors[] = 'In config/telegram.php ist noch kein Bot-Token hinterlegt.';
|
|
}
|
|
|
|
if (empty($recipients)) {
|
|
$errors[] = 'Es gibt derzeit keine verknuepften Telegram-Nutzer.';
|
|
}
|
|
|
|
$targetRecipients = [];
|
|
if (empty($errors)) {
|
|
if ($sendMode === 'self') {
|
|
if (empty($currentUser['telegram_chat_id'])) {
|
|
$errors[] = 'Dein Administrationskonto ist noch nicht mit Telegram verknuepft.';
|
|
} else {
|
|
foreach ($recipients as $recipient) {
|
|
if ((int) $recipient['id'] === $currentUserId) {
|
|
$targetRecipients[] = $recipient;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (empty($targetRecipients)) {
|
|
$targetRecipients[] = [
|
|
'id' => $currentUserId,
|
|
'username' => (string) $currentUser['username'],
|
|
'display_name' => (string) ($currentUser['display_name'] ?? ''),
|
|
'telegram_username' => '',
|
|
'telegram_chat_id' => (string) $currentUser['telegram_chat_id'],
|
|
'telegram_connected_at' => null,
|
|
];
|
|
}
|
|
}
|
|
} elseif ($sendMode === 'selected') {
|
|
if (empty($selectedRecipientIds)) {
|
|
$errors[] = 'Bitte mindestens einen Empfaenger auswaehlen.';
|
|
} else {
|
|
foreach ($selectedRecipientIds as $recipientId) {
|
|
if (isset($recipientIndex[$recipientId])) {
|
|
$targetRecipients[] = $recipientIndex[$recipientId];
|
|
}
|
|
}
|
|
|
|
if (empty($targetRecipients)) {
|
|
$errors[] = 'Die ausgewaehlten Empfaenger konnten nicht geladen werden.';
|
|
}
|
|
}
|
|
} elseif ($sendMode === 'all') {
|
|
$targetRecipients = $recipients;
|
|
} else {
|
|
$errors[] = 'Der Versandmodus ist ungueltig.';
|
|
}
|
|
}
|
|
|
|
if (empty($errors)) {
|
|
$successCount = 0;
|
|
|
|
foreach ($targetRecipients as $recipient) {
|
|
$chatId = trim((string) ($recipient['telegram_chat_id'] ?? ''));
|
|
$sendResult = sendTelegramMessage($botToken, $chatId, $messageText);
|
|
$wasSuccessful = (bool) ($sendResult['ok'] ?? false);
|
|
|
|
if ($wasSuccessful) {
|
|
$successCount++;
|
|
}
|
|
|
|
$resultRows[] = [
|
|
'username' => (string) ($recipient['username'] ?? ''),
|
|
'display_name' => (string) ($recipient['display_name'] ?? ''),
|
|
'telegram_username' => (string) ($recipient['telegram_username'] ?? ''),
|
|
'chat_id' => $chatId,
|
|
'ok' => $wasSuccessful,
|
|
'description' => (string) ($sendResult['description'] ?? ''),
|
|
];
|
|
}
|
|
|
|
$successMessage = sprintf(
|
|
'Versand abgeschlossen: %d von %d Telegram-Nutzern erfolgreich erreicht.',
|
|
$successCount,
|
|
count($targetRecipients)
|
|
);
|
|
}
|
|
}
|
|
?>
|
|
<?php require __DIR__ . '/../header.php'; ?>
|
|
|
|
<?php if ($successMessage !== ''): ?>
|
|
<p class="msg-success"><?= h($successMessage) ?></p>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($errors)): ?>
|
|
<ul class="msg-error">
|
|
<?php foreach ($errors as $error): ?>
|
|
<li><?= h($error) ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<h2>Testnachricht senden</h2>
|
|
<p class="hint">
|
|
Diese Seite sendet eine Nachricht an alle aktiven Benutzerkonten mit hinterlegter Telegram-Chat-ID.
|
|
</p>
|
|
|
|
<table class="data-table mb-lg">
|
|
<tr><td>Verknuepfte Telegram-Nutzer</td><td><?= count($recipients) ?></td></tr>
|
|
<tr><td>Bot</td><td><?= h(($telegramConfig['bot_username'] ?? '') !== '' ? '@' . (string) $telegramConfig['bot_username'] : 'nicht konfiguriert') ?></td></tr>
|
|
</table>
|
|
|
|
<form method="post" action="" class="form-stack">
|
|
<input type="hidden" name="action" value="send_broadcast">
|
|
<input type="hidden" name="csrf_token" value="<?= h($_SESSION['telegram_broadcast_csrf']) ?>">
|
|
|
|
<div class="form-group">
|
|
<label>Empfaenger</label>
|
|
<label>
|
|
<input type="radio" name="send_mode" value="self" <?= $sendMode === 'self' ? 'checked' : '' ?>>
|
|
Nur an mich senden
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="send_mode" value="selected" <?= $sendMode === 'selected' ? 'checked' : '' ?>>
|
|
An ausgewaehlte Nutzer senden
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="send_mode" value="all" <?= $sendMode === 'all' ? 'checked' : '' ?>>
|
|
An alle verknuepften Nutzer senden
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="recipient_ids">Auswahl fuer "ausgewaehlte Nutzer"</label>
|
|
<select id="recipient_ids" name="recipient_ids[]" multiple size="8">
|
|
<?php foreach ($recipients as $recipient): ?>
|
|
<?php $recipientId = (int) $recipient['id']; ?>
|
|
<?php $recipientLabel = ($recipient['display_name'] !== '' ? $recipient['display_name'] : $recipient['username']) . ' (' . $recipient['username'] . ')'; ?>
|
|
<option value="<?= $recipientId ?>" <?= in_array($recipientId, $selectedRecipientIds, true) ? 'selected' : '' ?>>
|
|
<?= h($recipientLabel) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
<p class="hint">Mit gedrueckter Strg-Taste lassen sich mehrere Benutzer markieren.</p>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="message_text">Nachricht</label>
|
|
<textarea
|
|
id="message_text"
|
|
name="message_text"
|
|
rows="8"
|
|
maxlength="4000"
|
|
required
|
|
><?= h($messageText) ?></textarea>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary">Testnachricht an alle senden</button>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Empfaenger</h2>
|
|
<?php if (empty($recipients)): ?>
|
|
<p class="hint">Noch keine verknuepften Telegram-Konten vorhanden.</p>
|
|
<?php else: ?>
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Benutzer</th>
|
|
<th>Telegram</th>
|
|
<th>Chat-ID</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($recipients as $recipient): ?>
|
|
<tr>
|
|
<td>
|
|
<strong><?= h($recipient['display_name'] !== '' ? $recipient['display_name'] : $recipient['username']) ?></strong><br>
|
|
<span class="hint"><?= h($recipient['username']) ?></span>
|
|
</td>
|
|
<td><?= h(($recipient['telegram_username'] ?? '') !== '' ? '@' . ltrim((string) $recipient['telegram_username'], '@') : '—') ?></td>
|
|
<td><?= h((string) $recipient['telegram_chat_id']) ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!empty($resultRows)): ?>
|
|
<div class="card">
|
|
<h2>Versandprotokoll</h2>
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Benutzer</th>
|
|
<th>Chat-ID</th>
|
|
<th>Status</th>
|
|
<th>Rueckmeldung</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($resultRows as $row): ?>
|
|
<tr>
|
|
<td><?= h($row['display_name'] !== '' ? $row['display_name'] : $row['username']) ?></td>
|
|
<td><?= h($row['chat_id']) ?></td>
|
|
<td><?= $row['ok'] ? 'OK' : 'Fehler' ?></td>
|
|
<td><?= h($row['description'] !== '' ? $row['description'] : 'Nachricht gesendet.') ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php require __DIR__ . '/../footer.php'; ?>
|