WebOrbiton
v3.0.0.0

FlatlyPage

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