__('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')
);
?>
$tab_label) : ?>
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;
?>
' . esc_html__('Noch keine veröffentlichten Beiträge gefunden.', 'wp-multi') . '';
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'));
?>
get_month_abbrev($wp_locale->get_month($month)), $month_totals[$month], $max_month_total); ?>
' . esc_html__('Noch keine veröffentlichten Beiträge gefunden.', 'wp-multi') . '';
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);
?>
$total) : ?>
|
|
|
$total) : ?>
|
|
|
—';
} else {
$diff = $total - $totals_by_year[$year - 1];
$sign = $diff > 0 ? '+' : '';
echo '' . esc_html($sign . number_format_i18n($diff)) . '';
}
?>
|
'🔥 ' . __('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 '' . esc_html__('Noch keine Daten für eine Zeitlinie vorhanden.', 'wp-multi') . '
';
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'));
?>
$label) : ?>
|
()
[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'));
?>
0 ? '+' : '') . number_format_i18n($total_diff))
);
?>