Files
Festive-Seasons-Pro/festive-seasons-pro.php

548 lines
24 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/*
Plugin Name: Festive Seasons Pro
Description: Zeigt saisonale Effekte (Schnee, Eier, Spinnen, Ballons, Feuerwerk) für feste Feiertage und individuelle Geburtstage an.
Version: 1.2
Author: M_Viper
*/
if (!defined('ABSPATH')) {
exit;
}
// === FESTIVE SEASONS PRO - UPDATE SYSTEM ===
// Aktuelle Plugin-Version aus Plugin-Header lesen
function fsp_get_plugin_version() {
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_data = get_plugin_data( __FILE__ );
return $plugin_data['Version'] ?? '0.0.0';
}
// Cache löschen (manueller Refresh)
function fsp_clear_update_cache() {
if ( isset($_GET['fsp_clear_cache']) && current_user_can('manage_options') ) {
check_admin_referer('fsp_clear_cache_action');
delete_transient('fsp_latest_release');
wp_redirect( admin_url('plugins.php') );
exit;
}
}
add_action('admin_init', 'fsp_clear_update_cache');
// Neueste Release-Infos von Gitea holen
function fsp_get_latest_release_info( $force_refresh = false ) {
$transient_key = 'fsp_latest_release';
if ( $force_refresh ) {
delete_transient( $transient_key );
}
$release_info = get_transient( $transient_key );
if ( false === $release_info ) {
$response = wp_remote_get(
'https://git.viper.ipv64.net/api/v1/repos/M_Viper/Festive-Seasons-Pro/releases/latest',
['timeout' => 10]
);
if ( ! is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response) ) {
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if ( $data && isset($data['tag_name']) ) {
$tag = ltrim($data['tag_name'], 'v');
$release_info = [
'version' => $tag,
'download_url' => $data['zipball_url'] ?? '',
'notes' => $data['body'] ?? '',
'published_at' => $data['published_at'] ?? '',
];
set_transient( $transient_key, $release_info, 6 * HOUR_IN_SECONDS );
} else {
set_transient( $transient_key, [], HOUR_IN_SECONDS );
}
} else {
set_transient( $transient_key, [], HOUR_IN_SECONDS );
}
}
return $release_info;
}
// Admin-Notice für Plugin-Update
function fsp_show_update_notice() {
if ( ! current_user_can('manage_options') ) {
return;
}
$current_version = fsp_get_plugin_version();
$latest_release = fsp_get_latest_release_info();
if ( ! empty($latest_release['version']) && version_compare($current_version, $latest_release['version'], '<') ) {
$refresh_url = wp_nonce_url(
admin_url('plugins.php?fsp_clear_cache=1'),
'fsp_clear_cache_action'
);
?>
<div class="notice notice-warning is-dismissible">
<h3>Festive Seasons Pro Update verfügbar</h3>
<p>
Aktuelle Version: <strong><?php echo esc_html($current_version); ?></strong><br>
Neueste Version: <strong><?php echo esc_html($latest_release['version']); ?></strong>
</p>
<p>
<a href="<?php echo esc_url($latest_release['download_url']); ?>" class="button button-primary" target="_blank">
Update herunterladen
</a>
<a href="https://git.viper.ipv64.net/M_Viper/Festive-Seasons-Pro/releases" class="button" target="_blank">
Release Notes
</a>
<a href="<?php echo esc_url($refresh_url); ?>" class="button">
Jetzt neu prüfen
</a>
</p>
</div>
<?php
}
}
add_action('admin_notices', 'fsp_show_update_notice');
class Festive_Seasons_Pro {
public function __construct() {
add_action('init', [$this, 'init_plugin']);
}
public function init_plugin() {
add_action('wp_enqueue_scripts', [$this, 'load_assets']);
add_action('wp_footer', [$this, 'render_music_consent']);
add_action('admin_menu', [$this, 'add_admin_menu_page']);
add_action('admin_init', [$this, 'register_settings']);
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']);
add_action('widgets_init', [$this, 'register_widget']);
add_shortcode('festive_event', [$this, 'render_shortcode']);
}
// Admin-Scripts laden
public function enqueue_admin_scripts($hook) {
// Nur auf der Plugin-Einstellungsseite laden
if ($hook !== 'settings_page_festive-seasons-pro') {
return;
}
// jQuery ist standardmäßig in WordPress verfügbar
wp_enqueue_script('jquery');
// Inline-Script für Event-Verwaltung
wp_add_inline_script('jquery', '
jQuery(document).ready(function($) {
// Neues Ereignis hinzufügen
$("#fsp-add-event").on("click", function() {
var index = $("#fsp-events-container .fsp-event-row").length;
var newRow = \'<div class="fsp-event-row" style="margin-bottom: 10px; display: flex; gap: 10px; align-items: center;">\' +
\'<input type="text" name="fsp_events[\' + index + \'][name]" placeholder="Name (z.B. Max)" style="width: 200px;" />\' +
\'<input type="text" name="fsp_events[\' + index + \'][date]" placeholder="Datum (TT-MM)" style="width: 100px;" />\' +
\'<input type="text" name="fsp_events[\' + index + \'][text]" placeholder="Text (z.B. wird 30!)" style="flex-grow: 1;" />\' +
\'<button type="button" class="button button-secondary fsp-remove-event">Entfernen</button>\' +
\'</div>\';
$("#fsp-events-container").append(newRow);
});
// Ereignis entfernen
$(document).on("click", ".fsp-remove-event", function() {
$(this).closest(".fsp-event-row").remove();
});
});
');
}
public function get_active_events() {
if (isset($_GET['fsp_test_date'])) {
$today = sanitize_text_field($_GET['fsp_test_date']);
} else {
$today = date('Y-m-d');
}
$active_events = [];
foreach ($this->get_fixed_events() as $event_key => $event_data) {
if (isset($event_data['start'], $event_data['end']) && $today >= $event_data['start'] && $today <= $event_data['end']) {
if (get_option("fsp_fixed_{$event_key}", 'yes') === 'yes') {
$active_events[] = $event_data;
}
}
}
foreach ($this->get_custom_events($today) as $event_data) {
$active_events[] = $event_data;
}
return $active_events;
}
private function get_fixed_events() {
$test_year = isset($_GET['fsp_test_date']) ? substr(sanitize_text_field($_GET['fsp_test_date']), 0, 4) : date('Y');
$year = is_numeric($test_year) ? $test_year : date('Y');
$easter_date = date('Y-m-d', easter_date($year));
return [
'advent' => ['name' => 'Adventszeit', 'start' => "$year-12-01", 'end' => "$year-12-23", 'class' => 'festive-advent', 'effects' => ['snow']],
'christmas' => ['name' => 'Weihnachten', 'start' => "$year-12-24", 'end' => "$year-12-26", 'class' => 'festive-christmas', 'effects' => ['snow', 'music', 'star', 'santa']],
'newyear' => ['name' => 'Silvester', 'start' => "$year-12-31", 'end' => ($year + 1) . "-01-01", 'class' => 'festive-newyear', 'effects' => ['newyear_fireworks']],
'halloween' => ['name' => 'Halloween', 'start' => "$year-10-31", 'end' => "$year-10-31", 'class' => 'festive-halloween', 'effects' => ['spiders']],
'easter' => ['name' => 'Ostern', 'start' => date('Y-m-d', strtotime($easter_date . ' -1 day')), 'end' => date('Y-m-d', strtotime($easter_date)), 'class' => 'festive-easter', 'effects' => ['eggs']],
];
}
private function get_custom_events($today) {
$custom_events = get_option('fsp_events', []);
$active_custom_events = [];
$today_day_month = date('m-d', strtotime($today));
if (is_array($custom_events)) {
foreach ($custom_events as $event) {
if (empty($event['date']) || !is_array($event)) continue;
$event_date = sanitize_text_field($event['date']);
$date_obj = DateTime::createFromFormat('d-m', $event_date) ?: DateTime::createFromFormat('Y-m-d', $event_date);
if ($date_obj && $date_obj->format('m-d') === $today_day_month) {
$name = trim(($event['name'] ?? '') . ' ' . ($event['text'] ?? ''));
$active_custom_events[] = [
'name' => $name ?: 'Besonderer Tag!',
'class' => 'festive-birthday',
'effects' => ['balloons', 'confetti']
];
}
}
}
return $active_custom_events;
}
public function load_assets() {
$active_events = $this->get_active_events();
if (empty($active_events)) return;
$body_classes = [];
$effects_to_load = [];
foreach ($active_events as $event) {
if (!empty($event['class'])) $body_classes[] = $event['class'];
if (!empty($event['effects'])) $effects_to_load = array_merge($effects_to_load, $event['effects']);
}
add_filter('body_class', function($classes) use ($body_classes) {
return array_merge($classes, array_unique($body_classes));
});
wp_enqueue_style('fsp-style', plugin_dir_url(__FILE__) . 'assets/css/festive.css', [], '3.4');
wp_enqueue_script('jquery');
wp_localize_script('jquery', 'fsp_vars', [
'plugin_url' => plugin_dir_url(__FILE__)
]);
if (in_array('confetti', $effects_to_load) || in_array('newyear_fireworks', $effects_to_load)) {
wp_enqueue_script('fsp-confetti-lib', plugin_dir_url(__FILE__) . 'assets/js/canvas-confetti.min.js', [], '3.4', true);
}
foreach (array_unique($effects_to_load) as $effect) {
switch ($effect) {
case 'snow':
wp_enqueue_script('fsp-snow', plugin_dir_url(__FILE__) . 'assets/js/snowstorm.js', [], '3.4', true);
break;
case 'confetti':
wp_add_inline_script('fsp-confetti-lib', 'jQuery(document).ready(function($) { setTimeout(() => confetti({particleCount: 200, spread: 120, origin: { y: 0.6 } }), 1000); });');
break;
case 'eggs':
wp_enqueue_script('fsp-eggs', plugin_dir_url(__FILE__) . 'assets/js/easter-eggs.js', ['jquery'], '3.4', true);
break;
case 'spiders':
wp_enqueue_script('fsp-spiders', plugin_dir_url(__FILE__) . 'assets/js/halloween-spiders.js', ['jquery'], '3.4', true);
break;
case 'balloons':
wp_enqueue_script('fsp-balloons', plugin_dir_url(__FILE__) . 'assets/js/balloons.js', ['jquery'], '3.4', true);
break;
case 'star':
wp_enqueue_script('fsp-star', plugin_dir_url(__FILE__) . 'assets/js/christmas-star.js', ['jquery'], '3.4', true);
break;
case 'santa':
wp_enqueue_script('fsp-santa', plugin_dir_url(__FILE__) . 'assets/js/santa-sleigh.js', ['jquery'], '3.4', true);
break;
case 'newyear_fireworks':
$this->add_newyear_script();
break;
}
}
}
private function add_newyear_script() {
$script = '
jQuery(document).ready(function($) {
if (!$("body").hasClass("festive-newyear")) {
return;
}
if (typeof confetti === "undefined") {
console.error("FSP: Konfetti-Bibliothek nicht gefunden.");
return;
}
function runBigFireworks() {
var count = 250;
var defaults = { origin: { y: 0.7 }, zIndex: 999999, colors: ["#bb0000", "#ffffff", "#ff0000", "#ffbb00", "#ffee00", "#00ff00", "#0099ff", "#0000ff"] };
function fire(particleRatio, opts) {
confetti(Object.assign({}, defaults, opts, { particleCount: Math.floor(count * particleRatio) }));
}
fire(0.25, { spread: 26, startVelocity: 55 });
fire(0.2, { spread: 60 });
fire(0.35, { spread: 100, decay: 0.91, scalar: 0.8 });
fire(0.1, { spread: 120, startVelocity: 25, decay: 0.92, scalar: 1.2 });
fire(0.1, { spread: 120, startVelocity: 45 });
var rainDefaults = { origin: { y: 0 }, angle: 90, drift: 0, gravity: 1.2, scalar: 1.2, zIndex: 999999 };
confetti(Object.assign({}, rainDefaults, { particleCount: 40, x: 0.1, colors: ["#ff0000", "#00ff00", "#0000ff", "#ffff00"] }));
confetti(Object.assign({}, rainDefaults, { particleCount: 40, x: 0.5, colors: ["#bb0000", "#ffffff", "#ffbb00", "#ffee00"] }));
confetti(Object.assign({}, rainDefaults, { particleCount: 40, x: 0.9, colors: ["#ff0000", "#00ff00", "#0000ff", "#ffff00"] }));
}
function createRocket(isLastRocket) {
const startX = Math.random() * window.innerWidth;
const $rocket = $("<div class=\"fsp-rocket\"></div>").css({
position: "fixed",
width: "6px",
height: "25px",
background: "linear-gradient(to top, #ff4500, #ffa500)",
bottom: "-30px",
left: startX + "px",
zIndex: 999999,
borderRadius: "50% 50% 0 0",
boxShadow: "0 0 15px 5px rgba(255, 165, 0, 0.8)"
});
$("body").append($rocket);
$rocket.animate(
{ bottom: window.innerHeight * 0.8 },
{
duration: 1800,
easing: "linear",
complete: function() {
const x = startX / window.innerWidth;
const y = 0.2;
confetti({
particleCount: 75,
spread: 100,
origin: { x: x, y: y },
colors: ["#ff0000", "#ffa500", "#ffff00", "#ffffff"],
gravity: 1.1,
scalar: 1.0,
startVelocity: 40
});
if (isLastRocket) {
setTimeout(runBigFireworks, 500);
}
$(this).remove();
}
}
);
}
function runNewYearShow() {
const rocketCount = 5 + Math.floor(Math.random() * 4);
for (let i = 0; i < rocketCount; i++) {
const isLast = (i === rocketCount - 1);
setTimeout(() => createRocket(isLast), i * 400);
}
}
runNewYearShow();
setInterval(runNewYearShow, 12000);
});
';
wp_add_inline_script('fsp-confetti-lib', $script);
}
public function add_admin_menu_page() {
add_options_page('Festive Seasons Pro', 'Festive Seasons', 'manage_options', 'festive-seasons-pro', [$this, 'render_admin_page']);
}
public function register_settings() {
register_setting('fsp_settings_group', 'fsp_events');
foreach (array_keys($this->get_fixed_events()) as $key) {
register_setting('fsp_settings_group', "fsp_fixed_{$key}");
}
register_setting('fsp_settings_group', 'fsp_enable_music');
}
public function render_admin_page() {
$events = get_option('fsp_events', []);
?>
<div class="wrap">
<h1>🎉 Festive Seasons Pro Einstellungen</h1>
<form method="post" action="options.php">
<?php settings_fields('fsp_settings_group'); ?>
<?php do_settings_sections('fsp_settings_group'); ?>
<h2>⚙️ Allgemeine Einstellungen</h2>
<table class="form-table" role="presentation">
<tr>
<th scope="row">Musik-Einwilligung</th>
<td>
<fieldset>
<label><input type="checkbox" name="fsp_enable_music" value="yes" <?php checked(get_option('fsp_enable_music', 'yes'), 'yes'); ?> /> Einwilligungsbox für Weihnachtsmusik anzeigen (funktioniert nur, wenn Weihnachten aktiv ist).</label>
</fieldset>
</td>
</tr>
</table>
<h2>🎄 Feste Feiertage</h2>
<table class="form-table" role="presentation">
<?php foreach ($this->get_fixed_events() as $key => $event): ?>
<tr>
<th scope="row"><?php echo esc_html($event['name']); ?></th>
<td>
<label><input type="checkbox" name="fsp_fixed_<?php echo $key; ?>" value="yes" <?php checked(get_option("fsp_fixed_{$key}", 'yes'), 'yes'); ?> /> Aktivieren</label>
</td>
</tr>
<?php endforeach; ?>
</table>
<h2>🎂 Geburtstage & Jubiläen</h2>
<p>Hier können Sie beliebige Ereignisse hinzufügen. Das Datum wird im Format <strong>TT-MM</strong> (z.B. 24-12) oder <strong>JJJJ-MM-TT</strong> (z.B. 2024-05-10) angegeben.</p>
<div id="fsp-events-container" style="margin-bottom: 15px;">
<?php if (is_array($events) && !empty($events)) : ?>
<?php foreach ($events as $index => $event) : ?>
<div class="fsp-event-row" style="margin-bottom: 10px; display: flex; gap: 10px; align-items: center;">
<input type="text" name="fsp_events[<?php echo $index; ?>][name]" placeholder="Name (z.B. Max)" value="<?php echo esc_attr($event['name'] ?? ''); ?>" style="width: 200px;" />
<input type="text" name="fsp_events[<?php echo $index; ?>][date]" placeholder="Datum (TT-MM)" value="<?php echo esc_attr($event['date'] ?? ''); ?>" style="width: 100px;" />
<input type="text" name="fsp_events[<?php echo $index; ?>][text]" placeholder="Text (z.B. wird 30!)" value="<?php echo esc_attr($event['text'] ?? ''); ?>" style="flex-grow: 1;" />
<button type="button" class="button button-secondary fsp-remove-event">Entfernen</button>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<button type="button" id="fsp-add-event" class="button button-primary" style="margin-bottom: 20px;">
<span class="dashicons dashicons-plus-alt" style="margin-top: 3px;"></span> Neues Ereignis hinzufügen
</button>
<?php submit_button('Einstellungen speichern'); ?>
</form>
</div>
<?php
}
public function render_shortcode() {
$active_events = $this->get_active_events();
if (empty($active_events)) return '';
$names = array_map(fn($e) => $e['name'], $active_events);
return '<div class="fsp-today">Heute feiern wir: <strong>' . esc_html(implode(' + ', $names)) . '</strong></div>';
}
public function register_widget() {
register_widget('FSP_Widget');
}
public function render_music_consent() {
if (get_option('fsp_enable_music', 'yes') !== 'yes') {
return;
}
$active_events = $this->get_active_events();
$has_music = false;
foreach ($active_events as $event) {
if (in_array('music', $event['effects'] ?? [])) { $has_music = true; break; }
}
if (!$has_music) return;
$audio_url = plugin_dir_url(__FILE__) . 'assets/audio/xmas-jingle.mp3';
echo '<div id="fsp-music-consent" style="position:fixed;bottom:20px;right:20px;background:#fff;color:#333;padding:20px;border-radius:8px;z-index:99999;box-shadow:0 4px 15px rgba(0,0,0,0.2);display:none;max-width:300px;">
<p style="margin:0 0 15px 0;">🎵 Weihnachtsmusik abspielen?</p>
<button id="fsp-music-yes" style="background:#c0392b;color:#fff;border:none;padding:8px 15px;border-radius:5px;cursor:pointer;margin-right:10px;">Ja</button>
<button id="fsp-music-no" style="background:#bdc3c7;color:#333;border:none;padding:8px 15px;border-radius:5px;cursor:pointer;">Nein</button>
</div>
<audio id="fsp-xmas-audio" src="' . $audio_url . '" loop style="display:none;"></audio>
<script>
jQuery(function($) {
const consentBox = $("#fsp-music-consent");
const audioPlayer = $("#fsp-xmas-audio")[0];
setTimeout(() => consentBox.fadeIn(500), 2000);
$("#fsp-music-yes").on("click", function() {
const playPromise = audioPlayer.play();
if (playPromise !== undefined) {
playPromise.then(_ => {
console.log("FSP: Musik wird abgespielt.");
}).catch(error => {
console.error("FSP: Autoplay blockiert. Zeige Steuerelemente an.");
audioPlayer.style.position = "fixed";
audioPlayer.style.bottom = "20px";
audioPlayer.style.left = "20px";
audioPlayer.style.zIndex = "99999";
audioPlayer.style.width = "250px";
audioPlayer.style.display = "block";
audioPlayer.controls = true;
});
}
consentBox.fadeOut(300);
});
$("#fsp-music-no").on("click", function() {
consentBox.fadeOut(300);
});
});
</script>';
}
}
class FSP_Widget extends WP_Widget {
public function __construct() {
parent::__construct('fsp_widget', 'Festive Seasons Anlass', ['description' => 'Zeigt den heutigen festlichen Anlass an.']);
}
public function widget($args, $instance) {
global $festive_seasons_pro_instance;
if (!$festive_seasons_pro_instance) return;
$active_events = $festive_seasons_pro_instance->get_active_events();
if (empty($active_events)) return;
$names = array_map(fn($e) => $e['name'], $active_events);
echo $args['before_widget'];
if (!empty($instance['title'])) {
echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
}
echo '<div style="text-align:center; padding:15px; background:rgba(255,255,255,0.2); border-radius:10px;">' . esc_html(implode(' + ', $names)) . '</div>';
echo $args['after_widget'];
}
public function form($instance) {
$title = !empty($instance['title']) ? $instance['title'] : 'Heute ist...';
?>
<p><label for="<?php echo esc_attr($this->get_field_id('title')); ?>">Titel:</label><input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>"></p>
<?php
}
public function update($new_instance, $old_instance) {
$instance = [];
$instance['title'] = (!empty($new_instance['title'])) ? sanitize_text_field($new_instance['title']) : '';
return $instance;
}
}
$festive_seasons_pro_instance = new Festive_Seasons_Pro();