GIF94;
| Path : /var/www/wordpress/wp-content/plugins/oxymade/includes/ |
| Current File : /var/www/wordpress/wp-content/plugins/oxymade/includes/color-palette.php |
<?php
namespace OxyMade\ColorPalette;
// Include required classes
if (!class_exists('OxyMade\\Includes\\Validator')) {
require_once plugin_dir_path(__FILE__) . 'class-oxymade-validator.php';
}
if (!class_exists('OxyMade\\Includes\\FileHandler')) {
require_once plugin_dir_path(__FILE__) . 'class-oxymade-file-handler.php';
}
if (!class_exists('OxyMade\\Includes\\Capabilities')) {
require_once plugin_dir_path(__FILE__) . 'class-oxymade-capabilities.php';
}
use OxyMade\Includes\Validator;
use OxyMade\Includes\FileHandler;
use OxyMade\Includes\Capabilities;
/**
* Register AJAX handler for saving color palette
*/
function register_ajax_handlers() {
\add_action('wp_ajax_oxymade_save_palette', __NAMESPACE__ . '\\save_color_palette');
\add_action('wp_ajax_oxymade_get_palette', __NAMESPACE__ . '\\get_color_palette');
}
\add_action('init', __NAMESPACE__ . '\\register_ajax_handlers');
/**
* Save color palette to WordPress options
*/
function save_color_palette() {
// Check rate limit (10 saves per minute max)
try {
Capabilities::check_rate_limit('save_color_palette', 10, MINUTE_IN_SECONDS);
} catch (\Exception $e) {
\wp_send_json_error(array(
'message' => \__('Rate limit exceeded', 'oxymade'),
'code' => 'rate_limit_exceeded'
));
exit;
}
// Check nonce for security (ALWAYS required - no exceptions)
if (!isset($_POST['nonce']) || !\wp_verify_nonce($_POST['nonce'], 'oxymade-color-palette')) {
\wp_send_json_error(array(
'message' => \__('Invalid or missing security token', 'oxymade'),
'code' => 'invalid_nonce'
));
exit;
}
// Check if user has permission
if (!\current_user_can('edit_theme_options')) {
\wp_send_json_error(array(
'message' => \__('You do not have permission to save color palette', 'oxymade'),
'code' => 'insufficient_permissions'
));
exit;
}
// Get palette data from POST
$palette = isset($_POST['palette']) ? $_POST['palette'] : null;
$flat_palette = isset($_POST['flatPalette']) ? $_POST['flatPalette'] : null;
// Validate structure
if (!is_array($palette) || !is_array($flat_palette)) {
\wp_send_json_error(array(
'message' => \__('Invalid palette data format', 'oxymade'),
'code' => 'invalid_format'
));
exit;
}
// Validate and sanitize palette
$validated_palette = Validator::validate_palette_structure($palette);
if (false === $validated_palette) {
\wp_send_json_error(array(
'message' => \__('Invalid palette structure', 'oxymade'),
'code' => 'invalid_structure'
));
exit;
}
// Sanitize flat palette
$sanitized_flat = Validator::sanitize_flat_palette($flat_palette);
if (empty($sanitized_flat)) {
\wp_send_json_error(array(
'message' => \__('No valid colors in palette', 'oxymade'),
'code' => 'no_valid_colors'
));
exit;
}
// Save both palette formats as JSON (wp_json_encode provides proper escaping)
\update_option('oxymade_color_palette', \wp_json_encode($validated_palette));
\update_option('oxymade_color_palette_flat', \wp_json_encode($sanitized_flat));
// Check if sync with Oxygen is enabled BEFORE syncing
$current_settings = get_option('oxymade_settings', []);
if (!is_array($current_settings)) {
$current_settings = [];
}
$sync_with_oxygen = $current_settings['sync_with_oxygen'] ?? true;
// Only update Breakdance if sync is enabled
if ($sync_with_oxygen) {
// Update Breakdance global colors
if (function_exists('\\Breakdance\\Data\\get_global_settings_array')) {
try {
$settings = \Breakdance\Data\get_global_settings_array();
update_breakdance_colors($sanitized_flat);
} catch (\Exception $e) {
// Silently handle Breakdance integration errors
}
}
// Sync ONLY color variables with Breakdance (not spacing, not typography)
if (class_exists('\\OxyMade\\Variables\\VariableManager')) {
try {
// Get all variables but only update the color collection
$all_variables = \OxyMade\Variables\VariableManager::get_color_and_spacing_variables();
// Filter to only color variables
$color_variables = array_filter($all_variables, function($var) {
$collection = $var['collection'] ?? '';
return $collection === 'OxyMade Colors';
});
if (!empty($color_variables)) {
$sync_result = \OxyMade\Variables\VariableManager::update_specific_variables(
array_values($color_variables),
['OxyMade Colors']
);
}
} catch (\Exception $e) {
// Silently handle variable sync errors
}
}
}
// Generate CSS file with variables (use sanitized/validated data)
$css_file_path = generate_css_file($sanitized_flat, $validated_palette);
// Only clear Breakdance cache if sync is enabled
if ($sync_with_oxygen && function_exists('\\Breakdance\\Render\\generateCacheForGlobalSettings')) {
try {
\Breakdance\Render\generateCacheForGlobalSettings();
} catch (\Exception $e) {
// Silently handle Breakdance cache clearing errors
}
}
\wp_send_json_success([
'message' => 'Color palette saved successfully',
'css_file' => $css_file_path
]);
exit;
}
/**
* Get color palette from WordPress options
*/
function get_color_palette() {
// Check nonce for security (ALWAYS required - no exceptions)
if (!isset($_GET['nonce']) || !\wp_verify_nonce($_GET['nonce'], 'oxymade-color-palette')) {
\wp_send_json_error(array(
'message' => \__('Invalid or missing security token', 'oxymade'),
'code' => 'invalid_nonce'
));
exit;
}
$palette_json = \get_option('oxymade_color_palette', '{}');
$flat_palette_json = \get_option('oxymade_color_palette_flat', '{}');
// Decode JSON to arrays
$palette = json_decode($palette_json, true);
$flat_palette = json_decode($flat_palette_json, true);
// Fallback to empty arrays if JSON is invalid
if (!is_array($palette)) {
$palette = [];
}
if (!is_array($flat_palette)) {
$flat_palette = [];
}
\wp_send_json_success([
'palette' => $palette,
'flatPalette' => $flat_palette
]);
exit;
}
/**
* Update Breakdance global settings with our color palette
*/
function update_breakdance_colors($flat_palette) {
// Get current global settings
$settings = \Breakdance\Data\get_global_settings_array();
// Update brand color
if (isset($flat_palette['base-primary'])) {
$settings['settings']['colors']['brand'] = $flat_palette['base-primary'];
}
// Update text color
if (isset($flat_palette['text-neutral'])) {
$settings['settings']['colors']['text'] = $flat_palette['text-neutral'];
}
// Update headings color
if (isset($flat_palette['heading-neutral'])) {
$settings['settings']['colors']['headings'] = $flat_palette['heading-neutral'];
}
// Update links color
if (isset($flat_palette['base-primary'])) {
$settings['settings']['colors']['links'] = $flat_palette['base-primary'];
}
// Update background color
if (isset($flat_palette['bg-neutral'])) {
$settings['settings']['colors']['background'] = $flat_palette['bg-neutral'];
}
// Update secondary color
if (isset($flat_palette['base-secondary'])) {
$settings['settings']['colors']['secondary'] = $flat_palette['base-secondary'];
}
// Update tertiary color
if (isset($flat_palette['base-tertiary'])) {
$settings['settings']['colors']['tertiary'] = $flat_palette['base-tertiary'];
}
// Update accent color
if (isset($flat_palette['base-accent'])) {
$settings['settings']['colors']['accent'] = $flat_palette['base-accent'];
}
// Add colors to the palette
$colors = [];
foreach ($flat_palette as $name => $value) {
// Skip non-hex values
if (!preg_match('/^#[0-9a-f]{3,6}$/i', $value)) {
continue;
}
$colors[] = [
'label' => ucfirst(str_replace('-', ' ', $name)),
'cssVariableName' => $name,
'value' => $value
];
}
// Update the palette
if (!empty($colors)) {
$settings['settings']['colors']['palette']['colors'] = $colors;
}
// Save the updated settings
\Breakdance\Data\save_global_settings(json_encode($settings));
}
/**
* Generate a CSS file with all color variables
*/
function generate_css_file($flat_palette, $structured_palette) {
if (empty($flat_palette)) {
return;
}
$css = ":root {\n";
// Add all flat palette variables
foreach ($flat_palette as $name => $value) {
// Skip -rgb keys as we generate those ourselves from base- colors
if (substr($name, -4) === '-rgb') {
continue;
}
$css .= " --{$name}: {$value};\n";
// Generate RGB values for colors needed for alpha variations
if (strpos($name, 'base-') === 0) {
$color_name = substr($name, 5); // Remove 'base-' prefix
$rgb = hex_to_rgb($value);
if ($rgb) {
$css .= " --{$color_name}-rgb: {$rgb['r']}, {$rgb['g']}, {$rgb['b']};\n";
}
}
}
// Add alpha variations for each main color
$css .= "\n /* Alpha Color Variables */\n";
// Define the main colors that need alpha variations
$main_colors = ['primary', 'secondary', 'tertiary', 'accent', 'neutral', 'white', 'black'];
// Create alpha variations from 10% to 90% for each color
foreach ($main_colors as $color) {
for ($alpha = 10; $alpha <= 90; $alpha += 10) {
$css .= " --{$color}-{$alpha}: rgba(var(--{$color}-rgb), 0.{$alpha});\n";
}
}
// Add fluid spacing variables with responsive scaling
$css .= "\n /* Fluid Spacing Variables */\n";
// Define specific spacing values based on requirements
$spacing_values = [
's1' => 'clamp(4px, calc(0.125rem + ((1vw - 3.6px) * 0.1852)), 4px)',
's2' => 'clamp(8px, calc(0.25rem + ((1vw - 3.6px) * 0.3704)), 8px)',
's3' => 'clamp(8px, calc(0.375rem + ((1vw - 3.6px) * 0.5556)), 12px)',
's4' => 'clamp(10px, calc(0.5rem + ((1vw - 3.6px) * 0.7407)), 16px)',
's5' => 'clamp(12px, calc(0.625rem + ((1vw - 3.6px) * 0.9259)), 20px)',
's6' => 'clamp(14px, calc(0.75rem + ((1vw - 3.6px) * 1.1111)), 24px)',
's7' => 'clamp(14px, calc(0.875rem + ((1vw - 3.6px) * 1.2963)), 28px)',
's8' => 'clamp(16px, calc(1rem + ((1vw - 3.6px) * 1.4815)), 32px)',
's9' => 'clamp(18px, calc(1.125rem + ((1vw - 3.6px) * 1.6667)), 36px)',
's10' => 'clamp(20px, calc(1.25rem + ((1vw - 3.6px) * 1.8519)), 40px)',
's12' => 'clamp(24px, calc(1.5rem + ((1vw - 3.6px) * 2.2222)), 48px)',
's14' => 'clamp(28px, calc(1.75rem + ((1vw - 3.6px) * 2.5926)), 56px)',
's16' => 'clamp(32px, calc(2rem + ((1vw - 3.6px) * 2.963)), 64px)',
's20' => 'clamp(40px, calc(2.5rem + ((1vw - 3.6px) * 3.7037)), 80px)',
's24' => 'clamp(48px, calc(3rem + ((1vw - 3.6px) * 4.4444)), 96px)',
's28' => 'clamp(56px, calc(3.5rem + ((1vw - 3.6px) * 5.1852)), 112px)',
's32' => 'clamp(64px, calc(4rem + ((1vw - 3.6px) * 5.9259)), 128px)',
's36' => 'clamp(72px, calc(4.5rem + ((1vw - 3.6px) * 6.6667)), 144px)',
's40' => 'clamp(80px, calc(5rem + ((1vw - 3.6px) * 7.4074)), 160px)',
's44' => 'clamp(88px, calc(5.5rem + ((1vw - 3.6px) * 8.1491)), 176px)',
's48' => 'clamp(96px, calc(6rem + ((1vw - 3.6px) * 9.2593)), 192px)',
's56' => 'clamp(112px, calc(7rem + ((1vw - 3.6px) * 11.029)), 224px)',
's64' => 'clamp(128px, calc(8rem + ((1vw - 3.6px) * 12.5926)), 256px)',
's72' => 'clamp(144px, calc(9rem + ((1vw - 3.6px) * 14.1491)), 288px)',
];
// Add spacing variables to CSS
foreach ($spacing_values as $name => $value) {
$css .= " --{$name}: {$value};\n";
}
// Define specific section spacing values based on requirements
$section_spacing_values = [
'ss-none' => '0px',
'ss-xxs' => 'clamp(10px, calc(0.625rem + ((1vw - 3.6px) * 0.9259)), 20px)',
'ss-xs' => 'clamp(20px, calc(1.25rem + ((1vw - 3.6px) * 1.8519)), 40px)',
'ss-sm' => 'clamp(32px, calc(2rem + ((1vw - 3.6px) * 2.963)), 64px)',
'ss-md' => 'clamp(40px, calc(2.5rem + ((1vw - 3.6px) * 3.7037)), 80px)',
'ss-lg' => 'clamp(64px, calc(4rem + ((1vw - 3.6px) * 5.9259)), 128px)',
'ss-xl' => 'clamp(80px, calc(5rem + ((1vw - 3.6px) * 7.4074)), 160px)',
'ss-xxl' => 'clamp(128px, calc(8rem + ((1vw - 3.6px) * 11.852)), 256px)'
];
// Add spacing variables to CSS
foreach ($section_spacing_values as $name => $value) {
$css .= " --{$name}: {$value};\n";
}
// Add tailwind border radius variables
$css .= "\n /* Tailwind Border Radius Variables */\n";
$css .= " --radius-none: 0px;\n";
$css .= " --radius-sm: 0.125rem;\n";
$css .= " --radius-md: 0.25rem;\n";
$css .= " --radius-lg: 0.5rem;\n";
$css .= " --radius-xl: 0.75rem;\n";
$css .= " --radius-2xl: 1rem;\n";
$css .= " --radius-3xl: 1.5rem;\n";
$css .= " --radius-full: 9999px;\n";
$css .= "}\n";
// Create the alt-shade class with flipped color values
$css .= "\n/* Alt-Shade Class - Flipped Color Variable Overrides */\n";
$css .= ".alt-shade, .alt-shade-hover:hover {\n";
// Define the flipping scheme for the alt-shade class
// Use flat_palette which has keys like "bg-primary", "dark-primary", etc.
foreach (['primary', 'secondary', 'tertiary', 'accent', 'neutral'] as $color) {
// Get shade values from flat palette
$bg = isset($flat_palette["bg-{$color}"]) ? $flat_palette["bg-{$color}"] : null;
$dark = isset($flat_palette["dark-{$color}"]) ? $flat_palette["dark-{$color}"] : null;
$surface = isset($flat_palette["surface-{$color}"]) ? $flat_palette["surface-{$color}"] : null;
$heading = isset($flat_palette["heading-{$color}"]) ? $flat_palette["heading-{$color}"] : null;
$subtle = isset($flat_palette["subtle-{$color}"]) ? $flat_palette["subtle-{$color}"] : null;
$active = isset($flat_palette["active-{$color}"]) ? $flat_palette["active-{$color}"] : null;
$border = isset($flat_palette["border-{$color}"]) ? $flat_palette["border-{$color}"] : null;
$text = isset($flat_palette["text-{$color}"]) ? $flat_palette["text-{$color}"] : null;
$muted = isset($flat_palette["muted-{$color}"]) ? $flat_palette["muted-{$color}"] : null;
$hover = isset($flat_palette["hover-{$color}"]) ? $flat_palette["hover-{$color}"] : null;
$base = isset($flat_palette["base-{$color}"]) ? $flat_palette["base-{$color}"] : null;
// Flip bg with dark
if ($bg && $dark) {
$css .= " --bg-{$color}: {$dark};\n";
$css .= " --dark-{$color}: {$bg};\n";
}
// Flip surface with heading
if ($surface && $heading) {
$css .= " --surface-{$color}: {$heading};\n";
$css .= " --heading-{$color}: {$surface};\n";
}
// Flip subtle with active
if ($subtle && $active) {
$css .= " --subtle-{$color}: {$active};\n";
$css .= " --active-{$color}: {$subtle};\n";
}
// Flip border with text
if ($border && $text) {
$css .= " --border-{$color}: {$text};\n";
$css .= " --text-{$color}: {$border};\n";
}
// Flip muted with hover
if ($muted && $hover) {
$css .= " --muted-{$color}: {$hover};\n";
$css .= " --hover-{$color}: {$muted};\n";
}
// Keep base the same
if ($base) {
$css .= " --base-{$color}: {$base};\n";
}
}
// Handle white and black specifically for alt-shade
// For white, we'll use the black value from the palette
if (isset($flat_palette['base-white']) && isset($flat_palette['base-black'])) {
$css .= " --base-white: {$flat_palette['base-black']};\n";
$rgb = hex_to_rgb($flat_palette['base-black']);
if ($rgb) {
$css .= " --white-rgb: {$rgb['r']}, {$rgb['g']}, {$rgb['b']};\n";
}
}
// For black, we'll use the white value from the palette
if (isset($flat_palette['base-black']) && isset($flat_palette['base-white'])) {
$css .= " --base-black: {$flat_palette['base-white']};\n";
$rgb = hex_to_rgb($flat_palette['base-white']);
if ($rgb) {
$css .= " --black-rgb: {$rgb['r']}, {$rgb['g']}, {$rgb['b']};\n";
}
}
$css .= "}\n";
// Add nested alt-shade class for double inversion (back to original)
$css .= "\n/* Nested Alt-Shade Class - Double Inversion Returns to Original */\n";
$css .= ".alt-shade .alt-shade, .alt-shade-hover:hover .alt-shade-hover {\n";
// For nested alt-shade, reapply the original values from flat_palette
foreach (['primary', 'secondary', 'tertiary', 'accent', 'neutral'] as $color) {
// Get shade values from flat palette
$bg = isset($flat_palette["bg-{$color}"]) ? $flat_palette["bg-{$color}"] : null;
$dark = isset($flat_palette["dark-{$color}"]) ? $flat_palette["dark-{$color}"] : null;
$surface = isset($flat_palette["surface-{$color}"]) ? $flat_palette["surface-{$color}"] : null;
$heading = isset($flat_palette["heading-{$color}"]) ? $flat_palette["heading-{$color}"] : null;
$subtle = isset($flat_palette["subtle-{$color}"]) ? $flat_palette["subtle-{$color}"] : null;
$active = isset($flat_palette["active-{$color}"]) ? $flat_palette["active-{$color}"] : null;
$border = isset($flat_palette["border-{$color}"]) ? $flat_palette["border-{$color}"] : null;
$text = isset($flat_palette["text-{$color}"]) ? $flat_palette["text-{$color}"] : null;
$muted = isset($flat_palette["muted-{$color}"]) ? $flat_palette["muted-{$color}"] : null;
$hover = isset($flat_palette["hover-{$color}"]) ? $flat_palette["hover-{$color}"] : null;
$base = isset($flat_palette["base-{$color}"]) ? $flat_palette["base-{$color}"] : null;
// Original values for bg and dark
if ($bg && $dark) {
$css .= " --bg-{$color}: {$bg};\n";
$css .= " --dark-{$color}: {$dark};\n";
}
// Original values for surface and heading
if ($surface && $heading) {
$css .= " --surface-{$color}: {$surface};\n";
$css .= " --heading-{$color}: {$heading};\n";
}
// Original values for subtle and active
if ($subtle && $active) {
$css .= " --subtle-{$color}: {$subtle};\n";
$css .= " --active-{$color}: {$active};\n";
}
// Original values for border and text
if ($border && $text) {
$css .= " --border-{$color}: {$border};\n";
$css .= " --text-{$color}: {$text};\n";
}
// Original values for muted and hover
if ($muted && $hover) {
$css .= " --muted-{$color}: {$muted};\n";
$css .= " --hover-{$color}: {$hover};\n";
}
// Keep base the same
if ($base) {
$css .= " --base-{$color}: {$base};\n";
}
}
// Restore original white and black values
if (isset($flat_palette['base-white'])) {
$css .= " --base-white: {$flat_palette['base-white']};\n";
$rgb = hex_to_rgb($flat_palette['base-white']);
if ($rgb) {
$css .= " --white-rgb: {$rgb['r']}, {$rgb['g']}, {$rgb['b']};\n";
}
}
if (isset($flat_palette['base-black'])) {
$css .= " --base-black: {$flat_palette['base-black']};\n";
$rgb = hex_to_rgb($flat_palette['base-black']);
if ($rgb) {
$css .= " --black-rgb: {$rgb['r']}, {$rgb['g']}, {$rgb['b']};\n";
}
}
$css .= "}\n";
// Write CSS file securely using FileHandler
$file_path = FileHandler::write_file('oxymade-variables.css', $css);
if (false === $file_path) {
error_log('OxyMade: Failed to write CSS variables file');
return false;
}
// Get URL to the CSS file
$css_url = FileHandler::get_file_url('oxymade-variables.css');
// Fix for HTTPS - ensure URL uses the correct protocol
$css_url = \set_url_scheme($css_url);
return $css_url;
}
/**
* Helper function to convert hex color to RGB values
*/
function hex_to_rgb($hex) {
// Remove # if present
$hex = str_replace('#', '', $hex);
// Handle both 3 and 6 digit hex codes
if (strlen($hex) == 3) {
$r = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1));
$g = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1));
$b = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1));
} else if (strlen($hex) == 6) {
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
} else {
return false;
}
return ['r' => $r, 'g' => $g, 'b' => $b];
}
/**
* Enqueue the color palette CSS file
*/
function enqueue_color_palette_css() {
// Check if colors are enabled
$colors_enabled = \get_option('oxymade_colors_enabled', true);
if (!$colors_enabled) {
return;
}
// Check if CSS file exists using FileHandler
if (FileHandler::file_exists('oxymade-variables.css')) {
$css_url = FileHandler::get_file_url('oxymade-variables.css');
// Fix for HTTPS - ensure URL uses the correct protocol
$css_url = set_url_scheme($css_url);
// Get modification time for cache busting
$dir_info = FileHandler::get_upload_dir();
if ($dir_info) {
$css_path = trailingslashit($dir_info['path']) . 'oxymade-variables.css';
$version = file_exists($css_path) ? filemtime($css_path) : OXYMADE_VERSION;
\wp_enqueue_style('oxymade-color-palette', $css_url, [], $version);
}
}
}
\add_action('wp_enqueue_scripts', __NAMESPACE__ . '\\enqueue_color_palette_css');
/**
* Enqueue the color palette CSS file in admin (only on OxyMade pages and Oxygen editor)
*/
function enqueue_color_palette_css_admin($hook) {
// Only load on OxyMade settings pages or Oxygen editor
$is_oxymade_page = strpos($hook, 'oxymade') !== false;
$is_oxygen_editor = isset($_GET['breakdance']) || isset($_GET['oxygen']) || (defined('BREAKDANCE_MODE') && \BREAKDANCE_MODE === 'oxygen');
if (!$is_oxymade_page && !$is_oxygen_editor) {
return;
}
enqueue_color_palette_css();
}
\add_action('admin_enqueue_scripts', __NAMESPACE__ . '\\enqueue_color_palette_css_admin');