| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/clients/client2/web20/web/wp-content/plugins/dgt-verkehrsmonitor/includes/ |
Upload File : |
<?php
/**
* Admin-Bereich: Dashboard-Widget, Menü, Quellen-Verwaltung.
*
* @package dgt-verkehrsmonitor
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/* ─── Admin-Menü ───────────────────────────────────────── */
function dgt_vm_admin_menu() {
add_menu_page(
'Verkehrsmonitor',
'Verkehrsmonitor',
'manage_options',
'dgt-vm',
'dgt_vm_admin_dashboard',
'dashicons-chart-line',
30
);
add_submenu_page(
'dgt-vm',
'Meldungen',
'Meldungen',
'manage_options',
'dgt-vm',
'dgt_vm_admin_dashboard'
);
add_submenu_page(
'dgt-vm',
'Quellen',
'Quellen',
'manage_options',
'dgt-vm-sources',
'dgt_vm_admin_sources'
);
add_submenu_page(
'dgt-vm',
'Statistiken',
'Statistiken',
'manage_options',
'dgt-vm-stats',
'dgt_vm_admin_stats'
);
add_submenu_page(
'dgt-vm',
'Einstellungen',
'Einstellungen',
'manage_options',
'dgt-vm-settings',
'dgt_vm_admin_settings'
);
}
add_action( 'admin_menu', 'dgt_vm_admin_menu' );
/* ─── Dashboard (Meldungen-Übersicht) ──────────────────── */
function dgt_vm_admin_dashboard() {
global $wpdb;
$t = dgt_vm_table( 'incidents' );
$counts = $wpdb->get_row(
"SELECT
COUNT(*) AS total,
SUM(status = 'publiziert') AS publiziert,
SUM(status = 'entwurf') AS entwurf,
SUM(status = 'abgelehnt') AS abgelehnt,
SUM(source_date = CURDATE()) AS heute
FROM {$t}"
);
$per_page = 25;
$paged = max( 1, (int) ( $_GET['paged'] ?? 1 ) );
$offset = ( $paged - 1 ) * $per_page;
$search = sanitize_text_field( $_GET['s'] ?? '' );
$fil_status = sanitize_text_field( $_GET['vm_status'] ?? '' );
$fil_bl = sanitize_text_field( $_GET['vm_bl'] ?? '' );
$fil_delikt = sanitize_text_field( $_GET['vm_delikt'] ?? '' );
$where = array();
$values = array();
if ( $search ) {
$like = '%' . $wpdb->esc_like( $search ) . '%';
$where[] = '(ai_headline LIKE %s OR stadt LIKE %s OR original_titel LIKE %s)';
$values[] = $like;
$values[] = $like;
$values[] = $like;
}
if ( $fil_status ) {
$where[] = 'status = %s';
$values[] = $fil_status;
}
if ( $fil_bl ) {
$where[] = 'bundesland = %s';
$values[] = $fil_bl;
}
if ( $fil_delikt ) {
$where[] = 'delikt = %s';
$values[] = $fil_delikt;
}
$where_sql = $where ? 'WHERE ' . implode( ' AND ', $where ) : '';
$total_q = "SELECT COUNT(*) FROM {$t} {$where_sql}";
$total = $values ? (int) $wpdb->get_var( $wpdb->prepare( $total_q, $values ) ) : (int) $wpdb->get_var( $total_q );
$pages = max( 1, (int) ceil( $total / $per_page ) );
if ( $paged > $pages ) {
$paged = $pages;
$offset = ( $paged - 1 ) * $per_page;
}
$query = "SELECT id, ai_headline, ai_slug, bundesland, stadt, delikt, source_date, status
FROM {$t} {$where_sql}
ORDER BY created_at DESC
LIMIT %d OFFSET %d";
$qvals = array_merge( $values, array( $per_page, $offset ) );
$rows = $wpdb->get_results( $wpdb->prepare( $query, $qvals ) );
$base_url = admin_url( 'admin.php?page=dgt-vm' );
?>
<div class="wrap">
<h1>Verkehrsmonitor – Meldungen</h1>
<div style="display:flex;gap:1rem;margin:1.5rem 0;">
<div class="card" style="flex:1;padding:1rem;text-align:center;">
<h2 style="margin:0;font-size:2rem;"><?php echo (int) $counts->total; ?></h2>
<p>Gesamt</p>
</div>
<div class="card" style="flex:1;padding:1rem;text-align:center;">
<h2 style="margin:0;font-size:2rem;color:#46b450;"><?php echo (int) $counts->publiziert; ?></h2>
<p>Publiziert</p>
</div>
<div class="card" style="flex:1;padding:1rem;text-align:center;">
<h2 style="margin:0;font-size:2rem;color:#f0b849;"><?php echo (int) $counts->entwurf; ?></h2>
<p>Entwurf</p>
</div>
<div class="card" style="flex:1;padding:1rem;text-align:center;">
<h2 style="margin:0;font-size:2rem;color:#dc3232;"><?php echo (int) $counts->abgelehnt; ?></h2>
<p>Abgelehnt</p>
</div>
<div class="card" style="flex:1;padding:1rem;text-align:center;">
<h2 style="margin:0;font-size:2rem;"><?php echo (int) $counts->heute; ?></h2>
<p>Heute</p>
</div>
</div>
<form method="get" action="<?php echo esc_url( $base_url ); ?>" style="margin:1rem 0;display:flex;gap:.5rem;flex-wrap:wrap;align-items:end;">
<input type="hidden" name="page" value="dgt-vm">
<div>
<input type="search" name="s" value="<?php echo esc_attr( $search ); ?>" placeholder="Headline, Stadt, Titel …" style="width:260px;">
</div>
<div>
<select name="vm_status">
<option value="">– Status –</option>
<?php foreach ( array( 'publiziert', 'entwurf', 'abgelehnt' ) as $st ) : ?>
<option value="<?php echo esc_attr( $st ); ?>" <?php selected( $fil_status, $st ); ?>><?php echo esc_html( ucfirst( $st ) ); ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<select name="vm_bl">
<option value="">– Bundesland –</option>
<?php foreach ( dgt_vm_bundeslaender() as $slug => $name ) : ?>
<option value="<?php echo esc_attr( $slug ); ?>" <?php selected( $fil_bl, $slug ); ?>><?php echo esc_html( $name ); ?></option>
<?php endforeach; ?>
</select>
</div>
<div>
<select name="vm_delikt">
<option value="">– Delikt –</option>
<?php foreach ( array( 'alkohol' => 'Alkohol', 'drogen' => 'Drogen', 'beides' => 'Beides', 'sonstiges' => 'Sonstiges' ) as $dk => $dl ) : ?>
<option value="<?php echo esc_attr( $dk ); ?>" <?php selected( $fil_delikt, $dk ); ?>><?php echo esc_html( $dl ); ?></option>
<?php endforeach; ?>
</select>
</div>
<?php submit_button( 'Suchen', 'primary', 'submit', false ); ?>
<?php if ( $search || $fil_status || $fil_bl || $fil_delikt ) : ?>
<a href="<?php echo esc_url( $base_url ); ?>" class="button">Zurücksetzen</a>
<?php endif; ?>
</form>
<?php if ( $search || $fil_status || $fil_bl || $fil_delikt ) : ?>
<p><?php printf( '<strong>%d</strong> Ergebnis(se) gefunden.', $total ); ?></p>
<?php endif; ?>
<table class="widefat striped">
<thead>
<tr>
<th>ID</th>
<th>Headline</th>
<th>Bundesland</th>
<th>Stadt</th>
<th>Delikt</th>
<th>Datum</th>
<th>Status</th>
<th>Frontend</th>
</tr>
</thead>
<tbody>
<?php if ( $rows ) : foreach ( $rows as $r ) : ?>
<tr>
<td><?php echo (int) $r->id; ?></td>
<td><?php echo esc_html( $r->ai_headline ?: '(kein Titel)' ); ?></td>
<td><?php echo esc_html( dgt_vm_bundesland_name( $r->bundesland ) ?: $r->bundesland ); ?></td>
<td><?php echo esc_html( $r->stadt ); ?></td>
<td><?php echo esc_html( $r->delikt ); ?></td>
<td><?php echo esc_html( $r->source_date ); ?></td>
<td><?php echo esc_html( $r->status ); ?></td>
<td>
<?php if ( 'publiziert' === $r->status && $r->ai_slug ) : ?>
<a href="<?php echo esc_url( dgt_vm_incident_url( $r ) ); ?>" target="_blank" class="button button-small">Ansehen ↗</a>
<?php else : ?>
<span style="color:#999;">–</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; else : ?>
<tr><td colspan="8">Keine Meldungen gefunden.</td></tr>
<?php endif; ?>
</tbody>
</table>
<?php if ( $pages > 1 ) : ?>
<div class="tablenav" style="margin-top:1rem;">
<div class="tablenav-pages">
<span class="displaying-num"><?php printf( '%d Einträge', $total ); ?></span>
<?php
$pag_args = array(
's' => $search,
'vm_status' => $fil_status,
'vm_bl' => $fil_bl,
'vm_delikt' => $fil_delikt,
);
$pag_args = array_filter( $pag_args );
if ( $paged > 1 ) :
$prev_url = add_query_arg( array_merge( $pag_args, array( 'page' => 'dgt-vm', 'paged' => $paged - 1 ) ), $base_url );
?>
<a class="prev-page button" href="<?php echo esc_url( $prev_url ); ?>">‹</a>
<?php endif; ?>
<span class="paging-input">
<strong><?php echo $paged; ?></strong> von <strong><?php echo $pages; ?></strong>
</span>
<?php if ( $paged < $pages ) :
$next_url = add_query_arg( array_merge( $pag_args, array( 'page' => 'dgt-vm', 'paged' => $paged + 1 ) ), $base_url );
?>
<a class="next-page button" href="<?php echo esc_url( $next_url ); ?>">›</a>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
<?php
}
/* ─── Quellen-Verwaltung ──────────────────────────────── */
function dgt_vm_admin_sources() {
global $wpdb;
$t = dgt_vm_table( 'sources' );
// Neue Quelle speichern.
if ( isset( $_POST['dgt_vm_source_nonce'] ) && wp_verify_nonce( $_POST['dgt_vm_source_nonce'], 'dgt_vm_add_source' ) ) {
$wpdb->insert( $t, array(
'name' => sanitize_text_field( $_POST['name'] ),
'bundesland' => sanitize_title( $_POST['bundesland'] ),
'feed_url' => esc_url_raw( $_POST['feed_url'] ),
'feed_type' => sanitize_text_field( $_POST['feed_type'] ),
'aktiv' => 1,
'created_at' => current_time( 'mysql' ),
) );
echo '<div class="updated"><p>Quelle hinzugefügt.</p></div>';
}
$sources = $wpdb->get_results( "SELECT * FROM {$t} ORDER BY bundesland, name" );
?>
<div class="wrap">
<h1>Verkehrsmonitor – Quellen</h1>
<h2>Neue Quelle hinzufügen</h2>
<form method="post" style="max-width:600px;">
<?php wp_nonce_field( 'dgt_vm_add_source', 'dgt_vm_source_nonce' ); ?>
<table class="form-table">
<tr>
<th><label for="name">Name</label></th>
<td><input type="text" id="name" name="name" class="regular-text" required></td>
</tr>
<tr>
<th><label for="bundesland">Bundesland</label></th>
<td>
<select id="bundesland" name="bundesland" required>
<?php foreach ( dgt_vm_bundeslaender() as $slug => $label ) : ?>
<option value="<?php echo esc_attr( $slug ); ?>"><?php echo esc_html( $label ); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr>
<th><label for="feed_url">Feed-URL</label></th>
<td><input type="url" id="feed_url" name="feed_url" class="regular-text" required></td>
</tr>
<tr>
<th><label for="feed_type">Typ</label></th>
<td>
<select id="feed_type" name="feed_type">
<option value="rss">RSS</option>
<option value="html">HTML</option>
<option value="api">API</option>
</select>
</td>
</tr>
</table>
<?php submit_button( 'Quelle hinzufügen' ); ?>
</form>
<h2>Aktive Quellen (<?php echo count( $sources ); ?>)</h2>
<table class="widefat striped">
<thead>
<tr>
<th>Name</th>
<th>Bundesland</th>
<th>Feed-URL</th>
<th>Typ</th>
<th>Letzter Fetch</th>
<th>Letzte Anzahl</th>
<th>Aktiv</th>
</tr>
</thead>
<tbody>
<?php if ( $sources ) : foreach ( $sources as $s ) : ?>
<tr>
<td><?php echo esc_html( $s->name ); ?></td>
<td><?php echo esc_html( dgt_vm_bundesland_name( $s->bundesland ) ); ?></td>
<td><code style="font-size:11px;"><?php echo esc_html( $s->feed_url ); ?></code></td>
<td><?php echo esc_html( $s->feed_type ); ?></td>
<td><?php echo $s->last_fetch ? esc_html( dgt_vm_format_datetime( $s->last_fetch ) ) : '–'; ?></td>
<td><?php echo (int) $s->last_count; ?></td>
<td><?php echo $s->aktiv ? '✓' : '✗'; ?></td>
</tr>
<?php endforeach; else : ?>
<tr><td colspan="7">Noch keine Quellen konfiguriert.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
}
/* ─── Statistik-Seite ──────────────────────────────────── */
function dgt_vm_admin_stats() {
global $wpdb;
$t = dgt_vm_table( 'daily_stats' );
$stats = $wpdb->get_results(
"SELECT * FROM {$t} WHERE bundesland = '_all' ORDER BY stat_date DESC LIMIT 30"
);
?>
<div class="wrap">
<h1>Verkehrsmonitor – Tagesstatistiken</h1>
<table class="widefat striped">
<thead>
<tr>
<th>Datum</th>
<th>Gesamt</th>
<th>Alkohol</th>
<th>Drogen</th>
<th>Beides</th>
<th>Unfälle</th>
<th>FS entzogen</th>
<th>∅ BAK</th>
<th>Max BAK</th>
</tr>
</thead>
<tbody>
<?php if ( $stats ) : foreach ( $stats as $s ) : ?>
<tr>
<td><?php echo esc_html( $s->stat_date ); ?></td>
<td><strong><?php echo (int) $s->total; ?></strong></td>
<td><?php echo (int) $s->alkohol; ?></td>
<td><?php echo (int) $s->drogen; ?></td>
<td><?php echo (int) $s->beides; ?></td>
<td><?php echo (int) $s->unfaelle; ?></td>
<td><?php echo (int) $s->fs_entzogen; ?></td>
<td><?php echo $s->avg_bak ? number_format_i18n( $s->avg_bak, 2 ) : '–'; ?></td>
<td><?php echo $s->max_bak ? number_format_i18n( $s->max_bak, 2 ) : '–'; ?></td>
</tr>
<?php endforeach; else : ?>
<tr><td colspan="9">Noch keine Statistiken vorhanden.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php
}
/* ─── Einstellungen ────────────────────────────────────── */
function dgt_vm_admin_settings() {
if ( isset( $_POST['dgt_vm_settings_nonce'] ) && wp_verify_nonce( $_POST['dgt_vm_settings_nonce'], 'dgt_vm_save_settings' ) ) {
update_option( 'dgt_vm_api_key', sanitize_text_field( $_POST['dgt_vm_api_key'] ) );
update_option( 'dgt_vm_ai_model', sanitize_text_field( $_POST['dgt_vm_ai_model'] ) );
echo '<div class="updated"><p>Einstellungen gespeichert.</p></div>';
}
if ( isset( $_POST['dgt_vm_action_nonce'] ) && wp_verify_nonce( $_POST['dgt_vm_action_nonce'], 'dgt_vm_manual_action' ) ) {
$action = sanitize_text_field( $_POST['dgt_vm_action'] );
if ( 'fetch' === $action ) {
dgt_vm_do_fetch_sources();
$count = get_option( 'dgt_vm_last_fetch_count', 0 );
echo '<div class="updated"><p>Fetch abgeschlossen: ' . (int) $count . ' neue Meldungen.</p></div>';
} elseif ( 'analyze' === $action ) {
dgt_vm_do_analyze_pending();
$count = get_option( 'dgt_vm_last_analyze_count', 0 );
echo '<div class="updated"><p>Analyse abgeschlossen: ' . (int) $count . ' Meldungen analysiert.</p></div>';
} elseif ( 'aggregate' === $action ) {
dgt_vm_do_aggregate_daily();
echo '<div class="updated"><p>Tagesstatistik für gestern aggregiert.</p></div>';
} elseif ( 'scrape' === $action ) {
$count = dgt_vm_do_scrape_listings( 5 );
$count2 = dgt_vm_do_scrape_categories( 3 );
echo '<div class="updated"><p>Scrape abgeschlossen: ' . ( (int) $count + (int) $count2 ) . ' neue Meldungen.</p></div>';
} elseif ( 'refetch' === $action ) {
$count = dgt_vm_refetch_fulltext( 50 );
echo '<div class="updated"><p>Volltext aktualisiert: ' . (int) $count . ' Einträge.</p></div>';
} elseif ( 'seed' === $action ) {
dgt_vm_seed_sources();
echo '<div class="updated"><p>Seed-Quellen eingefügt.</p></div>';
}
}
$api_key = get_option( 'dgt_vm_api_key', '' );
$model = get_option( 'dgt_vm_ai_model', 'claude-haiku-4-5-20251001' );
$last_fetch = get_option( 'dgt_vm_last_fetch', '–' );
$last_analyze = get_option( 'dgt_vm_last_analyze', '–' );
$last_aggregate = get_option( 'dgt_vm_last_aggregate', '–' );
global $wpdb;
$pending = (int) $wpdb->get_var( "SELECT COUNT(*) FROM " . dgt_vm_table( 'incidents' ) . " WHERE status = 'entwurf'" );
?>
<div class="wrap">
<h1>Verkehrsmonitor – Einstellungen</h1>
<h2>API-Konfiguration</h2>
<form method="post" style="max-width:600px;">
<?php wp_nonce_field( 'dgt_vm_save_settings', 'dgt_vm_settings_nonce' ); ?>
<table class="form-table">
<tr>
<th><label for="dgt_vm_api_key">Claude API-Key</label></th>
<td>
<input type="password" id="dgt_vm_api_key" name="dgt_vm_api_key" class="regular-text" value="<?php echo esc_attr( $api_key ); ?>">
<p class="description">Anthropic API-Key für die KI-Analyse.</p>
</td>
</tr>
<tr>
<th><label for="dgt_vm_ai_model">KI-Modell</label></th>
<td>
<select id="dgt_vm_ai_model" name="dgt_vm_ai_model">
<option value="claude-haiku-4-5-20251001" <?php selected( $model, 'claude-haiku-4-5-20251001' ); ?>>Claude Haiku 4.5 (günstig)</option>
<option value="claude-sonnet-4-6" <?php selected( $model, 'claude-sonnet-4-6' ); ?>>Claude Sonnet 4.6 (mittel)</option>
<option value="claude-opus-4-6" <?php selected( $model, 'claude-opus-4-6' ); ?>>Claude Opus 4.6 (beste Qualität)</option>
</select>
</td>
</tr>
</table>
<?php submit_button( 'Speichern' ); ?>
</form>
<hr>
<h2>Status</h2>
<table class="widefat" style="max-width:600px;">
<tr><th>Letzter Fetch</th><td><?php echo esc_html( $last_fetch ); ?></td></tr>
<tr><th>Letzte Analyse</th><td><?php echo esc_html( $last_analyze ); ?></td></tr>
<tr><th>Letzte Aggregation</th><td><?php echo esc_html( $last_aggregate ); ?></td></tr>
<tr><th>Ausstehende Entwürfe</th><td><strong><?php echo $pending; ?></strong></td></tr>
</table>
<hr>
<h2>Manuelle Aktionen</h2>
<p>Aktionen werden normalerweise automatisch per System-Crontab ausgeführt.</p>
<div style="display:flex;gap:1rem;flex-wrap:wrap;">
<form method="post" style="display:inline;">
<?php wp_nonce_field( 'dgt_vm_manual_action', 'dgt_vm_action_nonce' ); ?>
<input type="hidden" name="dgt_vm_action" value="fetch">
<?php submit_button( 'Quellen jetzt fetchen', 'secondary', 'submit', false ); ?>
</form>
<form method="post" style="display:inline;">
<?php wp_nonce_field( 'dgt_vm_manual_action', 'dgt_vm_action_nonce' ); ?>
<input type="hidden" name="dgt_vm_action" value="analyze">
<?php submit_button( 'Entwürfe analysieren (' . $pending . ')', 'secondary', 'submit', false ); ?>
</form>
<form method="post" style="display:inline;">
<?php wp_nonce_field( 'dgt_vm_manual_action', 'dgt_vm_action_nonce' ); ?>
<input type="hidden" name="dgt_vm_action" value="aggregate">
<?php submit_button( 'Statistik aggregieren', 'secondary', 'submit', false ); ?>
</form>
<form method="post" style="display:inline;">
<?php wp_nonce_field( 'dgt_vm_manual_action', 'dgt_vm_action_nonce' ); ?>
<input type="hidden" name="dgt_vm_action" value="scrape">
<?php submit_button( 'Listings scrapen', 'secondary', 'submit', false ); ?>
</form>
<form method="post" style="display:inline;">
<?php wp_nonce_field( 'dgt_vm_manual_action', 'dgt_vm_action_nonce' ); ?>
<input type="hidden" name="dgt_vm_action" value="refetch">
<?php submit_button( 'Volltext nachladen', 'secondary', 'submit', false ); ?>
</form>
<form method="post" style="display:inline;">
<?php wp_nonce_field( 'dgt_vm_manual_action', 'dgt_vm_action_nonce' ); ?>
<input type="hidden" name="dgt_vm_action" value="seed">
<?php submit_button( 'Seed-Quellen einfügen', 'secondary', 'submit', false ); ?>
</form>
</div>
</div>
<?php
}