From 84f7b44926bd79a17c64dd5a700d585b47b005ce Mon Sep 17 00:00:00 2001 From: Andrey Kuvshinov Date: Fri, 17 Oct 2025 12:23:41 +0300 Subject: [PATCH] add files --- .gitignore | 73 +++++++++ composer.json | 20 +++ composer.sh | 5 + config.php | 8 + exdv.js | 76 +++++++++ index.php | 96 +++++++++++ report.php | 428 ++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 706 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 composer.sh create mode 100644 config.php create mode 100644 exdv.js create mode 100644 index.php create mode 100644 report.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20a2218 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# Временные файлы редакторов и ОС +.DS_Store +Thumbs.db +*.swp +*.swo +*~ + +# Логи приложения +*.log +logs/ + +# Файлы окружения (не коммитим чувствительные данные) +.env +.env.local +.env.*.local + +# Зависимости Composer (можно коммитить, но обычно нет) +/vendor/ +/composer.lock + +# Кэш и скомпилированные файлы +/cache/ +/tmp/ +/var/cache/ +/var/tmp/ + +# Файлы IDE +.idea/ +.phpstorm.meta.php +*.buildpath +*.project +*.settings/ +.vscode/ + +# Unit тесты и покрытие кода +/coverage/ +.phpunit.result.cache + +# Деплой и билд +/build/ +/dist/ + +# Файлы операционной системы +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +[Tt]humbs.db + +# Apache/Nginx +.htaccess +nginx.conf.local + +# Сессии PHP +/sessions/ + +# Загружаемые файлы (если они не должны быть в репозитории) +/uploads/ +/storage/app/public/ +/public/storage/ + +# Файлы пакетов +*.tar.gz +*.zip + +# SQLite базы данных (если используются) +*.sqlite +*.db + +# Xdebug +xdebug.log \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..01eed2b --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + + "require": { + "php":">=5.3.0", + "masterforweb/kuri":"dev-master", + "masterforweb/db_lite":"dev-master" + }, + "repositories":[ + + { + "type":"git", + "url":"https://github.com/masterforweb/kuri" + }, + { + "type":"git", + "url":"https://github.com/masterforweb/db_lite" + } + + ] +} diff --git a/composer.sh b/composer.sh new file mode 100644 index 0000000..724a5ad --- /dev/null +++ b/composer.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +docker run --rm --interactive --tty \ + --volume $PWD:/app \ + composer update \ No newline at end of file diff --git a/config.php b/config.php new file mode 100644 index 0000000..2c97c59 --- /dev/null +++ b/config.php @@ -0,0 +1,8 @@ + { + const pictureData = img.getAttribute('data-picture'); + const advId = pictureData.replace('ex_', ''); + + // Проверяем, не отслеживали ли мы уже этот adv_id на этой странице + if (!trackedIds.has(advId)) { + trackedIds.add(advId); + + const data = { + adv_id: parseInt(advId), + ...userData + }; + + // Отправляем запрос на сервер + sendViewData(data); + } + }); +} + + +function sendViewData(data) { + const url = 'https://exdv.argumenti.ru'; + + console.log('📤 Отправка JSON данных:', data); + + fetch(url, { + method: 'POST', + mode: 'no-cors', + headers: { + 'Content-Type': 'application/json', // Важно для JSON! + }, + body: JSON.stringify(data) // Отправляем как JSON + }) + .then(response => { + console.log('📥 Статус ответа:', response.status); + if (!response.ok) { + return response.text().then(text => { + throw new Error(`HTTP ${response.status}: ${text}`); + }); + } + return response.json(); + }) + .then(result => { + console.log('✅ Успешный ответ:', result); + }) + .catch(error => { + console.error('❌ Ошибка:', error.message); + }); +} + + +// Дебаунсинг для избежания множественных вызовов +let trackTimeout = null; +function debouncedTrackAdvViews() { + if (trackTimeout) { + clearTimeout(trackTimeout); + } + trackTimeout = setTimeout(trackAdvViews, 100); +} + +// Запускаем отслеживание при загрузке страницы +document.addEventListener('DOMContentLoaded', debouncedTrackAdvViews); \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..5db5fdb --- /dev/null +++ b/index.php @@ -0,0 +1,96 @@ + 'Invalid JSON: ' . json_last_error_msg()]); + exit; + } + + // Валидация данных + if (!isset($input['adv_id']) || !is_numeric($input['adv_id'])) { + error_log("Ошибка: invalid adv_id"); + http_response_code(400); + echo json_encode(['error' => 'Invalid adv_id', 'received_data' => $input]); + exit; + } + + // Подготовка данных + $advId = (int)$input['adv_id']; + $ipAddress = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown'; + $userAgent = $input['user_agent'] ?? ''; + $pageUrl = $input['page_url'] ?? ''; + $viewedAt = date('Y-m-d H:i:s'); + + // Создаем хэш на основе adv_id, ip, user_agent и текущей минуты + $minuteWindow = date('Y-m-d H:i'); + $hashData = $advId . $ipAddress . $userAgent . $minuteWindow; + $viewHash = md5($hashData); + + try { + $pdo = new PDO('mysql:host=87.249.36.139;dbname=argumentiru;charset=utf8mb4', 'argumentiru', 'hjYu78kl*90-Uio23'); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + + // Проверяем существующий просмотр по хэшу + $checkStmt = $pdo->prepare("SELECT view_id FROM `adv_views` WHERE view_hash = ? LIMIT 1"); + $checkStmt->execute([$viewHash]); + + if ($checkStmt->fetch()) { + echo json_encode([ + 'success' => true, + 'duplicate' => true, + 'message' => 'View already recorded in this minute' + ]); + exit; + } + + // Сохраняем новый просмотр с хэшем + $stmt = $pdo->prepare("INSERT INTO `adv_views` (`view_hash`, `adv_id`, `ip_address`, `user_agent`, `viewed_at`, `page_url`) VALUES (?, ?, ?, ?, ?, ?)"); + $stmt->execute([$viewHash, $advId, $ipAddress, $userAgent, $viewedAt, $pageUrl]); + + $viewId = $pdo->lastInsertId(); + + http_response_code(200); + + echo json_encode([ + 'success' => true, + 'view_id' => $viewId, + 'view_hash' => $viewHash, + 'message' => 'View recorded successfully' + ]); + + } catch (PDOException $e) { + http_response_code(500); + error_log("Database error: " . $e->getMessage()); + echo json_encode(['error' => 'Database error: ' . $e->getMessage()]); + } + +} else { + http_response_code(200); + echo json_encode(['error' => 'Method not allowed']); +} +?> \ No newline at end of file diff --git a/report.php b/report.php new file mode 100644 index 0000000..0a40841 --- /dev/null +++ b/report.php @@ -0,0 +1,428 @@ + + + + + + + Доступ к отчету + + + +
+

🔒 Доступ к отчету

+
+ +
+ + +
+ +
+
+ + + setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +} catch (PDOException $e) { + die('Database connection failed: ' . $e->getMessage()); +} + +// Обработка параметров периода +$start_date = $_GET['start_date'] ?? date('Y-m-01'); +$end_date = $_GET['end_date'] ?? date('Y-m-d'); +$adv_id = $_GET['adv_id'] ?? null; +$space_id = $_GET['space_id'] ?? null; + +// Валидация дат +if (!strtotime($start_date) || !strtotime($end_date)) { + $start_date = date('Y-m-01'); + $end_date = date('Y-m-d'); +} + +// Получение статистики - ТОЛЬКО баннеры с показами за период +function getAdvStats($pdo, $start_date, $end_date, $adv_id = null, $space_id = null) { + $sql = " + SELECT + ai.item_id, + ai.itemname, + ai.erid, + ai.adv_link, + ai.space_id, + ai.adv_file, + ai.desktop, + ai.phone, + ai.tablet, + ai.android, + ai.adv_active, + COUNT(av.view_id) as views_count, + COUNT(DISTINCT av.ip_address) as unique_views, + MIN(av.viewed_at) as first_view, + MAX(av.viewed_at) as last_view + FROM adv_views av + INNER JOIN adv_items2 ai ON av.adv_id = ai.item_id + WHERE av.viewed_at BETWEEN :start_date AND DATE_ADD(:end_date, INTERVAL 1 DAY) + "; + + $params = [ + 'start_date' => $start_date, + 'end_date' => $end_date + ]; + + // Фильтр по ID баннера + if ($adv_id) { + $sql .= " AND ai.item_id = :adv_id"; + $params['adv_id'] = $adv_id; + } + + // Фильтр по space_id + if ($space_id) { + $sql .= " AND ai.space_id = :space_id"; + $params['space_id'] = $space_id; + } + + $sql .= " GROUP BY ai.item_id, ai.itemname, ai.erid + HAVING views_count > 0 + ORDER BY views_count DESC, ai.itemname ASC"; + + $stmt = $pdo->prepare($sql); + $stmt->execute($params); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +// Получение списка баннеров для фильтра - ТОЛЬКО те, у которых есть показы за период +function getAdvList($pdo, $start_date, $end_date) { + $sql = " + SELECT DISTINCT + ai.item_id, + ai.itemname, + ai.erid, + ai.space_id + FROM adv_views av + INNER JOIN adv_items2 ai ON av.adv_id = ai.item_id + WHERE av.viewed_at BETWEEN :start_date AND DATE_ADD(:end_date, INTERVAL 1 DAY) + ORDER BY ai.itemname + "; + + $stmt = $pdo->prepare($sql); + $stmt->execute([ + 'start_date' => $start_date, + 'end_date' => $end_date + ]); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +// Получение списка space_id для фильтра - ТОЛЬКО те, у которых есть показы за период +function getSpaceList($pdo, $start_date, $end_date) { + $sql = " + SELECT DISTINCT ai.space_id + FROM adv_views av + INNER JOIN adv_items2 ai ON av.adv_id = ai.item_id + WHERE av.viewed_at BETWEEN :start_date AND DATE_ADD(:end_date, INTERVAL 1 DAY) + ORDER BY ai.space_id + "; + + $stmt = $pdo->prepare($sql); + $stmt->execute([ + 'start_date' => $start_date, + 'end_date' => $end_date + ]); + + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +$stats = getAdvStats($pdo, $start_date, $end_date, $adv_id, $space_id); +$adv_list = getAdvList($pdo, $start_date, $end_date); +$space_list = getSpaceList($pdo, $start_date, $end_date); + +// Общая статистика +$total_views = array_sum(array_column($stats, 'views_count')); +$total_unique = array_sum(array_column($stats, 'unique_views')); +$total_banners = count($stats); + +// Топ баннеры +$top_banners = array_slice($stats, 0, 5); +?> + + + + + + Отчет по показам баннеров + + + +
+
+

📊 Отчет по показам баннеров

+
+ + +
+
+ + +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
+
+ + +
+
+
+
Баннеров с показами
+
+
+
+
Всего показов
+
+
+
+
Уникальные показы
+
+
+
+ 0 ? number_format($total_views / $total_banners, 1) : 0 ?> +
+
Среднее на баннер
+
+
+ + + +
+

🏆 Топ-5 баннеров по показам

+ $banner): ?> +
+ # + ID - + + () + + + показов + +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + 0 ? ($row['unique_views'] / $row['views_count'] * 100) : 0; + ?> + + + + + + + + + + + + + + + + +
IDНазваниеERIDСсылкаID пространстваУстройстваСтатусПоказыУникальныеПервый показПоследний показCTR
+ + + 🖥️ + 📱 + 📟 + 🤖 + + + %
+ +
+

📭 Нет данных за выбранный период

+

Попробуйте изменить параметры фильтрации

+
+ +
+
+ + + + + \ No newline at end of file