Files
bluesky-theme/functions.php
2026-05-28 20:22:02 +02:00

1583 lines
72 KiB
PHP

<?php
/**
* BlueSky MC Theme - functions.php
* Vollständiges Backend - alles über WordPress Admin bearbeitbar
*/
defined('ABSPATH') || exit;
// ============================================================
// THEME SETUP
// ============================================================
function bluesky_setup() {
add_theme_support('title-tag');
add_theme_support('custom-logo', ['height'=>40,'width'=>40,'flex-height'=>true,'flex-width'=>true]);
add_theme_support('post-thumbnails');
add_theme_support('html5', ['search-form','comment-form','comment-list','gallery','caption']);
load_theme_textdomain('bluesky-mc', get_template_directory() . '/languages');
register_nav_menus(['primary' => 'Hauptmenü']);
}
add_action('after_setup_theme', 'bluesky_setup');
// ============================================================
// ENQUEUE
// ============================================================
function bluesky_enqueue() {
wp_enqueue_style('bluesky-style', get_stylesheet_uri(), [], '2.0.0');
// Dynamic CSS-Variablen aus Backend
$main_color = get_option('bluesky_color_main', '#39BEFF');
$bg_color = get_option('bluesky_color_bg', '#24272B');
$desc_color = get_option('bluesky_color_desc', '#D2D0D0');
$green_color= get_option('bluesky_color_green', '#4AFF6B');
$red_color = get_option('bluesky_color_red', '#FF7C7C');
$custom_css = "
:root {
--main-color: {$main_color};
--background-color: {$bg_color};
--description-color: {$desc_color};
--green-color: {$green_color};
--red-color: {$red_color};
--copy-ip-button-background: " . bluesky_hex_to_rgba($main_color, 0.7) . ";
--stat-icon-background-2: " . bluesky_hex_to_rgba($main_color, 0.5) . ";
--ip-copied-background: " . bluesky_hex_to_rgba($green_color, 0.17) . ";
--warning-background: " . bluesky_hex_to_rgba($red_color, 0.17) . ";
}
html, body { background-color: {$bg_color} !important; }
";
wp_add_inline_style('bluesky-style', $custom_css);
wp_enqueue_script('bluesky-main', get_template_directory_uri() . '/js/main.js', [], '2.0.4', true);
wp_localize_script('bluesky-main', 'BlueSkyConfig', [
'serverIp' => get_option('bluesky_server_ip', 'mc.example.com'),
'discordServerId' => get_option('bluesky_discord_server_id', ''),
'skinType' => get_option('bluesky_skin_type', 'bust'),
'teamData' => bluesky_get_team_data_for_js(),
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('bluesky_nonce'),
]);
}
add_action('wp_enqueue_scripts', 'bluesky_enqueue');
// Helper: hex to rgba
function bluesky_hex_to_rgba($hex, $alpha = 1) {
$hex = ltrim($hex, '#');
if (strlen($hex) === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
list($r,$g,$b) = array_map('hexdec', str_split($hex, 2));
return "rgba($r,$g,$b,$alpha)";
}
// ============================================================
// CUSTOM POST TYPES
// ============================================================
function bluesky_register_post_types() {
$types = [
'bluesky_team' => ['label'=>'Team Mitglieder','icon'=>'dashicons-groups','pos'=>25,'public'=>false,'supports'=>['title','thumbnail']],
'bluesky_wiki' => ['label'=>'Wiki Artikel','icon'=>'dashicons-book-alt','pos'=>26,'public'=>true,'supports'=>['title','editor','thumbnail'],'archive'=>true,'slug'=>'wiki-artikel'],
'bluesky_rule' => ['label'=>'Server Regeln','icon'=>'dashicons-shield','pos'=>27,'public'=>false,'supports'=>['title']],
'bluesky_faq' => ['label'=>'FAQs','icon'=>'dashicons-editor-help','pos'=>28,'public'=>false,'supports'=>['title','editor']],
'bluesky_vote' => ['label'=>'Vote Links','icon'=>'dashicons-star-filled','pos'=>29,'public'=>false,'supports'=>['title']],
'bluesky_gamemode' => ['label'=>'Spielmodi','icon'=>'dashicons-games','pos'=>30,'public'=>true,'supports'=>['title','editor','thumbnail']],
];
foreach ($types as $key => $t) {
$args = [
'label' => $t['label'],
'public' => $t['public'],
'show_ui' => true,
'show_in_menu' => true,
'menu_icon' => $t['icon'],
'menu_position'=> $t['pos'],
'supports' => $t['supports'],
'show_in_rest' => true,
];
if (!empty($t['archive'])) $args['has_archive'] = true;
if (!empty($t['slug'])) $args['rewrite'] = ['slug' => $t['slug']];
register_post_type($key, $args);
}
}
add_action('init', 'bluesky_register_post_types');
// Rule entries use a custom editor layout, not Gutenberg.
add_filter('use_block_editor_for_post_type', function($use_block_editor, $post_type) {
if ($post_type === 'bluesky_rule') return false;
return $use_block_editor;
}, 10, 2);
// ============================================================
// TAXONOMIES
// ============================================================
function bluesky_register_taxonomies() {
register_taxonomy('team_rank', 'bluesky_team', ['label'=>'Ränge','hierarchical'=>false,'show_in_rest'=>true,'rewrite'=>false,'show_admin_column'=>true]);
register_taxonomy('wiki_category', 'bluesky_wiki', ['label'=>'Wiki Kategorien','hierarchical'=>true,'show_in_rest'=>true,'rewrite'=>['slug'=>'wiki-kategorie'],'show_admin_column'=>true]);
register_taxonomy('rule_category', 'bluesky_rule', ['label'=>'Regelkategorien','hierarchical'=>true,'show_in_rest'=>true,'rewrite'=>false,'show_admin_column'=>true]);
}
add_action('init', 'bluesky_register_taxonomies');
// ============================================================
// META BOXES
// ============================================================
function bluesky_add_meta_boxes() {
add_meta_box('bluesky_team_meta', 'Mitglied Details', 'bluesky_team_meta_cb', 'bluesky_team', 'normal', 'high');
add_meta_box('bluesky_vote_meta', 'Vote Link Details', 'bluesky_vote_meta_cb', 'bluesky_vote', 'normal', 'high');
add_meta_box('bluesky_gamemode_meta', 'Spielmodus Details', 'bluesky_gamemode_meta_cb', 'bluesky_gamemode', 'normal', 'high');
add_meta_box('bluesky_rule_meta', 'Regel Details', 'bluesky_rule_meta_cb', 'bluesky_rule', 'normal', 'high');
}
add_action('add_meta_boxes', 'bluesky_add_meta_boxes');
function bluesky_team_meta_cb($post) {
wp_nonce_field('bluesky_team_nonce_action', 'bluesky_team_nonce');
$f = fn($k) => get_post_meta($post->ID, "_bluesky_$k", true);
$skin_preview = $f('skin_url') ?: ('https://crafthead.net/bust/' . ($f('in_game_name') ?: 'ec561538f3fd461daff5086b22154bce') . '/128');
$rank_empty = empty($f('rank'));
?>
<style>.bs-meta td{padding:8px 5px;}.bs-meta input,.bs-meta select{width:100%;max-width:400px;} .skin-preview{width:100px;border-radius:5px;image-rendering:pixelated;}</style>
<?php if ($rank_empty): ?>
<div class="notice notice-warning inline" style="margin:10px 0"><p>⚠️ <strong>Rang ist leer!</strong> Bitte Rang eintragen und speichern — sonst wird "—" auf der Team-Seite angezeigt.</p></div>
<?php endif; ?>
<table class="form-table bs-meta">
<tr><th>In-Game Name *</th><td><input type="text" name="bluesky_in_game_name" value="<?php echo esc_attr($f('in_game_name')); ?>" placeholder="z.B. MuckiDEE"><p class="description">Minecraft Username — Skin wird automatisch geladen (Original-Account vorausgesetzt).</p></td><td rowspan="6" style="vertical-align:top;padding-left:20px"><img id="skin_preview_img" src="<?php echo esc_url($skin_preview); ?>" class="skin-preview"><br><small style="color:#aaa">Skin-Vorschau</small></td></tr>
<tr><th><label style="color:<?php echo $rank_empty ? '#ffb900' : 'inherit'; ?>">Rang *<?php echo $rank_empty ? ' ⚠️' : ''; ?></label></th><td><input type="text" name="bluesky_rank" value="<?php echo esc_attr($f('rank')); ?>" placeholder="z.B. Owner, Admin, Moderator" style="<?php echo $rank_empty ? 'border-color:#ffb900;' : ''; ?>"></td></tr>
<tr><th>Rangfarbe</th><td><input type="color" name="bluesky_rank_color_hex" value="<?php echo esc_attr(bluesky_rgba_to_hex($f('rank_color')) ?: '#ff0000'); ?>" style="width:60px;height:40px;border:none;cursor:pointer"> <input type="text" name="bluesky_rank_color" value="<?php echo esc_attr($f('rank_color') ?: 'rgba(156, 0, 0, 1)'); ?>" style="max-width:280px" placeholder="rgba(156, 0, 0, 1)"><p class="description">Kann rgba() oder #hex sein.</p></td></tr>
<tr><th>Gruppe</th><td><input type="text" name="bluesky_team_group" value="<?php echo esc_attr($f('team_group') ?: 'Team'); ?>" placeholder="Team, special, ..."><p class="description">Bestimmt die Gruppenüberschrift auf der Team-Seite.</p></td></tr>
<tr><th>Eigene Skin-URL</th><td><input type="url" name="bluesky_skin_url" value="<?php echo esc_url($f('skin_url')); ?>" placeholder="Leer lassen für Auto-Load (Original-Account)"><p class="description">Nur für Cracked/Offline-Accounts nötig.</p></td></tr>
<tr><th>Reihenfolge</th><td><input type="number" name="bluesky_sort_order" value="<?php echo esc_attr($f('sort_order') ?: 0); ?>" style="max-width:80px" min="0"></td></tr>
</table>
<script>
document.querySelector('[name="bluesky_rank_color_hex"]').addEventListener('input', function(){
document.querySelector('[name="bluesky_rank_color"]').value = this.value;
});
document.querySelector('[name="bluesky_in_game_name"]').addEventListener('change', function(){
if(!document.querySelector('[name="bluesky_skin_url"]').value){
document.getElementById('skin_preview_img').src = 'https://crafthead.net/bust/' + this.value + '/128';
}
});
</script>
<?php
}
function bluesky_vote_meta_cb($post) {
wp_nonce_field('bluesky_vote_nonce_action', 'bluesky_vote_nonce');
$url = get_post_meta($post->ID, '_bluesky_vote_url', true);
$desc = get_post_meta($post->ID, '_bluesky_vote_description', true);
?>
<table class="form-table">
<tr><th>Vote URL *</th><td><input type="url" name="bluesky_vote_url" value="<?php echo esc_url($url); ?>" class="large-text" placeholder="https://minecraft-server.eu/vote/..."></td></tr>
<tr><th>Beschreibung</th><td><input type="text" name="bluesky_vote_description" value="<?php echo esc_attr($desc ?: 'Klicke hier um jetzt zu Voten.'); ?>" class="large-text"></td></tr>
</table>
<?php
}
function bluesky_gamemode_meta_cb($post) {
wp_nonce_field('bluesky_gamemode_nonce_action', 'bluesky_gamemode_nonce');
$services = get_post_meta($post->ID, '_bluesky_gamemode_services', true);
?>
<table class="form-table">
<tr><th>Features (kommagetrennt)</th><td><input type="text" name="bluesky_gamemode_services" value="<?php echo esc_attr($services); ?>" class="large-text" placeholder="Jobs, Pet, Shops, und mehr"><p class="description">Werden als Liste unter dem Text angezeigt.</p></td></tr>
</table>
<?php
}
function bluesky_rule_meta_cb($post) {
wp_nonce_field('bluesky_rule_nonce_action', 'bluesky_rule_nonce');
$section_id = get_post_meta($post->ID, '_bluesky_rule_section_id', true);
$sort_order = get_post_meta($post->ID, '_bluesky_rule_sort_order', true);
$rule_items = get_post_meta($post->ID, '_bluesky_rule_items', true);
?>
<div class="bluesky-rule-editor">
<div class="bluesky-rule-editor__intro">
<strong>Eigener Regeln-Editor</strong>
<p>Eine Regel pro Zeile eintragen. Der WordPress-Inhaltseditor wird für Regeln nicht genutzt.</p>
</div>
<div class="bluesky-rule-editor__field">
<label for="bluesky_rule_items"><strong>Regeln (eine Zeile je Regel)</strong></label>
<textarea id="bluesky_rule_items" name="bluesky_rule_items" rows="10" class="large-text" placeholder="Beleidigungen sind verboten.&#10;Cheating ist verboten."><?php echo esc_textarea($rule_items); ?></textarea>
</div>
<div class="bluesky-rule-editor__grid">
<div class="bluesky-rule-editor__field">
<label for="bluesky_rule_section_id"><strong>Abschnitts-ID (Anker)</strong></label>
<input id="bluesky_rule_section_id" type="text" name="bluesky_rule_section_id" value="<?php echo esc_attr($section_id); ?>" class="regular-text" placeholder="regel-1">
<p class="description">Für Verlinkung wie /regeln/#regel-1</p>
</div>
<div class="bluesky-rule-editor__field">
<label for="bluesky_rule_sort_order"><strong>Reihenfolge</strong></label>
<input id="bluesky_rule_sort_order" type="number" name="bluesky_rule_sort_order" value="<?php echo esc_attr($sort_order ?: 0); ?>" class="small-text" min="0">
</div>
</div>
</div>
<?php
}
function bluesky_rule_editor_admin_styles() {
$screen = get_current_screen();
if (!$screen || $screen->post_type !== 'bluesky_rule') return;
?>
<style>
body.post-type-bluesky_rule #post-body-content,
body.post-type-bluesky_rule .edit-form-section {
margin-bottom: 14px;
}
body.post-type-bluesky_rule #titlediv,
body.post-type-bluesky_rule #poststuff .postbox {
border: 1px solid #dcdcde;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
}
body.post-type-bluesky_rule #titlediv {
padding: 14px;
background: #ffffff;
}
body.post-type-bluesky_rule #titlediv #title {
border-radius: 8px;
border-color: #c3c4c7;
min-height: 46px;
padding: 10px 14px;
font-size: 18px;
}
body.post-type-bluesky_rule #poststuff .postbox .hndle {
border-bottom: 1px solid #e6e6e8;
background: #f9f9fb;
font-size: 14px;
font-weight: 700;
}
body.post-type-bluesky_rule #poststuff .inside {
margin: 0;
padding: 16px;
background: #fff;
}
body.post-type-bluesky_rule .bluesky-rule-editor__intro {
background: #f6f9fc;
border: 1px solid #d4e2ef;
border-left: 4px solid #2271b1;
border-radius: 8px;
padding: 10px 12px;
margin-bottom: 14px;
}
body.post-type-bluesky_rule .bluesky-rule-editor__intro p {
margin: 6px 0 0;
color: #50575e;
}
body.post-type-bluesky_rule .bluesky-rule-editor__field {
margin-bottom: 12px;
}
body.post-type-bluesky_rule .bluesky-rule-editor__field label {
display: block;
margin-bottom: 6px;
}
body.post-type-bluesky_rule .bluesky-rule-editor__field textarea {
width: 100%;
border-radius: 8px;
border-color: #c3c4c7;
font-family: Consolas, monospace;
line-height: 1.5;
padding: 10px;
}
body.post-type-bluesky_rule .bluesky-rule-editor__grid {
display: grid;
grid-template-columns: 1fr 180px;
gap: 12px;
align-items: end;
}
@media (max-width: 782px) {
body.post-type-bluesky_rule .bluesky-rule-editor__grid {
grid-template-columns: 1fr;
}
}
</style>
<?php
}
add_action('admin_head-post.php', 'bluesky_rule_editor_admin_styles');
add_action('admin_head-post-new.php', 'bluesky_rule_editor_admin_styles');
// Save Meta
function bluesky_save_team_meta($post_id) {
if (!isset($_POST['bluesky_team_nonce']) || !wp_verify_nonce($_POST['bluesky_team_nonce'], 'bluesky_team_nonce_action')) return;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
foreach (['in_game_name','rank','rank_color','skin_url','team_group','sort_order'] as $f) {
if (isset($_POST["bluesky_$f"])) update_post_meta($post_id, "_bluesky_$f", sanitize_text_field($_POST["bluesky_$f"]));
}
}
add_action('save_post_bluesky_team', 'bluesky_save_team_meta');
function bluesky_save_vote_meta($post_id) {
if (!isset($_POST['bluesky_vote_nonce']) || !wp_verify_nonce($_POST['bluesky_vote_nonce'], 'bluesky_vote_nonce_action')) return;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
if (isset($_POST['bluesky_vote_url'])) update_post_meta($post_id, '_bluesky_vote_url', esc_url_raw($_POST['bluesky_vote_url']));
if (isset($_POST['bluesky_vote_description'])) update_post_meta($post_id, '_bluesky_vote_description', sanitize_text_field($_POST['bluesky_vote_description']));
}
add_action('save_post_bluesky_vote', 'bluesky_save_vote_meta');
function bluesky_save_gamemode_meta($post_id) {
if (!isset($_POST['bluesky_gamemode_nonce']) || !wp_verify_nonce($_POST['bluesky_gamemode_nonce'], 'bluesky_gamemode_nonce_action')) return;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
if (isset($_POST['bluesky_gamemode_services'])) update_post_meta($post_id, '_bluesky_gamemode_services', sanitize_text_field($_POST['bluesky_gamemode_services']));
}
add_action('save_post_bluesky_gamemode', 'bluesky_save_gamemode_meta');
function bluesky_save_rule_meta($post_id) {
if (!isset($_POST['bluesky_rule_nonce']) || !wp_verify_nonce($_POST['bluesky_rule_nonce'], 'bluesky_rule_nonce_action')) return;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
if (isset($_POST['bluesky_rule_items'])) {
$normalized = preg_replace("/\r\n?|\n/", "\n", wp_unslash($_POST['bluesky_rule_items']));
update_post_meta($post_id, '_bluesky_rule_items', sanitize_textarea_field($normalized));
}
if (isset($_POST['bluesky_rule_section_id'])) update_post_meta($post_id, '_bluesky_rule_section_id', sanitize_text_field($_POST['bluesky_rule_section_id']));
if (isset($_POST['bluesky_rule_sort_order'])) update_post_meta($post_id, '_bluesky_rule_sort_order', intval($_POST['bluesky_rule_sort_order']));
}
add_action('save_post_bluesky_rule', 'bluesky_save_rule_meta');
// ============================================================
// ADMIN MENÜ
// ============================================================
function bluesky_admin_menu() {
add_menu_page('BlueSky MC', 'BlueSky MC', 'manage_options', 'bluesky-settings', 'bluesky_settings_page', 'dashicons-admin-site-alt3', 3);
add_submenu_page('bluesky-settings', 'Server & Allgemein', 'Server & Allgemein', 'manage_options', 'bluesky-settings', 'bluesky_settings_page');
add_submenu_page('bluesky-settings', 'Design & Farben', 'Design & Farben', 'manage_options', 'bluesky-design', 'bluesky_design_page');
add_submenu_page('bluesky-settings', 'Navbar & Footer', 'Navbar & Footer', 'manage_options', 'bluesky-navbar', 'bluesky_navbar_page');
add_submenu_page('bluesky-settings', 'Homepage Texte', 'Homepage Texte', 'manage_options', 'bluesky-homepage', 'bluesky_homepage_page');
add_submenu_page('bluesky-settings', 'Bilder & Medien', 'Bilder & Medien', 'manage_options', 'bluesky-images', 'bluesky_images_page');
add_submenu_page('bluesky-settings', 'Impressum & Datenschutz','Impressum', 'manage_options', 'bluesky-impressum', 'bluesky_impressum_page');
}
add_action('admin_menu', 'bluesky_admin_menu');
// Enqueue Media Uploader on our pages
function bluesky_admin_scripts($hook) {
if (strpos($hook, 'bluesky') !== false) {
wp_enqueue_media();
wp_enqueue_style('wp-color-picker');
wp_enqueue_script('wp-color-picker');
wp_add_inline_style('wp-color-picker', '
.bluesky-admin-subtitle {
color: #50575e;
margin: 4px 0 16px;
font-size: 13px;
}
.bluesky-admin-intro {
background: #fff;
border: 1px solid #dcdcde;
border-left: 4px solid #2271b1;
border-radius: 8px;
padding: 14px 16px;
margin: 0 0 18px;
box-shadow: 0 1px 1px rgba(0,0,0,0.03);
}
.bluesky-admin-intro h2 {
margin: 0 0 8px;
font-size: 17px;
line-height: 1.3;
}
.bluesky-admin-intro p {
margin: 0;
color: #2c3338;
}
.bluesky-admin-steps {
margin: 10px 0 0 20px;
}
.bluesky-admin-steps li {
margin: 5px 0;
color: #2c3338;
}
.bluesky-admin-form {
background: #fff;
border: 1px solid #dcdcde;
border-radius: 8px;
padding: 10px 18px 18px;
box-shadow: 0 1px 1px rgba(0,0,0,0.03);
}
.bluesky-admin-form h2 {
margin-top: 24px;
padding-bottom: 8px;
border-bottom: 1px solid #dcdcde;
font-size: 17px;
}
.bluesky-admin-form h2:first-of-type {
margin-top: 6px;
}
.bluesky-admin-form .form-table th {
width: 240px;
font-weight: 600;
}
.bluesky-admin-form .form-table td .description {
margin-top: 6px;
color: #50575e;
}
.bluesky-admin-form .submit {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #dcdcde;
}
');
wp_add_inline_script('wp-color-picker', '
jQuery(document).ready(function($){
$(".bluesky-color").wpColorPicker();
$(".bluesky-upload-btn").on("click", function(e){
e.preventDefault();
var btn = $(this);
var target = btn.data("target");
var preview = btn.data("preview");
var frame = wp.media({title: "Bild auswählen", button:{text:"Auswählen"}, multiple:false});
frame.on("select", function(){
var att = frame.state().get("selection").first().toJSON();
$("#"+target).val(att.url);
if(preview) $("#"+preview).attr("src", att.url).show();
});
frame.open();
});
});');
}
}
add_action('admin_enqueue_scripts', 'bluesky_admin_scripts');
// ============================================================
// SETTINGS PAGES
// ============================================================
// --- Helper: save & header ---
function bluesky_save_options($nonce_action, $nonce_field, $fields, $url_fields = [], $wysiwyg_fields = []) {
if (!isset($_POST[$nonce_field]) || !wp_verify_nonce($_POST[$nonce_field], $nonce_action)) return false;
foreach ($fields as $f) if (isset($_POST[$f])) update_option($f, sanitize_text_field($_POST[$f]));
foreach ($url_fields as $f) if (isset($_POST[$f])) update_option($f, esc_url_raw($_POST[$f]));
foreach ($wysiwyg_fields as $f) if (isset($_POST[$f])) update_option($f, wp_kses_post($_POST[$f]));
return true;
}
function bluesky_admin_header($title, $icon = '🎮') {
echo '<div class="wrap"><h1 style="display:flex;align-items:center;gap:10px">' . $icon . ' ' . esc_html($title) . '</h1>';
echo '<p class="bluesky-admin-subtitle">BlueSky MC Theme — ' . esc_html($title) . '</p>';
}
function bluesky_admin_intro($headline, $description, $steps = []) {
echo '<div class="bluesky-admin-intro">';
echo '<h2>' . esc_html($headline) . '</h2>';
echo '<p>' . esc_html($description) . '</p>';
if (!empty($steps)) {
echo '<ol class="bluesky-admin-steps">';
foreach ($steps as $step) {
echo '<li>' . esc_html($step) . '</li>';
}
echo '</ol>';
}
echo '</div>';
}
// 1. Server & Allgemein
function bluesky_settings_page() {
if (!current_user_can('manage_options')) wp_die('Kein Zugriff');
$saved = bluesky_save_options('bluesky_save_settings', 'bluesky_settings_nonce',
['bluesky_server_name','bluesky_server_ip','bluesky_discord_server_id','bluesky_skin_type'],
['bluesky_discord_link']);
if ($saved) echo '<div class="updated notice is-dismissible"><p>✅ Gespeichert!</p></div>';
bluesky_admin_header('Server & Allgemein', '🖥️');
bluesky_admin_intro(
'Schnellstart',
'Diese Seite steuert die wichtigsten Basisdaten des Servers.',
[
'Server Name und IP eintragen',
'Discord-ID nur setzen, wenn das Discord-Widget aktiv ist',
'Speichern klicken und Frontend neu laden'
]
);
$skin_types = ['full','bust','head','face','front','frontFull','skin'];
?>
<form method="post" class="bluesky-admin-form">
<?php wp_nonce_field('bluesky_save_settings', 'bluesky_settings_nonce'); ?>
<table class="form-table">
<tr><th>Server Name</th><td><input type="text" name="bluesky_server_name" value="<?php echo esc_attr(get_option('bluesky_server_name','BlueSky')); ?>" class="regular-text"><p class="description">Wird im Navbar, Footer und Titel angezeigt.</p></td></tr>
<tr><th>Server IP</th><td><input type="text" name="bluesky_server_ip" value="<?php echo esc_attr(get_option('bluesky_server_ip','mc.example.com')); ?>" class="regular-text"><p class="description">Für Copy-IP Button und Online-Spieler-Counter.</p></td></tr>
<tr><th>Discord Server ID</th><td><input type="text" name="bluesky_discord_server_id" value="<?php echo esc_attr(get_option('bluesky_discord_server_id','')); ?>" class="regular-text"><p class="description">Für Online-User-Anzeige. Discord Widget muss aktiviert sein (Server-Einstellungen → Widget).</p></td></tr>
<tr><th>Discord Einladungslink</th><td><input type="url" name="bluesky_discord_link" value="<?php echo esc_url(get_option('bluesky_discord_link','#')); ?>" class="large-text" placeholder="https://discord.gg/..."></td></tr>
<tr><th>Skin Darstellungstyp</th><td>
<select name="bluesky_skin_type"><?php foreach($skin_types as $t): ?>
<option value="<?php echo $t; ?>" <?php selected(get_option('bluesky_skin_type','bust'),$t); ?>><?php echo $t; ?></option>
<?php endforeach; ?></select>
<p class="description">Wie der Minecraft-Skin auf der Team-Seite aussieht.</p>
</td></tr>
</table>
<?php submit_button('Speichern'); ?>
</form></div>
<?php
}
// 2. Design & Farben
function bluesky_design_page() {
if (!current_user_can('manage_options')) wp_die('Kein Zugriff');
$saved = bluesky_save_options('bluesky_save_design', 'bluesky_design_nonce',
['bluesky_color_main','bluesky_color_bg','bluesky_color_desc','bluesky_color_green','bluesky_color_red','bluesky_custom_css']);
if ($saved) echo '<div class="updated notice is-dismissible"><p>✅ Farben & Design gespeichert!</p></div>';
bluesky_admin_header('Design & Farben', '🎨');
bluesky_admin_intro(
'Farben einfach einstellen',
'Passe zuerst nur die 5 Farben an. Eigenes CSS ist optional für Fortgeschrittene.',
[
'Hauptfarbe definieren',
'Hintergrund und Textfarbe prüfen',
'Dann optional eigenes CSS ergänzen'
]
);
$colors = [
'bluesky_color_main' => ['Hauptfarbe (Akzent)', '#39BEFF', 'Links, Buttons, Highlights'],
'bluesky_color_bg' => ['Hintergrundfarbe', '#24272B', 'Seiten-Hintergrund'],
'bluesky_color_desc' => ['Text / Beschreibungsfarbe','#D2D0D0', 'Normaler Fließtext'],
'bluesky_color_green' => ['Erfolgsfarbe (Grün)', '#4AFF6B', 'Bestätigungen, Online-Anzeige'],
'bluesky_color_red' => ['Warnfarbe (Rot)', '#FF7C7C', 'Warnungen, Fehler'],
];
?>
<form method="post" class="bluesky-admin-form">
<?php wp_nonce_field('bluesky_save_design', 'bluesky_design_nonce'); ?>
<h2>Farben</h2>
<table class="form-table">
<?php foreach($colors as $key => [$label, $default, $desc]): ?>
<tr>
<th><?php echo esc_html($label); ?></th>
<td>
<input type="text" name="<?php echo $key; ?>" value="<?php echo esc_attr(get_option($key, $default)); ?>" class="bluesky-color regular-text" data-default-color="<?php echo $default; ?>">
<p class="description"><?php echo esc_html($desc); ?></p>
</td>
</tr>
<?php endforeach; ?>
</table>
<h2>Eigenes CSS</h2>
<p class="description">Hier kannst du eigenes CSS hinzufügen das die Theme-Styles ergänzt oder überschreibt.</p>
<textarea name="bluesky_custom_css" rows="10" style="width:100%;font-family:monospace;background:#1e1e1e;color:#d4d4d4;padding:15px;border-radius:5px"><?php echo esc_textarea(get_option('bluesky_custom_css','')); ?></textarea>
<?php submit_button('Farben & Design speichern'); ?>
</form></div>
<?php
// Output custom CSS
$custom_css = get_option('bluesky_custom_css','');
if ($custom_css) add_action('wp_head', fn() => print("<style>$custom_css</style>"), 99);
}
// 3. Navbar & Footer
function bluesky_navbar_page() {
if (!current_user_can('manage_options')) wp_die('Kein Zugriff');
$saved = bluesky_save_options('bluesky_save_navbar', 'bluesky_navbar_nonce',
['bluesky_nav_home_label','bluesky_nav_wiki_label','bluesky_nav_rules_label','bluesky_nav_team_label',
'bluesky_nav_home_url','bluesky_nav_wiki_url','bluesky_nav_rules_url','bluesky_nav_team_url',
'bluesky_footer_copyright','bluesky_server_name'],
['bluesky_footer_impressum_url','bluesky_footer_datenschutz_url']);
if ($saved) echo '<div class="updated notice is-dismissible"><p>✅ Navbar & Footer gespeichert!</p></div>';
bluesky_admin_header('Navbar & Footer', '🔗');
bluesky_admin_intro(
'Navigation bearbeiten',
'Hier passt du Menütexte, Links und Footer-Beschriftungen an.',
[
'Label = sichtbarer Text',
'URL immer mit / beginnen (z.B. /wiki/)',
'Nach Änderungen speichern'
]
);
?>
<form method="post" class="bluesky-admin-form">
<?php wp_nonce_field('bluesky_save_navbar', 'bluesky_navbar_nonce'); ?>
<h2>Navbar Links</h2>
<table class="form-table">
<?php
$nav_items = [
'home' => 'Startseite',
'wiki' => 'Wiki',
'rules' => 'Regeln',
'team' => 'Team',
];
$defaults_label = ['home'=>'Home','wiki'=>'Wiki','rules'=>'Regeln','team'=>'Team'];
$defaults_url = ['home'=>'/', 'wiki'=>'/wiki/', 'rules'=>'/regeln/', 'team'=>'/team/'];
foreach ($nav_items as $key => $desc):
?>
<tr>
<th><?php echo esc_html($desc); ?></th>
<td>
Label: <input type="text" name="bluesky_nav_<?php echo $key; ?>_label" value="<?php echo esc_attr(get_option("bluesky_nav_{$key}_label", $defaults_label[$key])); ?>" style="width:120px">
&nbsp;&nbsp; URL: <input type="text" name="bluesky_nav_<?php echo $key; ?>_url" value="<?php echo esc_attr(get_option("bluesky_nav_{$key}_url", $defaults_url[$key])); ?>" style="width:200px">
</td>
</tr>
<?php endforeach; ?>
</table>
<h2>Logo / Server-Name in Navbar</h2>
<table class="form-table">
<tr><th>Server Name (Navbar)</th><td><input type="text" name="bluesky_server_name" value="<?php echo esc_attr(get_option('bluesky_server_name','BlueSky')); ?>" class="regular-text"><p class="description">Wird neben dem Logo angezeigt.</p></td></tr>
</table>
<h2>Footer</h2>
<table class="form-table">
<tr>
<th>Footer Version</th>
<td>
<input type="text" value="Code-gesteuert" class="regular-text" disabled>
<p class="description">Die Footer-Version bleibt fest im Code und kann hier nicht geaendert werden.</p>
</td>
</tr>
<tr>
<th>Impressum Link URL</th>
<td>
<input type="url" name="bluesky_footer_impressum_url" value="<?php echo esc_url(get_option('bluesky_footer_impressum_url', home_url('/impressum/#impressum'))); ?>" class="large-text" placeholder="https://... oder /impressum/#impressum">
</td>
</tr>
<tr>
<th>Datenschutz Link URL</th>
<td>
<input type="url" name="bluesky_footer_datenschutz_url" value="<?php echo esc_url(get_option('bluesky_footer_datenschutz_url', home_url('/impressum/#datenschutz'))); ?>" class="large-text" placeholder="https://... oder /impressum/#datenschutz">
</td>
</tr>
</table>
<?php submit_button('Navbar & Footer speichern'); ?>
</form></div>
<?php
}
// 4. Homepage Texte
function bluesky_homepage_page() {
if (!current_user_can('manage_options')) wp_die('Kein Zugriff');
$saved = bluesky_save_options('bluesky_save_homepage', 'bluesky_homepage_nonce',
['bluesky_header_subtitle','bluesky_header_description','bluesky_about_title1','bluesky_about_text1',
'bluesky_about_title2','bluesky_about_text2','bluesky_vote_description','bluesky_faq_description',
'bluesky_discord_section_text','bluesky_copy_ip_btn_label','bluesky_see_more_btn_label',
'bluesky_rules_warning_text','bluesky_header_tag']);
if ($saved) echo '<div class="updated notice is-dismissible"><p>✅ Homepage-Texte gespeichert!</p></div>';
bluesky_admin_header('Homepage Texte', '🏠');
bluesky_admin_intro(
'Texte nach Bereichen',
'Die Felder sind in Homepage-Blöcke aufgeteilt. Du kannst alles ohne Code anpassen.',
[
'Oben mit Header-Texten starten',
'Dann Über Uns und Discord bearbeiten',
'Zum Schluss speichern'
]
);
?>
<form method="post" class="bluesky-admin-form">
<?php wp_nonce_field('bluesky_save_homepage', 'bluesky_homepage_nonce'); ?>
<h2>Header</h2>
<table class="form-table">
<tr><th>Untertitel (über der IP)</th><td><input type="text" name="bluesky_header_subtitle" value="<?php echo esc_attr(get_option('bluesky_header_subtitle','Survival Minecraft Server')); ?>" class="large-text"></td></tr>
<tr><th>Beschreibungstext</th><td><textarea name="bluesky_header_description" rows="3" class="large-text"><?php echo esc_textarea(get_option('bluesky_header_description','Erlebe Survival, eigene Welten, Jobs, Haustiere und ein dynamisches Wirtschaftssystem.')); ?></textarea></td></tr>
<tr><th>"Copy IP" Button Text</th><td><input type="text" name="bluesky_copy_ip_btn_label" value="<?php echo esc_attr(get_option('bluesky_copy_ip_btn_label','Copy IP')); ?>" class="regular-text"></td></tr>
<tr><th>"Siehe mehr" Button Text</th><td><input type="text" name="bluesky_see_more_btn_label" value="<?php echo esc_attr(get_option('bluesky_see_more_btn_label','Siehe mehr')); ?>" class="regular-text"></td></tr>
</table>
<h2>Über Uns</h2>
<table class="form-table">
<tr><th>Abschnitt 1 — Titel</th><td><input type="text" name="bluesky_about_title1" value="<?php echo esc_attr(get_option('bluesky_about_title1','Wer wir sind?')); ?>" class="large-text"></td></tr>
<tr><th>Abschnitt 1 — Text</th><td><textarea name="bluesky_about_text1" rows="4" class="large-text"><?php echo esc_textarea(get_option('bluesky_about_text1','Wir sind ein engagiertes Team aus drei kreativen Köpfen, die Minecraft lieben!')); ?></textarea></td></tr>
<tr><th>Abschnitt 2 — Titel</th><td><input type="text" name="bluesky_about_title2" value="<?php echo esc_attr(get_option('bluesky_about_title2','Was macht BlueSky so besonders?')); ?>" class="large-text"></td></tr>
<tr><th>Abschnitt 2 — Text</th><td><textarea name="bluesky_about_text2" rows="4" class="large-text"><?php echo esc_textarea(get_option('bluesky_about_text2','BlueSky gibt dir die Freiheit, deine eigene Welt zu erstellen und zu erweitern.')); ?></textarea></td></tr>
</table>
<h2>Discord Sektion</h2>
<table class="form-table">
<tr><th>Text</th><td><textarea name="bluesky_discord_section_text" rows="3" class="large-text"><?php echo esc_textarea(get_option('bluesky_discord_section_text',"Du hast ein Problem oder möchtest mit anderen in Kontakt treten?\nTritt unserem Discord bei!")); ?></textarea><p class="description">Die Wörter "Problem" und "Discord" werden automatisch farbig hervorgehoben.</p></td></tr>
</table>
<h2>Vote Sektion</h2>
<table class="form-table">
<tr><th>Beschreibungstext</th><td><textarea name="bluesky_vote_description" rows="4" class="large-text"><?php echo esc_textarea(get_option('bluesky_vote_description','Durchs Voten erhältst du nicht nur wertvolle Belohnungen, sondern auch eine zeitbegrenzte Premium-Berechtigung mit zahlreichen Vorteilen!')); ?></textarea></td></tr>
</table>
<h2>FAQ Sektion</h2>
<table class="form-table">
<tr><th>Untertitel</th><td><input type="text" name="bluesky_faq_description" value="<?php echo esc_attr(get_option('bluesky_faq_description','Meist gestellte Fragen und Antworten')); ?>" class="large-text"></td></tr>
</table>
<h2>Regeln-Seite</h2>
<table class="form-table">
<tr><th>Warntext</th><td><textarea name="bluesky_rules_warning_text" rows="2" class="large-text"><?php echo esc_textarea(get_option('bluesky_rules_warning_text','Achtung: Bei einer Abwesenheit von mehr als 90 Tagen werden alle Spielerinformationen und Daten gelöscht.')); ?></textarea></td></tr>
</table>
<?php submit_button('Alle Texte speichern'); ?>
</form></div>
<?php
}
// 5. Bilder & Medien
function bluesky_images_page() {
if (!current_user_can('manage_options')) wp_die('Kein Zugriff');
$saved = bluesky_save_options('bluesky_save_images', 'bluesky_images_nonce',
[], ['bluesky_header_bg_url','bluesky_logo_url','bluesky_about_image_url','bluesky_navbar_logo_url']);
if ($saved) echo '<div class="updated notice is-dismissible"><p>✅ Bilder gespeichert!</p></div>';
bluesky_admin_header('Bilder & Medien', '🖼️');
bluesky_admin_intro(
'Bilder austauschen',
'Am einfachsten über die Mediathek wählen. Das Vorschaubild zeigt sofort das Ergebnis.',
[
'Bildfeld auswählen',
'Aus Mediathek wählen klicken',
'Bei Bedarf mit Zurücksetzen auf Standard zurück'
]
);
$images = [
'bluesky_header_bg_url' => ['Header Hintergrundbild', 'images/header-background.jpg', 'Großes Hintergrundbild im Hero-Bereich und Discord-Sektion'],
'bluesky_logo_url' => ['Server Logo (Header)', 'images/logo.png', 'Schwebendes Logo rechts im Header'],
'bluesky_navbar_logo_url' => ['Navbar Logo', 'images/logo.png', 'Kleines Logo links in der Navbar'],
'bluesky_about_image_url' => ['Über-Uns Bild', 'images/teambild.png', 'Bild rechts im "Über Uns" Bereich'],
];
?>
<form method="post" class="bluesky-admin-form">
<?php wp_nonce_field('bluesky_save_images', 'bluesky_images_nonce'); ?>
<table class="form-table">
<?php foreach ($images as $key => [$label, $default_file, $desc]):
$current = get_option($key, '');
$preview = $current ?: get_template_directory_uri() . '/' . $default_file;
$pid = sanitize_title($key);
?>
<tr>
<th><?php echo esc_html($label); ?></th>
<td>
<div style="display:flex;align-items:flex-start;gap:20px">
<img id="prev_<?php echo $pid; ?>" src="<?php echo esc_url($preview); ?>" style="max-width:200px;max-height:120px;border-radius:8px;border:2px solid #444;object-fit:cover">
<div>
<input type="url" id="<?php echo $pid; ?>" name="<?php echo $key; ?>" value="<?php echo esc_url($current); ?>" class="large-text" placeholder="URL oder über Button auswählen">
<br><br>
<button type="button" class="button bluesky-upload-btn" data-target="<?php echo $pid; ?>" data-preview="prev_<?php echo $pid; ?>">📁 Aus Mediathek wählen</button>
<button type="button" class="button" onclick="document.getElementById('<?php echo $pid;?>').value='';document.getElementById('prev_<?php echo $pid;?>').src='<?php echo get_template_directory_uri().'/'.$default_file;?>'">↩ Zurücksetzen</button>
<p class="description"><?php echo esc_html($desc); ?></p>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php submit_button('Bilder speichern'); ?>
</form></div>
<?php
}
// 6. Impressum
function bluesky_impressum_page() {
if (!current_user_can('manage_options')) wp_die('Kein Zugriff');
if (isset($_POST['bluesky_impressum_nonce']) && wp_verify_nonce($_POST['bluesky_impressum_nonce'], 'bluesky_save_impressum')) {
if (isset($_POST['bluesky_impressum_content'])) update_option('bluesky_impressum_content', wp_kses_post($_POST['bluesky_impressum_content']));
if (isset($_POST['bluesky_datenschutz_content'])) update_option('bluesky_datenschutz_content', wp_kses_post($_POST['bluesky_datenschutz_content']));
echo '<div class="updated notice is-dismissible"><p>✅ Gespeichert!</p></div>';
}
bluesky_admin_header('Impressum & Datenschutz', '📄');
bluesky_admin_intro(
'Rechtstexte pflegen',
'Bearbeite Impressum und Datenschutz direkt mit dem WordPress-Editor.',
[
'Impressum vollständig eintragen',
'Datenschutztext aktualisieren',
'Speichern und Seite prüfen'
]
);
?>
<form method="post" class="bluesky-admin-form">
<?php wp_nonce_field('bluesky_save_impressum', 'bluesky_impressum_nonce'); ?>
<h2>Impressum</h2>
<?php wp_editor(get_option('bluesky_impressum_content','<h2>Impressum</h2><p>Angaben gemäß §5 TMG</p>'), 'bluesky_impressum_content', ['textarea_rows'=>12,'media_buttons'=>true]); ?>
<h2 style="margin-top:30px">Datenschutzerklärung</h2>
<?php wp_editor(get_option('bluesky_datenschutz_content','<h2>Datenschutzerklärung</h2><p>Datenschutzhinweise</p>'), 'bluesky_datenschutz_content', ['textarea_rows'=>12,'media_buttons'=>true]); ?>
<?php submit_button('Speichern'); ?>
</form></div>
<?php
}
// ============================================================
// HELPER FUNCTIONS (für Templates)
// ============================================================
function bluesky_rgba_to_hex($rgba) {
if (!$rgba) return '';
if (str_starts_with($rgba, '#')) return $rgba;
preg_match('/rgba?\((\d+),\s*(\d+),\s*(\d+)/i', $rgba, $m);
if (count($m) < 4) return '#ff0000';
return sprintf('#%02x%02x%02x', $m[1], $m[2], $m[3]);
}
function bluesky_get_image($option_key, $fallback_file) {
$url = get_option($option_key, '');
return $url ?: get_template_directory_uri() . '/' . $fallback_file;
}
function bluesky_navbar($current = 'home') {
$server_name = get_option('bluesky_server_name', 'BlueSky');
$logo_url = bluesky_get_image('bluesky_navbar_logo_url', 'images/logo.png');
$nav_items = [
'home' => [get_option('bluesky_nav_home_label','Home'), get_option('bluesky_nav_home_url','/')],
'wiki' => [get_option('bluesky_nav_wiki_label','Wiki'), get_option('bluesky_nav_wiki_url','/wiki/')],
'rules' => [get_option('bluesky_nav_rules_label','Regeln'), get_option('bluesky_nav_rules_url','/regeln/')],
'team' => [get_option('bluesky_nav_team_label','Team'), get_option('bluesky_nav_team_url','/team/')],
];
?>
<div class="navbar">
<div class="menu-mobile">
<a href="<?php echo home_url('/'); ?>">
<div class="logo">
<img src="<?php echo esc_url($logo_url); ?>" alt="Logo" class="logo-img">
<h3 class="server-name"><?php echo esc_html($server_name); ?></h3>
</div>
</a>
<div class="hamburger">
<img src="<?php echo get_template_directory_uri(); ?>/images/namnam.png" alt="Menu" style="max-width:32px">
</div>
</div>
<div class="links">
<?php foreach ($nav_items as $key => [$label, $url]): ?>
<a href="<?php echo esc_url(home_url($url)); ?>" class="link <?php echo $current === $key ? 'active' : ''; ?>"><?php echo esc_html($label); ?></a>
<?php endforeach; ?>
</div>
</div>
<?php
}
function bluesky_footer() {
$server_name = get_option('bluesky_server_name', 'BlueSky');
$impressum_url = get_option('bluesky_footer_impressum_url', home_url('/impressum/#impressum'));
$datenschutz_url = get_option('bluesky_footer_datenschutz_url', home_url('/impressum/#datenschutz'));
?>
<footer id="footer">
<p class="copyright">&copy; <?php echo date('Y'); ?> <span class="server-name-footer"><?php echo esc_html($server_name); ?></span> | Theme by M_Viper</p>
<div class="footer-links">
<a href="<?php echo esc_url($impressum_url); ?>" class="link">Impressum</a>
<span class="sep">|</span>
<a href="<?php echo esc_url($datenschutz_url); ?>" class="link">Datenschutz</a>
</div>
</footer>
<?php
}
function bluesky_get_team_data_for_js() {
$team_data = [];
$q = new WP_Query(['post_type'=>'bluesky_team','posts_per_page'=>-1,'post_status'=>'publish','meta_key'=>'_bluesky_sort_order','orderby'=>'meta_value_num','order'=>'ASC']);
if ($q->have_posts()) while ($q->have_posts()) { $q->the_post(); $id=get_the_ID(); $g=get_post_meta($id,'_bluesky_team_group',true)?:'Team'; $team_data[$g][]=['inGameName'=>get_post_meta($id,'_bluesky_in_game_name',true),'rank'=>get_post_meta($id,'_bluesky_rank',true),'rankColor'=>get_post_meta($id,'_bluesky_rank_color',true),'skinUrlOrPathToFile'=>get_post_meta($id,'_bluesky_skin_url',true)]; }
wp_reset_postdata();
return $team_data;
}
// ============================================================
// CUSTOM CSS output
// ============================================================
function bluesky_output_custom_css() {
$css = get_option('bluesky_custom_css','');
if ($css) echo '<style id="bluesky-custom-css">' . wp_strip_all_tags($css) . '</style>';
}
add_action('wp_head', 'bluesky_output_custom_css', 99);
// Force dark background
add_action('wp_head', fn() => print('<style>html,body{background-color:' . esc_attr(get_option('bluesky_color_bg','#24272B')) . '!important;margin:0;padding:0}</style>'), 1);
// ============================================================
// ADMIN COLUMNS
// ============================================================
add_filter('manage_bluesky_team_posts_columns', fn($c) => array_merge($c, ['in_game_name'=>'In-Game Name','rank'=>'Rang','group'=>'Gruppe']));
function bluesky_team_col($col, $id) {
if ($col==='in_game_name') echo esc_html(get_post_meta($id,'_bluesky_in_game_name',true));
if ($col==='rank') { $r=get_post_meta($id,'_bluesky_rank',true); $c=get_post_meta($id,'_bluesky_rank_color',true)?:'#999'; echo "<span style='background:$c;padding:2px 10px;border-radius:10px;color:#fff;font-size:12px'>".esc_html($r)."</span>"; }
if ($col==='group') echo esc_html(get_post_meta($id,'_bluesky_team_group',true)?:'Team');
}
add_action('manage_bluesky_team_posts_custom_column','bluesky_team_col',10,2);
// ============================================================
// DASHBOARD WIDGET
// ============================================================
function bluesky_dash_get_update_info($current_version) {
$cache_key = 'bluesky_theme_update_info';
$releases_url = 'https://git.viper.ipv64.net/M_Viper/bluesky-theme/releases';
$api_url = 'https://git.viper.ipv64.net/api/v1/repos/M_Viper/bluesky-theme/releases/latest';
$normalized_current = ltrim((string) $current_version, "vV");
$default = [
'latest_version' => $normalized_current,
'latest_tag' => 'v' . $normalized_current,
'release_url' => $releases_url,
'status' => 'unknown',
];
$cached = get_transient($cache_key);
if (is_array($cached)) {
$data = array_merge($default, $cached);
$data['has_update'] = $normalized_current !== '' && version_compare((string) $data['latest_version'], $normalized_current, '>');
return $data;
}
$result = $default;
$response = wp_remote_get($api_url, [
'timeout' => 8,
'headers' => ['Accept' => 'application/json'],
]);
if (!is_wp_error($response) && (int) wp_remote_retrieve_response_code($response) === 200) {
$body = json_decode((string) wp_remote_retrieve_body($response), true);
if (is_array($body)) {
$tag = (string) ($body['tag_name'] ?? '');
$latest = ltrim($tag, "vV");
$html_url = (string) ($body['html_url'] ?? '');
if ($latest !== '') {
$result['latest_version'] = $latest;
$result['latest_tag'] = $tag !== '' ? $tag : ('v' . $latest);
$result['status'] = 'ok';
}
if ($html_url !== '') {
$result['release_url'] = $html_url;
}
}
}
set_transient($cache_key, [
'latest_version' => $result['latest_version'],
'latest_tag' => $result['latest_tag'],
'release_url' => $result['release_url'],
'status' => $result['status'],
], 6 * HOUR_IN_SECONDS);
$result['has_update'] = $normalized_current !== '' && version_compare((string) $result['latest_version'], $normalized_current, '>');
return $result;
}
add_action('admin_init', 'bluesky_dash_handle_update_refresh');
function bluesky_dash_handle_update_refresh() {
if (!is_admin()) {
return;
}
if (empty($_GET['bluesky_refresh_update'])) {
return;
}
if (!current_user_can('manage_options')) {
return;
}
if (empty($_GET['_wpnonce']) || !wp_verify_nonce((string) $_GET['_wpnonce'], 'bluesky_refresh_update')) {
return;
}
delete_transient('bluesky_theme_update_info');
wp_safe_redirect(add_query_arg('bluesky_update_refreshed', '1', admin_url('index.php')));
exit;
}
add_action('admin_head-index.php', 'bluesky_dash_admin_styles');
function bluesky_dash_admin_styles() {
?>
<style>
#bluesky_dash.postbox {
border: 1px solid #c3c4c7;
border-radius: 8px;
overflow: hidden;
box-shadow: none;
}
#bluesky_dash.postbox .hndle {
color: #1d2327;
font-size: 16px;
font-weight: 600;
}
#bluesky_dash.postbox .handle-actions .handle-order-higher,
#bluesky_dash.postbox .handle-actions .handle-order-lower,
#bluesky_dash.postbox .handle-actions .toggle-indicator {
color: #646970;
}
#bluesky_dash .inside {
margin: 0;
padding: 10px;
background: #fff;
}
.bluesky-dash-widget {
display: flex;
flex-direction: column;
gap: 10px;
}
.bluesky-dash-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
}
.bluesky-dash-tile {
border: 1px solid #dcdcde;
border-radius: 6px;
background: #f6f7f7;
box-shadow: none;
padding: 9px 8px;
min-height: 70px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 4px;
text-align: center;
}
.bluesky-dash-ip {
grid-column: 1 / -1;
min-height: 56px;
}
.bluesky-dash-icon {
display: none;
}
.bluesky-dash-meta {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.bluesky-dash-count {
color: #1d2327;
font-size: 20px;
font-weight: 700;
line-height: 1;
}
.bluesky-dash-label {
color: #50575e;
font-size: 11px;
font-weight: 600;
}
.bluesky-dash-ip .bluesky-dash-count {
color: #1d2327;
font-size: 15px;
line-height: 1.1;
word-break: break-word;
}
.bluesky-dash-status {
border-top: 1px solid #dcdcde;
padding-top: 8px;
color: #1d2327;
font-size: 12px;
}
.bluesky-dash-status strong {
font-weight: 600;
}
.bluesky-dash-status-ok {
color: #008a20;
font-weight: 600;
}
.bluesky-dash-status-update {
color: #b32d2e;
font-weight: 600;
}
.bluesky-dash-banner {
border: 1px solid #dcdcde;
border-radius: 6px;
padding: 9px 10px;
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 600;
}
.bluesky-dash-banner .dashicons {
width: 14px;
height: 14px;
font-size: 14px;
line-height: 1;
}
.bluesky-dash-banner.is-update {
background: #fff1f1;
border-color: #f0b4b4;
color: #8a2424;
}
.bluesky-dash-banner.is-update .dashicons {
color: #b32d2e;
}
.bluesky-dash-banner.is-ok {
background: #f1fbf3;
border-color: #b8e0c0;
color: #196127;
}
.bluesky-dash-banner.is-ok .dashicons {
color: #008a20;
}
.bluesky-dash-update {
border-top: 1px solid #dcdcde;
padding-top: 8px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
font-size: 12px;
color: #1d2327;
}
.bluesky-dash-update a {
color: #2271b1;
text-decoration: none;
font-weight: 600;
white-space: nowrap;
}
.bluesky-dash-update a:hover {
color: #135e96;
text-decoration: underline;
}
.bluesky-dash-update-links {
display: inline-flex;
align-items: center;
gap: 10px;
}
.bluesky-dash-refresh-note {
color: #008a20;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
}
.bluesky-dash-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
border-top: 1px solid #dcdcde;
padding-top: 10px;
}
.bluesky-dash-actions .button {
border-radius: 0;
min-height: 30px;
padding: 0 10px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
font-weight: 500;
font-size: 12px;
border-color: #2271b1;
color: #2271b1;
background: #ffffff;
text-shadow: none;
box-shadow: none;
width: 100%;
}
.bluesky-dash-actions .button.button-primary {
border-color: #2271b1;
background: #2271b1;
color: #fff;
}
.bluesky-dash-actions .button:hover {
border-color: #135e96;
color: #135e96;
background: #fff;
}
.bluesky-dash-actions .button.button-primary:hover {
border-color: #135e96;
background: #135e96;
color: #fff;
}
.bluesky-dash-actions .button .dashicons {
width: 12px;
height: 12px;
font-size: 12px;
line-height: 1;
margin: 0;
display: inline-flex;
align-items: center;
justify-content: center;
vertical-align: middle;
}
.bluesky-dash-actions .button .dashicons::before {
line-height: 1;
}
@media screen and (max-width: 782px) {
.bluesky-dash-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.bluesky-dash-ip {
grid-column: 1 / -1;
}
}
@media screen and (max-width: 480px) {
.bluesky-dash-grid {
grid-template-columns: 1fr;
}
.bluesky-dash-tile {
min-height: 64px;
}
.bluesky-dash-actions {
grid-template-columns: 1fr;
}
.bluesky-dash-update {
flex-direction: column;
align-items: flex-start;
}
}
</style>
<?php
}
add_action('wp_dashboard_setup', function() {
add_meta_box('bluesky_dash','WP BlueSky Dashboard','bluesky_dash_content','dashboard','normal','high');
});
function bluesky_dash_content() {
$ip = get_option('bluesky_server_ip','—');
$t = wp_count_posts('bluesky_team')->publish ?? 0;
$w = wp_count_posts('bluesky_wiki')->publish ?? 0;
$f = wp_count_posts('bluesky_faq')->publish ?? 0;
$r = wp_count_posts('bluesky_rule')->publish ?? 0;
$theme_version = wp_get_theme()->get('Version');
$update_info = bluesky_dash_get_update_info($theme_version);
$has_update = !empty($update_info['has_update']);
$tiles = [
['count' => (int) $t, 'label' => 'Team'],
['count' => (int) $w, 'label' => 'Wiki'],
['count' => (int) $f, 'label' => 'FAQs'],
['count' => (int) $r, 'label' => 'Regeln'],
];
?>
<div class="bluesky-dash-widget">
<div class="bluesky-dash-grid">
<?php foreach ($tiles as $tile): ?>
<div class="bluesky-dash-tile">
<div class="bluesky-dash-meta">
<strong class="bluesky-dash-count"><?php echo esc_html($tile['count']); ?></strong>
<span class="bluesky-dash-label"><?php echo esc_html($tile['label']); ?></span>
</div>
</div>
<?php endforeach; ?>
<div class="bluesky-dash-tile bluesky-dash-ip">
<div class="bluesky-dash-meta">
<strong class="bluesky-dash-count"><?php echo esc_html($ip); ?></strong>
<span class="bluesky-dash-label">Server IP</span>
</div>
</div>
</div>
<div class="bluesky-dash-banner <?php echo $has_update ? 'is-update' : 'is-ok'; ?>">
<span class="dashicons <?php echo $has_update ? 'dashicons-warning' : 'dashicons-yes-alt'; ?>"></span>
<?php if ($has_update): ?>
<span>Neues Theme-Update verfügbar: <?php echo esc_html($update_info['latest_tag']); ?> (installiert: v<?php echo esc_html($theme_version); ?>)</span>
<?php else: ?>
<span>Theme ist auf dem neuesten Stand (v<?php echo esc_html($theme_version); ?>)</span>
<?php endif; ?>
</div>
<div class="bluesky-dash-status">
<strong>Status:</strong>
<?php if ($has_update): ?>
<span class="bluesky-dash-status-update">Update verfügbar (<?php echo esc_html($update_info['latest_tag']); ?>)</span>
<?php else: ?>
<span class="bluesky-dash-status-ok">Aktuell (v<?php echo esc_html($theme_version); ?>)</span>
<?php endif; ?>
</div>
<div class="bluesky-dash-update">
<span>Release-Seite: <strong><?php echo esc_html($update_info['latest_tag']); ?></strong></span>
<span class="bluesky-dash-update-links">
<?php
$refresh_url = wp_nonce_url(
add_query_arg('bluesky_refresh_update', '1', admin_url('index.php')),
'bluesky_refresh_update'
);
?>
<a href="<?php echo esc_url($refresh_url); ?>">Jetzt neu prüfen</a>
<a href="<?php echo esc_url($update_info['release_url']); ?>" target="_blank" rel="noopener">Releases öffnen</a>
<?php if (!empty($_GET['bluesky_update_refreshed'])): ?>
<span class="bluesky-dash-refresh-note">Neu geprüft</span>
<?php endif; ?>
</span>
</div>
<div class="bluesky-dash-actions">
<a href="<?php echo esc_url(admin_url('admin.php?page=bluesky-settings')); ?>" class="button button-primary"><span class="dashicons dashicons-admin-generic"></span>Settings</a>
<a href="<?php echo esc_url(admin_url('admin.php?page=bluesky-design')); ?>" class="button"><span class="dashicons dashicons-art"></span>Design</a>
<a href="<?php echo esc_url(admin_url('post-new.php?post_type=bluesky_team')); ?>" class="button"><span class="dashicons dashicons-groups"></span>Team +</a>
<a href="<?php echo esc_url(admin_url('post-new.php?post_type=bluesky_faq')); ?>" class="button"><span class="dashicons dashicons-editor-help"></span>FAQ +</a>
</div>
</div>
<?php
}
// ============================================================
// PAGE TEMPLATES
// ============================================================
add_filter('theme_page_templates', function($t) {
return array_merge($t, ['template-home.php'=>'BlueSky - Startseite','template-wiki.php'=>'BlueSky - Wiki','template-regeln.php'=>'BlueSky - Regeln','template-team.php'=>'BlueSky - Team','template-impressum.php'=>'BlueSky - Impressum']);
});
// ============================================================
// AUTO-CREATE PAGES ON ACTIVATION
// ============================================================
// ============================================================
// SEITEN AUTOMATISCH ERSTELLEN & REPARIEREN
// Läuft bei jedem Admin-Aufruf — prüft und korrigiert alles
// ============================================================
function bluesky_ensure_pages() {
// Nur einmal pro Stunde prüfen (Performance)
if (get_transient('bluesky_pages_checked')) return;
set_transient('bluesky_pages_checked', 1, HOUR_IN_SECONDS);
$pages = [
'wiki' => ['Wiki', 'template-wiki.php'],
'regeln' => ['Regeln', 'template-regeln.php'],
'team' => ['Team', 'template-team.php'],
'impressum' => ['Impressum', 'template-impressum.php'],
];
$changed = false;
foreach ($pages as $slug => [$title, $tpl]) {
$page = get_page_by_path($slug);
if (!$page) {
// Seite fehlt → erstellen
$id = wp_insert_post([
'post_title' => $title,
'post_name' => $slug,
'post_status' => 'publish',
'post_type' => 'page',
]);
if ($id && !is_wp_error($id)) {
update_post_meta($id, '_wp_page_template', $tpl);
$changed = true;
}
} else {
// Seite existiert → Template prüfen & korrigieren
$current = get_post_meta($page->ID, '_wp_page_template', true);
if ($current !== $tpl) {
update_post_meta($page->ID, '_wp_page_template', $tpl);
$changed = true;
}
// Sicherstellen dass Seite published ist
if ($page->post_status !== 'publish') {
wp_update_post(['ID' => $page->ID, 'post_status' => 'publish']);
$changed = true;
}
}
}
// Permalinks neu laden wenn etwas geändert wurde
if ($changed) {
flush_rewrite_rules();
delete_transient('bluesky_pages_checked');
}
}
add_action('admin_init', 'bluesky_ensure_pages');
add_action('after_switch_theme', 'bluesky_ensure_pages');
function bluesky_activation() {
bluesky_register_post_types();
bluesky_register_taxonomies();
delete_transient('bluesky_pages_checked'); // Force check on activation
bluesky_ensure_pages();
flush_rewrite_rules();
}
add_action('after_switch_theme', 'bluesky_activation');
// ============================================================
// SETUP-ASSISTENT & SEITEN REPARIEREN
// ============================================================
function bluesky_setup_page() {
if (!current_user_can('manage_options')) wp_die('Kein Zugriff');
// Seiten erstellen / reparieren
if (isset($_POST['bluesky_setup_nonce']) && wp_verify_nonce($_POST['bluesky_setup_nonce'], 'bluesky_run_setup')) {
$result = bluesky_create_pages_now();
flush_rewrite_rules();
echo '<div class="updated notice"><p>✅ ' . esc_html($result) . '</p></div>';
}
bluesky_admin_header('Setup & Seiten', '🔧');
bluesky_admin_intro(
'Automatische Seiten-Reparatur',
'Falls Links auf die Startseite gehen oder Seiten fehlen, kannst du hier alles automatisch korrigieren.',
[
'Status-Tabelle prüfen',
'Seiten erstellen/reparieren klicken',
'Bei Bedarf Permalinks speichern'
]
);
// Status aller Seiten prüfen
$pages_config = [
'team' => ['Team', 'template-team.php'],
'wiki' => ['Wiki', 'template-wiki.php'],
'regeln' => ['Regeln', 'template-regeln.php'],
'impressum' => ['Impressum', 'template-impressum.php'],
];
?>
<h2>Seiten-Status</h2>
<table class="widefat" style="max-width:700px">
<thead><tr><th>Seite</th><th>URL</th><th>Template</th><th>Status</th></tr></thead>
<tbody>
<?php foreach ($pages_config as $slug => [$title, $tpl]):
$page = get_page_by_path($slug);
$exists = !empty($page);
$tpl_set = $exists ? get_post_meta($page->ID, '_wp_page_template', true) : '';
$correct_tpl = ($tpl_set === $tpl);
$url = home_url('/' . $slug . '/');
?>
<tr>
<td><strong><?php echo esc_html($title); ?></strong></td>
<td><?php if ($exists): ?><a href="<?php echo esc_url($url); ?>" target="_blank"><?php echo esc_url($url); ?></a><?php else: echo '—'; endif; ?></td>
<td style="font-size:12px;color:#666"><?php echo esc_html($tpl_set ?: '—'); ?></td>
<td>
<?php if ($exists && $correct_tpl): ?>
<span style="color:#4AFF6B">✅ OK</span>
<?php elseif ($exists && !$correct_tpl): ?>
<span style="color:#FF7C7C">⚠️ Falsches Template</span>
<?php else: ?>
<span style="color:#FF7C7C">❌ Fehlt</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<br>
<form method="post" class="bluesky-admin-form" style="max-width:700px">
<?php wp_nonce_field('bluesky_run_setup', 'bluesky_setup_nonce'); ?>
<p>Klicke auf den Button um alle fehlenden Seiten zu erstellen und die korrekten Templates zuzuweisen. Bereits vorhandene Seiten werden dabei nicht gelöscht.</p>
<?php submit_button('🔧 Seiten erstellen / reparieren', 'primary large'); ?>
</form>
<hr>
<h2>Permalinks neu laden</h2>
<p>Falls Links immer noch auf die Startseite zeigen, einmal Permalinks flushen:</p>
<a href="<?php echo admin_url('options-permalink.php'); ?>" class="button button-secondary">⚙️ Permalinks-Einstellungen öffnen</a>
<p class="description" style="margin-top:8px">Dort einfach auf "Änderungen speichern" klicken — kein anderer Änderung nötig.</p>
</div>
<?php
}
function bluesky_create_pages_now() {
delete_transient('bluesky_pages_checked');
bluesky_ensure_pages();
$pages = [
'wiki' => ['Wiki', 'template-wiki.php'],
'regeln' => ['Regeln', 'template-regeln.php'],
'team' => ['Team', 'template-team.php'],
'impressum' => ['Impressum', 'template-impressum.php'],
];
$ok = 0;
foreach ($pages as $slug => [$title, $tpl]) {
$page = get_page_by_path($slug);
if ($page && get_post_meta($page->ID, '_wp_page_template', true) === $tpl) $ok++;
}
flush_rewrite_rules();
return "Fertig! $ok/4 Seiten korrekt konfiguriert. Permalinks wurden neu geladen.";
}
// Menüeintrag für Setup
add_action('admin_menu', function() {
add_submenu_page('bluesky-settings', 'Setup & Seiten reparieren', '🔧 Setup & Reparieren', 'manage_options', 'bluesky-setup', 'bluesky_setup_page');
}, 20);
// Admin-Hinweis falls Seiten fehlen
add_action('admin_notices', function() {
if (!current_user_can('manage_options')) return;
$screen = get_current_screen();
if ($screen && str_contains($screen->id ?? '', 'bluesky')) return;
$team_page = get_page_by_path('team');
if (!$team_page) {
echo '<div class="notice notice-warning"><p>⚠️ <strong>BlueSky MC Theme:</strong> Einige Seiten fehlen noch. <a href="' . admin_url('admin.php?page=bluesky-setup') . '"><strong>Jetzt reparieren →</strong></a></p></div>';
}
});
// ============================================================
// WIKI META BOX — Reihenfolge & Excerpt
// ============================================================
function bluesky_wiki_meta_boxes() {
add_meta_box('bluesky_wiki_meta', 'Wiki Artikel Einstellungen', 'bluesky_wiki_meta_cb', 'bluesky_wiki', 'side', 'high');
}
add_action('add_meta_boxes', 'bluesky_wiki_meta_boxes');
function bluesky_wiki_meta_cb($post) {
wp_nonce_field('bluesky_wiki_nonce_action', 'bluesky_wiki_nonce');
$order = get_post_meta($post->ID, '_bluesky_wiki_order', true);
$icon = get_post_meta($post->ID, '_bluesky_wiki_icon', true);
?>
<table class="form-table" style="margin:0">
<tr>
<th style="padding:5px 0"><label>Reihenfolge</label></th>
<td><input type="number" name="bluesky_wiki_order" value="<?php echo esc_attr($order ?: 0); ?>" style="width:80px" min="0">
<p class="description" style="font-size:11px">Niedrigere Zahl = weiter oben</p></td>
</tr>
<tr>
<th style="padding:5px 0"><label>Icon / Emoji</label></th>
<td><input type="text" name="bluesky_wiki_icon" value="<?php echo esc_attr($icon); ?>" style="width:80px" placeholder="📖">
<p class="description" style="font-size:11px">Emoji für die Kachel</p></td>
</tr>
</table>
<p class="description" style="margin-top:10px">
💡 <strong>Tipp:</strong> Beitragsbild setzen → wird als Icon auf der Wiki-Übersicht angezeigt.
</p>
<?php
}
function bluesky_save_wiki_meta($post_id) {
if (!isset($_POST['bluesky_wiki_nonce']) || !wp_verify_nonce($_POST['bluesky_wiki_nonce'], 'bluesky_wiki_nonce_action')) return;
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
if (isset($_POST['bluesky_wiki_order'])) update_post_meta($post_id, '_bluesky_wiki_order', intval($_POST['bluesky_wiki_order']));
if (isset($_POST['bluesky_wiki_icon'])) update_post_meta($post_id, '_bluesky_wiki_icon', sanitize_text_field($_POST['bluesky_wiki_icon']));
}
add_action('save_post_bluesky_wiki', 'bluesky_save_wiki_meta');
// Wiki Kategorie: Beschreibungsfeld = Emoji/Icon
// (Das Kategorie-Beschreibungsfeld wird als Icon-Container genutzt)
function bluesky_wiki_cat_icon_field($term) {
$icon = is_object($term) ? esc_attr($term->description) : '';
?>
<div class="form-field">
<label>Kategorie-Icon (Emoji)</label>
<input type="text" name="description" value="<?php echo $icon; ?>" placeholder="📚" style="width:80px">
<p>Ein Emoji das neben dem Kategorie-Titel angezeigt wird. z.B. ⚔️ 🏠 💰 🐾 📜</p>
</div>
<?php
}
add_action('wiki_category_add_form_fields', function() { bluesky_wiki_cat_icon_field(null); });
add_action('wiki_category_edit_form_fields', function($term) { bluesky_wiki_cat_icon_field($term); });
// Admin-Columns für Wiki
add_filter('manage_bluesky_wiki_posts_columns', function($cols) {
return array_merge($cols, ['wiki_cats' => 'Kategorie', 'wiki_order' => 'Reihenfolge']);
});
add_action('manage_bluesky_wiki_posts_custom_column', function($col, $id) {
if ($col === 'wiki_cats') {
$terms = get_the_terms($id, 'wiki_category');
echo $terms ? implode(', ', wp_list_pluck($terms, 'name')) : '—';
}
if ($col === 'wiki_order') echo get_post_meta($id, '_bluesky_wiki_order', true) ?: '0';
}, 10, 2);