From 5e0c026da0955f4cf193b20c8fbc71df1b244319 Mon Sep 17 00:00:00 2001
From: M_Viper Diese Leiste wird auf allen Seiten angezeigt. Die Vorschau unten zeigt sofort, wie die Ankündigung aussieht — Änderungen wirken direkt in der Vorschau, erst nach Änderungen speichern werden sie im Frontend übernommen.
-
-
- Hier kannst du Teammitglieder hinzufügen, sortieren und bearbeiten. ← ' . esc_html($desc_n) . ' ' . esc_html($desc_s) . ' ' . esc_html($desc_m) . '
-
-
-
-
-
-
-
-
-
- ',
- 'after_title' => '
',
- ) );
- 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' => '',
- 'before_title' => '',
- 'after_title' => '
',
- ) );
- }
- register_sidebar( array(
- 'name' => __( 'Footer Rechts', 'minecraft-modern-theme' ),
- 'id' => 'footer-right',
- 'description' => __( 'Widget-Bereich rechts im Footer.', 'minecraft-modern-theme' ),
- 'before_widget' => '',
- 'before_title' => '',
- 'after_title' => '
',
- ) );
-}
-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' => '',
- 'before_title' => '',
- 'after_title' => '
',
- ) );
- 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' => '',
- 'before_title' => '',
- 'after_title' => '
',
- ) );
- 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' => '',
- 'before_title' => '',
- 'after_title' => '
',
- ) );
- 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' => '',
- 'before_title' => '',
- 'after_title' => '
',
- ) );
- 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' => '',
- 'before_title' => '',
- 'after_title' => '
',
- ) );
-}
-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 '';
- }
- }
-}
-
-
-// === 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('');
- });
- " );
-
- $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 = '';
- }
- }
- $avatar_html .= '
Header-Ankündigung
- Team Verwaltung
- Neues Mitglied hinzufügen
-
-
-
-
-
-
-
-
-
-
-
- '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;
- }
- ?>
- Bild
- Name
- Rang
- Bio
- UUID / Bild
- Sort.
- Aktionen
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Noch keine Mitglieder vorhanden. ';
- endif;
- ?>
-
- ',
- 'after_title' => '
',
- ) );
-}
-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 {
- ?>
-
-
-
-
-
-
-
- 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;
- ?>
-
-
-
-
- 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 );
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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',
- '/ 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();
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
| ' . esc_html($label) . ' | '; - echo '';
- if ( $is_long ) {
- echo ' ' . esc_html($val) . ' ';
- } else {
- echo '' . esc_html($val) . '';
- }
- echo ' |
|---|
1&&u.push(e.virtualSize-r)}if(0===u.length&&(u=[0]),0!==a.spaceBetween){const s=e.isHorizontal()&&n?"marginLeft":t("marginRight");c.filter(((e,t)=>!a.cssMode||t!==c.length-1)).css({[s]:`${x}px`})}if(a.centeredSlides&&a.centeredSlidesBounds){let e=0;m.forEach((t=>{e+=t+(a.spaceBetween?a.spaceBetween:0)})),e-=a.spaceBetween;const t=e-r;u=u.map((e=>e<0?-f:e>t?t+g:e))}if(a.centerInsufficientSlides){let e=0;if(m.forEach((t=>{e+=t+(a.spaceBetween?a.spaceBetween:0)})),e-=a.spaceBetween,e {r.wrapperEl.style.scrollSnapType="",r._swiperImmediateVirtual=!1}))}else{if(!r.support.smoothScroll)return w({swiper:r,targetPosition:s,side:e?"left":"top"}),!0;h.scrollTo({[e?"left":"top"]:s,behavior:"smooth"})}return!0}return r.setTransition(t),r.setTranslate(v),r.updateActiveIndex(n),r.updateSlidesClasses(),r.emit("beforeTransitionStart",t,a),r.transitionStart(s,b),0===t?r.transitionEnd(s,b):r.animating||(r.animating=!0,r.onSlideToWrapperTransitionEnd||(r.onSlideToWrapperTransitionEnd=function(e){r&&!r.destroyed&&e.target===this&&(r.$wrapperEl[0].removeEventListener("transitionend",r.onSlideToWrapperTransitionEnd),r.$wrapperEl[0].removeEventListener("webkitTransitionEnd",r.onSlideToWrapperTransitionEnd),r.onSlideToWrapperTransitionEnd=null,delete r.onSlideToWrapperTransitionEnd,r.transitionEnd(s,b))}),r.$wrapperEl[0].addEventListener("transitionend",r.onSlideToWrapperTransitionEnd),r.$wrapperEl[0].addEventListener("webkitTransitionEnd",r.onSlideToWrapperTransitionEnd)),!0},slideToLoop:function(e,t,s,a){if(void 0===e&&(e=0),void 0===t&&(t=this.params.speed),void 0===s&&(s=!0),"string"==typeof e){const t=parseInt(e,10);if(!isFinite(t))throw new Error(`The passed-in 'index' (string) couldn't be converted to 'number'. [${e}] given.`);e=t}const i=this;let r=e;return i.params.loop&&(r+=i.loopedSlides),i.slideTo(r,t,s,a)},slideNext:function(e,t,s){void 0===e&&(e=this.params.speed),void 0===t&&(t=!0);const a=this,{animating:i,enabled:r,params:n}=a;if(!r)return a;let l=n.slidesPerGroup;"auto"===n.slidesPerView&&1===n.slidesPerGroup&&n.slidesPerGroupAuto&&(l=Math.max(a.slidesPerViewDynamic("current",!0),1));const o=a.activeIndex JR*X-QseE3}Y@mO@vp3~Bf#AQG8E%GEh&QgLp6~FBH70+9Vo_z$*HD-ejj_xoW
z^t4%Go}jb0n*&lWx19y$Tj;@mY4Cx9DFfe?!gTYCxJozZwP@uS>a_qXlS3YSVEhrq
z7i^4u-Og)w%;9A_$aMPp&ib0+0%(lJM=*Y)yYN5hlTY8;ZM$|EYI=HqlH@3Yolw>U
zw}k{cR49qT*&Cj$*A2@D@M8p$xx7)S*Q%7EjfWX+Cg2vP*FvF;8kgKR&4{fxmN)Hr
z2TGDC38oBhW)iY5^9xa*j(0bi^R6Et16uS4^dWI3RKINO+I1@qo&^>0h8(hu`>ZSp
z%5WY*i|~1qMJpFH(y2WJCx)$LvS3CdD3z#0&&o(u(t));const a=e.children(`.${s.slidePrevClass}`);a.length>0&&o(u(a))}}function p(){const e=r();if(!t||t.destroyed)return;const s=t.params.lazy.scrollingElement?d(t.params.lazy.scrollingElement):d(e),a=s[0]===e,i=a?e.innerWidth:s[0].offsetWidth,l=a?e.innerHeight:s[0].offsetHeight,o=t.$el.offset(),{rtlTranslate:u}=t;let h=!1;u&&(o.left-=t.$el[0].scrollLeft);const m=[[o.left,o.top],[o.left+t.width,o.top],[o.left,o.top+t.height],[o.left+t.width,o.top+t.height]];for(let e=0;eeQ)Ir%wH%v{EXhCX}m6r<}HIXv>b5U89szY3(^lm7p5HY5N2?J
z>nunoeJTN7K>B0)C@$#?=pUId$=f3JnR0OHzs)-i-K+UO5*}WdAHS5_q|Km-n>th%
z<(Tno(w^k?JLAxZ^vAX}(5OS+EuBp^+cp23DQ{BcnRCnu2{&z!HpK~(xns&U@#Dl#
zm^9DJyW}
P%?I9g;GG9
!oJQyI
zBlf-@7mHh3Xm!c#&du%
bQ>Bg4Ht&fFRL&Y{{XE*T!t!6J{$CkGA5l>Q2MZ&SyzYdS2e
z$T|oH%rCSGE~@m}h$>aA0OT|e?TL|dhU+2)BZuaY=E0F)ziX*>)~?yIWzAYg+{L|K
zPx}xnCEPW0?8=oZGn~hIjyeiL)R~p8c09-DS-_n9@uYFj9Nl>4%9W0@a^;z4;l{M5
zH$r%!von8Ze}Df5kLxjr)A+0f6ainHG1S?_qv{bD1-OmSAf3o2sgis6FaED9JX
zG$*Jwno29ME}U)HEim5okx~0s;z6I)HLQ<2?wDh0!#&+SdY9AF-QSlF2ZG^9y0j>n
zY$+BJdgE|c8T+hNtMu_hOpsjvz>1@WhK|xVW->j!L&dg^*49jsITdPYNu$;pa%+P}
zkJkoK4Z+VuXGeP{)jH@LLd?P rGPa_~wZFjxW+Y=b2Z8Hr$`h?X!x1cy80gV&kU8DksavoYp;XAC|Y
zY}2~;^R4dLS+T48-5
Rxb>o{!NkSOiZ2}t|*N-5+#<7bD6t;{+
zd^5uEcYu7qKxwUL|3D1ljUC|PS{=j@iXyz?C#&ib%6&o-QjrvK$#x){v7sQQACId&
z5Va#fR&lMPxOSE*UJa7X%YAB|azS)jJv26kD4P1A=yn}1gcQ%iW-=}|Eb*JeEpFr|
zl=4^lkEw(G$B5tqoQ~(z!Qs+ZjrrA-y_R?$~z24L2;->L{w}u!&rhK;6R+O067Qo}MZD@lQ&@Cy~_l
z&(zZdlLZtS7%N#|