91 Commits
2.0 ... main

Author SHA1 Message Date
43ee6153dd Upload via Git Manager GUI - single-faq.php 2026-03-30 18:43:09 +00:00
042a526aba Upload via Git Manager GUI - single.php 2026-03-30 18:43:08 +00:00
bc5e131749 Upload via Git Manager GUI - searchform.php 2026-03-30 18:43:08 +00:00
5bbba99f3b Upload via Git Manager GUI - search.php 2026-03-30 18:43:07 +00:00
46362b5355 Upload via Git Manager GUI - page-login.php 2026-03-30 18:42:55 +00:00
af25b0a2b0 Upload via Git Manager GUI - page-bewerbung.php 2026-03-30 18:42:54 +00:00
6d3152047a Upload via Git Manager GUI - page.php 2026-03-30 18:42:54 +00:00
e1a8074e99 Upload via Git Manager GUI - list-posts.php 2026-03-30 18:42:53 +00:00
43f40a39b0 Upload via Git Manager GUI - index.php 2026-03-30 18:42:53 +00:00
9f20b9433f Upload via Git Manager GUI - header.php 2026-03-30 18:42:52 +00:00
b9b6e5fa57 Upload via Git Manager GUI - get-channel-id.php 2026-03-30 18:42:52 +00:00
c08b3d6506 Upload via Git Manager GUI - functions.php 2026-03-30 18:42:51 +00:00
9db3bdff17 Upload via Git Manager GUI - front-page.php 2026-03-30 18:42:51 +00:00
6b6cf4e76f Upload via Git Manager GUI - footer.php 2026-03-30 18:42:50 +00:00
4ef8d4a393 Upload via Git Manager GUI - dynamic-styles.php 2026-03-30 18:42:50 +00:00
2b13b937ec Upload via Git Manager GUI - comments.php 2026-03-30 18:42:49 +00:00
85f96ea145 Upload via Git Manager GUI - clear-livestream-cache.php 2026-03-30 18:42:49 +00:00
4a12a88d2c Upload via Git Manager GUI - clear-cache.php 2026-03-30 18:42:48 +00:00
b67a6f2d52 Upload via Git Manager GUI - archive-video.php 2026-03-30 18:42:48 +00:00
82877cb410 Upload via Git Manager GUI - archive-team.php 2026-03-30 18:42:47 +00:00
2425b154cb Upload via Git Manager GUI - archive-faq.php 2026-03-30 18:42:47 +00:00
170ad670bc Upload via Git Manager GUI - archive.php 2026-03-30 18:42:46 +00:00
c74800a91e Upload via Git Manager GUI - 404.php 2026-03-30 18:42:46 +00:00
e417f31c99 Update from Git Manager GUI 2026-03-30 20:42:44 +02:00
85d12e832d Update from Git Manager GUI 2026-03-30 20:42:42 +02:00
12d1da2818 Upload via Git Manager GUI - update-dreamtripspk.php 2026-03-30 18:42:41 +00:00
3563752968 Upload via Git Manager GUI - test-youtube.php 2026-03-30 18:42:41 +00:00
095e3b5990 Upload via Git Manager GUI - test-video-id.php 2026-03-30 18:42:40 +00:00
abb711abbe Upload via Git Manager GUI - test-render.php 2026-03-30 18:42:40 +00:00
558f827fa5 Upload via Git Manager GUI - test-live.php 2026-03-30 18:42:39 +00:00
db2ca87135 Upload via Git Manager GUI - test-filter.php 2026-03-30 18:42:39 +00:00
62bab14817 Upload via Git Manager GUI - test-extraction.php 2026-03-30 18:42:38 +00:00
5008184633 Upload via Git Manager GUI - test-direct-url.php 2026-03-30 18:42:38 +00:00
83bff40565 Upload via Git Manager GUI - test-api-live.php 2026-03-30 18:42:38 +00:00
5a4115222e Upload via Git Manager GUI - test-api-key.php 2026-03-30 18:42:37 +00:00
2ccd1f5769 Upload via Git Manager GUI - style.css 2026-03-30 18:42:37 +00:00
94975c41fb Update from Git Manager GUI 2026-03-29 22:30:22 +02:00
8be73b9764 Update from Git Manager GUI 2026-03-29 22:30:20 +02:00
c2f366e391 Upload file update-dreamtripspk.php via GUI 2026-03-29 22:30:18 +02:00
e530922071 Upload file test-youtube.php via GUI 2026-03-29 22:30:06 +02:00
07b09a83c3 Upload file test-video-id.php via GUI 2026-03-29 22:29:55 +02:00
2487405491 Upload file test-render.php via GUI 2026-03-29 22:29:43 +02:00
853e9be519 Upload file test-live.php via GUI 2026-03-29 22:29:32 +02:00
11f7621ea9 Upload file test-filter.php via GUI 2026-03-29 22:29:20 +02:00
40ef763823 Upload file test-extraction.php via GUI 2026-03-29 22:29:09 +02:00
bb818139b2 Upload file test-direct-url.php via GUI 2026-03-29 22:28:57 +02:00
3e7aaf3560 Upload file test-api-live.php via GUI 2026-03-29 22:28:46 +02:00
32fb4c8204 Upload file test-api-key.php via GUI 2026-03-29 22:28:35 +02:00
2a70e23c6b Upload via Git Manager GUI - style.css 2026-03-29 20:28:25 +00:00
3fdf178a4b Upload via Git Manager GUI - single-faq.php 2026-03-29 20:28:25 +00:00
9fc7507037 Upload via Git Manager GUI - single.php 2026-03-29 20:28:24 +00:00
5beb82d9dd Upload via Git Manager GUI - searchform.php 2026-03-29 20:28:24 +00:00
50ebde6b70 Upload via Git Manager GUI - search.php 2026-03-29 20:28:23 +00:00
2ba14ed9a9 Upload via Git Manager GUI - page-login.php 2026-03-29 20:28:12 +00:00
4e16f543a5 Upload via Git Manager GUI - page-bewerbung.php 2026-03-29 20:28:11 +00:00
7e3f7303c4 Upload via Git Manager GUI - page.php 2026-03-29 20:28:11 +00:00
0a494f8148 Upload file list-posts.php via GUI 2026-03-29 22:28:09 +02:00
75c3583b11 Upload via Git Manager GUI - index.php 2026-03-29 20:27:59 +00:00
387ee5586f Upload via Git Manager GUI - header.php 2026-03-29 20:27:59 +00:00
5e087bb07a Upload file get-channel-id.php via GUI 2026-03-29 22:27:57 +02:00
21d9e0f56c Upload via Git Manager GUI - functions.php 2026-03-29 20:27:48 +00:00
37b01a5135 Upload via Git Manager GUI - front-page.php 2026-03-29 20:27:47 +00:00
cc0de6b5dd Upload via Git Manager GUI - footer.php 2026-03-29 20:27:47 +00:00
1a9f2ba8f2 Upload via Git Manager GUI - dynamic-styles.php 2026-03-29 20:27:46 +00:00
be9c444ba2 Upload via Git Manager GUI - comments.php 2026-03-29 20:27:46 +00:00
7870995bab Upload file clear-livestream-cache.php via GUI 2026-03-29 22:27:44 +02:00
16a4577e6b Upload file clear-cache.php via GUI 2026-03-29 22:27:33 +02:00
e319e91290 Upload via Git Manager GUI - archive-video.php 2026-03-29 20:27:24 +00:00
b2ba06c1f3 Upload via Git Manager GUI - archive-team.php 2026-03-29 20:27:23 +00:00
dce1de3f68 Upload via Git Manager GUI - archive-faq.php 2026-03-29 20:27:23 +00:00
65f6fbb0a2 Upload via Git Manager GUI - archive.php 2026-03-29 20:27:22 +00:00
9c6931d25d Upload via Git Manager GUI - 404.php 2026-03-29 20:27:22 +00:00
2882231550 Update from Git Manager GUI 2026-03-29 22:27:20 +02:00
7684cae147 Upload file header.php via GUI 2026-03-19 23:53:04 +01:00
c2e7ef1b46 Upload file functions.php via GUI 2026-03-19 23:52:58 +01:00
1c9b19acda Upload file front-page.php via GUI 2026-03-19 23:52:52 +01:00
96a71b0254 Upload file footer.php via GUI 2026-03-19 23:52:46 +01:00
e1dc54463a Upload file comments.php via GUI 2026-03-19 23:52:34 +01:00
15403ac652 Upload file archive-video.php via GUI 2026-03-19 23:52:28 +01:00
50343f6dca Upload file archive-team.php via GUI 2026-03-19 23:52:21 +01:00
9ee1e662ea Upload file archive.php via GUI 2026-03-19 23:52:10 +01:00
1f8fffb58a Upload file 404.php via GUI 2026-03-19 23:52:03 +01:00
2855fbada5 Update from Git Manager GUI 2026-03-19 23:51:57 +01:00
3d08d57d64 Update from Git Manager GUI 2026-03-19 23:51:55 +01:00
5e155c8398 Upload file style.css via GUI 2026-03-19 23:51:52 +01:00
c858112d7f Upload file single.php via GUI 2026-03-19 23:51:41 +01:00
b90684aa22 Upload file searchform.php via GUI 2026-03-19 23:51:35 +01:00
9aa5fc2507 Upload file search.php via GUI 2026-03-19 23:51:28 +01:00
53f5d57b32 Upload file page-login.php via GUI 2026-03-19 23:51:16 +01:00
f9bd5558b7 Upload file page-bewerbung.php via GUI 2026-03-19 23:51:10 +01:00
18c07628f5 Upload file index.php via GUI 2026-03-19 23:50:58 +01:00
45 changed files with 13600 additions and 1971 deletions

View File

@@ -0,0 +1,192 @@
<?php get_header(); ?>
<main id="primary" class="site-main">
<div class="container">
<div class="content-area">
<div class="error-404-container">
<div class="error-404-visual">
<div class="error-404-code">404</div>
<div class="error-404-blocks">
<!-- Minecraft-style Blöcke als Dekoration -->
<span class="block block-grass"></span>
<span class="block block-dirt"></span>
<span class="block block-stone"></span>
</div>
</div>
<div class="error-404-content">
<h1 class="error-404-title">
<?php _e('Diese Seite ist verloren gegangen...', 'minecraft-modern-theme'); ?>
</h1>
<p class="error-404-subtitle">
<?php _e('Wie eine Map ohne Spawn-Punkt diese Seite existiert nicht (mehr).', 'minecraft-modern-theme'); ?>
</p>
<div class="error-404-actions">
<a href="<?php echo esc_url( home_url('/') ); ?>" class="error-404-btn primary">
<i class="fas fa-home"></i> <?php _e('Zur Startseite', 'minecraft-modern-theme'); ?>
</a>
<a href="javascript:history.back()" class="error-404-btn secondary">
<i class="fas fa-arrow-left"></i> <?php _e('Zurück', 'minecraft-modern-theme'); ?>
</a>
</div>
<!-- Suchformular -->
<div class="error-404-search">
<p><?php _e('Oder suche direkt nach dem was du brauchst:', 'minecraft-modern-theme'); ?></p>
<?php get_search_form(); ?>
</div>
<!-- Letzte Beiträge als Hilfestellung -->
</div>
</div>
</div>
</div>
</main>
<style>
.error-404-container {
display: flex;
gap: 60px;
align-items: center;
padding: 60px 0;
min-height: 60vh;
}
.error-404-visual {
flex-shrink: 0;
text-align: center;
}
.error-404-code {
font-size: clamp(80px, 15vw, 160px);
font-weight: 900;
line-height: 1;
color: var(--primary-accent, #00d4ff);
text-shadow: 0 0 40px rgba(0, 212, 255, 0.3);
letter-spacing: -4px;
}
.error-404-blocks {
display: flex;
justify-content: center;
gap: 6px;
margin-top: 16px;
}
.block {
width: 32px;
height: 32px;
border-radius: 4px;
display: inline-block;
image-rendering: pixelated;
}
.block-grass { background: linear-gradient(to bottom, #5c8a1e 40%, #6b4c2a 40%); }
.block-dirt { background: #6b4c2a; }
.block-stone { background: #888; }
.error-404-content {
flex: 1;
}
.error-404-title {
font-size: clamp(1.4rem, 3vw, 2rem);
margin: 0 0 12px;
}
.error-404-subtitle {
color: var(--text-muted, #a0a0a0);
margin-bottom: 30px;
font-size: 1.05rem;
}
.error-404-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 30px;
}
.error-404-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
border-radius: 6px;
font-weight: 600;
text-decoration: none;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.error-404-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.error-404-btn.primary {
background: var(--primary-accent, #00d4ff);
color: #fff;
}
.error-404-btn.secondary {
background: var(--card-bg);
color: inherit;
border: 1px solid rgba(255,255,255,0.1);
}
.error-404-search {
margin-bottom: 30px;
}
.error-404-search p {
color: var(--text-muted, #a0a0a0);
margin-bottom: 10px;
}
.error-404-recent h3 {
font-size: 1rem;
color: var(--text-muted, #a0a0a0);
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.error-404-recent-list {
list-style: none;
padding: 0;
margin: 0;
}
.error-404-recent-list li {
padding: 8px 0;
border-bottom: 1px solid rgba(255,255,255,0.06);
display: flex;
align-items: center;
gap: 10px;
}
.error-404-recent-list li i {
color: var(--primary-accent, #00d4ff);
font-size: 0.75rem;
flex-shrink: 0;
}
.error-404-recent-list a {
text-decoration: none;
transition: color 0.2s;
}
.error-404-recent-list a:hover {
color: var(--primary-accent, #00d4ff);
}
@media (max-width: 768px) {
.error-404-container {
flex-direction: column;
gap: 30px;
padding: 40px 0;
text-align: center;
}
.error-404-actions {
justify-content: center;
}
}
</style>
<?php get_footer(); ?>

View File

@@ -1,31 +1,28 @@
<?php get_header(); ?> <!-- HIER WIRD DER HEADER EINGEBUNDEN --> <?php get_header(); ?>
<div class="container site-main"> <div class="container site-main">
<div class="content-area"> <div class="content-area">
<!-- Hülle um den gesamten FAQ-Inhalt für einen festen Hintergrund -->
<div class="faq-archive-container"> <div class="faq-archive-container">
<header class="page-header"> <header class="page-header">
<h1 class="page-title"><?php _e( 'Häufig gestellte Fragen (FAQ)', 'minecraft-modern-theme' ); ?></h1> <h1 class="page-title"><?php _e( 'Häufig gestellte Fragen (FAQ)', 'minecraft-modern-theme' ); ?></h1>
<p><?php _e( 'Wählen Sie eine Kategorie, um die passenden Fragen zu sehen.', 'minecraft-modern-theme' ); ?></p> <p><?php _e( 'Wählen Sie eine Kategorie, um die passenden Fragen zu sehen.', 'minecraft-modern-theme' ); ?></p>
</header> </header>
<?php <?php
// Alle FAQ-Kategorien abrufen
$categories = get_terms( array( $categories = get_terms( array(
'taxonomy' => 'faq_category', 'taxonomy' => 'faq_category',
'orderby' => 'name', 'orderby' => 'name',
'order' => 'ASC' 'order' => 'ASC',
) ); ) );
if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) : ?> if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) : ?>
<!-- Tab-Navigation -->
<ul class="faq-tabs"> <ul class="faq-tabs">
<?php <?php
$is_first = true; $is_first = true;
foreach ( $categories as $category ) : foreach ( $categories as $category ) :
$active_class = $is_first ? 'active' : ''; $active_class = $is_first ? 'active' : '';
?> ?>
<li> <li>
@@ -33,20 +30,19 @@
<?php echo esc_html( $category->name ); ?> <?php echo esc_html( $category->name ); ?>
</button> </button>
</li> </li>
<?php <?php
$is_first = false; $is_first = false;
endforeach; ?> endforeach; ?>
</ul> </ul>
<!-- Container für alle Tab-Inhalte -->
<div class="faq-tab-content-container"> <div class="faq-tab-content-container">
<?php <?php
$is_first_pane = true; $is_first_pane = true;
foreach ( $categories as $category ) : foreach ( $categories as $category ) :
$active_pane_class = $is_first_pane ? 'active' : ''; $active_pane_class = $is_first_pane ? 'active' : '';
?> ?>
<div class="faq-tab-pane <?php echo esc_attr($active_pane_class); ?>" data-category="<?php echo esc_attr($category->slug); ?>"> <div class="faq-tab-pane <?php echo esc_attr($active_pane_class); ?>" data-category="<?php echo esc_attr($category->slug); ?>">
<?php <?php
$faqs = new WP_Query( array( $faqs = new WP_Query( array(
'post_type' => 'faq', 'post_type' => 'faq',
@@ -58,8 +54,8 @@
'terms' => $category->slug, 'terms' => $category->slug,
), ),
), ),
'orderby' => 'menu_order', 'orderby' => 'menu_order',
'order' => 'ASC', 'order' => 'ASC',
) ); ) );
if ( $faqs->have_posts() ) : ?> if ( $faqs->have_posts() ) : ?>
@@ -77,8 +73,8 @@
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php <?php
$is_first_pane = false; $is_first_pane = false;
endforeach; ?> endforeach; ?>
</div> </div>
@@ -91,4 +87,4 @@
</div> </div>
</div> </div>
<?php get_footer(); ?> <!-- HIER WIRD DER FOOTER EINGEBUNDEN --> <?php get_footer(); ?>

View File

@@ -3,45 +3,107 @@
<div class="container site-main"> <div class="container site-main">
<div class="content-area"> <div class="content-area">
<div class="team-archive-container"> <div class="team-archive-container">
<header class="page-header"> <header class="page-header">
<h1 class="page-title">Unser Team</h1> <h1 class="page-title">Unser Team</h1>
<p class="page-description">Lerne die Leute kennen, die diesen Server am Laufen halten.</p> <p class="page-description">Lerne die Leute kennen, die diesen Server am Laufen halten.</p>
</header> </header>
<?php <?php
$query = new WP_Query(array('post_type' => 'team_member', 'posts_per_page' => -1, 'orderby' => 'menu_order', 'order' => 'ASC')); $query = new WP_Query( array(
'post_type' => 'team_member',
'posts_per_page' => -1,
'orderby' => 'menu_order',
'order' => 'ASC',
) );
if ( $query->have_posts() ) : ?> if ( $query->have_posts() ) : ?>
<div class="team-grid"> <div class="team-grid">
<?php while ( $query->have_posts() ) : $query->the_post(); <?php while ( $query->have_posts() ) : $query->the_post();
$rank_string = get_post_meta( get_the_ID(), '_team_member_rank', true ); $rank_string = get_post_meta( get_the_ID(), '_team_member_rank', true );
$img = get_the_post_thumbnail_url( get_the_ID(), 'medium' ); $uuid = get_post_meta( get_the_ID(), '_team_member_uuid', true );
if ( !$img ) $img = get_template_directory_uri() . '/images/default-avatar.png'; $banner_id = get_post_meta( get_the_ID(), '_team_member_banner', true );
$img = get_the_post_thumbnail_url( get_the_ID(), 'medium' );
$banner_url = $banner_id ? wp_get_attachment_image_url( $banner_id, 'medium_large' ) : false;
// UUID hat Vorrang Minecraft Avatar via visage.surgeplay.com
if ( $uuid ) {
$avatar_url = 'https://visage.surgeplay.com/bust/' . esc_attr( $uuid ) . '.png';
} elseif ( $img ) {
$avatar_url = $img;
} else {
$avatar_url = false;
}
$use_placeholder = ! $avatar_url;
$initials = mb_strtoupper( mb_substr( get_the_title(), 0, 1 ) );
?> ?>
<article class="team-card"> <article class="team-card">
<div class="team-image-wrapper"> <!-- Banner -->
<img src="<?php echo esc_url($img); ?>" alt="<?php echo esc_attr(get_the_title()); ?>"> <div class="team-card-banner" <?php if ( $banner_url ) echo 'style="background-image:url(' . esc_url($banner_url) . ')"'; ?>>
</div> </div>
<!-- Avatar -->
<div class="team-image-wrapper">
<?php if ( $avatar_url ) : ?>
<img src="<?php echo esc_url( $avatar_url ); ?>"
alt="<?php echo esc_attr( get_the_title() ); ?>"
loading="lazy">
<?php else : ?>
<div class="team-avatar-placeholder">
<?php echo esc_html( $initials ); ?>
</div>
<?php endif; ?>
</div>
<div class="team-info"> <div class="team-info">
<h3 class="team-name"><?php the_title(); ?></h3> <h3 class="team-name"><?php the_title(); ?></h3>
<div class="team-ranks-wrapper"> <div class="team-ranks-wrapper">
<?php if ( !empty($rank_string) ) { <?php if ( ! empty( $rank_string ) ) {
$ranks = explode(',', $rank_string); $ranks = explode( ',', $rank_string );
foreach ( $ranks as $r ) { foreach ( $ranks as $r ) {
echo '<span class="team-rank">' . esc_html(trim($r)) . '</span>'; echo '<span class="team-rank">' . esc_html( trim( $r ) ) . '</span>';
} }
} ?> } ?>
</div> </div>
<div class="team-bio"><?php the_excerpt(); ?></div> <div class="team-bio"><?php the_excerpt(); ?></div>
</div> </div>
</article> </article>
<?php endwhile; ?> <?php endwhile; wp_reset_postdata(); ?>
</div> </div>
<?php wp_reset_postdata(); ?>
<?php else : ?> <?php else : ?>
<p>Noch keine Teammitglieder.</p> <p><?php _e('Noch keine Teammitglieder.', 'minecraft-modern-theme'); ?></p>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
</div> </div>
<style>
/* Avatar-Placeholder wenn kein Bild gesetzt ist */
.team-avatar-placeholder {
width: 100%;
aspect-ratio: 1;
background: var(--card-bg);
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--primary-accent, #00d4ff);
font-size: 2.5rem;
font-weight: 700;
gap: 4px;
}
.team-avatar-placeholder svg {
width: 60%;
height: 60%;
color: var(--primary-accent, #00d4ff);
}
.team-avatar-placeholder span {
font-size: 2rem;
line-height: 1;
}
</style>
<?php get_footer(); ?> <?php get_footer(); ?>

View File

@@ -0,0 +1,461 @@
<?php get_header(); ?>
<style>
/* Basis-Styles für Player */
#yt-player-container {
position: relative;
width: 100%;
padding-bottom: 56.25%; /* 16:9 */
height: 0;
overflow: hidden;
}
#yt-player-container iframe,
#yt-player-container #yt-player {
position: absolute;
top: 0;
left: 0;
width: 100% !important;
height: 100% !important;
}
#other-player-container {
position: relative;
width: 100%;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
}
#other-player-container iframe,
#other-player-container video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* === NEU: Styles für das Livestream Popup (Lightbox) === */
.livestream-lightbox {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10000;
display: none; /* Wird via JS auf flex gesetzt */
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.livestream-lightbox.is-open {
display: flex;
opacity: 1;
}
.livestream-lightbox-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(8px);
}
.livestream-lightbox-box {
position: relative;
width: 90%;
max-width: 1100px;
background: #1a1a1a;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
transform: translateY(20px);
transition: transform 0.3s ease;
}
.livestream-lightbox.is-open .livestream-lightbox-box {
transform: translateY(0);
}
.livestream-lightbox-head {
padding: 15px 25px;
display: flex;
justify-content: space-between;
align-items: center;
background: #222;
border-bottom: 1px solid #333;
}
.livestream-lightbox-title {
margin: 0;
font-size: 1.2rem;
color: #fff;
}
.livestream-lightbox-close {
background: none;
border: none;
color: #aaa;
font-size: 24px;
cursor: pointer;
transition: color 0.2s;
}
.livestream-lightbox-close:hover {
color: #fff;
}
.livestream-lightbox-player {
position: relative;
padding-bottom: 56.25%;
height: 0;
background: #000;
}
.livestream-lightbox-player iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
.livestream-lightbox-meta {
padding: 15px 25px;
background: #222;
text-align: right;
}
.livestream-lightbox-meta a {
color: #00dcff;
text-decoration: none;
font-weight: bold;
}
/* Klick-Indikator für die Karten */
.video-livestream-list > div {
cursor: pointer;
transition: transform 0.2s ease;
}
.video-livestream-list > div:hover {
transform: scale(1.02);
}
</style>
<div class="container site-main">
<div class="content-area">
<div class="video-archive-container">
<header class="page-header">
<h1 class="page-title">
<i class="fas fa-play-circle"></i> <?php _e( 'Videos', 'minecraft-modern-theme' ); ?>
</h1>
<p class="page-description">
<?php _e( 'Alle Videos auf einem Blick. Klicke auf ein Video um es direkt hier anzusehen.', 'minecraft-modern-theme' ); ?>
</p>
<?php
$categories = array();
$all_vids = get_posts( array( 'post_type' => 'mm_video', 'posts_per_page' => -1, 'fields' => 'ids' ) );
foreach ( $all_vids as $vid_id ) {
$cat = get_post_meta( $vid_id, '_mm_video_category', true );
if ( $cat ) $categories[ $cat ] = $cat;
}
if ( count( $categories ) > 1 ) : ?>
<div class="video-filter-bar">
<button class="video-filter-btn active" data-filter="all">
<?php _e( 'Alle', 'minecraft-modern-theme' ); ?>
</button>
<?php foreach ( $categories as $cat ) : ?>
<button class="video-filter-btn" data-filter="<?php echo esc_attr( $cat ); ?>">
<?php echo esc_html( $cat ); ?>
</button>
<?php endforeach; ?>
</div>
<?php endif; ?>
</header>
<?php
$livestream_groups = mm_video_get_livestream_groups();
?>
<?php if ( ! empty( $livestream_groups ) ) : ?>
<div class="video-livestream-section">
<h2 class="section-livestream-title">
<span class="live-indicator"></span>
<span><?php _e( 'JETZT LIVE', 'minecraft-modern-theme' ); ?></span>
</h2>
<div class="video-livestream-list">
<?php foreach ( $livestream_groups as $group_index => $livestream_group ) : ?>
<?php echo mm_video_render_livestream_group( $livestream_group, $group_index ); ?>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php
$query = new WP_Query( array(
'post_type' => 'mm_video',
'posts_per_page' => -1,
'orderby' => 'menu_order date',
'order' => 'ASC',
) );
if ( $query->have_posts() ) : ?>
<div class="video-grid" id="video-grid">
<?php while ( $query->have_posts() ) : $query->the_post();
$url = get_post_meta( get_the_ID(), '_mm_video_url', true );
$category = get_post_meta( get_the_ID(), '_mm_video_category', true );
$thumb = get_the_post_thumbnail_url( get_the_ID(), 'medium_large' );
$type = $url ? mm_video_get_type( $url ) : 'unknown';
if ( ! $thumb && $url && $type === 'youtube' ) {
if ( preg_match( '/(?:youtube\.com\/(?:watch\?v=|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/', $url, $m ) ) {
$thumb = 'https://img.youtube.com/vi/' . $m[1] . '/maxresdefault.jpg';
}
}
$platform_icons = array(
'youtube' => array( 'icon' => 'fab fa-youtube', 'color' => '#ff0000' ),
'vimeo' => array( 'icon' => 'fab fa-vimeo-v', 'color' => '#1ab7ea' ),
'twitch' => array( 'icon' => 'fab fa-twitch', 'color' => '#9146ff' ),
'mp4' => array( 'icon' => 'fas fa-film', 'color' => '#aaa' ),
);
$platform = isset( $platform_icons[$type] ) ? $platform_icons[$type] : array( 'icon' => 'fas fa-play', 'color' => '#aaa' );
?>
<div class="video-card" data-category="<?php echo esc_attr( $category ); ?>">
<div class="video-card-thumb"
data-url="<?php echo esc_attr( $url ); ?>"
data-type="<?php echo esc_attr( $type ); ?>"
data-title="<?php echo esc_attr( get_the_title() ); ?>">
<?php if ( $thumb ) : ?>
<img src="<?php echo esc_url( $thumb ); ?>" alt="<?php echo esc_attr( get_the_title() ); ?>" loading="lazy">
<?php else : ?>
<div class="video-card-no-thumb"><i class="fas fa-play-circle"></i></div>
<?php endif; ?>
<div class="video-card-hover">
<i class="fas fa-play"></i>
</div>
<span class="video-platform-badge" style="color:<?php echo esc_attr($platform['color']); ?>">
<i class="<?php echo esc_attr($platform['icon']); ?>"></i>
</span>
</div>
<div class="video-card-body">
<h3 class="video-card-title"><?php the_title(); ?></h3>
<?php if ( has_excerpt() ) : ?>
<p class="video-card-excerpt"><?php echo wp_trim_words( get_the_excerpt(), 12 ); ?></p>
<?php endif; ?>
<?php if ( $category ) : ?>
<span class="video-card-tag"><?php echo esc_html( $category ); ?></span>
<?php endif; ?>
</div>
</div>
<?php endwhile; wp_reset_postdata(); ?>
</div>
<div id="video-lightbox" class="video-lightbox" style="display:none;" aria-hidden="true">
<div class="video-lightbox-backdrop"></div>
<div class="video-lightbox-box">
<div class="video-lightbox-head">
<h3 class="video-lightbox-title"></h3>
<button class="video-lightbox-close" aria-label="<?php esc_attr_e('Schließen','minecraft-modern-theme'); ?>">
<i class="fas fa-times"></i>
</button>
</div>
<div class="video-lightbox-player">
<div id="yt-player-container" style="display:none;">
<div id="yt-player"></div>
</div>
<div id="other-player-container" style="display:none;"></div>
</div>
</div>
</div>
<?php else : ?>
<div class="video-empty">
<i class="fas fa-video-slash"></i>
<p><?php _e( 'Noch keine Videos vorhanden.', 'minecraft-modern-theme' ); ?></p>
<?php if ( current_user_can('publish_posts') ) : ?>
<a href="<?php echo esc_url( admin_url('post-new.php?post_type=mm_video') ); ?>" class="btn-primary">
<i class="fas fa-plus"></i> <?php _e('Erstes Video hinzufügen','minecraft-modern-theme'); ?>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
<script src="https://www.youtube.com/iframe_api"></script>
<script>
var ytPlayer = null;
var ytPlayerReady = false;
var pendingVideoId = null;
var livestreamPlayers = new Map();
var livestreamPlayerCallbacks = [];
function onYouTubeIframeAPIReady() {
ytPlayer = new YT.Player('yt-player', {
height: '100%',
width: '100%',
playerVars: { autoplay: 1, rel: 0, modestbranding: 1 },
events: {
onReady: function(e) {
ytPlayerReady = true;
if (pendingVideoId) {
e.target.loadVideoById(pendingVideoId);
pendingVideoId = null;
}
}
}
});
livestreamPlayerCallbacks.forEach(function(callback) { callback(); });
livestreamPlayerCallbacks = [];
}
document.addEventListener('DOMContentLoaded', function() {
var lb = document.getElementById('video-lightbox');
var lbTitle = lb ? lb.querySelector('.video-lightbox-title') : null;
var ytContainer = document.getElementById('yt-player-container');
var otContainer = document.getElementById('other-player-container');
function extractYouTubeId(url) {
var m = url.match(/(?:youtube\.com\/(?:watch\?v=|shorts\/|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/);
return m ? m[1] : null;
}
function buildOtherEmbed(url, title) {
var t = (title||'Video').replace(/"/g,'&quot;');
var viM = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
if (viM) return '<iframe src="https://player.vimeo.com/video/'+viM[1]+'?autoplay=1&dnt=1" title="'+t+'" frameborder="0" allowfullscreen allow="autoplay; encrypted-media"></iframe>';
var tvM = url.match(/twitch\.tv\/videos\/(\d+)/);
if (tvM) return '<iframe src="https://player.twitch.tv/?video=v'+tvM[1]+'&parent='+location.hostname+'&autoplay=true" title="'+t+'" frameborder="0" allowfullscreen></iframe>';
var tcM = url.match(/twitch\.tv\/([a-zA-Z0-9_]+)$/);
if (tcM) return '<iframe src="https://player.twitch.tv/?channel='+tcM[1]+'&parent='+location.hostname+'&autoplay=true" title="'+t+'" frameborder="0" allowfullscreen></iframe>';
if (/\.(mp4|webm|ogv)(\?.*)?$/i.test(url)) return '<video controls autoplay playsinline><source src="'+url+'" type="video/mp4"></video>';
return '<p style="color:#aaa;text-align:center;padding:30px;">Ungültige URL</p>';
}
function openLb(url, title, type) {
if (!lb) return;
if (lbTitle) lbTitle.textContent = title || '';
lb.style.display = 'flex';
lb.setAttribute('aria-hidden','false');
document.body.style.overflow = 'hidden';
setTimeout(function(){ lb.classList.add('is-open'); }, 10);
var ytId = extractYouTubeId(url);
if (ytId) {
otContainer.style.display = 'none';
otContainer.innerHTML = '';
ytContainer.style.display = 'block';
if (ytPlayerReady && ytPlayer) ytPlayer.loadVideoById(ytId); else pendingVideoId = ytId;
} else {
ytContainer.style.display = 'none';
if (ytPlayerReady && ytPlayer) ytPlayer.stopVideo();
otContainer.style.display = 'block';
otContainer.innerHTML = buildOtherEmbed(url, title);
}
}
function closeLb() {
if (!lb) return;
lb.classList.remove('is-open');
setTimeout(function(){
lb.style.display = 'none';
lb.setAttribute('aria-hidden','true');
document.body.style.overflow = '';
if (ytPlayerReady && ytPlayer) ytPlayer.stopVideo();
ytContainer.style.display = 'none';
otContainer.innerHTML = '';
otContainer.style.display = 'none';
}, 300);
}
document.querySelectorAll('.video-card-thumb').forEach(function(el) {
el.addEventListener('click', function() { openLb(this.dataset.url, this.dataset.title, this.dataset.type); });
});
if (lb) {
lb.querySelector('.video-lightbox-close').addEventListener('click', closeLb);
lb.querySelector('.video-lightbox-backdrop').addEventListener('click', closeLb);
}
document.addEventListener('keydown', function(e){ if (e.key==='Escape') closeLb(); });
// Filter
document.querySelectorAll('.video-filter-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
document.querySelectorAll('.video-filter-btn').forEach(function(b){ b.classList.remove('active'); });
this.classList.add('active');
var f = this.dataset.filter;
document.querySelectorAll('.video-card').forEach(function(c) {
c.style.display = (f === 'all' || c.dataset.category === f) ? '' : 'none';
});
});
});
// === Livestream Lightbox System ===
var livestreamLightbox = document.createElement('div');
livestreamLightbox.id = 'livestream-lightbox';
livestreamLightbox.className = 'livestream-lightbox';
livestreamLightbox.setAttribute('aria-hidden', 'true');
livestreamLightbox.innerHTML = `
<div class="livestream-lightbox-backdrop"></div>
<div class="livestream-lightbox-box">
<div class="livestream-lightbox-head">
<h3 class="livestream-lightbox-title"></h3>
<button class="livestream-lightbox-close" aria-label="Schließen"><i class="fas fa-times"></i></button>
</div>
<div class="livestream-lightbox-player"></div>
<div class="livestream-lightbox-meta"></div>
</div>
`;
document.body.appendChild(livestreamLightbox);
var lsLightbox = document.getElementById('livestream-lightbox');
var lsTitle = lsLightbox.querySelector('.livestream-lightbox-title');
var lsPlayer = lsLightbox.querySelector('.livestream-lightbox-player');
var lsMeta = lsLightbox.querySelector('.livestream-lightbox-meta');
function openLivestreamLightbox(embedUrl, title, profileUrl, platform) {
lsTitle.textContent = title || 'Livestream';
// Wir nutzen ein sauberes Iframe für das Popup
lsPlayer.innerHTML = '<iframe src="' + embedUrl + '" allow="autoplay; encrypted-media" allowfullscreen></iframe>';
var icon = platform === 'youtube' ? 'fab fa-youtube' : (platform === 'twitch' ? 'fab fa-twitch' : 'fas fa-play');
lsMeta.innerHTML = profileUrl ? '<a href="' + profileUrl + '" target="_blank" rel="noopener"><i class="' + icon + '"></i> Zum Kanal</a>' : '';
lsLightbox.style.display = 'flex';
lsLightbox.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
setTimeout(function() { lsLightbox.classList.add('is-open'); }, 10);
}
function closeLivestreamLightbox() {
lsLightbox.classList.remove('is-open');
setTimeout(function() {
lsLightbox.style.display = 'none';
lsLightbox.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
lsPlayer.innerHTML = '';
}, 300);
}
lsLightbox.querySelector('.livestream-lightbox-close').addEventListener('click', closeLivestreamLightbox);
lsLightbox.querySelector('.livestream-lightbox-backdrop').addEventListener('click', closeLivestreamLightbox);
// Klick-Event für die "Jetzt Live" Karten
document.querySelectorAll('.video-livestream-list > div').forEach(function(card) {
card.addEventListener('click', function(e) {
// Falls auf den echten Button im Card geklickt wird, Standardverhalten lassen
if (e.target.closest('.btn-primary')) return;
e.preventDefault();
var iframe = card.querySelector('iframe');
var embedUrl = iframe ? iframe.src : '';
var title = card.querySelector('h3') ? card.querySelector('h3').textContent : 'Livestream';
var link = card.querySelector('.btn-primary') ? card.querySelector('.btn-primary').href : '';
var platform = embedUrl.includes('twitch') ? 'twitch' : 'youtube';
if (embedUrl && !embedUrl.includes('about:blank')) {
openLivestreamLightbox(embedUrl, title, link, platform);
}
});
});
});
</script>
<?php get_footer(); ?>

View File

@@ -0,0 +1,120 @@
<?php get_header(); ?>
<main id="primary" class="site-main">
<div class="container">
<div class="content-area">
<header class="archive-header">
<?php
if ( is_category() ) {
echo '<div class="archive-type-badge"><i class="fas fa-folder-open"></i> ' . __('Kategorie', 'minecraft-modern-theme') . '</div>';
the_archive_title( '<h1 class="archive-title">', '</h1>' );
the_archive_description( '<div class="archive-description">', '</div>' );
} elseif ( is_tag() ) {
echo '<div class="archive-type-badge"><i class="fas fa-hashtag"></i> ' . __('Tag', 'minecraft-modern-theme') . '</div>';
the_archive_title( '<h1 class="archive-title">', '</h1>' );
the_archive_description( '<div class="archive-description">', '</div>' );
} elseif ( is_author() ) {
$author = get_queried_object();
echo '<div class="archive-type-badge"><i class="fas fa-user"></i> ' . __('Autor', 'minecraft-modern-theme') . '</div>';
?>
<div class="archive-author-header">
<?php echo get_avatar( $author->ID, 80, '', '', array('class' => 'archive-author-avatar') ); ?>
<div>
<h1 class="archive-title"><?php echo esc_html( $author->display_name ); ?></h1>
<?php if ( $author->description ) : ?>
<p class="archive-description"><?php echo esc_html( $author->description ); ?></p>
<?php endif; ?>
</div>
</div>
<?php
} elseif ( is_date() ) {
echo '<div class="archive-type-badge"><i class="fas fa-calendar"></i> ' . __('Archiv', 'minecraft-modern-theme') . '</div>';
the_archive_title( '<h1 class="archive-title">', '</h1>' );
} else {
the_archive_title( '<h1 class="archive-title">', '</h1>' );
the_archive_description( '<div class="archive-description">', '</div>' );
}
?>
</header>
<?php if ( have_posts() ) : ?>
<div class="archive-posts-grid">
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class('post archive-post-card'); ?>>
<?php if ( has_post_thumbnail() ) : ?>
<div class="archive-card-thumb">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail('medium_large', array('loading' => 'lazy')); ?>
</a>
</div>
<?php endif; ?>
<div class="archive-card-body">
<?php $cats = get_the_category(); if ( $cats ) : ?>
<div class="archive-card-cats">
<a href="<?php echo esc_url( get_category_link( $cats[0]->term_id ) ); ?>" class="post-category-badge">
<?php echo esc_html( $cats[0]->name ); ?>
</a>
</div>
<?php endif; ?>
<h2 class="archive-card-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h2>
<div class="archive-card-meta">
<span><i class="fas fa-calendar-alt"></i> <?php echo esc_html( get_the_date() ); ?></span>
<span><i class="fas fa-user"></i> <?php the_author(); ?></span>
<?php
$wc = str_word_count( strip_tags( get_post_field('post_content', get_the_ID()) ) );
$rt = max(1, ceil($wc / 200));
?>
<span><i class="fas fa-clock"></i> <?php printf( _n('%d Min.', '%d Min.', $rt, 'minecraft-modern-theme'), $rt ); ?></span>
</div>
<div class="archive-card-excerpt">
<?php the_excerpt(); ?>
</div>
<a href="<?php the_permalink(); ?>" class="archive-card-read-more">
<?php _e('Weiterlesen', 'minecraft-modern-theme'); ?> <i class="fas fa-arrow-right"></i>
</a>
</div>
</article>
<?php endwhile; ?>
</div>
<!-- Pagination -->
<div class="archive-pagination">
<?php
the_posts_pagination( array(
'mid_size' => 2,
'prev_text' => '<i class="fas fa-chevron-left"></i> ' . __('Zurück', 'minecraft-modern-theme'),
'next_text' => __('Weiter', 'minecraft-modern-theme') . ' <i class="fas fa-chevron-right"></i>',
'before_page_number' => '<span class="page-num">',
'after_page_number' => '</span>',
) );
?>
</div>
<?php else : ?>
<div class="no-posts-found">
<i class="fas fa-search fa-2x"></i>
<p><?php _e('Keine Beiträge gefunden.', 'minecraft-modern-theme'); ?></p>
</div>
<?php endif; ?>
</div>
</div>
</main>
<?php get_footer(); ?>

View File

@@ -0,0 +1,9 @@
<?php
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
global $wpdb;
$deleted1 = $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_mm_yt_live%'");
$deleted2 = $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout_mm_yt_live%'");
echo "Cache cleared: Deleted $deleted1 transients and $deleted2 timeouts\n";

View File

@@ -0,0 +1,93 @@
<?php
/**
* Löscht alle Livestream-Caches
*/
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
echo "=== Cache löschen ===\n\n";
// Zähler
$deleted = 0;
global $wpdb;
// 1. Alle alten Channel-ID Caches löschen (mm_channel_id_* - alt)
$results = $wpdb->get_results(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_mm_channel_id_%'"
);
foreach ($results as $row) {
$key = str_replace('_transient_', '', $row->option_name);
delete_transient($key);
$deleted++;
}
echo "✓ Gelöscht: $deleted Channel-ID Caches (alt)\n";
// 2. Alle Live-Status Caches löschen (mm_live_status_*)
$deleted = 0;
$results = $wpdb->get_results(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_mm_live_status_%'"
);
foreach ($results as $row) {
$key = str_replace('_transient_', '', $row->option_name);
delete_transient($key);
$deleted++;
}
echo "✓ Gelöscht: $deleted Live-Status Caches\n";
// 3. Alte Live-ID Caches löschen (mm_live_id_* - alt)
$deleted = 0;
$results = $wpdb->get_results(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_mm_live_id_%'"
);
foreach ($results as $row) {
$key = str_replace('_transient_', '', $row->option_name);
delete_transient($key);
$deleted++;
}
echo "✓ Gelöscht: $deleted Live-ID Caches (alt)\n";
// 4. Alte Channel-ID Caches löschen (mm_id_for_* - neu)
$deleted = 0;
$results = $wpdb->get_results(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_mm_id_for_%'"
);
foreach ($results as $row) {
$key = str_replace('_transient_', '', $row->option_name);
delete_transient($key);
$deleted++;
}
echo "✓ Gelöscht: $deleted Channel-ID Caches (neu)\n";
// 3. Alte Video-Resolution Caches löschen (mm_yt_live_v2_*)
$deleted = 0;
$results = $wpdb->get_results(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_mm_yt_live_v2_%'"
);
foreach ($results as $row) {
$key = str_replace('_transient_', '', $row->option_name);
delete_transient($key);
$deleted++;
}
echo "✓ Gelöscht: $deleted YouTube Video Caches\n";
// 4. Alte Live-Status Caches löschen (mm_yt_status_*)
$deleted = 0;
$results = $wpdb->get_results(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_mm_yt_status_%'"
);
foreach ($results as $row) {
$key = str_replace('_transient_', '', $row->option_name);
delete_transient($key);
$deleted++;
}
echo "✓ Gelöscht: $deleted YouTube Status Caches\n";
echo "\n✓ Alle Caches wurden gelöscht!\n";
echo "Die Seite kann jetzt neu geladen werden.\n";

View File

@@ -0,0 +1,158 @@
<?php
// Kein direkter Zugriff
if ( ! defined('ABSPATH') ) exit;
// Wenn Passwort erforderlich, nichts anzeigen
if ( post_password_required() ) {
return;
}
?>
<div id="comments" class="comments-area">
<?php if ( have_comments() ) : ?>
<h3 class="comments-title">
<i class="fas fa-comments"></i>
<?php
$comment_count = get_comments_number();
printf(
_n(
'%d Kommentar zu &ldquo;%s&rdquo;',
'%d Kommentare zu &ldquo;%s&rdquo;',
$comment_count,
'minecraft-modern-theme'
),
$comment_count,
'<span>' . get_the_title() . '</span>'
);
?>
</h3>
<!-- Ältere Kommentare / Pagination oben -->
<?php if ( get_comment_pages_count() > 1 && get_option('page_comments') ) : ?>
<nav class="comment-navigation" aria-label="<?php esc_attr_e('Kommentar-Navigation', 'minecraft-modern-theme'); ?>">
<div class="comment-nav-prev"><?php previous_comments_link( '<i class="fas fa-chevron-left"></i> ' . __('Ältere Kommentare', 'minecraft-modern-theme') ); ?></div>
<div class="comment-nav-next"><?php next_comments_link( __('Neuere Kommentare', 'minecraft-modern-theme') . ' <i class="fas fa-chevron-right"></i>' ); ?></div>
</nav>
<?php endif; ?>
<ol class="comment-list">
<?php
wp_list_comments( array(
'style' => 'ol',
'short_ping' => true,
'avatar_size' => 48,
'callback' => 'minecraft_modern_comment_template',
) );
?>
</ol>
<!-- Neuere Kommentare / Pagination unten -->
<?php if ( get_comment_pages_count() > 1 && get_option('page_comments') ) : ?>
<nav class="comment-navigation" aria-label="<?php esc_attr_e('Kommentar-Navigation', 'minecraft-modern-theme'); ?>">
<div class="comment-nav-prev"><?php previous_comments_link( '<i class="fas fa-chevron-left"></i> ' . __('Ältere Kommentare', 'minecraft-modern-theme') ); ?></div>
<div class="comment-nav-next"><?php next_comments_link( __('Neuere Kommentare', 'minecraft-modern-theme') . ' <i class="fas fa-chevron-right"></i>' ); ?></div>
</nav>
<?php endif; ?>
<?php endif; // have_comments() ?>
<?php
// Kommentarformular
if ( comments_open() ) :
comment_form( array(
'title_reply' => '<span><i class="fas fa-pencil-alt"></i> ' . __('Hinterlasse einen Kommentar', 'minecraft-modern-theme') . '</span>',
'title_reply_to' => __('Antworten auf %s', 'minecraft-modern-theme'),
'cancel_reply_link' => __('Abbrechen', 'minecraft-modern-theme'),
'label_submit' => __('Kommentar absenden', 'minecraft-modern-theme'),
'comment_notes_before' => '',
'comment_notes_after' => '',
'class_form' => 'comment-form',
'class_submit' => 'submit btn-comment-submit',
) );
elseif ( ! is_user_logged_in() ) :
?>
<div class="comments-closed-notice">
<i class="fas fa-lock"></i>
<?php _e('Kommentare sind für diesen Beitrag deaktiviert.', 'minecraft-modern-theme'); ?>
</div>
<?php
endif;
?>
</div><!-- #comments -->
<?php
/**
* Callback für den Kommentar-Template.
* Wird in functions.php nicht definiert, daher hier.
*/
if ( ! function_exists('minecraft_modern_comment_template') ) :
function minecraft_modern_comment_template( $comment, $args, $depth ) {
$tag = ( 'div' === $args['style'] ) ? 'div' : 'li';
?>
<<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( empty($args['has_children']) ? '' : 'parent', $comment ); ?>>
<article id="div-comment-<?php comment_ID(); ?>" class="comment-body">
<div class="comment-meta">
<div class="comment-author-avatar">
<?php echo get_avatar( $comment, $args['avatar_size'], '', '', array('class' => 'comment-avatar') ); ?>
</div>
<div class="comment-author-info">
<span class="comment-author-name">
<?php
$author_url = get_comment_author_url($comment);
if ( $author_url ) {
echo '<a href="' . esc_url($author_url) . '" rel="external nofollow">' . get_comment_author($comment) . '</a>';
} else {
echo esc_html( get_comment_author($comment) );
}
?>
</span>
<span class="comment-date">
<i class="fas fa-calendar-alt"></i>
<a href="<?php echo esc_url( get_comment_link($comment, $args) ); ?>">
<time datetime="<?php comment_date('c'); ?>">
<?php
printf(
__('%1$s um %2$s Uhr', 'minecraft-modern-theme'),
get_comment_date('', $comment),
get_comment_time('', false, false, $comment)
);
?>
</time>
</a>
</span>
</div>
<?php if ( '0' === $comment->comment_approved ) : ?>
<div class="comment-awaiting-moderation">
<i class="fas fa-clock"></i> <?php _e('Dein Kommentar wartet auf Freigabe.', 'minecraft-modern-theme'); ?>
</div>
<?php endif; ?>
</div>
<div class="comment-content">
<?php comment_text(); ?>
</div>
<footer class="comment-footer">
<?php
comment_reply_link( array_merge( $args, array(
'add_below' => 'div-comment',
'depth' => $depth,
'max_depth' => $args['max_depth'],
'before' => '<div class="reply">',
'after' => '</div>',
) ) );
edit_comment_link( '<i class="fas fa-pencil-alt"></i> ' . __('Bearbeiten', 'minecraft-modern-theme'), '<div class="edit-link">', '</div>' );
?>
</footer>
</article>
<?php
}
endif;
?>

View File

@@ -0,0 +1,74 @@
#mm-assistant-chat {
display: none;
position: absolute;
bottom: 110px;
right: 0;
width: 320px;
background: #1a1d22; /* Dunklerer Hintergrund */
border-radius: 15px;
box-shadow: 0 15px 45px rgba(0,0,0,0.5);
border: 1px solid #333;
overflow: hidden;
z-index: 10000;
}
.chat-header {
background: #0099ff;
padding: 12px 15px;
display: flex;
align-items: center;
font-weight: bold;
color: white;
}
.status-dot {
height: 10px;
width: 10px;
background-color: #00ff00;
border-radius: 50%;
display: inline-block;
margin-right: 10px;
box-shadow: 0 0 5px #00ff00;
}
#mm-assistant-content {
padding: 20px;
max-height: 350px;
overflow-y: auto;
color: #e0e0e0;
line-height: 1.6;
font-size: 14px;
}
.chat-input-area {
background: #111;
padding: 10px;
display: flex;
gap: 8px;
border-top: 1px solid #333;
}
#mm-assistant-input {
flex: 1;
background: #222;
border: 1px solid #444;
color: white;
padding: 8px 12px;
border-radius: 8px;
outline: none;
}
#mm-assistant-send {
background: #0099ff;
border: none;
color: white;
padding: 5px 12px;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
transition: background 0.2s;
}
#mm-assistant-send:hover { background: #0077cc; }

View File

@@ -0,0 +1,139 @@
/* Modernes Design für Virtuellen Assistenten (unten rechts) */
/* Positionierung wird jetzt dynamisch über die Klasse mm-bot-pos-* am #mm-bot-root gesetzt */
#mm-assistant-btn {
background: transparent;
border: none;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,0.18);
border-radius: 16px;
padding: 0;
transition: box-shadow 0.2s;
}
#mm-assistant-btn:hover {
box-shadow: 0 4px 16px rgba(0,153,255,0.25);
}
#mm-assistant-btn img {
display: block;
background: #181c22;
border-radius: 16px;
width: 70px;
height: 70px;
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
}
#mm-assistant-chat {
display: none;
position: absolute;
bottom: 110px;
right: 0;
width: 340px;
max-width: 95vw;
background: #23272e;
color: #fff;
border-radius: 18px;
box-shadow: 0 8px 32px rgba(0,0,0,0.35);
padding: 0;
overflow: hidden;
animation: mmFadeIn 0.25s;
}
@keyframes mmFadeIn {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
#mm-assistant-chat > div:first-child {
background: #0099ff;
color: #fff;
font-weight: 600;
font-size: 1.1rem;
padding: 14px 18px 10px 18px;
border-bottom: 1px solid #1a1d22;
border-radius: 18px 18px 0 0;
}
#mm-assistant-content {
min-height: 40px;
font-size: 1rem;
padding: 18px 18px 8px 18px;
background: #23272e;
}
#mm-assistant-input {
width: 100%;
padding: 10px 14px;
border-radius: 8px;
border: none;
outline: none;
font-size: 1rem;
background: #1a1d22;
color: #fff;
margin-bottom: 8px;
box-sizing: border-box;
}
#mm-assistant-input::placeholder {
color: #aaa;
}
#mm-assistant-send {
width: 100%;
background: #0099ff;
color: #fff;
font-weight: bold;
border: none;
border-radius: 8px;
padding: 10px 0;
font-size: 1.1rem;
cursor: pointer;
transition: background 0.2s;
}
#mm-assistant-send:hover {
background: #007acc;
}
@media (max-width: 600px) {
#mm-assistant-chat {
width: 98vw;
min-width: 0;
border-radius: 12px;
right: -8px;
}
#mm-virtual-assistant {
right: 8px;
bottom: 8px;
}
}
/* BUG-FIX: Doppelter #mm-assistant-chat Block und veraltete Chat-Stile entfernt.
Die Regeln .chat-header, .status-dot, .chat-input-area sind Relikte des alten
Widget-Designs und werden vom aktuellen mm-bot-* Markup nicht mehr verwendet.
Sie wurden entfernt um Konflikte mit den modernen Stilen oben zu vermeiden. */
#mm-bot-root { position:fixed; bottom:30px; right:30px; z-index:999999; font-family:-apple-system,system-ui,sans-serif; }
#mm-bot-launcher { background:#0099ff; border:none; border-radius:50%; width:60px; height:60px; cursor:pointer; box-shadow:0 4px 12px rgba(0,0,0,.25); transition:.3s ease; display:flex; align-items:center; justify-content:center; overflow:hidden; }
#mm-bot-launcher:hover { transform:scale(1.08); background:#0088ee; }
#mm-bot-launcher img { width:42px; height:42px; border-radius:4px; }
#mm-bot-chat { position:absolute; bottom:80px; right:0; width:340px; background:#1e2124; border-radius:15px; box-shadow:0 12px 40px rgba(0,0,0,.45); display:flex; flex-direction:column; overflow:hidden; border:1px solid #333; }
.mm-bot-header { background:#2f3136; color:#fff; padding:14px 18px; display:flex; align-items:center; gap:12px; font-weight:700; border-bottom:1px solid #111; }
.mm-bot-status { width:10px; height:10px; background:#43b581; border-radius:50%; box-shadow:0 0 8px #43b581; flex-shrink:0; }
#mm-bot-content { height:320px; padding:16px; overflow-y:auto; background:#36393f; display:flex; flex-direction:column; gap:10px; scroll-behavior:smooth; }
.mm-msg { padding:10px 14px; border-radius:12px; font-size:14px; line-height:1.5; max-width:88%; word-break:break-word; }
.mm-msg.bot { background:#40444b; color:#dcddde; align-self:flex-start; border-bottom-left-radius:3px; }
.mm-msg.user { background:#0099ff; color:#fff; align-self:flex-end; border-bottom-right-radius:3px; }
.mm-msg a { color:#00b0f4; text-decoration:none; font-weight:600; }
.mm-msg a:hover { text-decoration:underline; }
.mm-loading { opacity:.6; font-size:20px; letter-spacing:4px; }
.mm-bot-quick { display:flex; flex-wrap:wrap; gap:6px; margin-top:4px; }
.mm-quick-btn { background:#2f3136; color:#dcddde; border:1px solid #444; border-radius:20px; padding:5px 12px; font-size:12px; cursor:pointer; transition:.2s; white-space:nowrap; }
.mm-quick-btn:hover { background:#0099ff; color:#fff; border-color:#0099ff; }
.mm-bot-input-area { padding:12px 14px; background:#2f3136; display:flex; gap:8px; border-top:1px solid #222; }
#mm-bot-field { flex:1; background:#40444b; border:1px solid #222; border-radius:8px; color:#fff; padding:9px 12px; outline:none; font-size:14px; }
#mm-bot-field:focus { border-color:#0099ff; }
/* BUG-FIX: ID war '#id-mm-bot-send', korrektes HTML-Element hat id="mm-bot-send" */
#mm-bot-send { background:#0099ff; border:none; color:#fff; border-radius:8px; padding:0 16px; cursor:pointer; font-size:16px; }
#mm-bot-send:hover { background:#00b0f4; }
#mm-bot-close { margin-left:auto; background:none; border:none; color:#888; font-size:24px; cursor:pointer; line-height:1; padding:0; }
#mm-bot-close:hover { color:#fff; }
#mm-bot-chat code { background:#222; padding:2px 6px; border-radius:4px; color:#ffa500; font-family:monospace; font-size:13px; }
@media (max-width:400px) {
#mm-bot-chat { width:calc(100vw - 20px); right:-10px; }
}

View File

@@ -15,13 +15,19 @@
<!-- 2. UNTERER BEREICH: Menü, Copyright, Legal --> <!-- 2. UNTERER BEREICH: Menü, Copyright, Legal -->
<div class="footer-bottom-bar"> <div class="footer-bottom-bar">
<div class="footer-left-group"> <div class="footer-left-group">
<!-- Copyright --> <!-- Copyright -->
<div class="site-info"> <div class="site-info">
<?php <?php
$copyright_text = get_theme_mod( 'footer_copyright', '&copy; ' . date('Y') . ' ' . get_bloginfo('name') ); // BUG-FIX: get_theme_mod() mit leerem Default ('') aus dem Customizer
// lieferte einen leeren String statt des Auto-Texts, wenn der User
// das Feld explizit gespeichert hatte. Jetzt wird auf empty() geprüft.
$copyright_text = get_theme_mod( 'footer_copyright', '' );
if ( empty( $copyright_text ) ) {
$copyright_text = '&copy; ' . date('Y') . ' ' . get_bloginfo('name');
}
if ( get_theme_mod('show_footer_credit', true) ) { if ( get_theme_mod('show_footer_credit', true) ) {
$full_footer_text = $copyright_text . ' <span class="footer-separator">|</span> <span class="footer-credit"> $full_footer_text = $copyright_text . ' <span class="footer-separator">|</span> <span class="footer-credit">
<a href="https://m-viper.de" target="_blank" rel="noopener noreferrer"> <a href="https://m-viper.de" target="_blank" rel="noopener noreferrer">
@@ -35,18 +41,16 @@
?> ?>
</div> </div>
<!-- Footer Menü ( falls vorhanden ) --> <!-- Footer Menü (falls vorhanden) -->
<?php if ( has_nav_menu( 'footer' ) ) : ?> <?php if ( has_nav_menu( 'footer' ) ) : ?>
<nav class="footer-navigation"> <nav class="footer-navigation">
<?php <?php
wp_nav_menu( wp_nav_menu( array(
array( 'theme_location' => 'footer',
'theme_location' => 'footer', 'menu_class' => 'footer-menu',
'menu_class' => 'footer-menu', 'container' => false,
'container' => false, 'depth' => 1,
'depth' => 1, // Nur eine Ebene ) );
)
);
?> ?>
</nav> </nav>
<?php endif; ?> <?php endif; ?>
@@ -56,29 +60,30 @@
<!-- Impressum & Datenschutz --> <!-- Impressum & Datenschutz -->
<div class="footer-legal-links"> <div class="footer-legal-links">
<?php <?php
$impressum_url = get_theme_mod('footer_impressum_url'); $impressum_url = get_theme_mod('footer_impressum_url');
$datenschutz_url = get_theme_mod('footer_datenschutz_url'); $datenschutz_url = get_theme_mod('footer_datenschutz_url');
$links = array(); $links = array();
if (!empty($impressum_url)) { if ( ! empty($impressum_url) ) {
$links[] = '<a href="' . esc_url($impressum_url) . '"><i class="fas fa-info-circle"></i> Impressum</a>'; $links[] = '<a href="' . esc_url($impressum_url) . '"><i class="fas fa-info-circle"></i> Impressum</a>';
} }
if ( ! empty($datenschutz_url) ) {
if (!empty($datenschutz_url)) {
$links[] = '<a href="' . esc_url($datenschutz_url) . '"><i class="fas fa-shield-alt"></i> Datenschutz</a>'; $links[] = '<a href="' . esc_url($datenschutz_url) . '"><i class="fas fa-shield-alt"></i> Datenschutz</a>';
} }
if ( ! empty($links) ) {
if (!empty($links)) { echo implode('', $links);
echo implode('', $links); // Kein Trennzeichen mehr, da wir Icons haben
} }
?> ?>
</div> </div>
</div> </div>
</div> <!-- Ende footer-bottom-bar --> </div><!-- Ende footer-bottom-bar -->
</div> <!-- Ende Container --> </div><!-- Ende Container -->
<!-- THEME TOGGLE (Fixiert unten rechts) --> <a href="#" id="scroll-to-top" aria-label="Zurück nach oben" title="Nach oben scrollen">
<i class="fas fa-chevron-up"></i>
</a>
<button class="theme-toggle" aria-label="Dark/Light Mode umschalten" title="Dark / Light Mode"> <button class="theme-toggle" aria-label="Dark/Light Mode umschalten" title="Dark / Light Mode">
<div class="theme-toggle-icons"> <div class="theme-toggle-icons">
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">

View File

@@ -20,7 +20,6 @@ if ( get_theme_mod('slider_enabled', false) ) :
<?php if ( $title ) : ?> <?php if ( $title ) : ?>
<h2 class="slider-title"><?php echo esc_html( $title ); ?></h2> <h2 class="slider-title"><?php echo esc_html( $title ); ?></h2>
<?php endif; ?> <?php endif; ?>
<?php if ( $subtitle ) : ?> <?php if ( $subtitle ) : ?>
<p class="slider-subtitle"><?php echo esc_html( $subtitle ); ?></p> <p class="slider-subtitle"><?php echo esc_html( $subtitle ); ?></p>
<?php endif; ?> <?php endif; ?>
@@ -41,10 +40,6 @@ if ( get_theme_mod('slider_enabled', false) ) :
<div class="swiper-button-next"></div> <div class="swiper-button-next"></div>
<?php endif; ?> <?php endif; ?>
</section> </section>
<!-- ================================================= -->
<!-- ANNOUNCEMENT ANKER (unter Slider, ohne Scroll-Bug) -->
<!-- ================================================= -->
<div id="mm-announcement-anchor"></div> <div id="mm-announcement-anchor"></div>
<?php else : ?> <?php else : ?>
@@ -60,29 +55,16 @@ if ( get_theme_mod('slider_enabled', false) ) :
?> ?>
<section class="hero-section" <section class="hero-section"
style="<?php style="<?php echo $hero_bg ? "background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url('" . esc_url( $hero_bg ) . "')" : ''; ?>">
echo $hero_bg
? "background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url('" . esc_url( $hero_bg ) . "')"
: '';
?>">
<div class="container"> <div class="container">
<h1 class="hero-title"><?php echo esc_html( $hero_title ); ?></h1> <h1 class="hero-title"><?php echo esc_html( $hero_title ); ?></h1>
<p class="hero-subtitle"><?php echo esc_html( $hero_subtitle ); ?></p> <p class="hero-subtitle"><?php echo esc_html( $hero_subtitle ); ?></p>
<div class="hero-buttons"> <div class="hero-buttons">
<a href="<?php echo esc_url( $btn1_url ); ?>" class="hero-button-1"> <a href="<?php echo esc_url( $btn1_url ); ?>" class="hero-button-1"><?php echo esc_html( $btn1_text ); ?></a>
<?php echo esc_html( $btn1_text ); ?> <a href="<?php echo esc_url( $btn2_url ); ?>" class="hero-button-2"><?php echo esc_html( $btn2_text ); ?></a>
</a>
<a href="<?php echo esc_url( $btn2_url ); ?>" class="hero-button-2">
<?php echo esc_html( $btn2_text ); ?>
</a>
</div> </div>
</div> </div>
</section> </section>
<!-- ================================================= -->
<!-- ANNOUNCEMENT ANKER (unter Hero, ohne Scroll-Bug) -->
<!-- ================================================= -->
<div id="mm-announcement-anchor"></div> <div id="mm-announcement-anchor"></div>
<?php endif; ?> <?php endif; ?>
@@ -92,22 +74,19 @@ if ( get_theme_mod('slider_enabled', false) ) :
<!-- ===================================================== --> <!-- ===================================================== -->
<main id="primary" class="site-main"> <main id="primary" class="site-main">
<div class="container"> <div class="container">
<?php <?php
// Hole Sidebar-Einstellungen $sidebar_enabled = get_theme_mod( 'homepage_sidebar_enabled', false );
$sidebar_enabled = get_theme_mod( 'homepage_sidebar_enabled', false );
$sidebar_position = get_theme_mod( 'homepage_sidebar_position', 'right' ); $sidebar_position = get_theme_mod( 'homepage_sidebar_position', 'right' );
// Prüfe ob mindestens ein Sidebar-Bereich aktiv ist
$has_sidebar_content = ( $has_sidebar_content = (
is_active_sidebar( 'homepage-sidebar-top' ) || is_active_sidebar( 'homepage-sidebar-top' ) ||
is_active_sidebar( 'homepage-sidebar-middle-1' ) || is_active_sidebar( 'homepage-sidebar-middle-1' ) ||
is_active_sidebar( 'homepage-sidebar-middle-2' ) || is_active_sidebar( 'homepage-sidebar-middle-2' ) ||
is_active_sidebar( 'homepage-sidebar-bottom' ) || is_active_sidebar( 'homepage-sidebar-bottom' ) ||
is_active_sidebar( 'homepage-sidebar-extra' ) is_active_sidebar( 'homepage-sidebar-extra' )
); );
// Setze CSS-Klassen basierend auf Sidebar-Status
$content_class = 'content-area'; $content_class = 'content-area';
if ( $sidebar_enabled && $has_sidebar_content ) { if ( $sidebar_enabled && $has_sidebar_content ) {
$content_class = 'content-area with-sidebar sidebar-' . esc_attr( $sidebar_position ); $content_class = 'content-area with-sidebar sidebar-' . esc_attr( $sidebar_position );
@@ -115,39 +94,10 @@ if ( get_theme_mod('slider_enabled', false) ) :
?> ?>
<div class="<?php echo esc_attr( $content_class ); ?>"> <div class="<?php echo esc_attr( $content_class ); ?>">
<?php if ( $sidebar_enabled && $sidebar_position === 'left' && $has_sidebar_content ) : ?> <?php if ( $sidebar_enabled && $sidebar_position === 'left' && $has_sidebar_content ) : ?>
<!-- SIDEBAR LINKS -->
<aside class="homepage-sidebar sidebar-left"> <aside class="homepage-sidebar sidebar-left">
<?php if ( is_active_sidebar( 'homepage-sidebar-top' ) ) : ?> <?php minecraft_modern_render_sidebar_sections(); ?>
<div class="sidebar-section sidebar-top">
<?php dynamic_sidebar( 'homepage-sidebar-top' ); ?>
</div>
<?php endif; ?>
<?php if ( is_active_sidebar( 'homepage-sidebar-middle-1' ) ) : ?>
<div class="sidebar-section sidebar-middle-1">
<?php dynamic_sidebar( 'homepage-sidebar-middle-1' ); ?>
</div>
<?php endif; ?>
<?php if ( is_active_sidebar( 'homepage-sidebar-middle-2' ) ) : ?>
<div class="sidebar-section sidebar-middle-2">
<?php dynamic_sidebar( 'homepage-sidebar-middle-2' ); ?>
</div>
<?php endif; ?>
<?php if ( is_active_sidebar( 'homepage-sidebar-bottom' ) ) : ?>
<div class="sidebar-section sidebar-bottom">
<?php dynamic_sidebar( 'homepage-sidebar-bottom' ); ?>
</div>
<?php endif; ?>
<?php if ( is_active_sidebar( 'homepage-sidebar-extra' ) ) : ?>
<div class="sidebar-section sidebar-extra">
<?php dynamic_sidebar( 'homepage-sidebar-extra' ); ?>
</div>
<?php endif; ?>
</aside> </aside>
<?php endif; ?> <?php endif; ?>
@@ -155,71 +105,32 @@ if ( get_theme_mod('slider_enabled', false) ) :
<div class="main-content"> <div class="main-content">
<?php if ( have_posts() ) : ?> <?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?> <?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class('post'); ?>>
<article id="post-<?php the_ID(); ?>" <?php post_class( 'post' ); ?>>
<?php if ( has_post_thumbnail() ) : ?> <?php if ( has_post_thumbnail() ) : ?>
<div class="post-thumbnail"> <div class="post-thumbnail">
<a href="<?php the_permalink(); ?>"> <a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail( 'medium_large' ); ?> <?php the_post_thumbnail('medium_large'); ?>
</a> </a>
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class="post-content"> <div class="post-content">
<h2 class="post-title"> <h2 class="post-title">
<a href="<?php the_permalink(); ?>"> <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
<?php the_title(); ?>
</a>
</h2> </h2>
<div class="post-full-content"> <div class="post-full-content">
<?php the_content(); ?> <?php the_content(); ?>
</div> </div>
</div> </div>
</article> </article>
<?php endwhile; ?> <?php endwhile; ?>
<?php else : ?> <?php else : ?>
<p><?php esc_html_e('Keine Beiträge gefunden.', 'minecraft-modern-theme'); ?></p>
<p><?php esc_html_e( 'Keine Beiträge gefunden.', 'minecraft-modern-theme' ); ?></p>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php if ( $sidebar_enabled && $sidebar_position === 'right' && $has_sidebar_content ) : ?> <?php if ( $sidebar_enabled && $sidebar_position === 'right' && $has_sidebar_content ) : ?>
<!-- SIDEBAR RECHTS -->
<aside class="homepage-sidebar sidebar-right"> <aside class="homepage-sidebar sidebar-right">
<?php if ( is_active_sidebar( 'homepage-sidebar-top' ) ) : ?> <?php minecraft_modern_render_sidebar_sections(); ?>
<div class="sidebar-section sidebar-top">
<?php dynamic_sidebar( 'homepage-sidebar-top' ); ?>
</div>
<?php endif; ?>
<?php if ( is_active_sidebar( 'homepage-sidebar-middle-1' ) ) : ?>
<div class="sidebar-section sidebar-middle-1">
<?php dynamic_sidebar( 'homepage-sidebar-middle-1' ); ?>
</div>
<?php endif; ?>
<?php if ( is_active_sidebar( 'homepage-sidebar-middle-2' ) ) : ?>
<div class="sidebar-section sidebar-middle-2">
<?php dynamic_sidebar( 'homepage-sidebar-middle-2' ); ?>
</div>
<?php endif; ?>
<?php if ( is_active_sidebar( 'homepage-sidebar-bottom' ) ) : ?>
<div class="sidebar-section sidebar-bottom">
<?php dynamic_sidebar( 'homepage-sidebar-bottom' ); ?>
</div>
<?php endif; ?>
<?php if ( is_active_sidebar( 'homepage-sidebar-extra' ) ) : ?>
<div class="sidebar-section sidebar-extra">
<?php dynamic_sidebar( 'homepage-sidebar-extra' ); ?>
</div>
<?php endif; ?>
</aside> </aside>
<?php endif; ?> <?php endif; ?>
@@ -227,190 +138,4 @@ if ( get_theme_mod('slider_enabled', false) ) :
</div> </div>
</main> </main>
<!-- ===================================================== -->
<!-- ZUSÄTZLICHES CSS FÜR SIDEBAR LAYOUT -->
<!-- ===================================================== -->
<style>
/* Content ohne Sidebar - volle Breite */
.content-area .main-content {
width: 100%;
margin-bottom: 40px;
}
/* Container mit Sidebar-Layout */
.content-area.with-sidebar {
display: grid;
gap: 30px;
align-items: start;
}
/* Sidebar rechts (Standard) */
.content-area.with-sidebar.sidebar-right {
grid-template-columns: 1fr 300px;
}
.content-area.with-sidebar.sidebar-right .main-content {
order: 1;
}
.content-area.with-sidebar.sidebar-right .homepage-sidebar {
order: 2;
}
/* Sidebar links */
.content-area.with-sidebar.sidebar-left {
grid-template-columns: 300px 1fr;
}
.content-area.with-sidebar.sidebar-left .homepage-sidebar {
order: 1;
}
.content-area.with-sidebar.sidebar-left .main-content {
order: 2;
}
/* Entfernt das Sticky-Verhalten der Sidebar komplett */
.homepage-sidebar {
position: relative !important;
top: auto !important;
background: var(--card-background, #1e2029);
border-radius: 8px;
padding: 20px;
overflow-y: auto;
max-height: calc(200vh - 10vh);
}
/* Sidebar Sections - Bereiche innerhalb der Sidebar */
.homepage-sidebar .sidebar-section {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.homepage-sidebar .sidebar-section:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
/* Spezielle Styling für bestimmte Bereiche */
.homepage-sidebar .sidebar-top {
border-bottom: 2px solid var(--primary-accent, #00d4ff);
}
.homepage-sidebar .sidebar-bottom {
padding-top: 20px;
border-top: 2px solid var(--primary-accent, #00d4ff);
border-bottom: none;
}
.homepage-sidebar .widget {
margin-bottom: 20px;
}
.homepage-sidebar .widget:last-child {
margin-bottom: 0;
}
.homepage-sidebar .widget-title {
font-size: 1.2rem;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid var(--primary-accent, #00d4ff);
color: var(--primary-accent, #00d4ff);
}
.homepage-sidebar ul {
list-style: none;
padding: 0;
margin: 0;
}
.homepage-sidebar ul li {
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.homepage-sidebar ul li:last-child {
border-bottom: none;
}
.homepage-sidebar a {
color: inherit;
text-decoration: none;
transition: color 0.2s ease;
}
.homepage-sidebar a:hover {
color: var(--primary-accent, #00d4ff);
}
/* Mobile: Sidebar unter Content */
@media (max-width: 768px) {
.content-area.with-sidebar.sidebar-right,
.content-area.with-sidebar.sidebar-left {
grid-template-columns: 1fr;
}
.content-area.with-sidebar .homepage-sidebar {
order: 3 !important; /* Immer unten auf mobilen Geräten */
position: relative;
top: 0;
max-height: none;
}
.content-area.with-sidebar .main-content {
order: 1 !important;
}
}
/* Dark Mode Anpassungen */
body.dark-mode .homepage-sidebar {
background: #1e1e1e;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
body.dark-mode .homepage-sidebar ul li {
border-bottom-color: rgba(255, 255, 255, 0.1);
}
/* Light Mode Anpassungen */
body.light-mode .homepage-sidebar {
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
body.light-mode .homepage-sidebar .sidebar-section {
border-bottom-color: rgba(0, 0, 0, 0.1);
}
body.light-mode .homepage-sidebar ul li {
border-bottom-color: rgba(0, 0, 0, 0.1);
}
body.light-mode .homepage-sidebar .widget-title {
color: var(--primary-accent, #00d4ff);
}
/* Scrollbar Styling für Sidebar */
.homepage-sidebar::-webkit-scrollbar {
width: 6px;
}
.homepage-sidebar::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 3px;
}
.homepage-sidebar::-webkit-scrollbar-thumb {
background: var(--primary-accent, #00d4ff);
border-radius: 3px;
}
.homepage-sidebar::-webkit-scrollbar-thumb:hover {
background: var(--primary-accent-hover, #00b8e6);
}
</style>
<?php get_footer(); ?> <?php get_footer(); ?>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
<?php
/**
* Hilfsskript: Finde Channel-ID für einen @Handle
* Verwendet direkt die YouTube API (ohne WordPress)
*/
// API Key aus wp-config.php lesen
$wp_config_path = __DIR__ . '/../../../wp-config.php';
$api_key = '';
if ( file_exists( $wp_config_path ) ) {
$wp_config_content = file_get_contents( $wp_config_path );
// Versuche YOUTUBE_API_KEY zu finden
if ( preg_match( "/define\s*\(\s*['\"]YOUTUBE_API_KEY['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $wp_config_content, $matches ) ) {
$api_key = $matches[1];
}
}
// Fallback: Direkte Eingabe hier (NUR FÜR TESTS!)
if ( empty( $api_key ) ) {
// TEMPORÄR: Trage hier deinen API Key ein
$api_key = 'AIzaSyD-jSXZO-R4NJBySF0WL6SoFJmBDk2Gdbk'; // ← Dein API Key aus dem Customizer
}
if ( ! isset( $argv[1] ) ) {
echo "Verwendung: php get-channel-id.php @DeinHandle\n";
echo "Beispiel: php get-channel-id.php @afartv\n";
exit(1);
}
$handle = $argv[1];
if ( empty( $api_key ) ) {
echo "❌ Kein YouTube API Key gefunden!\n";
echo " Trage den API Key temporär in Zeile 19 ein ODER\n";
echo " Definiere YOUTUBE_API_KEY in wp-config.php\n";
exit(1);
}
// @ entfernen falls vorhanden
$handle = ltrim( $handle, '@' );
echo "=== Channel-ID Abfrage ===\n\n";
echo "Handle: @$handle\n";
echo "API Key: " . substr($api_key, 0, 10) . "... (" . strlen($api_key) . " chars)\n\n";
// YouTube Data API v3: search.list
$search_url = 'https://www.googleapis.com/youtube/v3/search?' . http_build_query( array(
'part' => 'snippet',
'q' => '@' . $handle,
'type' => 'channel',
'key' => $api_key,
) );
echo "Sende API-Anfrage...\n";
$context = stream_context_create( array(
'http' => array(
'timeout' => 10,
),
) );
$response = file_get_contents( $search_url, false, $context );
if ( $response === false ) {
echo "❌ API-Anfrage fehlgeschlagen\n";
exit(1);
}
$data = json_decode( $response, true );
if ( isset( $data['error'] ) ) {
echo "❌ YouTube API Fehler:\n";
echo " Code: " . $data['error']['code'] . "\n";
echo " Nachricht: " . $data['error']['message'] . "\n";
exit(1);
}
if ( empty( $data['items'] ) ) {
echo "❌ Kein Kanal gefunden für @$handle\n";
echo " Versuche es mit einem anderen @Handle oder mit der vollständigen Kanal-URL.\n";
exit(1);
}
$channel = $data['items'][0];
$channel_id = $channel['id']['channelId'];
$channel_title = $channel['snippet']['title'];
echo "\n✅ Kanal gefunden!\n\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
echo "Kanal-Titel: $channel_title\n";
echo "Channel-ID: $channel_id\n";
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
echo "📋 Kopiere diese Channel-ID in dein Livestream-Post:\n";
echo " Livestreams → Bearbeiten → YouTube Kanal-ID: $channel_id\n\n";
echo "✅ Fertig!\n";

View File

@@ -3,77 +3,144 @@
<head> <head>
<meta charset="<?php bloginfo( 'charset' ); ?>"> <meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="strict-origin-when-cross-origin">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<?php wp_head(); ?> <?php wp_head(); ?>
</head> </head>
<body <?php body_class(); ?>> <body <?php body_class(); ?>>
<?php wp_body_open(); ?> <?php wp_body_open(); ?>
<header id="masthead" class="site-header"> <?php
$menu_style = get_theme_mod( 'header_menu_style', 'classic' );
$branding_pos = get_theme_mod( 'sidebar_branding_position', 'left' );
?>
<?php if ( $menu_style === 'sidebar' ) : ?>
<!-- ══════════════════════════════════════════════════════════════
LAYOUT 3: SIDEBAR
═══════════════════════════════════════════════════════════════ -->
<aside class="header-sidebar" id="header-sidebar" aria-hidden="true">
<div class="header-sidebar-inner">
<?php mm_branding( true ); ?>
<?php mm_nav(); ?>
</div>
<button class="sidebar-menu-close" aria-label="Menü schließen"><i class="fas fa-times"></i></button>
</aside>
<div class="header-sidebar-overlay" id="sidebar-overlay"></div>
<header id="masthead" class="site-header site-header--sidebar branding-<?php echo esc_attr( $branding_pos ); ?>">
<div class="container"> <div class="container">
<div class="header-main"> <div class="header-main">
<div class="site-branding"> <button class="sidebar-menu-toggle" aria-label="Menü öffnen" aria-expanded="false" aria-controls="header-sidebar">
<?php <i class="fas fa-bars"></i>
if ( function_exists( 'the_custom_logo' ) && has_custom_logo() ) { </button>
the_custom_logo(); <?php if ( $branding_pos === 'right' ) : ?>
} <?php mm_icons(); ?>
?> <?php mm_branding( true ); ?>
<?php elseif ( $branding_pos === 'center' ) : ?>
<?php <div class="header-center-spacer"></div>
if ( is_front_page() && is_home() ) : <?php mm_branding( true ); ?>
?> <?php mm_icons(); ?>
<h1 class="site-title"><a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a></h1> <?php else : ?>
<?php else : ?> <?php mm_branding( true ); ?>
<p class="site-title"><a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a></p> <?php mm_icons(); ?>
<?php endif; ?> <?php endif; ?>
</div> </div>
<!-- NAVIGATION START -->
<nav id="site-navigation" class="main-navigation" role="navigation" aria-label="<?php esc_attr_e('Hauptmenü','minecraft-modern-theme'); ?>">
<!-- Mobile Toggle Button -->
<button class="menu-toggle" aria-controls="primary-menu" aria-expanded="false">
<i class="fas fa-bars"></i>
</button>
<?php
wp_nav_menu( array(
'theme_location' => 'primary',
'container' => false,
'menu_class' => 'primary-menu',
'fallback_cb' => false,
) );
?>
</nav>
<!-- NAVIGATION ENDE -->
<div class="header-info">
<div class="social-links">
<?php
$social_icons = array(
'discord' => 'fab fa-discord',
'youtube' => 'fab fa-youtube',
'twitter' => 'fab fa-x-twitter',
'facebook' => 'fab fa-facebook-f',
'instagram' => 'fab fa-instagram',
'tiktok' => 'fab fa-tiktok',
'twitch' => 'fab fa-twitch',
'steam' => 'fab fa-steam',
'github' => 'fab fa-github',
'linkedin' => 'fab fa-linkedin-in',
'pinterest' => 'fab fa-pinterest-p',
'reddit' => 'fab fa-reddit-alien',
'teamspeak' => 'fab fa-teamspeak',
'spotify' => 'fab fa-spotify'
);
foreach ( $social_icons as $key => $class ) {
if ( get_theme_mod( 'social_' . $key ) ) {
echo '<a href="' . esc_url( get_theme_mod( 'social_' . $key ) ) . '" target="_blank" rel="noopener"><i class="' . esc_attr( $class ) . '"></i></a>';
}
}
?>
</div>
</div>
</div> <!-- .header-main -->
</div> </div>
</header> </header>
<?php elseif ( $menu_style === 'centered' ) : ?>
<!-- ══════════════════════════════════════════════════════════════
LAYOUT 2: ZENTRIERT
═══════════════════════════════════════════════════════════════ -->
<header id="masthead" class="site-header site-header--centered">
<div class="container">
<!-- Zeile 1: Branding + Icons -->
<div class="header-row header-row-branding pos-<?php echo esc_attr( $branding_pos ); ?>">
<?php if ( $branding_pos === 'left' ) : ?>
<?php mm_branding( true ); ?>
<div class="header-spacer"></div>
<?php mm_icons(); ?>
<?php elseif ( $branding_pos === 'right' ) : ?>
<?php mm_icons(); ?>
<div class="header-spacer"></div>
<?php mm_branding( true ); ?>
<?php else : ?>
<div class="header-spacer"></div>
<?php mm_branding( true ); ?>
<?php mm_icons(); ?>
<?php endif; ?>
</div>
<!-- Zeile 2: Navigation zentriert -->
<div class="header-row header-row-nav">
<?php mm_nav(); ?>
</div>
</div>
</header>
<?php elseif ( $menu_style === 'mega' ) : ?>
<!-- ══════════════════════════════════════════════════════════════
LAYOUT 4: MEGA-MENÜ
═══════════════════════════════════════════════════════════════ -->
<header id="masthead" class="site-header site-header--mega">
<div class="container">
<!-- Zeile 1: Branding + Icons -->
<div class="header-row header-row-branding pos-<?php echo esc_attr( $branding_pos ); ?>">
<?php if ( $branding_pos === 'left' ) : ?>
<?php mm_branding( true ); ?>
<div class="header-spacer"></div>
<?php mm_icons(); ?>
<?php elseif ( $branding_pos === 'right' ) : ?>
<?php mm_icons(); ?>
<div class="header-spacer"></div>
<?php mm_branding( true ); ?>
<?php else : ?>
<div class="header-spacer"></div>
<?php mm_branding( true ); ?>
<?php mm_icons(); ?>
<?php endif; ?>
</div>
</div>
<!-- Zeile 2: Menü volle Breite -->
<div class="header-mega-nav-bar">
<div class="container">
<?php mm_nav('mega-nav'); ?>
</div>
</div>
</header>
<?php else : ?>
<!-- ══════════════════════════════════════════════════════════════
LAYOUT 1: CLASSIC
═══════════════════════════════════════════════════════════════ -->
<header id="masthead" class="site-header site-header--classic">
<div class="container">
<?php if ( $branding_pos === 'center' ) : ?>
<!-- Center: Branding oben zentriert, Menü darunter -->
<div class="header-row header-row-branding pos-center">
<div class="header-spacer"></div>
<?php mm_branding( true ); ?>
<?php mm_icons(); ?>
</div>
<div class="header-row header-row-nav">
<?php mm_nav(); ?>
</div>
<?php elseif ( $branding_pos === 'right' ) : ?>
<!-- Right: Icons + Nav links, Branding rechts -->
<div class="header-main">
<?php mm_nav(); ?>
<div class="header-spacer"></div>
<?php mm_icons(); ?>
<?php mm_branding( true ); ?>
</div>
<?php else : ?>
<!-- Left: Branding links, Nav Mitte, Icons rechts -->
<div class="header-main">
<?php mm_branding( true ); ?>
<?php mm_nav(); ?>
<?php mm_icons(); ?>
</div>
<?php endif; ?>
</div>
</header>
<?php endif; ?>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,913 @@
<?php
// BUG-FIX: Das 'assistant_position' Setting wurde hier in einem eigenen
// customize_register-Hook registriert, BEVOR customizer.php die Section
// 'assistant_settings' anlegt. Das führte dazu, dass das Control keiner
// sichtbaren Section zugeordnet war.
// Die Registrierung wurde in inc/customizer.php verschoben (direkt nach
// den anderen assistant_*-Controls). Dieser Hook-Block kann daher entfallen.
/**
* Assistant Widget Virtueller Support-Assistent
*
* Vollständig integriert mit allen Plugins:
* → LiteBans Manager
* → MC Player History
* → BungeeCord Status
* → Multi Rules
* → WP Multi Wiki
* → WP Ingame Shop Pro
* → WP Multi Ticket Pro
* → WP Business Forum
* → MC MultiServer Gallery PRO
* → FAQ (Custom Post Type)
*
* @version 3.1
* @author M_Viper
*
* WICHTIG: Die eigentliche Antwort-Logik liegt in assistant-ajax.php.
* Diese Datei enthält nur: Admin-Backend, Widget-Frontend, JS.
*/
if ( ! defined( 'ABSPATH' ) ) exit;
// =========================================================================
// AJAX-Handler laden (assistant-ajax.php)
// =========================================================================
$mm_ajax_file = get_template_directory() . '/inc/assistant-ajax.php';
if ( file_exists( $mm_ajax_file ) ) {
require_once $mm_ajax_file;
}
// =========================================================================
// 1. ADMIN-BACKEND BOT-ZENTRALE
// =========================================================================
add_action( 'admin_menu', function () {
// Dashicon für Chatbot verwenden (z.B. dashicons-format-chat)
add_menu_page(
'Bot Setup',
'Bot Setup',
'manage_options',
'bot-setup',
'mm_render_bot_admin',
'dashicons-format-chat',
65
);
} );
add_action( 'admin_init', function () {
register_setting( 'mm_bot_settings', 'mm_bot_data', [
'sanitize_callback' => 'mm_bot_sanitize_settings',
] );
} );
function mm_bot_sanitize_settings( $input ) {
$clean = [];
$text_fields = [ 'server_ip', 'server_ver', 'server_specs', 'bot_name', 'welcome' ];
$url_fields = [
'url_wiki', 'url_rules', 'url_tickets', 'url_faq',
'url_team', 'url_shop', 'url_gallery', 'url_player_history',
'url_forum', 'link_discord', 'litebans_dashboard_url',
];
foreach ( $text_fields as $f ) {
$clean[ $f ] = isset( $input[ $f ] ) ? sanitize_text_field( $input[ $f ] ) : '';
}
foreach ( $url_fields as $f ) {
$clean[ $f ] = isset( $input[ $f ] ) ? esc_url_raw( $input[ $f ] ) : '';
}
$clean['qa'] = [];
if ( ! empty( $input['qa'] ) && is_array( $input['qa'] ) ) {
foreach ( $input['qa'] as $item ) {
if ( empty( $item['keys'] ) ) continue;
$clean['qa'][] = [
'keys' => sanitize_text_field( $item['keys'] ),
'val' => wp_kses_post( $item['val'] ),
];
}
}
return $clean;
}
// =========================================================================
// 2. ADMIN-SEITE
// =========================================================================
// BUG-FIX: Funktion außerhalb von mm_render_bot_admin() definieren.
// Innerhalb einer Funktion definierte benannte Funktionen erzeugen einen
// Fatal Error "Cannot redeclare", wenn die äußere Funktion jemals ein
// zweites Mal ausgeführt wird (z.B. durch AJAX, REST, bestimmte Plugins).
if ( ! function_exists( 'mm_find_page_by_shortcode' ) ) :
function mm_find_page_by_shortcode( $shortcodes ) {
global $wpdb;
$conditions = [];
foreach ( (array) $shortcodes as $sc ) {
$conditions[] = $wpdb->prepare( 'post_content LIKE %s', '%[' . $wpdb->esc_like( $sc ) . '%' );
}
if ( empty( $conditions ) ) return '';
$where = implode( ' OR ', $conditions );
$page = $wpdb->get_row(
"SELECT ID FROM {$wpdb->posts}
WHERE post_status = 'publish'
AND post_type IN ('page','post')
AND ({$where})
LIMIT 1"
);
return $page ? get_permalink( $page->ID ) : '';
}
endif;
function mm_render_bot_admin() {
$data = get_option( 'mm_bot_data', [] );
?>
<div class="wrap">
<h1><span class="dashicons dashicons-robot"></span> Bot-Zentrale</h1>
<style>
.bot-card { background:#fff; padding:20px; border:1px solid #ccd0d4; border-radius:8px; margin-top:20px; box-shadow:0 2px 4px rgba(0,0,0,.05); }
.bot-card h2 { margin-top:0; color:#0073aa; border-bottom:1px solid #eee; padding-bottom:12px; }
.bot-card p.description { color:#666; font-style:italic; margin-top:4px; }
.badge { display:inline-block; background:#0099ff; color:#fff; font-size:11px; padding:1px 7px; border-radius:10px; margin-left:6px; vertical-align:middle; }
.badge.inactive { background:#999; }
</style>
<form method="post" action="options.php">
<?php settings_fields( 'mm_bot_settings' ); ?>
<!-- KARTE 1: Server -->
<div class="bot-card">
<h2>1. Server-Infos</h2>
<table class="form-table">
<tr>
<th><label>Server-IP / Adresse</label></th>
<td>
<input type="text" name="mm_bot_data[server_ip]" value="<?php echo esc_attr( $data['server_ip'] ?? '' ); ?>" class="regular-text" placeholder="play.example.net">
<p class="description">Wird bei Fragen nach der Server-IP angezeigt.</p>
</td>
</tr>
<tr>
<th><label>Minecraft-Version</label></th>
<td><input type="text" name="mm_bot_data[server_ver]" value="<?php echo esc_attr( $data['server_ver'] ?? '' ); ?>" class="small-text" placeholder="1.21.1"></td>
</tr>
<tr>
<th><label>Hardware / Specs</label></th>
<td>
<input type="text" name="mm_bot_data[server_specs]" value="<?php echo esc_attr( $data['server_specs'] ?? '' ); ?>" class="large-text" placeholder="z.B. Ryzen 9, 64 GB RAM, NVMe SSD">
<p class="description">Wird bei Fragen nach der Server-Hardware angezeigt.</p>
</td>
</tr>
</table>
</div>
<!-- KARTE 2: Bot-Einstellungen -->
<div class="bot-card">
<h2>2. Bot-Einstellungen</h2>
<table class="form-table">
<tr>
<th><label>Bot-Name</label></th>
<td><input type="text" name="mm_bot_data[bot_name]" value="<?php echo esc_attr( $data['bot_name'] ?? '' ); ?>" class="regular-text" placeholder="Viper-Bot"></td>
</tr>
<tr>
<th><label>Minecraft-UUID / Name (Avatar)</label></th>
<td>
<?php
$uuid = get_theme_mod( 'assistant_minecraft_uuid', 'Steve' );
?>
<input type="text" name="assistant_mc_uuid_preview" value="<?php echo esc_attr( $uuid ); ?>" class="regular-text" disabled>
<p class="description">UUID / Name wird im Theme-Customizer unter "Assistent" gesetzt.</p>
</td>
</tr>
<tr>
<th><label>Begrüßungstext</label></th>
<td>
<textarea name="mm_bot_data[welcome]" rows="3" class="large-text"><?php echo esc_textarea( $data['welcome'] ?? '' ); ?></textarea>
<p class="description">Erster Text der beim Öffnen des Assistenten angezeigt wird.</p>
</td>
</tr>
</table>
</div>
<!-- KARTE 3: Links -->
<div class="bot-card">
<h2>3. Wichtige Links</h2>
<p class="description" style="margin-bottom:12px;">
💡 Wenn eine Seite mit dem passenden Shortcode gefunden wird, erscheint ein <strong>„Vorschlag übernehmen"</strong>-Button.
</p>
<?php
$autodetect = [
'url_wiki' => mm_find_page_by_shortcode( ['wmw_wiki', 'wmw_search', 'wmw_article'] ),
'url_rules' => mm_find_page_by_shortcode( ['mrp_rules', 'mc_rules', 'multi_rules'] ),
'url_tickets' => mm_find_page_by_shortcode( ['wmtp_tickets', 'wm_tickets', 'multi_ticket'] ),
'url_shop' => mm_find_page_by_shortcode( ['wis_shop', 'ingame_shop', 'wis_items'] ),
'url_gallery' => mm_find_page_by_shortcode( ['mc_gallery', 'mc_gallery_overview', 'mc_gallery_upload', 'mc_gallery_all_albums'] ),
'url_faq' => mm_find_page_by_shortcode( ['faq_list', 'faq', 'faq_page'] ),
'url_player_history' => mm_find_page_by_shortcode( ['mc_player_history', 'mc_players', 'player_history'] ),
'url_forum' => mm_find_page_by_shortcode( ['business_forum'] ),
'litebans_dashboard_url' => mm_find_page_by_shortcode( ['litebans_dashboard', 'litebans', 'wp_litebans'] ),
];
$links = [
'url_wiki' => [ 'label' => '📖 Wiki-URL', 'plugin' => 'WP Multi Wiki', 'active' => post_type_exists( 'wmw_article' ) ],
'url_rules' => [ 'label' => '📜 Regelwerk-URL', 'plugin' => 'Multi Rules', 'active' => function_exists( 'mrp_get_plugin_version' ) ],
'url_tickets' => [ 'label' => '🎫 Ticket/Support-URL', 'plugin' => 'WP Multi Ticket Pro', 'active' => class_exists( 'WP_Multi_Ticket_Pro' ) ],
'url_shop' => [ 'label' => '🛒 Shop-URL', 'plugin' => 'WP Ingame Shop Pro', 'active' => class_exists( 'WIS_Activator' ) ],
'url_gallery' => [ 'label' => '📷 Galerie-URL', 'plugin' => 'MC MultiServer Gallery PRO', 'active' => post_type_exists( 'mc_gallery' ) ],
'url_faq' => [ 'label' => '❓ FAQ-URL', 'plugin' => 'FAQ Post Type', 'active' => post_type_exists( 'faq' ) ],
'url_player_history' => [ 'label' => '👤 Spieler-History-URL', 'plugin' => 'MC Player History', 'active' => function_exists( 'mcph_get_plugin_version' ) ],
'url_forum' => [ 'label' => '💬 Forum-URL', 'plugin' => 'WP Business Forum', 'active' => class_exists( 'WBF_DB' ) ],
'url_team' => [ 'label' => '👥 Team-URL', 'plugin' => '', 'active' => true ],
'link_discord' => [ 'label' => '💬 Discord-Einladung', 'plugin' => '', 'active' => true ],
'litebans_dashboard_url' => [ 'label' => '🔨 LiteBans Dashboard-URL', 'plugin' => 'LiteBans Manager', 'active' => class_exists( 'WP_LiteBans_Pro' ) ],
];
?>
<style>
.mm-url-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mm-url-row input.large-text { flex: 1; min-width: 200px; }
.mm-suggest-btn {
background: #e8f5e9; color: #2e7d32; border: 1px solid #a5d6a7;
border-radius: 4px; padding: 5px 10px; font-size: 12px;
cursor: pointer; white-space: nowrap; line-height: 1.4;
transition: background .2s;
}
.mm-suggest-btn:hover { background: #c8e6c9; }
.mm-suggest-url { font-size: 11px; color: #666; max-width: 260px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
display: inline-block; vertical-align: middle; }
.mm-autofill-all {
margin-bottom: 12px; background: #0073aa; color: #fff;
border: none; border-radius: 4px; padding: 7px 16px;
cursor: pointer; font-size: 13px;
}
.mm-autofill-all:hover { background: #005f8d; }
</style>
<?php
$has_any_suggestion = ! empty( array_filter( $autodetect ) );
if ( $has_any_suggestion ) : ?>
<button type="button" class="mm-autofill-all" id="mm-autofill-all">
⚡ Alle erkannten URLs automatisch übernehmen
</button>
<?php endif; ?>
<table class="form-table">
<?php foreach ( $links as $key => $cfg ) :
$badge = '';
if ( $cfg['plugin'] ) {
$cls = $cfg['active'] ? 'badge' : 'badge inactive';
$txt = $cfg['active'] ? 'aktiv' : 'inaktiv';
$badge = '<span class="' . $cls . '">' . esc_html( $cfg['plugin'] ) . ' ' . $txt . '</span>';
}
$saved = $data[ $key ] ?? '';
$suggested = $autodetect[ $key ] ?? '';
$show_suggest = $suggested && $suggested !== $saved;
?>
<tr>
<th><label for="mm_link_<?php echo esc_attr( $key ); ?>"><?php echo $cfg['label'] . $badge; ?></label></th>
<td>
<div class="mm-url-row">
<input type="url"
id="mm_link_<?php echo esc_attr( $key ); ?>"
name="mm_bot_data[<?php echo esc_attr( $key ); ?>]"
value="<?php echo esc_url( $saved ); ?>"
class="large-text"
data-key="<?php echo esc_attr( $key ); ?>">
<?php if ( $show_suggest ) : ?>
<button type="button"
class="mm-suggest-btn"
data-target="mm_link_<?php echo esc_attr( $key ); ?>"
data-url="<?php echo esc_url( $suggested ); ?>">
✓ Vorschlag übernehmen
</button>
<span class="mm-suggest-url" title="<?php echo esc_attr( $suggested ); ?>">
<?php echo esc_html( $suggested ); ?>
</span>
<?php elseif ( $saved && $suggested && $suggested === $saved ) : ?>
<span style="color:#2e7d32;font-size:12px;">✔ Erkannt &amp; gesetzt</span>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</table>
</div>
<!-- KARTE 4: Individuelle Q&A -->
<div class="bot-card">
<h2>4. Individuelle Q&A</h2>
<p class="description">
Schlüsselwörter (kommagetrennt) → Antwort. Hat höchste Priorität vor allen Plugin-Abfragen.
</p>
<table class="form-table" style="width:100%;">
<tr>
<th style="width:200px;">Schlüsselwörter</th>
<th>Antwort (HTML erlaubt)</th>
<th style="width:50px;"></th>
</tr>
</table>
<div id="mm-qa-rows">
<?php
$qa = $data['qa'] ?? [];
foreach ( $qa as $i => $item ) :
if ( empty( $item['keys'] ) ) continue;
?>
<div class="qa-item" style="display:flex;gap:10px;margin-bottom:10px;align-items:center;">
<input type="text" name="mm_bot_data[qa][<?php echo $i; ?>][keys]"
value="<?php echo esc_attr( $item['keys'] ); ?>"
style="flex:1;" placeholder="discord, invite, join discord">
<input type="text" name="mm_bot_data[qa][<?php echo $i; ?>][val]"
value="<?php echo esc_attr( $item['val'] ); ?>"
style="flex:2;" placeholder="Antworttext oder HTML">
<button type="button" class="button remove-qa" title="Entfernen">✕</button>
</div>
<?php endforeach; ?>
</div>
<button type="button" id="add-qa" class="button button-primary">+ Neue Q&amp;A-Zeile</button>
</div>
<?php submit_button( 'Einstellungen speichern' ); ?>
</form>
</div>
<script>
jQuery(function($){
// Q&A Zeilen
$('#add-qa').on('click', function(){
var i = Date.now();
$('#mm-qa-rows').append(
'<div class="qa-item" style="display:flex;gap:10px;margin-bottom:10px;align-items:center;">' +
'<input type="text" name="mm_bot_data[qa]['+i+'][keys]" style="flex:1;" placeholder="discord, invite">' +
'<input type="text" name="mm_bot_data[qa]['+i+'][val]" style="flex:2;" placeholder="Antwort...">' +
'<button type="button" class="button remove-qa" title="Entfernen">✕</button>' +
'</div>'
);
});
$(document).on('click', '.remove-qa', function(){
$(this).closest('.qa-item').remove();
});
// Einzelnen Vorschlag übernehmen
$(document).on('click', '.mm-suggest-btn', function(){
var $btn = $(this);
var target = $btn.data('target');
var url = $btn.data('url');
$('#' + target).val(url);
$btn.replaceWith('<span style="color:#2e7d32;font-size:12px;">✔ Übernommen</span>');
$(this).siblings('.mm-suggest-url').remove();
});
// Alle auf einmal übernehmen
$('#mm-autofill-all').on('click', function(){
$('.mm-suggest-btn').each(function(){
var $btn = $(this);
var target = $btn.data('target');
var url = $btn.data('url');
if ( url && ! $('#' + target).val() ) {
$('#' + target).val(url);
$btn.closest('.mm-url-row').find('.mm-suggest-url').remove();
$btn.replaceWith('<span style="color:#2e7d32;font-size:12px;">✔ Übernommen</span>');
}
});
$(this).prop('disabled', true).text('✔ Fertig bitte speichern');
});
});
</script>
<?php
}
// =========================================================================
// 3. ASSETS LADEN
// =========================================================================
add_action( 'wp_enqueue_scripts', function () {
// CSS aus Theme-Ordner (falls vorhanden), sonst Inline-Fallback
$css_file = get_template_directory() . '/css/assistant-widget.css';
if ( file_exists( $css_file ) ) {
wp_enqueue_style(
'mm-assistant-widget',
get_template_directory_uri() . '/css/assistant-widget.css',
[],
'3.1'
);
}
} );
// =========================================================================
// 4. FRONTEND WIDGET
// =========================================================================
add_action( 'wp_footer', 'mm_bot_render_widget', 50 );
function mm_bot_render_widget() {
// Sichtbarkeit prüfen: Assistent nur anzeigen, wenn im Customizer aktiviert
// BUG-FIX: Early return MUSS als erstes kommen, bevor andere Variablen gelesen werden.
if ( ! get_theme_mod( 'assistant_enabled', false ) ) {
return;
}
$data = get_option( 'mm_bot_data', [] );
$uuid = sanitize_text_field( trim( get_theme_mod( 'assistant_minecraft_uuid', 'Steve' ) ) );
// BUG-FIX: $avatar_view muss VOR $body_offset definiert werden.
$avatar_view = get_theme_mod( 'assistant_avatar_view', 'head' );
// Offset für Chat-Fenster, wenn Ganzkörper-Avatar aktiv ist
$body_offset = ( $avatar_view === 'body' ) ? 'margin-right: 60px;' : '';
$name = ! empty( $data['bot_name'] ) ? esc_html( $data['bot_name'] ) : 'Viper-Bot';
$welcome = ! empty( $data['welcome'] ) ? $data['welcome'] : 'Hallo! Wie kann ich dir helfen? 👋';
$nonce = wp_create_nonce( 'mm_bot_nonce' );
$ajax = admin_url( 'admin-ajax.php' );
// ── Quick-Buttons: nur anzeigen wenn Plugin aktiv + URL gesetzt ──────
$quick = [];
// Server-Status (immer wenn IP konfiguriert oder BungeeCord-Plugin aktiv)
$servers = get_option( 'mcss_servers', [] );
if ( ! empty( $data['server_ip'] ) || ! empty( $servers ) ) {
$quick[] = [ 'label' => '🖥️ Server-Status', 'q' => 'server status' ];
}
// Regeln nur wenn URL im Backend gesetzt
if ( ! empty( $data['url_rules'] ) ) {
$quick[] = [ 'label' => '📜 Regelwerk', 'q' => 'regeln' ];
}
// Wiki nur wenn URL im Backend gesetzt
if ( ! empty( $data['url_wiki'] ) ) {
$quick[] = [ 'label' => '📖 Wiki', 'q' => 'wiki' ];
}
// Shop nur wenn URL im Backend gesetzt
global $wpdb;
if ( ! empty( $data['url_shop'] ) ) {
$quick[] = [ 'label' => '🛒 Shop', 'q' => 'shop' ];
}
// Ticket / Support nur wenn URL im Backend gesetzt
if ( ! empty( $data['url_tickets'] ) ) {
$quick[] = [ 'label' => '🎫 Support-Ticket', 'q' => 'ticket erstellen' ];
}
// Forum
if ( class_exists( 'WBF_DB' ) && ! empty( $data['url_forum'] ) ) {
$quick[] = [ 'label' => '💬 Forum', 'q' => 'forum' ];
}
// Galerie nur wenn URL im Backend gesetzt
if ( ! empty( $data['url_gallery'] ) ) {
$quick[] = [ 'label' => '📷 Galerie', 'q' => 'galerie' ];
}
// Ban-Status
$lb = get_option( 'wp_litebans_pro_settings', [] );
if ( ! empty( $lb['db_name'] ) ) {
$quick[] = [ 'label' => '🔨 Strafen prüfen', 'q' => 'meine strafen' ];
}
// Spieler-History nur wenn URL im Backend gesetzt
if ( ! empty( $data['url_player_history'] ) ) {
$quick[] = [ 'label' => '⏱️ Spielzeit', 'q' => 'spielzeit' ];
}
// Discord
if ( ! empty( $data['link_discord'] ) ) {
$quick[] = [ 'label' => '💬 Discord', 'q' => 'discord' ];
}
?>
<?php
$assistant_position = get_theme_mod( 'assistant_position', 'bottom_right' );
$pos_class = 'mm-bot-pos-' . esc_attr( $assistant_position );
?>
<div id="mm-bot-root" class="<?php echo $pos_class; ?>">
<!-- Chat-Fenster -->
<div id="mm-bot-chat" style="display:none;<?php echo $body_offset; ?>" aria-label="Assistent" role="dialog">
<!-- Header -->
<div class="mm-bot-header">
<div class="mm-bot-status" title="Online"></div>
<?php
// Avatar-URL je nach Ansicht
if ($avatar_view === 'body') {
$avatar_url = 'https://mc-heads.net/body/' . rawurlencode($uuid) . '/32';
} elseif ($avatar_view === '3dhead') {
$avatar_url = 'https://visage.surgeplay.com/head/48/' . rawurlencode($uuid);
} else {
$avatar_url = 'https://mc-heads.net/avatar/' . rawurlencode($uuid) . '/32';
}
?>
<img class="mm-bot-avatar-small" src="<?php echo esc_url($avatar_url); ?>" alt="<?php echo $name; ?>">
<span class="mm-bot-title"><?php echo $name; ?></span>
<button id="mm-bot-close" aria-label="Schließen">&times;</button>
</div>
<!-- Nachrichten -->
<div id="mm-bot-content" role="log" aria-live="polite">
<div class="mm-msg bot">
<?php echo nl2br( wp_kses_post( $welcome ) ); ?>
</div>
<?php if ( ! empty( $quick ) ) : ?>
<div class="mm-bot-quick">
<?php foreach ( $quick as $btn ) : ?>
<button class="mm-quick-btn"
data-q="<?php echo esc_attr( $btn['q'] ); ?>"
type="button">
<?php echo esc_html( $btn['label'] ); ?>
</button>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Eingabe -->
<div class="mm-bot-input-area">
<input type="text"
id="mm-bot-field"
placeholder="Deine Frage eingeben…"
autocomplete="off"
maxlength="300"
aria-label="Nachricht eingeben">
<button id="mm-bot-send" type="button" aria-label="Senden">➤</button>
</div>
</div>
<!-- Launcher-Button -->
<button id="mm-bot-launcher" type="button" aria-label="Assistenten öffnen" title="<?php echo $name; ?>" style="background:transparent;border:none;box-shadow:none;width:auto;height:auto;padding:0;display:flex;align-items:center;justify-content:center;overflow:visible;">
<?php
// Launcher-Avatar-URL je nach Ansicht (größer)
if ($avatar_view === 'body') {
$launcher_url = 'https://mc-heads.net/body/' . rawurlencode($uuid) . '/144';
$img_style = 'width:72px;height:144px;object-fit:contain;';
if ($assistant_position === 'bottom_right' || $assistant_position === 'top_right') {
$img_style .= 'transform:scaleX(-1);';
}
} elseif ($avatar_view === '3dhead') {
$launcher_url = 'https://visage.surgeplay.com/head/80/' . rawurlencode($uuid);
$img_style = 'width:60px;height:60px;object-fit:cover;';
if ($assistant_position === 'bottom_left' || $assistant_position === 'top_left') {
$img_style .= 'transform:scaleX(-1);';
}
} else {
$launcher_url = 'https://mc-heads.net/avatar/' . rawurlencode($uuid) . '/60';
$img_style = 'width:60px;height:60px;object-fit:cover;';
}
?>
<img src="<?php echo esc_url($launcher_url); ?>" alt="<?php echo $name; ?>" style="<?php echo $img_style; ?>border-radius:8px;background:none;box-shadow:none;">
</button>
</div>
<script>
(function($){
'use strict';
var $chat = $('#mm-bot-chat');
var $field = $('#mm-bot-field');
var $content = $('#mm-bot-content');
var nonce = <?php echo wp_json_encode( $nonce ); ?>;
var ajaxUrl = <?php echo wp_json_encode( $ajax ); ?>;
var isLoading = false;
// ── Öffnen / Schließen ────────────────────────────────────
$('#mm-bot-launcher').on('click', function(){
$chat.fadeToggle(200);
if ($chat.is(':visible')) {
$field.trigger('focus');
scrollBottom();
}
});
$('#mm-bot-close').on('click', function(){
$chat.fadeOut(200);
});
// ── Quick-Buttons ─────────────────────────────────────────
$(document).on('click', '.mm-quick-btn', function(){
sendMessage( $(this).data('q') );
});
// ── Senden ────────────────────────────────────────────────
$('#mm-bot-send').on('click', function(){ sendMessage(); });
$field.on('keydown', function(e){
if (e.which === 13 && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
function sendMessage(forceText) {
if (isLoading) return;
var val = forceText !== undefined
? String(forceText).trim()
: $field.val().trim();
if (!val) return;
// Nutzernachricht anzeigen
appendMsg('user', $('<span>').text(val).html());
if (forceText === undefined) $field.val('');
// Loader
isLoading = true;
var $loader = $('<div class="mm-msg bot mm-loading" aria-label="Lädt">···</div>');
$content.append($loader);
scrollBottom();
$.ajax({
url: ajaxUrl,
method: 'POST',
dataType: 'json',
data: {
action: 'mm_assistant_query', // ← Neuer Action-Name
q: val,
nonce: nonce
},
success: function(res){
$loader.remove();
isLoading = false;
var text;
if (res && res.success && res.data && res.data.reply) {
// Neues Format: {reply: '...', parts: [...]}
text = res.data.reply;
} else if (res && res.success && typeof res.data === 'string') {
// Altes Format: direkter String (Fallback)
text = res.data;
} else {
text = '⚠️ Keine Antwort erhalten.';
}
appendMsg('bot', text);
},
error: function(){
$loader.remove();
isLoading = false;
appendMsg('bot', '⚠️ Verbindungsfehler. Bitte versuche es erneut.');
}
});
}
function appendMsg(type, html) {
var $msg = $('<div class="mm-msg ' + type + '">').html(html);
$content.append($msg);
scrollBottom();
}
function scrollBottom() {
$content.scrollTop($content[0].scrollHeight);
}
})(jQuery);
</script>
<style>
/* ── Wrapper ── */
#mm-bot-root {
position: fixed;
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
/* Positionsklassen */
#mm-bot-root.mm-bot-pos-bottom_right { bottom: 30px; right: 100px; }
#mm-bot-root.mm-bot-pos-bottom_left { bottom: 90px; left: 30px; }
#mm-bot-root.mm-bot-pos-top_right { top: 30px; right: 30px; }
#mm-bot-root.mm-bot-pos-top_left { top: 30px; left: 30px; }
/* Chat-Fenster dynamisch positionieren */
#mm-bot-root.mm-bot-pos-bottom_right #mm-bot-chat { bottom: 78px; right: 0; left: auto; top: auto; }
#mm-bot-root.mm-bot-pos-bottom_left #mm-bot-chat { bottom: 78px; left: 0; right: auto; top: auto; }
#mm-bot-root.mm-bot-pos-top_right #mm-bot-chat { top: 78px; right: 0; left: auto; bottom: auto; }
#mm-bot-root.mm-bot-pos-top_left #mm-bot-chat { top: 78px; left: 0; right: auto; bottom: auto; }
/* ── Launcher ── */
#mm-bot-launcher {
background: transparent !important;
border: none !important;
box-shadow: none !important;
width: auto !important;
height: auto !important;
padding: 0 !important;
display: flex;
align-items: center;
justify-content: center;
overflow: visible;
border-radius: 0 !important;
cursor: pointer;
transition: none;
}
#mm-bot-launcher img {
width: 60px;
height: 60px;
border-radius: 8px;
background: none !important;
box-shadow: none !important;
}
/* ── Chat-Fenster ── */
#mm-bot-chat {
position: absolute;
bottom: 78px;
right: 0;
width: 350px;
background: #1e2124;
border-radius: 16px;
box-shadow: 0 16px 48px rgba(0,0,0,.55);
display: flex;
flex-direction: column;
overflow: hidden;
border: 1px solid #2a2d31;
}
/* ── Header ── */
.mm-bot-header {
background: #2f3136;
color: #fff;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid #1a1a1a;
}
.mm-bot-status {
width: 10px;
height: 10px;
background: #43b581;
border-radius: 50%;
box-shadow: 0 0 8px #43b581;
flex-shrink: 0;
}
.mm-bot-avatar-small {
width: 28px;
height: 28px;
border-radius: 4px;
}
.mm-bot-title {
font-weight: 700;
font-size: 15px;
flex: 1;
}
#mm-bot-close {
background: none;
border: none;
color: #888;
font-size: 22px;
cursor: pointer;
line-height: 1;
padding: 0 2px;
transition: color .2s;
}
#mm-bot-close:hover { color: #fff; }
/* ── Nachrichten ── */
#mm-bot-content {
height: 340px;
padding: 14px 14px 6px;
overflow-y: auto;
background: #36393f;
display: flex;
flex-direction: column;
gap: 10px;
scroll-behavior: smooth;
}
.mm-msg {
padding: 10px 14px;
border-radius: 14px;
font-size: 13.5px;
line-height: 1.55;
max-width: 90%;
word-break: break-word;
}
.mm-msg.bot {
background: #40444b;
color: #dcddde;
align-self: flex-start;
border-bottom-left-radius: 4px;
}
.mm-msg.user {
background: #0099ff;
color: #fff;
align-self: flex-end;
border-bottom-right-radius: 4px;
}
.mm-msg a {
color: #5bc0eb;
text-decoration: none;
font-weight: 600;
}
.mm-msg a:hover { text-decoration: underline; }
.mm-msg code {
background: #1a1a1a;
color: #ffa500;
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 12.5px;
}
.mm-msg hr {
border: 0;
border-top: 1px solid rgba(255,255,255,.12);
margin: 8px 0;
}
.mm-msg img {
max-width: 100%;
border-radius: 6px;
margin-top: 6px;
display: block;
}
.mm-msg small { opacity: .75; font-size: 12px; }
.mm-msg b { color: #fff; }
.mm-msg.bot b { color: #e3e4e6; }
/* TinyMCE Regelwerk-Inhalt im Chat */
.mm-msg p { margin: 4px 0; }
.mm-msg ul, .mm-msg ol {
margin: 4px 0 4px 16px;
padding: 0;
}
.mm-msg li { margin-bottom: 2px; list-style: disc; }
.mm-msg ol li { list-style: decimal; }
.mm-msg strong, .mm-msg b { font-weight: 700; }
.mm-msg em { font-style: italic; }
.mm-msg h1, .mm-msg h2, .mm-msg h3,
.mm-msg h4, .mm-msg h5, .mm-msg h6 {
margin: 6px 0 4px;
font-size: 14px;
color: #e3e4e6;
}
.mm-loading {
opacity: .55;
font-size: 22px;
letter-spacing: 5px;
padding: 6px 14px;
}
/* ── Quick-Buttons ── */
.mm-bot-quick {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 2px;
}
.mm-quick-btn {
background: #2f3136;
color: #b9bbbe;
border: 1px solid #3f4147;
border-radius: 20px;
padding: 5px 12px;
font-size: 12px;
cursor: pointer;
transition: background .2s, color .2s, border-color .2s;
white-space: nowrap;
}
.mm-quick-btn:hover {
background: #0099ff;
color: #fff;
border-color: #0099ff;
}
/* ── Eingabe ── */
.mm-bot-input-area {
padding: 10px 12px;
background: #2f3136;
display: flex;
gap: 8px;
border-top: 1px solid #1a1a1a;
}
#mm-bot-field {
flex: 1;
background: #40444b;
border: 1px solid #1a1a1a;
border-radius: 8px;
color: #fff;
padding: 9px 12px;
outline: none;
font-size: 13.5px;
transition: border-color .2s;
}
#mm-bot-field::placeholder { color: #72767d; }
#mm-bot-field:focus { border-color: #0099ff; }
#mm-bot-send {
background: #0099ff;
border: none;
color: #fff;
border-radius: 8px;
padding: 0 16px;
cursor: pointer;
font-size: 16px;
transition: background .2s;
flex-shrink: 0;
}
#mm-bot-send:hover { background: #00b0f4; }
/* ── Responsive ── */
@media (max-width: 420px) {
#mm-bot-root { bottom: 16px; right: 12px; }
#mm-bot-chat { width: calc(100vw - 24px); right: -12px; }
}
</style>
<?php
}

View File

@@ -1,478 +1,465 @@
<?php <?php
/** /**
* Minecraft Modern Theme - Customizer Settings * Minecraft Modern Theme - Customizer Settings
* * FIXES:
* Enthält Einstellungen für: * - Google Font via wp_enqueue_style() statt <link> direkt in wp_head
* - Header Slider & Hero * - slider_loop Setting ergänzt (war in slider-init.js referenziert, aber nie definiert)
* - Theme Presets (Nether, End, Classic) * - Scroll-to-Top Toggle ergänzt
* - Farben & Darstellung * - footer_copyright Default mit aktuellem Jahr zur Laufzeit
* - Social Media * - Import_Export_Control Klasse aus dem Callback extrahiert
* - Footer
* - Login
* - Sidebar
* - Import/Export
*/ */
// =========================================================================
// Import/Export Control außerhalb des Callbacks definiert
// =========================================================================
if ( class_exists('WP_Customize_Control') && ! class_exists('MM_Import_Export_Control') ) :
class MM_Import_Export_Control extends WP_Customize_Control {
public $type = 'mm_import_export';
public function render_content() {
$export_url = admin_url('admin-post.php?action=export_theme_settings');
$nonce = wp_create_nonce('theme-import-nonce');
?>
<div class="mm-import-export-wrapper">
<p class="description" style="margin-bottom:16px;">
<strong><?php _e('Hinweis:', 'minecraft-modern-theme'); ?></strong>
<?php _e('Hier kannst du alle Theme-Einstellungen und Inhalte sichern und wiederherstellen.', 'minecraft-modern-theme'); ?>
</p>
<div style="background:#e7f3ff;border:1px solid #b3d9ff;border-radius:4px;padding:12px;margin-bottom:16px;font-size:12px;line-height:1.5;">
<strong>📦 Was wird gesichert:</strong><br>
✓ Customizer-Einstellungen (Farben, Social Links, Menü-Design, etc.)<br>
✓ Livestream API Keys (YouTube, Twitch)<br>
✓ Homepage-Seite (Titel, Inhalt, Highlight-Bild)<br>
✓ Navigation Menüs inkl. aller Items & Struktur<br>
✓ Widget-Konfigurationen<br>
✓ Team-Mitglieder (mit UUID, Avatar, Banner)<br>
✓ FAQ-Einträge & Kategorien<br>
✓ Custom CSS<br>
✓ Announcement-Bar Einstellungen
</div>
<a href="<?php echo esc_url($export_url); ?>" class="button button-primary" style="display:inline-flex;align-items:center;gap:6px;margin-bottom:20px;">
<span class="dashicons dashicons-download"></span>
<?php _e('Einstellungen exportieren', 'minecraft-modern-theme'); ?>
</a>
<hr style="margin:16px 0;">
<label style="display:block;margin-bottom:8px;font-weight:600;">
<?php _e('Backup wiederherstellen:', 'minecraft-modern-theme'); ?>
</label>
<input type="file" id="mm-import-file" accept=".json" style="width:100%;margin-bottom:10px;">
<button type="button" class="button" id="mm-import-btn" disabled style="display:inline-flex;align-items:center;gap:6px;">
<span class="dashicons dashicons-upload"></span>
<?php _e('Einstellungen importieren', 'minecraft-modern-theme'); ?>
</button>
<p class="description" style="margin-top:12px;padding:10px;background:#fff3cd;border-left:4px solid #ffc107;color:#856404;">
⚠️ <?php _e('Beim Import werden alle aktuellen Einstellungen überschrieben!', 'minecraft-modern-theme'); ?>
</p>
</div>
<script>
(function($){
var ajaxUrl = '<?php echo esc_js(admin_url('admin-ajax.php')); ?>';
var nonce = '<?php echo esc_js($nonce); ?>';
$('#mm-import-file').on('change', function(){
$('#mm-import-btn').prop('disabled', $(this).val() === '');
});
$('#mm-import-btn').on('click', function(){
var file = $('#mm-import-file')[0].files[0];
if (!file) return;
if (!confirm('<?php echo esc_js(__('Alle aktuellen Einstellungen werden überschrieben. Fortfahren?', 'minecraft-modern-theme')); ?>')) return;
var $btn = $(this).prop('disabled', true).text('<?php echo esc_js(__('Importiere…', 'minecraft-modern-theme')); ?>');
var fd = new FormData();
fd.append('import_file', file);
fd.append('action', 'import_theme_settings');
fd.append('nonce', nonce);
$.ajax({ url: ajaxUrl, type: 'POST', data: fd, processData: false, contentType: false,
success: function(r){
if (r.success) { alert('✅ ' + r.data); location.reload(); }
else { alert('❌ ' + r.data); $btn.prop('disabled', false).text('<?php echo esc_js(__('Einstellungen importieren', 'minecraft-modern-theme')); ?>'); }
},
error: function(){ alert('<?php echo esc_js(__('Technischer Fehler.', 'minecraft-modern-theme')); ?>'); $btn.prop('disabled', false); }
});
});
})(jQuery);
</script>
<?php
}
}
endif;
// =========================================================================
// Customizer Register
// =========================================================================
function minecraft_modern_customize_register( $wp_customize ) { function minecraft_modern_customize_register( $wp_customize ) {
// ========================================================================= // =========================================================================
// === 1. HEADER-BEREICH =================================================== // 9. Virtueller Assistent
// ========================================================================= // =========================================================================
// BUG-FIX: add_section() muss VOR add_setting()/add_control() stehen.
// --- Sektion: Header Slider --- // Vorher war add_setting/add_control für 'assistant_avatar_view' aufgerufen
$wp_customize->add_section( 'header_slider', array( // bevor die Section 'assistant_settings' überhaupt registriert war.
'title' => 'Header Slider', $wp_customize->add_section( 'assistant_settings', array(
'priority' => 20, 'title' => __( 'Virtueller Assistent', 'minecraft-modern-theme' ),
'description' => 'Konfiguriere den großen Slider auf der Startseite.', 'priority' => 80,
'description' => __( 'Steuert den virtuellen Assistenten im Frontend. Avatar basiert auf Minecraft-UUID.', 'minecraft-modern-theme' ),
) ); ) );
// Checkbox zum Aktivieren des Sliders $wp_customize->add_setting( 'assistant_enabled', array(
$wp_customize->add_setting( 'slider_enabled', array(
'default' => false, 'default' => false,
'transport' => 'refresh',
'sanitize_callback' => 'wp_validate_boolean', 'sanitize_callback' => 'wp_validate_boolean',
) ); ) );
$wp_customize->add_control( 'slider_enabled', array( $wp_customize->add_control( 'assistant_enabled', array(
'label' => 'Header Slider aktivieren', 'label' => __( 'Virtuellen Assistenten aktivieren', 'minecraft-modern-theme' ),
'section' => 'assistant_settings',
'type' => 'checkbox',
) );
$wp_customize->add_setting( 'assistant_minecraft_uuid', array(
'default' => '',
'sanitize_callback' => 'sanitize_text_field',
) );
$wp_customize->add_control( 'assistant_minecraft_uuid', array(
'label' => __( 'Minecraft UUID für Avatar', 'minecraft-modern-theme' ),
'section' => 'assistant_settings',
'type' => 'text',
'description' => __( 'Gib die Minecraft-UUID für den Avatar des Assistenten ein.', 'minecraft-modern-theme' ),
) );
$wp_customize->add_setting( 'assistant_avatar_view', array(
'default' => 'head',
'sanitize_callback' => function ( $v ) {
return in_array( $v, array( 'head', 'body', '3dhead' ), true ) ? $v : 'head';
},
) );
$wp_customize->add_control( 'assistant_avatar_view', array(
'label' => __( 'Avatar-Ansicht', 'minecraft-modern-theme' ),
'section' => 'assistant_settings',
'type' => 'select',
'choices' => array(
'head' => __( 'Kopf', 'minecraft-modern-theme' ),
'body' => __( 'Ganzkörper', 'minecraft-modern-theme' ),
'3dhead' => __( '3D Kopf', 'minecraft-modern-theme' ),
),
'description' => __( 'Wähle, wie der Minecraft-Avatar im Assistenten angezeigt wird.', 'minecraft-modern-theme' ),
) );
// BUG-FIX: assistant_position war in assistant-widget.php in einem eigenen
// customize_register-Hook registriert also BEVOR diese Section existierte.
// Jetzt korrekt hier innerhalb von minecraft_modern_customize_register().
$wp_customize->add_setting( 'assistant_position', array(
'default' => 'bottom_right',
'transport' => 'refresh',
'sanitize_callback' => function ( $v ) {
$allowed = array( 'bottom_right', 'bottom_left', 'top_right', 'top_left' );
return in_array( $v, $allowed, true ) ? $v : 'bottom_right';
},
) );
$wp_customize->add_control( 'assistant_position', array(
'label' => __( 'Position des Assistenten', 'minecraft-modern-theme' ),
'section' => 'assistant_settings',
'type' => 'select',
'choices' => array(
'bottom_right' => __( 'Unten rechts', 'minecraft-modern-theme' ),
'bottom_left' => __( 'Unten links', 'minecraft-modern-theme' ),
'top_right' => __( 'Oben rechts', 'minecraft-modern-theme' ),
'top_left' => __( 'Oben links', 'minecraft-modern-theme' ),
),
) );
// =========================================================================
// 1. HEADER SLIDER
// =========================================================================
$wp_customize->add_section( 'header_slider', array(
'title' => __('Header Slider', 'minecraft-modern-theme'),
'priority' => 20,
'description' => __('Konfiguriere den großen Slider auf der Startseite.', 'minecraft-modern-theme'),
) );
$wp_customize->add_setting( 'slider_enabled', array( 'default' => false, 'transport' => 'refresh', 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_control( 'slider_enabled', array( 'label' => __('Header Slider aktivieren', 'minecraft-modern-theme'), 'section' => 'header_slider', 'type' => 'checkbox' ) );
// FIX: slider_loop war in slider-init.js als sliderSettings.loop referenziert, aber nie definiert
$wp_customize->add_setting( 'slider_loop', array( 'default' => true, 'transport' => 'refresh', 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_control( 'slider_loop', array(
'label' => __('Slider Endlos-Loop aktivieren', 'minecraft-modern-theme'),
'description' => __('Der Slider springt nach dem letzten Slide wieder zum ersten zurück.', 'minecraft-modern-theme'),
'section' => 'header_slider', 'section' => 'header_slider',
'settings' => 'slider_enabled',
'type' => 'checkbox', 'type' => 'checkbox',
) ); ) );
// Dynamische Slider-Bilder, Titel und Untertitel for ( $i = 1; $i <= 5; $i++ ) {
for ($i = 1; $i <= 5; $i++) {
$wp_customize->add_setting( 'slider_image_' . $i, array( 'sanitize_callback' => 'esc_url_raw' ) ); $wp_customize->add_setting( 'slider_image_' . $i, array( 'sanitize_callback' => 'esc_url_raw' ) );
$wp_customize->add_control( new WP_Customize_Image_Control( $wp_customize, 'slider_image_' . $i, array( $wp_customize->add_control( new WP_Customize_Image_Control( $wp_customize, 'slider_image_' . $i, array(
'label' => sprintf( 'Banner %d - Bild', $i ), 'label' => sprintf( __('Banner %d - Bild', 'minecraft-modern-theme'), $i ), 'section' => 'header_slider',
'section' => 'header_slider',
'settings' => 'slider_image_' . $i,
) ) ); ) ) );
$wp_customize->add_setting( 'slider_title_' . $i, array( 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_setting( 'slider_title_' . $i, array( 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( 'slider_title_' . $i, array( $wp_customize->add_control( 'slider_title_' . $i, array( 'label' => sprintf( __('Banner %d - Titel', 'minecraft-modern-theme'), $i ), 'section' => 'header_slider', 'type' => 'text' ) );
'label' => sprintf( 'Banner %d - Titel', $i ),
'section' => 'header_slider',
'settings' => 'slider_title_' . $i,
'type' => 'text',
) );
$wp_customize->add_setting( 'slider_subtitle_' . $i, array( 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_setting( 'slider_subtitle_' . $i, array( 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( 'slider_subtitle_' . $i, array( $wp_customize->add_control( 'slider_subtitle_' . $i, array( 'label' => sprintf( __('Banner %d - Untertitel', 'minecraft-modern-theme'), $i ), 'section' => 'header_slider', 'type' => 'text' ) );
'label' => sprintf( 'Banner %d - Untertitel', $i ),
'section' => 'header_slider',
'settings' => 'slider_subtitle_' . $i,
'type' => 'text',
) );
} }
// Slider Text- & Stil-Einstellungen
$wp_customize->add_setting( 'slider_font_family', array( 'default' => 'Raleway', 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_setting( 'slider_font_family', array( 'default' => 'Raleway', 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( 'slider_font_family', array( $wp_customize->add_control( 'slider_font_family', array(
'label' => 'Schriftart', 'section' => 'header_slider', 'settings' => 'slider_font_family', 'type' => 'select', 'label' => __('Schriftart', 'minecraft-modern-theme'), 'section' => 'header_slider', 'type' => 'select',
'choices' => array( 'Raleway' => 'Raleway', 'Poppins' => 'Poppins', 'Montserrat' => 'Montserrat', 'Oswald' => 'Oswald', 'Roboto' => 'Roboto', 'Lato' => 'Lato' ), 'choices' => array( 'Raleway' => 'Raleway', 'Poppins' => 'Poppins', 'Montserrat' => 'Montserrat', 'Oswald' => 'Oswald', 'Roboto' => 'Roboto', 'Lato' => 'Lato' ),
) ); ) );
$wp_customize->add_setting( 'slider_font_size', array( 'default' => 'mittel', 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_setting( 'slider_font_size', array( 'default' => 'mittel', 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( 'slider_font_size', array( $wp_customize->add_control( 'slider_font_size', array(
'label' => 'Schriftgröße', 'section' => 'header_slider', 'settings' => 'slider_font_size', 'type' => 'select', 'label' => __('Schriftgröße', 'minecraft-modern-theme'), 'section' => 'header_slider', 'type' => 'select',
'choices' => array( 'klein' => 'Klein', 'mittel' => 'Mittel', 'gross' => 'Groß', 'extra-gross' => 'Extra Groß' ), 'choices' => array( 'klein' => __('Klein', 'minecraft-modern-theme'), 'mittel' => __('Mittel', 'minecraft-modern-theme'), 'gross' => __('Groß', 'minecraft-modern-theme'), 'extra-gross' => __('Extra Groß', 'minecraft-modern-theme') ),
) ); ) );
$wp_customize->add_setting( 'slider_font_color', array( 'default' => '#ffffff', 'sanitize_callback' => 'sanitize_hex_color' ) ); $wp_customize->add_setting( 'slider_font_color', array( 'default' => '#ffffff', 'sanitize_callback' => 'sanitize_hex_color' ) );
$wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'slider_font_color', array( $wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'slider_font_color', array(
'label' => 'Schriftfarbe', 'section' => 'header_slider', 'settings' => 'slider_font_color', 'label' => __('Schriftfarbe', 'minecraft-modern-theme'), 'section' => 'header_slider',
) ) ); ) ) );
// Header-Höhe
$wp_customize->add_setting( 'header_height', array( 'default' => 'mittel', 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_setting( 'header_height', array( 'default' => 'mittel', 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( 'header_height', array( $wp_customize->add_control( 'header_height', array(
'label' => 'Header-Höhe', 'section' => 'header_slider', 'settings' => 'header_height', 'type' => 'select', 'label' => __('Header-Höhe', 'minecraft-modern-theme'), 'section' => 'header_slider', 'type' => 'select',
'choices' => array( 'klein' => 'Klein', 'mittel' => 'Mittel', 'gross' => 'Groß' ), 'choices' => array( 'klein' => __('Klein', 'minecraft-modern-theme'), 'mittel' => __('Mittel', 'minecraft-modern-theme'), 'gross' => __('Groß', 'minecraft-modern-theme') ),
) ); ) );
$wp_customize->add_setting( 'slider_hide_arrows', array( 'default' => false, 'sanitize_callback' => 'wp_validate_boolean' ) ); $wp_customize->add_setting( 'slider_hide_arrows', array( 'default' => false, 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_control( 'slider_hide_arrows', array( $wp_customize->add_control( 'slider_hide_arrows', array( 'label' => __('Pfeile ausblenden', 'minecraft-modern-theme'), 'section' => 'header_slider', 'type' => 'checkbox' ) );
'label' => 'Pfeile ausblenden', 'section' => 'header_slider', 'settings' => 'slider_hide_arrows', 'type' => 'checkbox',
) );
$wp_customize->add_setting( 'slider_hide_pagination', array( 'default' => false, 'sanitize_callback' => 'wp_validate_boolean' ) ); $wp_customize->add_setting( 'slider_hide_pagination', array( 'default' => false, 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_control( 'slider_hide_pagination', array( $wp_customize->add_control( 'slider_hide_pagination', array( 'label' => __('Paginierung (Punkte) ausblenden', 'minecraft-modern-theme'), 'section' => 'header_slider', 'type' => 'checkbox' ) );
'label' => 'Paginierung (Punkte) ausblenden', 'section' => 'header_slider', 'settings' => 'slider_hide_pagination', 'type' => 'checkbox',
) );
// --- Sektion: Startseiten-Hero (Fallback) ---
// =========================================================================
// 2. HERO SECTION (Fallback)
// =========================================================================
$wp_customize->add_section( 'hero_section', array( $wp_customize->add_section( 'hero_section', array(
'title' => 'Startseiten-Hero (wenn Slider deaktiviert)', 'title' => __('Startseiten-Hero (wenn Slider deaktiviert)', 'minecraft-modern-theme'),
'priority' => 21, 'priority' => 21,
'description' => 'Diese Inhalte werden angezeigt, wenn der Slider ausgeschaltet ist.',
) ); ) );
$wp_customize->add_setting( 'hero_title', array( 'default' => 'Willkommen auf unserem Server', 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( 'hero_title', array( 'label' => 'Haupttitel', 'section' => 'hero_section', 'settings' => 'hero_title', 'type' => 'text' ) ); $wp_customize->add_setting( 'hero_title', array( 'default' => __('Willkommen auf unserem Server', 'minecraft-modern-theme'), 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_setting( 'hero_subtitle', array( 'default' => 'Trete einer Community voller Abenteuer bei.', 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_control( 'hero_title', array( 'label' => __('Haupttitel', 'minecraft-modern-theme'), 'section' => 'hero_section', 'type' => 'text' ) );
$wp_customize->add_control( 'hero_subtitle', array( 'label' => 'Untertitel', 'section' => 'hero_section', 'settings' => 'hero_subtitle', 'type' => 'text' ) ); $wp_customize->add_setting( 'hero_subtitle', array( 'default' => __('Trete einer Community voller Abenteuer bei.', 'minecraft-modern-theme'), 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( 'hero_subtitle', array( 'label' => __('Untertitel', 'minecraft-modern-theme'), 'section' => 'hero_section', 'type' => 'text' ) );
$wp_customize->add_setting( 'hero_bg_image', array( 'sanitize_callback' => 'esc_url_raw' ) ); $wp_customize->add_setting( 'hero_bg_image', array( 'sanitize_callback' => 'esc_url_raw' ) );
$wp_customize->add_control( new WP_Customize_Image_Control( $wp_customize, 'hero_bg_image', array( $wp_customize->add_control( new WP_Customize_Image_Control( $wp_customize, 'hero_bg_image', array( 'label' => __('Hintergrundbild', 'minecraft-modern-theme'), 'section' => 'hero_section' ) ) );
'label' => 'Hintergrundbild', 'section' => 'hero_section', 'settings' => 'hero_bg_image', $wp_customize->add_setting( 'hero_button_1_text', array( 'default' => __('Zum Forum', 'minecraft-modern-theme'), 'sanitize_callback' => 'sanitize_text_field' ) );
) ) ); $wp_customize->add_control( 'hero_button_1_text', array( 'label' => __('Button 1 Text', 'minecraft-modern-theme'), 'section' => 'hero_section', 'type' => 'text' ) );
$wp_customize->add_setting( 'hero_button_1_text', array( 'default' => 'Zum Forum', 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_setting( 'hero_button_1_url', array( 'default' => '#', 'sanitize_callback' => 'esc_url_raw' ) );
$wp_customize->add_control( 'hero_button_1_text', array( 'label' => 'Button 1 Text', 'section' => 'hero_section', 'settings' => 'hero_button_1_text', 'type' => 'text' ) ); $wp_customize->add_control( 'hero_button_1_url', array( 'label' => __('Button 1 URL', 'minecraft-modern-theme'), 'section' => 'hero_section', 'type' => 'url' ) );
$wp_customize->add_setting( 'hero_button_1_url', array( 'default' => '#', 'sanitize_callback' => 'esc_url_raw' ) ); $wp_customize->add_setting( 'hero_button_2_text', array( 'default' => __('Zum Teamspeak', 'minecraft-modern-theme'), 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( 'hero_button_1_url', array( 'label' => 'Button 1 URL', 'section' => 'hero_section', 'settings' => 'hero_button_1_url', 'type' => 'url' ) ); $wp_customize->add_control( 'hero_button_2_text', array( 'label' => __('Button 2 Text', 'minecraft-modern-theme'), 'section' => 'hero_section', 'type' => 'text' ) );
$wp_customize->add_setting( 'hero_button_2_text', array( 'default' => 'Zum Teamspeak', 'sanitize_callback' => 'sanitize_text_field' ) ); $wp_customize->add_setting( 'hero_button_2_url', array( 'default' => '#', 'sanitize_callback' => 'esc_url_raw' ) );
$wp_customize->add_control( 'hero_button_2_text', array( 'label' => 'Button 2 Text', 'section' => 'hero_section', 'settings' => 'hero_button_2_text', 'type' => 'text' ) ); $wp_customize->add_control( 'hero_button_2_url', array( 'label' => __('Button 2 URL', 'minecraft-modern-theme'), 'section' => 'hero_section', 'type' => 'url' ) );
$wp_customize->add_setting( 'hero_button_2_url', array( 'default' => '#', 'sanitize_callback' => 'esc_url_raw' ) ); $wp_customize->add_setting( 'show_home_title', array( 'default' => false, 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_control( 'hero_button_2_url', array( 'label' => 'Button 2 URL', 'section' => 'hero_section', 'settings' => 'hero_button_2_url', 'type' => 'url' ) ); $wp_customize->add_control( 'show_home_title', array( 'label' => __('Seitentitel "Home" anzeigen', 'minecraft-modern-theme'), 'section' => 'hero_section', 'type' => 'checkbox' ) );
// --- Checkbox: Seitentitel auf Startseite verstecken ---
$wp_customize->add_setting( 'show_home_title', array(
'default' => false,
'sanitize_callback' => 'wp_validate_boolean',
) );
$wp_customize->add_control( 'show_home_title', array(
'label' => 'Seitentitel "Home" anzeigen',
'description' => 'Aktiviere diese Option, wenn der Titel "Home" über dem Slider/Inhalt angezeigt werden soll.',
'section' => 'hero_section',
'settings' => 'show_home_title',
'type' => 'checkbox',
) );
// ========================================================================= // =========================================================================
// === 2. FARBEN & DARSTELLUNG (KOMBINIERT) ================================ // 3. FARBEN & DARSTELLUNG
// ========================================================================= // =========================================================================
// --- Sektion: Farben & Darstellung ---
$wp_customize->add_section( 'theme_appearance_settings', array( $wp_customize->add_section( 'theme_appearance_settings', array(
'title' => 'Farben & Darstellung', 'title' => __('Farben & Darstellung', 'minecraft-modern-theme'), 'priority' => 30,
'priority' => 30,
) );
// =========================================================================
// === NEU: THEME PRESETS (VOREINSTELLUNGEN) ==============================
// =========================================================================
$wp_customize->add_setting( 'theme_color_preset', array(
'default' => 'classic',
'sanitize_callback' => 'sanitize_key',
'transport' => 'refresh',
) ); ) );
$wp_customize->add_setting( 'theme_color_preset', array( 'default' => 'classic', 'sanitize_callback' => 'sanitize_key', 'transport' => 'refresh' ) );
$wp_customize->add_control( 'theme_color_preset', array( $wp_customize->add_control( 'theme_color_preset', array(
'label' => 'Theme Preset (Farbschema)', 'label' => __('Theme Preset (Farbschema)', 'minecraft-modern-theme'),
'description' => 'Wähle ein voreingestelltes Farbschema (z. B. für Nether oder End Dimension).', 'section' => 'theme_appearance_settings',
'section' => 'theme_appearance_settings', 'type' => 'select',
'settings' => 'theme_color_preset', 'priority' => 1,
'type' => 'select', 'choices' => array(
'priority' => 1, // Zeig es ganz oben in dieser Sektion an 'classic' => __('Classic Minecraft (Diamant-Blau)', 'minecraft-modern-theme'),
'choices' => array( 'nether' => __('Nether (Lava-Rot)', 'minecraft-modern-theme'),
'classic' => 'Classic Minecraft (Diamant-Blau)', 'end' => __('The End (Ender-Purpur)', 'minecraft-modern-theme'),
'nether' => 'Nether (Lava-Rot)',
'end' => 'The End (Ender-Purpur)',
), ),
) ); ) );
// =========================================================================
// === ENDE THEME PRESETS ==================================================
// =========================================================================
// Akzentfarbe
$wp_customize->add_setting( 'primary_accent_color', array( 'default' => '#00d4ff', 'sanitize_callback' => 'sanitize_hex_color', 'transport' => 'refresh' ) ); $wp_customize->add_setting( 'primary_accent_color', array( 'default' => '#00d4ff', 'sanitize_callback' => 'sanitize_hex_color', 'transport' => 'refresh' ) );
$wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'primary_accent_color', array( $wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'primary_accent_color', array( 'label' => __('Akzentfarbe', 'minecraft-modern-theme'), 'section' => 'theme_appearance_settings' ) ) );
'label' => 'Akzentfarbe', 'section' => 'theme_appearance_settings', 'settings' => 'primary_accent_color',
) ) );
// Hintergrundfarbe
$wp_customize->add_setting( 'background_color', array(
'default' => '#ffffff',
'sanitize_callback' => 'sanitize_hex_color',
'transport' => 'refresh',
) );
$wp_customize->add_control( new WP_Customize_Color_Control( $wp_customize, 'background_color', array(
'label' => 'Hintergrundfarbe',
'section' => 'theme_appearance_settings',
'settings' => 'background_color',
) ) );
// Dark / Light Mode
$wp_customize->add_setting( 'default_theme_mode', array( 'default' => 'dark', 'sanitize_callback' => 'sanitize_key' ) ); $wp_customize->add_setting( 'default_theme_mode', array( 'default' => 'dark', 'sanitize_callback' => 'sanitize_key' ) );
$wp_customize->add_control( 'default_theme_mode', array( $wp_customize->add_control( 'default_theme_mode', array(
'label' => 'Standard-Theme-Modus', 'section' => 'theme_appearance_settings', 'type' => 'radio', 'label' => __('Standard-Theme-Modus', 'minecraft-modern-theme'),
'choices' => array( 'dark' => 'Dark Mode (Standard)', 'light' => 'Light Mode' ), 'section' => 'theme_appearance_settings',
'type' => 'radio',
'choices' => array( 'dark' => __('Dark Mode', 'minecraft-modern-theme'), 'light' => __('Light Mode', 'minecraft-modern-theme') ),
) ); ) );
// FIX: Scroll-to-Top war nie im Customizer steuerbar
// ========================================================================= $wp_customize->add_setting( 'show_scroll_to_top', array( 'default' => true, 'sanitize_callback' => 'wp_validate_boolean' ) );
// === 3. SIDEBAR EINSTELLUNGEN ============================================ $wp_customize->add_control( 'show_scroll_to_top', array(
// ========================================================================= 'label' => __('Scroll-to-Top Button anzeigen', 'minecraft-modern-theme'),
'description' => __('Zeigt einen Button unten rechts zum Hochscrollen an.', 'minecraft-modern-theme'),
$wp_customize->add_section( 'sidebar_settings', array( 'section' => 'theme_appearance_settings',
'title' => 'Sidebar Einstellungen',
'priority' => 35,
'description' => 'Konfiguriere die Sidebar auf der Startseite.',
) );
// Sidebar aktivieren
$wp_customize->add_setting( 'homepage_sidebar_enabled', array(
'default' => false,
'sanitize_callback' => 'wp_validate_boolean',
) );
$wp_customize->add_control( 'homepage_sidebar_enabled', array(
'label' => 'Sidebar auf Startseite aktivieren',
'description' => 'Zeigt eine Sidebar neben dem Hauptinhalt an.',
'section' => 'sidebar_settings',
'settings' => 'homepage_sidebar_enabled',
'type' => 'checkbox', 'type' => 'checkbox',
) ); ) );
// Sidebar Position
$wp_customize->add_setting( 'homepage_sidebar_position', array( // =========================================================================
'default' => 'right', // 4. SIDEBAR
'sanitize_callback' => 'sanitize_key', // =========================================================================
) ); $wp_customize->add_section( 'sidebar_settings', array( 'title' => __('Sidebar Einstellungen', 'minecraft-modern-theme'), 'priority' => 35 ) );
$wp_customize->add_setting( 'homepage_sidebar_enabled', array( 'default' => false, 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_control( 'homepage_sidebar_enabled', array( 'label' => __('Sidebar auf Startseite aktivieren', 'minecraft-modern-theme'), 'section' => 'sidebar_settings', 'type' => 'checkbox' ) );
$wp_customize->add_setting( 'homepage_sidebar_position', array( 'default' => 'right', 'sanitize_callback' => 'sanitize_key' ) );
$wp_customize->add_control( 'homepage_sidebar_position', array( $wp_customize->add_control( 'homepage_sidebar_position', array(
'label' => 'Sidebar Position', 'label' => __('Sidebar Position', 'minecraft-modern-theme'), 'section' => 'sidebar_settings', 'type' => 'select',
'section' => 'sidebar_settings', 'choices' => array( 'left' => __('Links', 'minecraft-modern-theme'), 'right' => __('Rechts', 'minecraft-modern-theme') ),
'settings' => 'homepage_sidebar_position',
'type' => 'select',
'choices' => array(
'left' => 'Links',
'right' => 'Rechts',
),
) ); ) );
// ========================================================================= // =========================================================================
// === 4. SOCIAL MEDIA ===================================================== // 5. SOCIAL MEDIA
// ========================================================================= // =========================================================================
$wp_customize->add_section( 'social_links', array( 'title' => __('Social Media Links', 'minecraft-modern-theme'), 'priority' => 40 ) );
$wp_customize->add_section( 'social_links', array( 'title' => 'Social Media Links', 'priority' => 40 ) ); $social_platforms = array(
$social_platforms = array( 'discord' => 'Discord', 'youtube' => 'YouTube', 'twitter' => 'Twitter (X)', 'facebook' => 'Facebook', 'instagram' => 'Instagram', 'tiktok' => 'TikTok', 'twitch' => 'Twitch', 'steam' => 'Steam', 'github' => 'GitHub', 'linkedin' => 'LinkedIn', 'pinterest' => 'Pinterest', 'reddit' => 'Reddit', 'teamspeak' => 'Teamspeak', 'spotify' => 'Spotify' ); 'bluesky' => 'BlueSky', 'discord' => 'Discord', 'youtube' => 'YouTube', 'twitter' => 'Twitter (X)',
foreach ($social_platforms as $key => $label) { 'facebook' => 'Facebook', 'instagram' => 'Instagram', 'tiktok' => 'TikTok',
'twitch' => 'Twitch', 'steam' => 'Steam', 'github' => 'GitHub',
'linkedin' => 'LinkedIn', 'pinterest' => 'Pinterest', 'reddit' => 'Reddit',
'mastodon' => 'Mastodon', 'threads' => 'Threads', 'kickstarter' => 'Kickstarter',
'teamspeak' => 'Teamspeak', 'spotify' => 'Spotify', 'stoat' => 'Stoat',
);
foreach ( $social_platforms as $key => $label ) {
$wp_customize->add_setting( 'social_' . $key, array( 'sanitize_callback' => 'esc_url_raw' ) ); $wp_customize->add_setting( 'social_' . $key, array( 'sanitize_callback' => 'esc_url_raw' ) );
$wp_customize->add_control( 'social_' . $key, array( $wp_customize->add_control( 'social_' . $key, array( 'label' => $label . ' URL', 'section' => 'social_links', 'type' => 'url' ) );
'label' => $label . ' URL', 'section' => 'social_links', 'settings' => 'social_' . $key, 'type' => 'url',
) );
} }
// ========================================================================= // =========================================================================
// === 5. FOOTER-BEREICH ================================================== // 6. FOOTER
// ========================================================================= // =========================================================================
$wp_customize->add_section( 'footer_settings', array( 'title' => __('Footer-Einstellungen', 'minecraft-modern-theme'), 'priority' => 50 ) );
$wp_customize->add_section( 'footer_settings', array( 'title' => 'Footer-Einstellungen', 'priority' => 50 ) ); // FIX: Default mit aktuellem Jahr zur Laufzeit auswerten, nicht bei class-load
$wp_customize->add_setting( 'footer_copyright', array(
// Copyright-Text 'default' => '', // Leer = dynamisch berechnet in footer.php
$wp_customize->add_setting( 'footer_copyright', array( 'default' => '&copy; ' . date('Y') . ' ' . get_bloginfo('name'), 'sanitize_callback' => 'wp_kses_post' ) ); 'sanitize_callback' => 'wp_kses_post',
) );
$wp_customize->add_control( 'footer_copyright', array( $wp_customize->add_control( 'footer_copyright', array(
'label' => 'Copyright-Text', 'section' => 'footer_settings', 'settings' => 'footer_copyright', 'type' => 'textarea', 'label' => __('Copyright-Text', 'minecraft-modern-theme'),
'description' => __('Leer lassen für automatischen Text mit aktuellem Jahr.', 'minecraft-modern-theme'),
'section' => 'footer_settings',
'type' => 'textarea',
) ); ) );
// Impressum & Datenschutz Links $wp_customize->add_setting( 'footer_impressum_url', array( 'sanitize_callback' => 'esc_url_raw' ) );
$wp_customize->add_setting( 'footer_impressum_url', array( 'sanitize_callback' => 'esc_url_raw' ) ); $wp_customize->add_control( 'footer_impressum_url', array( 'label' => __('URL für Impressum', 'minecraft-modern-theme'), 'section' => 'footer_settings', 'type' => 'url' ) );
$wp_customize->add_control( 'footer_impressum_url', array(
'label' => 'URL für Impressum', 'section' => 'footer_settings', 'settings' => 'footer_impressum_url', 'type' => 'url',
) );
$wp_customize->add_setting( 'footer_datenschutz_url', array( 'sanitize_callback' => 'esc_url_raw' ) ); $wp_customize->add_setting( 'footer_datenschutz_url', array( 'sanitize_callback' => 'esc_url_raw' ) );
$wp_customize->add_control( 'footer_datenschutz_url', array( $wp_customize->add_control( 'footer_datenschutz_url', array( 'label' => __('URL für Datenschutz', 'minecraft-modern-theme'), 'section' => 'footer_settings', 'type' => 'url' ) );
'label' => 'URL für Datenschutz', 'section' => 'footer_settings', 'settings' => 'footer_datenschutz_url', 'type' => 'url',
) );
// Footer-Credit
$wp_customize->add_setting( 'show_footer_credit', array( 'default' => true, 'sanitize_callback' => 'wp_validate_boolean' ) ); $wp_customize->add_setting( 'show_footer_credit', array( 'default' => true, 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_control( 'show_footer_credit', array( $wp_customize->add_control( 'show_footer_credit', array( 'label' => __('Footer-Credit anzeigen', 'minecraft-modern-theme'), 'section' => 'footer_settings', 'type' => 'checkbox' ) );
'label' => 'Footer-Credit ("Minecraft Theme Erstellt von...") anzeigen', 'section' => 'footer_settings', 'settings' => 'show_footer_credit', 'type' => 'checkbox',
) );
// ========================================================================= // =========================================================================
// === 6. ZUSÄTZLICHE FUNKTIONEN ========================================== // 7. FAQ & TEAM
// ========================================================================= // =========================================================================
$wp_customize->add_section( 'faq_settings', array( 'title' => __('FAQ Einstellungen', 'minecraft-modern-theme'), 'priority' => 60 ) );
$wp_customize->add_setting( 'faq_enabled', array( 'default' => true, 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_control( 'faq_enabled', array( 'label' => __('FAQ System aktivieren', 'minecraft-modern-theme'), 'section' => 'faq_settings', 'type' => 'checkbox' ) );
// --- Sektion: FAQ Einstellungen --- $wp_customize->add_section( 'team_settings', array( 'title' => __('Team Einstellungen', 'minecraft-modern-theme'), 'priority' => 65 ) );
$wp_customize->add_section( 'faq_settings', array( 'title' => 'FAQ Einstellungen', 'priority' => 60 ) ); $wp_customize->add_setting( 'team_enabled', array( 'default' => true, 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_setting( 'faq_enabled', array( 'default' => true, 'sanitize_callback' => 'wp_validate_boolean' ) ); $wp_customize->add_control( 'team_enabled', array( 'label' => __('Team Showcase aktivieren', 'minecraft-modern-theme'), 'section' => 'team_settings', 'type' => 'checkbox' ) );
$wp_customize->add_control( 'faq_enabled', array(
'label' => 'FAQ System aktivieren', 'section' => 'faq_settings', 'settings' => 'faq_enabled', 'type' => 'checkbox',
) );
// --- Sektion: Team Einstellungen ---
$wp_customize->add_section( 'team_settings', array( 'title' => 'Team Einstellungen', 'priority' => 65 ) );
$wp_customize->add_setting( 'team_enabled', array( 'default' => true, 'sanitize_callback' => 'wp_validate_boolean' ) );
$wp_customize->add_control( 'team_enabled', array(
'label' => 'Team Showcase aktivieren', 'section' => 'team_settings', 'settings' => 'team_enabled', 'type' => 'checkbox',
) );
// ========================================================================= // =========================================================================
// === 7. LOGIN-BEREICH ==================================================== // 8. LOGIN
// ========================================================================= // =========================================================================
$wp_customize->add_section( 'login_settings', array( 'title' => __('Login-Einstellungen', 'minecraft-modern-theme'), 'priority' => 70 ) );
$wp_customize->add_setting( 'login_background_image', array( 'sanitize_callback' => 'esc_url_raw', 'transport' => 'refresh' ) );
$wp_customize->add_control( new WP_Customize_Image_Control( $wp_customize, 'login_background_image', array( 'label' => __('Login-Hintergrundbild', 'minecraft-modern-theme'), 'section' => 'login_settings' ) ) );
$wp_customize->add_setting( 'login_logo', array( 'sanitize_callback' => 'esc_url_raw', 'transport' => 'refresh' ) );
$wp_customize->add_control( new WP_Customize_Image_Control( $wp_customize, 'login_logo', array( 'label' => __('Login-Logo', 'minecraft-modern-theme'), 'section' => 'login_settings' ) ) );
$wp_customize->add_section( 'login_settings', array( for ( $i = 1; $i <= 5; $i++ ) {
'title' => 'Login-Einstellungen', $wp_customize->add_setting( 'login_avatar_uuid_' . $i, array( 'sanitize_callback' => 'sanitize_text_field', 'transport' => 'refresh' ) );
'priority' => 70,
'description' => 'Passe das Aussehen der wp-admin Login-Seite an.',
) );
// Login-Hintergrundbild
$wp_customize->add_setting( 'login_background_image', array(
'sanitize_callback' => 'esc_url_raw',
'transport' => 'refresh',
) );
$wp_customize->add_control( new WP_Customize_Image_Control( $wp_customize, 'login_background_image', array(
'label' => 'Login-Hintergrundbild',
'section' => 'login_settings',
'settings' => 'login_background_image',
) ) );
// Login-Logo
$wp_customize->add_setting( 'login_logo', array(
'sanitize_callback' => 'esc_url_raw',
'transport' => 'refresh',
) );
$wp_customize->add_control( new WP_Customize_Image_Control( $wp_customize, 'login_logo', array(
'label' => 'Login-Logo (ersetzt das WordPress-Logo)',
'section' => 'login_settings',
'settings' => 'login_logo',
) ) );
// Multi-Avatar UUIDs
for ($i = 1; $i <= 5; $i++) {
$wp_customize->add_setting( 'login_avatar_uuid_' . $i, array(
'sanitize_callback' => 'sanitize_text_field',
'transport' => 'refresh',
) );
$wp_customize->add_control( 'login_avatar_uuid_' . $i, array( $wp_customize->add_control( 'login_avatar_uuid_' . $i, array(
'label' => sprintf( 'Avatar %d UUID', $i ), 'label' => sprintf( __('Avatar %d UUID', 'minecraft-modern-theme'), $i ),
'description' => sprintf( 'Gib die UUID für den %d. Avatar an. (Leer lassen, um zu deaktivieren)', $i ), 'section' => 'login_settings',
'section' => 'login_settings', 'type' => 'text',
'settings' => 'login_avatar_uuid_' . $i,
'type' => 'text',
) ); ) );
} }
// Slider-Geschwindigkeit $wp_customize->add_setting( 'login_avatar_slider_speed', array( 'default' => 4, 'sanitize_callback' => 'absint', 'transport' => 'refresh' ) );
$wp_customize->add_setting( 'login_avatar_slider_speed', array(
'default' => 4,
'sanitize_callback' => 'absint',
'transport' => 'refresh',
) );
$wp_customize->add_control( 'login_avatar_slider_speed', array( $wp_customize->add_control( 'login_avatar_slider_speed', array(
'label' => 'Avatar-Wechsel (in Sekunden)', 'label' => __('Avatar-Wechsel (Sekunden)', 'minecraft-modern-theme'),
'description' => 'Wie viele Sekunden soll ein Avatar angezeigt werden?',
'section' => 'login_settings', 'section' => 'login_settings',
'settings' => 'login_avatar_slider_speed',
'type' => 'number', 'type' => 'number',
'input_attrs' => array( 'min' => 2, 'max' => 10, 'step' => 1 ), 'input_attrs' => array( 'min' => 2, 'max' => 10, 'step' => 1 ),
) ); ) );
// ========================================================================= // =========================================================================
// === 8. EXPORT / IMPORT SECTION ========================================= // 8.5. VIDEO / LIVESTREAM EINSTELLUNGEN
// ========================================================================= // =========================================================================
$wp_customize->add_section( 'video_livestream_settings', array(
$wp_customize->add_section( 'theme_mods_import_export', array( 'title' => __('Video & Livestream', 'minecraft-modern-theme'),
'title' => __( 'Einstellungen sichern', 'minecraft-modern-theme' ), 'description' => __('Einstellungen für YouTube Livestream-Erkennung. Hauptmethode: Livestream-Posts unter "Livestreams" erstellen. Optional: Zusätzlichen Hauptkanal hier eintragen.', 'minecraft-modern-theme'),
'priority' => 999, 'priority' => 75,
'description' => '',
) ); ) );
// Füge ein Custom Control mit HTML hinzu $wp_customize->add_setting( 'youtube_api_key', array(
$wp_customize->add_setting( 'import_export_placeholder', array( 'default' => '',
'sanitize_callback' => 'sanitize_text_field', 'sanitize_callback' => 'sanitize_text_field',
'transport' => 'refresh',
) );
$wp_customize->add_control( 'youtube_api_key', array(
'label' => __('YouTube API Key', 'minecraft-modern-theme'),
'description' => __('Erforderlich für automatische YouTube Live-Erkennung. <a href="https://console.cloud.google.com/" target="_blank">Hier API Key erstellen</a>', 'minecraft-modern-theme'),
'section' => 'video_livestream_settings',
'type' => 'text',
'input_attrs' => array(
'placeholder' => 'AIzaSyD...',
),
) ); ) );
// Custom Control Class für Export/Import $wp_customize->add_setting( 'twitch_client_id', array(
class Import_Export_Control extends WP_Customize_Control { 'default' => '',
public $type = 'import_export'; 'sanitize_callback' => 'sanitize_text_field',
'transport' => 'refresh',
public function render_content() { ) );
$export_url = admin_url('admin-post.php?action=export_theme_settings'); $wp_customize->add_control( 'twitch_client_id', array(
$nonce = wp_create_nonce('theme-import-nonce'); 'label' => __('Twitch Client ID', 'minecraft-modern-theme'),
?> 'description' => __('Erforderlich für Twitch Live-Erkennung. <a href="https://dev.twitch.tv/console/apps" target="_blank">Hier App erstellen</a>', 'minecraft-modern-theme'),
<div class="import-export-wrapper" style="margin-top: 15px;"> 'section' => 'video_livestream_settings',
<p class="description" style="margin-bottom: 20px;"> 'type' => 'text',
<strong>Hinweis:</strong> Hier kannst du alle deine Theme-Einstellungen sichern und wiederherstellen. 'input_attrs' => array(
</p> 'placeholder' => 'xxxxxxxxxxxxxx',
),
<a href="<?php echo esc_url($export_url); ?>" class="button button-primary button-hero" id="export-settings-btn" style="display: inline-flex; align-items: center; gap: 8px; margin-bottom: 20px;"> ) );
<span class="dashicons dashicons-download" style="margin-top:3px;"></span> Einstellungen Exportieren
</a>
<div style="border-top: 2px solid #ddd; padding-top: 20px; margin-top: 20px;">
<label style="display:block; margin-bottom:10px; font-weight:bold;">
Backup wiederherstellen:
</label>
<input type="file" id="import-settings-file" accept=".json" style="width:100%; margin-bottom:10px;">
<button type="button" class="button button-secondary" id="import-settings-btn" disabled style="display: inline-flex; align-items: center; gap: 8px;">
<span class="dashicons dashicons-upload" style="margin-top:3px;"></span> Einstellungen Importieren
</button>
</div>
<p class="description" style="margin-top:15px; padding: 10px; background: #fff3cd; border-left: 4px solid #ffc107;">
<strong>⚠️ Warnung:</strong> Beim Import werden alle aktuellen Einstellungen überschrieben!
</p>
</div>
<script type="text/javascript">
(function($) {
var ajaxUrl = '<?php echo admin_url('admin-ajax.php'); ?>';
var nonce = '<?php echo $nonce; ?>';
$('#import-settings-file').on('change', function() {
$('#import-settings-btn').prop('disabled', $(this).val() === '');
});
$('#import-settings-btn').on('click', function() { $wp_customize->add_setting( 'twitch_client_secret', array(
var fileInput = $('#import-settings-file')[0]; 'default' => '',
'sanitize_callback' => 'sanitize_text_field',
if (fileInput.files.length === 0) { 'transport' => 'refresh',
alert('Bitte wähle eine JSON-Datei aus.'); ) );
return; $wp_customize->add_control( 'twitch_client_secret', array(
} 'label' => __('Twitch Client Secret', 'minecraft-modern-theme'),
'description' => __('Nur für Live-Prüfung. Wird serverseitig genutzt.', 'minecraft-modern-theme'),
'section' => 'video_livestream_settings',
'type' => 'text',
'input_attrs' => array(
'placeholder' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
),
) );
if (!confirm('WARNUNG: Alle aktuellen Einstellungen werden überschrieben. Bist du sicher?')) { // =========================================================================
return; // 9. EXPORT / IMPORT
} // =========================================================================
$wp_customize->add_section( 'theme_mods_import_export', array(
var formData = new FormData(); 'title' => __('Einstellungen sichern', 'minecraft-modern-theme'),
formData.append('import_file', fileInput.files[0]); 'priority' => 999,
formData.append('action', 'import_theme_settings'); ) );
formData.append('nonce', nonce); $wp_customize->add_setting( 'import_export_placeholder', array( 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( new MM_Import_Export_Control( $wp_customize, 'import_export_placeholder', array(
var $btn = $(this);
var originalText = $btn.html();
$btn.prop('disabled', true).html('<span class="dashicons dashicons-update" style="margin-top:3px; animation: rotation 1s infinite linear;"></span> Importiere...');
$.ajax({
url: ajaxUrl,
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
if (response.success) {
alert('✅ ' + response.data);
location.reload();
} else {
alert('❌ Fehler: ' + response.data);
$btn.prop('disabled', false).html(originalText);
}
},
error: function() {
alert('❌ Ein technischer Fehler ist aufgetreten.');
$btn.prop('disabled', false).html(originalText);
}
});
});
})(jQuery);
</script>
<style>
@keyframes rotation {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
<?php
}
}
$wp_customize->add_control( new Import_Export_Control( $wp_customize, 'import_export_placeholder', array(
'section' => 'theme_mods_import_export', 'section' => 'theme_mods_import_export',
) ) ); ) ) );
} }
@@ -480,110 +467,81 @@ add_action( 'customize_register', 'minecraft_modern_customize_register' );
// ========================================================================= // =========================================================================
// === DYNAMISCHES CSS ===================================================== // DYNAMISCHES CSS FIX: Google Font via wp_enqueue_style(), nicht <link> in wp_head
// ========================================================================= // =========================================================================
function minecraft_modern_enqueue_dynamic_styles() {
$font = get_theme_mod( 'slider_font_family', 'Raleway' );
function minecraft_modern_dynamic_css_output() { // Google Font sauber einbinden
$accent_color = get_theme_mod( 'primary_accent_color', '#00d4ff' ); $font_url = 'https://fonts.googleapis.com/css2?family=' . urlencode($font) . ':wght@400;600;700&display=swap';
$slider_font = get_theme_mod( 'slider_font_family', 'Raleway' ); wp_enqueue_style( 'minecraft-modern-google-font', $font_url, array(), null );
$slider_color = get_theme_mod( 'slider_font_color', '#ffffff' );
$slider_size_setting = get_theme_mod( 'slider_font_size', 'mittel' ); // Dynamisches CSS als inline style
$accent_color = get_theme_mod( 'primary_accent_color', '#00d4ff' );
$slider_color = get_theme_mod( 'slider_font_color', '#ffffff' );
$slider_size_setting = get_theme_mod( 'slider_font_size', 'mittel' );
$header_height_setting = get_theme_mod( 'header_height', 'mittel' ); $header_height_setting = get_theme_mod( 'header_height', 'mittel' );
// Header-Höhe umwandeln $height_map = array( 'klein' => '200px', 'mittel' => '300px', 'gross' => '400px' );
$header_height_value = '300px'; $header_height_value = isset($height_map[$header_height_setting]) ? $height_map[$header_height_setting] : '300px';
if ( $header_height_setting === 'klein' ) {
$header_height_value = '200px';
} elseif ( $header_height_setting === 'gross' ) {
$header_height_value = '400px';
}
// Schriftgrößen umwandeln
$font_sizes = array( $font_sizes = array(
'klein' => array( 'title' => '2.5rem', 'subtitle' => '1.2rem' ), 'klein' => array( 'title' => '2.5rem', 'subtitle' => '1.2rem' ),
'mittel' => array( 'title' => '3.5rem', 'subtitle' => '1.4rem' ), 'mittel' => array( 'title' => '3.5rem', 'subtitle' => '1.4rem' ),
'gross' => array( 'title' => '4.5rem', 'subtitle' => '1.6rem' ), 'gross' => array( 'title' => '4.5rem', 'subtitle' => '1.6rem' ),
'extra-gross' => array( 'title' => '5.5rem', 'subtitle' => '1.8rem' ), 'extra-gross' => array( 'title' => '5.5rem', 'subtitle' => '1.8rem' ),
); );
$chosen_sizes = isset( $font_sizes[$slider_size_setting] ) ? $font_sizes[$slider_size_setting] : $font_sizes['mittel']; $sizes = isset($font_sizes[$slider_size_setting]) ? $font_sizes[$slider_size_setting] : $font_sizes['mittel'];
$fonts_to_load = array($slider_font); $css = "
$fonts_url = 'https://fonts.googleapis.com/css2?family=' . implode( ':wght@400;600;700&family=', $fonts_to_load ) . '&display=swap';
?>
<link rel="stylesheet" href="<?php echo esc_url($fonts_url); ?>">
<style type="text/css">
:root { :root {
--primary-accent: <?php echo esc_attr($accent_color); ?>; --primary-accent: " . esc_attr($accent_color) . ";
--header-height: <?php echo esc_attr($header_height_value); ?>; --header-height: " . esc_attr($header_height_value) . ";
} }
.slider-title, .slider-subtitle, .hero-title, .hero-subtitle, .hero-button-1, .hero-button-2 { .slider-title, .slider-subtitle, .hero-title, .hero-subtitle, .hero-button-1, .hero-button-2 {
font-family: '<?php echo esc_attr($slider_font); ?>', sans-serif; font-family: '" . esc_attr($font) . "', sans-serif;
color: <?php echo esc_attr($slider_color); ?>; color: " . esc_attr($slider_color) . ";
}
.slider-title, .hero-title {
font-size: <?php echo esc_attr($chosen_sizes['title']); ?>;
}
.slider-subtitle, .hero-subtitle {
font-size: <?php echo esc_attr($chosen_sizes['subtitle']); ?>;
} }
.slider-title, .hero-title { font-size: " . esc_attr($sizes['title']) . "; }
.slider-subtitle, .hero-subtitle { font-size: " . esc_attr($sizes['subtitle']) . "; }
.site-header { border-bottom: 4px solid var(--primary-accent); }
.hero-slider { border-bottom: 4px solid var(--primary-accent); }
.site-footer { border-top: 4px solid var(--primary-accent); }
";
/* Trennlinie unter dem Header */ wp_add_inline_style( 'minecraft-modern-style', $css );
.site-header {
border-bottom: 4px solid var(--primary-accent);
}
/* Trennlinie unter dem Slider */
.hero-slider {
border-bottom: 4px solid var(--primary-accent);
}
/* Trennlinie am oberen Rand des Footers */
.site-footer {
border-top: 4px solid var(--primary-accent);
}
</style>
<?php
} }
add_action( 'wp_head', 'minecraft_modern_dynamic_css_output' ); add_action( 'wp_enqueue_scripts', 'minecraft_modern_enqueue_dynamic_styles', 20 );
// ========================================================================= // =========================================================================
// === THEME PRESET LOGIC (JAVASCRIPT) ===================================== // PRESET LOGIC (JS im Customizer)
// ========================================================================= // =========================================================================
function minecraft_preset_customize_js() { ?>
/** <script>
* Verbindet das Preset-Dropdown mit der Akzentfarbe-Einstellung. (function($){
* Sobald ein Preset gewählt wird, wird die Farbe im Customizer aktualisiert. var presetColors = { classic: '#00d4ff', nether: '#ff3333', end: '#aa00ff' };
*/ wp.customize.bind('ready', function(){
function minecraft_preset_customize_js() { wp.customize('theme_color_preset', function(setting){
?> setting.bind(function(to){
<script type="text/javascript"> if (presetColors[to]) {
(function($) { wp.customize('primary_accent_color').set(presetColors[to]);
// Definiere die Farben für die Presets }
var presetColors = {
'classic': '#00d4ff', // Diamant Blau
'nether': '#ff3333', // Lava Rot
'end': '#aa00ff' // Ender Purpur
};
// Warten bis der Customizer bereit ist
wp.customize.bind('ready', function() {
// Listener für das Preset-Dropdown
wp.customize('theme_color_preset', function(setting) {
setting.bind(function(to) {
// Wenn eine Farbe für das gewählte Preset existiert...
if (presetColors[to]) {
// ...setze die Akzentfarbe auf diesen Wert.
// Das aktualisiert auch den Color Picker und die Live-Vorschau.
wp.customize('primary_accent_color').set(presetColors[to]);
}
});
}); });
}); });
})(jQuery); });
})(jQuery);
</script> </script>
<?php <?php }
} add_action( 'customize_controls_print_footer_scripts', 'minecraft_preset_customize_js' );
add_action('customize_controls_print_footer_scripts', 'minecraft_preset_customize_js');
// =========================================================================
// FIX: slider_loop an JS übergeben (in functions.php in wp_localize_script ergänzen)
// Der folgende Filter hängt den Wert an sliderSettings an.
// Alternativ: direkt in functions.php bei wp_localize_script 'loop' hinzufügen.
// =========================================================================
add_filter( 'minecraft_modern_slider_settings', function( $settings ) {
$settings['loop'] = get_theme_mod('slider_loop', true) ? '1' : '0';
return $settings;
} );

View File

@@ -1,160 +1,141 @@
<?php <?php
/**
* Minecraft Modern Theme - Updater & Dashboard Status
*
* Diese Datei prüft auf neue Versionen via Gitea API und zeigt den Status im Dashboard an.
* Aus Sicherheitsgründen ist das automatische Update deaktiviert, um Datenverlust zu vermeiden.
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
// === THEME VERSION AUTOMATISCH AUS style.css LADEN (PARENT THEME PRIORITÄT) === class Minecraft_Modern_Theme_Manager {
function minecraft_modern_get_theme_version() {
// Holt das aktuell aktive Theme (child oder parent)
$theme = wp_get_theme();
// Wenn ein Child-Theme aktiv ist und ein Parent vorhanden ist, nutze die Parent-Version // BUG-FIX: Hardcoded 'Minecraft-Modern-Theme' schlug auf Linux-Servern
$parent = $theme->parent(); // (case-sensitive Dateisystem) fehl, wenn das Verzeichnis kleingeschrieben ist.
if ( $parent && $parent->exists() ) { // get_template() liefert immer den echten Verzeichnisnamen.
$parent_version = $parent->get( 'Version' ); private $theme_slug;
if ( ! empty( $parent_version ) ) { private $repo = 'M_Viper/Minecraft-Modern-Theme';
return $parent_version; private $transient_key = 'mm_theme_update_check';
public function __construct() {
$this->theme_slug = get_template();
add_action( 'admin_notices', [ $this, 'display_update_notice' ] );
add_action( 'wp_dashboard_setup', [ $this, 'add_dashboard_widget' ] );
add_action( 'admin_init', [ $this, 'handle_refresh_request' ] );
}
/**
* Holt die API-Daten von Gitea (mit Transient-Cache).
*/
private function get_latest_release() {
$update_data = get_transient( $this->transient_key );
if ( false === $update_data ) {
$api_url = "https://git.viper.ipv64.net/api/v1/repos/{$this->repo}/releases/latest";
$response = wp_remote_get( $api_url, [ 'timeout' => 10 ] );
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
set_transient( $this->transient_key, [ 'error' => true ], 2 * HOUR_IN_SECONDS );
return [ 'error' => true ];
}
$data = json_decode( wp_remote_retrieve_body( $response ) );
$new_version = ltrim( $data->tag_name, 'vV' );
$update_data = [
'version' => $new_version,
'url' => "https://git.viper.ipv64.net/{$this->repo}/releases",
];
set_transient( $this->transient_key, $update_data, 12 * HOUR_IN_SECONDS );
}
return $update_data;
}
/**
* Zeigt die gelbe Info-Box oben im Admin-Bereich an, wenn ein Update verfügbar ist.
*/
public function display_update_notice() {
if ( ! current_user_can( 'update_themes' ) ) return;
$latest = $this->get_latest_release();
if ( isset( $latest['error'] ) ) return;
$current_version = wp_get_theme( $this->theme_slug )->get( 'Version' );
if ( ! $current_version ) return;
if ( version_compare( $current_version, $latest['version'], '<' ) ) {
?>
<div class="notice notice-warning is-dismissible">
<p>
<span class="dashicons dashicons-update" style="color: #dba617; margin-right: 5px;"></span>
<strong>Minecraft Modern Update verfügbar:</strong>
Version <strong><?php echo esc_html( $latest['version'] ); ?></strong> ist bereit zum Download.
<a href="<?php echo esc_url( $latest['url'] ); ?>" target="_blank" style="margin-left: 10px; font-weight: bold;">
Zum Download &rarr;
</a>
</p>
</div>
<?php
} }
} }
// Fallback: Version des aktuell aktiven Themes (wenn kein Parent existiert oder Parent keine Version hat) /**
return $theme->get( 'Version' ); * Fügt das Widget zum WordPress-Dashboard hinzu.
} */
public function add_dashboard_widget() {
// === THEME UPDATE NOTIFICATION SYSTEM === wp_add_dashboard_widget(
'mm_theme_status_widget',
// Funktion zum Leeren des Caches (wenn man auf "Update prüfen" klickt) 'Minecraft Modern Theme Status',
function minecraft_modern_clear_cache() { [ $this, 'render_widget_content' ]
if ( isset( $_GET['mm_clear_cache'] ) && current_user_can( 'manage_options' ) ) {
check_admin_referer( 'mm_clear_cache_action' );
delete_transient( 'minecraft_modern_latest_release' );
wp_redirect( admin_url( 'index.php' ) );
exit;
}
}
add_action( 'admin_init', 'minecraft_modern_clear_cache' );
// Funktion zum Abrufen der neuesten Release-Informationen
function minecraft_modern_get_latest_release_info( $force_refresh = false ) {
$transient_key = 'minecraft_modern_latest_release';
// Wenn erzwungen wird (oder Cache leer), frische Daten holen
if ( $force_refresh ) {
delete_transient( $transient_key );
}
$release_info = get_transient( $transient_key );
if ( false === $release_info ) {
// Timeout auf 10 Sekunden erhöht für langsame Verbindungen
$response = wp_remote_get(
'https://git.viper.ipv64.net/api/v1/repos/M_Viper/Minecraft-Modern-Theme/releases/latest',
array( 'timeout' => 10 )
); );
}
if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
$body = wp_remote_retrieve_body( $response );
$release_data = json_decode( $body, true );
if ( $release_data && isset( $release_data['tag_name'] ) ) {
// Tag bereinigen (falls 'v' davor steht, z.B. v1.6 -> 1.6)
$tag_name = $release_data['tag_name'];
if ( strpos( $tag_name, 'v' ) === 0 ) {
$tag_name = ltrim( $tag_name, 'v' );
}
$release_info = array( /**
'version' => $tag_name, * HTML-Inhalt des Dashboard-Widgets.
'download_url' => $release_data['zipball_url'], */
'release_notes' => isset( $release_data['body'] ) ? $release_data['body'] : '', public function render_widget_content() {
'published_at' => isset( $release_data['published_at'] ) ? $release_data['published_at'] : '' $current_version = wp_get_theme( $this->theme_slug )->get( 'Version' );
); $latest = $this->get_latest_release();
// Cache für 6 Stunden echo '<div class="mm-status-widget">';
set_transient( $transient_key, $release_info, 6 * HOUR_IN_SECONDS ); echo '<p><span class="dashicons dashicons-admin-appearance"></span> <strong>Installiert:</strong> ' . esc_html( $current_version ?: '' ) . '</p>';
if ( isset( $latest['version'] ) ) {
echo '<p><span class="dashicons dashicons-cloud"></span> <strong>Aktuellste:</strong> ' . esc_html( $latest['version'] ) . '</p>';
if ( $current_version && version_compare( $current_version, $latest['version'], '<' ) ) {
echo '<div style="background: #fff8e5; border-left: 4px solid #ffb900; padding: 12px; margin: 15px 0;">';
echo '<p style="margin: 0 0 10px; color: #856404;"><strong>Update verfügbar!</strong></p>';
echo '<a href="' . esc_url( $latest['url'] ) . '" class="button button-primary" target="_blank">Download ZIP von Gitea</a>';
echo '</div>';
} else { } else {
// Fehlerhafte Daten leer cachen echo '<p style="color: #46b450; font-weight: bold; margin-top: 15px;">';
set_transient( $transient_key, array(), HOUR_IN_SECONDS ); echo '<span class="dashicons dashicons-yes"></span> Theme ist aktuell.</p>';
} }
} else { } else {
// Fehler beim Abrufen echo '<p style="color: #d63638;"><span class="dashicons dashicons-warning"></span> Prüfung fehlgeschlagen.</p>';
set_transient( $transient_key, array(), HOUR_IN_SECONDS ); }
$refresh_url = wp_nonce_url( admin_url( 'index.php?mm_refresh_check=1' ), 'mm_refresh_action' );
echo '<hr><p><small><a href="' . esc_url( $refresh_url ) . '">Update-Cache jetzt leeren</a></small></p>';
echo '</div>';
}
/**
* Verarbeitet den Klick auf "Update-Cache jetzt leeren".
*/
public function handle_refresh_request() {
if ( isset( $_GET['mm_refresh_check'] ) && check_admin_referer( 'mm_refresh_action' ) ) {
delete_transient( $this->transient_key );
wp_redirect( admin_url( 'index.php' ) );
exit;
} }
} }
return $release_info;
} }
// === BENACHRICHTIGUNG IM ADMIN-BEREICH === new Minecraft_Modern_Theme_Manager();
function minecraft_modern_show_update_notification() {
if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
return;
}
$current_version = minecraft_modern_get_theme_version();
$latest_release = minecraft_modern_get_latest_release_info();
if ( ! empty( $latest_release ) && isset( $latest_release['version'] ) && version_compare( $current_version, $latest_release['version'], '<' ) ) {
?>
<div class="notice notice-warning is-dismissible">
<h3><?php _e( 'Minecraft Modern Theme Update Available', 'minecraft-modern-theme' ); ?></h3>
<p>
<?php
printf(
__( 'You are using version %1$s of the Minecraft Modern Theme. Version %2$s is now available.', 'minecraft-modern-theme' ),
'<strong>' . esc_html( $current_version ) . '</strong>',
'<strong>' . esc_html( $latest_release['version'] ) . '</strong>'
);
?>
</p>
<p>
<a href="<?php echo esc_url( $latest_release['download_url'] ); ?>" class="button button-primary" target="_blank">
<?php _e( 'Download Latest Version', 'minecraft-modern-theme' ); ?>
</a>
<a href="https://git.viper.ipv64.net/M_Viper/Minecraft-Modern-Theme/releases" class="button" target="_blank">
<?php _e( 'View Release Notes', 'minecraft-modern-theme' ); ?>
</a>
</p>
</div>
<?php
}
}
add_action( 'admin_notices', 'minecraft_modern_show_update_notification' );
// === DASHBOARD WIDGET ===
function minecraft_modern_add_dashboard_widget() {
wp_add_dashboard_widget(
'minecraft_modern_update_widget',
'Minecraft Modern Theme Status',
'minecraft_modern_update_widget_function'
);
}
add_action( 'wp_dashboard_setup', 'minecraft_modern_add_dashboard_widget' );
function minecraft_modern_update_widget_function() {
$current_version = minecraft_modern_get_theme_version();
$latest_release = minecraft_modern_get_latest_release_info();
echo '<p><strong>' . __( 'Current Version:', 'minecraft-modern-theme' ) . '</strong> ' . esc_html( $current_version ) . '</p>';
if ( ! empty( $latest_release ) && isset( $latest_release['version'] ) ) {
echo '<p><strong>' . __( 'Latest Version:', 'minecraft-modern-theme' ) . '</strong> ' . esc_html( $latest_release['version'] ) . '</p>';
if ( version_compare( $current_version, $latest_release['version'], '<' ) ) {
echo '<p><strong>' . __( 'Status:', 'minecraft-modern-theme' ) . '</strong> <span style="color:#d63638;">' . __( 'Update Available', 'minecraft-modern-theme' ) . '</span></p>';
echo '<p><a href="' . esc_url( $latest_release['download_url'] ) . '" class="button button-primary" target="_blank">' . __( 'Download Update', 'minecraft-modern-theme' ) . '</a></p>';
} else {
echo '<p><strong>' . __( 'Status:', 'minecraft-modern-theme' ) . '</strong> <span style="color:#46b450;">' . __( 'Up to Date', 'minecraft-modern-theme' ) . '</span></p>';
}
} else {
echo '<p><strong>' . __( 'Status:', 'minecraft-modern-theme' ) . '</strong> ' . __( 'Unable to check for updates', 'minecraft-modern-theme' ) . '</p>';
}
// Link für "Jetzt prüfen"
$refresh_url = wp_nonce_url( admin_url( 'index.php?mm_clear_cache=1' ), 'mm_clear_cache_action' );
echo '<p><a href="' . esc_url( $refresh_url ) . '" onclick="return confirm(\'Cache leeren und neu prüfen?\');">' . __( 'Check for Updates Now', 'minecraft-modern-theme' ) . '</a></p>';
echo '<p><a href="https://git.viper.ipv64.net/M_Viper/Minecraft-Modern-Theme/releases" target="_blank">' . __( 'View All Releases', 'minecraft-modern-theme' ) . '</a></p>';
}

View File

@@ -3,25 +3,73 @@
<main id="primary" class="site-main"> <main id="primary" class="site-main">
<div class="container"> <div class="container">
<div class="content-area"> <div class="content-area">
<?php if ( have_posts() ) : ?> <?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class('post'); ?>> <div class="archive-posts-grid">
<?php if ( has_post_thumbnail() ) : ?> <?php while ( have_posts() ) : the_post(); ?>
<div class="post-thumbnail"> <article id="post-<?php the_ID(); ?>" <?php post_class('post archive-post-card'); ?>>
<a href="<?php the_permalink(); ?>"><?php the_post_thumbnail('medium_large'); ?></a>
<?php if ( has_post_thumbnail() ) : ?>
<div class="archive-card-thumb">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail('medium_large', array('loading' => 'lazy')); ?>
</a>
</div>
<?php endif; ?>
<div class="archive-card-body">
<?php $cats = get_the_category(); if ( $cats ) : ?>
<div class="archive-card-cats">
<a href="<?php echo esc_url( get_category_link( $cats[0]->term_id ) ); ?>" class="post-category-badge">
<?php echo esc_html( $cats[0]->name ); ?>
</a>
</div>
<?php endif; ?>
<h2 class="archive-card-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h2>
<div class="archive-card-meta">
<span><i class="fas fa-calendar-alt"></i> <?php echo esc_html( get_the_date() ); ?></span>
<span><i class="fas fa-user"></i> <?php the_author(); ?></span>
</div>
<div class="archive-card-excerpt">
<?php the_excerpt(); ?>
</div>
<a href="<?php the_permalink(); ?>" class="archive-card-read-more">
<?php _e('Weiterlesen', 'minecraft-modern-theme'); ?> <i class="fas fa-arrow-right"></i>
</a>
</div> </div>
<?php endif; ?> </article>
<div class="post-content"> <?php endwhile; ?>
<h2 class="post-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2> </div>
<div class="post-excerpt">
<?php the_excerpt(); ?> <!-- FIX: Pagination fehlte komplett -->
</div> <div class="archive-pagination">
</div> <?php
</article> the_posts_pagination( array(
<?php endwhile; ?> 'mid_size' => 2,
'prev_text' => '<i class="fas fa-chevron-left"></i> ' . __('Zurück', 'minecraft-modern-theme'),
'next_text' => __('Weiter', 'minecraft-modern-theme') . ' <i class="fas fa-chevron-right"></i>',
) );
?>
</div>
<?php else : ?> <?php else : ?>
<p><?php _e( 'Keine Beiträge gefunden.', 'minecraft-modern-theme' ); ?></p>
<div class="no-posts-found">
<i class="fas fa-inbox fa-2x"></i>
<p><?php _e('Keine Beiträge gefunden.', 'minecraft-modern-theme'); ?></p>
</div>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
</main> </main>

View File

@@ -7,8 +7,7 @@ document.addEventListener("DOMContentLoaded", function () {
if (position === "top") { if (position === "top") {
document.body.insertAdjacentElement('afterbegin', bar); document.body.insertAdjacentElement('afterbegin', bar);
} } else if (position === "below-slider") {
else if (position === "below-slider") {
const anchor = document.getElementById("mm-announcement-anchor"); const anchor = document.getElementById("mm-announcement-anchor");
if (anchor) { if (anchor) {
anchor.replaceWith(bar); anchor.replaceWith(bar);
@@ -16,69 +15,62 @@ document.addEventListener("DOMContentLoaded", function () {
const header = document.getElementById("masthead"); const header = document.getElementById("masthead");
if (header) header.insertAdjacentElement('afterend', bar); if (header) header.insertAdjacentElement('afterend', bar);
} }
} } else {
else if (position === "below-header") { // "below-header" und alle anderen Positionen
const header = document.getElementById("masthead");
if (header) header.insertAdjacentElement('afterend', bar);
}
else {
const header = document.getElementById("masthead"); const header = document.getElementById("masthead");
if (header) header.insertAdjacentElement('afterend', bar); if (header) header.insertAdjacentElement('afterend', bar);
} }
// --- 2. Schließen-Button ---
const closeBtn = bar.querySelector(".mm-announcement-close"); const closeBtn = bar.querySelector(".mm-announcement-close");
if (closeBtn) { if (closeBtn) {
closeBtn.addEventListener("click", function () { closeBtn.addEventListener("click", function () {
bar.style.display = "none"; bar.style.transition = 'opacity 0.3s ease';
bar.style.opacity = '0';
setTimeout(function() { bar.style.display = "none"; }, 300);
}); });
} }
// --- 2. Countdown Timer Logik --- // --- 3. Countdown Timer ---
const timerElement = document.querySelector('.mm-countdown-timer'); const timerElement = document.querySelector('.mm-countdown-timer');
if (timerElement) { if (timerElement) {
const targetDateString = timerElement.getAttribute('data-date'); const targetDateString = timerElement.getAttribute('data-date');
const expiredMessage = timerElement.getAttribute('data-expired'); const expiredMessage = timerElement.getAttribute('data-expired') || 'Abgelaufen';
// Prüfen ob Datum gesetzt ist
if (targetDateString) {
const countDownDate = new Date(targetDateString).getTime();
const updateTimer = setInterval(function() { if (!targetDateString) {
const now = new Date().getTime();
const distance = countDownDate - now;
if (distance < 0) {
clearInterval(updateTimer);
timerElement.innerHTML = expiredMessage;
// Optional: Rot einfärben wenn abgelaufen
timerElement.style.color = '#ff3333';
return;
}
// Zeitberechnung
const days = Math.floor(distance / (1000 * 60 * 60 * 24));
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
// Formatierung (z.B. 05 statt 5)
const fDays = days > 0 ? days + "d " : "";
const fHours = hours < 10 ? "0" + hours : hours;
const fMinutes = minutes < 10 ? "0" + minutes : minutes;
const fSeconds = seconds < 10 ? "0" + seconds : seconds;
timerElement.innerHTML = fDays + fHours + ":" + fMinutes + ":" + fSeconds;
}, 1000);
// Sofort einmal ausführen, um Ladezeit zu vermeiden
// Wir lösen das Interval manuell für den ersten Durchlauf aus,
// indem wir den Code kopieren oder den Timeout kurz setzen.
// Für diesen Fall reicht der erste Tick nach 1 Sekunde,
// aber wir können es direkt aufrufen:
// (Hier nutzen wir einfach den ersten Tick des Intervals)
} else {
timerElement.style.display = 'none'; timerElement.style.display = 'none';
return;
} }
const countDownDate = new Date(targetDateString).getTime();
// FIX: Tick-Funktion auslagern und SOFORT aufrufen kein 1s-Flackern beim Laden
function tick() {
const now = new Date().getTime();
const distance = countDownDate - now;
if (distance < 0) {
clearInterval(timerInterval);
timerElement.innerHTML = expiredMessage;
timerElement.style.color = '#ff3333';
return;
}
const days = Math.floor(distance / (1000 * 60 * 60 * 24));
const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((distance % (1000 * 60)) / 1000);
const fDays = days > 0 ? days + 'd ' : '';
const fHours = String(hours).padStart(2, '0');
const fMinutes = String(minutes).padStart(2, '0');
const fSeconds = String(seconds).padStart(2, '0');
timerElement.innerHTML = fDays + fHours + ':' + fMinutes + ':' + fSeconds;
}
tick(); // Sofort ausführen
const timerInterval = setInterval(tick, 1000);
} }
}); });

View File

@@ -0,0 +1 @@

View File

@@ -1,17 +1,124 @@
// /js/header-scroll.js // header-scroll.js kompakter Header beim Scrollen + Suche Toggle
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function () {
// WICHTIG: Das Skript nur ausführen, wenn wir NICHT im Customizer sind.
// Diese Prüfung ist robuster und funktioniert ohne PHP. // ── Customizer Guard ──────────────────────────────────────────────────
if ( window.location.href.includes('/wp-admin/customize.php') || ( window.parent && window.parent.wp && window.parent.wp.customize ) ) { if (
return; // Skript hier beenden, wenn wir im Customizer sind. window.location.href.includes('/wp-admin/customize.php') ||
(window.parent && window.parent.wp && window.parent.wp.customize)
) {
return;
} }
// ── Scroll-Effekt + kompakter Header ─────────────────────────────────
const header = document.querySelector('.site-header'); const header = document.querySelector('.site-header');
window.addEventListener('scroll', function() { const brandingRow = header ? header.querySelector('.header-row-branding') : null;
if (window.scrollY > 50) {
header.classList.add('scrolled'); if (header) {
} else { let lastScrollY = Math.max(window.scrollY, 0);
header.classList.remove('scrolled'); let directionAnchorY = lastScrollY;
} let isCompact = header.classList.contains('header-compact');
}); let lastStateChangeAt = 0;
let ticking = false;
const compactEnterThreshold = 110;
const compactExitThreshold = 24;
const directionThreshold = 10;
const expandDelta = 120;
const stateChangeCooldown = 260;
const setCompactState = function (nextCompact, now) {
if (isCompact === nextCompact) {
return;
}
isCompact = nextCompact;
lastStateChangeAt = now;
directionAnchorY = Math.max(window.scrollY, 0);
header.classList.toggle('header-compact', nextCompact);
if (brandingRow) {
brandingRow.classList.toggle('branding-hidden', nextCompact);
}
};
const updateHeaderState = function () {
ticking = false;
const now = window.performance && typeof window.performance.now === 'function'
? window.performance.now()
: Date.now();
const currentScrollY = Math.max(window.scrollY, 0);
const delta = currentScrollY - lastScrollY;
const canChangeState = now - lastStateChangeAt >= stateChangeCooldown;
header.classList.toggle('scrolled', currentScrollY > 50);
if (currentScrollY <= compactExitThreshold) {
setCompactState(false, now);
} else if (canChangeState && delta > directionThreshold) {
directionAnchorY = currentScrollY;
if (currentScrollY > compactEnterThreshold) {
setCompactState(true, now);
}
} else if (canChangeState && delta < -directionThreshold) {
if (directionAnchorY - currentScrollY >= expandDelta) {
setCompactState(false, now);
}
}
lastScrollY = currentScrollY;
};
const onScroll = function () {
if (ticking) {
return;
}
ticking = true;
window.requestAnimationFrame(updateHeaderState);
};
updateHeaderState();
window.addEventListener('scroll', onScroll, { passive: true });
}
// ── Suche Toggle ──────────────────────────────────────────────────────
const searchToggle = document.querySelector('.header-search-toggle');
const searchDropdown = document.querySelector('.header-search-dropdown');
if (searchToggle && searchDropdown) {
searchToggle.addEventListener('click', function (e) {
e.stopPropagation();
const isOpen = searchDropdown.classList.toggle('open');
searchToggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
searchDropdown.setAttribute('aria-hidden', isOpen ? 'false' : 'true');
if (isOpen) {
// Fokus ins Suchfeld setzen
const field = searchDropdown.querySelector('.search-field');
if (field) setTimeout(function () { field.focus(); }, 50);
}
});
// Schließen bei Außenklick
document.addEventListener('click', function (e) {
if (!searchDropdown.contains(e.target) && !searchToggle.contains(e.target)) {
searchDropdown.classList.remove('open');
searchToggle.setAttribute('aria-expanded', 'false');
searchDropdown.setAttribute('aria-hidden', 'true');
}
});
// Schließen mit Escape
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && searchDropdown.classList.contains('open')) {
searchDropdown.classList.remove('open');
searchToggle.setAttribute('aria-expanded', 'false');
searchDropdown.setAttribute('aria-hidden', 'true');
searchToggle.focus();
}
});
}
}); });

View File

@@ -1,25 +1,30 @@
jQuery(document).ready(function($) { jQuery(document).ready(function($) {
const slider = $('#minecraft-avatar-slider'); const slider = $('#minecraft-avatar-slider');
const slides = slider.find('.avatar-slide'); const slides = slider.find('.avatar-slide');
// Nur starten, wenn es mehr als einen Slide gibt
if (slides.length > 1) {
let currentIndex = 0;
function showSlide(index) { if ( slides.length === 0 ) return;
slides.removeClass('avatar-slide-active');
slides.eq(index).addClass('avatar-slide-active');
}
let currentIndex = 0;
function showSlide(index) {
slides.removeClass('avatar-slide-active');
slides.eq(index).addClass('avatar-slide-active');
}
// FIX: Ersten Slide sofort anzeigen, nicht erst nach dem ersten Interval-Tick
showSlide(0);
// Nur Interval starten wenn es mehr als einen Slide gibt
if ( slides.length > 1 ) {
function nextSlide() { function nextSlide() {
currentIndex = (currentIndex + 1) % slides.length; currentIndex = (currentIndex + 1) % slides.length;
showSlide(currentIndex); showSlide(currentIndex);
} }
// Geschwindigkeit aus den WordPress-Einstellungen holen const speed = ( typeof avatarSliderSettings !== 'undefined' && avatarSliderSettings.speed )
const speed = avatarSliderSettings.speed || 4000; ? avatarSliderSettings.speed
: 4000;
// Den Slider alle X Sekunden wechseln
setInterval(nextSlide, speed); setInterval(nextSlide, speed);
} }
}); });

View File

@@ -0,0 +1,63 @@
// Lädt skinview3d von CDN
(function(){
if(document.getElementById('skinview3d-cdn')) return;
var s = document.createElement('script');
s.id = 'skinview3d-cdn';
s.src = 'https://unpkg.com/skinview3d@4.1.1/bundles/skinview3d.min.js';
s.onload = function() {
window.skinview3dReady = true;
};
document.head.appendChild(s);
})();
window.showMinecraftSkinModal = function(uuid) {
if(document.getElementById('minecraft-skin-modal')) return;
var modal = document.createElement('div');
modal.id = 'minecraft-skin-modal';
modal.innerHTML = `
<div class="sv3d-modal-bg"></div>
<div class="sv3d-modal-content">
<button class="sv3d-modal-close" aria-label="Schließen">&times;</button>
<div id="sv3d-canvas-wrap" style="width:320px;height:320px;"></div>
</div>
`;
document.body.appendChild(modal);
document.querySelector('.sv3d-modal-close').onclick = function(){ modal.remove(); };
document.querySelector('.sv3d-modal-bg').onclick = function(){ modal.remove(); };
function renderSkin() {
var skinUrl = `https://crafatar.com/skins/${uuid}`;
var canvas = document.createElement('canvas');
canvas.width = 320; canvas.height = 320;
document.getElementById('sv3d-canvas-wrap').appendChild(canvas);
var viewer = new skinview3d.SkinViewer({
canvas: canvas,
width: 320,
height: 320,
skin: skinUrl
});
viewer.controls.enableZoom = false;
viewer.animation = new skinview3d.WalkingAnimation();
viewer.animation.speed = 1.2;
viewer.animation.play();
}
if(window.skinview3dReady) renderSkin();
else {
var check = setInterval(function(){
if(window.skinview3dReady) { clearInterval(check); renderSkin(); }
}, 100);
}
};
// Avatar-Widget-Click-Handler
window.addEventListener('DOMContentLoaded', function(){
var widget = document.getElementById('minecraft-avatar-widget');
if(widget) {
widget.style.cursor = 'pointer';
widget.title = 'Klicke für 3D Skin-Ansicht';
widget.onclick = function(){
var uuid = widget.getAttribute('data-uuid');
if(uuid) window.showMinecraftSkinModal(uuid);
};
}
});

View File

@@ -1,40 +1,174 @@
( function() { ( function () {
const siteNavigation = document.getElementById( 'site-navigation' ); 'use strict';
const menuToggle = siteNavigation.querySelector( '.menu-toggle' );
var isSidebarLayout = document.querySelector( '.site-header--sidebar' ) !== null;
if ( isSidebarLayout ) {
// --- Panel öffnen / schließen ---
var panel = document.getElementById( 'header-sidebar' );
var overlay = document.getElementById( 'sidebar-overlay' );
var openBtn = document.querySelector( '.sidebar-menu-toggle' );
var closeBtn = document.querySelector( '.sidebar-menu-close' );
function openPanel() {
if ( ! panel ) return;
panel.classList.add( 'is-open' );
panel.setAttribute( 'aria-hidden', 'false' );
if ( openBtn ) openBtn.setAttribute( 'aria-expanded', 'true' );
if ( overlay ) overlay.classList.add( 'is-visible' );
document.body.classList.add( 'sidebar-nav-open' );
}
function closePanel() {
if ( ! panel ) return;
panel.classList.remove( 'is-open' );
panel.setAttribute( 'aria-hidden', 'true' );
if ( openBtn ) openBtn.setAttribute( 'aria-expanded', 'false' );
if ( overlay ) overlay.classList.remove( 'is-visible' );
document.body.classList.remove( 'sidebar-nav-open' );
}
if ( openBtn ) openBtn.addEventListener( 'click', openPanel );
if ( closeBtn ) closeBtn.addEventListener( 'click', closePanel );
if ( overlay ) overlay.addEventListener( 'click', closePanel );
document.addEventListener( 'keydown', function ( e ) {
if ( e.key === 'Escape' ) closePanel();
} );
// --- Submenu Flyout ---
var sidebarNav = document.getElementById( 'site-navigation' );
if ( sidebarNav ) {
sidebarNav.querySelectorAll( '.menu-item-has-children' ).forEach( function ( item ) {
var subMenu = item.querySelector( ':scope > .sub-menu' );
if ( ! subMenu ) return;
var isOpen = false;
var moveHdlr = null;
function showFlyout() {
if ( isOpen ) return;
isOpen = true;
subMenu.style.top = item.getBoundingClientRect().top + 'px';
item.classList.add( 'flyout-open' );
// Erkennungszone berechnen:
// - vertikal: Höhe des li-Elements
// - horizontal: von linkem Rand der Sidebar (0) bis
// rechtem Rand des Flyout-Panels
// → voller horizontaler Streifen für diesen Menüpunkt
moveHdlr = function ( e ) {
var ir = item.getBoundingClientRect();
var sr = subMenu.getBoundingClientRect();
var zoneLeft = 0; // Bildschirmrand links
var zoneRight = Math.max( ir.right, sr.right ); // bis Ende Flyout
var zoneTop = ir.top; // oberer Rand des li
var zoneBottom = ir.bottom; // unterer Rand des li
var inZone = e.clientX >= zoneLeft &&
e.clientX <= zoneRight &&
e.clientY >= zoneTop &&
e.clientY <= zoneBottom;
if ( ! inZone ) hideFlyout();
};
document.addEventListener( 'mousemove', moveHdlr );
}
function hideFlyout() {
if ( ! isOpen ) return;
isOpen = false;
item.classList.remove( 'flyout-open' );
if ( moveHdlr ) {
document.removeEventListener( 'mousemove', moveHdlr );
moveHdlr = null;
}
}
item.addEventListener( 'mouseenter', showFlyout );
// Klick-Toggle für Touch / Tastatur
var btn = document.createElement( 'button' );
btn.className = 'submenu-toggle';
btn.setAttribute( 'aria-expanded', 'false' );
btn.innerHTML = '<i class="fas fa-chevron-down"></i>';
var link = item.querySelector( ':scope > a' );
if ( link ) link.insertAdjacentElement( 'afterend', btn );
btn.addEventListener( 'click', function ( e ) {
e.stopPropagation();
var open = item.classList.toggle( 'active' );
btn.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
if ( open ) {
subMenu.style.top = item.getBoundingClientRect().top + 'px';
item.classList.add( 'flyout-open' );
isOpen = true;
} else {
hideFlyout();
}
} );
} );
}
// Early exit wenn kein Toggle da ist
if ( ! menuToggle ) {
return; return;
} }
// Toggle Klassen hinzufügen (Menü öffnen/schließen) // =========================================================================
menuToggle.addEventListener( 'click', function() { // CLASSIC / CENTERED / MEGA
siteNavigation.classList.toggle( 'toggled' ); // =========================================================================
var siteNavigation = document.getElementById( 'site-navigation' );
// Aria States aktualisieren if ( ! siteNavigation ) return;
if ( menuToggle.getAttribute( 'aria-expanded' ) === 'true' ) {
menuToggle.setAttribute( 'aria-expanded', 'false' );
menuToggle.innerHTML = '<i class="fas fa-bars"></i>';
} else {
menuToggle.setAttribute( 'aria-expanded', 'true' );
menuToggle.innerHTML = '<i class="fas fa-times"></i>';
}
} );
// Mobile Submenu Toggle (Klick auf Parent-Item öffnet Untermenü) var menuToggle = siteNavigation.querySelector( '.menu-toggle' );
const subMenuParents = siteNavigation.querySelectorAll( '.menu-item-has-children' ); var subMenuParents = siteNavigation.querySelectorAll( '.menu-item-has-children' );
subMenuParents.forEach( function( subMenuParent ) { if ( menuToggle ) {
subMenuParent.addEventListener( 'click', function( e ) { menuToggle.addEventListener( 'click', function () {
// Nur auf Mobil aktivieren (Media Query Check) var expanded = siteNavigation.classList.toggle( 'toggled' );
if ( window.innerWidth <= 992 ) { this.setAttribute( 'aria-expanded', String( expanded ) );
// Optional: Verhindern, dass der Link geklickt wird, wenn man nur das Menü öffnen will this.innerHTML = expanded ? '<i class="fas fa-times"></i>' : '<i class="fas fa-bars"></i>';
// e.preventDefault(); } );
document.addEventListener( 'click', function ( e ) {
// Klasse 'active' umschalten für das CSS Display if ( siteNavigation.classList.contains( 'toggled' ) && ! siteNavigation.contains( e.target ) ) {
this.classList.toggle( 'active' ); siteNavigation.classList.remove( 'toggled' );
menuToggle.setAttribute( 'aria-expanded', 'false' );
menuToggle.innerHTML = '<i class="fas fa-bars"></i>';
} }
} ); } );
window.addEventListener( 'resize', function () {
if ( window.innerWidth > 992 ) {
siteNavigation.classList.remove( 'toggled' );
menuToggle.setAttribute( 'aria-expanded', 'false' );
menuToggle.innerHTML = '<i class="fas fa-bars"></i>';
subMenuParents.forEach( function ( i ) { i.classList.remove( 'active' ); } );
}
} );
}
subMenuParents.forEach( function ( item ) {
var btn = document.createElement( 'button' );
btn.className = 'submenu-toggle';
btn.setAttribute( 'aria-expanded', 'false' );
btn.innerHTML = '<i class="fas fa-chevron-down"></i>';
var link = item.querySelector( ':scope > a' );
if ( link ) link.insertAdjacentElement( 'afterend', btn );
btn.addEventListener( 'click', function ( e ) {
e.stopPropagation();
var open = item.classList.toggle( 'active' );
btn.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
} );
} );
document.addEventListener( 'click', function ( e ) {
if ( ! e.target.closest( '.menu-item-has-children' ) ) {
subMenuParents.forEach( function ( item ) {
item.classList.remove( 'active' );
var b = item.querySelector( '.submenu-toggle' );
if ( b ) b.setAttribute( 'aria-expanded', 'false' );
} );
}
} ); } );
} )(); } )();

View File

@@ -1,24 +1,25 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Hole den Slider-Container
const heroSlider = document.querySelector('.hero-slider');
// Stelle sicher, dass der Slider auf der Seite existiert const heroSlider = document.querySelector('.hero-slider');
if (!heroSlider) { if (!heroSlider) return;
// BUG-FIX: sliderSettings wird via wp_localize_script gesetzt. Fehlt es
// (z.B. wegen Caching oder falschem Enqueue), würde ein ReferenceError
// den gesamten Slider-Init abschießen.
if (typeof sliderSettings === 'undefined') {
console.warn('Minecraft Modern Theme: sliderSettings nicht gefunden. Slider wird nicht initialisiert.');
heroSlider.classList.add('swiper-initialized'); // Opacity-Fix trotzdem aufheben
return; return;
} }
// Konfiguration für den Slider vorbereiten
const swiperConfig = { const swiperConfig = {
// Der Effekt ist jetzt fest auf "Überblenden" eingestellt
effect: 'fade', effect: 'fade',
fadeEffect: { fadeEffect: {
crossFade: true crossFade: true
}, },
// Loop-Einstellung ist jetzt DYNAMISCH
loop: sliderSettings.loop === '1', loop: sliderSettings.loop === '1',
// Autoplay
autoplay: { autoplay: {
delay: 5000, delay: 5000,
disableOnInteraction: false, disableOnInteraction: false,
@@ -26,27 +27,24 @@ document.addEventListener('DOMContentLoaded', function() {
pauseOnMouseEnter: true, pauseOnMouseEnter: true,
// Prüfe, ob die Pfeile NICHT ausgeblendet werden sollen
navigation: sliderSettings.hideArrows !== '1' ? { navigation: sliderSettings.hideArrows !== '1' ? {
nextEl: '.swiper-button-next', nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev', prevEl: '.swiper-button-prev',
} : false, } : false,
// Prüfe, ob die Paginierung NICHT ausgeblendet werden soll
pagination: sliderSettings.hidePagination !== '1' ? { pagination: sliderSettings.hidePagination !== '1' ? {
el: '.swiper-pagination', el: '.swiper-pagination',
clickable: true, clickable: true,
} : false, } : false,
on: { on: {
init: function () { init: function () {
setTimeout(() => { setTimeout(function() {
heroSlider.classList.add('swiper-initialized'); heroSlider.classList.add('swiper-initialized');
}, 50); }, 50);
}, },
}, },
}; };
// Initialisiere den Slider mit der konfigurierten Optionen
new Swiper('.hero-slider', swiperConfig); new Swiper('.hero-slider', swiperConfig);
}); });

View File

@@ -1,32 +1,53 @@
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const html = document.documentElement; const toggle = document.querySelector('.theme-toggle');
const toggle = document.querySelector('.theme-toggle'); if ( ! toggle ) return;
const iconMoon = toggle.querySelector('.icon-moon');
const iconSun = toggle.querySelector('.icon-sun');
const defaultMode = typeof sliderSettings !== 'undefined' ? sliderSettings.defaultMode : 'dark'; const html = document.documentElement;
const saved = localStorage.getItem('themeMode') || defaultMode; const iconMoon = toggle.querySelector('.icon-moon');
const iconSun = toggle.querySelector('.icon-sun');
if (saved === 'light') { const defaultMode = typeof sliderSettings !== 'undefined' && sliderSettings.defaultMode
html.classList.add('light-mode'); ? sliderSettings.defaultMode
iconMoon.style.display = 'none'; : 'dark';
iconSun.style.display = 'block';
} else {
iconMoon.style.display = 'block';
iconSun.style.display = 'none';
}
toggle.addEventListener('click', function () { // Gespeicherten Wert laden oder OS-Schema/Theme-Default als Fallback
html.classList.toggle('light-mode'); let saved = localStorage.getItem('themeMode');
if ( ! saved ) {
saved = ( window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches )
? 'light'
: defaultMode;
}
if (html.classList.contains('light-mode')) { function applyMode( mode ) {
iconMoon.style.display = 'none'; if ( mode === 'light' ) {
iconSun.style.display = 'block'; html.classList.add('light-mode');
localStorage.setItem('themeMode', 'light'); if ( iconMoon ) iconMoon.style.display = 'none';
} else { if ( iconSun ) iconSun.style.display = 'block';
iconMoon.style.display = 'block'; } else {
iconSun.style.display = 'none'; html.classList.remove('light-mode');
localStorage.setItem('themeMode', 'dark'); if ( iconMoon ) iconMoon.style.display = 'block';
} if ( iconSun ) iconSun.style.display = 'none';
}); }
}
applyMode( saved );
// BUG-FIX: Vorher wurde html.classList.toggle() aufgerufen und danach
// applyMode() das hat die Klasse doppelt manipuliert. Jetzt lesen wir
// den aktuellen Zustand aus und rufen nur applyMode() auf.
toggle.addEventListener('click', function () {
const isCurrentlyLight = html.classList.contains('light-mode');
const newMode = isCurrentlyLight ? 'dark' : 'light';
applyMode( newMode );
localStorage.setItem('themeMode', newMode);
});
// Live-Reaktion auf OS-Umschalten (nur wenn keine manuelle Auswahl)
if ( window.matchMedia ) {
window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', function( e ) {
if ( ! localStorage.getItem('themeMode') ) {
applyMode( e.matches ? 'light' : 'dark' );
}
});
}
}); });

View File

@@ -0,0 +1,26 @@
<?php
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
$posts = get_posts(array(
'post_type' => 'mm_livestream',
'posts_per_page' => -1,
'post_status' => 'any',
));
echo "Gefundene Livestream Posts: " . count($posts) . "\n\n";
foreach ($posts as $p) {
echo "ID: {$p->ID}\n";
echo "Titel: {$p->post_title}\n";
echo "Status: {$p->post_status}\n";
$profile = get_post_meta($p->ID, '_mm_livestream_url', true);
$player = get_post_meta($p->ID, '_mm_livestream_player_url', true);
$owner = get_post_meta($p->ID, '_mm_livestream_owner', true);
echo "Owner: {$owner}\n";
echo "Profile: {$profile}\n";
echo "Player: {$player}\n";
echo "---\n\n";
}

View File

@@ -0,0 +1,240 @@
<?php get_header(); ?>
<div class="container site-main">
<div class="content-area">
<div class="bewerbung-container">
<header class="page-header">
<h1 class="page-title">
<i class="fas fa-clipboard-list"></i>
<?php echo esc_html( get_theme_mod('mm_bewerbung_title', __('Bewirb dich bei uns!', 'minecraft-modern-theme')) ); ?>
</h1>
<p class="page-description">
<?php echo esc_html( get_theme_mod('mm_bewerbung_desc', __('Du möchtest Teil unseres Teams werden? Füll das Formular aus und wir melden uns bei dir.', 'minecraft-modern-theme')) ); ?>
</p>
</header>
<?php if ( ! get_theme_mod('mm_bewerbung_enabled', false) ) : ?>
<div class="bewerbung-disabled">
<i class="fas fa-lock"></i>
<p><?php _e('Bewerbungen sind momentan nicht möglich.', 'minecraft-modern-theme'); ?></p>
</div>
<?php else : ?>
<div id="bewerbung-success" class="bewerbung-success" style="display:none;">
<i class="fas fa-check-circle"></i>
<p id="bewerbung-success-msg"></p>
</div>
<form id="bewerbung-form" class="bewerbung-form" novalidate>
<?php wp_nonce_field('mm_bewerbung_nonce', 'mm_bew_nonce_field'); ?>
<div class="bew-grid">
<!-- Minecraft Username -->
<div class="bew-field">
<label for="bew_mc_name" class="bew-label">
<i class="fas fa-gamepad"></i>
<?php _e('Minecraft Username', 'minecraft-modern-theme'); ?>
<span class="bew-required">*</span>
</label>
<div class="bew-mc-wrap">
<input type="text" id="bew_mc_name" name="mc_name"
class="bew-input" required maxlength="16"
placeholder="z.B. Steve_123"
autocomplete="off">
<div class="bew-mc-avatar" id="bew-mc-avatar">
<img src="" alt="" id="bew-mc-avatar-img" style="display:none;">
<i class="fas fa-user" id="bew-mc-avatar-placeholder"></i>
</div>
</div>
<p class="bew-hint"><?php _e('Dein exakter Minecraft-Username (Groß-/Kleinschreibung beachten)', 'minecraft-modern-theme'); ?></p>
</div>
<!-- Discord Username -->
<div class="bew-field">
<label for="bew_discord" class="bew-label">
<i class="fab fa-discord"></i>
<?php _e('Discord Username', 'minecraft-modern-theme'); ?>
<span class="bew-required">*</span>
</label>
<input type="text" id="bew_discord" name="discord"
class="bew-input" required maxlength="50"
placeholder="z.B. Steve#1234 oder @steve">
<p class="bew-hint"><?php _e('Dein Discord-Name damit wir dich erreichen können', 'minecraft-modern-theme'); ?></p>
</div>
<!-- Alter -->
<div class="bew-field bew-field--small">
<label for="bew_alter" class="bew-label">
<i class="fas fa-birthday-cake"></i>
<?php _e('Alter', 'minecraft-modern-theme'); ?>
<span class="bew-required">*</span>
</label>
<input type="number" id="bew_alter" name="alter"
class="bew-input" required min="1" max="99"
placeholder="z.B. 16">
<?php
$min = absint(get_theme_mod('mm_bewerbung_min_alter', 14));
if ($min > 0) :
?>
<p class="bew-hint"><?php printf( __('Mindestalter: %d Jahre', 'minecraft-modern-theme'), $min ); ?></p>
<?php endif; ?>
</div>
</div><!-- .bew-grid -->
<!-- Warum mitspielen -->
<div class="bew-field">
<label for="bew_warum" class="bew-label">
<i class="fas fa-question-circle"></i>
<?php _e('Warum möchtest du mitspielen?', 'minecraft-modern-theme'); ?>
<span class="bew-required">*</span>
</label>
<textarea id="bew_warum" name="warum" class="bew-textarea" required
rows="4" maxlength="1000"
placeholder="<?php esc_attr_e('Erzähl uns warum du auf unseren Server möchtest und was du dir davon erhoffst...', 'minecraft-modern-theme'); ?>"></textarea>
<div class="bew-char-count"><span id="warum-count">0</span>/1000</div>
</div>
<!-- Erfahrung / Vorstellung -->
<div class="bew-field">
<label for="bew_erfahrung" class="bew-label">
<i class="fas fa-star"></i>
<?php _e('Erfahrung & Vorstellung', 'minecraft-modern-theme'); ?>
<span class="bew-required">*</span>
</label>
<textarea id="bew_erfahrung" name="erfahrung" class="bew-textarea" required
rows="5" maxlength="2000"
placeholder="<?php esc_attr_e('Stell dich kurz vor: Wie lange spielst du Minecraft? Welche Erfahrungen bringst du mit? Was macht dich besonders?', 'minecraft-modern-theme'); ?>"></textarea>
<div class="bew-char-count"><span id="erfahrung-count">0</span>/2000</div>
</div>
<!-- Fehler-Meldung -->
<div id="bewerbung-error" class="bewerbung-error" style="display:none;">
<i class="fas fa-exclamation-triangle"></i>
<span id="bewerbung-error-msg"></span>
</div>
<!-- Submit -->
<div class="bew-submit-wrap">
<button type="submit" id="bewerbung-submit" class="bew-submit-btn">
<i class="fas fa-paper-plane"></i>
<?php _e('Bewerbung absenden', 'minecraft-modern-theme'); ?>
</button>
<p class="bew-submit-hint">
<i class="fas fa-shield-alt"></i>
<?php _e('Mit * markierte Felder sind Pflichtfelder. Deine Daten werden nur intern gespeichert.', 'minecraft-modern-theme'); ?>
</p>
</div>
</form>
<?php endif; ?>
</div><!-- .bewerbung-container -->
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var form = document.getElementById('bewerbung-form');
var submitBtn = document.getElementById('bewerbung-submit');
var errorBox = document.getElementById('bewerbung-error');
var errorMsg = document.getElementById('bewerbung-error-msg');
var successBox = document.getElementById('bewerbung-success');
var successMsg = document.getElementById('bewerbung-success-msg');
if (!form) return;
// ── Minecraft Avatar live laden ──
var mcInput = document.getElementById('bew_mc_name');
var avatarImg = document.getElementById('bew-mc-avatar-img');
var avatarIcon = document.getElementById('bew-mc-avatar-placeholder');
var avatarTimer;
if (mcInput) {
mcInput.addEventListener('input', function() {
clearTimeout(avatarTimer);
var val = this.value.trim();
if (val.length >= 3) {
avatarTimer = setTimeout(function() {
var url = 'https://mc-heads.net/avatar/' + encodeURIComponent(val) + '/40';
avatarImg.src = url;
avatarImg.style.display = 'block';
avatarIcon.style.display = 'none';
avatarImg.onerror = function() {
avatarImg.style.display = 'none';
avatarIcon.style.display = 'block';
};
}, 600);
} else {
avatarImg.style.display = 'none';
avatarIcon.style.display = 'block';
}
});
}
// ── Zeichenzähler ──
function addCounter(textareaId, countId) {
var ta = document.getElementById(textareaId);
var ct = document.getElementById(countId);
if (!ta || !ct) return;
ta.addEventListener('input', function() {
ct.textContent = this.value.length;
ct.style.color = this.value.length > this.maxLength * 0.9 ? '#ff6b6b' : '';
});
}
addCounter('bew_warum', 'warum-count');
addCounter('bew_erfahrung', 'erfahrung-count');
// ── Formular absenden ──
form.addEventListener('submit', function(e) {
e.preventDefault();
errorBox.style.display = 'none';
var nonce = document.getElementById('mm_bew_nonce_field');
if (!nonce) return;
var data = new FormData();
data.append('action', 'mm_submit_bewerbung');
data.append('nonce', nonce.value);
data.append('mc_name', document.getElementById('bew_mc_name').value.trim());
data.append('discord', document.getElementById('bew_discord').value.trim());
data.append('alter', document.getElementById('bew_alter').value.trim());
data.append('warum', document.getElementById('bew_warum').value.trim());
data.append('erfahrung', document.getElementById('bew_erfahrung').value.trim());
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <?php echo esc_js(__("Wird gesendet...","minecraft-modern-theme")); ?>';
fetch('<?php echo esc_url(admin_url("admin-ajax.php")); ?>', {
method: 'POST',
body: data
})
.then(function(r){ return r.json(); })
.then(function(res) {
if (res.success) {
form.style.display = 'none';
successMsg.textContent = res.data.msg;
successBox.style.display = 'flex';
window.scrollTo({ top: successBox.offsetTop - 80, behavior: 'smooth' });
} else {
errorMsg.textContent = res.data.msg;
errorBox.style.display = 'flex';
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> <?php echo esc_js(__("Bewerbung absenden","minecraft-modern-theme")); ?>';
window.scrollTo({ top: errorBox.offsetTop - 80, behavior: 'smooth' });
}
})
.catch(function() {
errorMsg.textContent = '<?php echo esc_js(__("Verbindungsfehler. Bitte erneut versuchen.","minecraft-modern-theme")); ?>';
errorBox.style.display = 'flex';
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> <?php echo esc_js(__("Bewerbung absenden","minecraft-modern-theme")); ?>';
});
});
});
</script>
<?php get_footer(); ?>

View File

@@ -9,76 +9,145 @@ get_header(); ?>
<div id="primary" class="content-area"> <div id="primary" class="content-area">
<main id="main" class="site-main"> <main id="main" class="site-main">
<div class="custom-auth-container"> <div class="custom-auth-container">
<?php if (is_user_logged_in()) : ?>
<!-- Wenn der Benutzer angemeldet ist, zeige eine Nachricht und einen Logout-Link --> <?php if ( is_user_logged_in() ) : ?>
<!-- Eingeloggt -->
<div class="logged-in-message"> <div class="logged-in-message">
<h2>Willkommen zurück, <?php echo esc_html(wp_get_current_user()->display_name); ?>!</h2> <h2><?php printf( __('Willkommen zurück, %s!', 'minecraft-modern-theme'), esc_html( wp_get_current_user()->display_name ) ); ?></h2>
<p>Du bist bereits angemeldet.</p> <p><?php _e('Du bist bereits angemeldet.', 'minecraft-modern-theme'); ?></p>
<p> <p>
<a href="<?php echo esc_url(wp_logout_url(home_url())); ?>" class="button">Abmelden</a> <a href="<?php echo esc_url( wp_logout_url( home_url() ) ); ?>" class="button">
<a href="<?php echo esc_url(admin_url()); ?>" class="button">Zum Dashboard</a> <i class="fas fa-sign-out-alt"></i> <?php _e('Abmelden', 'minecraft-modern-theme'); ?>
</a>
<a href="<?php echo esc_url( admin_url() ); ?>" class="button">
<i class="fas fa-tachometer-alt"></i> <?php _e('Zum Dashboard', 'minecraft-modern-theme'); ?>
</a>
</p> </p>
</div> </div>
<?php else : ?>
<!-- Wenn der Benutzer nicht angemeldet ist, zeige das Registrierungsformular --> <?php elseif ( get_option('users_can_register') ) : ?>
<h1 class="auth-title">Willkommen auf <?php bloginfo('name'); ?></h1>
<p class="auth-subtitle">Erstelle deinen Account und werde Teil unserer Community!</p> <!-- Registrierung erlaubt -->
<h1 class="auth-title"><?php printf( __('Willkommen auf %s', 'minecraft-modern-theme'), get_bloginfo('name') ); ?></h1>
<p class="auth-subtitle"><?php _e('Erstelle deinen Account und werde Teil unserer Community!', 'minecraft-modern-theme'); ?></p>
<?php <?php
// Zeige das Registrierungsformular an // Fehlermeldungen aus der Query-String (z.B. nach gescheiterter Registrierung)
$args = array( if ( isset($_GET['registration']) && $_GET['registration'] === 'disabled' ) : ?>
'echo' => true, <div class="auth-notice auth-notice-error">
'redirect' => home_url('/login/?checkemail=registered'), // Weiterleitung nach der Registrierung <i class="fas fa-exclamation-circle"></i>
<?php _e('Die Registrierung ist zurzeit deaktiviert.', 'minecraft-modern-theme'); ?>
</div>
<?php endif;
if ( isset($_GET['checkemail']) && $_GET['checkemail'] === 'registered' ) : ?>
<div class="auth-notice auth-notice-success">
<i class="fas fa-check-circle"></i>
<?php _e('Registrierung erfolgreich! Prüfe deine E-Mails für das Passwort.', 'minecraft-modern-theme'); ?>
</div>
<?php endif; ?>
<?php
wp_register_form( array(
'redirect' => home_url('/login/?checkemail=registered'),
'form_id' => 'custom_registerform', 'form_id' => 'custom_registerform',
'label_username' => __( 'Benutzername' ), 'label_username' => __('Benutzername', 'minecraft-modern-theme'),
'label_email' => __( 'E-Mail-Adresse' ), 'label_email' => __('E-Mail-Adresse', 'minecraft-modern-theme'),
'label_password' => __( 'Passwort' ), 'label_password' => __('Passwort', 'minecraft-modern-theme'),
'label_remember' => __( 'Angemeldet bleiben' ), 'label_log_in' => __('Registrieren', 'minecraft-modern-theme'),
'label_log_in' => __( 'Registrieren' ),
'id_username' => 'user_login', 'id_username' => 'user_login',
'id_email' => 'user_email', 'id_email' => 'user_email',
'id_password' => 'user_pass',
'id_remember' => 'rememberme',
'id_submit' => 'wp-submit', 'id_submit' => 'wp-submit',
'remember' => true, 'value_username' => isset($_GET['user_login']) ? sanitize_user( $_GET['user_login'] ) : '',
'value_username' => NULL, ) );
'value_remember' => false
);
wp_register_form($args);
?> ?>
<div class="login-form-link"> <div class="login-form-link">
<p>Schon hast du einen Account? <a href="<?php echo esc_url(wp_login_url()); ?>">Hier anmelden</a>.</p> <p><?php _e('Schon einen Account?', 'minecraft-modern-theme'); ?>
<a href="<?php echo esc_url( wp_login_url() ); ?>">
<?php _e('Hier anmelden', 'minecraft-modern-theme'); ?>
</a>
</p>
</div> </div>
<?php else : ?>
<!-- FIX: Registrierung deaktiviert saubere Fehlermeldung statt kaputter Formulare -->
<div class="auth-registration-closed">
<div class="auth-icon"><i class="fas fa-lock fa-3x"></i></div>
<h2><?php _e('Registrierung geschlossen', 'minecraft-modern-theme'); ?></h2>
<p><?php _e('Neue Registrierungen sind derzeit nicht möglich. Bitte schau später wieder vorbei oder kontaktiere einen Administrator.', 'minecraft-modern-theme'); ?></p>
<a href="<?php echo esc_url( wp_login_url() ); ?>" class="button">
<i class="fas fa-sign-in-alt"></i> <?php _e('Zum Login', 'minecraft-modern-theme'); ?>
</a>
</div>
<?php endif; ?> <?php endif; ?>
</div> </div>
</main> </main>
</div> </div>
</div> </div>
<?php <?php
// Hintergrundbild aus dem Customizer holen und als Inline-CSS ausgeben $auth_bg_image = get_theme_mod('login_background_image');
$auth_bg_image = get_theme_mod('login_background_image'); if ( $auth_bg_image ) : ?>
if ($auth_bg_image):
?>
<style> <style>
body.login, body.login-action-register { body {
background-image: url('<?php echo esc_url($auth_bg_image); ?>') !important; background-image: url('<?php echo esc_url($auth_bg_image); ?>') !important;
background-size: cover !important; background-size: cover !important;
background-position: center !important; background-position: center !important;
background-repeat: no-repeat !important; background-repeat: no-repeat !important;
} }
body.login::before, body.login-action-register::before { body::before {
content: ''; content: '';
position: fixed; position: fixed;
top: 0; inset: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(20, 21, 26, 0.8); background: rgba(20, 21, 26, 0.8);
z-index: -1; z-index: -1;
} }
</style> </style>
<?php endif; ?> <?php endif; ?>
<style>
/* Benachrichtigungen */
.auth-notice {
display: flex;
align-items: center;
gap: 10px;
padding: 14px 18px;
border-radius: 6px;
margin-bottom: 20px;
font-weight: 500;
}
.auth-notice-success {
background: rgba(0, 212, 100, 0.12);
border-left: 4px solid #00d464;
color: #00d464;
}
.auth-notice-error {
background: rgba(255, 60, 60, 0.1);
border-left: 4px solid #ff3c3c;
color: #ff3c3c;
}
/* Registrierung geschlossen */
.auth-registration-closed {
text-align: center;
padding: 60px 40px;
background: var(--card-bg);
border-radius: 10px;
max-width: 500px;
margin: 0 auto;
}
.auth-icon {
color: var(--primary-accent, #00d4ff);
margin-bottom: 20px;
opacity: 0.6;
}
.auth-registration-closed h2 { margin-bottom: 12px; }
.auth-registration-closed p { color: var(--text-muted, #a0a0a0); margin-bottom: 24px; }
</style>
<?php get_footer(); ?> <?php get_footer(); ?>

View File

@@ -0,0 +1,107 @@
<?php get_header(); ?>
<main id="primary" class="site-main">
<div class="container">
<div class="content-area">
<header class="search-header">
<div class="archive-type-badge">
<i class="fas fa-search"></i> <?php _e('Suchergebnisse', 'minecraft-modern-theme'); ?>
</div>
<h1 class="archive-title">
<?php
printf(
__('Ergebnisse für: <span class="search-query">%s</span>', 'minecraft-modern-theme'),
'<em>' . esc_html( get_search_query() ) . '</em>'
);
?>
</h1>
<?php global $wp_query; ?>
<p class="search-result-count">
<?php printf(
_n('%d Ergebnis gefunden', '%d Ergebnisse gefunden', $wp_query->found_posts, 'minecraft-modern-theme'),
$wp_query->found_posts
); ?>
</p>
<!-- Suchformular zum Verfeinern -->
<div class="search-refine">
<?php get_search_form(); ?>
</div>
</header>
<?php if ( have_posts() ) : ?>
<div class="archive-posts-grid">
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class('post archive-post-card'); ?>>
<?php if ( has_post_thumbnail() ) : ?>
<div class="archive-card-thumb">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail('medium_large', array('loading' => 'lazy')); ?>
</a>
</div>
<?php endif; ?>
<div class="archive-card-body">
<div class="archive-card-type">
<?php
$pt = get_post_type_object( get_post_type() );
echo '<span class="post-type-badge"><i class="fas fa-file-alt"></i> ' . esc_html($pt->labels->singular_name) . '</span>';
?>
</div>
<h2 class="archive-card-title">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h2>
<div class="archive-card-meta">
<span><i class="fas fa-calendar-alt"></i> <?php echo esc_html( get_the_date() ); ?></span>
<span><i class="fas fa-user"></i> <?php the_author(); ?></span>
</div>
<div class="archive-card-excerpt">
<?php the_excerpt(); ?>
</div>
<a href="<?php the_permalink(); ?>" class="archive-card-read-more">
<?php _e('Zum Beitrag', 'minecraft-modern-theme'); ?> <i class="fas fa-arrow-right"></i>
</a>
</div>
</article>
<?php endwhile; ?>
</div>
<!-- Pagination -->
<div class="archive-pagination">
<?php
the_posts_pagination( array(
'mid_size' => 2,
'prev_text' => '<i class="fas fa-chevron-left"></i> ' . __('Zurück', 'minecraft-modern-theme'),
'next_text' => __('Weiter', 'minecraft-modern-theme') . ' <i class="fas fa-chevron-right"></i>',
) );
?>
</div>
<?php else : ?>
<div class="no-posts-found">
<i class="fas fa-search fa-3x" style="opacity:0.3; margin-bottom:20px;"></i>
<h2><?php _e('Keine Ergebnisse gefunden', 'minecraft-modern-theme'); ?></h2>
<p><?php printf( __('Deine Suche nach <strong>%s</strong> hat keine Treffer ergeben.', 'minecraft-modern-theme'), esc_html( get_search_query() ) ); ?></p>
<p><?php _e('Versuche es mit anderen Suchbegriffen oder schau dir unsere letzten Beiträge an.', 'minecraft-modern-theme'); ?></p>
<?php get_search_form(); ?>
</div>
<?php endif; ?>
</div>
</div>
</main>
<?php get_footer(); ?>

View File

@@ -0,0 +1,23 @@
<?php
// Eindeutige ID für jede Instanz des Suchformulars
$search_id = 'search-field-' . uniqid();
?>
<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url('/') ); ?>">
<div class="search-form-inner">
<label for="<?php echo esc_attr( $search_id ); ?>" class="screen-reader-text">
<?php _e('Suche nach:', 'minecraft-modern-theme'); ?>
</label>
<input
type="search"
id="<?php echo esc_attr( $search_id ); ?>"
class="search-field"
placeholder="<?php esc_attr_e('Suche...', 'minecraft-modern-theme'); ?>"
value="<?php echo esc_attr( get_search_query() ); ?>"
name="s"
autocomplete="off"
>
<button type="submit" class="search-submit" aria-label="<?php esc_attr_e('Suchen', 'minecraft-modern-theme'); ?>">
<i class="fas fa-search"></i>
</button>
</div>
</form>

View File

@@ -1,19 +1,179 @@
<?php get_header(); ?> <?php get_header(); ?>
<?php
// Sidebar-Einstellungen aus dem Customizer
$sidebar_enabled = get_theme_mod( 'single_sidebar_enabled', true );
$sidebar_position = get_theme_mod( 'single_sidebar_position', 'right' );
$has_sidebar = $sidebar_enabled && is_active_sidebar( 'single-post-sidebar' );
$layout_class = $has_sidebar
? 'single-layout with-sidebar sidebar-' . esc_attr( $sidebar_position )
: 'single-layout';
?>
<main id="primary" class="site-main"> <main id="primary" class="site-main">
<div class="container"> <div class="container">
<div class="content-area"> <div class="<?php echo esc_attr( $layout_class ); ?>">
<?php while ( have_posts() ) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class('post'); ?>> <?php if ( $has_sidebar && $sidebar_position === 'left' ) : ?>
<div class="post-content"> <aside class="single-sidebar">
<h1 class="post-title"><?php the_title(); ?></h1> <?php minecraft_modern_render_single_sidebar(); ?>
<div class="post-entry"> </aside>
<?php the_content(); ?> <?php endif; ?>
</div>
</div> <div class="single-main-content">
</article> <?php while ( have_posts() ) : the_post(); ?>
<?php endwhile; ?>
</div> <article id="post-<?php the_ID(); ?>" <?php post_class('post single-post'); ?>>
<?php if ( has_post_thumbnail() ) : ?>
<div class="post-hero-image">
<?php the_post_thumbnail( 'large' ); ?>
</div>
<?php endif; ?>
<div class="post-content">
<?php if ( get_theme_mod( 'single_show_breadcrumb', true ) ) : ?>
<nav class="breadcrumb" aria-label="Breadcrumb">
<a href="<?php echo esc_url( home_url('/') ); ?>"><?php _e('Startseite', 'minecraft-modern-theme'); ?></a>
<span class="breadcrumb-sep"><i class="fas fa-chevron-right"></i></span>
<?php
$cats = get_the_category();
if ( $cats ) :
$cat = $cats[0];
?>
<a href="<?php echo esc_url( get_category_link( $cat->term_id ) ); ?>"><?php echo esc_html( $cat->name ); ?></a>
<span class="breadcrumb-sep"><i class="fas fa-chevron-right"></i></span>
<?php endif; ?>
<span class="breadcrumb-current"><?php the_title(); ?></span>
</nav>
<?php endif; ?>
<h1 class="post-title"><?php the_title(); ?></h1>
<div class="post-meta">
<span class="post-meta-item post-author">
<?php echo get_avatar( get_the_author_meta('ID'), 24, '', '', array('class' => 'author-avatar') ); ?>
<a href="<?php echo esc_url( get_author_posts_url( get_the_author_meta('ID') ) ); ?>"><?php the_author(); ?></a>
</span>
<span class="post-meta-item post-date">
<i class="fas fa-calendar-alt"></i>
<time datetime="<?php echo esc_attr( get_the_date('c') ); ?>"><?php echo esc_html( get_the_date() ); ?></time>
</span>
<?php if ( get_the_modified_date() !== get_the_date() ) : ?>
<span class="post-meta-item post-updated">
<i class="fas fa-pencil-alt"></i>
<?php printf( __('Aktualisiert: %s', 'minecraft-modern-theme'), '<time datetime="' . esc_attr( get_the_modified_date('c') ) . '">' . esc_html( get_the_modified_date() ) . '</time>' ); ?>
</span>
<?php endif; ?>
<?php
$content = get_post_field( 'post_content', get_the_ID() );
$word_count = str_word_count( strip_tags( $content ) );
$read_time = max( 1, ceil( $word_count / 200 ) );
?>
<span class="post-meta-item post-read-time">
<i class="fas fa-clock"></i>
<?php printf( _n('%d Min. Lesezeit', '%d Min. Lesezeit', $read_time, 'minecraft-modern-theme'), $read_time ); ?>
</span>
<?php if ( comments_open() ) : ?>
<span class="post-meta-item post-comments">
<i class="fas fa-comment"></i>
<a href="#comments"><?php comments_number( __('0 Kommentare', 'minecraft-modern-theme'), __('1 Kommentar', 'minecraft-modern-theme'), __('% Kommentare', 'minecraft-modern-theme') ); ?></a>
</span>
<?php endif; ?>
</div>
<?php $cats = get_the_category(); if ( $cats ) : ?>
<div class="post-categories-top">
<?php foreach ( $cats as $cat ) : ?>
<a href="<?php echo esc_url( get_category_link( $cat->term_id ) ); ?>" class="post-category-badge"><?php echo esc_html( $cat->name ); ?></a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="post-entry">
<?php the_content(); ?>
</div>
<footer class="post-footer">
<?php $tags = get_the_tags(); if ( $tags ) : ?>
<div class="post-tags">
<i class="fas fa-tags"></i>
<?php foreach ( $tags as $tag ) : ?>
<a href="<?php echo esc_url( get_tag_link( $tag->term_id ) ); ?>" class="post-tag">#<?php echo esc_html( $tag->name ); ?></a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<nav class="post-navigation" aria-label="<?php esc_attr_e('Beitrag Navigation', 'minecraft-modern-theme'); ?>">
<?php $prev_post = get_previous_post(); $next_post = get_next_post(); ?>
<?php if ( $prev_post ) : ?>
<a href="<?php echo esc_url( get_permalink( $prev_post->ID ) ); ?>" class="post-nav-link post-nav-prev">
<span class="post-nav-label"><i class="fas fa-arrow-left"></i> <?php _e('Vorheriger Beitrag', 'minecraft-modern-theme'); ?></span>
<span class="post-nav-title"><?php echo esc_html( get_the_title( $prev_post->ID ) ); ?></span>
</a>
<?php endif; ?>
<?php if ( $next_post ) : ?>
<a href="<?php echo esc_url( get_permalink( $next_post->ID ) ); ?>" class="post-nav-link post-nav-next">
<span class="post-nav-label"><?php _e('Nächster Beitrag', 'minecraft-modern-theme'); ?> <i class="fas fa-arrow-right"></i></span>
<span class="post-nav-title"><?php echo esc_html( get_the_title( $next_post->ID ) ); ?></span>
</a>
<?php endif; ?>
</nav>
</footer>
</div><!-- .post-content -->
</article>
<!-- Related Posts -->
<?php if ( get_theme_mod( 'single_show_related_posts', true ) ) : ?>
<?php
$current_cats = wp_get_post_categories( get_the_ID() );
if ( $current_cats ) :
$related = new WP_Query( array(
'category__in' => $current_cats,
'post__not_in' => array( get_the_ID() ),
'posts_per_page' => 3,
'orderby' => 'rand',
) );
if ( $related->have_posts() ) : ?>
<section class="related-posts">
<h3 class="related-posts-title">
<i class="fas fa-layer-group"></i> <?php _e('Ähnliche Beiträge', 'minecraft-modern-theme'); ?>
</h3>
<div class="related-posts-grid">
<?php while ( $related->have_posts() ) : $related->the_post(); ?>
<article class="related-post-card">
<?php if ( has_post_thumbnail() ) : ?>
<a href="<?php the_permalink(); ?>" class="related-post-thumb">
<?php the_post_thumbnail('medium', array('loading' => 'lazy')); ?>
</a>
<?php endif; ?>
<div class="related-post-info">
<h4 class="related-post-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h4>
<span class="related-post-date"><i class="fas fa-calendar-alt"></i> <?php echo esc_html( get_the_date() ); ?></span>
</div>
</article>
<?php endwhile; wp_reset_postdata(); ?>
</div>
</section>
<?php endif;
endif; ?>
<?php endif; // single_show_related_posts ?>
<?php comments_template(); ?>
<?php endwhile; ?>
</div><!-- .single-main-content -->
<?php if ( $has_sidebar && $sidebar_position === 'right' ) : ?>
<aside class="single-sidebar">
<?php minecraft_modern_render_single_sidebar(); ?>
</aside>
<?php endif; ?>
</div><!-- .single-layout -->
</div> </div>
</main> </main>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
<?php
/**
* Test-Skript für YouTube API Key
* Führe aus: php test-api-key.php
*/
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
echo "=== YouTube API Key Test ===\n\n";
// Hole API Key aus Customizer
$api_key = get_theme_mod('youtube_api_key', '');
$api_key_from_config = defined('YOUTUBE_API_KEY') ? YOUTUBE_API_KEY : '';
echo "API Key aus Customizer: " . ($api_key ? substr($api_key, 0, 10) . '...' : 'NICHT GESETZT') . "\n";
echo "API Key aus wp-config: " . ($api_key_from_config ? substr($api_key_from_config, 0, 10) . '...' : 'NICHT GESETZT') . "\n\n";
$active_key = $api_key ? $api_key : $api_key_from_config;
if (empty($active_key)) {
echo "⚠️ Kein API Key gefunden!\n\n";
echo "So fügst du einen API Key hinzu:\n";
echo "1. Gehe zu: Design → Customizer → Video & Livestream\n";
echo "2. Trage dort deinen YouTube API Key ein\n";
echo "3. Klicke auf 'Veröffentlichen'\n\n";
echo "Oder füge in wp-config.php hinzu:\n";
echo "define('YOUTUBE_API_KEY', 'DEIN_KEY_HIER');\n";
exit;
}
echo "✓ API Key gefunden: " . substr($active_key, 0, 15) . "...\n\n";
// Test: Video-Status abfragen
$test_video_id = 'ithwtp7aJlM'; // NashvilleBirdCam Stream
echo "Teste API Key mit Video ID: $test_video_id\n\n";
$url = sprintf(
'https://www.googleapis.com/youtube/v3/videos?id=%s&part=snippet,liveStreamingDetails&key=%s',
rawurlencode($test_video_id),
rawurlencode($active_key)
);
echo "API Request: " . substr($url, 0, 100) . "...\n\n";
$response = wp_remote_get($url, array('timeout' => 10));
if (is_wp_error($response)) {
echo "❌ Fehler beim API Request: " . $response->get_error_message() . "\n";
exit;
}
$status_code = wp_remote_retrieve_response_code($response);
echo "HTTP Status: $status_code\n\n";
if ($status_code !== 200) {
echo "❌ API Fehler (Status $status_code)\n";
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (isset($data['error']['message'])) {
echo "Fehlermeldung: " . $data['error']['message'] . "\n";
if (strpos($data['error']['message'], 'API key not valid') !== false) {
echo "\n⚠️ Der API Key ist ungültig!\n";
echo "Bitte überprüfe den Key im Customizer oder erstelle einen neuen.\n";
}
}
exit;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if (empty($data['items'])) {
echo "❌ Video nicht gefunden oder nicht verfügbar\n";
exit;
}
echo "✅ API Key funktioniert!\n\n";
$video = $data['items'][0];
$title = $video['snippet']['title'] ?? 'Unbekannt';
$state = $video['snippet']['liveBroadcastContent'] ?? 'none';
echo "Video Titel: $title\n";
echo "Live Status: $state\n";
if ($state === 'live') {
echo "🔴 Das Video ist LIVE!\n";
} elseif ($state === 'upcoming') {
echo "⏰ Das Video ist geplant (noch nicht live)\n";
} else {
echo "⚫ Das Video ist NICHT live (aufgezeichnet oder offline)\n";
}
echo "\n=== Test erfolgreich ===\n";

View File

@@ -0,0 +1,80 @@
<?php
/**
* Test das neue API-basierte Livestream-System
*/
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
echo "=== Test API-basiertes Livestream-System ===\n\n";
// 1. Check Customizer Settings
$api_key = get_theme_mod('youtube_api_key');
$handle = get_theme_mod('youtube_livestream_handle', '@DreamTripspk');
echo "1. Customizer Settings:\n";
echo " API Key: " . ($api_key ? "SET (" . strlen($api_key) . " chars)" : "NOT SET") . "\n";
echo " Handle: " . ($handle ? $handle : "NOT SET") . "\n\n";
if (empty($api_key)) {
echo "⚠️ WARNUNG: Kein API Key gesetzt. Ohne API Key funktioniert das neue System nicht!\n";
echo " Bitte im Customizer unter 'Video & Livestream' einen YouTube API Key eintragen.\n\n";
}
if (empty($handle)) {
echo "⚠️ WARNUNG: Kein Handle gesetzt.\n";
echo " Bitte im Customizer unter 'Video & Livestream' einen YouTube @Handle eintragen.\n\n";
}
// 2. Test Channel ID Resolution
if (!empty($handle)) {
echo "2. Channel ID Resolution:\n";
$channel_id = mm_get_channel_id_by_handle($handle);
if ($channel_id) {
echo " ✓ Channel ID: $channel_id\n\n";
// 3. Test Live Video Detection
echo "3. Live Video Detection:\n";
$live_id = mm_get_youtube_live_id_from_handle($handle);
if ($live_id) {
echo " ✓ LIVE! Video ID: $live_id\n";
echo " ✓ URL: https://www.youtube.com/watch?v=$live_id\n\n";
} else {
echo " ✗ Nicht live oder keine Videos gefunden\n\n";
}
} else {
echo " ✗ Channel ID konnte nicht ermittelt werden\n";
echo " Prüfe ob der Handle korrekt ist und API Key gültig ist\n\n";
}
}
// 4. Test mm_video_get_livestream_groups()
echo "4. Test mm_video_get_livestream_groups():\n";
$groups = mm_video_get_livestream_groups();
echo " Debug: Returned value type: " . gettype($groups) . "\n";
echo " Debug: Is array: " . (is_array($groups) ? 'yes' : 'no') . "\n";
echo " Debug: Count: " . count($groups) . "\n";
if (!empty($groups)) {
echo "" . count($groups) . " Gruppe(n) gefunden\n";
foreach ($groups as $i => $group) {
echo " Gruppe $i:\n";
echo " - Title: " . $group['title'] . "\n";
echo " - Platform: " . $group['platform'] . "\n";
echo " - YT ID: " . $group['yt_id'] . "\n";
echo " - Handle: " . $group['handle'] . "\n";
}
} else {
echo " ✗ Keine Gruppen (kein Live-Stream aktiv)\n";
echo " Debug: Testing internal call...\n";
$test_handle = get_theme_mod('youtube_livestream_handle', '');
echo " Debug: Handle from theme_mod: '" . $test_handle . "'\n";
$test_live_id = mm_get_youtube_live_id_from_handle($test_handle);
echo " Debug: Live ID from internal call: '" . ($test_live_id ? $test_live_id : 'false') . "'\n";
}
echo "\n=== Test Complete ===\n";
echo "\nNächste Schritte:\n";
echo "1. Gehe zu Design → Customizer → Video & Livestream\n";
echo "2. Trage deinen YouTube API Key ein\n";
echo "3. Trage deinen @Handle ein (z.B. @DreamTripspk)\n";
echo "4. Speichern und Seite neu laden\n";

View File

@@ -0,0 +1,29 @@
<?php
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
echo "=== Testing Direct Stream URL ===\n\n";
$profile_url = 'https://www.youtube.com/@NashvilleBirdCam';
$player_url = 'https://www.youtube.com/watch?v=ithwtp7aJlM';
$youtube_channel_id = 'UClOWy1mPxNB1D3cpmmxqGEg';
echo "Profile URL: $profile_url\n";
echo "Player URL: $player_url\n";
echo "Channel ID: $youtube_channel_id\n\n";
$data = mm_video_get_livestream_data($profile_url, $player_url, $youtube_channel_id);
echo "=== Result ===\n";
print_r($data);
echo "\n=== Key Fields ===\n";
echo "Platform: " . ($data['platform'] ?? 'NOT SET') . "\n";
echo "Video ID: " . ($data['video_id'] ?? 'NOT SET') . "\n";
echo "Embed URL: " . ($data['embed_url'] ?? 'NOT SET') . "\n";
if (!empty($data['video_id']) && !empty($data['embed_url'])) {
echo "\n✓ SUCCESS - Both video_id and embed_url are set!\n";
} else {
echo "\n✗ FAILURE - Missing video_id or embed_url\n";
}

View File

@@ -0,0 +1,72 @@
<?php
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
echo "=== Testing YouTube Video ID Extraction ===\n\n";
$url = 'https://www.youtube.com/@NashvilleBirdCam/streams';
echo "Fetching: $url\n\n";
$response = wp_remote_get($url, array(
'timeout' => 10,
'redirection' => 5,
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'headers' => array(
'Accept-Language' => 'en-US,en;q=0.9,de;q=0.8',
),
));
if (is_wp_error($response)) {
echo "ERROR: " . $response->get_error_message() . "\n";
exit;
}
$body = wp_remote_retrieve_body($response);
echo "Response size: " . strlen($body) . " bytes\n\n";
// Test all patterns
$patterns = array(
'watchEndpoint' => '/"watchEndpoint":\{"videoId":"([A-Za-z0-9_-]{11})"/i',
'videoId with isLiveNow' => '/"videoId":"([A-Za-z0-9_-]{11})"[^}]*"isLiveNow":true/i',
'URL with u0026' => '/\/watch\?v=([A-Za-z0-9_-]{11})\\u0026/i',
'URL standard' => '/\/watch\?v=([A-Za-z0-9_-]{11})/i',
'LIVE_NOW label' => '/LIVE_NOW.*?"videoId":"([A-Za-z0-9_-]{11})"/is',
);
echo "Testing patterns:\n";
foreach ($patterns as $name => $pattern) {
if (preg_match($pattern, $body, $m)) {
echo "$name: MATCH - video_id = " . $m[1] . "\n";
} else {
echo "$name: NO MATCH\n";
}
}
echo "\n=== Calling mm_video_extract_youtube_live_video_id ===\n";
$video_id = mm_video_extract_youtube_live_video_id($body);
if ($video_id) {
echo "✓ Extracted video_id: $video_id\n";
} else {
echo "✗ NO video_id extracted!\n";
}
// Show some context
echo "\n=== Searching for 'watchEndpoint' in response ===\n";
if (preg_match('/"watchEndpoint":\{"videoId":"[^"]+"/i', $body, $context)) {
echo "Found: " . $context[0] . "\n";
} else {
echo "Not found\n";
}
echo "\n=== Searching for 'PAmS12qbdzA' (known live video ID) ===\n";
if (strpos($body, 'PAmS12qbdzA') !== false) {
echo "✓ Found PAmS12qbdzA in response\n";
// Get context around it
$pos = strpos($body, 'PAmS12qbdzA');
$start = max(0, $pos - 100);
$context = substr($body, $start, 250);
echo "Context: " . htmlspecialchars($context) . "\n";
} else {
echo "✗ PAmS12qbdzA NOT found in response\n";
}

View File

@@ -0,0 +1,52 @@
<?php
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
echo "=== Debug: Livestream Filtering ===\n\n";
$args = array(
'post_type' => 'mm_livestream',
'posts_per_page' => -1,
'post_status' => 'publish',
);
$query = new WP_Query($args);
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
$post_id = get_the_ID();
$profile_url = get_post_meta($post_id, '_mm_livestream_url', true);
$player_url = get_post_meta($post_id, '_mm_livestream_player_url', true);
$youtube_channel_id = get_post_meta($post_id, '_mm_livestream_youtube_channel_id', true);
$owner = get_post_meta($post_id, '_mm_livestream_owner', true);
echo "Post #$post_id: " . get_the_title() . "\n";
echo " Owner: $owner\n";
echo " Profile: $profile_url\n";
echo " Player: $player_url\n";
echo " Channel ID: $youtube_channel_id\n";
$stream = mm_video_get_livestream_data($profile_url, $player_url, $youtube_channel_id);
echo " Platform: " . $stream['platform'] . "\n";
echo " Video ID: " . ($stream['video_id'] ?? 'EMPTY') . "\n";
echo " Embed URL: " . ($stream['embed_url'] ?? 'EMPTY') . "\n";
if ($stream['platform'] === 'youtube') {
$video_id = $stream['video_id'] ?? '';
if ($video_id) {
$is_live = mm_video_check_youtube_live_status($video_id);
echo " Live Status Check: " . ($is_live ? 'LIVE ✓' : 'NOT LIVE ✗') . "\n";
} else {
echo " Live Status Check: SKIPPED (no video_id)\n";
}
}
echo "\n";
}
wp_reset_postdata();
} else {
echo "No livestream posts found!\n";
}

View File

@@ -0,0 +1,47 @@
<?php
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
$urls = array(
'https://www.youtube.com/@NashvilleBirdCam/live',
'https://www.youtube.com/channel/UClOWy1mPxNB1D3cpmmxqGEg/live',
);
foreach ($urls as $url) {
echo "\n=== Testing: $url ===\n";
$response = wp_remote_get($url, array(
'timeout' => 10,
'redirection' => 5,
'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'headers' => array(
'Accept-Language' => 'en-US,en;q=0.9,de;q=0.8',
),
));
if (is_wp_error($response)) {
echo "ERROR: " . $response->get_error_message() . "\n";
continue;
}
$body = wp_remote_retrieve_body($response);
echo "Response size: " . strlen($body) . " bytes\n";
if (strpos($body, 'PAmS12qbdzA') !== false) {
echo "✓ Found PAmS12qbdzA\n";
$pos = strpos($body, 'PAmS12qbdzA');
$start = max(0, $pos - 150);
$context = substr($body, $start, 350);
echo "Context:\n" . htmlspecialchars($context) . "\n";
} else {
echo "✗ PAmS12qbdzA NOT found\n";
}
$video_id = mm_video_extract_youtube_live_video_id($body);
if ($video_id) {
echo "✓ Extracted video_id: $video_id\n";
} else {
echo "✗ NO video_id extracted\n";
}
}

View File

@@ -0,0 +1,69 @@
<?php
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
echo "=== Testing Livestream Rendering ===\n\n";
// Query für Videos mit Livestream
$args = array(
'post_type' => 'video',
'posts_per_page' => 2,
'meta_query' => array(
array(
'key' => 'video_is_livestream',
'value' => '1',
'compare' => '='
)
)
);
$query = new WP_Query($args);
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
$post_id = get_the_ID();
$profile_url = get_post_meta($post_id, 'video_livestream_profile_url', true);
$player_url = get_post_meta($post_id, 'video_livestream_player_url', true);
$youtube_channel_id = get_post_meta($post_id, 'video_youtube_channel_id', true);
echo "Post ID: $post_id\n";
echo "Title: " . get_the_title() . "\n";
echo "Profile URL: $profile_url\n";
echo "Player URL: $player_url\n";
echo "YouTube Channel ID: $youtube_channel_id\n";
$data = mm_video_get_livestream_data($profile_url, $player_url, $youtube_channel_id);
echo "\nLivestream Data:\n";
echo " Platform: " . ($data['platform'] ?? 'N/A') . "\n";
echo " Video ID: " . ($data['video_id'] ?? 'EMPTY') . "\n";
echo " Embed URL: " . ($data['embed_url'] ?? 'EMPTY') . "\n";
echo " Profile URL: " . ($data['profile_url'] ?? 'N/A') . "\n";
echo "\n" . str_repeat("-", 80) . "\n\n";
}
wp_reset_postdata();
} else {
echo "No livestream videos found.\n";
}
// Test the groups
echo "\n=== Testing Group Generation ===\n\n";
$groups = mm_video_get_livestream_groups();
echo "Found " . count($groups) . " groups\n\n";
foreach ($groups as $idx => $group) {
echo "Group $idx: " . $group['owner'] . "\n";
echo " Items: " . count($group['items']) . "\n";
foreach ($group['items'] as $item_idx => $item) {
echo " Item $item_idx:\n";
echo " Title: " . $item['title'] . "\n";
echo " Platform: " . $item['stream']['platform'] . "\n";
echo " Video ID: " . ($item['stream']['video_id'] ?? 'EMPTY') . "\n";
echo " Embed URL: " . (empty($item['stream']['embed_url']) ? 'EMPTY' : 'SET') . "\n";
}
echo "\n";
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* Test Video ID Detection for specific YouTube video
*/
// WordPress laden
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
$video_id = 'kWRhLLbLFE0';
echo "=== Testing Video ID: $video_id ===\n\n";
// 1. Check if video is live
echo "1. Checking Live Status:\n";
$live_status = mm_video_check_youtube_live_status($video_id);
echo " Result: " . ($live_status ? "LIVE" : "NOT LIVE") . "\n\n";
// 2. Try to fetch video info from YouTube
echo "2. Fetching YouTube HTML:\n";
$url = "https://www.youtube.com/watch?v=" . $video_id;
$response = wp_remote_get($url);
if (!is_wp_error($response)) {
$html = wp_remote_retrieve_body($response);
// Check for live indicators
if (stripos($html, '"isLiveContent":true') !== false) {
echo " ✓ isLiveContent found\n";
}
if (stripos($html, '"isLive":true') !== false) {
echo " ✓ isLive found\n";
}
if (stripos($html, '"isLiveNow":true') !== false) {
echo " ✓ isLiveNow found\n";
}
if (stripos($html, 'LIVE_NOW') !== false) {
echo " ✓ LIVE_NOW badge found\n";
}
// Check for DVR
if (stripos($html, '"isLiveDvrEnabled":true') !== false) {
echo " ✓ DVR enabled\n";
}
echo "\n";
} else {
echo " ERROR: " . $response->get_error_message() . "\n\n";
}
// 3. Check DreamTripspk channel for live videos
echo "3. Checking DreamTripspk Channel:\n";
$channel_id = 'UCetYFjkhf7S7LwiuJxeC28g';
$channel_url = 'https://www.youtube.com/channel/' . $channel_id . '/live';
$extracted_id = mm_video_extract_youtube_live_video_id($channel_url);
if ($extracted_id) {
echo " ✓ Found video ID: $extracted_id\n";
if ($extracted_id === $video_id) {
echo " ✓ MATCH! This is the current live video\n";
} else {
echo " ✗ DIFFERENT video is live: $extracted_id\n";
}
} else {
echo " ✗ No live video found on channel\n";
}
echo "\n";
// 4. Check cache
echo "4. Cache Status:\n";
$cache_key_video = 'mm_yt_live_v2_' . md5($channel_url);
$cached_video_id = get_transient($cache_key_video);
echo " Video ID Cache: " . ($cached_video_id ? $cached_video_id : "EMPTY") . "\n";
$cache_key_status = 'mm_yt_status_' . $video_id;
$cached_status = get_transient($cache_key_status);
echo " Live Status Cache: " . ($cached_status !== false ? ($cached_status ? "LIVE" : "NOT LIVE") : "EMPTY") . "\n";
echo "\n=== Test Complete ===\n";

View File

@@ -0,0 +1,28 @@
<?php
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
echo "=== Testing YouTube Livestream Data ===\n\n";
$profile_url = 'https://www.youtube.com/@NashvilleBirdCam';
$youtube_channel_id = 'UClOWy1mPxNB1D3cpmmxqGEg';
echo "Profile URL: $profile_url\n";
echo "Channel ID: $youtube_channel_id\n\n";
$data = mm_video_get_livestream_data($profile_url, '', $youtube_channel_id);
echo "=== Livestream Data ===\n";
print_r($data);
if (!empty($data['video_id'])) {
echo "\n✓ video_id found: " . $data['video_id'] . "\n";
} else {
echo "\n✗ video_id is EMPTY!\n";
}
if (!empty($data['embed_url'])) {
echo "✓ embed_url found: " . $data['embed_url'] . "\n";
} else {
echo "✗ embed_url is EMPTY!\n";
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* Update DreamTripspk post with direct video URL
*/
define('WP_USE_THEMES', false);
require('../../../wp-load.php');
$video_url = 'https://www.youtube.com/watch?v=kWRhLLbLFE0';
$post_id = 17512; // DreamTripspk Post ID
echo "=== Updating DreamTripspk Post ===\n\n";
// Update the meta field
$result = update_post_meta($post_id, '_mm_livestream_player_url', $video_url);
if ($result) {
echo "✓ Updated _mm_livestream_player_url to: $video_url\n";
} else {
// Check if value already exists
$current = get_post_meta($post_id, '_mm_livestream_player_url', true);
if ($current === $video_url) {
echo "✓ Value already set to: $video_url\n";
} else {
echo "✗ Update failed. Current value: $current\n";
}
}
// Clear caches
$channel_id = get_post_meta($post_id, '_mm_livestream_youtube_channel_id', true);
if ($channel_id) {
$channel_url = 'https://www.youtube.com/channel/' . $channel_id . '/live';
$cache_key = 'mm_yt_live_v2_' . md5($channel_url);
delete_transient($cache_key);
echo "✓ Cleared cache for channel\n";
}
// Clear video status cache
delete_transient('mm_yt_status_kWRhLLbLFE0');
echo "✓ Cleared video status cache\n";
// Test the livestream data
echo "\n=== Testing Livestream Item ===\n";
$item = mm_video_get_livestream_item($post_id);
if ($item) {
echo "Title: " . $item['title'] . "\n";
echo "Owner: " . $item['owner'] . "\n";
echo "Platform: " . $item['stream']['platform'] . "\n";
echo "Video ID: " . $item['stream']['video_id'] . "\n";
echo "Embed URL: " . ($item['stream']['embed_url'] ? 'SET' : 'EMPTY') . "\n";
// Test live status
if (!empty($item['stream']['video_id'])) {
$is_live = mm_video_check_youtube_live_status($item['stream']['video_id']);
echo "Live Status: " . ($is_live ? "LIVE" : "NOT LIVE") . "\n";
}
} else {
echo "ERROR: Could not get livestream item data\n";
}
echo "\n✓ Update Complete! Reload the page.\n";