index.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. <?php
  2. /**
  3. * Admin page: generate tracking links and view all captured clicks.
  4. */
  5. $dataDir = __DIR__ . '/data';
  6. $logFile = $dataDir . '/tracks.jsonl';
  7. $keysFile = $dataDir . '/keys.json';
  8. $baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http')
  9. . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost')
  10. . rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? '/'), '/') . '/';
  11. $trackingLink = $baseUrl . 'track.php';
  12. function countryCodeToFlag($countryCode)
  13. {
  14. if (!$countryCode || !preg_match('/^[A-Za-z]{2}$/', $countryCode)) {
  15. return null;
  16. }
  17. $countryCode = strtoupper($countryCode);
  18. $flag = '';
  19. foreach (str_split($countryCode) as $letter) {
  20. $flag .= mb_chr(0x1F1E6 + (ord($letter) - ord('A')), 'UTF-8');
  21. }
  22. return $flag;
  23. }
  24. $trackedKeys = [];
  25. if (is_file($keysFile) && is_readable($keysFile)) {
  26. $raw = file_get_contents($keysFile);
  27. $decoded = json_decode($raw, true);
  28. if (is_array($decoded)) {
  29. $trackedKeys = $decoded;
  30. }
  31. }
  32. if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'create_key') {
  33. $existing = array_flip($trackedKeys);
  34. do {
  35. $newKey = bin2hex(random_bytes(8));
  36. } while (isset($existing[$newKey]));
  37. $trackedKeys[] = $newKey;
  38. if (!is_dir($dataDir)) {
  39. mkdir($dataDir, 0755, true);
  40. }
  41. file_put_contents($keysFile, json_encode($trackedKeys, JSON_PRETTY_PRINT), LOCK_EX);
  42. }
  43. $maxTracks = 100;
  44. $tracks = [];
  45. if (is_file($logFile) && is_readable($logFile)) {
  46. $lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  47. if ($lines !== false) {
  48. if (count($lines) > $maxTracks) {
  49. $lines = array_slice($lines, -$maxTracks);
  50. file_put_contents($logFile, implode("\n", $lines) . "\n", LOCK_EX);
  51. }
  52. $reversed = array_reverse($lines);
  53. foreach ($reversed as $line) {
  54. $decoded = json_decode($line, true);
  55. if (is_array($decoded)) {
  56. $tracks[] = $decoded;
  57. }
  58. }
  59. }
  60. }
  61. ?>
  62. <!DOCTYPE html>
  63. <html lang="en">
  64. <head>
  65. <meta charset="UTF-8">
  66. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  67. <title>Click Tracker – Admin</title>
  68. <style>
  69. body { font-family: system-ui, sans-serif; margin: 1rem 2rem; max-width: 1800px; }
  70. h1 { margin-top: 0; }
  71. .form-group { margin-bottom: 1rem; }
  72. .form-group label { display: block; margin-bottom: 0.25rem; font-weight: 500; }
  73. .form-group input[type="url"] { width: 100%; max-width: 480px; padding: 0.5rem; box-sizing: border-box; }
  74. .generated { margin: 1rem 0; padding: 0.75rem; background: #e8f5e9; border-radius: 4px; word-break: break-all; }
  75. .generated a { color: #2e7d32; }
  76. .error { color: #c62828; margin: 1rem 0; }
  77. table { border-collapse: collapse; width: 100%; margin-top: 1.5rem; font-size: 0.9rem; }
  78. th, td { border: 1px solid #ccc; padding: 0.5rem 0.75rem; text-align: left; vertical-align: top; }
  79. th { background: #f5f5f5; font-weight: 600; }
  80. tr:nth-child(even) { background: #fafafa; }
  81. .col-time { white-space: nowrap; }
  82. .col-ua { max-width: 500px; word-break: break-all; }
  83. .col-referrer, .col-target { max-width: 400px; word-break: break-all; }
  84. .empty { color: #666; font-style: italic; margin-top: 1rem; }
  85. </style>
  86. </head>
  87. <body>
  88. <h1>Click Tracker – Admin</h1>
  89. <section>
  90. <h2>Tracking link</h2>
  91. <p class="generated">Use this link to capture clicks (redirects here after): <a href="<?php echo htmlspecialchars($trackingLink); ?>"><?php echo htmlspecialchars($trackingLink); ?></a></p>
  92. </section>
  93. <section>
  94. <h2>Tracked links (by key)</h2>
  95. <p>Create a unique key to get a link that identifies which link was clicked. Clicks are logged with the key.</p>
  96. <form method="post" action="">
  97. <input type="hidden" name="action" value="create_key">
  98. <button type="submit">Create unique key</button>
  99. </form>
  100. <?php if (!empty($trackedKeys)): ?>
  101. <?php $linkTemplate = $baseUrl . 'track.php?key=KEY'; ?>
  102. <ul style="margin-top: 1rem; list-style: none; padding: 0;">
  103. <?php foreach (array_reverse($trackedKeys) as $k): ?>
  104. <li style="margin-bottom: 0.5rem; padding: 0.5rem; background: #f5f5f5; border-radius: 4px;">
  105. <code style="font-size: 0.85em;"><?php echo htmlspecialchars($k); ?></code>
  106. <span style="margin-left: 0.5rem; word-break: break-all; color: #555;"><?php echo htmlspecialchars($linkTemplate); ?></span>
  107. </li>
  108. <?php endforeach; ?>
  109. </ul>
  110. <p class="empty" style="margin-top: 0.5rem;">Replace <code>KEY</code> in the URL with the key above to construct your link.</p>
  111. <?php else: ?>
  112. <p class="empty">No tracked keys yet. Click the button above to create one.</p>
  113. <?php endif; ?>
  114. </section>
  115. <section>
  116. <h2>Past tracks</h2>
  117. <?php if (empty($tracks)): ?>
  118. <p class="empty">No tracks yet.</p>
  119. <?php else: ?>
  120. <table>
  121. <thead>
  122. <tr>
  123. <th class="col-time">Time</th>
  124. <th>Key</th>
  125. <th>IP</th>
  126. <th class="col-ua">User-Agent</th>
  127. <th class="col-referrer">Referrer</th>
  128. <th class="col-target">Target URL</th>
  129. <th>Method</th>
  130. <th>Request URI</th>
  131. </tr>
  132. </thead>
  133. <tbody>
  134. <?php foreach ($tracks as $t): ?>
  135. <tr>
  136. <td class="col-time"><?php echo htmlspecialchars($t['timestamp'] ?? '-'); ?></td>
  137. <td><?php echo htmlspecialchars($t['key'] ?? '-'); ?></td>
  138. <td><?php $flag = countryCodeToFlag($t['country_code'] ?? null); ?><?php if ($flag): ?><span title="<?php echo htmlspecialchars($t['country_code']); ?>"><?php echo $flag; ?></span> <?php endif; ?><?php echo htmlspecialchars($t['ip'] ?? '-'); ?><?php if (!empty($t['ip_forwarded'])) echo ' <small>(X-Forwarded: ' . htmlspecialchars($t['ip_forwarded']) . ')</small>'; ?></td>
  139. <td class="col-ua"><?php echo htmlspecialchars($t['user_agent'] ?? '-'); ?></td>
  140. <td class="col-referrer"><?php echo htmlspecialchars($t['referrer'] ?? '-'); ?></td>
  141. <td class="col-target"><?php echo htmlspecialchars($t['target_url'] ?? '-'); ?></td>
  142. <td><?php echo htmlspecialchars($t['request_method'] ?? '-'); ?></td>
  143. <td><?php echo htmlspecialchars($t['request_uri'] ?? '-'); ?></td>
  144. </tr>
  145. <?php endforeach; ?>
  146. </tbody>
  147. </table>
  148. <?php endif; ?>
  149. </section>
  150. </body>
  151. </html>