Dienstag, 20 Januar 2026

Diese Woche am beliebtesten

Vertiefendes Material

Mailer mit reCAPTCHA

Mit unserem leistungsstarken All-in-One PHP Mailer Script erhältst du ein modernes Kontaktformular, das Sicherheit, Komfort und professionelles Design vereint.

Features im Überblick

  • Sichere E-Mail-Übertragung mit PHPMailer und SMTP-Unterstützung
  • Google reCAPTCHA Integration (sichtbar und clientseitig eingebunden) gegen Spam und Bots
  • Admin-Login mit verschlüsselter Anmeldung für Konfiguration und Einsicht
  • IP-Sperre & Missbrauchsschutz – blockiert Spammer automatisch
  • Zähler für gesendete Nachrichten direkt unter dem Formular
  • AJAX-Integration – versenden ohne Seitenreload, nutzerfreundlich und schnell
  • Responsives Premium-Design – optimiert für Desktop, Tablet & Smartphone
  • Einfach anpassbar – Texte, Farben und Layout direkt in einer Datei editierbar

Sicherheit inklusive

Dank reCAPTCHA, IP-Blockade und verschlüsseltem Login ist dein Formular bestens gegen Spam und Missbrauch geschützt.

Installation

  • Datei hochladen, SMTP-Daten eintragen, reCAPTCHA-Keys einfügen – fertig!
  • Keine zusätzlichen Abhängigkeiten notwendig – alles in einer Datei enthalten.

Ideal für Webseiten, Agenturen, Startups und alle, die ein sicheres und modernes Kontaktformular einsetzen möchten.

<?php
// mailer.php
// Ein Datei Email Mailer, AJAX, Captcha, Zähler, IP Sperre, Admin Login, PHPMailer optional
session_start();
date_default_timezone_set('Europe/Berlin');
header('X-Content-Type-Options: nosniff');

// ========== Konfiguration ==========
const USE_PHPMailer = true; // true wenn du composer und phpmailer installiert hast
const SMTP_HOST   = 'smtp.example.com';
const SMTP_PORT   = 587;
const SMTP_USER   = 'user@example.com';
const SMTP_PASS   = 'password';
const SMTP_FROM   = 'no-reply@example.com';
const SMTP_NAME   = 'Mailer';

const ADMIN_USER = 'admin'; // Admin Benutzername
// Falls du bereits ein Passwort-Hash hast, trage ihn hier ein. Ansonsten wird beim ersten Aufruf eine Setupseite angeboten
const ADMIN_PW_HASH = ''; // z. B. password_hash('DeinSicheresPasswort', PASSWORD_DEFAULT);

// reCAPTCHA Konfiguration trage deine keys ein
const RECAPTCHA_SITE_KEY = ''; // z. B. '6Lc...'
const RECAPTCHA_SECRET   = ''; // z. B. '6Lc...'

// Limits
const MAX_SENDS_PER_IP_PER_HOUR = 20;
const BLOCK_AFTER_FAILED_CAPTCHA = 5;
const BLOCK_DURATION_SECONDS = 3600 * 6; // 6 Stunden

// Dateien
const DATA_DIR = __DIR__ . '/data_mailer';
const LOG_FILE = DATA_DIR . '/mail_log.json';
const IP_FILE  = DATA_DIR . '/ip_block.json';
const COUNTER_FILE = DATA_DIR . '/counter.json';
const ADMIN_FILE = DATA_DIR . '/admin.json';

if(!is_dir(DATA_DIR)) {
  mkdir(DATA_DIR, 0755, true);
  file_put_contents(LOG_FILE, json_encode([]));
  file_put_contents(IP_FILE, json_encode([]));
  file_put_contents(COUNTER_FILE, json_encode(['total'=>0]));
  file_put_contents(ADMIN_FILE, json_encode(['user'=>ADMIN_USER,'hash'=>ADMIN_PW_HASH]));
}

// PHPMailer Autoload falls verwendet
if(USE_PHPMailer && file_exists(__DIR__ . '/vendor/autoload.php')){
  require_once __DIR__ . '/vendor/autoload.php';
}

// Hilfsfunktionen
function ipAddress(){
  if(!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) return $_SERVER['HTTP_CF_CONNECTING_IP'];
  if(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) return explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0];
  return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

function readJsonFile($f){
  if(!file_exists($f)) return null;
  $c = file_get_contents($f);
  return $c ? json_decode($c, true) : null;
}
function writeJsonFile($f, $data){
  file_put_contents($f, json_encode($data, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT));
}

function incrementCounter(){
  $c = readJsonFile(COUNTER_FILE) ?: ['total'=>0];
  $c['total'] = ($c['total'] ?? 0) + 1;
  writeJsonFile(COUNTER_FILE, $c);
  return $c['total'];
}

function logMailEvent($entry){
  $log = readJsonFile(LOG_FILE) ?: [];
  $log[] = $entry;
  if(count($log) > 4000) $log = array_slice($log, -4000);
  writeJsonFile(LOG_FILE, $log);
}

function isBlocked($ip){
  $blocks = readJsonFile(IP_FILE) ?: [];
  if(isset($blocks[$ip])){
    $b = $blocks[$ip];
    if($b['blocked_until'] > time()) return $b;
    unset($blocks[$ip]);
    writeJsonFile(IP_FILE, $blocks);
    return false;
  }
  return false;
}

function recordFailedCaptcha($ip){
  $blocks = readJsonFile(IP_FILE) ?: [];
  if(!isset($blocks[$ip])) $blocks[$ip] = ['fails'=>0, 'blocked_until'=>0, 'sends'=>[]];
  $blocks[$ip]['fails'] = ($blocks[$ip]['fails'] ?? 0) + 1;
  if($blocks[$ip]['fails'] >= BLOCK_AFTER_FAILED_CAPTCHA){
    $blocks[$ip]['blocked_until'] = time() + BLOCK_DURATION_SECONDS;
  }
  writeJsonFile(IP_FILE, $blocks);
}

function recordSend($ip){
  $blocks = readJsonFile(IP_FILE) ?: [];
  if(!isset($blocks[$ip])) $blocks[$ip] = ['fails'=>0, 'blocked_until'=>0, 'sends'=>[]];
  $blocks[$ip]['sends'][] = time();
  $blocks[$ip]['sends'] = array_slice($blocks[$ip]['sends'], -500);
  writeJsonFile(IP_FILE, $blocks);
}

function countSendsLastHour($ip){
  $blocks = readJsonFile(IP_FILE) ?: [];
  if(!isset($blocks[$ip]['sends'])) return 0;
  $cut = time() - 3600;
  $arr = array_filter($blocks[$ip]['sends'], function($t) use($cut){ return $t >= $cut; });
  return count($arr);
}

function verifyRecaptcha($token){
  if(!RECAPTCHA_SECRET) return true; // kein key gesetzt, akzeptieren
  $url = 'https://www.google.com/recaptcha/api/siteverify';
  $data = http_build_query(['secret'=>RECAPTCHA_SECRET,'response'=>$token,'remoteip'=>ipAddress()]);
  $opts = ['http'=>['method'=>'POST','header'=>"Content-type: application/x-www-form-urlencoded\r\n",'content'=>$data,'timeout'=>10]];
  $context = stream_context_create($opts);
  $resp = @file_get_contents($url, false, $context);
  if(!$resp) return false;
  $j = json_decode($resp, true);
  return !empty($j['success']);
}

// Einfacher text filter
function sanitize($s){
  return trim(htmlspecialchars((string)$s, ENT_QUOTES));
}

// Send mail helper
function sendMail($to, $subject, $html, $plain=''){
  if(USE_PHPMailer && class_exists('PHPMailer\PHPMailer\PHPMailer')){
    try{
      $mail = new PHPMailer\PHPMailer\PHPMailer(true);
      $mail->isSMTP();
      $mail->Host = SMTP_HOST;
      $mail->SMTPAuth = true;
      $mail->Username = SMTP_USER;
      $mail->Password = SMTP_PASS;
      $mail->SMTPSecure = PHPMailer\PHPMailer\PHPMailer::ENCRYPTION_STARTTLS;
      $mail->Port = SMTP_PORT;
      $mail->setFrom(SMTP_FROM, SMTP_NAME);
      $mail->addAddress($to);
      $mail->isHTML(true);
      $mail->Subject = $subject;
      $mail->Body = $html;
      if($plain) $mail->AltBody = $plain;
      $mail->send();
      return true;
    }catch(Exception $e){
      error_log('PHPMailer error ' . $e->getMessage());
    }
  }
  $headers = "From: " . SMTP_NAME . " <" . SMTP_FROM . ">\r\n";
  $headers .= "MIME-Version: 1.0\r\n";
  $headers .= "Content-Type: text/html; charset=UTF-8\r\n";
  return @mail($to, $subject, $html, $headers);
}

// ====== Admin Passwort Management ======
function getAdminData(){
  $a = readJsonFile(ADMIN_FILE) ?: ['user'=>ADMIN_USER,'hash'=>ADMIN_PW_HASH];
  if(empty($a['user'])) $a['user'] = ADMIN_USER;
  return $a;
}
function setAdminHash($hash){
  $a = getAdminData();
  $a['hash'] = $hash;
  writeJsonFile(ADMIN_FILE, $a);
}
function adminVerify($user, $pw){
  $a = getAdminData();
  if($user !== $a['user']) return false;
  if(empty($a['hash'])) return false;
  return password_verify($pw, $a['hash']);
}
function adminIsLogged(){
  return !empty($_SESSION['admin_logged']) && $_SESSION['admin_logged'] === true;
}

// CSRF token
if(empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(16));
function checkCsrf($token){
  return !empty($token) && hash_equals($_SESSION['csrf_token'], $token);
}

// ========== AJAX Endpoints & Admin Actions ==========
if(isset($_GET['action']) && $_GET['action'] === 'send' && $_SERVER['REQUEST_METHOD'] === 'POST'){
  header('Content-Type: application/json; charset=utf-8');
  $ip = ipAddress();
  if($b = isBlocked($ip)){
    echo json_encode(['ok'=>false, 'error'=>'ip_blocked', 'blocked_until'=>$b['blocked_until']]);
    exit;
  }

  $data = json_decode(file_get_contents('php://input'), true);
  $to      = filter_var($data['to'] ?? '', FILTER_VALIDATE_EMAIL) ? $data['to'] : '';
  $subject = sanitize($data['subject'] ?? '');
  $message = sanitize($data['message'] ?? '');
  $name    = sanitize($data['name'] ?? 'Anonym');
  $recaptcha = $data['recaptcha'] ?? '';

  if(!$to || !$subject || !$message){
    echo json_encode(['ok'=>false,'error'=>'missing']);
    exit;
  }

  $recOk = false;
  if(RECAPTCHA_SECRET){
    $recOk = verifyRecaptcha($recaptcha);
  } else {
    if(!empty($_SESSION['math_captcha_answer']) && isset($data['math_answer']) && intval($data['math_answer']) === intval($_SESSION['math_captcha_answer'])){
      $recOk = true;
    } else {
      $recOk = false;
    }
  }

  if(!$recOk){
    recordFailedCaptcha($ip);
    echo json_encode(['ok'=>false,'error'=>'captcha_failed']);
    exit;
  }

  if(countSendsLastHour($ip) >= MAX_SENDS_PER_IP_PER_HOUR){
    $blocks = readJsonFile(IP_FILE) ?: [];
    $blocks[$ip] = $blocks[$ip] ?? ['fails'=>0,'blocked_until'=>0,'sends'=>[]];
    $blocks[$ip]['blocked_until'] = time() + BLOCK_DURATION_SECONDS;
    writeJsonFile(IP_FILE, $blocks);
    echo json_encode(['ok'=>false,'error'=>'rate_limit_exceeded']);
    exit;
  }

  $html = "<html><body>";
  $html .= "<h2>" . htmlspecialchars($subject) . "</h2>";
  $html .= "<p><b>Von</b>: " . htmlspecialchars($name) . " (" . htmlspecialchars($ip) . ")</p>";
  $html .= "<hr>";
  $html .= "<div>" . nl2br(htmlspecialchars($message)) . "</div>";
  $html .= "<hr>";
  $html .= "<p>Gesendet über Mailer</p>";
  $html .= "</body></html>";
  $plain = strip_tags(str_replace(['<br>','<br/>','<br />'], "\n", $html));

  $mailOk = sendMail($to, $subject, $html, $plain);

  $entry = [
    'time' => time(),
    'ip'   => $ip,
    'to'   => $to,
    'subject' => $subject,
    'name' => $name,
    'ok' => $mailOk,
  ];
  logMailEvent($entry);
  if($mailOk){
    recordSend($ip);
    $total = incrementCounter();
    echo json_encode(['ok'=>true,'total'=>$total]);
  } else {
    echo json_encode(['ok'=>false,'error'=>'send_failed']);
  }
  exit;
}

// Status endpoint
if(isset($_GET['action']) && $_GET['action'] === 'status'){
  header('Content-Type: application/json; charset=utf-8');
  $c = readJsonFile(COUNTER_FILE) ?: ['total'=>0];
  $ip = ipAddress();
  $blocked = isBlocked($ip);
  echo json_encode(['ok'=>true, 'total'=>$c['total'], 'blocked' => $blocked ? true : false, 'blocked_until' => $blocked ? $blocked['blocked_until'] : null]);
  exit;
}

// Admin AJAX
if(isset($_GET['action']) && in_array($_GET['action'], ['admin_login','admin_logout','admin_logs','admin_clear_logs','admin_export_csv','admin_block_ip','admin_unblock_ip','admin_set_password','admin_stats'])){
  header('Content-Type: application/json; charset=utf-8');
  $act = $_GET['action'];
  // read body for post
  $data = $_SERVER['REQUEST_METHOD']==='POST' ? json_decode(file_get_contents('php://input'), true) : $_GET;

  if($act === 'admin_login'){
    $user = $data['user'] ?? '';
    $pw = $data['pw'] ?? '';
    if(adminVerify($user,$pw)){
      $_SESSION['admin_logged'] = true;
      echo json_encode(['ok'=>true]);
    } else {
      echo json_encode(['ok'=>false,'error'=>'invalid']);
    }
    exit;
  }

  if($act === 'admin_logout'){
    session_destroy();
    echo json_encode(['ok'=>true]);
    exit;
  }

  // require login for following
  if(!adminIsLogged()){
    echo json_encode(['ok'=>false,'error'=>'auth_required']);
    exit;
  }

  if($act === 'admin_logs'){
    $log = readJsonFile(LOG_FILE) ?: [];
    echo json_encode(['ok'=>true,'log'=>array_reverse(array_slice($log, -100))]);
    exit;
  }

  if($act === 'admin_clear_logs'){
    writeJsonFile(LOG_FILE, []);
    echo json_encode(['ok'=>true]);
    exit;
  }

  if($act === 'admin_export_csv'){
    $log = readJsonFile(LOG_FILE) ?: [];
    header('Content-Type: text/csv; charset=utf-8');
    header('Content-Disposition: attachment; filename="mailer_logs_' . date('Ymd_His') . '.csv"');
    $out = fopen('php://output','w');
    fputcsv($out, ['time','ip','to','subject','name','ok']);
    foreach($log as $row){
      fputcsv($out, [date('c',$row['time']), $row['ip'], $row['to'], $row['subject'], $row['name'], $row['ok'] ? '1' : '0']);
    }
    fclose($out);
    exit;
  }

  if($act === 'admin_block_ip'){
    $ip = $data['ip'] ?? '';
    if($ip){
      $blocks = readJsonFile(IP_FILE) ?: [];
      $blocks[$ip] = $blocks[$ip] ?? ['fails'=>0,'blocked_until'=>0,'sends'=>[]];
      $blocks[$ip]['blocked_until'] = time() + BLOCK_DURATION_SECONDS;
      writeJsonFile(IP_FILE, $blocks);
      echo json_encode(['ok'=>true]);
    } else echo json_encode(['ok'=>false,'error'=>'missing']);
    exit;
  }
  if($act === 'admin_unblock_ip'){
    $ip = $data['ip'] ?? '';
    if($ip){
      $blocks = readJsonFile(IP_FILE) ?: [];
      unset($blocks[$ip]);
      writeJsonFile(IP_FILE, $blocks);
      echo json_encode(['ok'=>true]);
    } else echo json_encode(['ok'=>false,'error'=>'missing']);
    exit;
  }
  if($act === 'admin_set_password'){
    $newpw = $data['newpw'] ?? '';
    if(strlen($newpw) < 6){ echo json_encode(['ok'=>false,'error'=>'short']); exit; }
    $hash = password_hash($newpw, PASSWORD_DEFAULT);
    setAdminHash($hash);
    echo json_encode(['ok'=>true]);
    exit;
  }
  if($act === 'admin_stats'){
    $c = readJsonFile(COUNTER_FILE) ?: ['total'=>0];
    $blocks = readJsonFile(IP_FILE) ?: [];
    $log = readJsonFile(LOG_FILE) ?: [];
    echo json_encode(['ok'=>true,'total'=>$c['total'],'blocked_count'=>count(array_filter($blocks,function($b){return $b['blocked_until']>time();})),'log_recent'=>array_slice($log,-20)]);
    exit;
  }
}

// Admin setup page create initial password if none set
if(isset($_GET['setup_admin']) && $_SERVER['REQUEST_METHOD']==='POST'){
  $pw = $_POST['newpw'] ?? '';
  if(strlen($pw) >= 6){
    setAdminHash(password_hash($pw, PASSWORD_DEFAULT));
    header('Location: ?admin=1');
    exit;
  } else {
    $err = 'Passwort zu kurz';
  }
}

// small endpoint to set math captcha answer in session from client
if(isset($_GET['set_math'])){
  $_SESSION['math_captcha_answer'] = intval($_GET['set_math']);
  echo 'ok';
  exit;
}

// ========== Frontend HTML, CSS, JS ==========
?><!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Professioneller E Mail Mailer</title>
<style>
  :root{
    --bg:#071026;
    --card:#0b1a2b;
    --muted:#9fb4d6;
    --accent:#1fb6ff;
    --success:#34d399;
    --danger:#ff7b7b;
    --glass: rgba(255,255,255,0.03);
  }
  html,body{height:100%;margin:0;font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial;color:#e6f0fb;background:linear-gradient(180deg,var(--bg),#021226)}
  .wrap{max-width:980px;margin:28px auto;padding:20px}
  .card{background:linear-gradient(180deg,var(--card),#071428);border-radius:12px;padding:18px;box-shadow:0 8px 30px rgba(2,6,23,0.7);border:1px solid rgba(255,255,255,0.04)}
  h1{margin:0 0 8px;font-weight:700}
  label{display:block;margin-top:10px;color:var(--muted);font-size:13px}
  input[type=text], input[type=email], textarea, select{width:100%;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.06);background:rgba(255,255,255,0.02);color:inherit;box-sizing:border-box}
  textarea{min-height:120px;resize:vertical}
  .row{display:flex;gap:12px;flex-wrap:wrap}
  .col{flex:1;min-width:120px}
  .btn{background:var(--accent);color:#042431;padding:10px 14px;border-radius:10px;border:none;font-weight:700;cursor:pointer}
  .btn.alt{background:transparent;border:1px solid rgba(255,255,255,0.06);color:var(--muted)}
  .btn:disabled{opacity:0.6;cursor:default}
  .info{margin-top:10px;color:var(--muted);font-size:13px}
  .meta{display:flex;justify-content:space-between;align-items:center;margin-top:12px;font-size:13px;color:var(--muted)}
  .muted{color:var(--muted)}
  .small{font-size:12px;color:var(--muted)}
  .status{padding:8px;border-radius:8px;margin-top:12px}
  .status.ok{background:rgba(52,211,153,0.06);border:1px solid rgba(52,211,153,0.12);color:var(--success)}
  .status.err{background:rgba(255,123,123,0.06);border:1px solid rgba(255,123,123,0.12);color:var(--danger)}
  .flex{display:flex;gap:10px;align-items:center}
  .captcha-box{display:flex;gap:10px;align-items:center;margin-top:10px;flex-wrap:wrap}
  .counter{font-weight:700;color:var(--accent)}
  footer{margin-top:18px;text-align:center;color:var(--muted);font-size:12px}
  .kbd{padding:6px 8px;background:rgba(255,255,255,0.02);border-radius:8px;border:1px solid rgba(255,255,255,0.03);font-size:12px}
  @media(max-width:720px){ .row{flex-direction:column} .meta{flex-direction:column;align-items:flex-start} }
</style>
<?php if(RECAPTCHA_SITE_KEY): ?>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<?php endif; ?>
</head>
<body>
<div class="wrap">
  <div class="card">
    <h1>Professioneller E Mail Mailer</h1>
    <p class="small muted">Sicher, mit Captcha, Limitierung und IP Sperre</p>

    <form id="mailerForm" onsubmit="return false" autocomplete="off">
      <input type="hidden" id="csrfToken" value="<?=htmlspecialchars($_SESSION['csrf_token'])?>" />
      <label for="to">Empfänger Email</label>
      <input id="to" type="email" placeholder="empfaenger@domain.tld" required />

      <div class="row">
        <div class="col">
          <label for="name">Dein Name</label>
          <input id="name" type="text" placeholder="Name" />
        </div>
        <div style="width:180px">
          <label for="subject">Betreff</label>
          <input id="subject" type="text" placeholder="Betreff" />
        </div>
      </div>

      <label for="message">Nachricht</label>
      <textarea id="message" placeholder="Deine Nachricht"></textarea>

      <div id="captchaArea" class="captcha-box">
<?php if(RECAPTCHA_SITE_KEY): ?>
        <div class="g-recaptcha" data-sitekey="<?=htmlspecialchars(RECAPTCHA_SITE_KEY)?>"></div>
<?php else: ?>
        <div id="mathCaptcha" class="small muted">Berechne</div>
<?php endif; ?>
      </div>

      <div style="display:flex;gap:10px;margin-top:14px;align-items:center;flex-wrap:wrap">
        <button id="sendBtn" class="btn">Senden</button>
        <button id="resetBtn" class="btn alt" type="button">Zurücksetzen</button>
        <div style="margin-left:auto" class="meta">
          <div>Gesendet insgesamt <span id="totalCounter" class="counter">0</span></div>
        </div>
      </div>

      <div id="statusBox" class="status" style="display:none"></div>

      <div class="info">
        <p class="small muted">Hinweis: Bei wiederholtem fehlschlagen des Captcha oder zu vielen Versendungen wird die IP temporär gesperrt</p>
      </div>
    </form>
  </div>

  <div style="height:14px"></div>

  <div class="card" id="adminCard">
    <div style="display:flex;align-items:center;gap:12px">
      <h2 style="margin:0">Admin Bereich</h2>
      <div style="margin-left:auto" id="adminControls"></div>
    </div>
    <p class="small muted">Logs, Export und IP Verwaltung</p>

    <div id="adminArea" style="margin-top:12px;display:none">
      <div style="display:flex;gap:10px;flex-wrap:wrap">
        <button id="btnFetchLogs" class="btn alt">Logs aktualisieren</button>
        <button id="btnClearLogs" class="btn alt">Logs löschen</button>
        <button id="btnExportCSV" class="btn">CSV export</button>
        <button id="btnStats" class="btn alt">Statistik</button>
      </div>

      <div style="margin-top:12px">
        <h3 style="margin:8px 0">Letzte Einträge</h3>
        <pre id="logPreview" style="background:rgba(255,255,255,0.02);padding:12px;border-radius:8px;max-height:260px;overflow:auto"></pre>
      </div>

      <div style="margin-top:12px">
        <h3 style="margin:8px 0">IP Verwaltung</h3>
        <div style="display:flex;gap:8px">
          <input id="ipInput" placeholder="IP Adresse" style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04)"></input>
          <button id="btnBlockIP" class="btn alt">Sperren</button>
          <button id="btnUnblockIP" class="btn alt">Entsperren</button>
        </div>
        <div id="blockedList" style="margin-top:8px"></div>
      </div>

      <div style="margin-top:12px">
        <h3 style="margin:8px 0">Admin Passwort ändern</h3>
        <div style="display:flex;gap:8px">
          <input id="newPw" type="password" placeholder="neues Passwort" style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04)">
          <button id="btnSetPw" class="btn">Setzen</button>
        </div>
        <div id="pwMsg" class="small muted" style="margin-top:6px"></div>
      </div>
    </div>

    <div id="loginArea" style="margin-top:12px">
      <h3 style="margin:8px 0">Admin Login</h3>
      <div style="display:flex;gap:8px;flex-wrap:wrap">
        <input id="adminUser" placeholder="Benutzer" value="<?=htmlspecialchars(getAdminData()['user'] ?? ADMIN_USER)?>" style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04)">
        <input id="adminPw" type="password" placeholder="Passwort" style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.04)">
        <button id="btnLogin" class="btn">Anmelden</button>
      </div>
      <div id="loginMsg" class="small muted" style="margin-top:8px"></div>
    </div>
  </div>

<footer>© <?=date('Y')?> <a href="https://www.dreamcodes.net" target="_blank" rel="noopener">Dreamcodes</a> — Mailer</footer>

</div>

<script>
(async function(){
  const recaptchaKey = "<?=htmlspecialchars(RECAPTCHA_SITE_KEY)?>";
  const csrfToken = document.getElementById('csrfToken').value;
  const SEND_URL = '?action=send';
  const STATUS_URL = '?action=status';

  const form = document.getElementById('mailerForm');
  const toEl = document.getElementById('to');
  const nameEl = document.getElementById('name');
  const subjectEl = document.getElementById('subject');
  const messageEl = document.getElementById('message');
  const sendBtn = document.getElementById('sendBtn');
  const resetBtn = document.getElementById('resetBtn');
  const statusBox = document.getElementById('statusBox');
  const totalCounterEl = document.getElementById('totalCounter');
  const captchaArea = document.getElementById('captchaArea');

  const adminControls = document.getElementById('adminControls');
  const adminArea = document.getElementById('adminArea');
  const loginArea = document.getElementById('loginArea');
  const btnLogin = document.getElementById('btnLogin');
  const btnFetchLogs = document.getElementById('btnFetchLogs');
  const btnClearLogs = document.getElementById('btnClearLogs');
  const btnExportCSV = document.getElementById('btnExportCSV');
  const btnStats = document.getElementById('btnStats');
  const logPreview = document.getElementById('logPreview');
  const btnBlockIP = document.getElementById('btnBlockIP');
  const btnUnblockIP = document.getElementById('btnUnblockIP');
  const ipInput = document.getElementById('ipInput');
  const blockedList = document.getElementById('blockedList');
  const newPw = document.getElementById('newPw');
  const btnSetPw = document.getElementById('btnSetPw');
  const pwMsg = document.getElementById('pwMsg');
  const loginMsg = document.getElementById('loginMsg');

  let mathAnswer = null;

  function showStatus(ok, txt){
    statusBox.style.display = 'block';
    statusBox.className = 'status ' + (ok ? 'ok' : 'err');
    statusBox.textContent = txt;
  }
  function hideStatus(){
    statusBox.style.display = 'none';
  }
  function setLoading(v){
    sendBtn.disabled = v; resetBtn.disabled = v;
  }

  async function fetchStatus(){
    try{
      const r = await fetch(STATUS_URL);
      const j = await r.json();
      totalCounterEl.textContent = j.total || 0;
      if(j.blocked) showStatus(false, 'Deine IP ist temporär gesperrt');
      else hideStatus();
    }catch(e){ console.error(e); }
  }

  async function loadLogs(){
    // fetch admin logs via admin ajax
    const res = await fetch('?action=admin_logs');
    if(res.status === 200){
      const j = await res.json();
      if(j.ok){
        const recent = j.log || [];
        logPreview.textContent = recent.map(x => {
          const t = new Date(x.time*1000).toLocaleString();
          return `[${t}] ${x.ip} → ${x.to} ${x.ok ? 'OK' : 'FEHLER'} ${x.subject}`;
        }).join("\n");
      } else {
        if(j.error === 'auth_required'){ adminArea.style.display='none'; loginArea.style.display='block'; }
      }
    } else console.error('logs fetch status', res.status);
  }

  async function loadBlocked(){
    const res = await fetch('?action=admin_stats');
    if(res.status === 200){
      const j = await res.json();
      if(j.ok){
        const blocks = j.log_recent || [];
        // show blocked count
        blockedList.innerHTML = 'Geblockte IPs ' + (j.blocked_count || 0);
      }
    }
  }

  function generateMathCaptcha(){
    const a = Math.floor(Math.random()*9)+1;
    const b = Math.floor(Math.random()*9)+1;
    mathAnswer = a + b;
    fetch('?set_math=' + mathAnswer).catch(()=>{});
    const el = document.getElementById('mathCaptcha');
    if(el) el.textContent = 'Bitte lösen: ' + a + ' + ' + b + ' =';
    // add small input
    if(!document.getElementById('mathAnswer')){
      const inp = document.createElement('input'); inp.id='mathAnswer'; inp.style.width='80px';
      captchaArea.appendChild(inp);
    }
  }

  if(!recaptchaKey) generateMathCaptcha();

  sendBtn.addEventListener('click', async function(){
    setLoading(true);
    hideStatus();
    const to = toEl.value.trim();
    const subject = subjectEl.value.trim();
    const message = messageEl.value.trim();
    const name = nameEl.value.trim() || 'Anonym';
    if(!to || !subject || !message){ showStatus(false,'Bitte fülle alle Felder aus'); setLoading(false); return; }

    let recaptchaToken = '';
    let mathVal = '';
    if(recaptchaKey){
      // try to get token if grecaptcha available
      if(window.grecaptcha){
        try{
          recaptchaToken = await grecaptcha.execute(recaptchaKey, {action: 'submit'});
        }catch(e){ console.warn('grecaptcha execute failed', e); }
      }
    } else {
      const ma = document.getElementById('mathAnswer'); mathVal = ma ? ma.value.trim() : '';
    }

    const payload = { to, subject, message, name, recaptcha: recaptchaToken, math_answer: mathVal, csrf: csrfToken };
    try{
      const res = await fetch('?action=send', {
        method: 'POST',
        headers: {'Content-Type':'application/json'},
        body: JSON.stringify(payload)
      });
      const j = await res.json();
      if(j.ok){
        showStatus(true, 'Mail erfolgreich gesendet. Insgesamt gesendet: ' + (j.total || '?'));
        await fetchStatus();
        if(!recaptchaKey) generateMathCaptcha();
      } else {
        let msg = 'Fehler beim Senden';
        if(j.error === 'captcha_failed') msg = 'Captcha ungültig';
        if(j.error === 'missing') msg = 'Eingabefelder fehlen';
        if(j.error === 'rate_limit_exceeded') msg = 'Zu viele Versuche, IP temporär gesperrt';
        if(j.error === 'ip_blocked') msg = 'Deine IP ist gesperrt';
        if(j.error === 'send_failed') msg = 'Versand fehlgeschlagen';
        showStatus(false, msg);
      }
    }catch(e){
      console.error(e);
      showStatus(false, 'Netzwerkfehler');
    }
    setLoading(false);
  });

  resetBtn.addEventListener('click', ()=>{ toEl.value=''; nameEl.value=''; subjectEl.value=''; messageEl.value=''; if(!recaptchaKey){ generateMathCaptcha(); document.getElementById('mathAnswer').value=''; } });

  // Admin login
  btnLogin.addEventListener('click', async ()=>{
    const user = document.getElementById('adminUser').value.trim();
    const pw = document.getElementById('adminPw').value;
    const res = await fetch('?action=admin_login', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({user,pw})});
    const j = await res.json();
    if(j.ok){ loginArea.style.display='none'; adminArea.style.display='block'; adminControls.innerHTML = '<button id=\"logoutBtn\" class=\"btn alt\">Abmelden</button>'; document.getElementById('logoutBtn').addEventListener('click', adminLogout); loadLogs(); loadBlocked(); }
    else loginMsg.textContent = 'Login fehlgeschlagen';
  });

  async function adminLogout(){
    const res = await fetch('?action=admin_logout');
    const j = await res.json();
    if(j.ok){ adminArea.style.display='none'; loginArea.style.display='block'; adminControls.innerHTML=''; }
  }

  btnFetchLogs.addEventListener('click', ()=>loadLogs());
  btnClearLogs.addEventListener('click', async ()=>{
    if(!confirm('Logs löschen?')) return;
    const res = await fetch('?action=admin_clear_logs');
    const j = await res.json();
    if(j.ok) { logPreview.textContent=''; alert('Logs gelöscht'); }
  });
  btnExportCSV.addEventListener('click', ()=>{ window.location.href='?action=admin_export_csv'; });

  btnBlockIP.addEventListener('click', async ()=>{
    const ip = ipInput.value.trim(); if(!ip) return alert('IP eingeben');
    const res = await fetch('?action=admin_block_ip', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ip})});
    const j = await res.json(); if(j.ok) { alert('IP gesperrt'); loadBlocked(); }
  });
  btnUnblockIP.addEventListener('click', async ()=>{
    const ip = ipInput.value.trim(); if(!ip) return alert('IP eingeben');
    const res = await fetch('?action=admin_unblock_ip', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ip})});
    const j = await res.json(); if(j.ok) { alert('IP entsperrt'); loadBlocked(); }
  });

  btnSetPw.addEventListener('click', async ()=>{
    const v = newPw.value.trim();
    if(v.length < 6){ pwMsg.textContent='Passwort zu kurz minimal 6 Zeichen'; return; }
    const res = await fetch('?action=admin_set_password', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({newpw:v})});
    const j = await res.json();
    if(j.ok){ pwMsg.textContent='Passwort gesetzt'; newPw.value=''; } else pwMsg.textContent='Fehler';
  });

  btnStats.addEventListener('click', async ()=>{
    const res = await fetch('?action=admin_stats');
    const j = await res.json();
    if(j.ok){ alert('Total gesendet: ' + j.total + '\nGeblockte IPs: ' + j.blocked_count); }
  });

  // Initial
  await fetchStatus();
  // try to fetch logs only if admin already logged in server side
  await loadLogs();

  // If no admin password set then show setup notice
  const adminDataRes = await fetch('?action=admin_stats');
  if(adminDataRes.status === 200){
    const jd = await adminDataRes.json();
    if(jd && jd.ok) { /* ok */ }
  } else {
    // ignore
  }

})();
</script>
</body>
</html>
Dreamcodes Redaktion
Dreamcodes Redaktion
Qualität als Standard. Verantwortung als Prinzip. Jede Ressource auf Dreamcodes basiert auf geprüften Best Practices und fundierter Praxiserfahrung. Unser Anspruch ist ein belastbares Fundament statt experimenteller Lösungen. Die Integration und Absicherung der Inhalte liegt in Ihrem Ermessen. Wir liefern die fachliche Basis, die Verantwortung für den produktiven Einsatz verbleibt bei Ihnen.
Vorheriges Tutorial
Nächstes Tutorial

Vielleicht einen Blick WERT?