Files
Minecraft-Modern-Theme/Minecraft-Modern-Theme/functions.php

3854 lines
191 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// === Theme Setup ===
function minecraft_modern_setup() {
add_theme_support( 'title-tag' );
add_theme_support( 'post-thumbnails' );
add_theme_support( 'custom-logo', array(
'height' => 9999,
'width' => 9999,
'flex-height' => true,
'flex-width' => true,
'header_text' => array( 'site-title', 'site-description' ),
) );
add_theme_support( 'custom-background' );
register_nav_menus( array(
'primary' => __( 'Hauptmenü', 'minecraft-modern-theme' ),
'footer' => __( 'Footer-Menü', 'minecraft-modern-theme' ),
) );
// FIX: 'script' und 'style' ergänzt für sauberes HTML5-Output-Format
add_theme_support( 'html5', array(
'search-form', 'comment-form', 'comment-list',
'gallery', 'caption', 'script', 'style',
) );
}
add_action( 'after_setup_theme', 'minecraft_modern_setup' );
// === Styles & Scripts laden ===
function minecraft_modern_scripts() {
// Haupt-Stylesheet
wp_enqueue_style( 'minecraft-modern-style', get_stylesheet_uri(), array(), filemtime( get_stylesheet_directory() . '/style.css' ) );
// Swiper.js CSS
wp_enqueue_style( 'swiper-css', 'https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.css' );
// Header-Scroll + Suche Toggle
wp_enqueue_script(
'minecraft-modern-header-script',
get_template_directory_uri() . '/js/header-scroll.js',
array(),
filemtime( get_template_directory() . '/js/header-scroll.js' ),
true
);
// Navigation (Dropdown + Außenklick-Schließen)
wp_enqueue_script(
'minecraft-navigation',
get_template_directory_uri() . '/js/navigation.js',
array(),
'1.1',
true
);
// Swiper.js
wp_enqueue_script(
'swiper-js',
'https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.js',
array(),
'8.0.0',
true
);
// Slider-Init
wp_enqueue_script(
'minecraft-modern-slider-script',
get_template_directory_uri() . '/js/slider-init.js',
array( 'swiper-js' ),
'1.1',
true
);
// Theme Toggle
wp_enqueue_script(
'theme-toggle-script',
get_template_directory_uri() . '/js/theme-toggle.js',
array(),
'1.1',
true
);
// BUG-FIX: post_type_exists('faq') prüft nur ob der CPT registriert ist, nicht ob wir
// auf einer FAQ-Seite sind das Script wurde damit seitenübergreifend geladen.
// Jetzt: nur auf FAQ-Archiv-, Einzel- und Seiten mit FAQ-Template laden.
if ( post_type_exists( 'faq' ) && ( is_post_type_archive( 'faq' ) || is_singular( 'faq' ) || is_page( 'FAQ' ) || is_page( 'faq' ) ) ) {
wp_enqueue_script(
'faq-accordion-script',
get_template_directory_uri() . '/js/faq-accordion.js',
array(),
'1.0',
true
);
}
// FIX: 'loop' ergänzt war in slider-init.js als sliderSettings.loop referenziert, aber nie übergeben
wp_localize_script(
'minecraft-modern-slider-script',
'sliderSettings',
array(
'hideArrows' => get_theme_mod( 'slider_hide_arrows', false ) ? '1' : '0',
'hidePagination' => get_theme_mod( 'slider_hide_pagination', false ) ? '1' : '0',
'effect' => get_theme_mod( 'slider_effect', 'fade' ),
'direction' => get_theme_mod( 'slider_direction', 'horizontal' ),
'defaultMode' => get_theme_mod( 'default_theme_mode', 'dark' ),
'loop' => get_theme_mod( 'slider_loop', true ) ? '1' : '0',
'ajax_url' => admin_url( 'admin-ajax.php' ),
)
);
wp_localize_script(
'minecraft-modern-header-script',
'headerSettings',
array(
'isCustomizer' => is_customize_preview(),
)
);
}
add_action( 'wp_enqueue_scripts', 'minecraft_modern_scripts' );
// FIX: Scroll-to-Top via Customizer ein-/ausblendbar
function minecraft_modern_scroll_to_top_css() {
if ( ! get_theme_mod( 'show_scroll_to_top', true ) ) {
wp_add_inline_style( 'minecraft-modern-style', '#scroll-to-top { display: none !important; }' );
}
}
add_action( 'wp_enqueue_scripts', 'minecraft_modern_scroll_to_top_css', 25 );
// === Customizer-Datei laden ===
require get_template_directory() . '/inc/customizer.php';
// =========================================================================
// INTELLIGENTER SUPPORT-ASSISTENT
// =========================================================================
// Ausgelagert in /inc/assistant-widget.php
require get_template_directory() . '/inc/assistant-widget.php';
// === Theme-Updater-Datei laden ===
require get_template_directory() . '/inc/theme-updater.php';
// === Footer-Widgets registrieren ===
function minecraft_modern_footer_widgets() {
register_sidebar( array(
'name' => __( 'Footer Links', 'minecraft-modern-theme' ),
'id' => 'footer-left',
'description' => __( 'Widget-Bereich links im Footer.', 'minecraft-modern-theme' ),
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h4 class="widget-title">',
'after_title' => '</h4>',
) );
for ( $i = 1; $i <= 3; $i++ ) {
register_sidebar( array(
'name' => sprintf( __( 'Footer Spalte %d', 'minecraft-modern-theme' ), $i ),
'id' => 'footer-' . $i,
'description' => sprintf( __( 'Widget für die %d. Spalte im Footer.', 'minecraft-modern-theme' ), $i ),
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h4 class="widget-title">',
'after_title' => '</h4>',
) );
}
register_sidebar( array(
'name' => __( 'Footer Rechts', 'minecraft-modern-theme' ),
'id' => 'footer-right',
'description' => __( 'Widget-Bereich rechts im Footer.', 'minecraft-modern-theme' ),
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h4 class="widget-title">',
'after_title' => '</h4>',
) );
}
add_action( 'widgets_init', 'minecraft_modern_footer_widgets' );
// === Homepage Sidebar registrieren ===
function minecraft_modern_homepage_sidebar() {
register_sidebar( array(
'name' => __( 'Startseiten Sidebar - Oben', 'minecraft-modern-theme' ),
'id' => 'homepage-sidebar-top',
'description' => __( 'Widget-Bereich oben in der Sidebar (z.B. für wichtige Infos).', 'minecraft-modern-theme' ),
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
) );
register_sidebar( array(
'name' => __( 'Startseiten Sidebar - Mitte 1', 'minecraft-modern-theme' ),
'id' => 'homepage-sidebar-middle-1',
'description' => __( 'Widget-Bereich in der Mitte der Sidebar.', 'minecraft-modern-theme' ),
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
) );
register_sidebar( array(
'name' => __( 'Startseiten Sidebar - Mitte 2', 'minecraft-modern-theme' ),
'id' => 'homepage-sidebar-middle-2',
'description' => __( 'Zweiter Widget-Bereich in der Mitte der Sidebar.', 'minecraft-modern-theme' ),
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
) );
register_sidebar( array(
'name' => __( 'Startseiten Sidebar - Unten', 'minecraft-modern-theme' ),
'id' => 'homepage-sidebar-bottom',
'description' => __( 'Widget-Bereich unten in der Sidebar.', 'minecraft-modern-theme' ),
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
) );
register_sidebar( array(
'name' => __( 'Startseiten Sidebar - Extra', 'minecraft-modern-theme' ),
'id' => 'homepage-sidebar-extra',
'description' => __( 'Zusätzlicher Widget-Bereich für spezielle Inhalte.', 'minecraft-modern-theme' ),
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
) );
}
add_action( 'widgets_init', 'minecraft_modern_homepage_sidebar' );
/**
* Rendert alle Sidebar-Sections der Homepage.
* Ersetzt den doppelten Sidebar-Code in front-page.php.
*/
function minecraft_modern_render_sidebar_sections() {
$sections = array(
'homepage-sidebar-top',
'homepage-sidebar-middle-1',
'homepage-sidebar-middle-2',
'homepage-sidebar-bottom',
'homepage-sidebar-extra',
);
$classes = array(
'homepage-sidebar-top' => 'sidebar-top',
'homepage-sidebar-middle-1' => 'sidebar-middle-1',
'homepage-sidebar-middle-2' => 'sidebar-middle-2',
'homepage-sidebar-bottom' => 'sidebar-bottom',
'homepage-sidebar-extra' => 'sidebar-extra',
);
foreach ( $sections as $sidebar_id ) {
if ( is_active_sidebar( $sidebar_id ) ) {
$class = isset( $classes[ $sidebar_id ] ) ? ' ' . $classes[ $sidebar_id ] : '';
echo '<div class="sidebar-section' . esc_attr( $class ) . '">';
dynamic_sidebar( $sidebar_id );
echo '</div>';
}
}
}
// === FAQ Custom Post Type & Taxonomy ===
function create_faq_post_type() {
if ( get_theme_mod( 'faq_enabled', true ) ) {
register_post_type( 'faq', array(
'labels' => array(
'name' => __( 'FAQs', 'minecraft-modern-theme' ),
'singular_name' => __( 'FAQ', 'minecraft-modern-theme' ),
'add_new' => __( 'Neue FAQ hinzufügen', 'minecraft-modern-theme' ),
'add_new_item' => __( 'Neue FAQ hinzufügen', 'minecraft-modern-theme' ),
'edit_item' => __( 'FAQ bearbeiten', 'minecraft-modern-theme' ),
'new_item' => __( 'Neue FAQ', 'minecraft-modern-theme' ),
'view_item' => __( 'FAQ ansehen', 'minecraft-modern-theme' ),
'search_items' => __( 'FAQs durchsuchen', 'minecraft-modern-theme' ),
'not_found' => __( 'Keine FAQs gefunden', 'minecraft-modern-theme' ),
'not_found_in_trash' => __( 'Keine FAQs im Papierkorb gefunden', 'minecraft-modern-theme' ),
'all_items' => __( 'Alle FAQs', 'minecraft-modern-theme' ),
),
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-format-chat',
'supports' => array( 'title', 'editor', 'page-attributes' ),
'rewrite' => array( 'slug' => 'faq' ),
'show_in_rest' => true,
) );
register_taxonomy( 'faq_category', 'faq', array(
'label' => __( 'FAQ Kategorien', 'minecraft-modern-theme' ),
'rewrite' => array( 'slug' => 'faq-kategorie' ),
'hierarchical' => true,
'show_in_rest' => true,
) );
}
}
add_action( 'init', 'create_faq_post_type' );
// =============================================================================
// Automatische "Home" Seitenerstellung
// =============================================================================
function set_static_front_page_automatically() {
if ( 'page' !== get_option( 'show_on_front' ) ) {
// BUG-FIX: get_page_by_title() ist seit WP 6.2 deprecated. Ersetzt durch WP_Query.
$home_query = new WP_Query( array(
'post_type' => 'page',
'title' => 'Home',
'post_status' => 'publish',
'posts_per_page' => 1,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
) );
$home_page = $home_query->have_posts() ? $home_query->posts[0] : null;
if ( ! $home_page ) {
$home_page_id = wp_insert_post( array(
'post_title' => 'Home',
'post_content' => 'Diese Seite dient als statische Startseite.',
'post_status' => 'publish',
'post_type' => 'page',
'post_author' => 1,
) );
} else {
$home_page_id = $home_page->ID;
}
if ( $home_page_id ) {
update_option( 'show_on_front', 'page' );
update_option( 'page_on_front', $home_page_id );
}
}
}
add_action( 'after_switch_theme', 'set_static_front_page_automatically' );
function add_home_body_class( $classes ) {
if ( is_front_page() && ! get_theme_mod( 'show_home_title', true ) ) {
$classes[] = 'home-title-hidden';
}
return $classes;
}
add_filter( 'body_class', 'add_home_body_class' );
// =============================================================================
// FAQ-Seitenerstellung
// =============================================================================
function create_faq_page_automatically() {
if ( ! get_theme_mod( 'faq_enabled', true ) ) return;
// BUG-FIX: get_page_by_title() ist seit WP 6.2 deprecated. Ersetzt durch WP_Query.
$faq_query = new WP_Query( array(
'post_type' => 'page',
'title' => 'FAQ',
'post_status' => 'publish',
'posts_per_page' => 1,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
) );
if ( $faq_query->have_posts() ) return;
wp_insert_post( array(
'post_title' => 'FAQ',
'post_content' => 'Diese Seite zeigt alle FAQs an. Der Inhalt wird automatisch generiert.',
'post_status' => 'publish',
'post_type' => 'page',
'post_author' => 1,
) );
}
add_action( 'customize_save_after', 'create_faq_page_automatically' );
function load_faq_page_template( $template ) {
if ( get_theme_mod( 'faq_enabled', true ) && is_page() ) {
global $post;
if ( $post && $post->post_title === 'FAQ' ) {
return get_template_directory() . '/archive-faq.php';
}
}
return $template;
}
add_filter( 'template_include', 'load_faq_page_template' );
// =========================================================================
// CUSTOM LOGIN FUNCTIONS
// =========================================================================
function minecraft_modern_login_assets() {
wp_enqueue_style( 'minecraft-modern-login-style', get_template_directory_uri() . '/css/login-style.css' );
wp_enqueue_style( 'font-awesome', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css' );
// FIX: login-slider.js erster Slide sofort sichtbar (fixiert)
wp_enqueue_script( 'minecraft-avatar-slider-script', get_template_directory_uri() . '/js/login-slider.js', array( 'jquery' ), '1.1', true );
wp_enqueue_script( 'minecraft-modern-login-script', get_template_directory_uri() . '/js/login-script.js', array( 'jquery' ), '1.0', true );
wp_add_inline_script( 'minecraft-modern-login-script', "
jQuery(document).ready(function($) {
$('.forgetmenot, #nav').wrapAll('<div class=\"login-options-container\"></div>');
});
" );
$slider_speed = get_theme_mod( 'login_avatar_slider_speed', 4 );
wp_localize_script( 'minecraft-avatar-slider-script', 'avatarSliderSettings', array(
'speed' => $slider_speed * 1000,
) );
$login_bg_image = get_theme_mod( 'login_background_image' );
if ( $login_bg_image ) {
wp_add_inline_style( 'minecraft-modern-login-style', "body.login { background-image: url('" . esc_url( $login_bg_image ) . "') !important; }" );
}
$logo_url = get_theme_mod( 'login_logo' );
if ( $logo_url ) {
wp_add_inline_style( 'minecraft-modern-login-style', ".login h1 a { background-image: url('" . esc_url( $logo_url ) . "') !important; }" );
}
}
add_action( 'login_enqueue_scripts', 'minecraft_modern_login_assets' );
function add_minecraft_avatar_slider_to_login() {
$avatar_html = '<div id="minecraft-avatar-slider">';
$has_avatars = false;
for ( $i = 1; $i <= 5; $i++ ) {
$uuid = get_theme_mod( 'login_avatar_uuid_' . $i );
if ( ! empty( $uuid ) ) {
$has_avatars = true;
$avatar_url = 'https://visage.surgeplay.com/full/' . $uuid . '.png';
// FIX: Klasse wird hier gesetzt, JS überschreibt sie trotzdem (kompatibel)
$active_class = ( $i === 1 ) ? 'avatar-slide avatar-slide-active' : 'avatar-slide';
$avatar_html .= '<img class="' . esc_attr( $active_class ) . '" src="' . esc_url( $avatar_url ) . '" alt="Minecraft Avatar ' . $i . '">';
}
}
$avatar_html .= '</div>';
if ( $has_avatars ) {
echo $avatar_html;
}
}
add_action( 'login_form', 'add_minecraft_avatar_slider_to_login' );
function add_post_login_links() {
?>
<div class="post-login-links">
<a href="<?php echo esc_url( home_url() ); ?>">&larr; Zu <?php bloginfo( 'name' ); ?></a>
</div>
<?php
}
add_action( 'login_form_bottom', 'add_post_login_links' );
function customize_login_page() {
add_filter( 'login_display_language_dropdown', '__return_false' );
add_filter( 'login_display_back_to_blog', '__return_false' );
}
add_action( 'login_head', 'customize_login_page' );
function custom_login_url() { return home_url(); }
add_filter( 'login_headerurl', 'custom_login_url' );
function custom_login_title() { return get_bloginfo( 'name' ); }
add_filter( 'login_headertext', 'custom_login_title' );
// =========================================================================
// SCROLL TO TOP BUTTON
// =========================================================================
// FIX: Button wird jetzt im footer.php direkt ausgegeben (kein doppelter Button mehr).
// Diese Funktion bleibt als Fallback aber deaktiviert, damit es keinen Konflikt gibt.
// Falls footer.php NICHT angepasst wurde, diese Zeile auskommentieren:
// add_action( 'wp_footer', 'add_scroll_to_top_button' );
function add_scroll_to_top_button() {
?>
<a href="#" id="scroll-to-top" aria-label="<?php esc_attr_e( 'Nach oben scrollen', 'minecraft-modern-theme' ); ?>" title="<?php esc_attr_e( 'Nach oben', 'minecraft-modern-theme' ); ?>">
<i class="fas fa-chevron-up"></i>
</a>
<?php
}
function minecraft_modern_scroll_to_top_script() {
wp_enqueue_script(
'minecraft-scroll-to-top-script',
get_template_directory_uri() . '/js/scroll-to-top.js',
array( 'jquery' ),
'1.0',
true
);
}
add_action( 'wp_enqueue_scripts', 'minecraft_modern_scroll_to_top_script' );
// =============================================================================
// THEME SETTINGS EXPORT / IMPORT
// =============================================================================
add_action( 'admin_post_export_theme_settings', 'handle_theme_settings_export' );
function handle_theme_settings_export() {
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_die( __( 'Du hast keine Berechtigung, diese Aktion auszuführen.', 'minecraft-modern-theme' ) );
}
$theme_slug = get_option( 'stylesheet' );
$data = array(
'_version' => '3.0',
'_exported' => date( 'Y-m-d H:i:s' ),
'_theme' => $theme_slug,
);
// 1. Alle Customizer-Einstellungen (Theme Mods)
// Enthält: Farben, Slider, Hero, Social Links, Menü-Design, Login-Einstellungen, Sidebar-Einstellungen usw.
$data['theme_mods'] = get_theme_mods();
// 2. Announcement-Bar (gespeichert als wp_options, nicht als Theme Mod)
$announcement_keys = array(
'mm_announcement_enabled', 'mm_announcement_text', 'mm_announcement_bg',
'mm_announcement_color', 'mm_announcement_font_size', 'mm_announcement_font_family',
'mm_announcement_position', 'mm_announcement_countdown_enabled',
'mm_announcement_countdown_label', 'mm_announcement_countdown_date',
'mm_announcement_countdown_expired_msg',
);
foreach ( $announcement_keys as $key ) {
$data['announcement'][ $key ] = get_option( $key );
}
// 3. Widget-Konfigurationen aller Theme-Sidebars
$theme_sidebars = array(
'single-post-sidebar',
'homepage-sidebar-top', 'homepage-sidebar-middle-1',
'homepage-sidebar-middle-2', 'homepage-sidebar-bottom',
'homepage-sidebar-extra', 'footer-1', 'footer-2', 'footer-3',
);
$all_sidebars = wp_get_sidebars_widgets();
$widget_data = array();
foreach ( $theme_sidebars as $sidebar_id ) {
$widget_data[ $sidebar_id ] = array();
if ( empty( $all_sidebars[ $sidebar_id ] ) ) continue;
foreach ( $all_sidebars[ $sidebar_id ] as $widget_id ) {
if ( ! preg_match( '/^(.+)-(\d+)$/', $widget_id, $m ) ) continue;
$type = $m[1];
$index = intval( $m[2] );
$all = get_option( 'widget_' . $type, array() );
$widget_data[ $sidebar_id ][] = array(
'type' => $type,
'index' => $index,
'settings' => isset( $all[ $index ] ) ? $all[ $index ] : array(),
);
}
}
$data['widgets'] = $widget_data;
// 4. Team-Mitglieder (mit UUID, Banner, Thumbnail)
$team_data = array();
$team_query = new WP_Query( array(
'post_type' => 'team_member',
'posts_per_page' => -1,
'orderby' => 'menu_order',
'order' => 'ASC',
) );
if ( $team_query->have_posts() ) {
while ( $team_query->have_posts() ) {
$team_query->the_post();
$post_id = get_the_ID();
$team_data[] = array(
'title' => get_the_title(),
'content' => get_the_content(),
'rank' => get_post_meta( $post_id, '_team_member_rank', true ),
'uuid' => get_post_meta( $post_id, '_team_member_uuid', true ),
'banner_id' => get_post_meta( $post_id, '_team_member_banner', true ),
'thumbnail_id' => get_post_thumbnail_id( $post_id ),
'menu_order' => get_post_field( 'menu_order', $post_id ),
);
}
wp_reset_postdata();
}
$data['team'] = $team_data;
// 5. FAQ-Einträge (theme-eigener Custom Post Type)
$faq_data = array();
$faq_query = new WP_Query( array(
'post_type' => 'faq',
'posts_per_page' => -1,
'orderby' => 'menu_order',
'order' => 'ASC',
) );
if ( $faq_query->have_posts() ) {
while ( $faq_query->have_posts() ) {
$faq_query->the_post();
$terms = wp_get_post_terms( get_the_ID(), 'faq_category', array( 'fields' => 'names' ) );
$faq_data[] = array(
'title' => get_the_title(),
'content' => get_the_content(),
'menu_order' => get_post_field( 'menu_order', get_the_ID() ),
'categories' => is_wp_error( $terms ) ? array() : $terms,
);
}
wp_reset_postdata();
}
$data['faqs'] = $faq_data;
// 6. Custom CSS (Customizer → Zusätzliches CSS)
$data['custom_css'] = wp_get_custom_css();
// 7. Homepage-Seite (falls eingestellt)
$homepage_id = get_option( 'page_on_front' );
if ( $homepage_id ) {
$homepage = get_post( $homepage_id );
if ( $homepage ) {
$data['homepage'] = array(
'title' => $homepage->post_title,
'content' => $homepage->post_content,
'excerpt' => $homepage->post_excerpt,
'featured_img' => get_post_thumbnail_id( $homepage_id ),
);
}
}
// 8. Navigation Menüs mit Items
$nav_menus = array();
$all_nav_menus = wp_get_nav_menus();
foreach ( $all_nav_menus as $menu ) {
$nav_menus[ $menu->slug ] = array(
'name' => $menu->name,
'description' => $menu->description,
'items' => array(),
);
$menu_items = wp_get_nav_menu_items( $menu->term_id );
if ( $menu_items ) {
foreach ( $menu_items as $item ) {
$nav_menus[ $menu->slug ]['items'][] = array(
'title' => $item->title,
'url' => $item->url,
'description' => $item->description,
'type' => $item->type,
'type_label' => $item->type_label,
'object' => $item->object,
'object_id' => $item->object_id,
'parent' => $item->menu_item_parent,
'menu_order' => $item->menu_order,
'target' => get_post_meta( $item->ID, '_menu_item_target', true ),
'classes' => implode( ' ', (array) $item->classes ),
'xfn' => get_post_meta( $item->ID, '_menu_item_xfn', true ),
);
}
}
}
$data['nav_menus'] = $nav_menus;
// 9. Menü-Positionen (welches Menü ist welchem Theme-Location zugewiesen)
$data['nav_menu_locations'] = get_theme_mod( 'nav_menu_locations', array() );
$json = json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
header( 'Content-Type: application/json; charset=utf-8' );
header( 'Content-Disposition: attachment; filename=' . $theme_slug . '-theme-backup-' . date( 'Y-m-d' ) . '.json' );
header( 'Pragma: no-cache' );
header( 'Expires: 0' );
echo $json;
exit;
}
add_action( 'wp_ajax_import_theme_settings', 'handle_theme_settings_import' );
function handle_theme_settings_import() {
if ( ! current_user_can( 'edit_theme_options' ) ) {
wp_send_json_error( __( 'Keine Berechtigung.', 'minecraft-modern-theme' ) );
}
check_ajax_referer( 'theme-import-nonce', 'nonce' );
if ( empty( $_FILES['import_file']['tmp_name'] ) ) {
wp_send_json_error( __( 'Keine Datei hochgeladen.', 'minecraft-modern-theme' ) );
}
$json_content = file_get_contents( $_FILES['import_file']['tmp_name'] );
$data = json_decode( $json_content, true );
if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $data ) ) {
wp_send_json_error( __( 'Ungültige JSON-Datei.', 'minecraft-modern-theme' ) );
}
$imported = array();
$version = isset( $data['_version'] ) ? $data['_version'] : '1.0';
// -------------------------------------------------------------------------
// 1. Customizer / Theme Mods
// -------------------------------------------------------------------------
$mods = array();
if ( $version === '3.0' && isset( $data['theme_mods'] ) ) {
$mods = $data['theme_mods'];
} elseif ( $version === '2.0' && isset( $data['theme_mods'] ) ) {
$mods = $data['theme_mods'];
} else {
// Altes v1-Format: alles auf oberster Ebene
$skip = array( '_version', '_exported', '_theme', 'announcement', 'widgets', 'team', 'faqs', 'menus', 'wp_options', 'custom_css', 'team_data' );
foreach ( $data as $k => $v ) {
if ( ! in_array( $k, $skip ) ) $mods[ $k ] = $v;
}
}
foreach ( $mods as $key => $value ) {
set_theme_mod( $key, $value );
}
$imported[] = 'Customizer-Einstellungen';
// -------------------------------------------------------------------------
// 2. Announcement-Bar
// -------------------------------------------------------------------------
$announcement_keys = array(
'mm_announcement_enabled', 'mm_announcement_text', 'mm_announcement_bg',
'mm_announcement_color', 'mm_announcement_font_size', 'mm_announcement_font_family',
'mm_announcement_position', 'mm_announcement_countdown_enabled',
'mm_announcement_countdown_label', 'mm_announcement_countdown_date',
'mm_announcement_countdown_expired_msg',
);
$ann = isset( $data['announcement'] ) ? $data['announcement'] : $data;
foreach ( $announcement_keys as $key ) {
if ( array_key_exists( $key, $ann ) ) {
update_option( $key, $ann[ $key ] );
}
}
$imported[] = 'Ankündigung';
// -------------------------------------------------------------------------
// 3. Widgets
// -------------------------------------------------------------------------
if ( ! empty( $data['widgets'] ) ) {
$current_sidebars = get_option( 'sidebars_widgets', array() );
foreach ( $data['widgets'] as $sidebar_id => $widgets ) {
$current_sidebars[ $sidebar_id ] = array();
foreach ( $widgets as $widget ) {
$type = sanitize_key( $widget['type'] );
$index = intval( $widget['index'] );
$all = get_option( 'widget_' . $type, array() );
$all[ $index ] = $widget['settings'];
update_option( 'widget_' . $type, $all );
$current_sidebars[ $sidebar_id ][] = $type . '-' . $index;
}
}
update_option( 'sidebars_widgets', $current_sidebars );
$imported[] = 'Widgets';
}
// -------------------------------------------------------------------------
// 4. Team-Mitglieder
// -------------------------------------------------------------------------
$team_data = isset( $data['team'] ) ? $data['team'] : ( isset( $data['team_data'] ) ? $data['team_data'] : array() );
if ( ! empty( $team_data ) ) {
$existing = new WP_Query( array( 'post_type' => 'team_member', 'posts_per_page' => -1, 'fields' => 'ids' ) );
if ( $existing->have_posts() ) {
foreach ( $existing->posts as $pid ) wp_delete_post( $pid, true );
}
wp_reset_postdata();
foreach ( $team_data as $member ) {
$id = wp_insert_post( array(
'post_title' => sanitize_text_field( $member['title'] ),
'post_content' => wp_kses_post( $member['content'] ),
'post_type' => 'team_member',
'post_status' => 'publish',
'menu_order' => intval( isset( $member['menu_order'] ) ? $member['menu_order'] : 0 ),
) );
if ( $id && ! is_wp_error( $id ) ) {
// Rank
if ( ! empty( $member['rank'] ) ) {
update_post_meta( $id, '_team_member_rank', sanitize_text_field( $member['rank'] ) );
}
// UUID
if ( ! empty( $member['uuid'] ) ) {
update_post_meta( $id, '_team_member_uuid', sanitize_text_field( $member['uuid'] ) );
}
// Thumbnail
if ( ! empty( $member['thumbnail_id'] ) ) {
set_post_thumbnail( $id, intval( $member['thumbnail_id'] ) );
}
// Banner
if ( ! empty( $member['banner_id'] ) ) {
update_post_meta( $id, '_team_member_banner', intval( $member['banner_id'] ) );
}
}
}
$imported[] = count( $team_data ) . ' Team-Mitglieder';
}
// -------------------------------------------------------------------------
// 5. FAQs
// -------------------------------------------------------------------------
if ( ! empty( $data['faqs'] ) ) {
$existing = new WP_Query( array( 'post_type' => 'faq', 'posts_per_page' => -1, 'fields' => 'ids' ) );
if ( $existing->have_posts() ) {
foreach ( $existing->posts as $pid ) wp_delete_post( $pid, true );
}
wp_reset_postdata();
foreach ( $data['faqs'] as $faq ) {
$id = wp_insert_post( array(
'post_title' => sanitize_text_field( $faq['title'] ),
'post_content' => wp_kses_post( $faq['content'] ),
'post_type' => 'faq',
'post_status' => 'publish',
'menu_order' => intval( isset( $faq['menu_order'] ) ? $faq['menu_order'] : 0 ),
) );
if ( $id && ! is_wp_error( $id ) && ! empty( $faq['categories'] ) ) {
foreach ( (array) $faq['categories'] as $cat_name ) {
$term = term_exists( sanitize_text_field( $cat_name ), 'faq_category' );
if ( ! $term ) {
$term = wp_insert_term( sanitize_text_field( $cat_name ), 'faq_category' );
}
if ( ! is_wp_error( $term ) ) {
wp_set_post_terms( $id, array( intval( $term['term_id'] ) ), 'faq_category', true );
}
}
}
}
$imported[] = count( $data['faqs'] ) . ' FAQs';
}
// -------------------------------------------------------------------------
// 6. Custom CSS
// -------------------------------------------------------------------------
if ( isset( $data['custom_css'] ) && $data['custom_css'] !== '' ) {
wp_update_custom_css_post( $data['custom_css'] );
$imported[] = 'Custom CSS';
}
// -------------------------------------------------------------------------
// 7. Homepage-Seite
// -------------------------------------------------------------------------
if ( ! empty( $data['homepage'] ) ) {
$homepage_id = get_option( 'page_on_front' );
if ( $homepage_id ) {
wp_update_post( array(
'ID' => $homepage_id,
'post_title' => sanitize_text_field( $data['homepage']['title'] ),
'post_content' => wp_kses_post( $data['homepage']['content'] ),
'post_excerpt' => sanitize_textarea_field( $data['homepage']['excerpt'] ),
) );
if ( ! empty( $data['homepage']['featured_img'] ) ) {
set_post_thumbnail( $homepage_id, intval( $data['homepage']['featured_img'] ) );
}
$imported[] = 'Homepage-Seite';
}
}
// -------------------------------------------------------------------------
// 8. Navigation Menüs mit Items
// -------------------------------------------------------------------------
if ( ! empty( $data['nav_menus'] ) ) {
$menu_id_map = array(); // slug => term_id Mapping für Item-Parent-Zuordnung
// Alle existierenden Menüs löschen
$existing_menus = wp_get_nav_menus();
foreach ( $existing_menus as $menu ) {
wp_delete_nav_menu( $menu->term_id );
}
// Neue Menüs und Items importieren
foreach ( $data['nav_menus'] as $menu_slug => $menu_data ) {
$new_menu = wp_create_nav_menu( $menu_data['name'] );
if ( ! is_wp_error( $new_menu ) ) {
$menu_id_map[ $menu_slug ] = $new_menu;
// Items hinzufügen
if ( ! empty( $menu_data['items'] ) ) {
$item_id_map = array(); // Altes Item ID => Neues Item ID Mapping
// Erste Runde: Root-Items (parent = 0)
foreach ( $menu_data['items'] as $idx => $item ) {
if ( empty( $item['parent'] ) || $item['parent'] == 0 ) {
$item_id = wp_update_nav_menu_item( $new_menu, 0, array(
'menu-item-title' => sanitize_text_field( $item['title'] ),
'menu-item-url' => esc_url_raw( $item['url'] ),
'menu-item-description' => sanitize_text_field( $item['description'] ),
'menu-item-type' => sanitize_key( $item['type'] ),
'menu-item-object' => sanitize_key( $item['object'] ),
'menu-item-object-id' => intval( $item['object_id'] ),
'menu-item-target' => sanitize_text_field( $item['target'] ),
'menu-item-classes' => sanitize_text_field( $item['classes'] ),
'menu-item-xfn' => sanitize_text_field( $item['xfn'] ),
'menu-item-status' => 'publish',
) );
if ( $item_id && ! is_wp_error( $item_id ) ) {
$item_id_map[ $idx ] = $item_id;
}
}
}
// Zweite Runde: Sub-Items (parent > 0)
foreach ( $menu_data['items'] as $idx => $item ) {
if ( ! empty( $item['parent'] ) && $item['parent'] > 0 ) {
$parent_item_id = isset( $item_id_map[ $item['parent'] - 1 ] ) ? $item_id_map[ $item['parent'] - 1 ] : 0;
$item_id = wp_update_nav_menu_item( $new_menu, 0, array(
'menu-item-title' => sanitize_text_field( $item['title'] ),
'menu-item-url' => esc_url_raw( $item['url'] ),
'menu-item-description' => sanitize_text_field( $item['description'] ),
'menu-item-type' => sanitize_key( $item['type'] ),
'menu-item-object' => sanitize_key( $item['object'] ),
'menu-item-object-id' => intval( $item['object_id'] ),
'menu-item-parent-id' => $parent_item_id,
'menu-item-target' => sanitize_text_field( $item['target'] ),
'menu-item-classes' => sanitize_text_field( $item['classes'] ),
'menu-item-xfn' => sanitize_text_field( $item['xfn'] ),
'menu-item-status' => 'publish',
) );
if ( $item_id && ! is_wp_error( $item_id ) ) {
$item_id_map[ $idx ] = $item_id;
}
}
}
}
}
}
$imported[] = 'Navigation Menüs (' . count( $data['nav_menus'] ) . ')';
}
// -------------------------------------------------------------------------
// 9. Menü-Positionen
// -------------------------------------------------------------------------
if ( ! empty( $data['nav_menu_locations'] ) ) {
set_theme_mod( 'nav_menu_locations', $data['nav_menu_locations'] );
$imported[] = 'Menü-Positionen';
}
wp_send_json_success( sprintf(
__( 'Erfolgreich wiederhergestellt: %s', 'minecraft-modern-theme' ),
implode( ', ', $imported )
) );
}
// =========================================================================
// ANNOUNCEMENT BAR
// =========================================================================
function mm_announcement_get_font_list() {
return array(
'inherit' => array( 'label' => 'Theme-Standard', 'css' => 'inherit', 'google' => false, 'google_name' => '' ),
'Arial' => array( 'label' => 'Arial', 'css' => 'Arial, Helvetica, sans-serif', 'google' => false, 'google_name' => '' ),
'Roboto' => array( 'label' => 'Roboto', 'css' => "'Roboto', sans-serif", 'google' => true, 'google_name' => 'Roboto' ),
'Montserrat' => array( 'label' => 'Montserrat', 'css' => "'Montserrat', sans-serif", 'google' => true, 'google_name' => 'Montserrat' ),
'Open Sans' => array( 'label' => 'Open Sans', 'css' => "'Open Sans', sans-serif", 'google' => true, 'google_name' => 'Open+Sans' ),
'Lato' => array( 'label' => 'Lato', 'css' => "'Lato', sans-serif", 'google' => true, 'google_name' => 'Lato' ),
'Poppins' => array( 'label' => 'Poppins', 'css' => "'Poppins', sans-serif", 'google' => true, 'google_name' => 'Poppins' ),
'Source Sans Pro' => array( 'label' => 'Source Sans Pro', 'css' => "'Source Sans Pro', sans-serif", 'google' => true, 'google_name' => 'Source+Sans+Pro' ),
'Noto Sans' => array( 'label' => 'Noto Sans', 'css' => "'Noto Sans', sans-serif", 'google' => true, 'google_name' => 'Noto+Sans' ),
'Raleway' => array( 'label' => 'Raleway', 'css' => "'Raleway', sans-serif", 'google' => true, 'google_name' => 'Raleway' ),
'Merriweather' => array( 'label' => 'Merriweather', 'css' => "'Merriweather', serif", 'google' => true, 'google_name' => 'Merriweather' ),
'Playfair Display' => array( 'label' => 'Playfair Display', 'css' => "'Playfair Display', serif", 'google' => true, 'google_name' => 'Playfair+Display' ),
'Oswald' => array( 'label' => 'Oswald', 'css' => "'Oswald', sans-serif", 'google' => true, 'google_name' => 'Oswald' ),
'Rubik' => array( 'label' => 'Rubik', 'css' => "'Rubik', sans-serif", 'google' => true, 'google_name' => 'Rubik' ),
'Inter' => array( 'label' => 'Inter', 'css' => "'Inter', sans-serif", 'google' => true, 'google_name' => 'Inter' ),
'Nunito' => array( 'label' => 'Nunito', 'css' => "'Nunito', sans-serif", 'google' => true, 'google_name' => 'Nunito' ),
'Ubuntu' => array( 'label' => 'Ubuntu', 'css' => "'Ubuntu', sans-serif", 'google' => true, 'google_name' => 'Ubuntu' ),
'PT Sans' => array( 'label' => 'PT Sans', 'css' => "'PT Sans', sans-serif", 'google' => true, 'google_name' => 'PT+Sans' ),
'Archivo' => array( 'label' => 'Archivo', 'css' => "'Archivo', sans-serif", 'google' => true, 'google_name' => 'Archivo' ),
'Fira Sans' => array( 'label' => 'Fira Sans', 'css' => "'Fira Sans', sans-serif", 'google' => true, 'google_name' => 'Fira+Sans' ),
'Work Sans' => array( 'label' => 'Work Sans', 'css' => "'Work Sans', sans-serif", 'google' => true, 'google_name' => 'Work+Sans' ),
'Quicksand' => array( 'label' => 'Quicksand', 'css' => "'Quicksand', sans-serif", 'google' => true, 'google_name' => 'Quicksand' ),
'Karla' => array( 'label' => 'Karla', 'css' => "'Karla', sans-serif", 'google' => true, 'google_name' => 'Karla' ),
'Dancing Script' => array( 'label' => 'Dancing Script', 'css' => "'Dancing Script', cursive", 'google' => true, 'google_name' => 'Dancing+Script' ),
'Pacifico' => array( 'label' => 'Pacifico', 'css' => "'Pacifico', cursive", 'google' => true, 'google_name' => 'Pacifico' ),
'Great Vibes' => array( 'label' => 'Great Vibes', 'css' => "'Great Vibes', cursive", 'google' => true, 'google_name' => 'Great+Vibes' ),
'Satisfy' => array( 'label' => 'Satisfy', 'css' => "'Satisfy', cursive", 'google' => true, 'google_name' => 'Satisfy' ),
'Allura' => array( 'label' => 'Allura', 'css' => "'Allura', cursive", 'google' => true, 'google_name' => 'Allura' ),
'Alex Brush' => array( 'label' => 'Alex Brush', 'css' => "'Alex Brush', cursive", 'google' => true, 'google_name' => 'Alex+Brush' ),
'Cookie' => array( 'label' => 'Cookie', 'css' => "'Cookie', cursive", 'google' => true, 'google_name' => 'Cookie' ),
);
}
function mm_announcement_admin_init() {
add_menu_page( 'Ankündigung', 'Ankündigung', 'manage_options', 'mm-announcement', 'mm_announcement_admin_page', 'dashicons-megaphone', 61 );
register_setting( 'mm_announcement_group', 'mm_announcement_enabled' );
register_setting( 'mm_announcement_group', 'mm_announcement_text' );
register_setting( 'mm_announcement_group', 'mm_announcement_bg' );
register_setting( 'mm_announcement_group', 'mm_announcement_color' );
register_setting( 'mm_announcement_group', 'mm_announcement_font_size' );
register_setting( 'mm_announcement_group', 'mm_announcement_font_family' );
register_setting( 'mm_announcement_group', 'mm_announcement_position' );
register_setting( 'mm_announcement_group', 'mm_announcement_countdown_enabled' );
register_setting( 'mm_announcement_group', 'mm_announcement_countdown_label' );
register_setting( 'mm_announcement_group', 'mm_announcement_countdown_date' );
register_setting( 'mm_announcement_group', 'mm_announcement_countdown_expired_msg' );
}
add_action( 'admin_menu', 'mm_announcement_admin_init' );
function mm_announcement_admin_page() {
if ( ! current_user_can( 'manage_options' ) ) return;
$fonts = mm_announcement_get_font_list();
$selected_font = get_option( 'mm_announcement_font_family', 'inherit' );
$selected_size = (int) get_option( 'mm_announcement_font_size', 16 );
$bg = esc_attr( get_option( 'mm_announcement_bg', '#1e1e1e' ) );
$color = esc_attr( get_option( 'mm_announcement_color', '#ffffff' ) );
$text_sample = wp_strip_all_tags( get_option( 'mm_announcement_text' ) ) ?: 'Das ist eine Vorschau: Wie sieht die Schrift aus?';
?>
<div class="wrap mm-announcement-admin">
<h1>Header-Ankündigung</h1>
<p class="description">Diese Leiste wird auf allen Seiten angezeigt. Die Vorschau unten zeigt sofort, wie die Ankündigung aussieht — Änderungen wirken <strong>direkt in der Vorschau</strong>, erst nach <em>Änderungen speichern</em> werden sie im Frontend übernommen.</p>
<form method="post" action="options.php" id="mm-announcement-form">
<?php settings_fields( 'mm_announcement_group' ); ?>
<h2>Allgemein</h2>
<table class="form-table">
<tr>
<th>Aktivieren</th>
<td>
<label>
<input type="checkbox" name="mm_announcement_enabled" value="1" <?php checked( 1, get_option( 'mm_announcement_enabled' ) ); ?>>
Ankündigung anzeigen
</label>
</td>
</tr>
</table>
<h2>Inhalt</h2>
<table class="form-table">
<tr>
<th style="vertical-align:top;">Text</th>
<td>
<?php wp_editor( get_option( 'mm_announcement_text' ), 'mm_announcement_text', array( 'textarea_rows' => 6, 'media_buttons' => false, 'tinymce' => true, 'quicktags' => true ) ); ?>
<h3>Verfügbare Icons</h3>
<p class="description">Klicke auf ein Icon, um es in den Editor einzufügen.</p>
<div id="mm-announcement-icon-list" style="display:flex;flex-wrap:wrap;gap:10px;margin-bottom:20px;">
<?php
$icons = array( '⚡', '‼️', '❗', '✅', '❌', '⭐', '🔥', '💡', '📢', '🎮', '🏆', '🔔', '🎉', '💬', '🛡️' );
foreach ( $icons as $icon ) {
echo '<button type="button" class="mm-icon-button" style="font-size:20px;padding:6px 10px;border:1px solid #ccc;border-radius:4px;background:#f7f7f7;cursor:pointer;" title="Klicke zum Einfügen">' . $icon . '</button>';
}
?>
</div>
<script>
jQuery(document).ready(function($){
$('#mm-announcement-icon-list .mm-icon-button').on('click', function(){
var icon = $(this).text();
if (typeof tinymce !== 'undefined') {
var ed = tinymce.get('mm_announcement_text');
if (ed) { ed.execCommand('mceInsertContent', false, icon); return; }
}
var ta = $('#mm_announcement_text'), s = ta[0].selectionStart, e = ta[0].selectionEnd, v = ta.val();
ta.val(v.substring(0,s) + icon + v.substring(e));
ta[0].selectionStart = ta[0].selectionEnd = s + icon.length;
ta.focus();
});
});
</script>
<p class="description">HTML erlaubt (z. B. &lt;a&gt;-Links).</p>
<div id="mm-announcement-preview" style="background:<?php echo $bg; ?>;color:<?php echo $color; ?>;padding:12px;border-radius:6px;box-shadow:0 1px 4px rgba(0,0,0,.06);margin-top:14px;">
<div id="mm-announcement-preview-text" style="font-size:<?php echo $selected_size; ?>px;font-family:<?php echo isset( $fonts[ $selected_font ] ) ? esc_attr( $fonts[ $selected_font ]['css'] ) : 'inherit'; ?>;text-align:center;">
<?php echo esc_html( $text_sample ); ?>
</div>
<p style="margin-top:10px;color:#888;font-size:13px;text-align:center;">Live Vorschau</p>
</div>
</td>
</tr>
</table>
<h2>Position</h2>
<table class="form-table">
<tr>
<th>Anzeigeort</th>
<td>
<select name="mm_announcement_position" id="mm-announcement-position">
<option value="top" <?php selected( get_option( 'mm_announcement_position' ), 'top' ); ?>>Ganz oben (über dem Header)</option>
<option value="below-header" <?php selected( get_option( 'mm_announcement_position' ), 'below-header' ); ?>>Unter dem Header (Standard)</option>
</select>
</td>
</tr>
</table>
<h2>Design</h2>
<table class="form-table">
<tr>
<th>Hintergrundfarbe</th>
<td><input type="color" name="mm_announcement_bg" id="mm-announcement-bg" value="<?php echo $bg; ?>"></td>
</tr>
<tr>
<th>Textfarbe</th>
<td><input type="color" name="mm_announcement_color" id="mm-announcement-color" value="<?php echo $color; ?>"></td>
</tr>
<tr>
<th>Schriftgröße (px)</th>
<td><input type="number" min="10" max="48" name="mm_announcement_font_size" id="mm-announcement-size" value="<?php echo $selected_size; ?>"></td>
</tr>
<tr>
<th>Schriftfamilie</th>
<td>
<select name="mm_announcement_font_family" id="mm-announcement-font">
<?php foreach ( $fonts as $key => $f ) : ?>
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $selected_font, $key ); ?>><?php echo esc_html( $f['label'] ); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
</table>
<h2>Countdown Timer</h2>
<table class="form-table">
<tr>
<th>Countdown aktivieren</th>
<td><label><input type="checkbox" name="mm_announcement_countdown_enabled" value="1" <?php checked( 1, get_option( 'mm_announcement_countdown_enabled' ) ); ?>> Timer im Banner anzeigen</label></td>
</tr>
<tr>
<th>Label (Text vor Timer)</th>
<td><input type="text" name="mm_announcement_countdown_label" value="<?php echo esc_attr( get_option( 'mm_announcement_countdown_label', 'Event in:' ) ); ?>" style="width:100%;"></td>
</tr>
<tr>
<th>Zieldatum & Uhrzeit</th>
<td><input type="datetime-local" name="mm_announcement_countdown_date" value="<?php echo esc_attr( get_option( 'mm_announcement_countdown_date' ) ); ?>"></td>
</tr>
<tr>
<th>Nachricht nach Ablauf</th>
<td><input type="text" name="mm_announcement_countdown_expired_msg" value="<?php echo esc_attr( get_option( 'mm_announcement_countdown_expired_msg', 'Event läuft gerade!' ) ); ?>" style="width:100%;"></td>
</tr>
</table>
<?php submit_button( 'Änderungen speichern' ); ?>
</form>
</div>
<?php
}
function mm_render_announcement_bar() {
if ( ! get_option( 'mm_announcement_enabled' ) ) return;
$countdown_enabled = get_option( 'mm_announcement_countdown_enabled', false );
$countdown_label = esc_attr( get_option( 'mm_announcement_countdown_label', 'Event in:' ) );
$countdown_date = esc_attr( get_option( 'mm_announcement_countdown_date', '' ) );
$countdown_expired = esc_html( get_option( 'mm_announcement_countdown_expired_msg', 'Event läuft gerade!' ) );
$position = esc_attr( get_option( 'mm_announcement_position', 'below-header' ) );
$bg = esc_attr( get_option( 'mm_announcement_bg', '#1e1e1e' ) );
$color = esc_attr( get_option( 'mm_announcement_color', '#ffffff' ) );
$size = (int) get_option( 'mm_announcement_font_size', 16 );
$font_key = get_option( 'mm_announcement_font_family', 'inherit' );
$fonts = mm_announcement_get_font_list();
$font_css = isset( $fonts[ $font_key ] ) ? wp_strip_all_tags( $fonts[ $font_key ]['css'] ) : 'inherit';
$text_kses = wp_kses_post( get_option( 'mm_announcement_text', '' ) );
$countdown_html = '';
if ( $countdown_enabled && ! empty( $countdown_date ) ) {
$countdown_html = '<div class="mm-countdown-wrapper" style="display:inline-flex;align-items:center;margin-left:15px;padding-left:15px;border-left:1px solid rgba(255,255,255,0.3);font-weight:bold;">
<span class="mm-countdown-label">' . $countdown_label . ' </span>
<span class="mm-countdown-timer" data-date="' . $countdown_date . '" data-expired="' . $countdown_expired . '">Laden...</span>
</div>';
}
?>
<div id="mm-announcement"
data-position="<?php echo esc_attr( $position ); ?>"
style="background:<?php echo esc_attr( $bg ); ?>;color:<?php echo esc_attr( $color ); ?>;font-size:<?php echo esc_attr( $size ); ?>px;font-family:<?php echo esc_attr( $font_css ); ?>;">
<div class="mm-announcement-inner">
<div class="mm-announcement-text" style="display:inline-flex;align-items:center;width:100%;justify-content:center;">
<?php echo $text_kses; ?>
<?php echo $countdown_html; ?>
</div>
<button class="mm-announcement-close" aria-label="<?php esc_attr_e( 'Schließen', 'minecraft-modern-theme' ); ?>">&times;</button>
</div>
</div>
<?php
}
add_action( 'wp_body_open', 'mm_render_announcement_bar' );
function mm_announcement_enqueue_assets() {
// BUG-FIX: CSS/JS nicht laden wenn die Ankündigungs-Bar deaktiviert ist.
if ( ! get_option( 'mm_announcement_enabled' ) ) return;
wp_enqueue_style( 'mm-announcement-style', get_template_directory_uri() . '/css/announcement.css', array(), '1.2' );
wp_enqueue_script( 'mm-announcement-script', get_template_directory_uri() . '/js/announcement.js', array(), '1.4', true );
$fonts = mm_announcement_get_font_list();
$font_key = get_option( 'mm_announcement_font_family', 'inherit' );
if ( isset( $fonts[ $font_key ] ) && ! empty( $fonts[ $font_key ]['google'] ) && $fonts[ $font_key ]['google_name'] ) {
$url = 'https://fonts.googleapis.com/css2?family=' . rawurlencode( $fonts[ $font_key ]['google_name'] ) . ':wght@400;700&display=swap';
wp_enqueue_style( 'mm-announcement-google-font', $url, array(), null );
}
}
add_action( 'wp_enqueue_scripts', 'mm_announcement_enqueue_assets' );
function mm_announcement_admin_assets( $hook ) {
if ( $hook !== 'toplevel_page_mm-announcement' ) return;
wp_add_inline_style( 'wp-admin', '
.mm-announcement-admin .form-table th { width: 180px; vertical-align: top; }
.mm-announcement-admin .description { margin-top: 6px; color: #666; max-width: 720px; }
.mm-announcement-admin h2 { margin-top: 24px; }
#mm-announcement-preview { transition: all 140ms ease; }
#mm-announcement-preview-text { transition: font-family 160ms ease, font-size 120ms ease; white-space: normal; word-break: break-word; }
' );
wp_enqueue_script( 'mm-announcement-admin-script', get_template_directory_uri() . '/js/mm-announcement-admin.js', array( 'jquery' ), '1.1', true );
wp_localize_script( 'mm-announcement-admin-script', 'MM_Announcement_Fonts', mm_announcement_get_font_list() );
wp_localize_script( 'mm-announcement-admin-script', 'MM_Announcement_Current', array(
'font' => get_option( 'mm_announcement_font_family', 'inherit' ),
'size' => (int) get_option( 'mm_announcement_font_size', 16 ),
'bg' => get_option( 'mm_announcement_bg', '#1e1e1e' ),
'color' => get_option( 'mm_announcement_color', '#ffffff' ),
'text' => wp_kses_post( get_option( 'mm_announcement_text' ) ),
) );
}
add_action( 'admin_enqueue_scripts', 'mm_announcement_admin_assets' );
// =========================================================================
// TEAM MODUL
// =========================================================================
function create_team_post_type() {
if ( get_theme_mod( 'team_enabled', true ) ) {
register_post_type( 'team_member', array(
'labels' => array(
'name' => __( 'Team', 'minecraft-modern-theme' ),
'singular_name' => __( 'Teammitglied', 'minecraft-modern-theme' ),
'add_new' => __( 'Neues Mitglied', 'minecraft-modern-theme' ),
'menu_name' => __( 'Team', 'minecraft-modern-theme' ),
),
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-groups',
'supports' => array( 'title', 'thumbnail', 'page-attributes' ),
'rewrite' => array( 'slug' => 'team' ),
'show_in_rest' => true,
'show_in_menu' => false,
) );
}
}
add_action( 'init', 'create_team_post_type' );
function add_team_meta_boxes() {
add_meta_box( 'team_member_rank_box', __( 'Rang & Position', 'minecraft-modern-theme' ), 'team_member_rank_callback', 'team_member', 'side', 'default' );
}
add_action( 'add_meta_boxes', 'add_team_meta_boxes' );
function team_member_rank_callback( $post ) {
wp_nonce_field( 'team_member_rank_save', 'team_member_rank_nonce' );
$value = get_post_meta( $post->ID, '_team_member_rank', true );
?>
<p>
<label for="team_member_rank" style="font-weight:600;">Rang:</label>
<input type="text" id="team_member_rank" name="team_member_rank" value="<?php echo esc_attr( $value ); ?>" style="width:100%;" placeholder="z.B. Admin, Mod">
</p>
<?php
}
function save_team_member_rank( $post_id ) {
if ( ! isset( $_POST['team_member_rank_nonce'] ) ) return;
if ( ! wp_verify_nonce( $_POST['team_member_rank_nonce'], 'team_member_rank_save' ) ) return;
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
if ( isset( $_POST['team_member_rank'] ) ) {
update_post_meta( $post_id, '_team_member_rank', sanitize_text_field( $_POST['team_member_rank'] ) );
}
}
add_action( 'save_post', 'save_team_member_rank' );
add_action( 'admin_menu', 'register_team_manager_page' );
function register_team_manager_page() {
add_menu_page( 'Team Manager', 'Team Manager', 'manage_options', 'mm-team-manager', 'mm_team_manager_page_html', 'dashicons-groups', 6 );
add_action( 'admin_enqueue_scripts', function( $hook ) {
if ( $hook === 'toplevel_page_mm-team-manager' ) {
wp_enqueue_media();
}
} );
}
// Breite-Fix nur auf der Team-Manager-Seite
function mm_team_manager_admin_css() {
$screen = get_current_screen();
if ( ! $screen || $screen->id !== 'toplevel_page_mm-team-manager' ) return;
echo '<style>.toplevel_page_mm-team-manager .card{max-width:100%!important;}.toplevel_page_mm-team-manager #mm-add-member-form{display:grid!important;grid-template-columns:repeat(auto-fit,minmax(250px,1fr))!important;gap:20px!important;}</style>';
}
add_action( 'admin_head', 'mm_team_manager_admin_css' );
function mm_team_manager_page_html() {
?>
<div class="wrap" style="max-width:1200px;">
<h1>Team Verwaltung</h1>
<p>Hier kannst du Teammitglieder hinzufügen, sortieren und bearbeiten.</p>
<div class="card" style="background:#fff;border:1px solid #ccd0d4;border-left:4px solid #0073aa;padding:20px;margin-bottom:20px;">
<h2>Neues Mitglied hinzufügen</h2>
<form id="mm-add-member-form" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px;align-items:start;">
<div>
<label for="new_name"><strong>Name:</strong></label><br>
<input type="text" id="new_name" style="width:100%;" required>
</div>
<div>
<label for="new_rank"><strong>Rang:</strong></label><br>
<input type="text" id="new_rank" style="width:100%;" placeholder="z.B. Admin, Mod">
</div>
<div style="grid-column:1/-1;">
<label for="new_bio"><strong>Kurzbeschreibung:</strong></label><br>
<textarea id="new_bio" rows="2" style="width:100%;" placeholder="Kurztext..."></textarea>
</div>
<div style="grid-column:1/-1;display:flex;align-items:flex-start;gap:20px;flex-wrap:wrap;">
<!-- UUID Feld -->
<div style="flex:1;min-width:200px;">
<label for="new_uuid"><strong>Minecraft UUID:</strong></label><br>
<input type="text" id="new_uuid" style="width:100%;margin-top:4px;" placeholder="z.B. 069a79f4-44e9-4726-a5be-fca90e38aaf5">
<p style="margin:4px 0 0;color:#666;font-size:12px;">UUID eingeben → Avatar wird automatisch geladen</p>
</div>
<!-- ODER Avatar-Bild hochladen -->
<div style="flex:1;min-width:200px;">
<label><strong>Oder: Avatar-Bild:</strong></label><br>
<div style="margin-top:4px;display:flex;align-items:center;gap:10px;">
<input type="hidden" id="new_image_id" value="">
<button type="button" class="button mm-upload-btn" data-target="new_image_id">Bild auswählen</button>
<span id="new_image_name" style="color:#666;font-size:12px;">Kein Bild gewählt</span>
</div>
<p style="margin:4px 0 0;color:#666;font-size:12px;">Wird ignoriert wenn UUID gesetzt ist</p>
</div>
<!-- Banner-Bild -->
<div style="flex:1;min-width:200px;">
<label><strong>Banner-Bild:</strong></label><br>
<div style="margin-top:4px;display:flex;align-items:center;gap:10px;">
<input type="hidden" id="new_banner_id" value="">
<button type="button" class="button mm-upload-btn" data-target="new_banner_id">Banner auswählen</button>
<span id="new_banner_name" style="color:#666;font-size:12px;">Kein Banner gewählt</span>
</div>
<p style="margin:4px 0 0;color:#666;font-size:12px;">Hintergrundbild der Card (optional)</p>
<div id="new_banner_preview" style="margin-top:8px;width:100%;height:50px;background:#eee;border:1px solid #ccc;border-radius:4px;overflow:hidden;background-size:cover;background-position:center;"></div>
</div>
<!-- Avatar Vorschau -->
<div style="flex:0 0 80px;">
<label><strong>Vorschau:</strong></label><br>
<div id="new_image_preview" style="width:80px;height:80px;background:#eee;border:1px solid #ccc;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;margin-top:4px;">
<span style="color:#999;font-size:11px;"></span>
</div>
</div>
</div>
<div style="grid-column:1/-1;">
<button type="submit" class="button button-primary button-large" style="width:100%;">Hinzufügen</button>
</div>
</form>
</div>
<div class="card" style="background:#fff;padding:20px;">
<table class="wp-list-table widefat fixed striped" id="team-table" style="width:100%;table-layout:fixed;">
<thead>
<tr>
<th style="width:60px;">Bild</th>
<th style="width:18%;">Name</th>
<th style="width:15%;">Rang</th>
<th>Bio</th>
<th style="width:180px;">UUID / Bild</th>
<th style="width:70px;text-align:center;">Sort.</th>
<th style="width:100px;text-align:right;">Aktionen</th>
</tr>
</thead>
<tbody id="team-list-body">
<?php
$team_query = new WP_Query( array( 'post_type' => 'team_member', 'posts_per_page' => -1, 'orderby' => 'menu_order', 'order' => 'ASC' ) );
if ( $team_query->have_posts() ) :
while ( $team_query->have_posts() ) : $team_query->the_post();
$id = get_the_ID();
$name = get_the_title();
$rank = get_post_meta( $id, '_team_member_rank', true );
$bio = get_the_content();
$uuid = get_post_meta( $id, '_team_member_uuid', true );
$img_id = get_post_thumbnail_id( $id );
$banner_id = get_post_meta( $id, '_team_member_banner', true );
$banner_url = $banner_id ? wp_get_attachment_image_url( $banner_id, 'medium' ) : false;
// Avatar anzeigen: UUID > Bild > Placeholder
if ( $uuid ) {
$avatar_src = 'https://visage.surgeplay.com/bust/' . esc_attr($uuid) . '.png';
} elseif ( $img_id ) {
$avatar_src = wp_get_attachment_image_url( $img_id, array(40,40) );
} else {
$avatar_src = false;
}
?>
<tr data-id="<?php echo $id; ?>">
<td style="text-align:center;vertical-align:middle;">
<div class="mm-avatar-preview" style="width:40px;height:40px;background:#eee;display:inline-flex;align-items:center;justify-content:center;overflow:hidden;border-radius:4px;border:1px solid #ddd;">
<?php if ( $avatar_src ) : ?>
<img src="<?php echo esc_url($avatar_src); ?>" style="width:100%;height:100%;object-fit:cover;">
<?php else : ?>
<span style="font-size:20px;">👤</span>
<?php endif; ?>
</div>
</td>
<td><input type="text" class="inline-edit" data-field="post_title" value="<?php echo esc_attr( $name ); ?>" style="width:100%;box-sizing:border-box;padding:6px;"></td>
<td><input type="text" class="inline-edit" data-field="_team_member_rank" value="<?php echo esc_attr( $rank ); ?>" style="width:100%;box-sizing:border-box;padding:6px;"></td>
<td><input type="text" class="inline-edit" data-field="post_content" value="<?php echo esc_attr( wp_strip_all_tags( $bio ) ); ?>" style="width:100%;box-sizing:border-box;padding:6px;"></td>
<td>
<input type="text" class="inline-edit uuid-field" data-field="_team_member_uuid"
value="<?php echo esc_attr( $uuid ); ?>"
placeholder="UUID oder leer lassen"
style="width:100%;box-sizing:border-box;padding:6px;font-size:11px;margin-bottom:4px;">
<div style="display:flex;align-items:center;gap:6px;margin-top:4px;">
<input type="hidden" class="row-banner-id" value="<?php echo esc_attr( $banner_id ); ?>">
<button type="button" class="button button-small mm-upload-btn-banner" data-id="<?php echo $id; ?>" style="font-size:11px;">Banner</button>
<?php if ( $banner_url ) : ?>
<div style="width:40px;height:20px;border-radius:3px;overflow:hidden;background:url('<?php echo esc_url($banner_url); ?>') center/cover;border:1px solid #ccc;"></div>
<?php endif; ?>
</div>
</td>
<td style="text-align:center;vertical-align:middle;">
<button class="button button-small sort-up" title="Nach oben">▲</button>
<button class="button button-small sort-down" title="Nach unten">▼</button>
</td>
<td style="text-align:right;vertical-align:middle;">
<button class="button button-primary button-small save-row" title="Speichern">💾</button>
<button class="button button-small delete-row" title="Löschen">🗑️</button>
</td>
</tr>
<?php
endwhile;
else :
echo '<tr><td colspan="7" style="text-align:center;">Noch keine Mitglieder vorhanden.</td></tr>';
endif;
?>
</tbody>
</table>
</div>
</div>
<script>
jQuery(document).ready(function($) {
var mediaUploader;
$(document).on('click', '.mm-upload-btn', function(e) {
e.preventDefault();
var targetInput = $(this).data('target');
var isBanner = (targetInput === 'new_banner_id');
var uploader = wp.media({ title: isBanner ? 'Banner auswählen' : 'Bild auswählen', button: { text: 'Auswählen' }, multiple: false });
uploader.on('select', function() {
var att = uploader.state().get('selection').first().toJSON();
$('#' + targetInput).val(att.id);
if (isBanner) {
$('#new_banner_name').text(att.title);
var url = att.sizes && att.sizes.medium ? att.sizes.medium.url : att.url;
$('#new_banner_preview').css({'background-image': 'url(' + url + ')', 'background-size': 'cover', 'background-position': 'center'});
} else {
$('#new_image_name').text(att.title);
var thumb = att.sizes && att.sizes.thumbnail ? att.sizes.thumbnail.url : att.url;
$('#new_image_preview').html('<img src="' + thumb + '" style="width:100%;height:100%;object-fit:cover;">');
}
});
uploader.open();
});
$('#mm-add-member-form').on('submit', function(e) {
e.preventDefault();
var $form = $(this);
var btn = $form.find('button[type="submit"]').prop('disabled', true).text('Lade...');
$.ajax({
url: ajaxurl,
type: 'POST',
timeout: 15000,
data: {
action: 'mm_add_team_member',
name: $('#new_name').val(),
rank: $('#new_rank').val(),
bio: $('#new_bio').val(),
img_id: $('#new_image_id').val(),
uuid: $('#new_uuid').val(),
banner_id: $('#new_banner_id').val(),
nonce: '<?php echo wp_create_nonce( 'mm_team_nonce' ); ?>'
},
success: function(r) {
if (r.success) {
location.reload();
} else {
alert('Fehler: ' + r.data);
btn.prop('disabled', false).text('Hinzufügen');
}
},
error: function(xhr, status) {
alert('Verbindungsfehler (' + status + '). Bitte erneut versuchen.');
btn.prop('disabled', false).text('Hinzufügen');
}
});
});
// Banner-Upload in Tabellenzeile
$(document).on('click', '.mm-upload-btn-banner', function() {
var row = $(this).closest('tr');
var uploader = wp.media({ title: 'Banner auswählen', button: { text: 'Auswählen' }, multiple: false });
uploader.on('select', function() {
var att = uploader.state().get('selection').first().toJSON();
row.find('.row-banner-id').val(att.id);
var url = att.sizes && att.sizes.medium ? att.sizes.medium.url : att.url;
var preview = row.find('.mm-upload-btn-banner').next('div');
if (preview.length) {
preview.css({'background-image': 'url('+url+')'});
} else {
row.find('.mm-upload-btn-banner').after('<div style="width:40px;height:20px;border-radius:3px;overflow:hidden;background:url('+url+') center/cover;border:1px solid #ccc;display:inline-block;vertical-align:middle;margin-left:4px;"></div>');
}
});
uploader.open();
});
var uuidTimer;
$('#new_uuid').on('input', function() {
clearTimeout(uuidTimer);
var uuid = $(this).val().trim();
var preview = $('#new_image_preview');
if (!uuid) {
preview.html('<span style="color:#999;font-size:11px;"></span>');
return;
}
uuidTimer = setTimeout(function() {
var url = 'https://visage.surgeplay.com/bust/' + uuid + '.png';
preview.html('<img src="' + url + '" style="width:100%;height:100%;object-fit:cover;" onerror="this.parentNode.innerHTML=\'<span style=color:#c00;font-size:11px>Ungültig</span>\'" />');
}, 600);
});
$('.save-row').on('click', function() {
var row = $(this).closest('tr'), btn = $(this);
var data = { action: 'mm_update_team_member', id: row.data('id'), nonce: '<?php echo wp_create_nonce( 'mm_team_nonce' ); ?>' };
row.find('.inline-edit').each(function() {
var f = $(this).data('field'), v = $(this).val();
if (f === 'post_title') data.title = v;
if (f === '_team_member_rank') data.rank = v;
if (f === 'post_content') data.bio = v;
if (f === '_team_member_uuid') data.uuid = v;
});
// Bild-ID falls vorhanden
var imgInput = row.find('.row-img-id');
if (imgInput.length) data.img_id = imgInput.val();
// Banner-ID
var bannerInput = row.find('.row-banner-id');
if (bannerInput.length) data.banner_id = bannerInput.val();
btn.text('✓').prop('disabled', true);
$.post(ajaxurl, data, function(r) {
// Avatar live aktualisieren
if (data.uuid) {
var url = 'https://visage.surgeplay.com/bust/' + data.uuid + '.png';
row.find('.mm-avatar-preview').html('<img src="' + url + '" style="width:100%;height:100%;object-fit:cover;">');
}
setTimeout(function(){ btn.text('💾').prop('disabled', false); }, 1000);
});
});
$('.delete-row').on('click', function() {
if (!confirm('Löschen?')) return;
var row = $(this).closest('tr');
$.post(ajaxurl, { action: 'mm_delete_team_member', id: row.data('id'), nonce: '<?php echo wp_create_nonce( 'mm_team_nonce' ); ?>' }, function() { row.fadeOut().remove(); });
});
$('.sort-up').on('click', function() { var r = $(this).closest('tr'), p = r.prev('tr'); if (p.length) r.insertBefore(p); });
$('.sort-down').on('click', function() { var r = $(this).closest('tr'), n = r.next('tr'); if (n.length) r.insertAfter(n); });
});
</script>
<?php
}
add_action( 'wp_ajax_mm_add_team_member', 'handle_mm_add_member' );
function handle_mm_add_member() {
check_ajax_referer( 'mm_team_nonce', 'nonce' );
if ( ! current_user_can( 'publish_posts' ) ) wp_send_json_error( 'Keine Berechtigung' );
$id = wp_insert_post( array(
'post_title' => sanitize_text_field( $_POST['name'] ),
'post_content' => sanitize_textarea_field( $_POST['bio'] ),
'post_type' => 'team_member',
'post_status' => 'publish',
'menu_order' => 999,
) );
if ( ! $id || is_wp_error( $id ) ) {
wp_send_json_error( 'Fehler beim Erstellen' );
}
update_post_meta( $id, '_team_member_rank', sanitize_text_field( $_POST['rank'] ) );
// UUID speichern (kein externer Request hier nur als Text speichern)
if ( ! empty( $_POST['uuid'] ) ) {
$uuid = sanitize_text_field( trim( $_POST['uuid'] ) );
update_post_meta( $id, '_team_member_uuid', $uuid );
} elseif ( ! empty( $_POST['img_id'] ) ) {
set_post_thumbnail( $id, intval( $_POST['img_id'] ) );
}
// Banner-Bild speichern
if ( ! empty( $_POST['banner_id'] ) ) {
update_post_meta( $id, '_team_member_banner', intval( $_POST['banner_id'] ) );
}
wp_send_json_success( array( 'id' => $id, 'msg' => 'Hinzugefügt' ) );
}
add_action( 'wp_ajax_mm_update_team_member', 'handle_mm_update_team_member' );
function handle_mm_update_team_member() {
check_ajax_referer( 'mm_team_nonce', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) wp_send_json_error( 'Keine Berechtigung' );
$id = intval( $_POST['id'] );
wp_update_post( array(
'ID' => $id,
'post_title' => sanitize_text_field( $_POST['title'] ),
'post_content' => sanitize_textarea_field( $_POST['bio'] ),
) );
update_post_meta( $id, '_team_member_rank', sanitize_text_field( $_POST['rank'] ) );
// UUID speichern oder löschen
if ( isset( $_POST['uuid'] ) ) {
$uuid = sanitize_text_field( trim( $_POST['uuid'] ) );
if ( $uuid ) {
update_post_meta( $id, '_team_member_uuid', $uuid );
} else {
delete_post_meta( $id, '_team_member_uuid' );
}
}
// Bild nur setzen wenn keine UUID
if ( empty( $_POST['uuid'] ) && ! empty( $_POST['img_id'] ) ) {
set_post_thumbnail( $id, intval( $_POST['img_id'] ) );
}
// Banner speichern
if ( isset( $_POST['banner_id'] ) ) {
if ( ! empty( $_POST['banner_id'] ) ) {
update_post_meta( $id, '_team_member_banner', intval( $_POST['banner_id'] ) );
}
}
wp_send_json_success( 'Gespeichert' );
}
add_action( 'wp_ajax_mm_delete_team_member', 'handle_mm_delete_team_member' );
function handle_mm_delete_team_member() {
check_ajax_referer( 'mm_team_nonce', 'nonce' );
if ( ! current_user_can( 'delete_posts' ) ) wp_send_json_error( 'Keine Berechtigung' );
wp_delete_post( intval( $_POST['id'] ), true );
wp_send_json_success( 'Gelöscht' );
}
function create_team_page_automatically() {
if ( get_theme_mod( 'team_enabled', true ) && get_page_by_title( 'Team' ) == null ) {
wp_insert_post( array( 'post_title' => 'Team', 'post_status' => 'publish', 'post_type' => 'page', 'post_author' => 1 ) );
}
}
add_action( 'customize_save_after', 'create_team_page_automatically' );
function load_team_page_template( $template ) {
if ( ! get_theme_mod( 'team_enabled', true ) ) return $template;
if ( is_post_type_archive( 'team_member' ) ) return get_template_directory() . '/archive-team.php';
if ( is_page() ) {
$obj = get_queried_object();
if ( $obj && $obj->post_name === 'team' ) return get_template_directory() . '/archive-team.php';
}
return $template;
}
add_filter( 'template_include', 'load_team_page_template' );
// Doppelte team_customize_register entfernt ist jetzt in inc/customizer.php
// =========================================================================
// MENÜ-LAYOUTS: Hilfsfunktionen für header.php
// =========================================================================
if ( ! function_exists('mm_branding') ) :
function mm_branding( $show_title_with_logo = false ) { ?>
<div class="site-branding">
<?php if ( function_exists('the_custom_logo') && has_custom_logo() ) : ?>
<?php the_custom_logo(); ?>
<?php if ( $show_title_with_logo ) : ?>
<p class="site-title">
<a href="<?php echo esc_url( home_url('/') ); ?>" rel="home"><?php bloginfo('name'); ?></a>
</p>
<?php endif; ?>
<?php else : ?>
<?php if ( is_front_page() && is_home() ) : ?>
<h1 class="site-title"><a href="<?php echo esc_url( home_url('/') ); ?>" rel="home"><?php bloginfo('name'); ?></a></h1>
<?php else : ?>
<p class="site-title"><a href="<?php echo esc_url( home_url('/') ); ?>" rel="home"><?php bloginfo('name'); ?></a></p>
<?php endif; ?>
<?php endif; ?>
</div>
<?php }
endif;
if ( ! function_exists('mm_nav') ) :
function mm_nav( $extra_class = '' ) {
$menu_style = get_theme_mod( 'header_menu_style', 'classic' );
?>
<nav id="site-navigation" class="main-navigation <?php echo esc_attr($extra_class); ?>" role="navigation"
aria-label="<?php esc_attr_e('Hauptmenü', 'minecraft-modern-theme'); ?>">
<?php
// BUG-FIX: .menu-toggle nur für Classic / Centered / Mega ausgeben.
// Im Sidebar-Layout sitzt die Navigation im <aside> Panel
// der Hamburger wird dort nicht gebraucht und verursacht JS-Konflikte.
if ( $menu_style !== 'sidebar' ) : ?>
<button class="menu-toggle" aria-controls="primary-menu" aria-expanded="false">
<i class="fas fa-bars"></i>
</button>
<?php endif; ?>
<?php wp_nav_menu( array(
'theme_location' => 'primary',
'container' => false,
'menu_class' => 'primary-menu',
'fallback_cb' => false,
) ); ?>
</nav>
<?php }
endif;
if ( ! function_exists('mm_icons') ) :
function mm_icons() {
$social_icons = array(
'bluesky' => 'fab fa-bluesky', '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',
'mastodon' => 'fab fa-mastodon', 'threads' => 'fab fa-threads',
'kickstarter' => 'fab fa-kickstarter', 'teamspeak' => 'fab fa-teamspeak', 'spotify' => 'fab fa-spotify',
'stoat' => 'fab fa-diaspora',
); ?>
<div class="header-info">
<div class="header-search">
<button class="header-search-toggle" aria-label="<?php esc_attr_e('Suche öffnen', 'minecraft-modern-theme'); ?>" aria-expanded="false">
<i class="fas fa-search"></i>
</button>
<div class="header-search-dropdown" aria-hidden="true">
<?php get_search_form(); ?>
</div>
</div>
<div class="social-links">
<?php foreach ( $social_icons as $key => $class ) :
$url = get_theme_mod( 'social_' . $key );
if ( $url ) echo '<a href="' . esc_url($url) . '" target="_blank" rel="noopener noreferrer" aria-label="' . esc_attr($key) . '"><i class="' . esc_attr($class) . '"></i></a>';
endforeach; ?>
</div>
</div>
<?php }
endif;
// === Menü-Design Customizer-Einstellung ===
function minecraft_modern_menu_style_customizer( $wp_customize ) {
$wp_customize->add_section( 'header_menu_style_section', array(
'title' => __( 'Menü-Design', 'minecraft-modern-theme' ),
'priority' => 30,
) );
$wp_customize->add_setting( 'header_menu_style', array(
'default' => 'classic',
'sanitize_callback' => 'sanitize_text_field',
'transport' => 'refresh',
) );
$wp_customize->add_control( 'header_menu_style', array(
'label' => __( 'Menü-Layout wählen', 'minecraft-modern-theme' ),
'section' => 'header_menu_style_section',
'type' => 'select',
'choices' => array(
'classic' => __( '① Classic Logo links, Menü Mitte, Icons rechts', 'minecraft-modern-theme' ),
'centered' => __( '② Zentriert Logo oben, Menü darunter', 'minecraft-modern-theme' ),
'sidebar' => __( '③ Sidebar Menü als vertikale Leiste', 'minecraft-modern-theme' ),
'mega' => __( '④ Mega-Menü breite Dropdown-Spalten', 'minecraft-modern-theme' ),
),
) );
// Branding-Position (gilt für alle Layouts)
$wp_customize->add_setting( 'sidebar_branding_position', array(
'default' => 'left',
'sanitize_callback' => 'sanitize_text_field',
'transport' => 'refresh',
) );
$wp_customize->add_control( 'sidebar_branding_position', array(
'label' => __( 'Logo/Titel Position', 'minecraft-modern-theme' ),
'description' => __( 'Gilt für alle Menü-Layouts.', 'minecraft-modern-theme' ),
'section' => 'header_menu_style_section',
'type' => 'select',
'choices' => array(
'left' => __( 'Links', 'minecraft-modern-theme' ),
'center' => __( 'Mitte', 'minecraft-modern-theme' ),
'right' => __( 'Rechts', 'minecraft-modern-theme' ),
),
) );
}
add_action( 'customize_register', 'minecraft_modern_menu_style_customizer' );
// === Sidebar-Menü JavaScript (nur wenn Sidebar-Layout aktiv) ===
function minecraft_modern_sidebar_menu_script() {
if ( get_theme_mod( 'header_menu_style', 'classic' ) !== 'sidebar' ) return;
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
var toggle = document.querySelector('.sidebar-menu-toggle');
var sidebar = document.getElementById('header-sidebar');
var closeBtn = document.querySelector('.sidebar-menu-close');
// BUG-FIX: Overlay NICHT mehr per JS erstellen.
// header.php gibt bereits <div id="sidebar-overlay"> aus.
// Das alte createElement() erzeugte ein ZWEITES Overlay-Div,
// was zu doppelten Klick-Handlern und visuellem Flickern führte.
var overlay = document.getElementById('sidebar-overlay');
if (!toggle || !sidebar) return;
function openSidebar() {
sidebar.classList.add('is-open');
sidebar.setAttribute('aria-hidden', 'false');
toggle.setAttribute('aria-expanded', 'true');
if (overlay) overlay.classList.add('is-visible');
document.body.classList.add('sidebar-nav-open');
}
function closeSidebar() {
sidebar.classList.remove('is-open');
sidebar.setAttribute('aria-hidden', 'true');
toggle.setAttribute('aria-expanded', 'false');
if (overlay) overlay.classList.remove('is-visible');
document.body.classList.remove('sidebar-nav-open');
}
toggle.addEventListener('click', openSidebar);
if (closeBtn) closeBtn.addEventListener('click', closeSidebar);
if (overlay) overlay.addEventListener('click', closeSidebar);
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeSidebar();
});
});
</script>
<?php
}
add_action( 'wp_footer', 'minecraft_modern_sidebar_menu_script' );
// Such-Toggle wird von header-scroll.js behandelt.
// === Single-Post Sidebar registrieren ===
function minecraft_modern_single_sidebar() {
register_sidebar( array(
'name' => __( 'Beitrag Sidebar', 'minecraft-modern-theme' ),
'id' => 'single-post-sidebar',
'description' => __( 'Widget-Bereich für die Sidebar auf Einzelbeitrags-Seiten.', 'minecraft-modern-theme' ),
'before_widget' => '<div id="%1$s" class="widget %2$s sidebar-widget">',
'after_widget' => '</div>',
'before_title' => '<h3 class="widget-title">',
'after_title' => '</h3>',
) );
}
add_action( 'widgets_init', 'minecraft_modern_single_sidebar' );
// === Single-Post Sidebar Render-Funktion ===
function minecraft_modern_render_single_sidebar() {
if ( is_active_sidebar( 'single-post-sidebar' ) ) {
dynamic_sidebar( 'single-post-sidebar' );
} else {
?>
<div class="sidebar-widget widget_search">
<h3 class="widget-title"><?php _e('Suche', 'minecraft-modern-theme'); ?></h3>
<?php get_search_form(); ?>
</div>
<div class="sidebar-widget widget_recent_entries">
<h3 class="widget-title"><?php _e('Letzte Beiträge', 'minecraft-modern-theme'); ?></h3>
<?php $recent = new WP_Query( array( 'posts_per_page' => 5, 'post_status' => 'publish' ) );
if ( $recent->have_posts() ) : ?>
<ul class="sidebar-recent-posts">
<?php while ( $recent->have_posts() ) : $recent->the_post(); ?>
<li>
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
<span class="sidebar-post-date"><i class="fas fa-calendar-alt"></i> <?php echo esc_html( get_the_date() ); ?></span>
</li>
<?php endwhile; wp_reset_postdata(); ?>
</ul>
<?php endif; ?>
</div>
<div class="sidebar-widget widget_categories">
<h3 class="widget-title"><?php _e('Kategorien', 'minecraft-modern-theme'); ?></h3>
<ul class="sidebar-categories">
<?php wp_list_categories( array( 'show_count' => true, 'title_li' => '', 'orderby' => 'count', 'order' => 'DESC', 'number' => 10 ) ); ?>
</ul>
</div>
<div class="sidebar-widget widget_tag_cloud">
<h3 class="widget-title"><?php _e('Schlagwörter', 'minecraft-modern-theme'); ?></h3>
<?php wp_tag_cloud( array( 'smallest' => 11, 'largest' => 16, 'unit' => 'px', 'number' => 30, 'format' => 'flat', 'orderby' => 'count', 'order' => 'DESC' ) ); ?>
</div>
<div class="sidebar-widget sidebar-archive-dropdown">
<h3 class="widget-title"><?php _e('Archiv', 'minecraft-modern-theme'); ?></h3>
<select class="archive-select" onchange="if(this.value) window.location.href=this.value;">
<option value=""><?php _e('Monat auswählen', 'minecraft-modern-theme'); ?></option>
<?php wp_get_archives( array( 'type' => 'monthly', 'format' => 'option', 'show_post_count' => true ) ); ?>
</select>
</div>
<?php
}
}
// Archiv-Widget Liste → Dropdown per JS umwandeln
function minecraft_modern_archive_to_dropdown() {
if ( ! is_single() ) return;
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.single-sidebar .widget_archive').forEach(function(widget) {
var list = widget.querySelector('ul');
if (!list) return;
var select = document.createElement('select');
select.className = 'archive-select';
select.innerHTML = '<option value="">Monat auswählen</option>';
list.querySelectorAll('a').forEach(function(a) {
var opt = document.createElement('option');
opt.value = a.href;
opt.textContent = a.textContent;
select.appendChild(opt);
});
select.addEventListener('change', function() { if (this.value) window.location.href = this.value; });
list.replaceWith(select);
});
});
</script>
<?php
}
add_action( 'wp_footer', 'minecraft_modern_archive_to_dropdown' );
// === Single-Post Sidebar Customizer-Einstellungen ===
function minecraft_modern_single_sidebar_customizer( $wp_customize ) {
$wp_customize->add_section( 'single_sidebar_section', array(
'title' => __( 'Beitrag Sidebar', 'minecraft-modern-theme' ),
'priority' => 55,
) );
$wp_customize->add_setting( 'single_sidebar_enabled', array(
'default' => true,
'sanitize_callback' => 'wp_validate_boolean',
) );
$wp_customize->add_control( 'single_sidebar_enabled', array(
'label' => __( 'Sidebar auf Einzelbeiträgen anzeigen', 'minecraft-modern-theme' ),
'section' => 'single_sidebar_section',
'type' => 'checkbox',
) );
$wp_customize->add_setting( 'single_sidebar_position', array(
'default' => 'right',
'sanitize_callback' => 'sanitize_text_field',
) );
$wp_customize->add_control( 'single_sidebar_position', array(
'label' => __( 'Sidebar-Position', 'minecraft-modern-theme' ),
'section' => 'single_sidebar_section',
'type' => 'select',
'choices' => array(
'right' => __( 'Rechts', 'minecraft-modern-theme' ),
'left' => __( 'Links', 'minecraft-modern-theme' ),
),
) );
}
add_action( 'customize_register', 'minecraft_modern_single_sidebar_customizer' );
// === Announcement-Bar Höhe als CSS-Variable + Body-Klasse ===
function minecraft_modern_announcement_offset_script() {
if ( ! get_option( 'mm_announcement_enabled' ) ) return;
?>
<script>
document.addEventListener('DOMContentLoaded', function() {
var bar = document.getElementById('mm-announcement');
if (!bar) return;
function updateOffset() {
var h = bar.offsetHeight;
document.documentElement.style.setProperty('--announcement-height', h + 'px');
document.body.classList.add('has-announcement');
}
updateOffset();
new ResizeObserver(updateOffset).observe(bar);
// Wenn Bar geschlossen wird
var closeBtn = bar.querySelector('.mm-announcement-close');
if (closeBtn) {
closeBtn.addEventListener('click', function() {
document.documentElement.style.setProperty('--announcement-height', '0px');
document.body.classList.remove('has-announcement');
});
}
});
</script>
<?php
}
add_action( 'wp_footer', 'minecraft_modern_announcement_offset_script' );
// =============================================================================
// === COOKIE-BANNER (DSGVO) 4 Varianten, alle gleichzeitig gerendert =======
// =============================================================================
// Vorschau-Control
if ( class_exists('WP_Customize_Control') && ! class_exists('MM_Cookie_Preview_Control') ) :
class MM_Cookie_Preview_Control extends WP_Customize_Control {
public $type = 'mm_cookie_preview';
public function render_content() { ?>
<div style="margin-top:4px;">
<span class="customize-control-title" style="display:block;margin-bottom:8px;"><?php _e('Live-Vorschau','minecraft-modern-theme'); ?></span>
<div style="padding:12px 14px;background:rgba(0,212,255,0.07);border-left:3px solid #00d4ff;border-radius:0 6px 6px 0;">
<p style="margin:0 0 5px;font-size:12px;color:#00d4ff;font-weight:600;">← <?php _e('Vorschau rechts','minecraft-modern-theme'); ?></p>
<p style="margin:0;font-size:11px;color:#aaa;line-height:1.5;"><?php _e('Der Banner wird direkt in der Seitenvorschau angezeigt und aktualisiert sich live beim Wechseln der Variante.','minecraft-modern-theme'); ?></p>
</div>
</div>
<?php }
}
endif;
// --- 1. Customizer ---
function mm_cookie_banner_customizer( $wp_customize ) {
$wp_customize->add_section( 'mm_cookie_banner_section', array(
'title' => __( 'Cookie-Banner (DSGVO)', 'minecraft-modern-theme' ),
'priority' => 75,
) );
$wp_customize->add_setting( 'mm_cookie_enabled', array( 'default' => true, 'sanitize_callback' => 'wp_validate_boolean', 'transport' => 'postMessage' ) );
$wp_customize->add_control( 'mm_cookie_enabled', array( 'label' => __( 'Cookie-Banner aktivieren', 'minecraft-modern-theme' ), 'section' => 'mm_cookie_banner_section', 'type' => 'checkbox' ) );
$wp_customize->add_setting( 'mm_cookie_style', array( 'default' => 'bar', 'sanitize_callback' => 'sanitize_text_field', 'transport' => 'postMessage' ) );
$wp_customize->add_control( 'mm_cookie_style', array(
'label' => __( 'Design-Variante', 'minecraft-modern-theme' ),
'section' => 'mm_cookie_banner_section',
'type' => 'select',
'choices' => array(
'bar' => __( 'Variante 1 Schmale Bar (volle Breite)', 'minecraft-modern-theme' ),
'split' => __( 'Variante 2 Zweispaltig (3A)', 'minecraft-modern-theme' ),
'slide' => __( 'Variante 3 Slide-In von rechts (3B)', 'minecraft-modern-theme' ),
'stepper' => __( 'Variante 4 Kompakt-Center mit Stepper (3C)', 'minecraft-modern-theme' ),
),
) );
$wp_customize->add_setting( 'mm_cookie_text', array( 'default' => __( 'Wir nutzen Cookies und ähnliche Technologien. Einige sind essenziell, andere helfen uns diese Website zu verbessern. Du kannst deine Auswahl jederzeit anpassen.', 'minecraft-modern-theme' ), 'sanitize_callback' => 'wp_kses_post', 'transport' => 'postMessage' ) );
$wp_customize->add_control( 'mm_cookie_text', array( 'label' => __( 'Banner-Text', 'minecraft-modern-theme' ), 'section' => 'mm_cookie_banner_section', 'type' => 'textarea' ) );
$wp_customize->add_setting( 'mm_cookie_privacy_url', array( 'default' => '', 'sanitize_callback' => 'esc_url_raw' ) );
$wp_customize->add_control( 'mm_cookie_privacy_url', array( 'label' => __( 'URL Datenschutzerklärung', 'minecraft-modern-theme' ), 'description' => __( 'Leer lassen um den Link auszublenden.', 'minecraft-modern-theme' ), 'section' => 'mm_cookie_banner_section', 'type' => 'url' ) );
foreach ( array(
'necessary' => array( 'label' => __( 'Beschreibung: Notwendige', 'minecraft-modern-theme' ), 'default' => __( 'Grundlegende Funktionen der Website. Können nicht deaktiviert werden.', 'minecraft-modern-theme' ) ),
'statistics' => array( 'label' => __( 'Beschreibung: Statistik', 'minecraft-modern-theme' ), 'default' => __( 'Helfen uns zu verstehen wie Besucher mit der Website interagieren (z.B. Google Analytics).', 'minecraft-modern-theme' ) ),
'marketing' => array( 'label' => __( 'Beschreibung: Marketing', 'minecraft-modern-theme' ), 'default' => __( 'Werden genutzt um Werbung relevanter zu gestalten (z.B. YouTube, Facebook).', 'minecraft-modern-theme' ) ),
) as $key => $opts ) {
$wp_customize->add_setting( 'mm_cookie_desc_' . $key, array( 'default' => $opts['default'], 'sanitize_callback' => 'sanitize_textarea_field' ) );
$wp_customize->add_control( 'mm_cookie_desc_' . $key, array( 'label' => $opts['label'], 'section' => 'mm_cookie_banner_section', 'type' => 'textarea' ) );
}
$wp_customize->add_setting( 'mm_cookie_ga_id', array( 'default' => '', 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( 'mm_cookie_ga_id', array( 'label' => __( 'Google Analytics ID (optional)', 'minecraft-modern-theme' ), 'description' => __( 'z.B. G-XXXXXXXXXX', 'minecraft-modern-theme' ), 'section' => 'mm_cookie_banner_section', 'type' => 'text' ) );
$wp_customize->add_setting( 'mm_cookie_lifetime', array( 'default' => 365, 'sanitize_callback' => 'absint' ) );
$wp_customize->add_control( 'mm_cookie_lifetime', array( 'label' => __( 'Cookie-Laufzeit (Tage)', 'minecraft-modern-theme' ), 'section' => 'mm_cookie_banner_section', 'type' => 'number', 'input_attrs' => array( 'min' => 1, 'max' => 730 ) ) );
if ( class_exists( 'MM_Cookie_Preview_Control' ) ) {
$wp_customize->add_setting( 'mm_cookie_preview_dummy', array( 'sanitize_callback' => 'sanitize_text_field' ) );
$wp_customize->add_control( new MM_Cookie_Preview_Control( $wp_customize, 'mm_cookie_preview_dummy', array( 'section' => 'mm_cookie_banner_section', 'priority' => 200 ) ) );
}
}
add_action( 'customize_register', 'mm_cookie_banner_customizer' );
// --- 2. Banner HTML ALLE 4 LAYOUTS gleichzeitig ausgeben ---
// Im Customizer wechselt JS nur die Klasse auf #mm-cookie-banner.
// CSS zeigt immer nur das passende .mmc-layout-* div.
function mm_cookie_banner_render() {
$is_preview = is_customize_preview();
if ( ! $is_preview && ! get_theme_mod( 'mm_cookie_enabled', true ) ) return;
$style = get_theme_mod( 'mm_cookie_style', 'bar' );
$text = get_theme_mod( 'mm_cookie_text', __( 'Wir nutzen Cookies und ähnliche Technologien. Einige sind essenziell, andere helfen uns diese Website zu verbessern.', 'minecraft-modern-theme' ) );
$priv_url = get_theme_mod( 'mm_cookie_privacy_url', '' );
$lifetime = absint( get_theme_mod( 'mm_cookie_lifetime', 365 ) );
$ga_id = get_theme_mod( 'mm_cookie_ga_id', '' );
$desc_n = get_theme_mod( 'mm_cookie_desc_necessary', __( 'Grundlegende Funktionen der Website. Können nicht deaktiviert werden.', 'minecraft-modern-theme' ) );
$desc_s = get_theme_mod( 'mm_cookie_desc_statistics', __( 'Helfen uns zu verstehen wie Besucher mit der Website interagieren (z.B. Google Analytics).', 'minecraft-modern-theme' ) );
$desc_m = get_theme_mod( 'mm_cookie_desc_marketing', __( 'Werden genutzt um Werbung relevanter zu gestalten (z.B. YouTube, Facebook).', 'minecraft-modern-theme' ) );
$priv_link = $priv_url ? '<a href="' . esc_url($priv_url) . '" target="_blank" rel="noopener noreferrer" class="mmc-priv-link">' . __( 'Datenschutzerklärung', 'minecraft-modern-theme' ) . '</a>' : '';
$preview_class = $is_preview ? ' mmc-visible mmc-preview-mode' : '';
$inline_style = $is_preview ? '' : 'display:none;';
// Kategorien-Block (identisch in allen Varianten mit Kategorien)
$cats = '<div class="mmc-cats">
<div class="mmc-cat"><div class="mmc-cat-row"><span class="mmc-cat-name"><i class="fas fa-shield-alt"></i> ' . __('Notwendige','minecraft-modern-theme') . '</span><span class="mmc-always">' . __('Immer aktiv','minecraft-modern-theme') . '</span></div><p class="mmc-cat-desc">' . esc_html($desc_n) . '</p></div>
<div class="mmc-cat"><div class="mmc-cat-row"><label class="mmc-cat-name" for="mmc-stat"><i class="fas fa-chart-bar"></i> ' . __('Statistik','minecraft-modern-theme') . '</label><label class="mmc-toggle"><input type="checkbox" id="mmc-stat" checked><span class="mmc-knob"></span></label></div><p class="mmc-cat-desc">' . esc_html($desc_s) . '</p></div>
<div class="mmc-cat"><div class="mmc-cat-row"><label class="mmc-cat-name" for="mmc-mark"><i class="fas fa-bullhorn"></i> ' . __('Marketing','minecraft-modern-theme') . '</label><label class="mmc-toggle"><input type="checkbox" id="mmc-mark"><span class="mmc-knob"></span></label></div><p class="mmc-cat-desc">' . esc_html($desc_m) . '</p></div>
</div>';
$btn_accept = '<button id="mmc-accept" class="mmc-btn mmc-btn-accept"><i class="fas fa-check"></i> ' . __('Alle akzeptieren','minecraft-modern-theme') . '</button>';
$btn_select = '<button id="mmc-select" class="mmc-btn mmc-btn-select"><i class="fas fa-sliders-h"></i> ' . __('Auswahl speichern','minecraft-modern-theme') . '</button>';
$btn_neces = '<button id="mmc-neces" class="mmc-btn mmc-btn-neces">' . __('Nur notwendige','minecraft-modern-theme') . '</button>';
$text_esc = wp_kses_post($text);
?>
<div id="mm-cookie-banner"
class="mmc-style-<?php echo esc_attr($style); ?><?php echo $preview_class; ?>"
role="dialog" aria-modal="true"
style="<?php echo $inline_style; ?>">
<!-- Overlay (nur für stepper sichtbar, per CSS gesteuert) -->
<div class="mmc-overlay"></div>
<!-- ══ LAYOUT 1: Schmale Bar ══ -->
<div class="mmc-layout-bar">
<div class="mmc-bar-wrap">
<i class="fas fa-cookie-bite mmc-bar-icon"></i>
<p class="mmc-text"><?php echo $text_esc; ?> <?php echo $priv_link; ?></p>
<div class="mmc-bar-btns"><?php echo $btn_neces; ?><?php echo $btn_accept; ?></div>
</div>
</div>
<!-- ══ LAYOUT 2 (3A): Zweispaltig ══ -->
<div class="mmc-layout-split">
<div class="mmc-split-left">
<i class="fas fa-cookie-bite mmc-split-icon"></i>
<h3 class="mmc-split-title"><?php _e('Datenschutz-Einstellungen','minecraft-modern-theme'); ?></h3>
<p class="mmc-split-sub"><?php echo $text_esc; ?></p>
<?php if ($priv_link) echo '<p class="mmc-split-priv">' . $priv_link . '</p>'; ?>
</div>
<div class="mmc-split-right">
<?php echo $cats; ?>
<div class="mmc-split-btns"><?php echo $btn_neces; ?><?php echo $btn_select; ?><?php echo $btn_accept; ?></div>
</div>
</div>
<!-- ══ LAYOUT 3 (3B): Slide-In von rechts ══ -->
<div class="mmc-layout-slide">
<div class="mmc-slide-header">
<div class="mmc-slide-icon"><i class="fas fa-cookie-bite"></i></div>
<h3 class="mmc-slide-title"><?php _e('Datenschutz-Einstellungen','minecraft-modern-theme'); ?></h3>
</div>
<p class="mmc-slide-text"><?php echo $text_esc; ?> <?php echo $priv_link; ?></p>
<?php echo $cats; ?>
<div class="mmc-slide-btns"><?php echo $btn_accept; ?><?php echo $btn_select; ?><?php echo $btn_neces; ?></div>
</div>
<!-- ══ LAYOUT 4 (3C): Stepper ══ -->
<div class="mmc-layout-stepper">
<div class="mmc-steps">
<div class="mmc-step mmc-step-done"><span class="mmc-step-num">✓</span><?php _e('Info','minecraft-modern-theme'); ?></div>
<div class="mmc-step mmc-step-active"><span class="mmc-step-num">2</span><?php _e('Auswahl','minecraft-modern-theme'); ?></div>
<div class="mmc-step"><span class="mmc-step-num">3</span><?php _e('Fertig','minecraft-modern-theme'); ?></div>
</div>
<div class="mmc-stepper-body">
<p class="mmc-stepper-text"><?php echo $text_esc; ?> <?php echo $priv_link; ?></p>
<?php echo $cats; ?>
<div class="mmc-stepper-btns"><?php echo $btn_neces; ?><?php echo $btn_select; ?><?php echo $btn_accept; ?></div>
</div>
</div>
</div><!-- #mm-cookie-banner -->
<script>
(function(){
var IS_PREVIEW=<?php echo $is_preview?'true':'false';?>;
var COOKIE='mm_cookie_consent',DAYS=<?php echo intval($lifetime);?>,GA_ID=<?php echo json_encode($ga_id);?>,STYLE=<?php echo json_encode($style);?>;
var BLOCKED=['youtube.com','youtube-nocookie.com','youtu.be','vimeo.com','maps.google.com','google.com/maps','facebook.com','fb.com','twitter.com','platform.twitter.com','tiktok.com','instagram.com','open.spotify.com','twitch.tv'];
function getCookie(n){var m=document.cookie.match(new RegExp('(?:^|; )'+n.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+'=([^;]*)'));try{return m?JSON.parse(decodeURIComponent(m[1])):null;}catch(e){return null;}}
function setCookie(n,v,d){var e=new Date();e.setDate(e.getDate()+d);document.cookie=n+'='+encodeURIComponent(JSON.stringify(v))+'; expires='+e.toUTCString()+'; path=/; SameSite=Lax';}
var banner=document.getElementById('mm-cookie-banner');
if(!banner)return;
function getCurrentStyle(){return banner.className.match(/mmc-style-(\w+)/)?banner.className.match(/mmc-style-(\w+)/)[1]:STYLE;}
function show(){
banner.style.display='';
var s=getCurrentStyle();
if(s==='stepper')document.body.style.overflow='hidden';
setTimeout(function(){banner.classList.add('mmc-visible');},30);
}
function hide(){
if(IS_PREVIEW)return;
banner.classList.add('mmc-hiding');
setTimeout(function(){banner.style.display='none';banner.classList.remove('mmc-hiding','mmc-visible');document.body.style.overflow='';},400);
}
function loadGA(id){if(window._mmGaOk)return;window._mmGaOk=true;var s=document.createElement('script');s.async=true;s.src='https://www.googletagmanager.com/gtag/js?id='+id;document.head.appendChild(s);window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config',id,{anonymize_ip:true});}
function isBlocked(src){return src&&BLOCKED.some(function(d){return src.indexOf(d)!==-1;});}
function blockIframes(){if(IS_PREVIEW)return;document.querySelectorAll('iframe').forEach(function(f){var src=f.getAttribute('src')||f.getAttribute('data-src')||'';if(!isBlocked(src)||f.dataset.mmBlocked)return;f.dataset.mmOrigSrc=src;f.dataset.mmBlocked='1';f.setAttribute('src','about:blank');f.style.display='none';var ph=document.createElement('div');ph.className='mmc-placeholder';ph.innerHTML='<i class="fas fa-cookie-bite"></i><p><?php echo esc_js(__("Inhalt wegen Cookie-Einstellungen blockiert.","minecraft-modern-theme"));?></p><button class="mmc-ph-btn"><?php echo esc_js(__("Einstellungen ändern","minecraft-modern-theme"));?></button>';f.parentNode.insertBefore(ph,f);ph.querySelector('.mmc-ph-btn').addEventListener('click',show);});}
function unblockIframes(){document.querySelectorAll('iframe[data-mm-blocked]').forEach(function(f){var prev=f.previousElementSibling;if(prev&&prev.classList.contains('mmc-placeholder'))prev.remove();f.setAttribute('src',f.dataset.mmOrigSrc||'');f.style.display='';delete f.dataset.mmBlocked;});}
function applyConsent(c){document.dispatchEvent(new CustomEvent('mm_cookie_consent_set',{detail:c}));if(c.statistics&&GA_ID)loadGA(GA_ID);if(c.marketing){unblockIframes();}else{blockIframes();}}
function saveAndClose(stat,mark){var c={necessary:true,statistics:!!stat,marketing:!!mark};setCookie(COOKIE,c,DAYS);applyConsent(c);hide();}
document.addEventListener('DOMContentLoaded',function(){
var existing=getCookie(COOKIE);
// Checkboxen aus allen Layouts referenzieren (nur die ersten sind aktiv)
function getStatCb(){return document.querySelector('#mm-cookie-banner .mmc-layout-'+getCurrentStyle()+' #mmc-stat, #mmc-stat');}
function getMarkCb(){return document.querySelector('#mm-cookie-banner .mmc-layout-'+getCurrentStyle()+' #mmc-mark, #mmc-mark');}
if(!IS_PREVIEW&&existing){applyConsent(existing);var s=getStatCb(),m=getMarkCb();if(s)s.checked=!!existing.statistics;if(m)m.checked=!!existing.marketing;}
else if(!IS_PREVIEW){blockIframes();setTimeout(show,800);}
banner.addEventListener('click',function(e){
var t=e.target;
if(t.id==='mmc-accept'||t.closest('#mmc-accept')){var s=getStatCb(),m=getMarkCb();if(s)s.checked=true;if(m)m.checked=true;saveAndClose(true,true);}
else if(t.id==='mmc-select'||t.closest('#mmc-select')){var s=getStatCb(),m=getMarkCb();saveAndClose(s&&s.checked,m&&m.checked);}
else if(t.id==='mmc-neces'||t.closest('#mmc-neces')){saveAndClose(false,false);}
});
});
window.mmOpenCookieBanner=show;
})();
</script>
<?php
}
add_action( 'wp_footer', 'mm_cookie_banner_render' );
// --- 3. Hilfsfunktionen ---
function mm_cookie_consent(){if(!isset($_COOKIE['mm_cookie_consent']))return array();$v=json_decode(stripslashes($_COOKIE['mm_cookie_consent']),true);return is_array($v)?$v:array();}
function mm_cookie_accepted($category='necessary'){$c=mm_cookie_consent();return!empty($c[$category]);}
function mm_cookie_settings_shortcode($atts){$a=shortcode_atts(array('text'=>__('Cookie-Einstellungen','minecraft-modern-theme')),$atts);return '<a href="javascript:void(0)" onclick="if(window.mmOpenCookieBanner)window.mmOpenCookieBanner();" class="mmc-settings-link">'.esc_html($a['text']).'</a>';}
add_shortcode('cookie_settings','mm_cookie_settings_shortcode');
// --- 4. Live-Vorschau: nur Klasse + Text wechseln, HTML ist bereits vollständig ---
function mm_cookie_banner_preview_js(){
if(!is_customize_preview())return; ?>
<script>
(function(){
var ALL=['bar','split','slide','stepper'];
wp.customize.bind('preview-ready',function(){
// Variante wechseln: nur CSS-Klasse tauschen, HTML bleibt
wp.customize('mm_cookie_style',function(setting){setting.bind(function(v){
var b=document.getElementById('mm-cookie-banner');if(!b)return;
ALL.forEach(function(x){b.classList.remove('mmc-style-'+x);});
b.classList.add('mmc-style-'+v);
// Scroll-Lock für Stepper
document.body.style.overflow=(v==='stepper'?'hidden':'');
});});
// Text in allen 4 Layouts gleichzeitig aktualisieren
wp.customize('mm_cookie_text',function(setting){setting.bind(function(v){
['.mmc-text','.mmc-split-sub','.mmc-slide-text','.mmc-stepper-text'].forEach(function(sel){
document.querySelectorAll('#mm-cookie-banner '+sel).forEach(function(el){
var lnk=el.querySelector('a.mmc-priv-link');
el.textContent=v+' ';
if(lnk)el.appendChild(lnk);
});
});
});});
// Banner ein/aus
wp.customize('mm_cookie_enabled',function(setting){setting.bind(function(v){
var b=document.getElementById('mm-cookie-banner');if(!b)return;
if(v){b.style.display='';b.classList.remove('mmc-hiding');setTimeout(function(){b.classList.add('mmc-visible');},30);}
else{b.classList.add('mmc-hiding');setTimeout(function(){b.style.display='none';b.classList.remove('mmc-hiding','mmc-visible');},400);}
});});
});
})();
</script>
<?php
}
add_action('customize_preview_init',function(){add_action('wp_footer','mm_cookie_banner_preview_js',99);});
// =============================================================================
// === VIDEO-MODUL =============================================================
// =============================================================================
// --- 1. Custom Post Type: Video ---
function mm_register_video_post_type() {
register_post_type( 'mm_video', array(
'labels' => array(
'name' => __( 'Videos', 'minecraft-modern-theme' ),
'singular_name' => __( 'Video', 'minecraft-modern-theme' ),
'add_new' => __( 'Neues Video', 'minecraft-modern-theme' ),
'add_new_item' => __( 'Neues Video hinzufügen', 'minecraft-modern-theme' ),
'edit_item' => __( 'Video bearbeiten', 'minecraft-modern-theme' ),
'all_items' => __( 'Alle Videos', 'minecraft-modern-theme' ),
'menu_name' => __( 'Videos', 'minecraft-modern-theme' ),
),
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-video-alt3',
'menu_position' => 7,
'supports' => array( 'title', 'thumbnail', 'excerpt', 'page-attributes' ),
'rewrite' => array( 'slug' => 'videos' ),
'show_in_rest' => true,
) );
register_post_type( 'mm_livestream', array(
'labels' => array(
'name' => __( 'Livestreams', 'minecraft-modern-theme' ),
'singular_name' => __( 'Livestream', 'minecraft-modern-theme' ),
'add_new' => __( 'Neuer Livestream', 'minecraft-modern-theme' ),
'add_new_item' => __( 'Neuen Livestream hinzufügen', 'minecraft-modern-theme' ),
'edit_item' => __( 'Livestream bearbeiten', 'minecraft-modern-theme' ),
'all_items' => __( 'Alle Livestreams', 'minecraft-modern-theme' ),
'menu_name' => __( 'Livestreams', 'minecraft-modern-theme' ),
),
'public' => true,
'publicly_queryable' => false,
'exclude_from_search' => true,
'show_ui' => true,
'show_in_menu' => 'edit.php?post_type=mm_video',
'menu_position' => 8,
'supports' => array( 'title', 'excerpt', 'page-attributes' ),
'show_in_rest' => true,
) );
}
add_action( 'init', 'mm_register_video_post_type' );
// --- 2. Meta-Box: Video URL + Kategorie ---
function mm_video_meta_boxes() {
add_meta_box(
'mm_video_data',
__( 'Video-Einstellungen', 'minecraft-modern-theme' ),
'mm_video_meta_box_html',
'mm_video', 'normal', 'high'
);
add_meta_box(
'mm_livestream_data',
__( 'Livestream-Einstellungen', 'minecraft-modern-theme' ),
'mm_livestream_meta_box_html',
'mm_livestream', 'normal', 'high'
);
}
add_action( 'add_meta_boxes', 'mm_video_meta_boxes' );
function mm_video_meta_box_html( $post ) {
wp_nonce_field( 'mm_video_save', 'mm_video_nonce' );
$url = get_post_meta( $post->ID, '_mm_video_url', true );
$category = get_post_meta( $post->ID, '_mm_video_category', true );
$post_id = $post->ID;
?>
<table class="form-table" style="margin-top:0;">
<tr>
<th style="width:140px;padding-top:12px;"><label for="mm_video_url"><strong><?php _e('Video-URL', 'minecraft-modern-theme'); ?></strong></label></th>
<td style="padding-top:12px;">
<input type="url" id="mm_video_url" name="mm_video_url"
value="<?php echo esc_attr($url); ?>"
style="width:100%;max-width:600px;"
placeholder="https://www.youtube.com/watch?v=... oder https://vimeo.com/... oder https://www.twitch.tv/videos/... oder direkte .mp4 URL">
<p class="description" style="margin-top:6px;">
<?php _e('Unterstützt: YouTube, Vimeo, Twitch VOD, direkte MP4-Datei. Einfach die normale Seiten-URL einfügen.', 'minecraft-modern-theme'); ?>
</p>
</td>
</tr>
<tr>
<th><label for="mm_video_category"><strong><?php _e('Kategorie', 'minecraft-modern-theme'); ?></strong></label></th>
<td>
<input type="text" id="mm_video_category" name="mm_video_category"
value="<?php echo esc_attr($category); ?>"
style="width:300px;"
placeholder="z.B. Highlights, Tutorials, Events">
<p class="description"><?php _e('Zum Filtern auf der Galerie-Seite. Leer lassen wenn nicht benötigt.', 'minecraft-modern-theme'); ?></p>
</td>
</tr>
<tr>
<th style="padding-top:14px;"><strong><?php _e('Shortcode', 'minecraft-modern-theme'); ?></strong></th>
<td style="padding-top:14px;">
<?php if ( $url ) : ?>
<div id="mm-video-shortcode-wrap" style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
<?php else : ?>
<div id="mm-video-shortcode-wrap" style="display:none;align-items:center;gap:10px;flex-wrap:wrap;">
<?php endif; ?>
<code id="mm-video-shortcode-code"
style="background:#1e1e1e;color:#00d4ff;padding:8px 14px;border-radius:6px;font-size:13px;border:1px solid #333;user-select:all;cursor:text;"
><?php echo $url ? esc_html('[mm_video url="' . $url . '"]') : ''; ?></code>
<button type="button" id="mm-video-copy-btn"
style="padding:7px 14px;background:#0073aa;color:#fff;border:none;border-radius:5px;cursor:pointer;font-size:13px;font-weight:600;">
<span class="dashicons dashicons-clipboard" style="vertical-align:middle;font-size:16px;margin-right:4px;"></span>
<?php _e('Kopieren', 'minecraft-modern-theme'); ?>
</button>
<span id="mm-video-copy-ok" style="display:none;color:#46b450;font-weight:600;font-size:13px;">
✓ <?php _e('Kopiert!', 'minecraft-modern-theme'); ?>
</span>
</div>
<p class="description" style="margin-top:6px;">
<?php _e('Diesen Shortcode in beliebigen Beiträgen oder Seiten einfügen um das Video dort einzubetten.', 'minecraft-modern-theme'); ?>
</p>
<script>
(function() {
var urlInput = document.getElementById('mm_video_url');
var scWrap = document.getElementById('mm-video-shortcode-wrap');
var scCode = document.getElementById('mm-video-shortcode-code');
var copyBtn = document.getElementById('mm-video-copy-btn');
var copyOk = document.getElementById('mm-video-copy-ok');
// Live-Update beim Tippen
if (urlInput) {
urlInput.addEventListener('input', function() {
var val = this.value.trim();
if (val) {
scCode.textContent = '[mm_video url="' + val + '"]';
scWrap.style.display = 'flex';
} else {
scWrap.style.display = 'none';
}
});
}
// Kopieren-Button
if (copyBtn) {
copyBtn.addEventListener('click', function() {
var text = scCode.textContent;
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(function() {
showCopied();
});
} else {
// Fallback für ältere Browser
var ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
showCopied();
}
});
}
function showCopied() {
copyOk.style.display = 'inline';
setTimeout(function() { copyOk.style.display = 'none'; }, 2500);
}
})();
</script>
</td>
</tr>
</table>
<?php
}
function mm_livestream_meta_box_html( $post ) {
wp_nonce_field( 'mm_livestream_save', 'mm_livestream_nonce' );
$url = get_post_meta( $post->ID, '_mm_livestream_url', true );
$player_url = get_post_meta( $post->ID, '_mm_livestream_player_url', true );
$owner = get_post_meta( $post->ID, '_mm_livestream_owner', true );
?>
<table class="form-table" style="margin-top:0;">
<tr>
<th style="width:160px;padding-top:12px;"><label for="mm_livestream_url"><strong><?php _e('Profil-URL', 'minecraft-modern-theme'); ?></strong></label></th>
<td style="padding-top:12px;">
<input type="url" id="mm_livestream_url" name="mm_livestream_url"
value="<?php echo esc_attr( $url ); ?>"
style="width:100%;max-width:700px;"
placeholder="https://www.youtube.com/@handle oder https://www.twitch.tv/kanal">
<p class="description" style="margin-top:6px;">
<?php _e('Kanal-Link einfügen. Bei YouTube kannst du den @Handle oder die Channel-URL verwenden - die Kanal-ID wird automatisch ermittelt.', 'minecraft-modern-theme'); ?>
</p>
</td>
</tr>
<tr>
<th style="padding-top:12px;"><label for="mm_livestream_player_url"><strong><?php _e('Direkte Stream-URL', 'minecraft-modern-theme'); ?></strong></label></th>
<td style="padding-top:12px;">
<input type="url" id="mm_livestream_player_url" name="mm_livestream_player_url"
value="<?php echo esc_attr( $player_url ); ?>"
style="width:100%;max-width:700px;"
placeholder="https://www.youtube.com/watch?v=... oder https://www.twitch.tv/videos/...">
<p class="description" style="margin-top:6px;">
<?php _e('Optional. Wenn ein Kanal mehrere Streams parallel hat, kannst du hier einen konkreten Stream-Link eintragen. Ohne dieses Feld wird der Live-Player aus der Profil-URL erzeugt.', 'minecraft-modern-theme'); ?>
</p>
</td>
</tr>
<tr>
<th style="padding-top:12px;"><label for="mm_livestream_owner"><strong><?php _e('Streamer / Gruppe', 'minecraft-modern-theme'); ?></strong></label></th>
<td style="padding-top:12px;">
<input type="text" id="mm_livestream_owner" name="mm_livestream_owner"
value="<?php echo esc_attr( $owner ); ?>"
style="width:100%;max-width:420px;"
placeholder="z.B. Streamer-Name oder Gruppen-Name">
<p class="description" style="margin-top:6px;">
<?php _e('Mehrere Livestream-Einträge mit demselben Streamer/Gruppen-Namen werden oberhalb der Videos in einer gemeinsamen Box mit Umschalter zusammengefasst.', 'minecraft-modern-theme'); ?>
</p>
</td>
</tr>
<tr>
<th style="padding-top:12px;"><strong><?php _e('Optionaler Text', 'minecraft-modern-theme'); ?></strong></th>
<td style="padding-top:12px;">
<p class="description"><?php _e('Du kannst den Auszug des Eintrags optional als kurze Beschreibung für diesen Livestream verwenden.', 'minecraft-modern-theme'); ?></p>
</td>
</tr>
</table>
<?php
}
function mm_normalize_youtube_channel_id( $input ) {
$input = trim( (string) $input );
if ( $input === '' ) {
return '';
}
if ( preg_match( '/^UC[a-zA-Z0-9_-]+$/', $input ) ) {
return $input;
}
if ( preg_match( '~youtube\.com/channel/(UC[a-zA-Z0-9_-]+)~i', $input, $matches ) ) {
return $matches[1];
}
return '';
}
function mm_video_save_meta( $post_id ) {
if ( ! isset( $_POST['mm_video_nonce'] ) || ! wp_verify_nonce( $_POST['mm_video_nonce'], 'mm_video_save' ) ) return;
if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
if ( isset( $_POST['mm_video_url'] ) ) {
update_post_meta( $post_id, '_mm_video_url', esc_url_raw( $_POST['mm_video_url'] ) );
}
if ( isset( $_POST['mm_video_category'] ) ) {
update_post_meta( $post_id, '_mm_video_category', sanitize_text_field( $_POST['mm_video_category'] ) );
}
}
// BUG-FIX: 'save_post' feuert bei JEDEM Post-Type. Spezifischer Hook benutzen.
add_action( 'save_post_mm_video', 'mm_video_save_meta' );
function mm_livestream_save_meta( $post_id ) {
if ( ! isset( $_POST['mm_livestream_nonce'] ) || ! wp_verify_nonce( $_POST['mm_livestream_nonce'], 'mm_livestream_save' ) ) return;
if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
if ( isset( $_POST['mm_livestream_url'] ) ) {
update_post_meta( $post_id, '_mm_livestream_url', esc_url_raw( $_POST['mm_livestream_url'] ) );
}
if ( isset( $_POST['mm_livestream_player_url'] ) ) {
update_post_meta( $post_id, '_mm_livestream_player_url', esc_url_raw( $_POST['mm_livestream_player_url'] ) );
}
if ( isset( $_POST['mm_livestream_owner'] ) ) {
update_post_meta( $post_id, '_mm_livestream_owner', sanitize_text_field( $_POST['mm_livestream_owner'] ) );
}
}
add_action( 'save_post_mm_livestream', 'mm_livestream_save_meta' );
// --- 3. URL → Embed-URL umwandeln ---
function mm_video_get_embed_url( $url ) {
if ( empty($url) ) return false;
// YouTube: watch?v=ID, youtu.be/ID, shorts/ID
if ( preg_match( '/(?:youtube\.com\/(?:watch\?v=|shorts\/|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/', $url, $m ) ) {
return 'https://www.youtube-nocookie.com/embed/' . $m[1] . '?rel=0&modestbranding=1';
}
// Vimeo: vimeo.com/ID
if ( preg_match( '/vimeo\.com\/(?:video\/)?(\d+)/', $url, $m ) ) {
return 'https://player.vimeo.com/video/' . $m[1] . '?dnt=1';
}
// Twitch VOD: twitch.tv/videos/ID
if ( preg_match( '/twitch\.tv\/videos\/(\d+)/', $url, $m ) ) {
$parent = parse_url( home_url(), PHP_URL_HOST );
return 'https://player.twitch.tv/?video=v' . $m[1] . '&parent=' . $parent . '&autoplay=false';
}
// Twitch Kanal-Stream: twitch.tv/CHANNEL (mit oder ohne trailing slash)
if ( preg_match( '/twitch\.tv\/([a-zA-Z0-9_]+)\/?(?:\?.*)?$/', $url, $m ) ) {
$parent = parse_url( home_url(), PHP_URL_HOST );
return 'https://player.twitch.tv/?channel=' . $m[1] . '&parent=' . $parent . '&autoplay=false';
}
// Direkte MP4 / WebM / OGV
if ( preg_match( '/\.(mp4|webm|ogv|ogg)(\?.*)?$/i', $url ) ) {
return $url; // wird als <video> Tag ausgegeben, nicht iframe
}
return false;
}
function mm_video_get_type( $url ) {
if ( preg_match( '/youtube\.com|youtu\.be/', $url ) ) return 'youtube';
if ( strpos( $url, 'vimeo.com' ) !== false ) return 'vimeo';
if ( strpos( $url, 'twitch.tv' ) !== false ) return 'twitch';
if ( preg_match( '/\.(mp4|webm|ogv|ogg)(\?.*)?$/i', $url ) ) return 'mp4';
return 'unknown';
}
function mm_twitch_get_channel_from_url( $url ) {
if ( preg_match( '/twitch\.tv\/([a-zA-Z0-9_]+)(?:\/|\?|$)/', (string) $url, $m ) ) {
return strtolower( $m[1] );
}
return '';
}
function mm_twitch_get_app_token() {
$client_id = get_theme_mod( 'twitch_client_id', '' );
$client_secret = get_theme_mod( 'twitch_client_secret', '' );
if ( empty( $client_id ) || empty( $client_secret ) ) {
return false;
}
$cache_key = 'mm_twitch_app_token';
$cached = get_transient( $cache_key );
if ( ! empty( $cached ) ) {
return $cached;
}
$response = wp_remote_post( 'https://id.twitch.tv/oauth2/token', array(
'timeout' => 8,
'body' => array(
'client_id' => $client_id,
'client_secret' => $client_secret,
'grant_type' => 'client_credentials',
),
) );
if ( is_wp_error( $response ) ) {
return false;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $data['access_token'] ) || empty( $data['expires_in'] ) ) {
return false;
}
$ttl = max( 60, (int) $data['expires_in'] - 60 );
set_transient( $cache_key, $data['access_token'], $ttl );
return $data['access_token'];
}
function mm_twitch_is_live( $channel ) {
$channel = sanitize_text_field( (string) $channel );
if ( $channel === '' ) {
return false;
}
$cache_key = 'mm_twitch_live_' . $channel;
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached === 'live';
}
$client_id = get_theme_mod( 'twitch_client_id', '' );
$token = mm_twitch_get_app_token();
if ( empty( $client_id ) || empty( $token ) ) {
return false;
}
$url = add_query_arg( array(
'user_login' => $channel,
), 'https://api.twitch.tv/helix/streams' );
$response = wp_remote_get( $url, array(
'timeout' => 8,
'headers' => array(
'Client-ID' => $client_id,
'Authorization' => 'Bearer ' . $token,
),
) );
if ( is_wp_error( $response ) ) {
return false;
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$is_live = ! empty( $data['data'][0] );
set_transient( $cache_key, $is_live ? 'live' : 'offline', 2 * MINUTE_IN_SECONDS );
return $is_live;
}
function mm_video_get_youtube_player_url( $identifier, $mode = 'video' ) {
$params = 'autoplay=0&rel=0&modestbranding=1';
if ( $mode === 'channel' ) {
return 'https://www.youtube.com/embed/live_stream?channel=' . rawurlencode( $identifier ) . '&' . $params;
}
if ( $mode === 'handle-live' ) {
return 'https://www.youtube.com/embed/' . ltrim( $identifier ) . '/live?' . $params;
}
return 'https://www.youtube.com/embed/' . rawurlencode( $identifier ) . '?' . $params;
}
function mm_video_get_youtube_handle_from_url( $url ) {
if ( preg_match( '~youtube\.com/(@[A-Za-z0-9._-]+)(?:/.*)?$~i', (string) $url, $matches ) ) {
return sanitize_text_field( $matches[1] );
}
return '';
}
function mm_video_extract_youtube_live_video_id( $html ) {
$patterns = array(
'/"canonicalBaseUrl":"\\/watch\?v=([A-Za-z0-9_-]{11})"/i',
'/"watchEndpoint":\{"videoId":"([A-Za-z0-9_-]{11})"/i',
'/\/watch\?v=([A-Za-z0-9_-]{11})\\u0026/i',
'/"videoId":"([A-Za-z0-9_-]{11})".{0,600}?"isLiveNow":true/is',
'/"videoId":"([A-Za-z0-9_-]{11})".{0,600}?"style":"LIVE"/is',
'/"videoId":"([A-Za-z0-9_-]{11})".{0,600}?LIVE_NOW/is',
'/https:\/\/www\.youtube\.com\/watch\?v=([A-Za-z0-9_-]{11})/i',
'/<link rel="canonical" href="https:\/\/www\.youtube\.com\/watch\?v=([A-Za-z0-9_-]{11})"/i',
);
foreach ( $patterns as $pattern ) {
if ( preg_match( $pattern, $html, $matches ) ) {
return $matches[1];
}
}
return '';
}
/**
* Prüft ob ein YouTube Video/Stream tatsächlich LIVE ist (optional mit API)
* @param string $video_id YouTube Video ID
* @return bool|null true wenn live, false wenn offline, null wenn unbekannt
*/
function mm_video_check_youtube_live_status( $video_id ) {
if ( empty( $video_id ) ) {
return null;
}
// Cache-Check
$cache_key = 'mm_yt_status_' . $video_id;
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
return $cached === 'live';
}
// YouTube API Key: Erst Customizer, dann wp-config.php Fallback
$api_key = get_theme_mod( 'youtube_api_key', '' );
if ( empty( $api_key ) && defined( 'YOUTUBE_API_KEY' ) ) {
$api_key = YOUTUBE_API_KEY;
}
if ( ! empty( $api_key ) ) {
// Mit API prüfen
$url = sprintf(
'https://www.googleapis.com/youtube/v3/videos?id=%s&part=snippet,liveStreamingDetails&key=%s',
rawurlencode( $video_id ),
rawurlencode( $api_key )
);
$response = wp_remote_get( $url, array( 'timeout' => 5 ) );
if ( ! is_wp_error( $response ) ) {
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! empty( $data['items'][0] ) ) {
$state = $data['items'][0]['snippet']['liveBroadcastContent'] ?? '';
$is_live = ( $state === 'live' );
set_transient( $cache_key, $is_live ? 'live' : 'offline', 2 * MINUTE_IN_SECONDS );
return $is_live;
}
}
}
// Fallback: Wenn keine API, gehen wir davon aus dass die video_id aktuell ist
// weil sie von mm_video_resolve_youtube_live_video_id() kam
set_transient( $cache_key, 'live', 2 * MINUTE_IN_SECONDS );
return true;
}
function mm_video_resolve_youtube_live_video_id( $profile_url = '', $youtube_channel_id = '' ) {
$profile_url = trim( (string) $profile_url );
$youtube_channel_id = trim( (string) $youtube_channel_id );
$cache_key = 'mm_yt_live_v2_' . md5( strtolower( $profile_url . '|' . $youtube_channel_id ) );
$cached = get_transient( $cache_key );
if ( is_array( $cached ) && array_key_exists( 'video_id', $cached ) ) {
return $cached['video_id'];
}
$candidate_urls = array();
$handle = mm_video_get_youtube_handle_from_url( $profile_url );
if ( $youtube_channel_id && preg_match( '/^UC[a-zA-Z0-9_-]+$/', $youtube_channel_id ) ) {
$candidate_urls[] = 'https://www.youtube.com/channel/' . rawurlencode( $youtube_channel_id ) . '/live';
$candidate_urls[] = 'https://www.youtube.com/channel/' . rawurlencode( $youtube_channel_id ) . '/streams';
}
if ( $handle ) {
$candidate_urls[] = 'https://www.youtube.com/' . rawurlencode( ltrim( $handle, '/' ) ) . '/live';
$candidate_urls[] = 'https://www.youtube.com/' . rawurlencode( ltrim( $handle, '/' ) ) . '/streams';
}
if ( $profile_url ) {
$candidate_urls[] = untrailingslashit( $profile_url ) . '/live';
$candidate_urls[] = untrailingslashit( $profile_url ) . '/streams';
}
$candidate_urls = array_values( array_unique( array_filter( $candidate_urls ) ) );
$request_args = 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',
),
);
foreach ( $candidate_urls as $candidate_url ) {
$response = wp_remote_get( $candidate_url, $request_args );
if ( is_wp_error( $response ) ) {
continue;
}
$body = wp_remote_retrieve_body( $response );
if ( ! is_string( $body ) || $body === '' ) {
continue;
}
$video_id = mm_video_extract_youtube_live_video_id( $body );
if ( $video_id ) {
set_transient( $cache_key, array( 'video_id' => $video_id ), 2 * MINUTE_IN_SECONDS );
return $video_id;
}
}
set_transient( $cache_key, array( 'video_id' => '' ), 60 );
return '';
}
function mm_video_get_livestream_data( $profile_url = '', $player_url = '', $youtube_channel_id = '' ) {
if ( empty( $profile_url ) && empty( $player_url ) && empty( $youtube_channel_id ) ) {
return false;
}
$profile_url = trim( $profile_url );
$player_url = trim( $player_url );
$youtube_channel_id = trim( $youtube_channel_id );
$source_url = $player_url ? $player_url : $profile_url;
$parent_host = parse_url( home_url(), PHP_URL_HOST );
$data = array(
'profile_url' => esc_url_raw( $profile_url ? $profile_url : $player_url ),
'platform' => 'unknown',
'label' => __( 'Livestream', 'minecraft-modern-theme' ),
'icon' => 'fas fa-tower-broadcast',
'color' => '#00d4ff',
'cta' => __( 'Profil öffnen', 'minecraft-modern-theme' ),
'embed_url' => '',
'video_id' => '',
'channel' => '',
'channel_display' => '',
);
if ( ! empty( $youtube_channel_id ) && preg_match( '/^UC[a-zA-Z0-9_-]+$/', $youtube_channel_id ) ) {
$resolved_video_id = mm_video_resolve_youtube_live_video_id( $profile_url, $youtube_channel_id );
$data['platform'] = 'youtube';
$data['label'] = __( 'YouTube Livestream', 'minecraft-modern-theme' );
$data['icon'] = 'fab fa-youtube';
$data['color'] = '#ff0000';
$data['cta'] = __( 'Zum YouTube-Kanal', 'minecraft-modern-theme' );
$data['channel'] = $youtube_channel_id;
$data['channel_display'] = $youtube_channel_id;
$data['video_id'] = $resolved_video_id;
$data['embed_url'] = $resolved_video_id ? mm_video_get_youtube_player_url( $resolved_video_id, 'video' ) : '';
if ( preg_match( '~youtube\.com/(@[A-Za-z0-9._-]+)(?:/.*)?$~i', $profile_url, $handle_matches ) ) {
$data['channel_display'] = sanitize_text_field( $handle_matches[1] );
}
if ( empty( $data['profile_url'] ) ) {
$data['profile_url'] = 'https://www.youtube.com/channel/' . rawurlencode( $youtube_channel_id );
}
if ( empty( $player_url ) ) {
return $data;
}
}
if ( empty( $source_url ) ) {
return false;
}
if ( preg_match( '/youtube\.com|youtu\.be/', $source_url ) && preg_match( '/(?:youtube\.com\/(?:watch\?v=|shorts\/|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/', $source_url ) ) {
$data['platform'] = 'youtube';
$data['label'] = __( 'YouTube Livestream', 'minecraft-modern-theme' );
$data['icon'] = 'fab fa-youtube';
$data['color'] = '#ff0000';
$data['cta'] = __( 'Zum YouTube-Kanal', 'minecraft-modern-theme' );
preg_match( '/(?:youtube\.com\/(?:watch\?v=|shorts\/|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/', $source_url, $video_match );
$data['video_id'] = ! empty( $video_match[1] ) ? $video_match[1] : '';
$data['embed_url'] = ! empty( $video_match[1] ) ? mm_video_get_youtube_player_url( $video_match[1], 'video' ) : '';
return $data;
}
if ( preg_match( '~twitch\.tv/([A-Za-z0-9_]+)(?:/)?(?:\?.*)?$~i', $source_url, $matches ) && strtolower( $matches[1] ) !== 'videos' ) {
$channel = sanitize_key( $matches[1] );
$data['platform'] = 'twitch';
$data['label'] = __( 'Twitch Livestream', 'minecraft-modern-theme' );
$data['icon'] = 'fab fa-twitch';
$data['color'] = '#9146ff';
$data['cta'] = __( 'Zum Twitch-Kanal', 'minecraft-modern-theme' );
$data['channel'] = $channel;
$data['channel_display'] = '@' . $channel;
$data['embed_url'] = 'https://player.twitch.tv/?channel=' . rawurlencode( $channel ) . '&parent=' . rawurlencode( $parent_host ) . '&autoplay=false';
return $data;
}
if ( preg_match( '~kick\.com/([A-Za-z0-9_]+)(?:/)?(?:\?.*)?$~i', $source_url, $matches ) ) {
$channel = sanitize_key( $matches[1] );
$data['platform'] = 'kick';
$data['label'] = __( 'Kick Livestream', 'minecraft-modern-theme' );
$data['icon'] = 'fas fa-satellite-dish';
$data['color'] = '#53fc18';
$data['cta'] = __( 'Zum Kick-Kanal', 'minecraft-modern-theme' );
$data['channel'] = $channel;
$data['channel_display'] = '@' . $channel;
$data['embed_url'] = 'https://player.kick.com/' . rawurlencode( $channel );
return $data;
}
if ( preg_match( '~youtube\.com/channel/(UC[a-zA-Z0-9_-]+)~i', $source_url, $matches ) ) {
$channel_id = sanitize_text_field( $matches[1] );
$resolved_video_id = mm_video_resolve_youtube_live_video_id( $source_url, $channel_id );
$data['platform'] = 'youtube';
$data['label'] = __( 'YouTube Livestream', 'minecraft-modern-theme' );
$data['icon'] = 'fab fa-youtube';
$data['color'] = '#ff0000';
$data['cta'] = __( 'Zum YouTube-Kanal', 'minecraft-modern-theme' );
$data['channel'] = $channel_id;
$data['channel_display'] = $channel_id;
$data['video_id'] = $resolved_video_id;
$data['embed_url'] = $resolved_video_id ? mm_video_get_youtube_player_url( $resolved_video_id, 'video' ) : '';
return $data;
}
if ( preg_match( '~youtube\.com/(@[A-Za-z0-9._-]+)(?:/.*)?$~i', $source_url, $matches ) ) {
$handle = sanitize_text_field( $matches[1] );
$resolved_video_id = mm_video_resolve_youtube_live_video_id( $source_url, '' );
$data['platform'] = 'youtube';
$data['label'] = __( 'YouTube Livestream', 'minecraft-modern-theme' );
$data['icon'] = 'fab fa-youtube';
$data['color'] = '#ff0000';
$data['cta'] = __( 'Zum YouTube-Kanal', 'minecraft-modern-theme' );
$data['channel'] = ltrim( $handle, '@' );
$data['channel_display'] = $handle;
$data['video_id'] = $resolved_video_id;
$data['embed_url'] = $resolved_video_id ? mm_video_get_youtube_player_url( $resolved_video_id, 'video' ) : '';
return $data;
}
if ( preg_match( '~youtube\.com/(?:c|user)/([A-Za-z0-9._-]+)(?:/.*)?$~i', $source_url, $matches ) ) {
$channel_name = sanitize_text_field( $matches[1] );
$resolved_video_id = mm_video_resolve_youtube_live_video_id( $source_url, '' );
$data['platform'] = 'youtube';
$data['label'] = __( 'YouTube Livestream', 'minecraft-modern-theme' );
$data['icon'] = 'fab fa-youtube';
$data['color'] = '#ff0000';
$data['cta'] = __( 'Zum YouTube-Kanal', 'minecraft-modern-theme' );
$data['channel'] = $channel_name;
$data['channel_display'] = '@' . ltrim( $channel_name, '@' );
$data['video_id'] = $resolved_video_id;
$data['embed_url'] = $resolved_video_id ? mm_video_get_youtube_player_url( $resolved_video_id, 'video' ) : '';
return $data;
}
if ( strpos( $source_url, 'youtube.com' ) !== false || strpos( $source_url, 'youtu.be' ) !== false ) {
$data['platform'] = 'youtube';
$data['label'] = __( 'YouTube Livestream', 'minecraft-modern-theme' );
$data['icon'] = 'fab fa-youtube';
$data['color'] = '#ff0000';
$data['cta'] = __( 'Zum YouTube-Kanal', 'minecraft-modern-theme' );
}
return $data;
}
function mm_video_get_livestream_item( $post_id ) {
$stream_url = get_post_meta( $post_id, '_mm_livestream_url', true );
$player_url = get_post_meta( $post_id, '_mm_livestream_player_url', true );
$owner_meta = trim( get_post_meta( $post_id, '_mm_livestream_owner', true ) );
$youtube_channel_id = get_post_meta( $post_id, '_mm_livestream_youtube_channel_id', true );
$stream = mm_video_get_livestream_data( $stream_url, $player_url, $youtube_channel_id );
if ( ! $stream || empty( $stream['profile_url'] ) ) {
return false;
}
$stream_title = trim( get_the_title( $post_id ) );
if ( empty( $stream_title ) || $stream_title === '(kein Titel)' || stripos( $stream_title, 'Automatisch gespeicherter Entwurf' ) !== false || stripos( $stream_title, 'Auto Draft' ) !== false ) {
$stream_title = ! empty( $stream['channel_display'] ) ? $stream['channel_display'] : $stream['label'];
}
$stream_excerpt = has_excerpt( $post_id ) ? get_the_excerpt( $post_id ) : '';
$owner = $owner_meta;
if ( empty( $owner ) ) {
$owner = ! empty( $stream['channel_display'] ) ? $stream['channel_display'] : $stream_title;
}
return array(
'post_id' => $post_id,
'owner' => $owner,
'title' => $stream_title,
'description' => $stream_excerpt,
'profile_url' => $stream['profile_url'],
'channel' => $stream['channel'],
'stream' => $stream,
);
}
/**
* Automatische YouTube-Live-Erkennung via @Handle (Optimiert)
*/
/**
* 1. Hilfsfunktion: Wandelt @Handle in eine Channel-ID um
*/
function mm_get_channel_id_by_handle( $handle ) {
$api_key = get_theme_mod( 'youtube_api_key' );
if ( empty( $api_key ) || empty( $handle ) ) {
return false;
}
// Handle normalisieren (ohne @)
$handle = ltrim( $handle, '@' );
// Cache für die Channel-ID (diese ändert sich nie, daher 30 Tage speichern)
$cache_key = 'mm_id_for_' . $handle;
$channel_id = get_transient( $cache_key );
if ( false !== $channel_id ) {
return $channel_id;
}
// Suche Kanal zum Handle
$url = 'https://www.googleapis.com/youtube/v3/search?part=snippet&q=' . urlencode( '@' . $handle ) . '&type=channel&maxResults=1&key=' . $api_key;
$response = wp_remote_get( $url );
if ( is_wp_error( $response ) ) {
return false;
}
$data = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! empty( $data->items[0]->snippet->channelId ) ) {
$id = $data->items[0]->snippet->channelId;
set_transient( $cache_key, $id, DAY_IN_SECONDS * 30 );
return $id;
}
return false;
}
/**
* 2. Hauptfunktion: Findet die Video-ID des aktuellen Livestreams
*/
function mm_get_youtube_live_id_from_handle( $handle ) {
$api_key = get_theme_mod( 'youtube_api_key' );
if ( empty( $api_key ) ) {
return false;
}
// 1. Kanal-ID zum Handle finden
$channel_id = mm_get_channel_id_by_handle( $handle );
if ( ! $channel_id ) {
return false;
}
// 2. Aktuellen Livestream in diesem Kanal suchen
$cache_key_live = 'mm_live_status_' . $channel_id;
$live_id = get_transient( $cache_key_live );
if ( false !== $live_id ) {
return ( $live_id === 'none' ) ? false : $live_id;
}
// Wir suchen direkt nach dem Live-Event des Kanals
$url = 'https://www.googleapis.com/youtube/v3/search?part=id&channelId=' . $channel_id . '&eventType=live&type=video&key=' . $api_key;
$response = wp_remote_get( $url );
if ( is_wp_error( $response ) ) {
return false;
}
$data = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! empty( $data->items[0]->id->videoId ) ) {
$video_id = $data->items[0]->id->videoId;
set_transient( $cache_key_live, $video_id, 2 * MINUTE_IN_SECONDS ); // Nur 2 Min für schnellere Reaktion
return $video_id;
}
// Wenn nicht live, 'none' speichern, um API-Anfragen zu drosseln
set_transient( $cache_key_live, 'none', 2 * MINUTE_IN_SECONDS );
return false;
}
/**
* 3. Update für mm_video_get_livestream_groups - Hybrid-System
* Zeigt ALLE konfigurierten Livestreams an:
* - Livestream-Posts (Hauptmethode für mehrere Kanäle)
* - Optional: Customizer @Handle (für einzelnen Hauptkanal)
*/
function mm_video_get_livestream_groups() {
$groups = array();
$api_key = get_theme_mod( 'youtube_api_key', '' );
// === METHODE 1: Livestream-Posts (IMMER abfragen) ===
$posts = get_posts( array(
'post_type' => 'mm_livestream',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'ID',
'order' => 'ASC',
) );
if ( ! empty( $posts ) ) {
foreach ( $posts as $post ) {
$profile_url = get_post_meta( $post->ID, '_mm_livestream_url', true );
$stream_url = get_post_meta( $post->ID, '_mm_livestream_player_url', true );
$owner = get_post_meta( $post->ID, '_mm_livestream_owner', true );
$channel_id = '';
// Versuche Channel-ID aus Profil-URL zu extrahieren (nur für YouTube)
if ( ! empty( $profile_url ) && strpos( $profile_url, 'youtube.com' ) !== false ) {
// Channel-URL mit UC-ID
if ( preg_match( '~/channel/(UC[a-zA-Z0-9_-]+)~', $profile_url, $m ) ) {
$channel_id = $m[1];
}
// @Handle -> über API auflösen
elseif ( preg_match( '~/@([a-zA-Z0-9_.-]+)(?:/|$)~', $profile_url, $m ) && ! empty( $api_key ) ) {
$channel_id = mm_get_channel_id_by_handle( $m[1] );
}
}
// YouTube-Livestream: Prüfe ob live via API
if ( ! empty( $channel_id ) && ! empty( $api_key ) ) {
$cache_key_live = 'mm_live_status_' . $channel_id;
$live_id = get_transient( $cache_key_live );
if ( false === $live_id ) {
$search_url = add_query_arg( array(
'part' => 'id',
'channelId' => $channel_id,
'eventType' => 'live',
'type' => 'video',
'key' => $api_key,
), 'https://www.googleapis.com/youtube/v3/search' );
$response = wp_remote_get( $search_url, array( 'timeout' => 10 ) );
if ( ! is_wp_error( $response ) ) {
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( isset( $data['items'][0]['id']['videoId'] ) ) {
$live_id = $data['items'][0]['id']['videoId'];
set_transient( $cache_key_live, $live_id, 2 * MINUTE_IN_SECONDS );
} else {
set_transient( $cache_key_live, 'none', 2 * MINUTE_IN_SECONDS );
$live_id = false;
}
} else {
$live_id = false;
}
} elseif ( $live_id === 'none' ) {
$live_id = false;
}
// Nur hinzufügen wenn live
if ( $live_id && $live_id !== 'none' ) {
$groups[] = array(
'title' => $post->post_title,
'platform' => 'youtube',
'yt_id' => $live_id,
'handle' => $profile_url,
);
}
}
// Andere Plattformen (Twitch, etc.): Prüfe zuerst direkte Stream-URL, sonst Profil-URL
elseif ( ! empty( $stream_url ) || ! empty( $profile_url ) ) {
$use_url = ! empty( $stream_url ) ? $stream_url : $profile_url;
$embed_url = mm_video_get_embed_url( $use_url );
// Nur Live-Streams anzeigen (Twitch muss live sein)
if ( $embed_url ) {
$platform = mm_video_get_type( $use_url );
if ( $platform !== 'twitch' ) {
continue;
}
$channel = mm_twitch_get_channel_from_url( $use_url );
if ( ! mm_twitch_is_live( $channel ) ) {
continue;
}
$groups[] = array(
'title' => $post->post_title,
'platform' => $platform,
'embed_url' => $embed_url,
'profile_url' => $profile_url,
'owner' => $owner,
);
}
}
}
}
return $groups;
}
/**
* Debug-Informationen für Livestream-System
*/
function mm_video_get_api_livestream_debug() {
if ( ! is_user_logged_in() || ! current_user_can( 'edit_posts' ) ) {
return array();
}
$api_key = get_theme_mod( 'youtube_api_key', '' );
// Zähle Livestream-Posts
$posts_count = wp_count_posts( 'mm_livestream' );
$published_posts = isset( $posts_count->publish ) ? $posts_count->publish : 0;
// === Fall 1: Keine Konfiguration ===
if ( $published_posts === 0 ) {
return array(
'status' => 'error',
'message' => 'Keine Livestream-Posts gefunden',
'hint' => 'Erstelle Livestream-Posts über "Livestreams → Neu"',
);
}
// === Fall 2: API Key fehlt (nur Warnung) ===
if ( empty( $api_key ) ) {
return array(
'status' => 'warning',
'message' => 'Kein YouTube API Key',
'hint' => 'API Key wird für YouTube Live-Erkennung benötigt. Twitch-Streams funktionieren ohne.',
'posts_count' => $published_posts,
);
}
// === Fall 3: Alles OK ===
return array(
'status' => 'ok',
'posts_count' => $published_posts,
'api_key_length' => strlen( $api_key ),
);
}
function mm_video_get_hidden_livestream_count() {
// Für API-basiertes System nicht relevant
return 0;
}
function mm_video_get_livestream_debug_rows() {
// Ersetzt durch mm_video_get_api_livestream_debug()
return array();
}
function mm_video_render_livestream_group( $group, $index = 0 ) {
// Format 1: Neues API-YouTube-Format mit yt_id (Customizer oder Posts)
if ( isset( $group['yt_id'] ) && isset( $group['platform'] ) && $group['platform'] === 'youtube' ) {
$video_id = trim( (string) $group['yt_id'] );
$title = isset( $group['title'] ) ? $group['title'] : __( 'Live', 'minecraft-modern-theme' );
$handle = isset( $group['handle'] ) ? trim( (string) $group['handle'] ) : '';
if ( empty( $video_id ) ) {
return '';
}
$embed_url = 'https://www.youtube-nocookie.com/embed/' . rawurlencode( $video_id ) . '?autoplay=0&rel=0&modestbranding=1&playsinline=1';
ob_start();
?>
<div class="video-livestream-api-simple">
<div class="responsive-video">
<iframe
src="<?php echo esc_url( $embed_url ); ?>"
allow="autoplay; encrypted-media; picture-in-picture"
allowfullscreen
loading="lazy"
title="<?php echo esc_attr( $title ); ?>"></iframe>
</div>
<div class="video-livestream-meta">
<?php if ( ! empty( $handle ) ) : ?>
<a href="https://www.youtube.com/<?php echo esc_attr( $handle ); ?>" target="_blank" rel="noopener noreferrer" class="video-livestream-channel-link">
<i class="fab fa-youtube"></i>
<?php echo esc_html( $title ); ?>
</a>
<?php else : ?>
<span class="video-livestream-channel-link">
<i class="fab fa-youtube"></i>
<?php echo esc_html( $title ); ?>
</span>
<?php endif; ?>
</div>
</div>
<?php
return ob_get_clean();
}
// Format 2: Einfaches Embed-Format (andere Plattformen via Posts)
if ( isset( $group['embed_url'] ) && ! empty( $group['embed_url'] ) ) {
$title = isset( $group['title'] ) ? $group['title'] : __( 'Live', 'minecraft-modern-theme' );
$embed_url = $group['embed_url'];
$profile_url = isset( $group['profile_url'] ) ? $group['profile_url'] : '';
$platform = isset( $group['platform'] ) ? $group['platform'] : 'unknown';
if ( $platform === 'youtube' && isset( $group['video_id'] ) && ! empty( $group['video_id'] ) ) {
$embed_url = 'https://www.youtube-nocookie.com/embed/' . rawurlencode( (string) $group['video_id'] ) . '?autoplay=0&rel=0&modestbranding=1&playsinline=1';
}
$platform_icons = array(
'twitch' => array( 'icon' => 'fab fa-twitch', 'color' => '#9146ff' ),
'vimeo' => array( 'icon' => 'fab fa-vimeo-v', 'color' => '#1ab7ea' ),
'youtube' => array( 'icon' => 'fab fa-youtube', 'color' => '#ff0000' ),
);
$icon = isset( $platform_icons[$platform] ) ? $platform_icons[$platform]['icon'] : 'fas fa-play';
ob_start();
?>
<div class="video-livestream-api-simple">
<div class="responsive-video">
<iframe
src="<?php echo esc_url( $embed_url ); ?>"
allow="autoplay; encrypted-media; picture-in-picture"
allowfullscreen
loading="lazy"
title="<?php echo esc_attr( $title ); ?>"></iframe>
</div>
<div class="video-livestream-meta">
<?php if ( ! empty( $profile_url ) ) : ?>
<a href="<?php echo esc_url( $profile_url ); ?>" target="_blank" rel="noopener noreferrer" class="video-livestream-channel-link">
<i class="<?php echo esc_attr( $icon ); ?>"></i>
<?php echo esc_html( $title ); ?>
</a>
<?php else : ?>
<span class="video-livestream-channel-link">
<i class="<?php echo esc_attr( $icon ); ?>"></i>
<?php echo esc_html( $title ); ?>
</span>
<?php endif; ?>
</div>
</div>
<?php
return ob_get_clean();
}
// Format 3: Altes Post-basiertes Format mit Items (komplex) - für Rückwärtskompatibilität
$items = isset( $group['items'] ) ? $group['items'] : array();
if ( empty( $items ) ) {
return '';
}
$active_item = $items[0];
$group_id = 'video-livestream-' . absint( $index );
$description = ! empty( $group['description'] )
? $group['description']
: ( count( $items ) > 1
? __( 'Mehrere aktive oder gespeicherte Streams dieses Streamers lassen sich hier direkt umschalten.', 'minecraft-modern-theme' )
: __( 'Der Bereich wird automatisch aus deinem Profil-Link erzeugt und oberhalb der Videos eingebunden.', 'minecraft-modern-theme' ) );
ob_start();
?>
<section class="video-livestream" aria-label="<?php esc_attr_e( 'Livestream', 'minecraft-modern-theme' ); ?>" id="<?php echo esc_attr( $group_id ); ?>">
<div class="video-livestream-copy">
<span class="video-livestream-kicker" style="--stream-accent: <?php echo esc_attr( $active_item['stream']['color'] ); ?>;">
<i class="<?php echo esc_attr( $active_item['stream']['icon'] ); ?>"></i>
<?php esc_html_e( 'Livestream', 'minecraft-modern-theme' ); ?>
</span>
<h2 class="video-livestream-title"><?php echo esc_html( $group['owner'] ); ?></h2>
<p class="video-livestream-text"><?php echo esc_html( $description ); ?></p>
<?php if ( count( $items ) > 1 ) : ?>
<div class="video-livestream-nav">
<button type="button" class="video-livestream-nav-btn" data-stream-prev aria-label="<?php esc_attr_e( 'Vorheriger Stream', 'minecraft-modern-theme' ); ?>">
<i class="fas fa-chevron-left"></i>
<?php esc_html_e( 'Zurueck', 'minecraft-modern-theme' ); ?>
</button>
<span class="video-livestream-nav-count" data-stream-count>1 / <?php echo esc_html( count( $items ) ); ?></span>
<button type="button" class="video-livestream-nav-btn" data-stream-next aria-label="<?php esc_attr_e( 'Naechster Stream', 'minecraft-modern-theme' ); ?>">
<?php esc_html_e( 'Weiter', 'minecraft-modern-theme' ); ?>
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="video-livestream-switcher" role="tablist" aria-label="<?php esc_attr_e( 'Stream-Auswahl', 'minecraft-modern-theme' ); ?>">
<?php foreach ( $items as $item_index => $item ) : ?>
<button
type="button"
class="video-livestream-switch<?php echo $item_index === 0 ? ' is-active' : ''; ?>"
data-stream-switch
data-embed-url="<?php echo esc_url( $item['stream']['embed_url'] ); ?>"
data-profile-url="<?php echo esc_url( $item['profile_url'] ); ?>"
data-cta="<?php echo esc_attr( $item['stream']['cta'] ); ?>"
data-icon="<?php echo esc_attr( $item['stream']['icon'] ); ?>"
data-platform="<?php echo esc_attr( $item['stream']['platform'] ); ?>"
data-youtube-video-id="<?php echo esc_attr( $item['stream']['video_id'] ); ?>"
data-title="<?php echo esc_attr( $item['title'] ); ?>"
data-note="<?php echo esc_attr( ( empty( $item['stream']['embed_url'] ) && $item['stream']['platform'] === 'youtube' ) ? __( 'Aktuell konnte kein laufender YouTube-Livestream fuer diesen Kanal erkannt werden. Der Kanal-Link bleibt aber verfuegbar.', 'minecraft-modern-theme' ) : '' ); ?>"
aria-selected="<?php echo $item_index === 0 ? 'true' : 'false'; ?>">
<?php echo esc_html( $item['title'] ); ?>
</button>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="video-livestream-actions">
<a class="video-livestream-button" data-livestream-cta href="<?php echo esc_url( $active_item['profile_url'] ); ?>" target="_blank" rel="noopener noreferrer">
<i class="<?php echo esc_attr( $active_item['stream']['icon'] ); ?>"></i>
<span><?php echo esc_html( $active_item['stream']['cta'] ); ?></span>
</a>
<?php if ( ! empty( $active_item['stream']['channel_display'] ) ) : ?>
<span class="video-livestream-channel" data-livestream-channel><?php echo esc_html( $active_item['stream']['channel_display'] ); ?></span>
<?php endif; ?>
</div>
<p class="video-livestream-note<?php echo ( empty( $active_item['stream']['embed_url'] ) && $active_item['stream']['platform'] === 'youtube' ) ? '' : ' is-hidden'; ?>" data-livestream-note><?php esc_html_e( 'Aktuell konnte kein laufender YouTube-Livestream fuer diesen Kanal erkannt werden. Der Kanal-Link bleibt aber verfuegbar.', 'minecraft-modern-theme' ); ?></p>
</div>
<div class="video-livestream-player-wrap">
<?php if ( ! empty( $active_item['stream']['embed_url'] ) ) : ?>
<div class="video-livestream-player" data-livestream-player>
<iframe
data-livestream-iframe
src="<?php echo esc_url( $active_item['stream']['platform'] === 'youtube' && ! empty( $active_item['stream']['video_id'] ) ? 'https://www.youtube-nocookie.com/embed/' . rawurlencode( (string) $active_item['stream']['video_id'] ) . '?autoplay=0&rel=0&modestbranding=1&playsinline=1' : $active_item['stream']['embed_url'] ); ?>"
title="<?php echo esc_attr( $active_item['title'] ); ?>"
frameborder="0"
allowfullscreen
loading="lazy"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"></iframe>
</div>
<div class="video-livestream-placeholder is-hidden" data-livestream-placeholder>
<i class="<?php echo esc_attr( $active_item['stream']['icon'] ); ?>"></i>
<p><?php esc_html_e( 'Für diesen Profil-Link ist aktuell kein direkter Player möglich. Der Kanal-Link bleibt aber verfügbar.', 'minecraft-modern-theme' ); ?></p>
</div>
<?php else : ?>
<div class="video-livestream-player is-hidden" data-livestream-player>
<iframe data-livestream-iframe src="about:blank" title="" frameborder="0" allowfullscreen loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"></iframe>
</div>
<div class="video-livestream-placeholder" data-livestream-placeholder>
<i class="<?php echo esc_attr( $active_item['stream']['icon'] ); ?>"></i>
<p><?php esc_html_e( 'Für diesen Profil-Link ist aktuell kein direkter Player möglich. Der Kanal-Link bleibt aber verfügbar.', 'minecraft-modern-theme' ); ?></p>
</div>
<?php endif; ?>
</div>
</section>
<?php
return ob_get_clean();
}
// --- 4. Embed HTML rendern ---
function mm_video_render_embed( $url, $args = array() ) {
$embed_url = mm_video_get_embed_url( $url );
if ( ! $embed_url ) return '<p class="mm-video-error">' . __('Ungültige Video-URL.', 'minecraft-modern-theme') . '</p>';
$type = mm_video_get_type( $url );
$title = isset($args['title']) ? esc_attr($args['title']) : __('Video', 'minecraft-modern-theme');
if ( $type === 'mp4' ) {
return '<div class="mm-video-wrapper">'
. '<video class="mm-video-player" controls preload="metadata" playsinline>'
. '<source src="' . esc_url($embed_url) . '" type="video/mp4">'
. __('Dein Browser unterstützt kein HTML5-Video.', 'minecraft-modern-theme')
. '</video></div>';
}
return '<div class="mm-video-wrapper">'
. '<iframe src="' . esc_url($embed_url) . '" '
. 'title="' . $title . '" '
. 'frameborder="0" allowfullscreen allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share">'
. '</iframe></div>';
}
// --- 5. Shortcode: [mm_video url="..."] ---
// Beispiele:
// [mm_video url="https://www.youtube.com/watch?v=dQw4w9WgXcQ"]
// [mm_video url="https://vimeo.com/123456789" title="Mein Video"]
function mm_video_shortcode( $atts ) {
$a = shortcode_atts( array(
'url' => '',
'title' => __('Video', 'minecraft-modern-theme'),
), $atts );
if ( empty($a['url']) ) return '';
return mm_video_render_embed( $a['url'], $a );
}
add_shortcode( 'mm_video', 'mm_video_shortcode' );
// --- 6. Template-Loader für Video-Archiv und Galerie-Seite ---
function mm_video_template_loader( $template ) {
// Archiv (domain.de/videos/)
if ( is_post_type_archive('mm_video') ) {
$t = get_template_directory() . '/archive-video.php';
if ( file_exists($t) ) return $t;
}
// Seite mit dem Slug "videos"
if ( is_page() ) {
$obj = get_queried_object();
if ( $obj && $obj->post_name === 'videos' ) {
$t = get_template_directory() . '/archive-video.php';
if ( file_exists($t) ) return $t;
}
}
return $template;
}
add_filter( 'template_include', 'mm_video_template_loader' );
// --- 7. "Videos"-Seite automatisch anlegen ---
function mm_video_create_page() {
if ( ! get_page_by_path('videos') ) {
wp_insert_post( array(
'post_title' => __('Videos', 'minecraft-modern-theme'),
'post_name' => 'videos',
'post_status' => 'publish',
'post_type' => 'page',
'post_author' => 1,
) );
}
}
add_action( 'after_switch_theme', 'mm_video_create_page' );
// Auch beim Speichern im Customizer anlegen
add_action( 'customize_save_after', 'mm_video_create_page' );
// =============================================================================
// === BEWERBUNGSFORMULAR ======================================================
// =============================================================================
// --- 1. Customizer: Ein/Ausschalten ---
function mm_bewerbung_customizer( $wp_customize ) {
$wp_customize->add_section( 'mm_bewerbung_section', array(
'title' => __( 'Bewerbungsformular', 'minecraft-modern-theme' ),
'priority' => 80,
) );
$wp_customize->add_setting( 'mm_bewerbung_enabled', array(
'default' => false,
'sanitize_callback' => 'wp_validate_boolean',
) );
$wp_customize->add_control( 'mm_bewerbung_enabled', array(
'label' => __( 'Bewerbungsformular aktivieren', 'minecraft-modern-theme' ),
'description' => __( 'Schaltet die Bewerbungsseite und das Formular ein.', 'minecraft-modern-theme' ),
'section' => 'mm_bewerbung_section',
'type' => 'checkbox',
) );
$wp_customize->add_setting( 'mm_bewerbung_title', array(
'default' => __( 'Bewirb dich bei uns!', 'minecraft-modern-theme' ),
'sanitize_callback' => 'sanitize_text_field',
) );
$wp_customize->add_control( 'mm_bewerbung_title', array(
'label' => __( 'Titel der Bewerbungsseite', 'minecraft-modern-theme' ),
'section' => 'mm_bewerbung_section',
'type' => 'text',
) );
$wp_customize->add_setting( 'mm_bewerbung_desc', array(
'default' => __( 'Du möchtest Teil unseres Teams werden? Füll das Formular aus und wir melden uns bei dir.', 'minecraft-modern-theme' ),
'sanitize_callback' => 'sanitize_textarea_field',
) );
$wp_customize->add_control( 'mm_bewerbung_desc', array(
'label' => __( 'Beschreibungstext', 'minecraft-modern-theme' ),
'section' => 'mm_bewerbung_section',
'type' => 'textarea',
) );
$wp_customize->add_setting( 'mm_bewerbung_success_msg', array(
'default' => __( 'Deine Bewerbung wurde erfolgreich eingereicht! Wir melden uns so bald wie möglich bei dir.', 'minecraft-modern-theme' ),
'sanitize_callback' => 'sanitize_textarea_field',
) );
$wp_customize->add_control( 'mm_bewerbung_success_msg', array(
'label' => __( 'Erfolgsmeldung nach dem Absenden', 'minecraft-modern-theme' ),
'section' => 'mm_bewerbung_section',
'type' => 'textarea',
) );
$wp_customize->add_setting( 'mm_bewerbung_min_alter', array(
'default' => 14,
'sanitize_callback' => 'absint',
) );
$wp_customize->add_control( 'mm_bewerbung_min_alter', array(
'label' => __( 'Mindestalter', 'minecraft-modern-theme' ),
'description' => __( 'Bewerbungen unter diesem Alter werden abgelehnt. 0 = kein Limit.', 'minecraft-modern-theme' ),
'section' => 'mm_bewerbung_section',
'type' => 'number',
'input_attrs' => array( 'min' => 0, 'max' => 99 ),
) );
}
add_action( 'customize_register', 'mm_bewerbung_customizer' );
// --- 2. Custom Post Type: Bewerbung ---
function mm_register_bewerbung_cpt() {
if ( ! get_theme_mod( 'mm_bewerbung_enabled', false ) ) return;
register_post_type( 'mm_bewerbung', array(
'labels' => array(
'name' => __( 'Bewerbungen', 'minecraft-modern-theme' ),
'singular_name' => __( 'Bewerbung', 'minecraft-modern-theme' ),
'all_items' => __( 'Alle Bewerbungen', 'minecraft-modern-theme' ),
'menu_name' => __( 'Bewerbungen', 'minecraft-modern-theme' ),
),
'public' => false,
'show_ui' => true,
'show_in_menu' => true,
'menu_icon' => 'dashicons-clipboard',
'menu_position' => 8,
'supports' => array( 'title' ),
'capabilities' => array(
'create_posts' => 'do_not_allow', // Nur über Frontend erstellbar
),
'map_meta_cap' => true,
) );
}
add_action( 'init', 'mm_register_bewerbung_cpt' );
// --- 3. Admin: Meta-Box für Bewerbungs-Details ---
function mm_bewerbung_meta_box() {
add_meta_box(
'mm_bewerbung_details',
__( 'Bewerbungs-Details', 'minecraft-modern-theme' ),
'mm_bewerbung_meta_box_html',
'mm_bewerbung', 'normal', 'high'
);
add_meta_box(
'mm_bewerbung_status_box',
__( 'Status', 'minecraft-modern-theme' ),
'mm_bewerbung_status_box_html',
'mm_bewerbung', 'side', 'high'
);
}
add_action( 'add_meta_boxes', 'mm_bewerbung_meta_box' );
function mm_bewerbung_meta_box_html( $post ) {
$fields = array(
'_mm_bew_mc_name' => __( 'Minecraft Username', 'minecraft-modern-theme' ),
'_mm_bew_discord' => __( 'Discord Username', 'minecraft-modern-theme' ),
'_mm_bew_alter' => __( 'Alter', 'minecraft-modern-theme' ),
'_mm_bew_warum' => __( 'Warum möchtest du mitspielen?', 'minecraft-modern-theme' ),
'_mm_bew_erfahrung' => __( 'Erfahrung / Vorstellung', 'minecraft-modern-theme' ),
'_mm_bew_datum' => __( 'Eingereicht am', 'minecraft-modern-theme' ),
'_mm_bew_ip' => __( 'IP-Adresse', 'minecraft-modern-theme' ),
);
echo '<table class="form-table" style="margin:0;">';
foreach ( $fields as $key => $label ) {
$val = get_post_meta( $post->ID, $key, true );
if ( ! $val ) continue;
$is_long = in_array( $key, array( '_mm_bew_warum', '_mm_bew_erfahrung' ) );
echo '<tr>';
echo '<th style="width:180px;padding:10px 0;vertical-align:top;"><strong>' . esc_html($label) . '</strong></th>';
echo '<td style="padding:10px 0;">';
if ( $is_long ) {
echo '<div style="background:#f9f9f9;border:1px solid #ddd;border-radius:4px;padding:10px 14px;line-height:1.6;max-width:700px;white-space:pre-wrap;">' . esc_html($val) . '</div>';
} else {
echo '<span style="font-size:14px;">' . esc_html($val) . '</span>';
}
echo '</td></tr>';
}
echo '</table>';
}
function mm_bewerbung_status_box_html( $post ) {
$status = get_post_meta( $post->ID, '_mm_bew_status', true ) ?: 'neu';
wp_nonce_field( 'mm_bew_status_save', 'mm_bew_status_nonce' );
$options = array(
'neu' => array( 'label' => __('Neu','minecraft-modern-theme'), 'color' => '#0073aa' ),
'in_pruef' => array( 'label' => __('In Prüfung','minecraft-modern-theme'), 'color' => '#f0ad4e' ),
'angenommen' => array( 'label' => __('Angenommen','minecraft-modern-theme'), 'color' => '#46b450' ),
'abgelehnt' => array( 'label' => __('Abgelehnt','minecraft-modern-theme'), 'color' => '#dc3232' ),
);
echo '<select name="mm_bew_status" style="width:100%;margin-bottom:10px;">';
foreach ( $options as $val => $opt ) {
$sel = selected( $status, $val, false );
echo '<option value="' . esc_attr($val) . '" ' . $sel . '>' . esc_html($opt['label']) . '</option>';
}
echo '</select>';
$cur = isset($options[$status]) ? $options[$status] : $options['neu'];
echo '<div style="background:' . esc_attr($cur['color']) . ';color:#fff;text-align:center;padding:6px;border-radius:4px;font-weight:600;">' . esc_html($cur['label']) . '</div>';
// Admin-Notiz
$notiz = get_post_meta( $post->ID, '_mm_bew_notiz', true );
echo '<hr style="margin:12px 0;">';
echo '<label style="font-weight:600;display:block;margin-bottom:6px;">' . __('Interne Notiz:', 'minecraft-modern-theme') . '</label>';
echo '<textarea name="mm_bew_notiz" rows="4" style="width:100%;font-size:12px;">' . esc_textarea($notiz) . '</textarea>';
}
function mm_bewerbung_save_status( $post_id ) {
if ( ! isset($_POST['mm_bew_status_nonce']) || ! wp_verify_nonce($_POST['mm_bew_status_nonce'], 'mm_bew_status_save') ) return;
if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) return;
if ( ! current_user_can('edit_post', $post_id) ) return;
if ( isset($_POST['mm_bew_status']) ) {
update_post_meta( $post_id, '_mm_bew_status', sanitize_text_field($_POST['mm_bew_status']) );
}
if ( isset($_POST['mm_bew_notiz']) ) {
update_post_meta( $post_id, '_mm_bew_notiz', sanitize_textarea_field($_POST['mm_bew_notiz']) );
}
}
add_action( 'save_post', 'mm_bewerbung_save_status' );
// --- 4. Admin-Spalten in der Bewerbungsliste ---
function mm_bewerbung_columns( $cols ) {
return array(
'cb' => $cols['cb'],
'title' => __( 'Name', 'minecraft-modern-theme' ),
'mc_name' => __( 'Minecraft', 'minecraft-modern-theme' ),
'discord' => __( 'Discord', 'minecraft-modern-theme' ),
'alter' => __( 'Alter', 'minecraft-modern-theme' ),
'status' => __( 'Status', 'minecraft-modern-theme' ),
'datum' => __( 'Eingereicht', 'minecraft-modern-theme' ),
);
}
add_filter( 'manage_mm_bewerbung_posts_columns', 'mm_bewerbung_columns' );
function mm_bewerbung_column_content( $col, $post_id ) {
$status_colors = array(
'neu' => '#0073aa',
'in_pruef' => '#f0ad4e',
'angenommen' => '#46b450',
'abgelehnt' => '#dc3232',
);
$status_labels = array(
'neu' => __('Neu','minecraft-modern-theme'),
'in_pruef' => __('In Prüfung','minecraft-modern-theme'),
'angenommen' => __('Angenommen','minecraft-modern-theme'),
'abgelehnt' => __('Abgelehnt','minecraft-modern-theme'),
);
switch ($col) {
case 'mc_name':
$mc = get_post_meta($post_id, '_mm_bew_mc_name', true);
if ($mc) {
echo '<div style="display:flex;align-items:center;gap:8px;">';
echo '<img src="https://mc-heads.net/avatar/' . esc_attr($mc) . '/24" style="border-radius:3px;" width="24" height="24" alt="">';
echo esc_html($mc) . '</div>';
}
break;
case 'discord':
echo esc_html( get_post_meta($post_id, '_mm_bew_discord', true) );
break;
case 'alter':
echo esc_html( get_post_meta($post_id, '_mm_bew_alter', true) );
break;
case 'status':
$s = get_post_meta($post_id, '_mm_bew_status', true) ?: 'neu';
$color = isset($status_colors[$s]) ? $status_colors[$s] : '#0073aa';
$label = isset($status_labels[$s]) ? $status_labels[$s] : $s;
echo '<span style="background:' . esc_attr($color) . ';color:#fff;padding:2px 10px;border-radius:12px;font-size:12px;font-weight:600;">' . esc_html($label) . '</span>';
break;
case 'datum':
echo esc_html( get_post_meta($post_id, '_mm_bew_datum', true) );
break;
}
}
add_action( 'manage_mm_bewerbung_posts_custom_column', 'mm_bewerbung_column_content', 10, 2 );
// --- 5. AJAX: Formular absenden ---
add_action( 'wp_ajax_mm_submit_bewerbung', 'mm_submit_bewerbung' );
add_action( 'wp_ajax_nopriv_mm_submit_bewerbung', 'mm_submit_bewerbung' );
function mm_submit_bewerbung() {
// Nonce prüfen
if ( ! isset($_POST['nonce']) || ! wp_verify_nonce($_POST['nonce'], 'mm_bewerbung_nonce') ) {
wp_send_json_error( array('msg' => __('Sicherheitsfehler. Bitte Seite neu laden.', 'minecraft-modern-theme')) );
}
// Felder validieren
$mc_name = sanitize_text_field( $_POST['mc_name'] ?? '' );
$discord = sanitize_text_field( $_POST['discord'] ?? '' );
$alter_raw = absint( $_POST['alter'] ?? 0 );
$warum = sanitize_textarea_field( $_POST['warum'] ?? '' );
$erfahrung = sanitize_textarea_field( $_POST['erfahrung'] ?? '' );
$errors = array();
if ( empty($mc_name) ) $errors[] = __('Minecraft Username ist erforderlich.', 'minecraft-modern-theme');
if ( empty($discord) ) $errors[] = __('Discord Username ist erforderlich.', 'minecraft-modern-theme');
if ( $alter_raw < 1 ) $errors[] = __('Bitte gib dein Alter an.', 'minecraft-modern-theme');
if ( empty($warum) ) $errors[] = __('Bitte erkläre warum du mitspielen möchtest.', 'minecraft-modern-theme');
if ( empty($erfahrung) ) $errors[] = __('Bitte stell dich kurz vor.', 'minecraft-modern-theme');
// Mindestalter prüfen
$min_alter = absint( get_theme_mod('mm_bewerbung_min_alter', 14) );
if ( $min_alter > 0 && $alter_raw < $min_alter ) {
$errors[] = sprintf( __('Du musst mindestens %d Jahre alt sein.', 'minecraft-modern-theme'), $min_alter );
}
// Doppelbewerbung prüfen (gleicher MC-Name in den letzten 30 Tagen)
$existing = get_posts( array(
'post_type' => 'mm_bewerbung',
'post_status' => 'publish',
'meta_query' => array(
array( 'key' => '_mm_bew_mc_name', 'value' => $mc_name, 'compare' => '=' ),
),
'date_query' => array(
array( 'after' => '30 days ago' ),
),
'numberposts' => 1,
) );
if ( $existing ) {
$errors[] = __('Mit diesem Minecraft-Namen wurde in den letzten 30 Tagen bereits eine Bewerbung eingereicht.', 'minecraft-modern-theme');
}
if ( ! empty($errors) ) {
wp_send_json_error( array('msg' => implode(' ', $errors)) );
}
// Bewerbung als Post speichern
$post_id = wp_insert_post( array(
'post_type' => 'mm_bewerbung',
'post_status' => 'publish',
'post_title' => $mc_name . ' ' . date_i18n('d.m.Y H:i'),
) );
if ( is_wp_error($post_id) ) {
wp_send_json_error( array('msg' => __('Fehler beim Speichern. Bitte versuche es erneut.', 'minecraft-modern-theme')) );
}
update_post_meta( $post_id, '_mm_bew_mc_name', $mc_name );
update_post_meta( $post_id, '_mm_bew_discord', $discord );
update_post_meta( $post_id, '_mm_bew_alter', $alter_raw );
update_post_meta( $post_id, '_mm_bew_warum', $warum );
update_post_meta( $post_id, '_mm_bew_erfahrung', $erfahrung );
update_post_meta( $post_id, '_mm_bew_status', 'neu' );
update_post_meta( $post_id, '_mm_bew_datum', date_i18n('d.m.Y H:i:s') );
update_post_meta( $post_id, '_mm_bew_ip', sanitize_text_field( $_SERVER['REMOTE_ADDR'] ?? '' ) );
$success_msg = get_theme_mod( 'mm_bewerbung_success_msg', __('Deine Bewerbung wurde erfolgreich eingereicht! Wir melden uns so bald wie möglich bei dir.', 'minecraft-modern-theme') );
wp_send_json_success( array('msg' => $success_msg) );
}
// --- 6. Template-Loader ---
function mm_bewerbung_template_loader( $template ) {
if ( ! get_theme_mod('mm_bewerbung_enabled', false) ) return $template;
if ( is_page('bewerbung') ) {
$t = get_template_directory() . '/page-bewerbung.php';
if ( file_exists($t) ) return $t;
}
return $template;
}
add_filter( 'template_include', 'mm_bewerbung_template_loader' );
// --- 7. Seite automatisch anlegen ---
function mm_bewerbung_create_page() {
if ( ! get_theme_mod('mm_bewerbung_enabled', false) ) return;
if ( ! get_page_by_path('bewerbung') ) {
wp_insert_post( array(
'post_title' => __('Bewerbung', 'minecraft-modern-theme'),
'post_name' => 'bewerbung',
'post_status' => 'publish',
'post_type' => 'page',
'post_author' => 1,
) );
}
}
add_action( 'customize_save_after', 'mm_bewerbung_create_page' );