Upload via GUI (182 Dateien)

This commit is contained in:
2026-08-13 21:22:58 +00:00
parent e41f21b515
commit 0fda461bec
46 changed files with 9042 additions and 3835 deletions
@@ -0,0 +1,49 @@
/* Roboto lokal gehostet (DSGVO). Generiert, nicht manuell bearbeiten. */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('roboto-latin-ext-0fb06be4.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('roboto-latin-cb43c055.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('roboto-latin-ext-0fb06be4.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('roboto-latin-cb43c055.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('roboto-latin-ext-0fb06be4.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('roboto-latin-cb43c055.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -0,0 +1,49 @@
/* Rubik lokal gehostet (DSGVO). Generiert, nicht manuell bearbeiten. */
@font-face {
font-family: 'Rubik';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('rubik-latin-ext-b7b482e1.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Rubik';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('rubik-latin-f673d854.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Rubik';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('rubik-latin-ext-b7b482e1.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Rubik';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('rubik-latin-f673d854.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Rubik';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('rubik-latin-ext-b7b482e1.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Rubik';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('rubik-latin-f673d854.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -0,0 +1,9 @@
/* Satisfy lokal gehostet (DSGVO). Generiert, nicht manuell bearbeiten. */
@font-face {
font-family: 'Satisfy';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('satisfy-latin-b4c9305d.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -0,0 +1,49 @@
/* Source Sans Pro lokal gehostet (DSGVO). Generiert, nicht manuell bearbeiten. */
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('source-sans-pro-latin-ext-c12498bb.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('source-sans-pro-latin-ea667083.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('source-sans-pro-latin-ext-c008dc5c.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('source-sans-pro-latin-49902363.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('source-sans-pro-latin-ext-17c620e3.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('source-sans-pro-latin-c7df1611.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -0,0 +1,33 @@
/* Ubuntu lokal gehostet (DSGVO). Generiert, nicht manuell bearbeiten. */
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('ubuntu-latin-ext-b2700a09.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('ubuntu-latin-62d0e61f.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('ubuntu-latin-ext-4f2e4a8f.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('ubuntu-latin-7b2f7147.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -0,0 +1,49 @@
/* Work Sans lokal gehostet (DSGVO). Generiert, nicht manuell bearbeiten. */
@font-face {
font-family: 'Work Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('work-sans-latin-ext-94b95f7b.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Work Sans';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('work-sans-latin-f7b83de3.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Work Sans';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('work-sans-latin-ext-94b95f7b.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Work Sans';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('work-sans-latin-f7b83de3.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Work Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('work-sans-latin-ext-94b95f7b.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Work Sans';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url('work-sans-latin-f7b83de3.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
+327
View File
@@ -0,0 +1,327 @@
<?php
/**
* Ankuendigungs-Leiste inkl. Admin-Seite und Hoehenausgleich
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// =========================================================================
// 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;
// BUG-FIX: Hier wurde bisher /css/announcement.css eingebunden diese Datei
// existiert nicht (404 bei jedem Aufruf mit aktiver Bar). Die Styles der
// Ankündigungs-Leiste stehen bereits in style.css (#mm-announcement ff.),
// der Enqueue war also toter Code und wurde entfernt.
wp_enqueue_script( 'mm-announcement-script', get_template_directory_uri() . '/js/announcement.js', array(), filemtime( get_template_directory() . '/js/announcement.js' ), true );
// Gewählte Schrift lokal einbinden (kein Google-Aufruf mehr).
// Der Array-Key entspricht dem Schriftnamen, z. B. "Open Sans" -> /fonts/open-sans/.
$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'] ) ) {
mm_enqueue_local_font( $font_key );
}
}
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' ), filemtime( get_template_directory() . '/js/mm-announcement-admin.js' ), 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' ) ),
// Basis-URL der lokalen Schriften für die Live-Vorschau (kein Google-Aufruf)
'fontsBase' => get_template_directory_uri() . '/fonts/',
) );
}
add_action( 'admin_enqueue_scripts', 'mm_announcement_admin_assets' );
// === 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' );
+394
View File
@@ -0,0 +1,394 @@
<?php
/**
* Bewerbungsformular: CPT, Formularverarbeitung und Verwaltung
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// =============================================================================
// === 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 ),
) );
// ── Positionen ──
$wp_customize->add_setting( 'mm_bewerbung_positionen', array(
'default' => "Moderator
Supporter
Builder
Developer",
'sanitize_callback' => 'sanitize_textarea_field',
) );
$wp_customize->add_control( 'mm_bewerbung_positionen', array(
'label' => __( 'Bewerbungs-Positionen', 'minecraft-modern-theme' ),
'description' => __( 'Eine Position pro Zeile. Der Bewerber wählt genau eine davon aus.', 'minecraft-modern-theme' ),
'section' => 'mm_bewerbung_section',
'type' => 'textarea',
) );
}
add_action( 'customize_register', 'mm_bewerbung_customizer' );
// Hilfsfunktion: Positions-Liste als Array zurückgeben
function mm_bewerbung_get_positionen() {
$raw = get_theme_mod( 'mm_bewerbung_positionen', "Moderator
Supporter
Builder
Developer" );
$list = array_filter( array_map( 'trim', explode( "
", $raw ) ) );
return array_values( $list );
}
// --- 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_position' => __( 'Bewirbt sich als', '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' ),
'position' => __( 'Position', '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 'position':
$pos = get_post_meta($post_id, '_mm_bew_position', true);
if ( $pos ) {
echo '<span style="background:#1e3a5f;color:#58a6ff;padding:2px 10px;border-radius:12px;font-size:12px;font-weight:600;">'
. esc_html($pos) . '</span>';
} else {
echo '<span style="color:#999;">—</span>';
}
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'] ?? '' );
$position = sanitize_text_field( $_POST['position'] ?? '' );
$positionen = mm_bewerbung_get_positionen();
$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($positionen) && ! in_array($position, $positionen, true) ) {
$errors[] = __('Bitte wähle eine gültige Position aus.', '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_position', $position );
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') ) {
// BUG-FIX: locate_template() statt get_template_directory(),
// damit ein page-bewerbung.php im Child-Theme greift.
$t = locate_template( 'page-bewerbung.php' );
if ( $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' );
@@ -0,0 +1,283 @@
<?php
/**
* Cookie-Banner (DSGVO) mit vier Darstellungsvarianten
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// =============================================================================
// === 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);});
+4 -4
View File
@@ -467,14 +467,14 @@ add_action( 'customize_register', 'minecraft_modern_customize_register' );
// ========================================================================= // =========================================================================
// DYNAMISCHES CSS FIX: Google Font via wp_enqueue_style(), nicht <link> in wp_head // DYNAMISCHES CSS Slider-Schrift wird lokal eingebunden (DSGVO)
// ========================================================================= // =========================================================================
function minecraft_modern_enqueue_dynamic_styles() { function minecraft_modern_enqueue_dynamic_styles() {
$font = get_theme_mod( 'slider_font_family', 'Raleway' ); $font = get_theme_mod( 'slider_font_family', 'Raleway' );
// Google Font sauber einbinden // Lokale Schrift statt fonts.googleapis.com (siehe mm_enqueue_local_font()).
$font_url = 'https://fonts.googleapis.com/css2?family=' . urlencode($font) . ':wght@400;600;700&display=swap'; // Fällt die Schrift aus, greift die Basis-Schrift bzw. sans-serif aus dem CSS.
wp_enqueue_style( 'minecraft-modern-google-font', $font_url, array(), null ); mm_enqueue_local_font( $font );
// Dynamisches CSS als inline style // Dynamisches CSS als inline style
$accent_color = get_theme_mod( 'primary_accent_color', '#00d4ff' ); $accent_color = get_theme_mod( 'primary_accent_color', '#00d4ff' );
+92
View File
@@ -0,0 +1,92 @@
<?php
/**
* FAQ: Custom Post Type, automatische Seite und Template-Loader
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// === 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' );
// =============================================================================
// 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' ) {
// BUG-FIX: get_template_directory() erzwang das Parent-Template und
// ignorierte ein archive-faq.php im Child-Theme. locate_template()
// bevorzugt das Child-Theme und fällt sonst aufs Parent zurück.
$t = locate_template( 'archive-faq.php' );
if ( $t ) return $t;
}
}
return $template;
}
add_filter( 'template_include', 'load_faq_page_template' );
+58
View File
@@ -0,0 +1,58 @@
<?php
/**
* Automatische Erstellung der statischen Startseite
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// =============================================================================
// 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' );
+308
View File
@@ -0,0 +1,308 @@
<?php
/**
* Header-Hilfsfunktionen, Menue-Layouts und Beitrags-Sidebar
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// =========================================================================
// 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' );
+251
View File
@@ -0,0 +1,251 @@
<?php
/**
* Eigene Login-URL statt /wp-login.php
*
* Der Slug wird im Customizer unter "Login-URL" gesetzt. Ist die Funktion aktiv,
* beantwortet WordPress das Login-Formular nur noch unter der eigenen Adresse;
* /wp-login.php und /wp-admin leiten für Nicht-Angemeldete auf die Startseite.
*
* WICHTIG - bewusst offen gelassen:
* - wp-admin/admin-ajax.php (Frontend-Plugins UND der OAuth-Rückkanal von
* Authentik: home_url('/wp-admin/admin-ajax.php?action=authentik_callback'))
* - wp-admin/admin-post.php (u. a. der Einstellungs-Export dieses Themes)
*
* NOTFALL - falls du dich aussperrst:
* In wp-config.php diese Zeile ergänzen, dann ist /wp-login.php sofort
* wieder erreichbar:
* define( 'MM_DISABLE_LOGIN_SLUG', true );
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Slugs, die nicht verwendet werden dürfen (Kollision mit WordPress selbst).
*/
function mm_login_slug_reserviert() {
return array(
'wp-admin', 'wp-login', 'wp-login.php', 'wp-content', 'wp-includes',
'admin-ajax', 'admin-ajax.php', 'admin-post', 'admin-post.php',
'wp-json', 'feed', 'rss', 'rss2', 'index', 'index.php', 'xmlrpc.php',
'wp-cron.php', 'wp-signup.php', 'wp-activate.php',
);
}
/**
* Liefert den aktiven Login-Slug - oder '' wenn die Funktion nicht greift.
*/
function mm_login_slug_get() {
// Notfall-Schalter aus wp-config.php hat immer Vorrang
if ( defined( 'MM_DISABLE_LOGIN_SLUG' ) && MM_DISABLE_LOGIN_SLUG ) {
return '';
}
if ( ! get_theme_mod( 'mm_login_slug_enabled', false ) ) {
return '';
}
$slug = trim( (string) get_theme_mod( 'mm_login_slug', '' ), '/' );
if ( $slug === '' || in_array( $slug, mm_login_slug_reserviert(), true ) ) {
return '';
}
return $slug;
}
/**
* Pfad der aktuellen Anfrage, relativ zum WordPress-Stammverzeichnis.
* Berücksichtigt Installationen in einem Unterordner.
*/
function mm_login_slug_current_path() {
if ( empty( $_SERVER['REQUEST_URI'] ) ) {
return '';
}
$path = (string) wp_parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
$path = trim( $path, '/' );
$home = trim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' );
if ( $home !== '' && ( $path === $home || strpos( $path, $home . '/' ) === 0 ) ) {
$path = trim( substr( $path, strlen( $home ) ), '/' );
}
return $path;
}
/**
* Ersetzt wp-login.php-Adressen durch die eigene Login-URL.
* Der Query-String bleibt erhalten (redirect_to, action=logout, interim-login ...).
*/
function mm_login_slug_rewrite_url( $url ) {
$slug = mm_login_slug_get();
if ( ! $slug || ! is_string( $url ) || strpos( $url, 'wp-login.php' ) === false ) {
return $url;
}
$teile = explode( '?', $url, 2 );
$query = isset( $teile[1] ) ? $teile[1] : '';
$neu = home_url( '/' . $slug . '/' );
return $query !== '' ? $neu . '?' . $query : $neu;
}
add_filter( 'login_url', 'mm_login_slug_rewrite_url', 20 );
add_filter( 'logout_url', 'mm_login_slug_rewrite_url', 20 );
add_filter( 'lostpassword_url', 'mm_login_slug_rewrite_url', 20 );
add_filter( 'register_url', 'mm_login_slug_rewrite_url', 20 );
add_filter( 'wp_redirect', 'mm_login_slug_rewrite_url', 20 );
/**
* site_url()/network_site_url() umschreiben, damit auch das Formular-Ziel
* (action="...wp-login.php") auf die eigene Adresse zeigt.
*
* Hinweis: Das Authentik-Plugin baut seine redirect_uri bewusst über home_url()
* und ist von diesem Filter deshalb nicht betroffen.
*/
function mm_login_slug_filter_site_url( $url, $path = '', $scheme = null ) {
return mm_login_slug_rewrite_url( $url );
}
add_filter( 'site_url', 'mm_login_slug_filter_site_url', 20, 3 );
add_filter( 'network_site_url', 'mm_login_slug_filter_site_url', 20, 3 );
/**
* Kernstück: Anfragen abfangen, bevor WordPress die Query auflöst.
*/
function mm_login_slug_handle_request() {
$slug = mm_login_slug_get();
if ( ! $slug ) {
return;
}
$path = mm_login_slug_current_path();
if ( $path === '' ) {
return;
}
// 1) Eigene Login-Adresse -> wp-login.php ausliefern
if ( $path === $slug ) {
global $pagenow, $error, $interim_login, $action, $user_login;
$pagenow = 'wp-login.php';
require_once ABSPATH . 'wp-login.php';
exit;
}
// 2) Alte Login-Adresse -> Startseite
if ( $path === 'wp-login.php' ) {
wp_safe_redirect( home_url( '/' ), 302 );
exit;
}
// 3) /wp-admin für Nicht-Angemeldete -> Startseite.
// admin-ajax.php und admin-post.php bleiben offen, sonst brechen
// Frontend-AJAX und der Authentik-OAuth-Rückkanal.
if ( $path === 'wp-admin' || strpos( $path, 'wp-admin/' ) === 0 ) {
$datei = basename( $path );
$erlaubt = array( 'admin-ajax.php', 'admin-post.php' );
if ( ! in_array( $datei, $erlaubt, true ) && ! is_user_logged_in() ) {
wp_safe_redirect( home_url( '/' ), 302 );
exit;
}
}
}
add_action( 'init', 'mm_login_slug_handle_request', 1 );
// =========================================================================
// CUSTOMIZER: Login-URL einstellen
// =========================================================================
function mm_login_slug_sanitize( $value ) {
$value = sanitize_title( (string) $value );
return in_array( $value, mm_login_slug_reserviert(), true ) ? '' : $value;
}
/**
* Prüft die Eingabe und meldet Kollisionen direkt im Customizer zurück,
* statt sie stillschweigend zu verwerfen.
*/
function mm_login_slug_validate( $validity, $value ) {
$value = sanitize_title( (string) $value );
if ( $value === '' ) {
return $validity; // leer ist erlaubt (= Funktion greift nicht)
}
if ( in_array( $value, mm_login_slug_reserviert(), true ) ) {
$validity->add( 'reserviert', __( 'Dieser Pfad ist von WordPress belegt. Bitte einen anderen wählen.', 'minecraft-modern-theme' ) );
return $validity;
}
// Kollision mit einer bestehenden Seite/Beitrag?
$vorhanden = get_page_by_path( $value, OBJECT, array( 'page', 'post' ) );
if ( $vorhanden ) {
$validity->add( 'belegt', sprintf(
/* translators: %s: Titel des kollidierenden Inhalts */
__( 'Der Pfad wird bereits von "%s" verwendet - diese Seite wäre dann nicht mehr erreichbar.', 'minecraft-modern-theme' ),
$vorhanden->post_title
) );
}
return $validity;
}
add_filter( 'customize_validate_mm_login_slug', 'mm_login_slug_validate', 10, 2 );
function mm_login_slug_customizer( $wp_customize ) {
$notfall = "define( 'MM_DISABLE_LOGIN_SLUG', true );";
$wp_customize->add_section( 'mm_login_url_section', array(
'title' => __( 'Login-URL', 'minecraft-modern-theme' ),
'priority' => 85,
'description' => __( 'Ersetzt die Standard-Adresse /wp-login.php durch einen eigenen Pfad und reduziert so automatisierte Anmeldeversuche.', 'minecraft-modern-theme' )
. '<br><br><strong>' . esc_html__( 'Notfall:', 'minecraft-modern-theme' ) . '</strong> '
. esc_html__( 'Diese Zeile in die wp-config.php eintragen, dann ist /wp-login.php sofort wieder erreichbar:', 'minecraft-modern-theme' )
. '<br><code>' . esc_html( $notfall ) . '</code>',
) );
$wp_customize->add_setting( 'mm_login_slug', array(
'default' => '',
'sanitize_callback' => 'mm_login_slug_sanitize',
'transport' => 'refresh',
) );
$wp_customize->add_control( 'mm_login_slug', array(
'label' => __( 'Eigener Login-Pfad', 'minecraft-modern-theme' ),
'description' => sprintf(
/* translators: %s: Beispiel-URL */
__( 'Nur der Pfad, ohne Schrägstriche. Ergibt: %s', 'minecraft-modern-theme' ),
'<code>' . esc_html( home_url( '/' ) ) . 'DEIN-PFAD/</code>'
),
'section' => 'mm_login_url_section',
'type' => 'text',
) );
$wp_customize->add_setting( 'mm_login_slug_enabled', array(
'default' => false,
'sanitize_callback' => 'wp_validate_boolean',
'transport' => 'refresh',
) );
$wp_customize->add_control( 'mm_login_slug_enabled', array(
'label' => __( 'Eigene Login-URL aktivieren', 'minecraft-modern-theme' ),
'description' => __( 'Erst aktivieren, wenn oben ein Pfad eingetragen und gespeichert ist. Notiere dir die neue Adresse, bevor du dich abmeldest!', 'minecraft-modern-theme' ),
'section' => 'mm_login_url_section',
'type' => 'checkbox',
) );
}
add_action( 'customize_register', 'mm_login_slug_customizer' );
/**
* Hinweis im Adminbereich mit der aktuell gültigen Login-Adresse.
*/
function mm_login_slug_admin_notice() {
$slug = mm_login_slug_get();
if ( ! $slug || ! current_user_can( 'manage_options' ) ) {
return;
}
$screen = get_current_screen();
if ( ! $screen || ! in_array( $screen->id, array( 'dashboard', 'themes' ), true ) ) {
return;
}
printf(
'<div class="notice notice-info"><p>%s <code>%s</code></p></div>',
esc_html__( 'Die Anmeldung erfolgt auf dieser Seite über:', 'minecraft-modern-theme' ),
esc_html( home_url( '/' . $slug . '/' ) )
);
}
add_action( 'admin_notices', 'mm_login_slug_admin_notice' );
+104
View File
@@ -0,0 +1,104 @@
<?php
/**
* Anpassungen der WordPress-Login-Seite
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// =========================================================================
// CUSTOM LOGIN FUNCTIONS
// =========================================================================
function minecraft_modern_login_assets() {
wp_enqueue_style(
'minecraft-modern-login-style',
get_template_directory_uri() . '/css/login-style.css',
array(),
filemtime( get_template_directory() . '/css/login-style.css' )
);
// Font Awesome lokal (identisches Handle wie im Frontend kein Doppel-Laden)
wp_enqueue_style(
'font-awesome',
get_template_directory_uri() . '/css/fontawesome.min.css',
array(),
filemtime( get_template_directory() . '/css/fontawesome.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' ), filemtime( get_template_directory() . '/js/login-slider.js' ), true );
wp_enqueue_script( 'minecraft-modern-login-script', get_template_directory_uri() . '/js/login-script.js', array( 'jquery' ), filemtime( get_template_directory() . '/js/login-script.js' ), 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' );
+367
View File
@@ -0,0 +1,367 @@
<?php
/**
* Eigene Links in der Backend-Seitenleiste
*
* Verwaltung unter dem Hauptmenüpunkt "Menü-Links". Jeder Eintrag erscheint
* als eigener Punkt in der linken Adminleiste und kann auf eine beliebige
* Adresse zeigen - auch auf externe Seiten.
*
* Technischer Hinweis: add_menu_page() schickt den Slug durch plugin_basename()
* und damit durch wp_normalize_path(). Dort bleibt "https://" nur erhalten,
* solange https als Stream-Wrapper registriert ist (hängt an allow_url_fopen).
* Ist die Direktive abgeschaltet - auf gehärteten Servern üblich - wird aus
* "https://" ein "https:/" und der Link ist kaputt. Die Einträge werden deshalb
* direkt in das globale $menu-Array geschrieben; dort bleibt die Adresse in
* jedem Fall unverändert (siehe wp-admin/menu-header.php).
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/** Auswählbare Einfügepositionen in der Seitenleiste. */
function mm_menu_links_positionen() {
return array(
'oben' => array( 'label' => __( 'Ganz oben (unter Dashboard)', 'minecraft-modern-theme' ), 'pos' => 3 ),
'inhalte' => array( 'label' => __( 'Nach den Inhalten', 'minecraft-modern-theme' ), 'pos' => 26 ),
'werkzeug' => array( 'label' => __( 'Bei den Werkzeugen', 'minecraft-modern-theme' ), 'pos' => 76 ),
'unten' => array( 'label' => __( 'Ganz unten', 'minecraft-modern-theme' ), 'pos' => 101 ),
);
}
/** Gespeicherte Links. */
function mm_menu_links_get() {
$links = get_option( 'mm_menu_links', array() );
return is_array( $links ) ? $links : array();
}
/**
* Prüft ein Linkziel und gibt es zurück - oder '' wenn es nicht zulässig ist.
*
* Erlaubt sind ausschliesslich http/https sowie relative Ziele innerhalb des
* Backends (z. B. edit.php?post_type=video). Alles andere - insbesondere
* javascript: und data: - wird verworfen, da die Adresse später unverändert
* als href in der Adminleiste landet.
*/
function mm_menu_links_url_pruefen( $url ) {
$url = (string) $url;
// Steuerzeichen und Leerraum entfernen. Ohne das liesse sich die
// Schema-Prüfung mit "java\nscript:..." umgehen - Browser ignorieren
// solche Zeichen beim Auflösen der Adresse.
$url = preg_replace( '/[\x00-\x20\x7F]/', '', $url );
if ( $url === '' ) {
return '';
}
// Protokollrelative Adressen (//example.com) nicht zulassen
if ( strpos( $url, '//' ) === 0 ) {
return '';
}
// Enthält das Ziel ein Schema?
if ( preg_match( '#^([a-z][a-z0-9+.\-]*):#i', $url, $t ) ) {
if ( ! in_array( strtolower( $t[1] ), array( 'http', 'https' ), true ) ) {
return '';
}
return esc_url_raw( $url, array( 'http', 'https' ) );
}
// Relatives Ziel im Backend
return sanitize_text_field( $url );
}
// =========================================================================
// 1. Links in die Seitenleiste einhängen
// =========================================================================
/**
* Sucht ab $wunsch den nächsten freien Platz im Menü, damit vorhandene
* Einträge nicht überschrieben werden.
*/
function mm_menu_links_freier_platz( $wunsch ) {
global $menu;
// Bewusst nur der String-Schlüssel: Bei einem Float-Schlüssel schneidet PHP
// die Nachkommastellen ab, $menu[3.1] würde also $menu[3] abfragen und
// jede Zwischenposition fälschlich als belegt melden.
$pos = (float) $wunsch;
while ( isset( $menu[ (string) $pos ] ) ) {
$pos = round( $pos + 0.1, 1 );
}
return (string) $pos;
}
function mm_menu_links_registrieren() {
global $menu;
$positionen = mm_menu_links_positionen();
foreach ( mm_menu_links_get() as $i => $link ) {
$titel = isset( $link['titel'] ) ? $link['titel'] : '';
// Zweite Prüfung beim Ausgeben: Die Adresse landet unverändert als
// href in der Adminleiste, deshalb auch hier nichts Ungeprüftes durchlassen.
$url = mm_menu_links_url_pruefen( $link['url'] ?? '' );
if ( $titel === '' || $url === '' ) {
continue;
}
$icon = ! empty( $link['icon'] ) ? $link['icon'] : 'dashicons-admin-links';
$extern = ! empty( $link['neues_fenster'] );
$schl = isset( $link['position'] ) ? $link['position'] : 'unten';
$wunsch = isset( $positionen[ $schl ] ) ? $positionen[ $schl ]['pos'] : 101;
$klassen = 'menu-top mm-menu-link';
if ( $extern ) {
$klassen .= ' mm-menu-link-extern';
}
// Aufbau wie in wp-admin/menu.php:
// 0 Beschriftung, 1 Fähigkeit, 2 Ziel, 3 Seitentitel, 4 Klassen, 5 Id, 6 Icon
$menu[ mm_menu_links_freier_platz( $wunsch ) ] = array(
$titel,
'read', // alle mit Backend-Zugang
$url,
$titel,
$klassen,
'mm-menu-link-' . (int) $i,
$icon,
);
}
}
add_action( 'admin_menu', 'mm_menu_links_registrieren', 100 );
/**
* Externe Links in einem neuen Tab öffnen.
* WordPress kennt kein target-Attribut für Menüpunkte, deshalb per Skript.
*/
function mm_menu_links_target() {
$hat_externe = false;
foreach ( mm_menu_links_get() as $link ) {
if ( ! empty( $link['neues_fenster'] ) ) {
$hat_externe = true;
break;
}
}
if ( ! $hat_externe ) {
return;
}
?>
<script>
document.querySelectorAll('#adminmenu li.mm-menu-link-extern > a').forEach(function (a) {
a.target = '_blank';
a.rel = 'noopener noreferrer';
});
</script>
<?php
}
add_action( 'admin_footer', 'mm_menu_links_target' );
// Hinweis: Hier stand ein zusätzliches Pfeil-Symbol hinter externen Einträgen
// (::after mit einem Dashicon). Es brach bei kurzen Beschriftungen in die
// nächste Zeile um und wirkte dort wie ein loses Icon - deshalb entfernt.
// Externe Ziele erkennt man weiterhin daran, dass sie sich in einem neuen
// Tab öffnen.
// =========================================================================
// 2. Verwaltungsseite
// =========================================================================
function mm_menu_links_admin_menu() {
add_menu_page(
__( 'Menü-Links', 'minecraft-modern-theme' ),
__( 'Menü-Links', 'minecraft-modern-theme' ),
'manage_options',
'mm-menu-links',
'mm_menu_links_seite',
'dashicons-admin-links',
63
);
register_setting( 'mm_menu_links_group', 'mm_menu_links', array(
'type' => 'array',
'sanitize_callback' => 'mm_menu_links_sanitize',
'default' => array(),
) );
}
add_action( 'admin_menu', 'mm_menu_links_admin_menu' );
function mm_menu_links_sanitize( $roh ) {
$sauber = array();
if ( ! is_array( $roh ) ) {
return $sauber;
}
$erlaubte_positionen = array_keys( mm_menu_links_positionen() );
foreach ( $roh as $z ) {
$titel = sanitize_text_field( $z['titel'] ?? '' );
$url = mm_menu_links_url_pruefen( $z['url'] ?? '' );
// Zeilen ohne Beschriftung oder ohne zulässiges Ziel verwerfen
if ( $titel === '' || $url === '' ) {
continue;
}
$pos = sanitize_key( $z['position'] ?? 'unten' );
$sauber[] = array(
'titel' => $titel,
'url' => $url,
'icon' => sanitize_html_class( $z['icon'] ?? '' ) ?: 'dashicons-admin-links',
'position' => in_array( $pos, $erlaubte_positionen, true ) ? $pos : 'unten',
'neues_fenster' => ! empty( $z['neues_fenster'] ) ? 1 : 0,
);
}
return $sauber;
}
/** Eine Eingabezeile. */
function mm_menu_links_zeile( $i, $z ) {
$titel = $z['titel'] ?? '';
$url = $z['url'] ?? '';
$icon = $z['icon'] ?? 'dashicons-admin-links';
$pos = $z['position'] ?? 'unten';
$extern = ! empty( $z['neues_fenster'] );
$n = 'mm_menu_links[' . $i . ']';
?>
<div class="mm-liste-zeile">
<span class="mm-vorschau dashicons <?php echo esc_attr( $icon ); ?>" aria-hidden="true"></span>
<label class="mm-spalte">
<span><?php esc_html_e( 'Beschriftung', 'minecraft-modern-theme' ); ?></span>
<input type="text" name="<?php echo esc_attr( $n ); ?>[titel]" value="<?php echo esc_attr( $titel ); ?>"
placeholder="<?php esc_attr_e( 'Discord', 'minecraft-modern-theme' ); ?>">
</label>
<label class="mm-spalte mm-spalte-breit">
<span><?php esc_html_e( 'Adresse', 'minecraft-modern-theme' ); ?></span>
<input type="text" name="<?php echo esc_attr( $n ); ?>[url]" value="<?php echo esc_attr( $url ); ?>"
placeholder="https://discord.gg/...">
</label>
<label class="mm-spalte">
<span><?php esc_html_e( 'Icon', 'minecraft-modern-theme' ); ?></span>
<input type="text" class="mm-icon-feld" name="<?php echo esc_attr( $n ); ?>[icon]"
value="<?php echo esc_attr( $icon ); ?>" placeholder="dashicons-admin-links">
</label>
<label class="mm-spalte">
<span><?php esc_html_e( 'Position', 'minecraft-modern-theme' ); ?></span>
<select name="<?php echo esc_attr( $n ); ?>[position]">
<?php foreach ( mm_menu_links_positionen() as $schl => $p ) : ?>
<option value="<?php echo esc_attr( $schl ); ?>" <?php selected( $pos, $schl ); ?>>
<?php echo esc_html( $p['label'] ); ?>
</option>
<?php endforeach; ?>
</select>
</label>
<label class="mm-spalte mm-spalte-schmal mm-checkbox">
<span><?php esc_html_e( 'Neues Fenster', 'minecraft-modern-theme' ); ?></span>
<input type="checkbox" name="<?php echo esc_attr( $n ); ?>[neues_fenster]" value="1" <?php checked( $extern ); ?>>
</label>
<button type="button" class="button-link mm-liste-entfernen" title="<?php esc_attr_e( 'Link entfernen', 'minecraft-modern-theme' ); ?>">
<span class="dashicons dashicons-trash" aria-hidden="true"></span>
<span class="screen-reader-text"><?php esc_html_e( 'Link entfernen', 'minecraft-modern-theme' ); ?></span>
</button>
</div>
<?php
}
function mm_menu_links_seite() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$links = mm_menu_links_get();
?>
<div class="wrap mm-menu-links-admin">
<h1><?php esc_html_e( 'Menü-Links', 'minecraft-modern-theme' ); ?></h1>
<p class="description" style="max-width:760px;">
<?php esc_html_e( 'Diese Einträge erscheinen zusätzlich in der linken Adminleiste - für alle, die sich im Backend anmelden können. Als Adresse funktioniert eine vollständige externe Adresse (https://...) genauso wie ein internes Ziel, zum Beispiel edit.php?post_type=video.', 'minecraft-modern-theme' ); ?>
</p>
<form method="post" action="options.php">
<?php settings_fields( 'mm_menu_links_group' ); ?>
<div class="mm-liste" data-mm-liste="mm_menu_links">
<?php foreach ( $links as $i => $z ) : ?>
<?php mm_menu_links_zeile( (int) $i, $z ); ?>
<?php endforeach; ?>
</div>
<?php if ( empty( $links ) ) : ?>
<p class="mm-leer"><?php esc_html_e( 'Noch keine Links angelegt.', 'minecraft-modern-theme' ); ?></p>
<?php endif; ?>
<p>
<button type="button" class="button button-secondary mm-liste-hinzufuegen" data-ziel="mm_menu_links">
<span class="mm-plus" aria-hidden="true">+</span>
<?php esc_html_e( 'Link hinzufügen', 'minecraft-modern-theme' ); ?>
</button>
</p>
<script type="text/html" class="mm-liste-vorlage" data-fuer="mm_menu_links">
<?php mm_menu_links_zeile( '__INDEX__', array() ); ?>
</script>
<?php submit_button(); ?>
</form>
<h2><?php esc_html_e( 'Häufige Symbole', 'minecraft-modern-theme' ); ?></h2>
<p class="description">
<?php esc_html_e( 'Trage eine dieser Klassen im Feld "Icon" ein. Die vollständige Liste findest du in der WordPress-Dokumentation unter "Dashicons".', 'minecraft-modern-theme' ); ?>
</p>
<ul class="mm-icon-liste">
<?php
$beispiele = array(
'dashicons-admin-links' => __( 'Link', 'minecraft-modern-theme' ),
'dashicons-external' => __( 'Extern', 'minecraft-modern-theme' ),
'dashicons-admin-site' => __( 'Webseite', 'minecraft-modern-theme' ),
'dashicons-groups' => __( 'Community', 'minecraft-modern-theme' ),
'dashicons-shield' => __( 'Sicherheit', 'minecraft-modern-theme' ),
'dashicons-chart-area' => __( 'Statistik', 'minecraft-modern-theme' ),
'dashicons-cloud' => __( 'Server', 'minecraft-modern-theme' ),
'dashicons-media-video' => __( 'Video', 'minecraft-modern-theme' ),
'dashicons-book' => __( 'Wiki', 'minecraft-modern-theme' ),
'dashicons-sos' => __( 'Support', 'minecraft-modern-theme' ),
);
foreach ( $beispiele as $klasse => $name ) :
?>
<li>
<span class="dashicons <?php echo esc_attr( $klasse ); ?>"></span>
<code><?php echo esc_html( $klasse ); ?></code>
<span class="mm-icon-name"><?php echo esc_html( $name ); ?></span>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php
}
/** Skript und Styles nur auf der Verwaltungsseite laden. */
function mm_menu_links_admin_assets( $hook ) {
if ( $hook !== 'toplevel_page_mm-menu-links' ) {
return;
}
wp_enqueue_style(
'mm-menu-links-admin',
get_template_directory_uri() . '/css/menu-links-admin.css',
array(),
filemtime( get_template_directory() . '/css/menu-links-admin.css' )
);
wp_enqueue_script(
'mm-menu-links-admin',
get_template_directory_uri() . '/js/menu-links-admin.js',
array(),
filemtime( get_template_directory() . '/js/menu-links-admin.js' ),
true
);
}
add_action( 'admin_enqueue_scripts', 'mm_menu_links_admin_assets' );
@@ -0,0 +1,41 @@
<?php
/**
* Nach-oben-Button
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// =========================================================================
// 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' ),
filemtime( get_template_directory() . '/js/scroll-to-top.js' ),
true
);
}
add_action( 'wp_enqueue_scripts', 'minecraft_modern_scroll_to_top_script' );
+447
View File
@@ -0,0 +1,447 @@
<?php
/**
* Export und Import saemtlicher Theme-Einstellungen
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// =============================================================================
// 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 )
) );
}
+513
View File
@@ -0,0 +1,513 @@
<?php
/**
* Team-Modul: Custom Post Type, Verwaltungsseite und AJAX-Handler
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// =========================================================================
// 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>
<button id="save-order-btn" class="button button-primary" style="margin-top:16px;float:right;">Reihenfolge speichern</button>
<div id="order-save-msg" style="margin-top:16px;float:left;color:green;display:none;">Reihenfolge gespeichert!</div>
<div style="clear:both;"></div>
</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); });
// Reihenfolge speichern
$('#save-order-btn').on('click', function() {
var ids = [];
$('#team-list-body tr').each(function(){ ids.push($(this).data('id')); });
var btn = $(this);
btn.prop('disabled', true).text('Speichern...');
$('#order-save-msg').hide();
$.post(ajaxurl, {
action: 'mm_save_team_order',
ids: ids,
nonce: '<?php echo wp_create_nonce( 'mm_team_nonce' ); ?>'
}, function(r) {
btn.prop('disabled', false).text('Reihenfolge speichern');
if(r.success){
$('#order-save-msg').fadeIn().delay(1200).fadeOut();
} else {
alert('Fehler beim Speichern der Reihenfolge!');
}
});
});
});
</script>
<?php
}
// AJAX-Handler für Reihenfolge speichern
add_action( 'wp_ajax_mm_save_team_order', 'handle_mm_save_team_order' );
function handle_mm_save_team_order() {
check_ajax_referer( 'mm_team_nonce', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) wp_send_json_error( 'Keine Berechtigung' );
if ( empty( $_POST['ids'] ) ) {
wp_send_json_error( 'Keine IDs' );
}
$ids = is_array($_POST['ids']) ? $_POST['ids'] : explode(',', $_POST['ids']);
$ids = array_map('intval', $ids);
$pos = 0;
foreach ( $ids as $id ) {
wp_update_post( array( 'ID' => $id, 'menu_order' => $pos ) );
$pos++;
}
wp_send_json_success();
}
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;
// BUG-FIX: locate_template() statt get_template_directory() so wird ein
// archive-team.php im Child-Theme berücksichtigt.
$is_team_page = false;
if ( is_post_type_archive( 'team_member' ) ) {
$is_team_page = true;
} elseif ( is_page() ) {
$obj = get_queried_object();
$is_team_page = ( $obj && $obj->post_name === 'team' );
}
if ( $is_team_page ) {
$t = locate_template( 'archive-team.php' );
if ( $t ) return $t;
}
return $template;
}
add_filter( 'template_include', 'load_team_page_template' );
// Doppelte team_customize_register entfernt ist jetzt in inc/customizer.php
+267
View File
@@ -0,0 +1,267 @@
<?php
/**
* Theme-Setup, lokale Schriften und Einbindung von Styles/Skripten
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// 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' );
// =========================================================================
// LOKALE SCHRIFTEN (DSGVO)
// =========================================================================
// Schriften liegen unter /fonts/<slug>/font.css samt woff2-Dateien und werden
// lokal ausgeliefert. Dadurch wird beim Seitenaufruf keine Verbindung zu
// fonts.googleapis.com / fonts.gstatic.com aufgebaut und keine Besucher-IP
// an Google übertragen.
/**
* Wandelt einen Schriftnamen in den Ordnernamen unter /fonts/ um.
* "Open Sans" -> "open-sans"
*/
function mm_font_slug( $font_name ) {
return strtolower( str_replace( ' ', '-', trim( $font_name ) ) );
}
/**
* Bindet eine lokal gehostete Schrift ein.
*
* Das Handle wird aus dem Schriftnamen abgeleitet ("Open Sans" -> mm-font-open-sans).
* Dadurch wird dieselbe Schrift automatisch nur einmal eingebunden, auch wenn sie
* an mehreren Stellen angefordert wird (z. B. Basis- und Slider-Schrift beide Raleway).
*
* @param string $font_name Schriftname, z. B. "Open Sans".
* @return bool True, wenn die Schrift lokal vorhanden war und eingebunden wurde.
*/
function mm_enqueue_local_font( $font_name ) {
if ( empty( $font_name ) ) {
return false;
}
$slug = mm_font_slug( $font_name );
$rel = '/fonts/' . $slug . '/font.css';
$path = get_template_directory() . $rel;
if ( ! file_exists( $path ) ) {
return false;
}
wp_enqueue_style( 'mm-font-' . $slug, get_template_directory_uri() . $rel, array(), filemtime( $path ) );
return true;
}
// === Styles & Scripts laden ===
function minecraft_modern_scripts() {
// Haupt-Stylesheet des Parent-Themes.
// Bewusst get_template_directory_uri() statt get_stylesheet_uri(): Bei aktivem
// Child-Theme zeigt get_stylesheet_uri() auf das Child. Das Child-Theme belegt
// dasselbe Handle mit der Parent-Datei zeigten beide auf unterschiedliche
// Dateien, entschiede die Aufrufreihenfolge darüber, welches CSS geladen wird
// (WordPress ignoriert den zweiten Aufruf eines bereits registrierten Handles).
// So liefern beide Seiten dieselbe Datei und die Reihenfolge ist egal.
wp_enqueue_style(
'minecraft-modern-style',
get_template_directory_uri() . '/style.css',
array(),
filemtime( get_template_directory() . '/style.css' )
);
// Bei aktivem Child-Theme dessen style.css danach laden, damit das Theme auch
// dann vollständig ist, wenn das Child keine eigene Enqueue-Funktion mitbringt.
if ( is_child_theme() && file_exists( get_stylesheet_directory() . '/style.css' ) ) {
wp_enqueue_style(
'minecraft-modern-child-style',
get_stylesheet_uri(),
array( 'minecraft-modern-style' ),
filemtime( get_stylesheet_directory() . '/style.css' )
);
}
// Basis-Schrift des Themes (lokal, siehe mm_enqueue_local_font)
mm_enqueue_local_font( 'Raleway' );
// Font Awesome (lokal statt cdnjs) vorher als fester <link> in header.php
wp_enqueue_style(
'font-awesome',
get_template_directory_uri() . '/css/fontawesome.min.css',
array(),
filemtime( get_template_directory() . '/css/fontawesome.min.css' )
);
// Swiper.js CSS (lokal statt CDN)
wp_enqueue_style(
'swiper-css',
get_template_directory_uri() . '/css/swiper-bundle.min.css',
array(),
filemtime( get_template_directory() . '/css/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(),
filemtime( get_template_directory() . '/js/navigation.js' ),
true
);
// Swiper.js (lokal statt CDN)
wp_enqueue_script(
'swiper-js',
get_template_directory_uri() . '/js/swiper-bundle.min.js',
array(),
filemtime( get_template_directory() . '/js/swiper-bundle.min.js' ),
true
);
// Slider-Init
wp_enqueue_script(
'minecraft-modern-slider-script',
get_template_directory_uri() . '/js/slider-init.js',
array( 'swiper-js' ),
filemtime( get_template_directory() . '/js/slider-init.js' ),
true
);
// Theme Toggle
wp_enqueue_script(
'theme-toggle-script',
get_template_directory_uri() . '/js/theme-toggle.js',
array(),
filemtime( get_template_directory() . '/js/theme-toggle.js' ),
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(),
filemtime( get_template_directory() . '/js/faq-accordion.js' ),
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 );
// =========================================================================
// PERFORMANCE: WordPress-Emoji-Ersetzung abschalten
// =========================================================================
// WordPress prüft per JavaScript, ob der Browser den neuesten Emoji-Satz
// kennt. Fällt der Test negativ aus (unter Windows 10 häufig der Fall),
// werden ALLE Emoji im Seiteninhalt durch SVG-Grafiken von s.w.org ersetzt.
// Auf dieser Seite waren das 9 externe Downloads mit rund 13 Sekunden
// Gesamtladezeit ausgelöst durch die Emoji in der Ankündigungs-Leiste und
// den Assistent-Buttons.
//
// Ohne das Skript stellt der Browser die Emoji als ganz normale Schriftzeichen
// dar; sie bleiben also sichtbar, es entfällt nur der externe Bilder-Download.
// Zum Rückgängigmachen genügt es, die folgende Funktion samt Hooks zu löschen.
function minecraft_modern_disable_emoji_scripts() {
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
remove_action( 'wp_print_styles', 'print_emoji_styles' );
remove_action( 'admin_print_styles', 'print_emoji_styles' );
remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
}
add_action( 'init', 'minecraft_modern_disable_emoji_scripts' );
/**
* Entfernt zusätzlich das Emoji-Skript aus dem TinyMCE-Editor.
*/
function minecraft_modern_disable_emoji_tinymce( $plugins ) {
return is_array( $plugins ) ? array_diff( $plugins, array( 'wpemoji' ) ) : array();
}
add_filter( 'tiny_mce_plugins', 'minecraft_modern_disable_emoji_tinymce' );
/**
* Verhindert den DNS-Prefetch auf s.w.org, den WordPress sonst zusätzlich setzt.
*/
function minecraft_modern_remove_emoji_dns_prefetch( $urls, $relation_type ) {
if ( 'dns-prefetch' === $relation_type ) {
$emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' );
$urls = array_diff( $urls, array( $emoji_svg_url ) );
}
return $urls;
}
add_filter( 'wp_resource_hints', 'minecraft_modern_remove_emoji_dns_prefetch', 10, 2 );
File diff suppressed because it is too large Load Diff
+129
View File
@@ -0,0 +1,129 @@
<?php
/**
* Registrierung der Footer- und Startseiten-Widget-Bereiche
*
* Ausgelagert aus functions.php.
*
* @package Minecraft-Modern-Theme
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// === 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>';
}
}
}
@@ -0,0 +1,116 @@
/**
* Verwaltungsseite "Menü-Links": Zeilen hinzufügen, entfernen,
* Icon-Vorschau aktualisieren.
*/
( function () {
'use strict';
/** Feldnamen durchnummerieren, damit PHP ein lückenloses Array erhält. */
function neuNummerieren( liste ) {
var feld = liste.getAttribute( 'data-mm-liste' );
if ( ! feld ) {
return;
}
var muster = new RegExp( feld.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ) + '\\[[^\\]]*\\]' );
liste.querySelectorAll( ':scope > .mm-liste-zeile' ).forEach( function ( zeile, i ) {
zeile.querySelectorAll( '[name^="' + feld + '["]' ).forEach( function ( eingabe ) {
eingabe.name = eingabe.name.replace( muster, feld + '[' + i + ']' );
} );
} );
}
/** Vorschausymbol links in der Zeile an das Icon-Feld angleichen. */
function vorschauAktualisieren( zeile ) {
var feld = zeile.querySelector( '.mm-icon-feld' );
var bild = zeile.querySelector( '.mm-vorschau' );
if ( ! feld || ! bild ) {
return;
}
var klasse = feld.value.trim() || 'dashicons-admin-links';
bild.className = 'mm-vorschau dashicons ' + klasse;
}
// --- Zeile hinzufügen ---
document.addEventListener( 'click', function ( e ) {
var knopf = e.target.closest( '.mm-liste-hinzufuegen' );
if ( ! knopf ) {
return;
}
e.preventDefault();
var feld = knopf.getAttribute( 'data-ziel' );
var liste = document.querySelector( '.mm-liste[data-mm-liste="' + feld + '"]' );
var vorlage = document.querySelector( '.mm-liste-vorlage[data-fuer="' + feld + '"]' );
if ( ! liste || ! vorlage ) {
return;
}
var huelle = document.createElement( 'div' );
huelle.innerHTML = vorlage.innerHTML.replace( /__INDEX__/g, String( Date.now() ) ).trim();
var zeile = huelle.querySelector( '.mm-liste-zeile' );
if ( ! zeile ) {
return;
}
liste.appendChild( zeile );
neuNummerieren( liste );
var hinweis = document.querySelector( '.mm-leer' );
if ( hinweis ) {
hinweis.remove();
}
var erstes = zeile.querySelector( 'input' );
if ( erstes ) {
erstes.focus();
}
} );
// --- Zeile entfernen ---
document.addEventListener( 'click', function ( e ) {
var knopf = e.target.closest( '.mm-liste-entfernen' );
if ( ! knopf ) {
return;
}
e.preventDefault();
var zeile = knopf.closest( '.mm-liste-zeile' );
var liste = zeile ? zeile.closest( '.mm-liste' ) : null;
if ( ! zeile || ! liste ) {
return;
}
zeile.remove();
neuNummerieren( liste );
} );
// --- Icon-Vorschau ---
document.addEventListener( 'input', function ( e ) {
if ( e.target.classList.contains( 'mm-icon-feld' ) ) {
var zeile = e.target.closest( '.mm-liste-zeile' );
if ( zeile ) {
vorschauAktualisieren( zeile );
}
}
} );
// Klick auf ein Beispielsymbol trägt die Klasse in die letzte Zeile ein
document.addEventListener( 'click', function ( e ) {
var code = e.target.closest( '.mm-icon-liste code' );
if ( ! code ) {
return;
}
var zeilen = document.querySelectorAll( '.mm-liste-zeile' );
if ( ! zeilen.length ) {
return;
}
var letzte = zeilen[ zeilen.length - 1 ];
var feld = letzte.querySelector( '.mm-icon-feld' );
if ( feld ) {
feld.value = code.textContent.trim();
vorschauAktualisieren( letzte );
feld.focus();
}
} );
} )();