Montag, 25 August 2025

Top 5 diese Woche

Ähnliche Tutorials

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="http://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>
Vorheriges Tutorial
Nächstes Tutorial

Hier etwas für dich dabei?