6 Commits
Author SHA1 Message Date
M_Viper 73852c8241 Upload via GUI (41 Dateien) 2026-10-18 09:31:29 +00:00
M_Viper d1039413ba Upload via GUI (41 Dateien) 2026-10-18 09:31:28 +00:00
M_Viper 5d90e28914 Upload via GUI (41 Dateien) 2026-10-18 09:31:28 +00:00
M_Viper 06e3d59ab9 Upload via GUI (41 Dateien) 2026-10-08 07:07:46 +00:00
M_Viper 7e6250d45f Upload via GUI (41 Dateien) 2026-10-08 07:07:46 +00:00
M_Viper 4209a832bc Upload via GUI (41 Dateien) 2026-10-08 07:07:44 +00:00
2 changed files with 241 additions and 48 deletions
+201 -5
View File
@@ -64,6 +64,32 @@ function wp_multi_display_guest_author($author_name) {
}
add_filter('the_author', 'wp_multi_display_guest_author');
// Link hinter dem Autornamen: zur Gastautor-Seite statt zum (redirecteten) WordPress-Benutzer
function wp_multi_guest_author_link($link, $author_id) {
if (is_admin() || !(is_single() || is_archive() || is_home() || is_search())) {
return $link;
}
$post = get_post();
if (!$post || (int) $post->post_author !== (int) $author_id) {
return $link;
}
$guest_author = get_post_meta($post->ID, '_guest_author', true);
if (empty($guest_author)) {
return $link;
}
$page_id = (int) get_option('wp_multi_guest_author_page_id');
$page_url = $page_id ? get_permalink($page_id) : '';
if (!$page_url) {
return $link;
}
return add_query_arg('guest_author', rawurlencode($guest_author), $page_url);
}
add_filter('author_link', 'wp_multi_guest_author_link', 10, 2);
// Anzeige des Gast-Autors in der Beitragsübersicht (Backend)
function wp_multi_add_guest_author_column($columns) {
if (isset($columns['author'])) {
@@ -1728,6 +1754,175 @@ function wp_multi_get_guest_author_overview_url() {
return $cached ? get_permalink($cached) : '';
}
/**
* Datum · Kategorie · Altersfreigabe (Age Gate) einer Geschichte als HTML.
* Es wird nie Text der Geschichte ausgegeben – gesperrte Beiträge bleiben verborgen.
*/
function wp_multi_guest_author_post_meta($post_id, $post_date) {
$bits = [esc_html(mysql2date(get_option('date_format'), $post_date))];
$names = [];
foreach ((array) get_the_category((int) $post_id) as $category) {
$names[] = esc_html($category->name);
}
if ($names) {
$bits[] = implode(', ', $names);
}
$html = implode(' · ', $bits);
if (function_exists('zst_profile_for_post')) {
$profile = zst_profile_for_post((int) $post_id);
if (is_array($profile) && !empty($profile['title'])) {
$html .= ' <span class="ga-badge" title="' . esc_attr__('Hinter einer Altersfreigabe verborgen', 'wp-multi') . '">' . esc_html($profile['title']) . '</span>';
}
}
return $html;
}
/**
* Alle Teile eines Autors je Serie, nach Teilnummer sortiert.
*
* @param string $guest_author Name des Gast-Autors.
* @param array $series Serienzeilen aus wp_multi_get_guest_author_series().
* @return array term_id => [ ['id','title','date','part'], … ]
*/
function wp_multi_get_guest_author_series_parts($guest_author, $series) {
$source = wp_multi_detect_series_source();
if (!$series || !$source || $guest_author === '') {
return [];
}
global $wpdb;
$term_ids = array_map('intval', wp_list_pluck($series, 'term_id'));
$in = implode(',', $term_ids);
$parent_sql = $source['parent'] ? $wpdb->prepare('AND tt.parent = %d', $source['parent']) : '';
$rows = $wpdb->get_results($wpdb->prepare("
SELECT DISTINCT p.ID, p.post_title, p.post_date, tt.term_id
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 = %s
INNER JOIN {$wpdb->term_relationships} tr ON tr.object_id = p.ID
INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id AND tt.taxonomy = %s {$parent_sql}
WHERE p.post_status = 'publish' AND tt.term_id IN ({$in})
", $guest_author, $source['taxonomy']));
$default_key = defined('SERIES_PART_KEY') ? SERIES_PART_KEY : '_series_part';
$out = [];
foreach ((array) $rows as $row) {
$term_id = (int) $row->term_id;
$key = apply_filters('orgseries_part_key', $default_key, $term_id);
$out[$term_id][] = [
'id' => (int) $row->ID,
'title' => $row->post_title,
'date' => $row->post_date,
'part' => (int) get_post_meta((int) $row->ID, $key, true),
];
}
foreach ($out as &$list) {
usort($list, function ($a, $b) {
if ($a['part'] !== $b['part']) {
return $a['part'] <=> $b['part'];
}
return strcmp($a['date'], $b['date']);
});
}
unset($list);
return $out;
}
/**
* Einklappbare Teile-Liste einer Serie.
*
* @param array $parts Teile des Autors in dieser Serie.
* @param int $total_parts Gesamtzahl der Teile der Serie.
*/
function wp_multi_guest_author_render_parts($parts, $total_parts) {
if (!$parts) {
return '';
}
$summary = sprintf(
/* translators: 1: Anzahl Teile des Autors, 2: Gesamtzahl der Teile */
_n('%1$d Teil von %2$d anzeigen', '%1$d Teile von %2$d anzeigen', count($parts), 'wp-multi'),
count($parts),
max((int) $total_parts, count($parts))
);
$html = '<details class="ga-parts"><summary>' . esc_html($summary) . '</summary><ol>';
foreach ($parts as $part) {
$label = $part['part'] > 0
? sprintf(__('Teil %d', 'wp-multi'), $part['part'])
: '';
$html .= '<li>';
if ($label) {
$html .= '<span class="ga-part-no">' . esc_html($label) . '</span> ';
}
$html .= '<a href="' . esc_url(get_permalink($part['id'])) . '">' . esc_html(get_the_title($part['id'])) . '</a>'
. ' <span class="ga-part-meta">' . wp_multi_guest_author_post_meta($part['id'], $part['date']) . '</span></li>';
}
return $html . '</ol></details>';
}
/**
* Eigenes CSS für Autorenübersicht und -profil (ersetzt das frühere Tailwind-CDN).
* Farben werden vom Theme geerbt, damit helle und dunkle Designs funktionieren.
*/
function wp_multi_guest_author_styles() {
static $printed = false;
if ($printed) {
return;
}
$printed = true;
?>
<style id="wp-multi-guest-author-css">
/* Breite zwingend an die Spalte binden: das Theme gibt der Klasse "container" feste Breiten. */
#guest-author-section, #guest-author-posts { width: 100%; min-width: 0; max-width: 100%; float: none; margin: 0; padding: 2rem 0; box-sizing: border-box; overflow-wrap: anywhere; }
#guest-author-section *, #guest-author-posts * { box-sizing: border-box; }
#guest-author-section .grid, #guest-author-posts .grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-bottom: 2rem; }
@media (min-width: 768px) { #guest-author-section .grid, #guest-author-posts .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
#guest-author-section .guest-author-card, #guest-author-posts .post-card {
display: block; padding: 1.25rem 1.5rem; border-radius: .6rem; text-decoration: none; color: inherit;
background: rgba(127,127,127,.09); border: 1px solid rgba(127,127,127,.28);
box-shadow: 0 1px 3px rgba(0,0,0,.12); transition: box-shadow .15s, border-color .15s;
}
#guest-author-section .guest-author-card:hover, #guest-author-posts .post-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,.22); border-color: rgba(127,127,127,.55); }
#guest-author-section h3, #guest-author-posts h2, #guest-author-posts h3, #guest-author-posts h4 { margin: 0 0 .4rem; color: inherit; }
#guest-author-posts h2 { font-size: 1.6rem; }
#guest-author-posts h3 { font-size: 1.25rem; margin-top: 2rem; }
#guest-author-posts h4 { font-size: 1.05rem; }
#guest-author-section p, #guest-author-posts p { margin: 0 0 .5rem; }
#guest-author-section .guest-author-sort { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: 1.5rem; }
#guest-author-section .guest-author-sort-label { font-size: 16px !important; }
#guest-author-section .guest-author-sort-link { padding: .4rem 1rem; border: 1px solid rgba(127,127,127,.45); border-radius: .4rem; text-decoration: none; font-size: 16px !important; line-height: 1.3; }
#guest-author-section .guest-author-sort-link.is-active { font-weight: 600; border-color: currentColor; }
#guest-author-section .text-sm, #guest-author-posts .text-sm { font-size: 15px !important; }
#guest-author-section .guest-author-last-post { font-size: 15px !important; }
#guest-author-section .text-gray-500, #guest-author-posts .text-gray-500,
#guest-author-section .text-gray-600, #guest-author-posts .text-gray-600 { opacity: .75; }
#guest-author-posts .guest-author-popular { padding-left: 1.25rem; list-style: disc; }
#guest-author-posts .guest-author-popular li { margin-bottom: .25rem; }
#guest-author-posts .guest-author-milestone-badge { font-size: 1.1rem; font-weight: 600; }
.ga-badge { display: inline-block; padding: 0 .45rem; border-radius: 999px; font-size: .75rem; background: rgba(120,80,200,.22); }
.ga-parts { margin-top: .6rem; }
.ga-parts summary { cursor: pointer; font-size: .9rem; }
.ga-parts ol { margin: .5rem 0 0; padding-left: 0; list-style: none; max-height: 18rem; overflow: auto; }
.ga-parts li { padding: .2rem 0; border-top: 1px solid rgba(127,127,127,.18); font-size: .9rem; }
.ga-part-no { font-weight: 600; margin-right: .25rem; }
.ga-part-meta { opacity: .7; font-size: .8rem; }
</style>
<?php
}
// Shortcode für Gastautoren-Liste
function wp_multi_guest_author_shortcode($atts = []) {
ob_start();
@@ -1847,9 +2042,8 @@ function wp_multi_guest_author_shortcode($atts = []) {
</div>
</div>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<?php
wp_multi_guest_author_styles();
return ob_get_clean();
}
@@ -1879,6 +2073,7 @@ function wp_multi_guest_author_posts_shortcode() {
</p>
</div>
<?php
wp_multi_guest_author_styles();
return ob_get_clean();
}
@@ -1889,6 +2084,7 @@ function wp_multi_guest_author_posts_shortcode() {
$series_data = wp_multi_get_guest_author_series($guest_author);
$series = $series_data['series'];
$series_post_ids = $series_data['post_ids'];
$series_parts = wp_multi_get_guest_author_series_parts($guest_author, $series);
// Neueste Einzelgeschichten (Serienteile stehen im eigenen Abschnitt)
$exclude_sql = $series_post_ids ? 'AND p.ID NOT IN (' . implode(',', $series_post_ids) . ')' : '';
@@ -1998,6 +2194,7 @@ function wp_multi_guest_author_posts_shortcode() {
&middot;
<?php printf(esc_html__('zuletzt %s', 'wp-multi'), esc_html(mysql2date(get_option('date_format'), $row->last_part_date))); ?>
</p>
<?php echo wp_multi_guest_author_render_parts(isset($series_parts[(int) $row->term_id]) ? $series_parts[(int) $row->term_id] : [], (int) $row->total_parts); // phpcs:ignore WordPress.Security.EscapeOutput ?>
</div>
<?php endforeach; ?>
</div>
@@ -2022,16 +2219,15 @@ function wp_multi_guest_author_posts_shortcode() {
<h4 class="text-lg font-medium text-gray-800">
<a href="<?php echo esc_url(get_permalink((int) $row->ID)); ?>" class="hover:text-blue-600"><?php echo esc_html(get_the_title((int) $row->ID)); ?></a>
</h4>
<p class="text-gray-500 text-sm"><?php echo esc_html(mysql2date(get_option('date_format'), $row->post_date)); ?></p>
<p class="text-gray-500 text-sm"><?php echo wp_multi_guest_author_post_meta((int) $row->ID, $row->post_date); // phpcs:ignore WordPress.Security.EscapeOutput ?></p>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<?php
wp_multi_guest_author_styles();
return ob_get_clean();
}
+40 -43
View File
@@ -205,6 +205,16 @@ function statistik_manager_get_statistics() {
// 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() {
// Abstand oben für fixe Leisten (ShiftNav-Menüleiste usw.) wird IMMER
// berechnet – auch ohne Banner –, sonst liegt der Seitenkopf darunter.
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);
if (!get_option('statistik_manager_banner_enabled', 1)) {
return;
}
@@ -226,58 +236,47 @@ function statistik_manager_get_statistics() {
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.
// Sorgt dafür, dass fixe Leisten oben (ShiftNav-Menüleiste) und – falls aktiv –
// das Banner keinen Inhalt verdecken. Die WordPress-Admin-Leiste ist per
// html-margin bereits berücksichtigt und wird nicht doppelt gezählt.
function statistik_manager_banner_offset() {
if (!get_option('statistik_manager_banner_enabled', 1)) {
return;
}
$enabled = (bool) get_option('statistik_manager_banner_enabled', 1);
$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; } }';
if ($enabled) {
$h = 56; // ungefähre Bannerhöhe – nur für den No-JS-Fallback
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>';
}
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.
// Mit JavaScript: Unterkante der fixen Leisten oben messen, ein Top-Banner
// direkt darunter setzen und den Inhalt um Leisten + Banner nach unten
// schieben (zusätzlich zum vorhandenen Abstand des Themes).
$is_bottom = ($pos === 'bottom') ? 'true' : 'false';
echo '<script>(function(){'
. 'var isBottom=' . $is_bottom . ';'
. 'var bars=["wpadminbar","shiftnav-toggle-main"];'
. 'var setTop=false,setBottom=false;'
. 'function adjust(){'
. 'if(!document.body){return;}'
. '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 s=document.body.style;'
. 'if(setTop){s.removeProperty("padding-top");setTop=false;}'
. 'if(setBottom){s.removeProperty("padding-bottom");setBottom=false;}'
. 'var cs=getComputedStyle(document.body);'
. 'var baseTop=parseFloat(cs.paddingTop)||0,baseBottom=parseFloat(cs.paddingBottom)||0;'
. 'var off=0;'
. 'bars.forEach(function(id){'
. 'var e=document.getElementById(id);if(!e){return;}'
@@ -286,13 +285,11 @@ function statistik_manager_get_statistics() {
. '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");'
. 'var top=Math.max(0,off-hm);'
. 'if(b&&!isBottom){b.style.setProperty("top",off+"px","important");top+=b.offsetHeight;}'
. 'if(top>0){s.setProperty("padding-top",(baseTop+top)+"px","important");setTop=true;}'
. 'if(b&&isBottom){s.setProperty("padding-bottom",(baseBottom+b.offsetHeight)+"px","important");setBottom=true;}'
. '}'
. 'window.statistikManagerBannerAdjust=adjust;'
. 'function watch(){'