Files
OTSSigns-Website/theme/inc/theme-generator.php

307 lines
9.7 KiB
PHP

<?php
/**
* Theme Generator - Builds and caches a CSS file from saved design tokens.
*
* Reads theme-mods written by the admin settings page and produces a
* static CSS file in the uploads directory. The file is enqueued after
* main.css so that custom values override defaults.
*
* @package OTS_Theme
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Return the absolute filesystem path of the generated CSS file.
*
* Multi-site aware - each site gets its own file.
*
* @return string
*/
function oribi_generated_css_path() {
$upload_dir = wp_upload_dir();
$blog_id = get_current_blog_id();
return trailingslashit( $upload_dir['basedir'] ) . "oribi-theme-{$blog_id}-custom.css";
}
/**
* Return the public URL of the generated CSS file.
*
* @return string
*/
function oribi_generated_css_url() {
$upload_dir = wp_upload_dir();
$blog_id = get_current_blog_id();
return trailingslashit( $upload_dir['baseurl'] ) . "oribi-theme-{$blog_id}-custom.css";
}
/**
* Helper: convert a hex colour like #004225 to its "r,g,b" string.
*
* @param string $hex Hex colour with or without leading #.
* @return string e.g. "0,66,37"
*/
function oribi_hex_to_rgb( $hex ) {
$hex = ltrim( $hex, '#' );
if ( strlen( $hex ) === 3 ) {
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
}
if ( strlen( $hex ) !== 6 ) {
return '0,0,0';
}
$r = hexdec( substr( $hex, 0, 2 ) );
$g = hexdec( substr( $hex, 2, 2 ) );
$b = hexdec( substr( $hex, 4, 2 ) );
return "{$r},{$g},{$b}";
}
/**
* Build the CSS string from current theme-mod values.
*
* @return string Complete CSS content.
*/
function oribi_build_css() {
$s = 'oribi_get_setting'; // shorthand
// Gather values.
$primary = $s( 'color_primary' );
$primary_dk = $s( 'color_primary_dk' );
$primary_lt = $s( 'color_primary_lt' );
$accent = $s( 'color_accent' );
$accent_dk = $s( 'color_accent_dk' );
$accent_lt = $s( 'color_accent_lt' );
$dark = $s( 'color_dark' );
$dark_2 = $s( 'color_dark_2' );
$text = $s( 'color_text' );
$text_muted = $s( 'color_text_muted' );
$border = $s( 'color_border' );
$bg = $s( 'color_bg' );
$bg_alt = $s( 'color_bg_alt' );
// Dark mode.
$dk_primary = $s( 'dark_primary' );
$dk_primary_dk = $s( 'dark_primary_dk' );
$dk_primary_lt = $s( 'dark_primary_lt' );
$dk_accent = $s( 'dark_accent' );
$dk_accent_dk = $s( 'dark_accent_dk' );
$dk_accent_lt = $s( 'dark_accent_lt' );
$dk_dark = $s( 'dark_dark' );
$dk_dark_2 = $s( 'dark_dark_2' );
$dk_text = $s( 'dark_text' );
$dk_text_muted = $s( 'dark_text_muted' );
$dk_border = $s( 'dark_border' );
$dk_bg = $s( 'dark_bg' );
$dk_bg_alt = $s( 'dark_bg_alt' );
$dk_bg_dark = $s( 'dark_bg_dark' );
$dk_heading = $s( 'dark_heading' );
$dk_card_bg = $s( 'dark_card_bg' );
// Typography.
$body_font_slug = $s( 'font_family' );
$heading_font_slug = $s( 'font_heading' );
$body_font_css = oribi_get_font_family_css( $body_font_slug );
$heading_font_css = $heading_font_slug ? oribi_get_font_family_css( $heading_font_slug ) : $body_font_css;
// Border radii.
$r_sm = intval( $s( 'radius_sm' ) );
$r_md = intval( $s( 'radius_md' ) );
$r_lg = intval( $s( 'radius_lg' ) );
$r_xl = intval( $s( 'radius_xl' ) );
// Layout.
$c_max = intval( $s( 'container_max' ) );
$c_pad_min = floatval( $s( 'container_pad_min' ) );
$c_pad_max = floatval( $s( 'container_pad_max' ) );
$wide = intval( $s( 'wide_size' ) );
$primary_rgb = oribi_hex_to_rgb( $primary );
$accent_rgb = oribi_hex_to_rgb( $accent );
$dk_primary_rgb = oribi_hex_to_rgb( $dk_primary );
// Build CSS.
$css = <<<CSS
/* ================================================================
OTS Theme - Generated Theme Overrides
Generated: %s
================================================================ */
/* ── WordPress preset colour overrides ─────────────────────────── */
:root {
--wp--preset--color--primary: {$primary};
--wp--preset--color--primary-dk: {$primary_dk};
--wp--preset--color--primary-lt: {$primary_lt};
--wp--preset--color--accent: {$accent};
--wp--preset--color--accent-dk: {$accent_dk};
--wp--preset--color--accent-lt: {$accent_lt};
--wp--preset--color--dark: {$dark};
--wp--preset--color--dark-2: {$dark_2};
--wp--preset--color--text: {$text};
--wp--preset--color--text-muted: {$text_muted};
--wp--preset--color--border: {$border};
--wp--preset--color--bg: {$bg};
--wp--preset--color--bg-alt: {$bg_alt};
}
/* ── Light-mode aliases (consumed by main.css) ─────────────────── */
:root,
[data-theme="light"] {
--color-primary: {$primary};
--color-primary-dk: {$primary_dk};
--color-primary-lt: {$primary_lt};
--color-primary-rgb: {$primary_rgb};
--color-accent: {$accent};
--color-accent-dk: {$accent_dk};
--color-accent-lt: {$accent_lt};
--color-accent-rgb: {$accent_rgb};
--color-dark: {$dark};
--color-dark-2: {$dark_2};
--color-text: {$text};
--color-text-muted: {$text_muted};
--color-border: {$border};
--color-bg: {$bg};
--color-bg-alt: {$bg_alt};
--color-bg-dark: {$dark};
--color-heading: {$dark};
--header-scrolled-bg: rgba(255,255,255,.97);
--header-scrolled-text: {$text};
--card-bg: {$bg};
--form-bg: {$bg_alt};
--form-bg-focus: {$bg};
/* ── Typography ── */
--font-sans: {$body_font_css};
--font-heading: {$heading_font_css};
/* ── Border radii ── */
--radius-sm: {$r_sm}px;
--radius-md: {$r_md}px;
--radius-lg: {$r_lg}px;
--radius-xl: {$r_xl}px;
--wp--custom--radius--sm: {$r_sm}px;
--wp--custom--radius--md: {$r_md}px;
--wp--custom--radius--lg: {$r_lg}px;
--wp--custom--radius--xl: {$r_xl}px;
/* ── Layout ── */
--container-max: {$c_max}px;
--container-pad: clamp({$c_pad_min}rem, 5vw, {$c_pad_max}rem);
--wp--custom--container--max: {$c_max}px;
--wp--custom--container--pad: clamp({$c_pad_min}rem, 5vw, {$c_pad_max}rem);
}
/* ── Dark mode overrides ───────────────────────────────────────── */
[data-theme="dark"] {
--wp--custom--dark--primary: {$dk_primary};
--wp--custom--dark--primary-dk: {$dk_primary_dk};
--wp--custom--dark--primary-lt: {$dk_primary_lt};
--wp--custom--dark--accent: {$dk_accent};
--wp--custom--dark--accent-dk: {$dk_accent_dk};
--wp--custom--dark--accent-lt: {$dk_accent_lt};
--wp--custom--dark--dark: {$dk_dark};
--wp--custom--dark--dark-2: {$dk_dark_2};
--wp--custom--dark--text: {$dk_text};
--wp--custom--dark--text-muted: {$dk_text_muted};
--wp--custom--dark--border: {$dk_border};
--wp--custom--dark--bg: {$dk_bg};
--wp--custom--dark--bg-alt: {$dk_bg_alt};
--wp--custom--dark--bg-dark: {$dk_bg_dark};
--wp--custom--dark--heading: {$dk_heading};
--wp--custom--dark--card-bg: {$dk_card_bg};
--color-primary: {$dk_primary};
--color-primary-dk: {$dk_primary_dk};
--color-primary-lt: {$dk_primary_lt};
--color-primary-rgb: {$dk_primary_rgb};
--color-accent: {$dk_accent};
--color-accent-dk: {$dk_accent_dk};
--color-accent-lt: {$dk_accent_lt};
--color-dark: {$dk_dark};
--color-dark-2: {$dk_dark_2};
--color-text: {$dk_text};
--color-text-muted: {$dk_text_muted};
--color-border: {$dk_border};
--color-bg: {$dk_bg};
--color-bg-alt: {$dk_bg_alt};
--color-bg-dark: {$dk_bg_dark};
--color-heading: {$dk_heading};
--header-scrolled-bg: rgba(15,23,36,.97);
--header-scrolled-text: {$dk_text};
--card-bg: {$dk_card_bg};
--form-bg: {$dk_card_bg};
--form-bg-focus: #1A2538;
}
/* ── Typography application ────────────────────────────────────── */
body {
font-family: var(--font-sans);
}
h1, h2, h3, h4, h5, h6,
.wp-block-heading {
font-family: var(--font-heading, var(--font-sans));
}
/* ── WordPress layout overrides ────────────────────────────────── */
.wp-site-blocks > .wp-block-group,
.wp-site-blocks > .alignfull {
max-width: none;
}
body > .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) {
max-width: {$c_max}px;
}
CSS;
return sprintf( $css, gmdate( 'Y-m-d H:i:s' ) );
}
/**
* Write the generated CSS file to disk.
*
* @return bool|WP_Error True on success, WP_Error on failure.
*/
function oribi_write_generated_css() {
$css = oribi_build_css();
$path = oribi_generated_css_path();
// Ensure directory exists.
$dir = dirname( $path );
if ( ! file_exists( $dir ) ) {
wp_mkdir_p( $dir );
}
// Use WP_Filesystem for safe file writing.
require_once ABSPATH . 'wp-admin/includes/file.php';
global $wp_filesystem;
if ( ! WP_Filesystem() ) {
return new WP_Error( 'fs', __( 'Could not initialise filesystem.', 'ots-theme' ) );
}
$result = $wp_filesystem->put_contents( $path, $css, FS_CHMOD_FILE );
if ( ! $result ) {
return new WP_Error( 'write', __( 'Could not write generated CSS file.', 'ots-theme' ) );
}
// Bump version number so browsers cache-bust.
$version = intval( get_theme_mod( 'oribi_css_version', 0 ) ) + 1;
set_theme_mod( 'oribi_css_version', $version );
return true;
}
/**
* Regenerate the CSS file if it doesn't exist yet (e.g. first page load).
*
* Hooked early so the file is ready before wp_enqueue_scripts fires.
*/
add_action( 'init', function () {
if ( ! file_exists( oribi_generated_css_path() ) ) {
oribi_write_generated_css();
}
} );