WebOrbiton
v3.0.0.2

FlatlyPage

1,204 lines · 41.6 KB
  1. <?php
  2. ​
  3. session_start();
  4. ​
  5. if (!file_exists('hash.php')) {
  6. header('Location: settings.php');
  7. exit;
  8. }
  9. ​
  10. $admin_accounts = include('hash.php');
  11. ​
  12. if (!isset($_SESSION['newsletter_admin'])) {
  13. if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['username']) && isset($_POST['password'])) {
  14. $username = trim($_POST['username']);
  15. $password = $_POST['password'];
  16. if (isset($admin_accounts[$username]) && password_verify($password, $admin_accounts[$username]['password_hash'])) {
  17. session_regenerate_id(true);
  18. $_SESSION['newsletter_admin'] = true;
  19. $_SESSION['admin_username'] = $username;
  20. header('Location: manager.php');
  21. exit;
  22. } else {
  23. $login_error = "That username or password isn't right.";
  24. }
  25. }
  26. ?>
  27. <!DOCTYPE html>
  28. <html lang="en">
  29. <head>
  30. <meta charset="UTF-8">
  31. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  32. <title>Newsletter Manager - Login</title>
  33. <link rel="icon" href="nl.ico?v=<?= filemtime(__DIR__ . '/newsletter/nl.ico') ?>" type="image/x-icon">
  34. <style>
  35. * { margin: 0; padding: 0; box-sizing: border-box; }
  36. :root {
  37. --bg: #0a0a0a;
  38. --bg-elevated: #111;
  39. --bg-card: #161616;
  40. --bg-hover: #1c1c1c;
  41. --border: #262626;
  42. --border-focus: #404040;
  43. --text: #fafafa;
  44. --text-muted: #737373;
  45. --text-subtle: #525252;
  46. --accent: #fff;
  47. --success: #22c55e;
  48. --error: #ef4444;
  49. --blue: #3b82f6;
  50. --radius: 12px;
  51. --radius-sm: 8px;
  52. }
  53. body {
  54. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  55. background: var(--bg);
  56. display: flex;
  57. align-items: center;
  58. justify-content: center;
  59. min-height: 100vh;
  60. padding: 20px;
  61. }
  62. .login-box {
  63. background: var(--bg-card);
  64. border: 1px solid var(--border);
  65. border-radius: var(--radius);
  66. padding: 40px;
  67. max-width: 400px;
  68. width: 100%;
  69. }
  70. .icon-lock {
  71. width: 48px;
  72. height: 48px;
  73. margin: 0 auto 20px;
  74. display: block;
  75. }
  76. h1 {
  77. color: var(--text);
  78. margin-bottom: 10px;
  79. font-size: 24px;
  80. text-align: center;
  81. }
  82. p {
  83. color: var(--text-muted);
  84. margin-bottom: 30px;
  85. font-size: 14px;
  86. text-align: center;
  87. }
  88. input[type="text"],
  89. input[type="password"] {
  90. width: 100%;
  91. padding: 12px;
  92. border: 1px solid var(--border);
  93. background: var(--bg-elevated);
  94. color: var(--text);
  95. border-radius: var(--radius-sm);
  96. font-size: 16px;
  97. margin-bottom: 20px;
  98. transition: border-color 0.3s;
  99. }
  100. input[type="text"]:focus,
  101. input[type="password"]:focus {
  102. outline: none;
  103. border-color: var(--border-focus);
  104. }
  105. button {
  106. width: 100%;
  107. padding: 12px;
  108. background: var(--accent);
  109. color: var(--bg);
  110. border: none;
  111. border-radius: var(--radius-sm);
  112. font-size: 16px;
  113. font-weight: 600;
  114. cursor: pointer;
  115. transition: opacity 0.2s;
  116. }
  117. button:hover { opacity: 0.9; }
  118. .error {
  119. background: rgba(239, 68, 68, 0.1);
  120. color: var(--error);
  121. border: 1px solid rgba(239, 68, 68, 0.2);
  122. padding: 12px;
  123. border-radius: var(--radius-sm);
  124. margin-bottom: 20px;
  125. font-size: 14px;
  126. }
  127. </style>
  128. </head>
  129. <body>
  130. <div class="login-box">
  131. <svg class="icon-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: var(--text-muted);">
  132. <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
  133. <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
  134. </svg>
  135. <h1>Newsletter Manager</h1>
  136. <p>Sign in to manage your newsletter</p>
  137. <?php if (isset($login_error)): ?>
  138. <div class="error"><?= htmlspecialchars($login_error) ?></div>
  139. <?php endif; ?>
  140. <form method="POST">
  141. <input type="text" name="username" placeholder="Username" required autofocus>
  142. <input type="password" name="password" placeholder="Password" required>
  143. <button type="submit">Login</button>
  144. </form>
  145. </div>
  146. </body>
  147. </html>
  148. <?php
  149. exit;
  150. }
  151. ​
  152. if (isset($_GET['logout'])) {
  153. session_destroy();
  154. header('Location: manager.php');
  155. exit;
  156. }
  157. ​
  158. if (empty($_SESSION['newsletter_csrf']) || !is_string($_SESSION['newsletter_csrf'])) {
  159. $_SESSION['newsletter_csrf'] = bin2hex(random_bytes(32));
  160. }
  161. $csrf_token = $_SESSION['newsletter_csrf'];
  162. $csrf_valid = $_SERVER['REQUEST_METHOD'] === 'POST' && is_string($_POST['csrf_token'] ?? null) && hash_equals($csrf_token, $_POST['csrf_token']);
  163. $csrf_field = '<input type="hidden" name="csrf_token" value="' . htmlspecialchars($csrf_token) . '">';
  164. $csrf_error = $_SERVER['REQUEST_METHOD'] === 'POST' && !$csrf_valid ? 'Your session timed out. Reload the page and try again.' : null;
  165. ​
  166. if (!file_exists('config.php')) {
  167. ​
  168. if ($csrf_error !== null) {
  169. $setup_error = $csrf_error;
  170. } elseif ($csrf_valid) {
  171. if (isset($_POST['enable_newsletter'])) {
  172. ​
  173. $default_config = "<?php
  174. // config.php - Configuration
  175. ​
  176. // === DATABASE SETTINGS ===
  177. define('DB_HOST', 'localhost');
  178. define('DB_NAME', 'your_database'); // CHANGE THIS
  179. define('DB_USER', 'your_user'); // CHANGE THIS
  180. define('DB_PASS', 'your_password'); // CHANGE THIS
  181. ​
  182. // === EMAIL SETTINGS ===
  183. define('SENDER_EMAIL', 'newsletter@yoursite.com'); // CHANGE THIS
  184. define('SENDER_NAME', 'Your Company'); // CHANGE THIS
  185. define('SITE_URL', 'https://yoursite.com'); // CHANGE THIS
  186. ​
  187. // === GENERAL SETTINGS ===
  188. define('REQUIRE_CONFIRMATION', true); // require email confirmation?
  189. ​
  190. // Database connection function
  191. function getDB() {
  192. static \$pdo = null;
  193. if (\$pdo === null) {
  194. try {
  195. \$pdo = new PDO(
  196. \"mysql:host=\" . DB_HOST . \";dbname=\" . DB_NAME . \";charset=utf8mb4\",
  197. DB_USER,
  198. DB_PASS,
  199. [
  200. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  201. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
  202. ]
  203. );
  204. } catch (PDOException \$e) {
  205. error_log(\"Database connection failed: \" . \$e->getMessage());
  206. return null;
  207. }
  208. }
  209. return \$pdo;
  210. }
  211. ​
  212. // Safe JSON response function
  213. function jsonResponse(\$success, \$message) {
  214. header('Content-Type: application/json');
  215. echo json_encode([
  216. 'success' => \$success,
  217. 'message' => \$message
  218. ]);
  219. exit;
  220. }
  221. ?>";
  222. if (file_put_contents('config.php', $default_config)) {
  223. header('Location: manager.php');
  224. exit;
  225. } else {
  226. $setup_error = "Couldn't turn on the newsletter. Check the folder permissions on your server.";
  227. }
  228. } elseif (isset($_POST['disable_newsletter'])) {
  229. ​
  230. header('Location: ' . rtrim(str_replace('\\', '/', dirname(dirname($_SERVER['SCRIPT_NAME']))), '/') . '/admin/');
  231. exit;
  232. }
  233. }
  234. ?>
  235. <!DOCTYPE html>
  236. <html lang="en">
  237. <head>
  238. <meta charset="UTF-8">
  239. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  240. <title>Enable Newsletter Feature</title>
  241. <link rel="icon" href="nl.ico?v=<?= filemtime(__DIR__ . '/nl.ico') ?>" type="image/x-icon">
  242. <style>
  243. * { margin: 0; padding: 0; box-sizing: border-box; }
  244. :root {
  245. --bg: #0a0a0a;
  246. --bg-elevated: #111;
  247. --bg-card: #161616;
  248. --bg-hover: #1c1c1c;
  249. --border: #262626;
  250. --border-focus: #404040;
  251. --text: #fafafa;
  252. --text-muted: #737373;
  253. --text-subtle: #525252;
  254. --accent: #fff;
  255. --success: #22c55e;
  256. --error: #ef4444;
  257. --blue: #3b82f6;
  258. --radius: 12px;
  259. --radius-sm: 8px;
  260. }
  261. body {
  262. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  263. background: var(--bg);
  264. display: flex;
  265. align-items: center;
  266. justify-content: center;
  267. min-height: 100vh;
  268. padding: 20px;
  269. }
  270. .prompt-box {
  271. background: var(--bg-card);
  272. border: 1px solid var(--border);
  273. border-radius: var(--radius);
  274. padding: 40px;
  275. max-width: 500px;
  276. width: 100%;
  277. }
  278. .icon-mail {
  279. width: 64px;
  280. height: 64px;
  281. margin: 0 auto 20px;
  282. display: block;
  283. color: var(--text-muted);
  284. }
  285. h1 {
  286. color: var(--text);
  287. margin-bottom: 10px;
  288. font-size: 24px;
  289. text-align: center;
  290. }
  291. p {
  292. color: var(--text-muted);
  293. margin-bottom: 30px;
  294. font-size: 14px;
  295. text-align: center;
  296. line-height: 1.6;
  297. }
  298. .button-group {
  299. display: flex;
  300. gap: 12px;
  301. flex-direction: column;
  302. }
  303. button {
  304. width: 100%;
  305. padding: 14px;
  306. border: none;
  307. border-radius: var(--radius-sm);
  308. font-size: 16px;
  309. font-weight: 600;
  310. cursor: pointer;
  311. transition: opacity 0.2s;
  312. display: flex;
  313. align-items: center;
  314. justify-content: center;
  315. gap: 8px;
  316. }
  317. button:hover { opacity: 0.9; }
  318. .btn-primary {
  319. background: var(--success);
  320. color: white;
  321. }
  322. .btn-secondary {
  323. background: var(--bg-hover);
  324. color: var(--text);
  325. border: 1px solid var(--border);
  326. }
  327. .error {
  328. background: rgba(239, 68, 68, 0.1);
  329. color: var(--error);
  330. border: 1px solid rgba(239, 68, 68, 0.2);
  331. padding: 12px;
  332. border-radius: var(--radius-sm);
  333. margin-bottom: 20px;
  334. font-size: 14px;
  335. text-align: center;
  336. }
  337. .features {
  338. background: var(--bg-elevated);
  339. border: 1px solid var(--border);
  340. border-radius: var(--radius-sm);
  341. padding: 20px;
  342. margin-bottom: 30px;
  343. }
  344. .features h3 {
  345. color: var(--text);
  346. font-size: 14px;
  347. margin-bottom: 12px;
  348. font-weight: 600;
  349. }
  350. .features ul {
  351. list-style: none;
  352. color: var(--text-muted);
  353. font-size: 13px;
  354. line-height: 1.8;
  355. }
  356. .features li::before {
  357. content: "✓ ";
  358. color: var(--success);
  359. font-weight: bold;
  360. margin-right: 8px;
  361. }
  362. .logout-link {
  363. text-align: center;
  364. margin-top: 20px;
  365. }
  366. .logout-link a {
  367. color: var(--text-muted);
  368. text-decoration: none;
  369. font-size: 14px;
  370. }
  371. .logout-link a:hover {
  372. color: var(--text);
  373. }
  374. </style>
  375. </head>
  376. <body>
  377. <div class="prompt-box">
  378. <svg class="icon-mail" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  379. <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
  380. <polyline points="22,6 12,13 2,6"></polyline>
  381. </svg>
  382. <h1>Turn on the newsletter?</h1>
  383. <p>This lets visitors sign up for your emails. You can turn it off again later.</p>
  384. <?php if (isset($setup_error)): ?>
  385. <div class="error"><?= htmlspecialchars($setup_error) ?></div>
  386. <?php endif; ?>
  387. <div class="features">
  388. <h3>This will enable:</h3>
  389. <ul>
  390. <li>Email subscription management</li>
  391. <li>Double opt-in confirmation</li>
  392. <li>Subscriber database</li>
  393. <li>CSV export functionality</li>
  394. <li>Admin management panel</li>
  395. </ul>
  396. </div>
  397. <form method="POST" class="button-group">
  398. <?= $csrf_field ?>
  399. <button type="submit" name="enable_newsletter" class="btn-primary">
  400. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  401. <polyline points="20 6 9 17 4 12"></polyline>
  402. </svg>
  403. Yes, Enable Newsletter
  404. </button>
  405. <button type="submit" name="disable_newsletter" class="btn-secondary">
  406. <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  407. <line x1="18" y1="6" x2="6" y2="18"></line>
  408. <line x1="6" y1="6" x2="18" y2="18"></line>
  409. </svg>
  410. No, Return to Admin
  411. </button>
  412. </form>
  413. <div class="logout-link">
  414. <a href="?logout">Logout</a>
  415. </div>
  416. </div>
  417. </body>
  418. </html>
  419. <?php
  420. exit;
  421. }
  422. ​
  423. require_once 'config.php';
  424. ​
  425. if ($csrf_error !== null) {
  426. $config_message = $csrf_error;
  427. $config_success = false;
  428. }
  429. ​
  430. if ($csrf_valid && isset($_POST['setup'])) {
  431. $pdo = getDB();
  432. if ($pdo) {
  433. try {
  434. $pdo->exec("
  435. CREATE TABLE IF NOT EXISTS `newsletter_subscribers` (
  436. `id` int(11) NOT NULL AUTO_INCREMENT,
  437. `email` varchar(255) NOT NULL,
  438. `token` varchar(64) NOT NULL,
  439. `confirmed` tinyint(1) DEFAULT 0,
  440. `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  441. `confirmed_at` timestamp NULL DEFAULT NULL,
  442. `ip_address` varchar(45) DEFAULT NULL,
  443. PRIMARY KEY (`id`),
  444. UNIQUE KEY `email` (`email`),
  445. KEY `idx_token` (`token`),
  446. KEY `idx_confirmed` (`confirmed`)
  447. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  448. ");
  449. $setup_message = 'Database is ready.';
  450. $setup_success = true;
  451. } catch (PDOException $e) {
  452. $setup_message = "Couldn't set up the database: " . $e->getMessage();
  453. $setup_success = false;
  454. }
  455. } else {
  456. $setup_message = "Couldn't connect to the database. Check the database settings below.";
  457. $setup_success = false;
  458. }
  459. }
  460. ​
  461. if ($csrf_valid && isset($_POST['update_config'])) {
  462. $config_content = "<?php
  463. // config.php - Configuration
  464. ​
  465. // === DATABASE SETTINGS ===
  466. define('DB_HOST', '" . addslashes($_POST['db_host']) . "');
  467. define('DB_NAME', '" . addslashes($_POST['db_name']) . "');
  468. define('DB_USER', '" . addslashes($_POST['db_user']) . "');
  469. define('DB_PASS', '" . addslashes($_POST['db_pass']) . "');
  470. ​
  471. // === EMAIL SETTINGS ===
  472. define('SENDER_EMAIL', '" . addslashes($_POST['sender_email']) . "');
  473. define('SENDER_NAME', '" . addslashes($_POST['sender_name']) . "');
  474. define('SITE_URL', '" . addslashes($_POST['site_url']) . "');
  475. ​
  476. // === GENERAL SETTINGS ===
  477. define('REQUIRE_CONFIRMATION', " . (isset($_POST['require_confirmation']) ? 'true' : 'false') . ");
  478. ​
  479. // Database connection function
  480. function getDB() {
  481. static \$pdo = null;
  482. if (\$pdo === null) {
  483. try {
  484. \$pdo = new PDO(
  485. \"mysql:host=\" . DB_HOST . \";dbname=\" . DB_NAME . \";charset=utf8mb4\",
  486. DB_USER,
  487. DB_PASS,
  488. [
  489. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  490. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
  491. ]
  492. );
  493. } catch (PDOException \$e) {
  494. error_log(\"Database connection failed: \" . \$e->getMessage());
  495. return null;
  496. }
  497. }
  498. return \$pdo;
  499. }
  500. ​
  501. // Safe JSON response function
  502. function jsonResponse(\$success, \$message) {
  503. header('Content-Type: application/json');
  504. echo json_encode([
  505. 'success' => \$success,
  506. 'message' => \$message
  507. ]);
  508. exit;
  509. }
  510. ?>";
  511. ​
  512. if (file_put_contents('config.php', $config_content)) {
  513. $config_message = 'Settings saved.';
  514. $config_success = true;
  515. require_once 'config.php';
  516. } else {
  517. $config_message = "Couldn't save the settings. Check the folder permissions on your server.";
  518. $config_success = false;
  519. }
  520. }
  521. ​
  522. if ($csrf_valid && isset($_POST['delete'])) {
  523. $pdo = getDB();
  524. if ($pdo) {
  525. try {
  526. $stmt = $pdo->prepare("DELETE FROM newsletter_subscribers WHERE id = ?");
  527. $stmt->execute([(int) $_POST['delete']]);
  528. $delete_message = 'Subscriber deleted.';
  529. } catch (PDOException $e) {
  530. $delete_message = "Couldn't delete the subscriber.";
  531. }
  532. }
  533. }
  534. ​
  535. if (isset($_GET['export'])) {
  536. $pdo = getDB();
  537. if ($pdo) {
  538. $stmt = $pdo->query("SELECT email, confirmed, created_at, confirmed_at FROM newsletter_subscribers ORDER BY created_at DESC");
  539. $subscribers = $stmt->fetchAll();
  540. header('Content-Type: text/csv');
  541. header('Content-Disposition: attachment; filename="newsletter-subscribers-' . date('Y-m-d') . '.csv"');
  542. $output = fopen('php://output', 'w');
  543. fputcsv($output, ['Email', 'Confirmed', 'Created At', 'Confirmed At']);
  544. foreach ($subscribers as $sub) {
  545. fputcsv($output, [
  546. $sub['email'],
  547. $sub['confirmed'] ? 'Yes' : 'No',
  548. $sub['created_at'],
  549. $sub['confirmed_at'] ?? 'N/A'
  550. ]);
  551. }
  552. fclose($output);
  553. exit;
  554. }
  555. }
  556. ​
  557. $pdo = getDB();
  558. $subscribers = [];
  559. $stats = ['total' => 0, 'confirmed' => 0, 'pending' => 0];
  560. ​
  561. if ($pdo) {
  562. try {
  563. $stmt = $pdo->query("SELECT
  564. COUNT(*) as total,
  565. SUM(confirmed = 1) as confirmed,
  566. SUM(confirmed = 0) as pending
  567. FROM newsletter_subscribers
  568. ");
  569. $stats = $stmt->fetch();
  570. $stmt = $pdo->query("SELECT * FROM newsletter_subscribers ORDER BY created_at DESC LIMIT 100");
  571. $subscribers = $stmt->fetchAll();
  572. } catch (PDOException $e) {
  573. $db_error = $e->getMessage();
  574. }
  575. }
  576. ​
  577. ?>
  578. <!DOCTYPE html>
  579. <html lang="en">
  580. <head>
  581. <meta charset="UTF-8">
  582. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  583. <title>Newsletter Manager</title>
  584. <style>
  585. * { margin: 0; padding: 0; box-sizing: border-box; }
  586. :root {
  587. --bg: #0a0a0a;
  588. --bg-elevated: #111;
  589. --bg-card: #161616;
  590. --bg-hover: #1c1c1c;
  591. --border: #262626;
  592. --border-focus: #404040;
  593. --text: #fafafa;
  594. --text-muted: #737373;
  595. --text-subtle: #525252;
  596. --accent: #fff;
  597. --success: #22c55e;
  598. --error: #ef4444;
  599. --blue: #3b82f6;
  600. --radius: 12px;
  601. --radius-sm: 8px;
  602. }
  603. body {
  604. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  605. background: var(--bg);
  606. color: var(--text);
  607. padding: 20px;
  608. }
  609. .container {
  610. max-width: 1200px;
  611. margin: 0 auto;
  612. }
  613. .header {
  614. background: var(--bg-card);
  615. border: 1px solid var(--border);
  616. padding: 30px;
  617. border-radius: var(--radius);
  618. margin-bottom: 30px;
  619. display: flex;
  620. justify-content: space-between;
  621. align-items: center;
  622. }
  623. .header-title {
  624. display: flex;
  625. align-items: center;
  626. gap: 12px;
  627. }
  628. .header-title svg {
  629. width: 32px;
  630. height: 32px;
  631. }
  632. .header h1 { font-size: 28px; }
  633. .logout-btn {
  634. background: var(--bg-hover);
  635. color: var(--text);
  636. padding: 10px 20px;
  637. border: 1px solid var(--border);
  638. border-radius: var(--radius-sm);
  639. text-decoration: none;
  640. font-size: 14px;
  641. cursor: pointer;
  642. transition: background 0.3s;
  643. display: inline-flex;
  644. align-items: center;
  645. gap: 8px;
  646. }
  647. .logout-btn:hover { background: var(--bg-elevated); }
  648. .stats {
  649. display: grid;
  650. grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  651. gap: 20px;
  652. margin-bottom: 30px;
  653. }
  654. .stat-card {
  655. background: var(--bg-card);
  656. border: 1px solid var(--border);
  657. padding: 25px;
  658. border-radius: var(--radius);
  659. }
  660. .stat-card h3 {
  661. color: var(--text-muted);
  662. font-size: 14px;
  663. font-weight: 500;
  664. margin-bottom: 10px;
  665. text-transform: uppercase;
  666. }
  667. .stat-card .number {
  668. font-size: 36px;
  669. font-weight: 700;
  670. color: var(--text);
  671. }
  672. .card {
  673. background: var(--bg-card);
  674. border: 1px solid var(--border);
  675. border-radius: var(--radius);
  676. padding: 30px;
  677. margin-bottom: 30px;
  678. }
  679. .card h2 {
  680. margin-bottom: 20px;
  681. color: var(--text);
  682. font-size: 20px;
  683. display: flex;
  684. align-items: center;
  685. gap: 10px;
  686. }
  687. .card h2 svg {
  688. width: 24px;
  689. height: 24px;
  690. }
  691. .alert {
  692. padding: 15px 20px;
  693. border-radius: var(--radius-sm);
  694. margin-bottom: 20px;
  695. font-size: 14px;
  696. border: 1px solid;
  697. }
  698. .alert-success {
  699. background: rgba(34, 197, 94, 0.1);
  700. color: var(--success);
  701. border-color: rgba(34, 197, 94, 0.2);
  702. }
  703. .alert-error {
  704. background: rgba(239, 68, 68, 0.1);
  705. color: var(--error);
  706. border-color: rgba(239, 68, 68, 0.2);
  707. }
  708. .alert-warning {
  709. background: rgba(234, 179, 8, 0.1);
  710. color: #eab308;
  711. border-color: rgba(234, 179, 8, 0.2);
  712. }
  713. .form-group {
  714. margin-bottom: 20px;
  715. }
  716. .form-group label {
  717. display: block;
  718. margin-bottom: 8px;
  719. color: var(--text);
  720. font-weight: 500;
  721. font-size: 14px;
  722. }
  723. .form-group input[type="text"],
  724. .form-group input[type="email"],
  725. .form-group input[type="password"],
  726. .form-group input[type="url"] {
  727. width: 100%;
  728. padding: 12px;
  729. border: 1px solid var(--border);
  730. background: var(--bg-elevated);
  731. color: var(--text);
  732. border-radius: var(--radius-sm);
  733. font-size: 14px;
  734. transition: border-color 0.3s;
  735. }
  736. .form-group input:focus {
  737. outline: none;
  738. border-color: var(--border-focus);
  739. }
  740. .checkbox-group {
  741. display: flex;
  742. align-items: center;
  743. gap: 10px;
  744. }
  745. .checkbox-group input[type="checkbox"] {
  746. width: 20px;
  747. height: 20px;
  748. cursor: pointer;
  749. accent-color: var(--accent);
  750. }
  751. .btn {
  752. padding: 12px 24px;
  753. border: none;
  754. border-radius: var(--radius-sm);
  755. font-size: 14px;
  756. font-weight: 600;
  757. cursor: pointer;
  758. text-decoration: none;
  759. display: inline-flex;
  760. align-items: center;
  761. gap: 8px;
  762. font-family: inherit;
  763. transition: opacity 0.2s;
  764. }
  765. .btn:hover { opacity: 0.9; }
  766. .btn svg {
  767. width: 16px;
  768. height: 16px;
  769. }
  770. .btn-primary {
  771. background: var(--accent);
  772. color: var(--bg);
  773. }
  774. .btn-success {
  775. background: var(--success);
  776. color: var(--bg);
  777. }
  778. .btn-danger {
  779. background: var(--error);
  780. color: white;
  781. }
  782. .btn-secondary {
  783. background: var(--bg-hover);
  784. border: 1px solid var(--border);
  785. color: var(--text);
  786. }
  787. .btn-sm {
  788. padding: 8px 16px;
  789. font-size: 12px;
  790. }
  791. .table-container {
  792. overflow-x: auto;
  793. }
  794. table {
  795. width: 100%;
  796. border-collapse: collapse;
  797. }
  798. table th,
  799. table td {
  800. padding: 12px;
  801. text-align: left;
  802. border-bottom: 1px solid var(--border);
  803. }
  804. table th {
  805. background: var(--bg-elevated);
  806. font-weight: 600;
  807. font-size: 14px;
  808. color: var(--text);
  809. }
  810. table td {
  811. font-size: 14px;
  812. color: var(--text-muted);
  813. }
  814. .badge {
  815. display: inline-block;
  816. padding: 4px 12px;
  817. border-radius: 12px;
  818. font-size: 12px;
  819. font-weight: 600;
  820. }
  821. .badge-success {
  822. background: rgba(34, 197, 94, 0.1);
  823. color: var(--success);
  824. }
  825. .badge-warning {
  826. background: rgba(234, 179, 8, 0.1);
  827. color: #eab308;
  828. }
  829. .actions {
  830. display: flex;
  831. gap: 10px;
  832. margin-bottom: 20px;
  833. }
  834. .grid-2 {
  835. display: grid;
  836. grid-template-columns: 1fr 1fr;
  837. gap: 20px;
  838. }
  839. code {
  840. background: var(--bg-elevated);
  841. padding: 2px 6px;
  842. border-radius: 4px;
  843. font-family: 'Courier New', monospace;
  844. font-size: 13px;
  845. color: var(--text-muted);
  846. }
  847. ol, ul {
  848. color: var(--text-muted);
  849. }
  850. @media (max-width: 768px) {
  851. .grid-2 { grid-template-columns: 1fr; }
  852. .stats { grid-template-columns: 1fr; }
  853. .header {
  854. flex-direction: column;
  855. gap: 20px;
  856. align-items: flex-start;
  857. }
  858. }
  859. </style>
  860. </head>
  861. <body>
  862. <div class="container">
  863. <div class="header">
  864. <div class="header-title">
  865. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  866. <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
  867. <polyline points="22,6 12,13 2,6"></polyline>
  868. </svg>
  869. <h1>Newsletter Manager</h1>
  870. </div>
  871. <a href="?logout" class="logout-btn">
  872. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width: 16px; height: 16px;">
  873. <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
  874. <polyline points="16 17 21 12 16 7"></polyline>
  875. <line x1="21" y1="12" x2="9" y2="12"></line>
  876. </svg>
  877. Logout
  878. </a>
  879. </div>
  880. <?php if (isset($setup_message)): ?>
  881. <div class="alert alert-<?= $setup_success ? 'success' : 'error' ?>">
  882. <?= htmlspecialchars($setup_message) ?>
  883. </div>
  884. <?php endif; ?>
  885. <?php if (isset($config_message)): ?>
  886. <div class="alert alert-<?= $config_success ? 'success' : 'error' ?>">
  887. <?= htmlspecialchars($config_message) ?>
  888. </div>
  889. <?php endif; ?>
  890. <?php if (isset($delete_message)): ?>
  891. <div class="alert alert-success">
  892. <?= htmlspecialchars($delete_message) ?>
  893. </div>
  894. <?php endif; ?>
  895. <?php if (isset($db_error)): ?>
  896. <div class="alert alert-error">
  897. Couldn't read from the database: <?= htmlspecialchars($db_error) ?>
  898. </div>
  899. <?php endif; ?>
  900. <div class="stats">
  901. <div class="stat-card">
  902. <h3>Total Subscribers</h3>
  903. <div class="number"><?= number_format($stats['total'] ?? 0) ?></div>
  904. </div>
  905. <div class="stat-card">
  906. <h3>Confirmed</h3>
  907. <div class="number" style="color: var(--success);"><?= number_format($stats['confirmed'] ?? 0) ?></div>
  908. </div>
  909. <div class="stat-card">
  910. <h3>Pending</h3>
  911. <div class="number" style="color: #eab308;"><?= number_format($stats['pending'] ?? 0) ?></div>
  912. </div>
  913. </div>
  914. <?php if (!$pdo || empty($subscribers)): ?>
  915. <div class="card">
  916. <h2>
  917. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  918. <circle cx="12" cy="12" r="10"></circle>
  919. <polyline points="12 6 12 12 16 14"></polyline>
  920. </svg>
  921. Quick Setup
  922. </h2>
  923. <div class="alert alert-warning">
  924. First time here? Click below to set up the database.
  925. </div>
  926. <form method="POST" style="display: inline;">
  927. <?= $csrf_field ?>
  928. <button type="submit" name="setup" value="1" class="btn btn-success">
  929. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  930. <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
  931. <polyline points="22 4 12 14.01 9 11.01"></polyline>
  932. </svg>
  933. Setup Database Table
  934. </button>
  935. </form>
  936. </div>
  937. <?php endif; ?>
  938. <div class="card">
  939. <h2>
  940. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  941. <circle cx="12" cy="12" r="3"></circle>
  942. <path d="M12 1v6m0 6v6m5.66-13.66l-4.24 4.24m0 6l-4.24 4.24m11.66-5.66l-6 0m-6 0l-6 0m13.66 5.66l-4.24-4.24m0-6l-4.24-4.24"></path>
  943. </svg>
  944. Configuration
  945. </h2>
  946. <form method="POST">
  947. <?= $csrf_field ?>
  948. <div class="grid-2">
  949. <div>
  950. <h3 style="margin-bottom: 15px; color: var(--text-muted); font-size: 16px;">Database Settings</h3>
  951. <div class="form-group">
  952. <label>Database Host</label>
  953. <input type="text" name="db_host" value="<?= htmlspecialchars(DB_HOST) ?>" required>
  954. </div>
  955. <div class="form-group">
  956. <label>Database Name</label>
  957. <input type="text" name="db_name" value="<?= htmlspecialchars(DB_NAME) ?>" required>
  958. </div>
  959. <div class="form-group">
  960. <label>Database User</label>
  961. <input type="text" name="db_user" value="<?= htmlspecialchars(DB_USER) ?>" required>
  962. </div>
  963. <div class="form-group">
  964. <label>Database Password</label>
  965. <input type="password" name="db_pass" value="<?= htmlspecialchars(DB_PASS) ?>" required>
  966. </div>
  967. </div>
  968. <div>
  969. <h3 style="margin-bottom: 15px; color: var(--text-muted); font-size: 16px;">Email Settings</h3>
  970. <div class="form-group">
  971. <label>Sender Email</label>
  972. <input type="email" name="sender_email" value="<?= htmlspecialchars(SENDER_EMAIL) ?>" required>
  973. </div>
  974. <div class="form-group">
  975. <label>Sender Name</label>
  976. <input type="text" name="sender_name" value="<?= htmlspecialchars(SENDER_NAME) ?>" required>
  977. </div>
  978. <div class="form-group">
  979. <label>Site URL</label>
  980. <input type="url" name="site_url" value="<?= htmlspecialchars(SITE_URL) ?>" required>
  981. </div>
  982. <div class="form-group">
  983. <div class="checkbox-group">
  984. <input type="checkbox" name="require_confirmation" id="require_confirmation" <?= REQUIRE_CONFIRMATION ? 'checked' : '' ?>>
  985. <label for="require_confirmation" style="margin: 0;">Require Email Confirmation</label>
  986. </div>
  987. </div>
  988. </div>
  989. </div>
  990. <button type="submit" name="update_config" class="btn btn-primary">
  991. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  992. <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
  993. <polyline points="17 21 17 13 7 13 7 21"></polyline>
  994. <polyline points="7 3 7 8 15 8"></polyline>
  995. </svg>
  996. Save Configuration
  997. </button>
  998. </form>
  999. </div>
  1000. <div class="card">
  1001. <h2>
  1002. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  1003. <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
  1004. <rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>
  1005. </svg>
  1006. Subscribers (<?= count($subscribers) ?>)
  1007. </h2>
  1008. <div class="actions">
  1009. <a href="?export" class="btn btn-secondary btn-sm">
  1010. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  1011. <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
  1012. <polyline points="7 10 12 15 17 10"></polyline>
  1013. <line x1="12" y1="15" x2="12" y2="3"></line>
  1014. </svg>
  1015. Export CSV
  1016. </a>
  1017. </div>
  1018. <?php if (empty($subscribers)): ?>
  1019. <div class="alert alert-warning">
  1020. No subscribers yet. Add a Newsletter block to one of your pages so visitors can sign up.
  1021. </div>
  1022. <?php else: ?>
  1023. <div class="table-container">
  1024. <table>
  1025. <thead>
  1026. <tr>
  1027. <th>Email</th>
  1028. <th>Status</th>
  1029. <th>Subscribed</th>
  1030. <th>Confirmed</th>
  1031. <th>IP</th>
  1032. <th>Actions</th>
  1033. </tr>
  1034. </thead>
  1035. <tbody>
  1036. <?php foreach ($subscribers as $sub): ?>
  1037. <tr>
  1038. <td><?= htmlspecialchars($sub['email']) ?></td>
  1039. <td>
  1040. <?php if ($sub['confirmed']): ?>
  1041. <span class="badge badge-success">Confirmed</span>
  1042. <?php else: ?>
  1043. <span class="badge badge-warning">Pending</span>
  1044. <?php endif; ?>
  1045. </td>
  1046. <td><?= date('Y-m-d H:i', strtotime($sub['created_at'])) ?></td>
  1047. <td><?= $sub['confirmed_at'] ? date('Y-m-d H:i', strtotime($sub['confirmed_at'])) : '-' ?></td>
  1048. <td><?= htmlspecialchars($sub['ip_address'] ?? 'N/A') ?></td>
  1049. <td>
  1050. <form method="POST" style="display: inline;" onsubmit="return confirm('Delete this subscriber?')">
  1051. <?= $csrf_field ?>
  1052. <button type="submit" name="delete" value="<?= (int) $sub['id'] ?>" class="btn btn-danger btn-sm">
  1053. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  1054. <polyline points="3 6 5 6 21 6"></polyline>
  1055. <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
  1056. </svg>
  1057. Delete
  1058. </button>
  1059. </form>
  1060. </td>
  1061. </tr>
  1062. <?php endforeach; ?>
  1063. </tbody>
  1064. </table>
  1065. </div>
  1066. <?php endif; ?>
  1067. </div>
  1068. <div class="card">
  1069. <h2>
  1070. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  1071. <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
  1072. <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
  1073. </svg>
  1074. Quick Guide
  1075. </h2>
  1076. <ol style="line-height: 2; color: var(--text-muted);">
  1077. <li>Configure database and email settings above</li>
  1078. <li>Click "Setup Database Table" if not done yet</li>
  1079. <li>Add newsletter form to your website (it's already configured in your CMS)</li>
  1080. <li>Monitor subscribers here</li>
  1081. <li>Export CSV to use with email marketing tools</li>
  1082. </ol>
  1083. <h3 style="margin-top: 20px; margin-bottom: 10px; color: var(--text);">Important Files:</h3>
  1084. <ul style="line-height: 2; color: var(--text-muted);">
  1085. <li><code>/newsletter/config.php</code> - Configuration (auto-managed by this panel)</li>
  1086. <li><code>/newsletter/subscribe.php</code> - Handles subscriptions</li>
  1087. <li><code>/newsletter/confirm.php</code> - Email confirmation page</li>
  1088. <li><code>/newsletter/unsubscribe.php</code> - Unsubscribe page</li>
  1089. <li><code>/newsletter/manager.php</code> - This admin panel</li>
  1090. </ul>
  1091. </div>
  1092. </div>
  1093. </body>
  1094. </html>