first commit

This commit is contained in:
2026-06-18 16:00:11 +02:00
parent 89a15f06ad
commit efe73b79c8
10 changed files with 1370 additions and 0 deletions
+413
View File
@@ -0,0 +1,413 @@
<?php
/**
* Main plugin bootstrap.
*
* @package Blocksy_Light_Dark_Toggle
*/
defined( 'ABSPATH' ) || exit;
/**
* Blocksy Light/Dark Toggle plugin.
*/
class BLC_LDT {
/**
* Blocksy localStorage keys used for colour mode.
*
* @var string[]
*/
const BLOCKSY_STORAGE_KEYS = array( 'blocksyColorMode', 'blocksy_color_mode', 'theme' );
/**
* Initialize hooks.
*/
public static function init() {
add_action( 'plugins_loaded', array( __CLASS__, 'bootstrap' ) );
add_action( 'admin_notices', array( __CLASS__, 'admin_theme_notice' ) );
}
/**
* Load frontend assets when Blocksy (or a child) is active.
*/
public static function bootstrap() {
if ( ! self::is_target_theme() ) {
return;
}
add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
add_action( 'wp_footer', array( __CLASS__, 'render_toggle' ), 5 );
add_action( 'wp_head', array( __CLASS__, 'inline_preference_script' ), 0 );
add_action( 'wp_head', array( __CLASS__, 'inline_position_styles' ), 20 );
add_action( 'wp_ajax_blc_ldt_save_scheme', array( __CLASS__, 'ajax_save_scheme' ) );
add_action( 'wp_ajax_nopriv_blc_ldt_save_scheme', array( __CLASS__, 'ajax_save_scheme' ) );
add_filter( 'body_class', array( __CLASS__, 'body_class' ) );
}
/**
* Whether Blocksy (or a child of it) is active.
*
* @return bool
*/
public static function is_target_theme() {
$theme = wp_get_theme();
return 'blocksy' === $theme->get_template();
}
/**
* Whether Blocksy's native Colour Mode Switch extension is active.
*
* @return bool
*/
public static function has_blocksy_color_mode() {
$extensions = get_option( 'blocksy_active_extensions', array() );
if ( is_array( $extensions ) && in_array( 'color-mode-switch', $extensions, true ) ) {
return true;
}
if ( function_exists( 'blc_get_extensions_status' ) ) {
$status = blc_get_extensions_status();
if ( is_array( $status ) && ! empty( $status['color-mode-switch'] ) ) {
return true;
}
}
return (bool) apply_filters( 'blc_ldt_has_blocksy_color_mode', false );
}
/**
* Whether the toggle should render on the current request.
*
* @return bool
*/
public static function should_render() {
if ( is_admin() || wp_doing_ajax() || is_customize_preview() ) {
return false;
}
$settings = BLC_LDT_Settings::get();
if ( empty( $settings['show_on_mobile'] ) && empty( $settings['show_on_desktop'] ) ) {
return false;
}
return (bool) apply_filters( 'blc_ldt_should_render', true );
}
/**
* Warn in admin if the theme is not supported.
*/
public static function admin_theme_notice() {
if ( self::is_target_theme() || ! current_user_can( 'switch_themes' ) ) {
return;
}
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
if ( $screen && 'plugins' !== $screen->id ) {
return;
}
echo '<div class="notice notice-warning"><p>';
echo esc_html__(
'Blocksy Light/Dark Toggle is active but Blocksy is not the current theme. The toggle only appears with Blocksy.',
'blocksy-light-dark-toggle'
);
echo '</p></div>';
}
/**
* Prevent flash of wrong theme by applying Blocksy classes before paint.
*/
public static function inline_preference_script() {
if ( ! self::should_render() ) {
return;
}
$scheme = self::get_initial_scheme();
$settings = BLC_LDT_Settings::get();
$storage_keys = array_merge( array( BLC_LDT_STORAGE ), self::BLOCKSY_STORAGE_KEYS );
?>
<script id="blc-ldt-init">
(function () {
var storageKeys = <?php echo wp_json_encode( $storage_keys ); ?>;
var cookieName = <?php echo wp_json_encode( BLC_LDT_COOKIE ); ?>;
var serverScheme = <?php echo wp_json_encode( $scheme ); ?>;
var respectOs = <?php echo wp_json_encode( ! empty( $settings['respect_os_preference'] ) ); ?>;
var stored = '';
function readStored() {
var i, val;
try {
for (i = 0; i < storageKeys.length; i++) {
val = localStorage.getItem(storageKeys[i]);
if (val === 'dark' || val === 'light') {
return val;
}
}
} catch (e) {}
if (document.cookie.indexOf(cookieName + '=') !== -1) {
var match = document.cookie.match(new RegExp(cookieName + '=([^;]+)'));
if (match) {
val = decodeURIComponent(match[1]);
if (val === 'dark' || val === 'light') {
return val;
}
}
}
return '';
}
function applyScheme(scheme) {
var root = document.documentElement;
root.classList.remove('theme-dark', 'theme-light');
root.classList.add(scheme === 'dark' ? 'theme-dark' : 'theme-light');
root.setAttribute('data-blc-scheme', scheme);
root.style.colorScheme = scheme;
}
function resolveScheme() {
stored = readStored();
if (stored) {
return stored;
}
if (serverScheme) {
return serverScheme;
}
if (respectOs && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
applyScheme(resolveScheme());
})();
</script>
<?php
}
/**
* Output dynamic positioning and visibility CSS from settings.
*/
public static function inline_position_styles() {
if ( ! self::should_render() ) {
return;
}
$settings = BLC_LDT_Settings::get();
$position = $settings['position'];
$offset_x = $settings['offset_x'];
$offset_y = $settings['offset_y'];
$z_index = (int) $settings['z_index'];
$rules = array(
'z-index:' . $z_index,
);
switch ( $position ) {
case 'bottom-left':
$rules[] = 'left:' . $offset_x;
$rules[] = 'right:auto';
$rules[] = 'bottom:' . $offset_y;
$rules[] = 'top:auto';
break;
case 'top-right':
$rules[] = 'right:' . $offset_x;
$rules[] = 'left:auto';
$rules[] = 'top:' . $offset_y;
$rules[] = 'bottom:auto';
break;
case 'top-left':
$rules[] = 'left:' . $offset_x;
$rules[] = 'right:auto';
$rules[] = 'top:' . $offset_y;
$rules[] = 'bottom:auto';
break;
default:
$rules[] = 'right:' . $offset_x;
$rules[] = 'left:auto';
$rules[] = 'bottom:' . $offset_y;
$rules[] = 'top:auto';
break;
}
$css = '#blc-ldt-toggle{' . implode( ';', $rules ) . ';}';
if ( empty( $settings['show_on_mobile'] ) ) {
$css .= '@media (max-width:689px){#blc-ldt-toggle{display:none!important;}}';
}
if ( empty( $settings['show_on_desktop'] ) ) {
$css .= '@media (min-width:690px){#blc-ldt-toggle{display:none!important;}}';
}
if ( ! empty( $settings['hide_header_switch'] ) ) {
$css .= '[data-id="color-mode-switch"],[data-shortcut="color-mode-switch"],.ct-color-switch,.ct-header-color-switch{display:none!important;}';
}
echo '<style id="blc-ldt-position">' . esc_html( $css ) . '</style>';
}
/**
* Resolve saved scheme for first paint (cookie or user meta only).
*
* @return string light|dark|empty string when none saved.
*/
public static function get_initial_scheme() {
if ( isset( $_COOKIE[ BLC_LDT_COOKIE ] ) ) {
$cookie = sanitize_key( wp_unslash( $_COOKIE[ BLC_LDT_COOKIE ] ) );
if ( in_array( $cookie, array( 'light', 'dark' ), true ) ) {
return apply_filters( 'blc_ldt_initial_scheme', $cookie );
}
}
if ( is_user_logged_in() ) {
$meta = get_user_meta( get_current_user_id(), BLC_LDT_STORAGE, true );
if ( in_array( $meta, array( 'light', 'dark' ), true ) ) {
return apply_filters( 'blc_ldt_initial_scheme', $meta );
}
}
return apply_filters( 'blc_ldt_initial_scheme', '' );
}
/**
* Enqueue styles and toggle script.
*/
public static function enqueue_assets() {
if ( ! self::should_render() ) {
return;
}
if ( ! self::has_blocksy_color_mode() ) {
wp_enqueue_style(
'blc-ldt-fallback',
BLC_LDT_URL . 'assets/css/dark-fallback.css',
array(),
BLC_LDT_VERSION
);
}
wp_enqueue_style(
'blc-ldt-overrides',
BLC_LDT_URL . 'assets/css/dark-overrides.css',
self::has_blocksy_color_mode() ? array() : array( 'blc-ldt-fallback' ),
BLC_LDT_VERSION
);
wp_enqueue_style(
'blc-ldt-toggle',
BLC_LDT_URL . 'assets/css/toggle.css',
array( 'blc-ldt-overrides' ),
BLC_LDT_VERSION
);
wp_enqueue_script(
'blc-ldt-toggle',
BLC_LDT_URL . 'assets/js/toggle.js',
array(),
BLC_LDT_VERSION,
true
);
$settings = BLC_LDT_Settings::get();
wp_localize_script(
'blc-ldt-toggle',
'blcLdt',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'blc_ldt_save' ),
'storageKey' => BLC_LDT_STORAGE,
'cookieName' => BLC_LDT_COOKIE,
'cookiePath' => COOKIEPATH ? COOKIEPATH : '/',
'cookieDomain' => COOKIE_DOMAIN,
'isLoggedIn' => is_user_logged_in(),
'hasBlocksyColorMode' => self::has_blocksy_color_mode(),
'blocksyStorageKeys' => self::BLOCKSY_STORAGE_KEYS,
'respectOsPreference' => ! empty( $settings['respect_os_preference'] ),
'enableAnimation' => ! empty( $settings['enable_animation'] ),
'i18n' => array(
'light' => __( 'Switch to light mode', 'blocksy-light-dark-toggle' ),
'dark' => __( 'Switch to dark mode', 'blocksy-light-dark-toggle' ),
'lightLabel' => __( 'Light', 'blocksy-light-dark-toggle' ),
'darkLabel' => __( 'Dark', 'blocksy-light-dark-toggle' ),
),
)
);
}
/**
* Output floating toggle button.
*/
public static function render_toggle() {
if ( ! self::should_render() ) {
return;
}
$saved = self::get_initial_scheme();
$is_dark = 'dark' === $saved;
?>
<button
type="button"
id="blc-ldt-toggle"
class="blc-ldt-toggle<?php echo $is_dark ? ' blc-ldt-toggle--dark' : ''; ?>"
aria-pressed="<?php echo $is_dark ? 'true' : 'false'; ?>"
aria-label="<?php echo esc_attr( $is_dark ? __( 'Switch to light mode', 'blocksy-light-dark-toggle' ) : __( 'Switch to dark mode', 'blocksy-light-dark-toggle' ) ); ?>"
title="<?php echo esc_attr( $is_dark ? __( 'Light mode', 'blocksy-light-dark-toggle' ) : __( 'Dark mode', 'blocksy-light-dark-toggle' ) ); ?>"
>
<span class="blc-ldt-toggle__track" aria-hidden="true">
<span class="blc-ldt-toggle__thumb"></span>
</span>
<span class="blc-ldt-toggle__icon blc-ldt-toggle__icon--sun" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
</span>
<span class="blc-ldt-toggle__icon blc-ldt-toggle__icon--moon" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
</span>
</button>
<?php
}
/**
* Add helper body classes for page-specific dark-mode fixes.
*
* @param string[] $classes Body classes.
* @return string[]
*/
public static function body_class( $classes ) {
if ( is_admin() ) {
return $classes;
}
if ( is_page( 'codes' ) ) {
$classes[] = 'blc-ldt-page-codes';
}
if ( is_page( array( 'reservations', 'studio-boeken', 'reserveren', 'reserveringen', 'booking', 'planning' ) ) ) {
$classes[] = 'blc-ldt-page-reservations';
}
return $classes;
}
/**
* Persist preference for logged-in users.
*/
public static function ajax_save_scheme() {
check_ajax_referer( 'blc_ldt_save', 'nonce' );
$scheme = isset( $_POST['scheme'] ) ? sanitize_key( wp_unslash( $_POST['scheme'] ) ) : '';
if ( ! in_array( $scheme, array( 'light', 'dark' ), true ) ) {
wp_send_json_error( array( 'message' => 'Invalid scheme' ), 400 );
}
if ( is_user_logged_in() ) {
update_user_meta( get_current_user_id(), BLC_LDT_STORAGE, $scheme );
}
wp_send_json_success();
}
}