7 Commits
Author SHA1 Message Date
M_Viper 980ccb2cc1 Upload via GUI (41 Dateien) 2026-10-01 05:59:49 +00:00
M_Viper db4863de16 Upload via GUI (41 Dateien) 2026-10-01 05:59:48 +00:00
M_Viper 1016373e63 Upload via GUI (41 Dateien) 2026-10-01 05:59:47 +00:00
M_Viper b6ebdde2cf Upload via GUI (40 Dateien) 2026-08-23 15:23:16 +00:00
M_Viper 0f1c5632d6 Upload via GUI (40 Dateien) 2026-08-23 15:23:16 +00:00
M_Viper 8bcb7f1303 Upload via GUI (40 Dateien) 2026-08-23 15:23:15 +00:00
M_Viper 2b162e581f Upload via GUI (30 Dateien) 2026-08-23 13:35:10 +00:00
7 changed files with 2916 additions and 215 deletions
+1
View File
@@ -48,6 +48,7 @@ function wp_multi_register_admin_menus() {
// Analyse & Inhalte
$hooks[] = add_submenu_page($parent, __('Benutzer Analytics', 'wp-multi'), __('Benutzer-Analytics', 'wp-multi'), 'manage_options', 'wp_multi_analytics', 'wp_multi_display_user_analytics');
$hooks[] = add_submenu_page($parent, __('Gast-Autor Übersicht', 'wp-multi'), __('Gast-Autoren', 'wp-multi'), 'manage_options', 'guest_author_overview', 'wp_multi_guest_author_overview_page');
$hooks[] = add_submenu_page($parent, __('Veröffentlichungen', 'wp-multi'), __('Veröffentlichungen', 'wp-multi'), 'manage_options', 'wp-multi-publications', 'wp_multi_publication_stats_page');
$hooks[] = add_submenu_page($parent, __('Pinwand', 'wp-multi'), __('Pinwand', 'wp-multi'), 'manage_options', 'message-board', 'wp_multi_add_message_board');
$hooks[] = add_submenu_page($parent, __('Gemeldete Beiträge', 'wp-multi'), __('Gemeldete Beiträge', 'wp-multi'), 'manage_options', 'reported-posts', 'wp_stat_notice_reported_posts_page');
$hooks[] = add_submenu_page($parent, __('WP Stat & Notice', 'wp-multi'), __('Statistik & Notice', 'wp-multi'), 'manage_options', 'statistik_manager', 'statistik_manager_options_page');
+136 -29
View File
@@ -1,13 +1,13 @@
<?php
/**
* WP Multi – Modul: Benutzer-Analytics
*
* Automatisch aus wp-multi.php ausgelagert. Wird über den Loader in
* wp-multi.php eingebunden (nur wenn das WP Multi Toolkit aktiv ist).
*/
if (!defined('ABSPATH')) { exit; }
<?php
/**
* WP Multi – Modul: Benutzer-Analytics
*
* Automatisch aus wp-multi.php ausgelagert. Wird über den Loader in
* wp-multi.php eingebunden (nur wenn das WP Multi Toolkit aktiv ist).
*/
if (!defined('ABSPATH')) { exit; }
/*
* Benutzer-Analytics mit verbesserten Sicherheits-, Performance- und Benutzerfreundlichkeits-Features
*/
@@ -62,7 +62,8 @@ function wp_multi_track_user_activity($user_id, $action, $post_id = null) {
$post_id = get_the_ID();
}
if (!$user_id || !$action) {
// user_id 0 ist gewollt: Die Seite bietet keinen Login, Aufrufe sind anonym.
if (!$action) {
return false;
}
@@ -92,26 +93,93 @@ function wp_multi_comment_activity($comment_id) {
add_action('comment_post', 'wp_multi_comment_activity');
/**
* Verfolgt Beitragsaufrufe.
* Erkennt Bots und Crawler anhand des User-Agents.
*
* @return bool
*/
function wp_multi_is_bot_request() {
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? strtolower(sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT']))) : '';
// Ohne User-Agent ist es so gut wie nie ein echter Besucher
if ($user_agent === '') {
return true;
}
$signatures = apply_filters('wp_multi_bot_signatures', [
'bot', 'crawl', 'spider', 'slurp', 'archiver', 'preview', 'monitor', 'validator',
'facebookexternalhit', 'headless', 'phantomjs', 'python', 'curl', 'wget', 'go-http',
'java/', 'okhttp', 'axios', 'scrapy', 'lighthouse', 'pingdom', 'uptime',
]);
foreach ($signatures as $signature) {
if (strpos($user_agent, $signature) !== false) {
return true;
}
}
return false;
}
/**
* Prüft, ob dieser Besucher den Beitrag gerade erst aufgerufen hat.
*
* Ohne Login gibt es keine Benutzer-ID, deshalb dient ein kurzlebiger Hash aus
* IP und User-Agent als Erkennungsmerkmal. Gespeichert wird nur der Hash, nie
* die IP selbst – ein Reload zählt damit nicht mehrfach.
*
* @param int $post_id Beitrag-ID.
* @return bool True, wenn der Aufruf bereits gezählt wurde.
*/
function wp_multi_view_already_counted($post_id) {
$ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '';
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';
$key = 'wp_multi_view_' . md5($post_id . '|' . $ip . '|' . $user_agent . '|' . wp_salt());
if (get_transient($key)) {
return true;
}
// Sperrfrist je Besucher und Beitrag
$ttl = (int) apply_filters('wp_multi_view_throttle', 30 * MINUTE_IN_SECONDS);
set_transient($key, 1, $ttl);
return false;
}
/**
* Verfolgt Beitragsaufrufe – auch von nicht eingeloggten Besuchern.
*/
function wp_multi_post_view_activity() {
if (is_single() && is_user_logged_in()) {
$user_id = get_current_user_id();
$post_id = get_the_ID();
wp_multi_track_user_activity($user_id, 'view', $post_id);
if (is_admin() || !is_single() || is_feed()) {
return;
}
// Eigene Aufrufe der Redaktion verfälschen die Zahlen
if (is_user_logged_in() && current_user_can('edit_posts') && !apply_filters('wp_multi_track_editor_views', false)) {
return;
}
if (wp_multi_is_bot_request()) {
return;
}
$post_id = get_the_ID();
if (!$post_id || wp_multi_view_already_counted($post_id)) {
return;
}
// Ohne Login ist die Benutzer-ID 0 – der Aufruf zählt trotzdem
wp_multi_track_user_activity(get_current_user_id(), 'view', $post_id);
}
add_action('wp_head', 'wp_multi_post_view_activity');
/**
* Ruft rohe Analytics-Daten aus der Datenbank ab.
* Baut die WHERE-Bedingung für den gewählten Zeitraum.
*
* @param string $date_query Datum für die Abfrage.
* @return array Rohe Analytics-Daten.
* @return array [WHERE-Fragment, Parameter]
*/
function wp_multi_fetch_raw_analytics($start_datetime = null, $end_datetime = null) {
global $wpdb;
function wp_multi_analytics_date_where($start_datetime = null, $end_datetime = null) {
if (!$start_datetime) {
$start_datetime = gmdate('Y-m-d H:i:s', strtotime('-7 days'));
}
@@ -124,22 +192,60 @@ function wp_multi_fetch_raw_analytics($start_datetime = null, $end_datetime = nu
$params[] = $end_datetime;
}
$sql = "SELECT DATE(timestamp) AS date, action, post_id, COUNT(*) AS count, user_id, timestamp
return [$where, $params];
}
/**
* Ruft die Tagessummen je Aktion ab – Grundlage für das Diagramm.
*
* Aggregiert in der Datenbank statt in PHP: Seit auch anonyme Aufrufe gezählt
* werden, kämen hier sonst zehntausende Einzelzeilen an.
*
* @return array Zeilen mit date, action und count.
*/
function wp_multi_fetch_analytics_totals($start_datetime = null, $end_datetime = null) {
global $wpdb;
list($where, $params) = wp_multi_analytics_date_where($start_datetime, $end_datetime);
$sql = "SELECT DATE(timestamp) AS date, action, COUNT(*) AS count
FROM " . WP_MULTI_ANALYTICS_TABLE . "
$where
GROUP BY date, action, post_id, user_id, timestamp
GROUP BY date, action
ORDER BY date ASC";
return $wpdb->get_results($wpdb->prepare($sql, $params));
}
/**
* Ruft die jüngsten Einzelereignisse ab – Grundlage für die Detailtabelle.
*
* @return array Rohe Analytics-Daten (begrenzt).
*/
function wp_multi_fetch_raw_analytics($start_datetime = null, $end_datetime = null) {
global $wpdb;
list($where, $params) = wp_multi_analytics_date_where($start_datetime, $end_datetime);
// Ohne Limit würde die Detailtabelle bei anonymem Tracking die Seite sprengen
$params[] = (int) apply_filters('wp_multi_analytics_detail_limit', 200);
$sql = "SELECT DATE(timestamp) AS date, action, post_id, 1 AS count, user_id, timestamp
FROM " . WP_MULTI_ANALYTICS_TABLE . "
$where
ORDER BY timestamp DESC
LIMIT %d";
return $wpdb->get_results($wpdb->prepare($sql, $params));
}
/**
* Verarbeitet rohe Analytics-Daten für Diagramm und Tabelle.
*
* @param array $results Rohe Analytics-Daten.
* @return array Verarbeitete Daten.
*/
function wp_multi_process_analytics_data($results) {
function wp_multi_process_analytics_data($results, $recent = null) {
$dates = [];
$comment_counts = [];
$view_counts = [];
@@ -184,7 +290,7 @@ function wp_multi_process_analytics_data($results) {
'fill' => false,
]
],
'data' => $results
'data' => ($recent === null) ? $results : $recent
];
}
@@ -202,8 +308,9 @@ function wp_multi_get_analytics_data($start_datetime = null, $end_datetime = nul
return $cached_data;
}
$results = wp_multi_fetch_raw_analytics($start_datetime, $end_datetime);
$data = wp_multi_process_analytics_data($results);
$totals = wp_multi_fetch_analytics_totals($start_datetime, $end_datetime);
$recent = wp_multi_fetch_raw_analytics($start_datetime, $end_datetime);
$data = wp_multi_process_analytics_data($totals, $recent);
set_transient($cache_key, $data, HOUR_IN_SECONDS);
return $data;
@@ -308,7 +415,7 @@ function wp_multi_display_user_analytics() {
<tbody>
<?php foreach ($results['data'] as $row) : ?>
<tr>
<td><?php echo esc_html($row->user_id); ?></td>
<td><?php echo $row->user_id ? esc_html($row->user_id) : esc_html__('Gast', 'wp-multi'); ?></td>
<td><span class="wpm-badge <?php echo $row->action === 'comment' ? 'wpm-badge--success' : ''; ?>"><?php echo esc_html($row->action); ?></span></td>
<td>
<?php
+1837 -115
View File
File diff suppressed because it is too large Load Diff
+696
View File
@@ -0,0 +1,696 @@
<?php
/**
* WP Multi – Modul: Veröffentlichungs-Statistik
*
* Zeigt, wie viele Geschichten pro Monat und pro Jahr veröffentlicht wurden.
* Eigener Menüpunkt, unabhängig von der Gast-Autoren-Übersicht.
*/
if (!defined('ABSPATH')) { exit; }
/**
* Liefert die Reiter der Statistik-Seite.
*/
function wp_multi_get_publication_stats_tabs() {
return [
'month' => __('Monat', 'wp-multi'),
'year' => __('Jahr', 'wp-multi'),
'timeline' => __('Autoren-Zeitlinie', 'wp-multi'),
];
}
/**
* Beitragstyp, der gezählt wird.
*/
function wp_multi_get_stats_post_type() {
return apply_filters('wp_multi_stats_post_type', 'post');
}
/**
* Liefert alle Jahre mit veröffentlichten Beiträgen samt Anzahl.
*
* @return array Zeilen mit year und total, aufsteigend nach Jahr.
*/
function wp_multi_get_publication_years() {
static $years = null;
if ($years !== null) {
return $years;
}
global $wpdb;
$years = $wpdb->get_results($wpdb->prepare("
SELECT YEAR(post_date) AS year, COUNT(*) AS total
FROM {$wpdb->posts}
WHERE post_status = 'publish'
AND post_type = %s
GROUP BY YEAR(post_date)
ORDER BY year ASC
", wp_multi_get_stats_post_type()));
return $years;
}
/**
* Callback für den Menüpunkt "Veröffentlichungen".
*/
function wp_multi_publication_stats_page() {
$tabs = wp_multi_get_publication_stats_tabs();
$current_tab = isset($_GET['tab']) ? sanitize_key($_GET['tab']) : 'month';
if (!isset($tabs[$current_tab])) {
$current_tab = 'month';
}
$base_url = add_query_arg(['page' => 'wp-multi-publications'], admin_url('admin.php'));
wpmt_admin_page_open(
__('Veröffentlichungen', 'wp-multi'),
__('Wie viele Geschichten pro Monat und pro Jahr veröffentlicht wurden', 'wp-multi')
);
?>
<h2 class="nav-tab-wrapper wpm-tabs">
<?php foreach ($tabs as $tab_key => $tab_label) : ?>
<a href="<?php echo esc_url(add_query_arg('tab', $tab_key, $base_url)); ?>"
class="nav-tab<?php echo ($current_tab === $tab_key) ? ' nav-tab-active' : ''; ?>">
<?php echo esc_html($tab_label); ?>
</a>
<?php endforeach; ?>
</h2>
<?php
if ($current_tab === 'year') {
wp_multi_render_publication_years($base_url);
} elseif ($current_tab === 'timeline') {
wp_multi_render_author_timeline($base_url);
} else {
wp_multi_render_publication_months($base_url);
}
wpmt_admin_page_close();
}
/**
* Gibt eine Balkenzeile für die Statistik aus.
*
* Bewusst ohne Diagramm-Bibliothek: ein <div> mit prozentualer Breite reicht,
* lädt nichts nach und funktioniert auch ohne JavaScript.
*
* @param string $label Beschriftung links.
* @param int $value Wert.
* @param int $max Größter Wert der Reihe (für die Balkenbreite).
*/
function wp_multi_render_stat_bar($label, $value, $max) {
$percent = $max > 0 ? round(($value / $max) * 100) : 0;
?>
<div class="wpm-stat-row" style="display:flex;align-items:center;gap:10px;margin-bottom:6px;">
<span class="wpm-stat-label" style="flex:0 0 6em;"><?php echo esc_html($label); ?></span>
<span class="wpm-stat-track" style="flex:1 1 auto;background:rgba(125,125,125,.2);border-radius:3px;overflow:hidden;">
<span class="wpm-stat-fill" style="display:block;height:14px;width:<?php echo (int) $percent; ?>%;min-width:<?php echo $value > 0 ? '2px' : '0'; ?>;background:#2271b1;"></span>
</span>
<span class="wpm-stat-value" style="flex:0 0 5em;text-align:right;"><?php echo esc_html(number_format_i18n($value)); ?></span>
</div>
<?php
}
/**
* Reiter "Monat": Veröffentlichungen je Monat eines Jahres.
*
* @param string $base_url Basis-URL der Seite.
*/
function wp_multi_render_publication_months($base_url) {
global $wpdb, $wp_locale;
$years = wp_multi_get_publication_years();
if (!$years) {
wpmt_admin_card_open(__('Veröffentlichungen pro Monat', 'wp-multi'));
echo '<p class="wpm-muted">' . esc_html__('Noch keine veröffentlichten Beiträge gefunden.', 'wp-multi') . '</p>';
wpmt_admin_card_close();
return;
}
$available_years = [];
foreach ($years as $row) {
$available_years[] = (int) $row->year;
}
// Jahr aus der URL, sonst das jüngste Jahr mit Beiträgen
$selected_year = isset($_GET['stats_year']) ? (int) $_GET['stats_year'] : 0;
if (!in_array($selected_year, $available_years, true)) {
$selected_year = end($available_years);
}
$months = $wpdb->get_results($wpdb->prepare("
SELECT MONTH(post_date) AS month, COUNT(*) AS total
FROM {$wpdb->posts}
WHERE post_status = 'publish'
AND post_type = %s
AND YEAR(post_date) = %d
GROUP BY MONTH(post_date)
ORDER BY month ASC
", wp_multi_get_stats_post_type(), $selected_year));
$month_totals = array_fill(1, 12, 0);
$max_month_total = 0;
$year_total = 0;
foreach ((array) $months as $row) {
$month_totals[(int) $row->month] = (int) $row->total;
$year_total += (int) $row->total;
if ((int) $row->total > $max_month_total) {
$max_month_total = (int) $row->total;
}
}
$month_url = add_query_arg('tab', 'month', $base_url);
wpmt_admin_card_open(__('Veröffentlichungen pro Monat', 'wp-multi'));
?>
<p class="wpm-muted">
<?php _e('Jahr:', 'wp-multi'); ?>
<?php foreach (array_reverse($available_years) as $year) : ?>
<a href="<?php echo esc_url(add_query_arg('stats_year', $year, $month_url)); ?>" class="wpm-filter-link<?php echo ($year === $selected_year) ? ' wpm-filter-active' : ''; ?>">
<?php if ($year === $selected_year) : ?><strong><?php endif; ?>
<?php echo esc_html($year); ?>
<?php if ($year === $selected_year) : ?></strong><?php endif; ?>
</a>
<?php endforeach; ?>
</p>
<p class="wpm-muted">
<?php
printf(
/* translators: 1: Jahr, 2: Anzahl der Geschichten */
esc_html__('%1$d: %2$s Geschichten', 'wp-multi'),
(int) $selected_year,
esc_html(number_format_i18n($year_total))
);
?>
</p>
<div class="wpm-stat-chart" style="margin:16px 0;">
<?php for ($month = 1; $month <= 12; $month++) : ?>
<?php wp_multi_render_stat_bar($wp_locale->get_month_abbrev($wp_locale->get_month($month)), $month_totals[$month], $max_month_total); ?>
<?php endfor; ?>
</div>
<div class="wpm-table-wrap">
<table class="wpm-table">
<thead>
<tr>
<th><?php _e('Monat', 'wp-multi'); ?></th>
<th><?php _e('Geschichten', 'wp-multi'); ?></th>
</tr>
</thead>
<tbody>
<?php for ($month = 1; $month <= 12; $month++) : ?>
<tr>
<td><?php echo esc_html($wp_locale->get_month($month)); ?></td>
<td>
<?php if ($month_totals[$month]) : ?>
<?php echo esc_html(number_format_i18n($month_totals[$month])); ?>
<?php else : ?>
<span class="wpm-muted">0</span>
<?php endif; ?>
</td>
</tr>
<?php endfor; ?>
</tbody>
</table>
</div>
<?php
wpmt_admin_card_close();
}
/**
* Reiter "Jahr": Veröffentlichungen je Jahr.
*
* @param string $base_url Basis-URL der Seite.
*/
function wp_multi_render_publication_years($base_url) {
$years = wp_multi_get_publication_years();
wpmt_admin_card_open(__('Veröffentlichungen pro Jahr', 'wp-multi'));
if (!$years) {
echo '<p class="wpm-muted">' . esc_html__('Noch keine veröffentlichten Beiträge gefunden.', 'wp-multi') . '</p>';
wpmt_admin_card_close();
return;
}
$max_total = 0;
$grand_total = 0;
$totals_by_year = [];
foreach ($years as $row) {
$totals_by_year[(int) $row->year] = (int) $row->total;
$grand_total += (int) $row->total;
if ((int) $row->total > $max_total) {
$max_total = (int) $row->total;
}
}
$year_keys = array_keys($totals_by_year);
$month_url = add_query_arg('tab', 'month', $base_url);
?>
<p class="wpm-muted">
<?php
printf(
/* translators: 1: Gesamtzahl, 2: erstes Jahr, 3: letztes Jahr */
esc_html__('%1$s Geschichten von %2$d bis %3$d.', 'wp-multi'),
esc_html(number_format_i18n($grand_total)),
(int) reset($year_keys),
(int) end($year_keys)
);
?>
</p>
<div class="wpm-stat-chart" style="margin:16px 0;">
<?php foreach ($totals_by_year as $year => $total) : ?>
<?php wp_multi_render_stat_bar($year, $total, $max_total); ?>
<?php endforeach; ?>
</div>
<div class="wpm-table-wrap">
<table class="wpm-table">
<thead>
<tr>
<th><?php _e('Jahr', 'wp-multi'); ?></th>
<th><?php _e('Geschichten', 'wp-multi'); ?></th>
<th><?php _e('Veränderung', 'wp-multi'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach (array_reverse($totals_by_year, true) as $year => $total) : ?>
<tr>
<td>
<a href="<?php echo esc_url(add_query_arg('stats_year', (int) $year, $month_url)); ?>" title="<?php esc_attr_e('Monate dieses Jahres anzeigen', 'wp-multi'); ?>">
<?php echo esc_html($year); ?>
</a>
</td>
<td><?php echo esc_html(number_format_i18n($total)); ?></td>
<td>
<?php
// Vergleich mit dem Vorjahr, sofern es Beiträge hatte
if (!isset($totals_by_year[$year - 1])) {
echo '<span class="wpm-muted">—</span>';
} else {
$diff = $total - $totals_by_year[$year - 1];
$sign = $diff > 0 ? '+' : '';
echo '<span class="wpm-muted">' . esc_html($sign . number_format_i18n($diff)) . '</span>';
}
?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php
wpmt_admin_card_close();
}
/**
* Liefert die Beschriftungen der Autorengruppen.
*/
function wp_multi_get_timeline_groups() {
return [
'lost' => '🔥 ' . __('Verlorene Vielschreiber', 'wp-multi'),
'active' => '🟢 ' . __('Aktive Autoren', 'wp-multi'),
'new' => '🆕 ' . __('Neue Autoren', 'wp-multi'),
'quiet' => '🟡 ' . __('Stille Autoren', 'wp-multi'),
'one_off' => '💤 ' . __('Einmalige Autoren', 'wp-multi'),
];
}
/**
* Ordnet einen Autor anhand seiner Jahreswerte einer Gruppe zu.
*
* 🆕 Neue Autoren – erste Geschichte im aktuellen oder letzten Jahr
* 🔥 Verlorene Vielschreiber – früher ein starkes Jahr, seitdem so gut wie nichts
* 🟢 Aktive Autoren – im aktuellen oder letzten Jahr veröffentlicht
* 💤 Einmalige Autoren – höchstens zwei Geschichten und lange nichts mehr
* 🟡 Stille Autoren – alles dazwischen: mehr als zwei Geschichten, aber still
*
* @param array $per_year Zuordnung "Jahr => Anzahl".
* @param int $current_year Aktuelles Jahr.
* @return string Gruppenschlüssel.
*/
function wp_multi_get_author_timeline_group($per_year, $current_year) {
// Die letzten beiden Jahre gelten als "jetzt"
$recent_span = (int) apply_filters('wp_multi_timeline_recent_years', 2);
$recent_from = $current_year - ($recent_span - 1);
$total = 0;
$recent_total = 0;
$earlier_peak = 0;
$first_year = null;
foreach ($per_year as $year => $count) {
$year = (int) $year;
$count = (int) $count;
$total += $count;
if ($first_year === null || $year < $first_year) {
$first_year = $year;
}
if ($year >= $recent_from) {
$recent_total += $count;
} elseif ($count > $earlier_peak) {
$earlier_peak = $count;
}
}
// Wer erst im "Jetzt"-Fenster angefangen hat, ist neu
if ($first_year !== null && $first_year >= $recent_from) {
return 'new';
}
$prolific_min = (int) apply_filters('wp_multi_timeline_prolific_min', 5);
$recent_max = (int) apply_filters('wp_multi_timeline_recent_max', 1);
if ($earlier_peak >= $prolific_min && $recent_total <= $recent_max) {
return 'lost';
}
if ($recent_total > 0) {
return 'active';
}
if ($total <= 2) {
return 'one_off';
}
return 'quiet';
}
/**
* Liest die Veröffentlichungen je Autor und Jahr.
*
* @return array Zuordnung "Autor => [Jahr => Anzahl]".
*/
function wp_multi_get_author_timeline_data() {
static $data = null;
if ($data !== null) {
return $data;
}
global $wpdb;
$rows = $wpdb->get_results($wpdb->prepare("
SELECT pm.meta_value AS guest_author, YEAR(p.post_date) AS year, COUNT(*) AS total
FROM {$wpdb->posts} p
INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_guest_author' AND pm.meta_value != ''
WHERE p.post_status = 'publish'
AND p.post_type = %s
GROUP BY pm.meta_value, YEAR(p.post_date)
", wp_multi_get_stats_post_type()));
$data = [];
foreach ((array) $rows as $row) {
$data[$row->guest_author][(int) $row->year] = (int) $row->total;
}
return $data;
}
/**
* Reiter "Autoren-Zeitlinie": wer wann geschrieben hat und wer abgewandert ist.
*
* @param string $base_url Basis-URL der Seite.
*/
function wp_multi_render_author_timeline($base_url) {
$years = wp_multi_get_publication_years();
$timeline = wp_multi_get_author_timeline_data();
if (!$years || !$timeline) {
wpmt_admin_card_open(__('Autoren-Zeitlinie', 'wp-multi'));
echo '<p class="wpm-muted">' . esc_html__('Noch keine Daten für eine Zeitlinie vorhanden.', 'wp-multi') . '</p>';
wpmt_admin_card_close();
return;
}
$available_years = [];
foreach ($years as $row) {
$available_years[] = (int) $row->year;
}
$current_year = (int) end($available_years);
// Nur die letzten Jahre als Spalten, sonst wird die Tabelle unlesbar
$columns = (int) apply_filters('wp_multi_timeline_columns', 6);
$column_years = array_slice($available_years, -$columns);
$groups = wp_multi_get_timeline_groups();
$group_filter = isset($_GET['group']) ? sanitize_key($_GET['group']) : '';
if (!isset($groups[$group_filter])) {
$group_filter = '';
}
// Autoren aufbereiten: Gruppe, Gesamtzahl und Jahreswerte
$authors = [];
$group_counts = array_fill_keys(array_keys($groups), 0);
foreach ($timeline as $name => $per_year) {
$group = wp_multi_get_author_timeline_group($per_year, $current_year);
$group_counts[$group]++;
$total = 0;
foreach ($per_year as $count) {
$total += (int) $count;
}
if ($group_filter !== '' && $group !== $group_filter) {
continue;
}
$authors[] = [
'name' => $name,
'group' => $group,
'total' => $total,
'per_year' => $per_year,
];
}
// Nach Gesamtzahl sortieren – die produktivsten zuerst
usort($authors, function ($a, $b) {
if ($a['total'] === $b['total']) {
return strcasecmp($a['name'], $b['name']);
}
return $b['total'] - $a['total'];
});
$timeline_url = add_query_arg('tab', 'timeline', $base_url);
$author_base_url = add_query_arg(['page' => 'guest_author_overview'], admin_url('admin.php'));
wpmt_admin_card_open(__('Autoren-Zeitlinie', 'wp-multi'));
?>
<p class="wpm-muted">
<?php _e('Gruppe:', 'wp-multi'); ?>
<a href="<?php echo esc_url(remove_query_arg('group', $timeline_url)); ?>" class="wpm-filter-link<?php echo ($group_filter === '') ? ' wpm-filter-active' : ''; ?>">
<?php if ($group_filter === '') : ?><strong><?php endif; ?>
<?php printf(esc_html__('Alle (%d)', 'wp-multi'), count($timeline)); ?>
<?php if ($group_filter === '') : ?></strong><?php endif; ?>
</a>
<?php foreach ($groups as $key => $label) : ?>
|
<a href="<?php echo esc_url(add_query_arg('group', $key, $timeline_url)); ?>" class="wpm-filter-link<?php echo ($group_filter === $key) ? ' wpm-filter-active' : ''; ?>">
<?php if ($group_filter === $key) : ?><strong><?php endif; ?>
<?php echo esc_html($label); ?> (<?php echo (int) $group_counts[$key]; ?>)
<?php if ($group_filter === $key) : ?></strong><?php endif; ?>
</a>
<?php endforeach; ?>
</p>
<div class="wpm-table-wrap">
<table class="wpm-table">
<thead>
<tr>
<th><?php _e('Autor', 'wp-multi'); ?></th>
<?php foreach ($column_years as $year) : ?>
<th><?php echo esc_html($year); ?></th>
<?php endforeach; ?>
<th><?php _e('Gesamt', 'wp-multi'); ?></th>
<th><?php _e('Gruppe', 'wp-multi'); ?></th>
</tr>
</thead>
<tbody>
<?php if ($authors) : ?>
<?php foreach ($authors as $author) : ?>
<tr>
<td>
<?php if (function_exists('wp_multi_get_guest_author_detail_url')) : ?>
<a href="<?php echo esc_url(wp_multi_get_guest_author_detail_url($author_base_url, $author['name'])); ?>"><?php echo esc_html($author['name']); ?></a>
<?php else : ?>
<?php echo esc_html($author['name']); ?>
<?php endif; ?>
</td>
<?php foreach ($column_years as $year) : ?>
<?php $count = isset($author['per_year'][$year]) ? (int) $author['per_year'][$year] : 0; ?>
<td>
<?php if ($count) : ?>
<?php echo esc_html(number_format_i18n($count)); ?>
<?php else : ?>
<span class="wpm-muted">·</span>
<?php endif; ?>
</td>
<?php endforeach; ?>
<td><strong><?php echo esc_html(number_format_i18n($author['total'])); ?></strong></td>
<td><span class="wpm-muted"><?php echo esc_html($groups[$author['group']]); ?></span></td>
</tr>
<?php endforeach; ?>
<?php else : ?>
<tr><td colspan="<?php echo (int) (count($column_years) + 3); ?>"><?php _e('Keine Autoren in dieser Gruppe.', 'wp-multi'); ?></td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<p class="wpm-muted">
<?php
printf(
/* translators: 1: Anzahl Jahre, 2: Mindestanzahl für Vielschreiber */
esc_html__('Spalten zeigen die letzten %1$d Jahre. Als "verlorener Vielschreiber" gilt, wer früher mindestens %2$d Geschichten in einem Jahr hatte und seither so gut wie nichts mehr veröffentlicht.', 'wp-multi'),
count($column_years),
(int) apply_filters('wp_multi_timeline_prolific_min', 5)
);
?>
</p>
<?php
wpmt_admin_card_close();
wp_multi_render_year_comparison($base_url, $available_years, $timeline, $author_base_url);
}
/**
* Vergleicht zwei Jahre und zeigt, bei welchen Autoren die Differenz entsteht.
*
* @param string $base_url Basis-URL der Seite.
* @param array $available_years Alle Jahre mit Beiträgen.
* @param array $timeline Zuordnung "Autor => [Jahr => Anzahl]".
* @param string $author_base_url Basis-URL der Autoren-Detailansicht.
*/
function wp_multi_render_year_comparison($base_url, $available_years, $timeline, $author_base_url) {
if (count($available_years) < 2) {
return;
}
// Standard: die beiden letzten abgeschlossenen Jahre
$default_to = $available_years[count($available_years) - 2];
$default_from = $available_years[count($available_years) - 3] ?? $available_years[0];
$from = isset($_GET['cmp_from']) ? (int) $_GET['cmp_from'] : $default_from;
$to = isset($_GET['cmp_to']) ? (int) $_GET['cmp_to'] : $default_to;
if (!in_array($from, $available_years, true)) {
$from = $default_from;
}
if (!in_array($to, $available_years, true)) {
$to = $default_to;
}
$rows = [];
$sum_from = 0;
$sum_to = 0;
foreach ($timeline as $name => $per_year) {
$count_from = isset($per_year[$from]) ? (int) $per_year[$from] : 0;
$count_to = isset($per_year[$to]) ? (int) $per_year[$to] : 0;
$sum_from += $count_from;
$sum_to += $count_to;
if ($count_from === $count_to) {
continue;
}
$rows[] = [
'name' => $name,
'from' => $count_from,
'to' => $count_to,
'diff' => $count_to - $count_from,
];
}
// Größte Rückgänge zuerst
usort($rows, function ($a, $b) {
if ($a['diff'] === $b['diff']) {
return strcasecmp($a['name'], $b['name']);
}
return $a['diff'] - $b['diff'];
});
$compare_url = add_query_arg('tab', 'timeline', $base_url);
$total_diff = $sum_to - $sum_from;
wpmt_admin_card_open(__('Jahresvergleich', 'wp-multi'));
?>
<p class="wpm-muted">
<?php _e('Von:', 'wp-multi'); ?>
<?php foreach (array_reverse($available_years) as $year) : ?>
<a href="<?php echo esc_url(add_query_arg(['cmp_from' => $year, 'cmp_to' => $to], $compare_url)); ?>" class="wpm-filter-link<?php echo ($year === $from) ? ' wpm-filter-active' : ''; ?>">
<?php if ($year === $from) : ?><strong><?php endif; ?><?php echo esc_html($year); ?><?php if ($year === $from) : ?></strong><?php endif; ?>
</a>
<?php endforeach; ?>
</p>
<p class="wpm-muted">
<?php _e('Bis:', 'wp-multi'); ?>
<?php foreach (array_reverse($available_years) as $year) : ?>
<a href="<?php echo esc_url(add_query_arg(['cmp_from' => $from, 'cmp_to' => $year], $compare_url)); ?>" class="wpm-filter-link<?php echo ($year === $to) ? ' wpm-filter-active' : ''; ?>">
<?php if ($year === $to) : ?><strong><?php endif; ?><?php echo esc_html($year); ?><?php if ($year === $to) : ?></strong><?php endif; ?>
</a>
<?php endforeach; ?>
</p>
<p>
<?php
printf(
/* translators: 1: erstes Jahr, 2: Anzahl, 3: zweites Jahr, 4: Anzahl, 5: Differenz */
esc_html__('%1$d: %2$s Geschichten – %3$d: %4$s Geschichten – Differenz: %5$s', 'wp-multi'),
(int) $from,
esc_html(number_format_i18n($sum_from)),
(int) $to,
esc_html(number_format_i18n($sum_to)),
esc_html(($total_diff > 0 ? '+' : '') . number_format_i18n($total_diff))
);
?>
</p>
<div class="wpm-table-wrap">
<table class="wpm-table">
<thead>
<tr>
<th><?php _e('Autor', 'wp-multi'); ?></th>
<th><?php echo esc_html($from); ?></th>
<th><?php echo esc_html($to); ?></th>
<th><?php _e('Differenz', 'wp-multi'); ?></th>
</tr>
</thead>
<tbody>
<?php if ($rows) : ?>
<?php foreach ($rows as $row) : ?>
<tr>
<td>
<?php if (function_exists('wp_multi_get_guest_author_detail_url')) : ?>
<a href="<?php echo esc_url(wp_multi_get_guest_author_detail_url($author_base_url, $row['name'])); ?>"><?php echo esc_html($row['name']); ?></a>
<?php else : ?>
<?php echo esc_html($row['name']); ?>
<?php endif; ?>
</td>
<td><?php echo esc_html(number_format_i18n($row['from'])); ?></td>
<td><?php echo esc_html(number_format_i18n($row['to'])); ?></td>
<td>
<?php if ($row['diff'] < 0) : ?>
<span class="wpm-badge wpm-badge--danger"><?php echo esc_html(number_format_i18n($row['diff'])); ?></span>
<?php else : ?>
<span class="wpm-badge wpm-badge--success">+<?php echo esc_html(number_format_i18n($row['diff'])); ?></span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php else : ?>
<tr><td colspan="4"><?php _e('Kein Unterschied zwischen diesen Jahren.', 'wp-multi'); ?></td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
<p class="wpm-muted"><?php _e('Gezählt werden nur Geschichten mit hinterlegtem Gast-Autor. Beiträge ohne dieses Feld tauchen in der Jahresstatistik auf, hier aber nicht.', 'wp-multi'); ?></p>
<?php
wpmt_admin_card_close();
}
+143 -70
View File
@@ -1,13 +1,13 @@
<?php
/**
* WP Multi – Modul: Statistik & Banner
*
* Automatisch aus wp-multi.php ausgelagert. Wird über den Loader in
* wp-multi.php eingebunden (nur wenn das WP Multi Toolkit aktiv ist).
*/
if (!defined('ABSPATH')) { exit; }
<?php
/**
* WP Multi – Modul: Statistik & Banner
*
* Automatisch aus wp-multi.php ausgelagert. Wird über den Loader in
* wp-multi.php eingebunden (nur wenn das WP Multi Toolkit aktiv ist).
*/
if (!defined('ABSPATH')) { exit; }
/*
* Statistik & Banner
*/
@@ -131,6 +131,10 @@ function statistik_manager_get_statistics() {
// Banner-Funktion
function statistik_manager_display_banner($position) {
static $rendered = false;
if ($rendered) {
return; // verhindert doppelte Ausgabe (wp_body_open + Footer-Fallback)
}
if (!get_option('statistik_manager_banner_enabled', 1)) {
return;
}
@@ -161,21 +165,31 @@ function statistik_manager_get_statistics() {
return;
}
// Standard-Styles für das Banner
$style = "background-color: " . esc_attr($banner_color) . ";
color: #fff;
text-align: center;
padding: 10px;
width: 100%;
height: 50px; /* Feste Höhe */
line-height: 30px; /* Zentrierte Schrift */
z-index: 9999;
position: fixed;
top: 0;
left: 0;";
$rendered = true;
$text_style = "font-size: " . esc_attr($font_size_css) . ";";
$icon_style = "font-size: 24px; margin-right: 8px;"; // Feste Größe für Icon
// Standard-Styles für das Banner (auto-Höhe, zentriert, umbruchfähig).
// !important, damit aktive Themes (z. B. Sueva) Farbe/Breite/Position
// nicht per eigener !important-Regel überschreiben können.
// top/bottom bewusst ohne !important: wird per Script unter fixe Leisten gesetzt.
$vertical_imp = ($position === 'bottom') ? 'bottom: 0;' : 'top: 0;';
$style = "background-color: " . esc_attr($banner_color) . " !important;
color: #fff !important;
text-align: center !important;
padding: 12px 16px !important;
margin: 0 !important;
width: 100% !important;
box-sizing: border-box !important;
z-index: 99990 !important;
position: fixed !important;
left: 0 !important;
$vertical_imp
display: flex !important;
align-items: center !important;
justify-content: center !important;
gap: 8px !important;";
$text_style = "font-size: " . esc_attr($font_size_css) . " !important; color: #fff !important; margin: 0 !important; line-height: 1.4 !important;";
$icon_style = "font-size: 24px !important; margin-right: 8px !important; color: #fff !important;"; // Feste Größe für Icon
echo '<div class="statistik-manager-banner" id="statistik-manager-banner" style="' . esc_attr($style) . '">';
@@ -188,54 +202,113 @@ function statistik_manager_get_statistics() {
echo '</div>';
}
// Funktion für das Banner im Header (nach <body>)
function statistik_manager_display_banner_header() {
add_action('wp_body_open', function() {
statistik_manager_display_banner('top');
});
}
// Falls `wp_body_open` nicht unterstützt wird, als Fallback `wp_footer` nutzen
function statistik_manager_display_banner_header_fallback() {
add_action('wp_footer', function() {
statistik_manager_display_banner('top');
}, 5);
}
// Funktion für das Banner im Footer
function statistik_manager_display_banner_footer() {
add_action('wp_footer', function() {
statistik_manager_display_banner('bottom');
}, 10);
}
// Banner laden (Header mit Fallback)
if (function_exists('wp_body_open')) {
statistik_manager_display_banner_header();
} else {
statistik_manager_display_banner_header_fallback();
}
// Fix: Admin-Leiste (Wenn Admin angemeldet ist, Banner nach unten verschieben)
function statistik_manager_admin_bar_fix() {
if (is_admin_bar_showing()) {
echo '<style>
#statistik-manager-banner {
top: 32px !important; /* Admin-Leiste ausgleichen */
}
body {
padding-top: 82px !important; /* Extra Platz für Admin-Leiste */
}
</style>';
} else {
echo '<style>
body {
padding-top: 70px !important; /* Standard Abstand */
}
</style>';
// Banner an der richtigen Stelle einhängen – abhängig von der konfigurierten
// Position. Nur wenn das Banner auch wirklich aktiviert ist.
function statistik_manager_register_banner() {
if (!get_option('statistik_manager_banner_enabled', 1)) {
return;
}
$pos = get_option('statistik_manager_banner_position', 'top');
if ($pos === 'bottom') {
// Unten: im Footer ausgeben
add_action('wp_footer', function() {
statistik_manager_display_banner('bottom');
}, 10);
} else {
// Oben: bevorzugt direkt nach <body>, plus Footer-Fallback für Themes
// ohne wp_body_open (die statische Sperre verhindert Doppelausgabe).
add_action('wp_body_open', function() {
statistik_manager_display_banner('top');
});
add_action('wp_footer', function() {
statistik_manager_display_banner('top');
}, 5);
}
// Abstand für den Seiteninhalt, damit das fixe Banner nichts verdeckt
add_action('wp_head', 'statistik_manager_banner_offset');
// Am Seitenende sofort nachrechnen – dann sind Banner und fixe Leisten
// (z. B. ShiftNav) garantiert im DOM, auch wenn DOMContentLoaded spät kommt.
add_action('wp_footer', function() {
echo '<script>window.statistikManagerBannerAdjust&&window.statistikManagerBannerAdjust();</script>';
}, 999);
}
add_action('wp', 'statistik_manager_register_banner');
// Fügt NUR bei aktivem Banner den passenden Abstand hinzu – auf der richtigen
// Seite (oben/unten) und mit Rücksicht auf die WordPress-Admin-Leiste.
function statistik_manager_banner_offset() {
if (!get_option('statistik_manager_banner_enabled', 1)) {
return;
}
$pos = get_option('statistik_manager_banner_position', 'top');
$h = 56; // ungefähre Bannerhöhe – nur für den No-JS-Fallback
// Fallback ohne JavaScript (WordPress verschiebt die Seite für die
// Admin-Leiste bereits selbst per html-margin, daher hier nicht addieren).
echo '<noscript><style>';
if ($pos === 'bottom') {
echo 'body { padding-bottom: ' . $h . 'px !important; }';
} else {
echo 'body { padding-top: ' . $h . 'px !important; }';
if (is_admin_bar_showing()) {
echo '#statistik-manager-banner { top: 32px !important; }';
echo '@media screen and (max-width: 782px) { #statistik-manager-banner { top: 46px !important; } }';
}
}
echo '</style></noscript>';
// Mit JavaScript: Banner exakt unter fixe Leisten oben setzen (WP-Admin-Leiste,
// ShiftNav-Menüleiste), sonst verdecken diese es. Der Inhalt wird nur um die
// tatsächliche Bannerhöhe verschoben – zusätzlich zum vorhandenen Abstand
// des Themes, damit das Layout ansonsten unverändert bleibt.
$is_bottom = ($pos === 'bottom') ? 'true' : 'false';
echo '<script>(function(){'
. 'var isBottom=' . $is_bottom . ';'
. 'var bars=["wpadminbar","shiftnav-toggle-main"];'
. 'function adjust(){'
. 'var b=document.getElementById("statistik-manager-banner");'
. 'if(!b){return;}'
. 'var s=document.body.style,prop=isBottom?"padding-bottom":"padding-top";'
. 's.removeProperty(prop);'
. 'var base=parseFloat(getComputedStyle(document.body)[isBottom?"paddingBottom":"paddingTop"])||0;'
. 'var extra=0;'
. 'if(!isBottom){'
. 'var off=0;'
. 'bars.forEach(function(id){'
. 'var e=document.getElementById(id);if(!e){return;}'
. 'var c=getComputedStyle(e);'
. 'if(c.position!=="fixed"||c.display==="none"||c.visibility==="hidden"){return;}'
. 'var r=e.getBoundingClientRect();'
. 'if(r.height>0&&r.bottom>off){off=r.bottom;}'
. '});'
. 'b.style.setProperty("top",off+"px","important");'
// Inhalt komplett unter Leisten + Banner beginnen lassen; die Admin-Leiste
// hat WordPress per html-margin bereits berücksichtigt.
. 'var hm=parseFloat(getComputedStyle(document.documentElement).marginTop)||0;'
. 'extra=Math.max(0,off-hm);'
. '}'
. 's.setProperty(prop,(base+extra+b.offsetHeight)+"px","important");'
. '}'
. 'window.statistikManagerBannerAdjust=adjust;'
. 'function watch(){'
. 'adjust();'
// Nachzügler abfangen (verzögert geladene CSS/JS anderer Plugins)
. '[300,1000,2500,5000].forEach(function(t){setTimeout(adjust,t);});'
. 'if(window.ResizeObserver){'
. 'var ro=new ResizeObserver(function(){adjust();});'
. '["statistik-manager-banner"].concat(bars).forEach(function(id){var e=document.getElementById(id);if(e){ro.observe(e);}});'
. '}'
. '}'
. 'if(document.readyState!=="loading"){watch();}else{document.addEventListener("DOMContentLoaded",watch);}'
. 'window.addEventListener("load",adjust);'
. 'window.addEventListener("resize",adjust);'
. '})();</script>';
}
add_action('wp_head', 'statistik_manager_admin_bar_fix');
// Shortcode für die Anzeige der Statistiken
function statistik_manager_shortcode() {
+101
View File
@@ -0,0 +1,101 @@
Aktuell eingebaut
1 - Statistik shortcode = [statistik_manager]
2 - Telegram Benachrichtigung
3 - Discord Benachrichtigung
4 - Custom Admin Links
5 - Gast-Autor
6 - Beitrags Report
7 - Custom Textbox (am ende eines Beitrags)
8 - Banner für Nachrichten
9 - Admin Dashboard Update Anzeige
10 - Gast Lesezeichen shortcode = [add_bookmark] & [display_bookmarks]
11 - custom Shortcode (können per Auswahl im Editor eingefügt werden)
12 - Benutzer für Kommentar Sperren
13 - Filter für Rufnummer, Mailadressen, URL, IP und schimpfwöter
14 - Besucher Analytics
15 - Pinwand
16 - Schutz vor Brute-Force-Angriffen
17 - Anti Spam Honey
18 - Auto Tag
19 - Login deaktivieren
20 - Text Copy schutz
21 - Sperre Trash Mail Adressen
22 - Inhaltsverzeichnis shortcode = [alphabetical_index]
Statistik:
Zeigt folgende Statistik an
- Anzahl der Beiträge
- Anzahl der Kommentare
- Anzahl der Kategorien
- Anzahl der Serien
Eröffnung der Webseite
Shortcode für die Seite:
[statistik_manager]
Telegram Benachrichtigung & Discord Benachrichtigung:
Schickt eine Benachrichtigung bei einem Neuen Beitrag
Custom Admin Links:
Fügt eigene Links im Adminpanel ein
Gast-Autor:
Name des Autoren kann hier eingegeben werden der wird auch im Frontend im Beitrag angezeigt
Beitrags Report:
Unangemessene oder Falsche Beiträge mit Grund an den Seiten Admin Melden (Der Admin sieht die Reports in einer Liste im AdminPanel)
Custom Textbox (fügt bei jedem Beitrag eine Box hinzu in dem Vordefinierte Teste Angezeigt werden (Texte können im AdminPanel hinterlegt werden))
Banner für Nachrichten:
Banner für Ankündigungen oder Informationen im Frontend (wahlweist im Header oder im Footer)
Admin Dashboard Update Anzeige:
Zeigt an ob das Plugin WP-Mulit Aktuell ist oder ob ein Update zur Verfügung steht
Gast Lesezeichen:
Gäste können eigene Lesezeichen anlegen die mittels Cookie zugeordnet werden
Shortcode:
[add_bookmark] & [display_bookmarks]
Benutzer für Kommentar Sperren:
Einzelne Benutzer können für Kommentare Gesperrt werden in dem man Banutzernamen, IP, Mailadresse eingibt
Kommentar Filter:
hier kann wahlweise folgendes verboten werden (Rufnummer, Mailadressen, URL, IP und schimpfwöter) sollte trotz verbot eines dieses gesendet werden wird der Betroffene Bereich durch * ersetzt
custom Shortcode:
es können eigene Shortcode erstellt werden die per Auswahl im Editor eingefügt werden
Besucher Analytics:
Hier kann man sehen welche Beiträge am Beliebtesten sind (Angezeigt wird Action (view oder comment) Titel des Beitrags, ID-Beitrag, Zeitstempel)
Pinwand:
hier können Nachrichten im AdminPanel für ander User Eingetragen, Bearbeitet oder auch Gelöscht werden
Schutz vor Brute-Force-Angriffen:
Logt jeden Fehlgeschlagenen Login versuch und sperrt ggf. nach 5 Fehlgeschlagenen Logins den Account und sendet eine MAil an den Kontoinhaber und den Seiten Admin
Anti Spam Honey:
Erkennt Spam, Bots und vieles mehr und Blockt diese (eine Übersicht ist im AdminPanel)
Auto Tag:
Fügt bei jedem Beitrag der keine Tags hat Automatisch die Passenden Tags hinzu, im AdminPanel können ausgewählte wörter Kommagetrennt hinzugefügt werden die nicht genutzt werden sollten)
Login deaktivieren:
Damit kann mal für Bestimmte Benutzer das Login deaktivieren (Aktivierbar im Entsprechenden Profil )
Text Copy schutz:
Schützt den Text vor unerlaubten Kopieren
Sperre Trash Mail Adressen:
Verbietet Trash-mails in Kommentaren die Liste kann nur vom Entwickler des Plugins erweitert werden
Inhaltsverzeichnis
Erstellt ein Inhaltsverzeichnis von mit allen Beiträgen
shortcode = [alphabetical_index]
+2 -1
View File
@@ -3,7 +3,7 @@
* Plugin Name: WP Multi
* Plugin URI: https://git.viper.ipv64.net/M_Viper/wp-multi
* Description: Erweiterter Anti-Spam-Schutz mit Honeypot, Keyword-Filter, Link-Limit und mehr. Jetzt mit Statistik im Dashboard und HappyForms-Integration.
* Version: 3.5
* Version: 3.7
* Author: M_Viper
* Author URI: https://m-viper.de
* Requires at least: 6.7.2
@@ -93,6 +93,7 @@ $wp_multi_modules = array(
'notify-telegram',
'notify-dashboard',
'guest-authors',
'publication-stats',
'custom-text',
'custom-admin-links',
'post-report',