In Reihe von unten nach oben zum Plug-in.
Aktuelle Version ist im Beitrag: Visit Duration
Entwicklung:
<?php
/*
* Plugin Name: Visit Duration
* Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
* Version: (2/19.11.24)
* Author: Team WP Wegerl
* Author URI: https://wegerl.at/visit-duration/
* Text Domain: visit-duration
Die Funktion 'is_bot_or_spider' prüft anhand des User-Agents, ob es sich bei einem Besucher um einen Bot handelt.
Diese Funktion nutzt Caching, um wiederholte Anfragen zu vermeiden und verbessert so die Performance.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Am Anfang der Datei, um die Bot-Erkennungsfunktion zu laden
if ( file_exists( plugin_dir_path( __FILE__ ) . 'bot-functions.php' ) ) {
require_once plugin_dir_path( __FILE__ ) . 'bot-functions.php';
} else {
// Fehlerbehandlung, falls die Datei nicht gefunden wurde
error_log('bot-functions.php wurde nicht gefunden.');
}
// Prüft, ob es sich um einen Bot handelt
$is_bot = is_bot_or_spider();
function start_visit_handler() {
check_ajax_referer('your_nonce', 'security');
$unique_id = isset($_POST['unique_id']) ? sanitize_text_field($_POST['unique_id']) : '';
$page_title = isset($_POST['page_title']) ? sanitize_text_field($_POST['page_title']) : '';
if ($unique_id && $page_title) {
global $wpdb;
$table_name = $wpdb->prefix . 'visit_tracking';
$wpdb->insert($table_name, array(
'unique_id' => $unique_id,
'page_title' => $page_title,
'visit_time' => current_time('mysql')
));
wp_send_json_success('Besuch protokolliert');
} else {
wp_send_json_error('Ungültige Daten');
}
}
// Stelle sicher, dass die Sitzung zu Beginn der Verarbeitung gestartet wird
function start_session_if_needed() {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
}
add_action('init', 'start_session_if_needed');
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
// Den angemeldeten Admin und Bots ausschließen
if (current_user_can('administrator') || is_bot_or_spider() || is_test_tool()) {
return; // Frühzeitig abbrechen, wenn der angemeldete Benutzer ein Admin ist, sich um einen Bot oder Testtool handelt
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// Besuchsdaten abrufen und AJAX-Skript laden
echo "<script>
(function() {
var ajaxUrl = '" . esc_url(admin_url('admin-ajax.php')) . "';
if (!ajaxUrl) {
console.error('AJAX-URL konnte nicht geladen werden.');
return;
}
window.ajaxUrl = ajaxUrl;
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
var isTabActive = true;
// Besuch nur nach 3 Sekunden tracken
setTimeout(function() {
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
}).catch(err => console.error('Fehler beim Start des Besuchs:', err));
}, 3000);
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
if (isTabActive) {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
}
}).catch(err => console.error('Fehler bei Timeout-Überprüfung:', err));
}
}, 30000);
// Besuch beenden: Sichtbarkeit oder Schließen
function sendEndVisit() {
if (window.uniqueId && window.ajaxUrl) {
const payload = JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
});
const beaconSuccess = navigator.sendBeacon(window.ajaxUrl + '?action=check_visit_timeout', payload);
if (!beaconSuccess) {
const xhr = new XMLHttpRequest();
xhr.open('POST', window.ajaxUrl + '?action=check_visit_timeout', false); // Synchrone Anfrage
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(payload);
}
}
}
// Sichtbarkeitswechsel überwachen
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') {
isTabActive = false;
sendEndVisit();
} else {
isTabActive = true;
}
});
// Besuch beim Schließen des Tabs oder Browsers beenden
window.addEventListener('beforeunload', sendEndVisit);
})();
// Safari spezifisch
document.addEventListener('DOMContentLoaded', function() {
// Safari-Erkennung
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
var visitStartTime = localStorage.getItem('visit_start_time') || Date.now();
var uniqueId = window.uniqueId || 'default_user';
var ajaxUrl = window.ajaxUrl || '/wp-admin/admin-ajax.php';
localStorage.setItem('visit_start_time', visitStartTime);
if (isSafari) {
console.log('Safari-Browser erkannt: Spezifische Anpassung aktiv.');
function sendDataForSafari(startTime, endTime) {
var data = {
unique_id: uniqueId,
start_time: startTime,
end_time: endTime,
};
var xhr = new XMLHttpRequest();
xhr.open('POST', ajaxUrl + '?action=update_visit_duration', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
xhr.onload = function() {
if (xhr.status === 200) {
console.log('Safari-Spezifisch:', xhr.responseText);
} else {
console.error('Safari Fehler:', xhr.statusText);
}
};
}
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') {
sendDataForSafari(visitStartTime, Date.now());
}
});
window.addEventListener('beforeunload', function() {
sendDataForSafari(visitStartTime, Date.now());
});
setInterval(function() {
sendDataForSafari(visitStartTime, Date.now());
}, 30000);
}
});
</script>";
}
// Bot-Erkennungsfunktion laden, falls noch nicht geladen
if (!function_exists('is_bot_or_spider')) {
if (file_exists(plugin_dir_path(__FILE__) . 'bot-functions.php')) {
require_once plugin_dir_path(__FILE__) . 'bot-functions.php';
} else {
error_log('bot-functions.php wurde nicht gefunden.');
}
}
add_action('wp_footer', 'start_visit_tracking');
// Besuch beenden und Verweildauer speichern
function end_visit_tracking() {
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time'];
update_option('last_visitor_duration', $duration);
unset($_SESSION['visit_start_time']);
error_log("Visit ended. Duration: " . $duration . " seconds.");
}
}
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Visit Duration: Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Besuche nach Startzeit sortieren (neueste oben)
usort($visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
// Besucher nach Startzeit absteigend sortieren
response.data.updated_visits.sort(function(a, b) {
return b.start_time - a.start_time; // Sortiert absteigend nach Startzeit
});
// Besucher in die Tabelle einfügen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
// Besuchsdaten nach Startzeit absteigend sortieren
usort($updated_visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Speichern der neuen Verweildauern
update_option('current_visits', $visits);
// Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
wp_send_json_success(['updated_visits' => $updated_visits]);
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen sehr funktionell
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen oder bei Inaktivität, nur für nicht-Admins
// Option 1
// test Vorteil: Sitzungen bleiben nach automatischen Beenden bis zum Seitenverlassen im Hintergrund offen.
// test Nachteil: Überlange Sitzungszeiten möglich, die nicht real sind.
add_action('wp_footer', function() {
if ( !current_user_can('administrator') ) { // Überprüft, ob der aktuelle Benutzer kein Administrator ist
echo "<script>
// Funktionsblock zur Überwachung der Inaktivität
let inactivityTime = function() {
let timeout;
function resetTimer() {
clearTimeout(timeout);
timeout = setTimeout(endSessionDueToInactivity, 5 * 60 * 1000); // 5 Minuten Inaktivität
}
function endSessionDueToInactivity() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Besuch beendet wegen Inaktivität.');
}
}
// Events für Maus- und Tastaturaktivität zur Reaktivierung des Timers
document.onload = resetTimer;
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
document.onscroll = resetTimer;
};
inactivityTime();
// Besuch beim Schließen des Tabs oder Browsers beenden
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Daten erfolgreich gesendet: Tab oder Browser geschlossen.');
}
});
</script>";
}
});
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen oder bei Inaktivität, nur für nicht-Admins.
// Option 2:
// test Vorteil: Sitzungen werden endgültig nach Timeout abgeschlossen, keine überlangen Zeiten.
// test Nachteil: Nachträgliche Aktionen können nicht mehr hinzugefügt werden.
/*add_action('wp_footer', function() {
if ( !current_user_can('administrator') ) { // Überprüft, ob der aktuelle Benutzer kein Administrator ist
echo "<script>
let timeout;
let sessionEnded = false; // Flag, um den Status der Sitzung zu verfolgen
function resetTimer() {
clearTimeout(timeout);
timeout = setTimeout(endSessionDueToInactivity, 5 * 60 * 1000); // 5 Minuten Inaktivität
}
function endSessionDueToInactivity() {
if (!sessionEnded) { // Sitzung nur einmal als beendet markieren
sessionEnded = true;
console.log('Besuch beendet wegen Inaktivität.');
sendSessionUpdate();
}
}
function sendSessionUpdate() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
}
function handlePageNavigation() {
// Sitzung vor dem Verlassen der Seite sauber beenden
if (!sessionEnded) {
console.log('Seitenwechsel erkannt, Sitzung wird beendet.');
sendSessionUpdate();
sessionEnded = true; // Markiere Sitzung als beendet
}
}
// Benutzeraktivität überwachen
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
document.onscroll = resetTimer;
// Besuch beim Schließen des Tabs oder bei internem Navigieren beenden
window.addEventListener('beforeunload', handlePageNavigation);
window.addEventListener('unload', handlePageNavigation);
// Timer starten, sobald die Seite geladen wird
resetTimer();
</script>";
}
});*/
Entwicklung:
<?php
/*
* Plugin Name: Visit Duration
* Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
* Version: (1/19.11.24)
* Author: Team WP Wegerl
* Author URI: https://wegerl.at/visit-duration/
* Text Domain: visit-duration
Die Funktion 'is_bot_or_spider' prüft anhand des User-Agents, ob es sich bei einem Besucher um einen Bot handelt.
Diese Funktion nutzt Caching, um wiederholte Anfragen zu vermeiden und verbessert so die Performance.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Am Anfang der Datei, um die Bot-Erkennungsfunktion zu laden
if ( file_exists( plugin_dir_path( __FILE__ ) . 'bot-functions.php' ) ) {
require_once plugin_dir_path( __FILE__ ) . 'bot-functions.php';
} else {
// Fehlerbehandlung, falls die Datei nicht gefunden wurde
error_log('bot-functions.php wurde nicht gefunden.');
}
// Prüft, ob es sich um einen Bot handelt
$is_bot = is_bot_or_spider();
function start_visit_handler() {
check_ajax_referer('your_nonce', 'security');
$unique_id = isset($_POST['unique_id']) ? sanitize_text_field($_POST['unique_id']) : '';
$page_title = isset($_POST['page_title']) ? sanitize_text_field($_POST['page_title']) : '';
if ($unique_id && $page_title) {
global $wpdb;
$table_name = $wpdb->prefix . 'visit_tracking';
$wpdb->insert($table_name, array(
'unique_id' => $unique_id,
'page_title' => $page_title,
'visit_time' => current_time('mysql')
));
wp_send_json_success('Besuch protokolliert');
} else {
wp_send_json_error('Ungültige Daten');
}
}
// Stelle sicher, dass die Sitzung zu Beginn der Verarbeitung gestartet wird
function start_session_if_needed() {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
}
add_action('init', 'start_session_if_needed');
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
// Den angemeldeten Admin und Bots ausschließen
if ( current_user_can( 'administrator' ) || is_bot_or_spider() ) {
return; // Frühzeitig abbrechen, wenn der angemeldete Benutzer ein Admin ist oder es sich um einen Bot handelt
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// Besuchsdaten abrufen und AJAX-Skript laden
echo "<script>
(function() {
var ajaxUrl = '" . esc_url(admin_url('admin-ajax.php')) . "';
if (!ajaxUrl) {
console.error('AJAX-URL konnte nicht geladen werden.');
return;
}
window.ajaxUrl = ajaxUrl;
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
var isTabActive = true;
// Besuch nur nach 3 Sekunden tracken
setTimeout(function() {
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
}).catch(err => console.error('Fehler beim Start des Besuchs:', err));
}, 3000);
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
if (isTabActive) {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
}
}).catch(err => console.error('Fehler bei Timeout-Überprüfung:', err));
}
}, 30000);
// Besuch beenden: Sichtbarkeit oder Schließen
function sendEndVisit() {
if (window.uniqueId && window.ajaxUrl) {
const payload = JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
});
const beaconSuccess = navigator.sendBeacon(window.ajaxUrl + '?action=check_visit_timeout', payload);
if (!beaconSuccess) {
const xhr = new XMLHttpRequest();
xhr.open('POST', window.ajaxUrl + '?action=check_visit_timeout', false); // Synchrone Anfrage
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(payload);
}
}
}
// Sichtbarkeitswechsel überwachen
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') {
isTabActive = false;
sendEndVisit();
} else {
isTabActive = true;
}
});
// Besuch beim Schließen des Tabs oder Browsers beenden
window.addEventListener('beforeunload', sendEndVisit);
})();
// Safari spezifisch
document.addEventListener('DOMContentLoaded', function() {
// Safari-Erkennung
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
var visitStartTime = localStorage.getItem('visit_start_time') || Date.now();
var uniqueId = window.uniqueId || 'default_user';
var ajaxUrl = window.ajaxUrl || '/wp-admin/admin-ajax.php';
localStorage.setItem('visit_start_time', visitStartTime);
if (isSafari) {
console.log('Safari-Browser erkannt: Spezifische Anpassung aktiv.');
function sendDataForSafari(startTime, endTime) {
var data = {
unique_id: uniqueId,
start_time: startTime,
end_time: endTime,
};
var xhr = new XMLHttpRequest();
xhr.open('POST', ajaxUrl + '?action=update_visit_duration', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
xhr.onload = function() {
if (xhr.status === 200) {
console.log('Safari-Spezifisch:', xhr.responseText);
} else {
console.error('Safari Fehler:', xhr.statusText);
}
};
}
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') {
sendDataForSafari(visitStartTime, Date.now());
}
});
window.addEventListener('beforeunload', function() {
sendDataForSafari(visitStartTime, Date.now());
});
setInterval(function() {
sendDataForSafari(visitStartTime, Date.now());
}, 30000);
}
});
</script>";
}
// Bot-Erkennungsfunktion laden, falls noch nicht geladen
if (!function_exists('is_bot_or_spider')) {
if (file_exists(plugin_dir_path(__FILE__) . 'bot-functions.php')) {
require_once plugin_dir_path(__FILE__) . 'bot-functions.php';
} else {
error_log('bot-functions.php wurde nicht gefunden.');
}
}
add_action('wp_footer', 'start_visit_tracking');
// Besuch beenden und Verweildauer speichern
function end_visit_tracking() {
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time'];
update_option('last_visitor_duration', $duration);
unset($_SESSION['visit_start_time']);
error_log("Visit ended. Duration: " . $duration . " seconds.");
}
}
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Visit Duration: Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Besuche nach Startzeit sortieren (neueste oben)
usort($visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
// Besucher nach Startzeit absteigend sortieren
response.data.updated_visits.sort(function(a, b) {
return b.start_time - a.start_time; // Sortiert absteigend nach Startzeit
});
// Besucher in die Tabelle einfügen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Sek:Min)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
// Besuchsdaten nach Startzeit absteigend sortieren
usort($updated_visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Speichern der neuen Verweildauern
update_option('current_visits', $visits);
// Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
wp_send_json_success(['updated_visits' => $updated_visits]);
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen sehr funktionell
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen oder bei Inaktivität, nur für nicht-Admins
// Option 1
// test Vorteil: Sitzungen bleiben nach automatischen Beenden bis zum Seitenverlassen im Hintergrund offen.
// test Nachteil: Überlange Sitzungszeiten möglich, die nicht real sind.
add_action('wp_footer', function() {
if ( !current_user_can('administrator') ) { // Überprüft, ob der aktuelle Benutzer kein Administrator ist
echo "<script>
// Funktionsblock zur Überwachung der Inaktivität
let inactivityTime = function() {
let timeout;
function resetTimer() {
clearTimeout(timeout);
timeout = setTimeout(endSessionDueToInactivity, 5 * 60 * 1000); // 5 Minuten Inaktivität
}
function endSessionDueToInactivity() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Besuch beendet wegen Inaktivität.');
}
}
// Events für Maus- und Tastaturaktivität zur Reaktivierung des Timers
document.onload = resetTimer;
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
document.onscroll = resetTimer;
};
inactivityTime();
// Besuch beim Schließen des Tabs oder Browsers beenden
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Daten erfolgreich gesendet: Tab oder Browser geschlossen.');
}
});
</script>";
}
});
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen oder bei Inaktivität, nur für nicht-Admins.
// Option 2:
// test Vorteil: Sitzungen werden endgültig nach Timeout abgeschlossen, keine überlangen Zeiten.
// test Nachteil: Nachträgliche Aktionen können nicht mehr hinzugefügt werden.
/*add_action('wp_footer', function() {
if ( !current_user_can('administrator') ) { // Überprüft, ob der aktuelle Benutzer kein Administrator ist
echo "<script>
let timeout;
let sessionEnded = false; // Flag, um den Status der Sitzung zu verfolgen
function resetTimer() {
clearTimeout(timeout);
timeout = setTimeout(endSessionDueToInactivity, 5 * 60 * 1000); // 5 Minuten Inaktivität
}
function endSessionDueToInactivity() {
if (!sessionEnded) { // Sitzung nur einmal als beendet markieren
sessionEnded = true;
console.log('Besuch beendet wegen Inaktivität.');
sendSessionUpdate();
}
}
function sendSessionUpdate() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
}
function handlePageNavigation() {
// Sitzung vor dem Verlassen der Seite sauber beenden
if (!sessionEnded) {
console.log('Seitenwechsel erkannt, Sitzung wird beendet.');
sendSessionUpdate();
sessionEnded = true; // Markiere Sitzung als beendet
}
}
// Benutzeraktivität überwachen
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
document.onscroll = resetTimer;
// Besuch beim Schließen des Tabs oder bei internem Navigieren beenden
window.addEventListener('beforeunload', handlePageNavigation);
window.addEventListener('unload', handlePageNavigation);
// Timer starten, sobald die Seite geladen wird
resetTimer();
</script>";
}
});*/
Entwicklung:
<?php
/*
* Plugin Name: Visit Duration
* Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
* Version: (18.11.24)
* Author: Team WP Wegerl
* Author URI: https://wegerl.at/visit-duration/
* Text Domain: visit-duration
Die Funktion 'is_bot_or_spider' prüft anhand des User-Agents, ob es sich bei einem Besucher um einen Bot handelt.
Diese Funktion nutzt Caching, um wiederholte Anfragen zu vermeiden und verbessert so die Performance.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Am Anfang der Datei, um die Bot-Erkennungsfunktion zu laden
if ( file_exists( plugin_dir_path( __FILE__ ) . 'bot-functions.php' ) ) {
require_once plugin_dir_path( __FILE__ ) . 'bot-functions.php';
} else {
// Fehlerbehandlung, falls die Datei nicht gefunden wurde
error_log('bot-functions.php wurde nicht gefunden.');
}
// Prüft, ob es sich um einen Bot handelt
$is_bot = is_bot_or_spider();
function start_visit_handler() {
check_ajax_referer('your_nonce', 'security');
$unique_id = isset($_POST['unique_id']) ? sanitize_text_field($_POST['unique_id']) : '';
$page_title = isset($_POST['page_title']) ? sanitize_text_field($_POST['page_title']) : '';
if ($unique_id && $page_title) {
global $wpdb;
$table_name = $wpdb->prefix . 'visit_tracking';
$wpdb->insert($table_name, array(
'unique_id' => $unique_id,
'page_title' => $page_title,
'visit_time' => current_time('mysql')
));
wp_send_json_success('Besuch protokolliert');
} else {
wp_send_json_error('Ungültige Daten');
}
}
// Stelle sicher, dass die Sitzung zu Beginn der Verarbeitung gestartet wird
function start_session_if_needed() {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
}
add_action('init', 'start_session_if_needed');
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
// Den angemeldeten Admin und Bots ausschließen
if ( current_user_can( 'administrator' ) || is_bot_or_spider() ) {
return; // Frühzeitig abbrechen, wenn der angemeldete Benutzer ein Admin ist oder es sich um einen Bot handelt
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// Besuchsdaten abrufen und AJAX-Skript laden
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
// Besuch nur nach 3 Sekunden tracken
setTimeout(function() {
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
}, 3000);
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
}
});
}, 30000);
// Besuch beenden beim Schließen des Tabs oder Browsers
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=check_visit_timeout', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
});
// Safari spezifisch
document.addEventListener('DOMContentLoaded', function() {
// Safari-Erkennung
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
var visitStartTime = localStorage.getItem('visit_start_time') || Date.now();
var uniqueId = window.uniqueId || 'default_user';
var ajaxUrl = window.ajaxUrl || '/wp-admin/admin-ajax.php';
localStorage.setItem('visit_start_time', visitStartTime);
if (isSafari) {
console.log('Safari-Browser erkannt: Spezifische Anpassung aktiv.');
function sendDataForSafari(startTime, endTime) {
var data = {
unique_id: uniqueId,
start_time: startTime,
end_time: endTime,
};
var xhr = new XMLHttpRequest();
xhr.open('POST', ajaxUrl + '?action=update_visit_duration', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
xhr.onload = function() {
if (xhr.status === 200) {
console.log('Safari-Spezifisch:', xhr.responseText);
} else {
console.error('Safari Fehler:', xhr.statusText);
}
};
}
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') {
sendDataForSafari(visitStartTime, Date.now());
}
});
window.addEventListener('beforeunload', function() {
sendDataForSafari(visitStartTime, Date.now());
});
setInterval(function() {
sendDataForSafari(visitStartTime, Date.now());
}, 30000);
}
});
</script>";
}
// Bot-Erkennungsfunktion laden, falls noch nicht geladen
if (!function_exists('is_bot_or_spider')) {
if (file_exists(plugin_dir_path(__FILE__) . 'bot-functions.php')) {
require_once plugin_dir_path(__FILE__) . 'bot-functions.php';
} else {
error_log('bot-functions.php wurde nicht gefunden.');
}
}
add_action('wp_footer', 'start_visit_tracking');
// Besuch beenden und Verweildauer speichern
function end_visit_tracking() {
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time'];
update_option('last_visitor_duration', $duration);
unset($_SESSION['visit_start_time']);
error_log("Visit ended. Duration: " . $duration . " seconds.");
}
}
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Visit Duration: Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Besuche nach Startzeit sortieren (neueste oben)
usort($visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
// Besucher nach Startzeit absteigend sortieren
response.data.updated_visits.sort(function(a, b) {
return b.start_time - a.start_time; // Sortiert absteigend nach Startzeit
});
// Besucher in die Tabelle einfügen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Sek:Min)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
// Besuchsdaten nach Startzeit absteigend sortieren
usort($updated_visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Speichern der neuen Verweildauern
update_option('current_visits', $visits);
// Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
wp_send_json_success(['updated_visits' => $updated_visits]);
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen sehr funktionell
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen oder bei Inaktivität, nur für nicht-Admins
// Option 1
// test Vorteil: Sitzungen bleiben nach automatischen Beenden bis zum Seitenverlassen im Hintergrund offen.
// test Nachteil: Überlange Sitzungszeiten möglich, die nicht real sind.
add_action('wp_footer', function() {
if ( !current_user_can('administrator') ) { // Überprüft, ob der aktuelle Benutzer kein Administrator ist
echo "<script>
// Funktionsblock zur Überwachung der Inaktivität
let inactivityTime = function() {
let timeout;
function resetTimer() {
clearTimeout(timeout);
timeout = setTimeout(endSessionDueToInactivity, 5 * 60 * 1000); // 5 Minuten Inaktivität
}
function endSessionDueToInactivity() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Besuch beendet wegen Inaktivität.');
}
}
// Events für Maus- und Tastaturaktivität zur Reaktivierung des Timers
document.onload = resetTimer;
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
document.onscroll = resetTimer;
};
inactivityTime();
// Besuch beim Schließen des Tabs oder Browsers beenden
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Daten erfolgreich gesendet: Tab oder Browser geschlossen.');
}
});
</script>";
}
});
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen oder bei Inaktivität, nur für nicht-Admins.
// Option 2:
// test Vorteil: Sitzungen werden endgültig nach Timeout abgeschlossen, keine überlangen Zeiten.
// test Nachteil: Nachträgliche Aktionen können nicht mehr hinzugefügt werden.
/*add_action('wp_footer', function() {
if ( !current_user_can('administrator') ) { // Überprüft, ob der aktuelle Benutzer kein Administrator ist
echo "<script>
let timeout;
let sessionEnded = false; // Flag, um den Status der Sitzung zu verfolgen
function resetTimer() {
clearTimeout(timeout);
timeout = setTimeout(endSessionDueToInactivity, 5 * 60 * 1000); // 5 Minuten Inaktivität
}
function endSessionDueToInactivity() {
if (!sessionEnded) { // Sitzung nur einmal als beendet markieren
sessionEnded = true;
console.log('Besuch beendet wegen Inaktivität.');
sendSessionUpdate();
}
}
function sendSessionUpdate() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
}
function handlePageNavigation() {
// Sitzung vor dem Verlassen der Seite sauber beenden
if (!sessionEnded) {
console.log('Seitenwechsel erkannt, Sitzung wird beendet.');
sendSessionUpdate();
sessionEnded = true; // Markiere Sitzung als beendet
}
}
// Benutzeraktivität überwachen
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
document.onscroll = resetTimer;
// Besuch beim Schließen des Tabs oder bei internem Navigieren beenden
window.addEventListener('beforeunload', handlePageNavigation);
window.addEventListener('unload', handlePageNavigation);
// Timer starten, sobald die Seite geladen wird
resetTimer();
</script>";
}
});*/
Entwicklung:
<?php
/*
* Plugin Name: Visit Duration
* Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
* Version: 1.0.0 (Entwicklung vom 15.11.24)
* Author: Team WP Wegerl
* Author URI: https://wegerl.at
* Text Domain: visit-duration
Die Funktion 'is_bot_or_spider' prüft anhand des User-Agents, ob es sich bei einem Besucher um einen Bot handelt.
Diese Funktion nutzt Caching, um wiederholte Anfragen zu vermeiden und verbessert so die Performance.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Am Anfang der Datei, um die Bot-Erkennungsfunktion zu laden
if ( file_exists( plugin_dir_path( __FILE__ ) . 'bot-functions.php' ) ) {
require_once plugin_dir_path( __FILE__ ) . 'bot-functions.php';
} else {
// Fehlerbehandlung, falls die Datei nicht gefunden wurde
error_log('bot-functions.php wurde nicht gefunden.');
}
// Beispiel, wie die Funktion verwendet wird:
if ( is_bot_or_spider() ) {
// Bot erkannt, keine Zählung vornehmen
}
// Stelle sicher, dass die Sitzung zu Beginn der Verarbeitung gestartet wird
function start_session_if_needed() {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
}
add_action('init', 'start_session_if_needed');
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
// Den angemeldeten Admin und Bots ausschließen
if ( current_user_can( 'administrator' ) || is_bot_or_spider() ) {
return; // Frühzeitig abbrechen, wenn der angemeldete Benutzer ein Admin ist oder es sich um einen Bot handelt
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// Besuchsdaten abrufen und AJAX-Skript laden
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
}
});
}, 30000);
// Besuch beenden beim Schließen des Tabs oder Browsers
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=check_visit_timeout', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
});
</script>";
}
// Bot-Erkennungsfunktion laden, falls noch nicht geladen
if (!function_exists('is_bot_or_spider')) {
if (file_exists(plugin_dir_path(__FILE__) . 'bot-functions.php')) {
require_once plugin_dir_path(__FILE__) . 'bot-functions.php';
} else {
error_log('bot-functions.php wurde nicht gefunden.');
}
}
add_action('wp_footer', 'start_visit_tracking');
// Besuch beenden und Verweildauer speichern
function end_visit_tracking() {
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time'];
update_option('last_visitor_duration', $duration);
unset($_SESSION['visit_start_time']);
error_log("Visit ended. Duration: " . $duration . " seconds.");
}
}
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// Funktion zur Erkennung von Bots und Spidern
if (is_bot_or_spider()) {
// Bot erkannt
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Visit Duration: Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Besuche nach Startzeit sortieren (neueste oben)
usort($visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
// Besucher nach Startzeit absteigend sortieren
response.data.updated_visits.sort(function(a, b) {
return b.start_time - a.start_time; // Sortiert absteigend nach Startzeit
});
// Besucher in die Tabelle einfügen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Sek:Min)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
// Besuchsdaten nach Startzeit absteigend sortieren
usort($updated_visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Speichern der neuen Verweildauern
update_option('current_visits', $visits);
// Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
wp_send_json_success(['updated_visits' => $updated_visits]);
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen sehr funktionell
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen oder bei Inaktivität, nur für nicht-Admins
add_action('wp_footer', function() {
if ( !current_user_can('administrator') ) { // Überprüft, ob der aktuelle Benutzer kein Administrator ist
echo "<script>
// Funktionsblock zur Überwachung der Inaktivität
let inactivityTime = function() {
let timeout;
function resetTimer() {
clearTimeout(timeout);
timeout = setTimeout(endSessionDueToInactivity, 10 * 60 * 1000); // 10 Minuten Inaktivität
}
function endSessionDueToInactivity() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Besuch beendet wegen Inaktivität.');
}
}
// Events für Maus- und Tastaturaktivität zur Reaktivierung des Timers
document.onload = resetTimer;
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
document.onscroll = resetTimer;
};
inactivityTime();
// Besuch beim Schließen des Tabs oder Browsers beenden
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Daten erfolgreich gesendet: Tab oder Browser geschlossen.');
}
});
</script>";
}
});
Entwicklung.
<?php
/*
* Plugin Name: Visit Duration
* Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
* Entwicklungs-Datum: 15.11.24 / 1
* Author: Team WP Wegerl
* Author URI: https://wegerl.at
* Text Domain: visit-duration
Die Funktion 'is_bot_or_spider' prüft anhand des User-Agents, ob es sich bei einem Besucher um einen Bot handelt.
Diese Funktion nutzt Caching, um wiederholte Anfragen zu vermeiden und verbessert so die Performance.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Am Anfang der Datei, um die Bot-Erkennungsfunktion zu laden
if ( file_exists( plugin_dir_path( __FILE__ ) . 'bot-functions.php' ) ) {
require_once plugin_dir_path( __FILE__ ) . 'bot-functions.php';
} else {
// Fehlerbehandlung, falls die Datei nicht gefunden wurde
error_log('bot-functions.php wurde nicht gefunden.');
}
// Beispiel, wie die Funktion verwendet wird:
if ( is_bot_or_spider() ) {
// Bot erkannt, keine Zählung vornehmen
}
// Stelle sicher, dass die Sitzung zu Beginn der Verarbeitung gestartet wird
function start_session_if_needed() {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
}
add_action('init', 'start_session_if_needed');
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
// Den angemeldeten Admin und Bots ausschließen
if ( current_user_can( 'administrator' ) || is_bot_or_spider() ) {
return; // Frühzeitig abbrechen, wenn der angemeldete Benutzer ein Admin ist oder es sich um einen Bot handelt
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// Besuchsdaten abrufen und AJAX-Skript laden
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
}
});
}, 30000);
// Besuch beenden beim Schließen des Tabs oder Browsers
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=check_visit_timeout', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
});
</script>";
}
// Bot-Erkennungsfunktion laden, falls noch nicht geladen
if (!function_exists('is_bot_or_spider')) {
if (file_exists(plugin_dir_path(__FILE__) . 'bot-functions.php')) {
require_once plugin_dir_path(__FILE__) . 'bot-functions.php';
} else {
error_log('bot-functions.php wurde nicht gefunden.');
}
}
add_action('wp_footer', 'start_visit_tracking');
// Besuch beenden und Verweildauer speichern
function end_visit_tracking() {
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time'];
update_option('last_visitor_duration', $duration);
unset($_SESSION['visit_start_time']);
error_log("Visit ended. Duration: " . $duration . " seconds.");
}
}
// Ajax-Handler für Timeout-Überprüfung
function check_visit_timeout() {
if (!isset($_SESSION['visit_start_time'])) {
wp_send_json(['timeout_reached' => false]);
return;
}
$max_duration = 90 * 60; // 90 Minuten
$current_duration = time() - $_SESSION['visit_start_time'];
// Fehlerprotokollierung hinzufügen
error_log("Current visit duration: " . $current_duration . " seconds.");
if ($current_duration > $max_duration) {
end_visit_tracking(); // Besuch beenden bei Timeout
wp_send_json(['timeout_reached' => true]);
} else {
wp_send_json(['timeout_reached' => false]);
}
}
add_action('wp_ajax_check_visit_timeout', 'check_visit_timeout');
add_action('wp_ajax_nopriv_check_visit_timeout', 'check_visit_timeout');
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// Funktion zur Erkennung von Bots und Spidern
if (is_bot_or_spider()) {
// Bot erkannt
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Visit Duration: Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Besuche nach Startzeit sortieren (neueste oben)
usort($visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
// Besucher nach Startzeit absteigend sortieren
response.data.updated_visits.sort(function(a, b) {
return b.start_time - a.start_time; // Sortiert absteigend nach Startzeit
});
// Besucher in die Tabelle einfügen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Sek:Min)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
// Besuchsdaten nach Startzeit absteigend sortieren
usort($updated_visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Speichern der neuen Verweildauern
update_option('current_visits', $visits);
// Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
wp_send_json_success(['updated_visits' => $updated_visits]);
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen sehr funktionell
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen oder bei Inaktivität, nur für nicht-Admins
add_action('wp_footer', function() {
if ( !current_user_can('administrator') ) { // Überprüft, ob der aktuelle Benutzer kein Administrator ist
echo "<script>
// Funktionsblock zur Überwachung der Inaktivität
let inactivityTime = function() {
let timeout;
function resetTimer() {
clearTimeout(timeout);
timeout = setTimeout(endSessionDueToInactivity, 10 * 60 * 1000); // 5 Minuten Inaktivität
}
function endSessionDueToInactivity() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Besuch beendet wegen Inaktivität.');
}
}
// Events für Maus- und Tastaturaktivität zur Reaktivierung des Timers
document.onload = resetTimer;
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
document.onscroll = resetTimer;
};
inactivityTime();
// Intervallüberprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
// alert('Der Besuch wurde aufgrund des Timeouts beendet.');
}
});
}, 30000); // Intervall von 30 Sekunden
// Besuch beim Schließen des Tabs oder Browsers beenden
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
console.log('Daten erfolgreich gesendet: Tab oder Browser geschlossen.');
}
});
</script>";
}
});
Entwicklung;
<?php
/*
* Plugin Name: Visit Duration
* Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
* Entwicklungs-Datum: 13.11.24
* Author: Team WP Wegerl
* Author URI: https://wegerl.at
* Text Domain: visit-duration
Die Funktion 'is_bot_or_spider' prüft anhand des User-Agents, ob es sich bei einem Besucher um einen Bot handelt.
Diese Funktion nutzt Caching, um wiederholte Anfragen zu vermeiden und verbessert so die Performance.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Am Anfang der Datei, um die Bot-Erkennungsfunktion zu laden
if ( file_exists( plugin_dir_path( __FILE__ ) . 'bot-functions.php' ) ) {
require_once plugin_dir_path( __FILE__ ) . 'bot-functions.php';
} else {
// Fehlerbehandlung, falls die Datei nicht gefunden wurde
error_log('bot-functions.php wurde nicht gefunden.');
}
// Beispiel, wie die Funktion verwendet wird:
if ( is_bot_or_spider() ) {
// Bot erkannt, keine Zählung vornehmen
}
// Stelle sicher, dass die Sitzung zu Beginn der Verarbeitung gestartet wird
function start_session_if_needed() {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
}
add_action('init', 'start_session_if_needed');
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
// Den angemeldeten Admin und Bots ausschließen
if ( current_user_can( 'administrator' ) || is_bot_or_spider() ) {
return; // Frühzeitig abbrechen, wenn der angemeldete Benutzer ein Admin ist oder es sich um einen Bot handelt
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// Besuchsdaten abrufen und AJAX-Skript laden
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
}
});
}, 30000);
// Besuch beenden beim Schließen des Tabs oder Browsers
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=check_visit_timeout', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
});
</script>";
}
// Bot-Erkennungsfunktion laden, falls noch nicht geladen
if (!function_exists('is_bot_or_spider')) {
if (file_exists(plugin_dir_path(__FILE__) . 'bot-functions.php')) {
require_once plugin_dir_path(__FILE__) . 'bot-functions.php';
} else {
error_log('bot-functions.php wurde nicht gefunden.');
}
}
add_action('wp_footer', 'start_visit_tracking');
// Besuch beenden und Verweildauer speichern
function end_visit_tracking() {
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time'];
update_option('last_visitor_duration', $duration);
unset($_SESSION['visit_start_time']);
error_log("Visit ended. Duration: " . $duration . " seconds.");
}
}
// Ajax-Handler für Timeout-Überprüfung
function check_visit_timeout() {
if (!isset($_SESSION['visit_start_time'])) {
wp_send_json(['timeout_reached' => false]);
return;
}
$max_duration = 90 * 60; // 90 Minuten
$current_duration = time() - $_SESSION['visit_start_time'];
// Fehlerprotokollierung hinzufügen
error_log("Current visit duration: " . $current_duration . " seconds.");
if ($current_duration > $max_duration) {
end_visit_tracking(); // Besuch beenden bei Timeout
wp_send_json(['timeout_reached' => true]);
} else {
wp_send_json(['timeout_reached' => false]);
}
}
add_action('wp_ajax_check_visit_timeout', 'check_visit_timeout');
add_action('wp_ajax_nopriv_check_visit_timeout', 'check_visit_timeout');
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// Funktion zur Erkennung von Bots und Spidern
if (is_bot_or_spider()) {
// Bot erkannt
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Visit Duration: Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Besuche nach Startzeit sortieren (neueste oben)
usort($visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
// Besucher nach Startzeit absteigend sortieren
response.data.updated_visits.sort(function(a, b) {
return b.start_time - a.start_time; // Sortiert absteigend nach Startzeit
});
// Besucher in die Tabelle einfügen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Sekunden)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
// Besuchsdaten nach Startzeit absteigend sortieren
usort($updated_visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Speichern der neuen Verweildauern
update_option('current_visits', $visits);
// Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
wp_send_json_success(['updated_visits' => $updated_visits]);
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen sehr funktionell
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen, nur für nicht-Admins
add_action('wp_footer', function() {
if ( !current_user_can('administrator') ) { // Überprüft, ob der aktuelle Benutzer kein Administrator ist
echo "<script>
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
// alert('Der Besuch wurde aufgrund des Timeouts beendet.');
}
});
}, 30000); // Intervall von 30 Sekunden
// Besuch beim Schließen des Tabs beenden
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}), function(response) {
console.log('Daten erfolgreich gesendet:', response);
});
}
});
</script>";
}
});
Entwicklung:
<?php
/*
* Plugin Name: Visit Duration
* Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
* Version: Entwicklung 7
* Author: Team WP Wegerl
* Author URI: https://wegerl.at/visit-duration/
* Text Domain: visit-duration
Die Funktion 'is_bot_or_spider' prüft anhand des User-Agents, ob es sich bei einem Besucher um einen Bot handelt.
Diese Funktion nutzt Caching, um wiederholte Anfragen zu vermeiden und verbessert so die Performance.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Am Anfang der Datei, um die Bot-Erkennungsfunktion zu laden
if ( file_exists( plugin_dir_path( __FILE__ ) . 'bot-functions.php' ) ) {
require_once plugin_dir_path( __FILE__ ) . 'bot-functions.php';
} else {
// Fehlerbehandlung, falls die Datei nicht gefunden wurde
error_log('bot-functions.php wurde nicht gefunden.');
}
// Beispiel, wie die Funktion verwendet wird:
if ( is_bot_or_spider() ) {
// Bot erkannt, keine Zählung vornehmen
}
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
// Den angemeldeten Admin und Bots ausschließen
if ( current_user_can( 'administrator' ) || is_bot_or_spider() ) {
return; // Frühzeitig abbrechen, wenn der angemeldete Benutzer ein Admin ist oder es sich um einen Bot handelt
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// Besuchsdaten abrufen und AJAX-Skript laden
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
}
});
}, 30000);
// Besuch beenden beim Schließen des Tabs oder Browsers
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=check_visit_timeout', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
});
</script>";
}
// Bot-Erkennungsfunktion laden, falls noch nicht geladen
if (!function_exists('is_bot_or_spider')) {
if (file_exists(plugin_dir_path(__FILE__) . 'bot-functions.php')) {
require_once plugin_dir_path(__FILE__) . 'bot-functions.php';
} else {
error_log('bot-functions.php wurde nicht gefunden.');
}
}
add_action('wp_footer', 'start_visit_tracking');
// Besuch beenden und Verweildauer speichern
function end_visit_tracking() {
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time'];
update_option('last_visitor_duration', $duration);
unset($_SESSION['visit_start_time']);
error_log("Visit ended. Duration: " . $duration . " seconds.");
}
}
// Ajax-Handler für Timeout-Überprüfung
function check_visit_timeout() {
if (!isset($_SESSION['visit_start_time'])) {
wp_send_json(['timeout_reached' => false]);
return;
}
$max_duration = 90 * 60; // 90 Minuten
$current_duration = time() - $_SESSION['visit_start_time'];
if ($current_duration > $max_duration) {
end_visit_tracking(); // Besuch beenden bei Timeout
wp_send_json(['timeout_reached' => true]);
} else {
wp_send_json(['timeout_reached' => false]);
}
}
add_action('wp_ajax_check_visit_timeout', 'check_visit_timeout');
add_action('wp_ajax_nopriv_check_visit_timeout', 'check_visit_timeout');
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// Funktion zur Erkennung von Bots und Spidern
if (is_bot_or_spider()) {
// Bot erkannt
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Visit Duration: Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Besuche nach Startzeit sortieren (neueste oben)
usort($visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
// Besucher nach Startzeit absteigend sortieren
response.data.updated_visits.sort(function(a, b) {
return b.start_time - a.start_time; // Sortiert absteigend nach Startzeit
});
// Besucher in die Tabelle einfügen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Sekunden)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
// Besuchsdaten nach Startzeit absteigend sortieren
usort($updated_visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Speichern der neuen Verweildauern
update_option('current_visits', $visits);
// Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
wp_send_json_success(['updated_visits' => $updated_visits]);
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen sehr funktionell
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen
add_action('wp_footer', function() {
echo "<script>
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
// alert('Der Besuch wurde aufgrund des Timeouts beendet.');
}
});
}, 30000); // Intervall von 30 Sekunden
// Besuch beim Schließen des Tabs beenden
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
</script>";
});
Entwicklung:
<?php
/*
Plugin Name: Visit Duration Tracker
Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
Version: Entwicklung 6
Author: Team WP Wegerl.at
Author URI: https://wegerl.at
Text Domain: visit-duration-tracker
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
$user_ip = $_SERVER['REMOTE_ADDR'];
// Admin- und Bot-Ausschluss mithilfe der IP- und Bot-Check-Funktionen
if (function_exists('is_ip_excluded') && is_ip_excluded($user_ip) || is_bot_or_spider()) {
return; // Frühzeitig abbrechen
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Wenn keine Startzeit gesetzt ist, wird sie auf den aktuellen Zeitpunkt gesetzt
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// Besuchsdaten abrufen und AJAX-Skript laden
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
// Hier kannst du zusätzliche Aktionen durchführen, z.B. Seite neu laden oder den Status im Frontend ändern.
// alert('Der Besuch wurde aufgrund des Timeouts beendet.');
}
});
}, 30000); // Alle 30 Sekunden prüfen
// Besuch beim Schließen des Tabs oder Browsers beenden mit sendBeacon
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
// sendBeacon anstelle von fetch verwenden, um die Anfrage zuverlässig abzuschicken
navigator.sendBeacon(window.ajaxUrl + '?action=check_visit_timeout', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now() // Besuch beenden
}));
}
});
});
</script>";
}
add_action('wp_footer', 'start_visit_tracking');
// Besuch beenden und Verweildauer speichern
function end_visit_tracking() {
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time'];
update_option('last_visitor_duration', $duration);
unset($_SESSION['visit_start_time']);
error_log("Visit ended. Duration: " . $duration . " seconds.");
}
}
// Ajax-Handler für Timeout-Überprüfung
function check_visit_timeout() {
if (isset($_SESSION['visit_start_time'])) {
$max_duration = 90 * 60; // 90 Minuten in Sekunden
$current_duration = time() - $_SESSION['visit_start_time'];
if ($current_duration > $max_duration) {
end_visit_tracking(); // Besuch beenden bei Timeout
// Besuchsdaten im Backend aktualisieren
$visits = get_option('current_visits', []);
foreach ($visits as $unique_id => $visit_data) {
if (!isset($visit_data['duration'])) {
// Besuch als beendet markieren
$visits[$unique_id]['status'] = 'Beendet';
$visits[$unique_id]['duration'] = $current_duration;
$visits[$unique_id]['formatted_duration'] = sprintf('%02d:%02d', floor($current_duration / 60), $current_duration % 60);
}
}
update_option('current_visits', $visits); // Besuche aktualisieren
wp_send_json(['timeout_reached' => true]);
} else {
wp_send_json(['timeout_reached' => false]);
}
} else {
wp_send_json(['timeout_reached' => false]);
}
}
add_action('wp_ajax_check_visit_timeout', 'check_visit_timeout');
add_action('wp_ajax_nopriv_check_visit_timeout', 'check_visit_timeout');
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// Funktion zur Erkennung von Bots und Spidern
if (!function_exists('is_bot_or_spider')) {
function is_bot_or_spider() {
$cached_result = get_transient('is_bot_' . $_SERVER['REMOTE_ADDR']);
if ($cached_result !== false) {
return $cached_result;
}
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
$bots = [
'googlebot', 'bingbot', 'slurp', 'duckduckbot', 'baidu', 'yandex',
'sogou', 'exabot', 'facebook', 'twitter', 'linkedin', 'pinterest',
'msnbot', 'bot', 'crawl', 'crawler', 'spider', 'ia_archiver',
'curl', 'fetch', 'python', 'wget', 'monitor'
];
// Erstellen des Regex-Musters basierend auf der Liste der Bot-Namen
$pattern = '/(' . implode('|', $bots) . ')/i';
// Prüfen, ob der User-Agent mit dem Muster übereinstimmt
if (preg_match($pattern, $user_agent)) {
set_transient('is_bot_' . $_SERVER['REMOTE_ADDR'], true, 12 * HOUR_IN_SECONDS);
return true;
}
set_transient('is_bot_' . $_SERVER['REMOTE_ADDR'], false, 12 * HOUR_IN_SECONDS);
return false;
}
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Besuche nach Startzeit sortieren (neueste oben)
usort($visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
// Besucher nach Startzeit absteigend sortieren
response.data.updated_visits.sort(function(a, b) {
return b.start_time - a.start_time; // Sortiert absteigend nach Startzeit
});
// Besucher in die Tabelle einfügen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Sekunden)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
// Besuchsdaten nach Startzeit absteigend sortieren
usort($updated_visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Speichern der neuen Verweildauern
update_option('current_visits', $visits);
// Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
wp_send_json_success(['updated_visits' => $updated_visits]);
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen sehr funktionell
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Timeout-Überprüfung und Beendigung beim Seitenverlassen
add_action('wp_footer', function() {
echo "<script>
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
alert('Der Besuch wurde aufgrund des Timeouts beendet.');
}
});
}, 30000); // Intervall von 30 Sekunden
// Besuch beim Schließen des Tabs beenden
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
</script>";
});
Entwicklung :
<?php
/*
Plugin Name: Visit Duration Tracker
Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
Version: Entwicklung 5
Author: Team WP Wegerl.at
Author URI: https://wegerl.at
Text Domain: visit-duration-tracker
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
$user_ip = $_SERVER['REMOTE_ADDR'];
// Admin- und Bot-Ausschluss mithilfe der IP- und Bot-Check-Funktionen
if (function_exists('is_ip_excluded') && is_ip_excluded($user_ip) || is_bot_or_spider()) {
return; // Frühzeitig abbrechen
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Wenn keine Startzeit gesetzt ist, wird sie auf den aktuellen Zeitpunkt gesetzt
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// Besuchsdaten abrufen und AJAX-Skript laden
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
// Timeout-Überprüfung alle 30 Sekunden
setInterval(function() {
fetch(window.ajaxUrl + '?action=check_visit_timeout', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
}).then(response => response.json()).then(data => {
if (data.timeout_reached) {
console.log('Besuch beendet wegen Timeout.');
// Hier kannst du zusätzliche Aktionen durchführen, z.B. Seite neu laden oder den Status im Frontend ändern.
// alert('Der Besuch wurde aufgrund des Timeouts beendet.');
}
});
}, 30000); // Alle 30 Sekunden prüfen
});
</script>";
}
add_action('wp_footer', 'start_visit_tracking');
// Besuch beenden und Verweildauer speichern
function end_visit_tracking() {
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time'];
update_option('last_visitor_duration', $duration);
unset($_SESSION['visit_start_time']);
error_log("Visit ended. Duration: " . $duration . " seconds.");
}
}
// Ajax-Handler für Timeout-Überprüfung
function check_visit_timeout() {
if (isset($_SESSION['visit_start_time'])) {
$max_duration = 90 * 60; // 90 Minuten in Sekunden
$current_duration = time() - $_SESSION['visit_start_time'];
if ($current_duration > $max_duration) {
end_visit_tracking(); // Besuch beenden bei Timeout
// Besuchsdaten im Backend aktualisieren
$visits = get_option('current_visits', []);
foreach ($visits as $unique_id => $visit_data) {
if (!isset($visit_data['duration'])) {
// Besuch als beendet markieren
$visits[$unique_id]['status'] = 'Beendet';
$visits[$unique_id]['duration'] = $current_duration;
$visits[$unique_id]['formatted_duration'] = sprintf('%02d:%02d', floor($current_duration / 60), $current_duration % 60);
}
}
update_option('current_visits', $visits); // Besuche aktualisieren
wp_send_json(['timeout_reached' => true]);
} else {
wp_send_json(['timeout_reached' => false]);
}
} else {
wp_send_json(['timeout_reached' => false]);
}
}
add_action('wp_ajax_check_visit_timeout', 'check_visit_timeout');
add_action('wp_ajax_nopriv_check_visit_timeout', 'check_visit_timeout');
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// Funktion zur Erkennung von Bots und Spidern
if (!function_exists('is_bot_or_spider')) {
function is_bot_or_spider() {
$cached_result = get_transient('is_bot_' . $_SERVER['REMOTE_ADDR']);
if ($cached_result !== false) {
return $cached_result;
}
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
$bots = [
'googlebot', 'bingbot', 'slurp', 'duckduckbot', 'baidu', 'yandex',
'sogou', 'exabot', 'facebook', 'twitter', 'linkedin', 'pinterest',
'msnbot', 'bot', 'crawl', 'spider', 'ia_archiver'
];
foreach ($bots as $bot) {
if (strpos($user_agent, $bot) !== false) {
set_transient('is_bot_' . $_SERVER['REMOTE_ADDR'], true, 12 * HOUR_IN_SECONDS);
return true;
}
}
set_transient('is_bot_' . $_SERVER['REMOTE_ADDR'], false, 12 * HOUR_IN_SECONDS);
return false;
}
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Besuche nach Startzeit sortieren (neueste oben)
usort($visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
// Besucher nach Startzeit absteigend sortieren
response.data.updated_visits.sort(function(a, b) {
return b.start_time - a.start_time; // Sortiert absteigend nach Startzeit
});
// Besucher in die Tabelle einfügen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Sekunden)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
// Besuchsdaten nach Startzeit absteigend sortieren
usort($updated_visits, function($a, $b) {
return $b['start_time'] - $a['start_time']; // Sortiert absteigend nach Startzeit
});
// Speichern der neuen Verweildauern
update_option('current_visits', $visits);
// Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
wp_send_json_success(['updated_visits' => $updated_visits]);
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Erfassung der Verweildauer beim Seitenverlassen
add_action('wp_footer', function() {
echo "<script>
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
</script>";
});
Entwicklung :
<?php
/*
Plugin Name: Visit Duration Tracker
Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
Version: Entwicklung 4
Author: Team WP Wegerl.at
Author URI: https://wegerl.at
Text Domain: visit-duration-tracker
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
$user_ip = $_SERVER['REMOTE_ADDR'];
// Admin- und Bot-Ausschluss mithilfe der IP- und Bot-Check-Funktionen
if (function_exists('is_ip_excluded') && is_ip_excluded($user_ip) || is_bot_or_spider()) {
return; // Frühzeitig abbrechen
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Wenn keine Startzeit gesetzt, dann setzen wir sie auf den aktuellen Zeitpunkt
if (!isset($_SESSION['visit_start_time'])) {
$_SESSION['visit_start_time'] = time();
}
// 90 Minuten Timeout: Überprüfen, ob die Zeit überschritten wurde
$max_duration = 90 * 60; // 90 Minuten in Sekunden
if (isset($_SESSION['visit_start_time']) && (time() - $_SESSION['visit_start_time']) > $max_duration) {
// Timeout erreicht, Verweildauer als beendet setzen
end_visit_tracking(); // Diese Funktion behandelt das Beenden der Verweildauer
}
// Besuchsdaten abrufen und AJAX-Skript laden
$visits = get_option('current_visits', []);
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
});
</script>";
}
add_action('wp_footer', 'start_visit_tracking');
function end_visit_tracking() {
// Berechnung der Verweildauer
if (isset($_SESSION['visit_start_time'])) {
$duration = time() - $_SESSION['visit_start_time']; // Verweildauer in Sekunden
// Verweildauer speichern, z.B. in einer Option oder einer Sitzung
// Zum Beispiel: update_option('visit_duration_' . session_id(), $duration);
// Oder hier eine einfache Option, um die Verweildauer zu speichern
update_option('last_visitor_duration', $duration); // Option für den letzten Besuch
// Verweildauer zurücksetzen
unset($_SESSION['visit_start_time']);
}
// Optional: Sende eine Nachricht oder führe eine Aktion nach dem Timeout aus
// Zum Beispiel: Benachrichtigung in der Admin-Oberfläche
// wp_mail('admin@deinedomain.com', 'Besucher hat die Seite 90 Minuten lang offen gelassen', 'Die Verweildauer wurde überschritten.');
}
// Optionale Funktion zum Abfragen der letzten Verweildauer
function get_last_visitor_duration() {
return get_option('last_visitor_duration', 0); // Standardwert ist 0, falls keine Verweildauer gesetzt
}
add_action('wp_footer', 'end_visit_tracking');
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// Funktion zur Erkennung von Bots und Spidern
if (!function_exists('is_bot_or_spider')) {
function is_bot_or_spider() {
$cached_result = get_transient('is_bot_' . $_SERVER['REMOTE_ADDR']);
if ($cached_result !== false) {
return $cached_result;
}
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
$bots = [
'googlebot', 'bingbot', 'slurp', 'duckduckbot', 'baidu', 'yandex',
'sogou', 'exabot', 'facebook', 'twitter', 'linkedin', 'pinterest',
'msnbot', 'bot', 'crawl', 'spider', 'ia_archiver'
];
foreach ($bots as $bot) {
if (strpos($user_agent, $bot) !== false) {
set_transient('is_bot_' . $_SERVER['REMOTE_ADDR'], true, 12 * HOUR_IN_SECONDS);
return true;
}
}
set_transient('is_bot_' . $_SERVER['REMOTE_ADDR'], false, 12 * HOUR_IN_SECONDS);
return false;
}
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 25 Einträge begrenzen
$max_visits = get_option('max_visits', 25);
if (count($visits) > $max_visits) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
// Überprüfen, ob die erforderlichen Daten vorhanden sind
if (!isset($data['unique_id']) || !isset($data['end_time'])) {
wp_send_json_error(['message' => 'Fehlende Parameter']);
return;
}
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000); // Zeitstempel konvertieren
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten und Scrollfunktion
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
// Füge CSS hinzu, um den Scrollbalken nur bei Bedarf anzuzeigen
echo '<style>
/* Standardmäßig versteckter Scrollbalken, der nur bei Bedarf erscheint */
#visit-duration-container {
height: 360px;
overflow-y: auto; /* Scrollbalken erscheint nur bei Bedarf */
border: 1px solid #ddd;
}
/* Schmaler Scrollbalken für Webkit-basierte Browser */
#visit-duration-container::-webkit-scrollbar {
width: 4px; /* Schmaler Scrollbalken */
}
#visit-duration-container::-webkit-scrollbar-thumb {
background-color: darkgray;
border-radius: 10px;
}
#visit-duration-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
</style>';
// Scrollbarer Container für die Tabelle
echo '<div id="visit-duration-container">';
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
// <thead> mit Sticky-Header-Styling
echo '<thead style="position: sticky; top: 0; background-color: #fff; z-index: 1;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
echo '</thead>';
echo '<tbody>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
// Setze die Hintergrundfarbe abhängig vom Status
$row_color = ($status === 'Aktiv') ? 'rgba(255, 235, 59, 0.7)' : '#fff';
echo '<tr style="background-color: ' . $row_color . ';">';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
echo '</div>'; // Ende des scrollbaren Containers
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Die Tabelle aktualisieren und die Zeilen mit den korrekten Hintergrundfarben
var tableBody = jQuery('#visit-duration-table').find('tbody');
tableBody.empty(); // Bestehende Zeilen löschen
response.data.updated_visits.forEach(function(visit) {
var rowColor = (visit.status === "Aktiv") ? "#ffeb3b" : "#fff";
var formattedDuration = visit.formatted_duration || 'Noch aktiv';
tableBody.append(
'<tr style="background-color: ' + rowColor + '">' +
'<td>' + visit.page_title + '</td>' +
'<td>' + formattedDuration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<thead><tr><th>Seiten-Titel</th><th>Verweildauer (Sekunden)</th><th>Status</th></tr></thead><tbody></tbody>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
update_option('current_visits', $visits); // Speichern der neuen Verweildauern
wp_send_json_success(['updated_visits' => $updated_visits]); // Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Erfassung der Verweildauer beim Seitenverlassen
add_action('wp_footer', function() {
echo "<script>
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
</script>";
});
Entwicklung:
<?php
/*
Plugin Name: Visit Duration Tracker
Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
Version: Entwicklung 3
Author: Team WP Wegerl.at
*/
// Besuchs-Tracking initialisieren
function start_visit_tracking() {
$user_ip = $_SERVER['REMOTE_ADDR'];
// Admin- und Bot-Ausschluss mithilfe der IP- und Bot-Check-Funktionen
if (function_exists('is_ip_excluded') && is_ip_excluded($user_ip) || is_bot_or_spider()) {
return; // Frühzeitig abbrechen
}
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Besuchsdaten abrufen und AJAX-Skript laden
$visits = get_option('current_visits', []);
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16);
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
});
</script>";
}
add_action('wp_footer', 'start_visit_tracking');
// Prüft, ob die IP ausgeschlossen ist
if (!function_exists('is_ip_excluded')) {
function is_ip_excluded($user_ip) {
$excluded_ips = get_option('excluded_ips', array());
return in_array($user_ip, $excluded_ips);
}
}
// Funktion zur Erkennung von Bots und Spidern
if (!function_exists('is_bot_or_spider')) {
function is_bot_or_spider() {
$cached_result = get_transient('is_bot_' . $_SERVER['REMOTE_ADDR']);
if ($cached_result !== false) {
return $cached_result;
}
$user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
$bots = [
'googlebot', 'bingbot', 'slurp', 'duckduckbot', 'baidu', 'yandex',
'sogou', 'exabot', 'facebook', 'twitter', 'linkedin', 'pinterest',
'msnbot', 'bot', 'crawl', 'spider', 'ia_archiver'
];
foreach ($bots as $bot) {
if (strpos($user_agent, $bot) !== false) {
set_transient('is_bot_' . $_SERVER['REMOTE_ADDR'], true, 12 * HOUR_IN_SECONDS);
return true;
}
}
set_transient('is_bot_' . $_SERVER['REMOTE_ADDR'], false, 12 * HOUR_IN_SECONDS);
return false;
}
}
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 12 Einträge begrenzen
if (count($visits) > 12) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000);
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
echo '<tr>';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>'; // Formatiert als "Min:Sek"
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</table>';
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Erstelle die neue Tabelle ohne Unique ID
jQuery('#visit-duration-table').html('<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>');
response.data.updated_visits.forEach(function(visit) {
jQuery('#visit-duration-table').append(
'<tr>' +
'<td>' + visit.page_title + '</td>' +
'<td>' + visit.formatted_duration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<tr><th>Seiten-Titel</th><th>Verweildauer (Sekunden)</th><th>Status</th></tr>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
// Funktion zur Aktualisierung der Verweildauer aller Besuche
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
update_option('current_visits', $visits); // Speichern der neuen Verweildauern
wp_send_json_success(['updated_visits' => $updated_visits]); // Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Erfassung der Verweildauer beim Seitenverlassen
add_action('wp_footer', function() {
echo "<script>
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
</script>";
});
Entwicklung:
<?php
/*
Plugin Name: Visit Duration Tracker
Description: Ermöglicht das Messen der Verweildauer von Besuchern auf einer WordPress-Seite ohne Cookies und ohne separate Datenbanktabelle. DSGVO-konform.
Version: Entwicklung 2
Author: Team WP Wegerl.at
Author URI: https://wegerl.at
Text Domain: visit-duration-tracker
*/
// Funktion zur Initialisierung und Startzeit eines Besuchs
function start_visit_tracking() {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Besuchsdaten aus der Optionstabelle abrufen
$visits = get_option('current_visits', []);
// Übergabe von AJAX-URL und Anweisungen an den Browser
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
// Bei jedem Neuladen der Seite wird eine neue ID generiert
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16); // Zufällige ID erstellen
// Senden der Startzeit und ID an den Server
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
});
</script>";
}
add_action('wp_footer', 'start_visit_tracking');
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 12 Einträge begrenzen
if (count($visits) > 12) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000);
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
echo '<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
// Berechne Minuten und Sekunden für die Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
} else {
$formatted_duration = 'Noch aktiv';
}
echo '<tr>';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($formatted_duration) . '</td>'; // Formatiert als "Min:Sek"
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</table>';
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Erstelle die neue Tabelle ohne Unique ID
jQuery('#visit-duration-table').html('<tr><th>Seiten-Titel</th><th>Verweildauer (Min:Sek)</th><th>Status</th></tr>');
response.data.updated_visits.forEach(function(visit) {
jQuery('#visit-duration-table').append(
'<tr>' +
'<td>' + visit.page_title + '</td>' +
'<td>' + visit.formatted_duration + '</td>' +
'<td>' + visit.status + '</td>' +
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button mit Doppel-Klick-Mechanismus
document.getElementById('reset-duration-btn').addEventListener('click', function(event) {
event.preventDefault();
if (this.dataset.clickedOnce === "true") {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<tr><th>Seiten-Titel</th><th>Verweildauer (Sekunden)</th><th>Status</th></tr>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
} else {
this.dataset.clickedOnce = "true";
this.innerText = "Zum Bestätigen erneut klicken";
setTimeout(() => {
this.dataset.clickedOnce = "false";
this.innerText = "Tabelle zurücksetzen";
}, 1500);
}
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
update_option('current_visits', $visits); // Speichern der neuen Verweildauern
wp_send_json_success(['updated_visits' => $updated_visits]); // Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Erfassung der Verweildauer beim Seitenverlassen
add_action('wp_footer', function() {
echo "<script>
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
</script>";
});
Verweildauer in WordPress tracken: Eine einfache Lösung mit "Visit Duration Tracker"
Entwicklung:
<?php
/*
Plugin Name: Visit Duration Tracker
Description:
Version: Entwicklung 1
Author: Team WP Wegerl.at
*/
// Funktion zur Initialisierung und Startzeit eines Besuchs
function start_visit_tracking() {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Besuchsdaten aus der Optionstabelle abrufen
$visits = get_option('current_visits', []);
// Übergabe von AJAX-URL und Anweisungen an den Browser
echo "<script>
window.ajaxUrl = '" . admin_url('admin-ajax.php') . "';
// Bei jedem Neuladen der Seite wird eine neue ID generiert
window.addEventListener('load', function() {
window.uniqueId = 'id-' + Math.random().toString(36).substr(2, 16); // Zufällige ID erstellen
// Senden der Startzeit und ID an den Server
fetch(window.ajaxUrl + '?action=start_visit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ unique_id: window.uniqueId, page_title: document.title })
});
});
</script>";
}
add_action('wp_footer', 'start_visit_tracking');
// AJAX-Handler zur Speicherung der Startzeit
function start_visit() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$page_title = strip_tags($data['page_title']);
$start_time = time();
$visits = get_option('current_visits', []);
// Neuer Eintrag für jeden Seitenaufruf speichern
$visits[$unique_id] = [
'unique_id' => $unique_id,
'page_title' => $page_title,
'start_time' => $start_time
];
// Besuche auf 12 Einträge begrenzen
if (count($visits) > 12) {
array_shift($visits);
}
update_option('current_visits', $visits);
wp_send_json_success();
}
add_action('wp_ajax_start_visit', 'start_visit');
add_action('wp_ajax_nopriv_start_visit', 'start_visit');
// Funktion zur Aktualisierung der Verweildauer
function update_visit_duration() {
$data = json_decode(file_get_contents('php://input'), true);
$unique_id = sanitize_text_field($data['unique_id']);
$end_time = intval($data['end_time'] / 1000);
$visits = get_option('current_visits', []);
if (isset($visits[$unique_id])) {
$duration = $end_time - $visits[$unique_id]['start_time'];
$visits[$unique_id]['duration'] = $duration;
update_option('current_visits', $visits);
}
wp_send_json_success();
}
add_action('wp_ajax_update_visit_duration', 'update_visit_duration');
add_action('wp_ajax_nopriv_update_visit_duration', 'update_visit_duration');
// Widget zur Anzeige der Verweildauer
function add_visit_duration_dashboard_widget() {
wp_add_dashboard_widget(
'visit_duration_widget',
'Aktuelle Verweildauer der Besucher',
'display_visit_duration_widget'
);
}
add_action('wp_dashboard_setup', 'add_visit_duration_dashboard_widget');
// Widget für das Dashboard mit aktualisierten Besuchsdaten
function display_visit_duration_widget() {
$visits = get_option('current_visits', []);
if (empty($visits)) {
echo '<p>Keine aktuellen Besuchsdaten verfügbar.</p>';
return;
}
echo '<table id="visit-duration-table" style="width:100%; text-align:left;">';
echo '<tr><th>Unique ID</th><th>Seiten-Titel</th><th>Verweildauer (Sekunden)</th><th>Status</th></tr>';
foreach ($visits as $visit_data) {
// Setze den Status basierend auf dem Vorhandensein der Verweildauer
$status = isset($visit_data['duration']) ? 'Beendet' : 'Aktiv';
$duration = isset($visit_data['duration']) ? $visit_data['duration'] : 'Noch aktiv';
echo '<tr>';
echo '<td>' . esc_html($visit_data['unique_id']) . '</td>';
echo '<td>' . esc_html($visit_data['page_title']) . '</td>';
echo '<td>' . esc_html($duration) . '</td>';
echo '<td>' . esc_html($status) . '</td>';
echo '</tr>';
}
echo '</table>';
echo '<button id="reset-duration-btn" class="reset-button" style="margin: 15px 15px 0;">Tabelle zurücksetzen</button>';
echo '<button id="update-duration-btn" class="update-button">Verweildauer aktualisieren</button>';
?>
<script type="text/javascript">
// "Update"-Button
document.getElementById('update-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'update_all_visit_durations',
},
success: function(response) {
if (response.success) {
// Erstelle die neue Tabelle mit formatierten Verweildauern und Status
jQuery('#visit-duration-table').html('<tr><th>Unique ID</th><th>Seiten-Titel</th><th>Verweildauer (Minuten:Sekunden)</th><th>Status</th></tr>');
response.data.updated_visits.forEach(function(visit) {
// Zeige die formatierten Zeiten (Minuten:Sekunden) und den Status an
jQuery('#visit-duration-table').append(
'<tr>' +
'<td>' + visit.unique_id + '</td>' +
'<td>' + visit.page_title + '</td>' +
'<td>' + visit.formatted_duration + '</td>' +
'<td>' + visit.status + '</td>' + // Status hinzugefügt
'</tr>'
);
});
} else {
alert('Fehler bei der Aktualisierung der Verweildauer.');
}
},
error: function() {
alert('Fehler beim Aktualisieren der Verweildauer.');
}
});
});
// "Reset"-Button
document.getElementById('reset-duration-btn').addEventListener('click', function() {
jQuery.ajax({
url: '<?php echo admin_url('admin-ajax.php'); ?>',
type: 'POST',
data: {
action: 'reset_visit_duration',
},
success: function(response) {
if (response.success) {
// Die Tabelle zurücksetzen und nur die Kopfzeile anzeigen
jQuery('#visit-duration-table').html('<tr><th>Unique ID</th><th>Seiten-Titel</th><th>Verweildauer (Sekunden)</th><th>Status</th></tr>');
} else {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
},
error: function() {
alert('Fehler beim Zurücksetzen der Tabelle.');
}
});
});
</script>
<?php
}
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
// Funktion zur Aktualisierung der Verweildauer aller Besuche
// AJAX-Handler zur Aktualisierung der Verweildauer aller Besucher
function update_all_visit_durations() {
$visits = get_option('current_visits', []);
$updated_visits = [];
foreach ($visits as $visit_id => $visit_data) {
// Nur Besucher, bei denen die Verweildauer noch nicht festgelegt wurde (d.h., die noch aktiv sind)
if (isset($visit_data['start_time']) && !isset($visit_data['duration'])) {
// Berechne die Verweildauer für die aktiven Besuche
$duration = time() - $visit_data['start_time']; // Verwende aktuelle Zeit
$visit_data['duration'] = $duration;
$visit_data['status'] = 'Aktiv'; // Status bleibt 'Aktiv', wenn noch keine Dauer
} else {
// Wenn die Verweildauer bereits festgelegt ist, wird der Status als 'Beendet' angezeigt
$visit_data['status'] = 'Beendet';
}
// Berechne Minuten und Sekunden für jede Verweildauer
if (isset($visit_data['duration'])) {
$minutes = floor($visit_data['duration'] / 60);
$seconds = $visit_data['duration'] % 60;
$formatted_duration = sprintf('%02d:%02d', $minutes, $seconds);
$visit_data['formatted_duration'] = $formatted_duration;
} else {
$visit_data['formatted_duration'] = 'Noch aktiv';
}
$updated_visits[] = $visit_data; // Füge die (aktualisierte) Besuchsdaten hinzu
}
update_option('current_visits', $visits); // Speichern der neuen Verweildauern
wp_send_json_success(['updated_visits' => $updated_visits]); // Sende die aktualisierten Daten mit der formatierten Dauer und dem Status zurück
}
add_action('wp_ajax_update_all_visit_durations', 'update_all_visit_durations');
add_action('wp_ajax_nopriv_update_all_visit_durations', 'update_all_visit_durations');
// Besuchsdaten zurücksetzen
function reset_visit_duration() {
delete_option('current_visits');
update_option('current_visits', []);
wp_cache_flush();
wp_send_json_success();
}
add_action('wp_ajax_reset_visit_duration', 'reset_visit_duration');
add_action('wp_ajax_nopriv_reset_visit_duration', 'reset_visit_duration');
// JavaScript zur Erfassung der Verweildauer beim Seitenverlassen
add_action('wp_footer', function() {
echo "<script>
window.addEventListener('beforeunload', function() {
if (window.uniqueId && window.ajaxUrl) {
navigator.sendBeacon(window.ajaxUrl + '?action=update_visit_duration', JSON.stringify({
unique_id: window.uniqueId,
end_time: Date.now()
}));
}
});
</script>";
});