Import CSS into Editor v4 Global Variables and Classes
PHP Code Snippet
Read this post first for important information on usage or watch the video.
Click Copy in the top right hand corner. Appears as ‘ImportGlobals’ menu item under Settings in WordPress Admin panel.
Editor v4 Limitations
- Cannot create classes that contain #, space, colon or dot (no multiple class declarations)
- No support (yet) for elements like body, h1, p, ul, li, nav etc
- No support for ::before and ::after or other psuedo elements
Import Tool Notes
- Import variables first by type: colors, font, then size
- Avoid shorthand properties like border: solid 1px #cccccc;
- Supported: box-shadow and background-image: linear-gradient().
- Use well formatted CSS – every property ends with ;
- No support for @media or @container queries
/*Snippet Name: Import Global Classes
Description: Adds an admin panel for importing CSS into Elementor Editor v4 global classes and variables.
Compatibale with Elementor version 3.35 beta 4
Version: 2.0
Author: Charles Emes and ChatGPT, and Imran Siddiq for inspiration
Modified to use get_post_meta and update_post_meta
*/
// Hook into admin menu to add settings page
add_action('admin_menu', 'importglobals_add_admin_menu');
function importglobals_add_admin_menu() {
$hook = add_submenu_page(
'options-general.php', // Parent menu: Settings
'ImportGlobals', // Page title
'ImportGlobals', // Menu title
'manage_options', // Capability required
'importglobals', // Menu slug
'importglobals_render_page' // Callback function
);
}
// Render the Import Globals admin page
function importglobals_render_page() {
// Check user permissions
if (!current_user_can('manage_options')) {
wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'importglobals'));
}
// Add nonce for security
wp_nonce_field('importglobals_action', 'importglobals_nonce');
$message = '';
$logOutput = '';
$jsonOutput = '';
$variablesJson = '';
// get the post id that Elementor uses to store global classes and variables in wp_postmeta
$postid = get_post_id_function();
// check the '_elementor_global_classes' row exists
$exists = false;
if (metadata_exists('post', $postid, '_elementor_global_classes')) {
// do nothing;
// print_red('do nothing for classes');
}
else { // insert it with the default of '{"items":[],"order":[]}'
$default = '{"items":[],"order":[]}';
update_post_meta($postid , '_elementor_global_classes', $default);
$message = 'Inserted global classes row';
print($message . "<br/>" );
$message = '';
}
// check the '_elementor_global_variables' row exists
$exists = false;
if (metadata_exists('post', $postid, '_elementor_global_variables')) {
// do nothing;
//print_red('do nothing for vars');
}
else { // insert it with the default of ''
update_post_meta($postid , '_elementor_global_variables', '');
$message = 'Inserted global variables row';
print($message . "<br/>");
$message = '';
}
// Handles the POST action on form submit
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$parser = new CSSParserToJson();
$logger = new ImportGlobalsLogger ();
if (isset($_POST['generate_json'])) {
$inputCSS = $_POST['css_input'] ?? '';
$classSort = $_POST['clsorder'] ?? '';
$jsonOutput = $parser->parse($inputCSS, $classSort, $logger);
$logOutput = currentLog($logger);
//$logOutput = implode(',', currentLog($logger));
}
if (isset($_POST['generate_variables'])) {
$vargroup = '';
if (isset($_POST['vargroup'])) {
$vargroup = $_POST['vargroup'];
} else {
$vargroup = 'global-color-variable';
}
$variablesInput = $_POST['variables_input'] ?? '';
$variablesSort = $_POST['varorder'] ?? '';
$arr_json = array();
$var_result = get_post_meta($postid , '_elementor_global_variables', true); //returns a json array of existing global variables or null if emtpy
if ($var_result !== ''){
$arr_json = json_decode($var_result, true);
}
else { // otherwise pass an empty array
$arr_json = array();
}
$variablesJson = $parser->parseVariables($variablesInput, $variablesSort, $vargroup, $arr_json );
}
if (isset($_POST['validate_classes'])) {
$jsonOutput = $_POST['json_output'] ?? '';
$jsonOutput = stripslashes( $jsonOutput);
$message = checkClasses($jsonOutput);
}
if (isset($_POST['update_elementor'])) {
$jsonOutput = $_POST['json_output'] ?? '';
$jsonOutput = stripslashes( $jsonOutput);
if ($jsonOutput == ''){
$message = "Nothing to do!";
}
else {
$mode = '';
if (isset($_POST['mode'])) {
$mode = $_POST['mode'];
} else {
$mode = 'append';
}
if ($mode == 'overwrite') {
$updated = update_post_meta($postid , '_elementor_global_classes', $jsonOutput);
$message = $updated !== false ? 'Elementor global classes updated. Go to Elementor->Tools->*CLEAR FILES AND DATA*' : 'Failed to update.';
}
else { // we are appending
//$message = "mode: " . $mode;
$str_json = get_post_meta($postid , '_elementor_global_classes', true); //returns a json array of existing global classes
$arr_json = json_decode($str_json, true);
$classMap = json_decode($jsonOutput, true);
$result = $parser->mergeClassMaps($arr_json, $classMap);
$str_result = json_encode($result, JSON_UNESCAPED_SLASHES);
$updated = update_post_meta($postid , '_elementor_global_classes', $str_result);
$message = $updated !== false ? 'Elementor global classes updated. Go to Elementor->Tools->*CLEAR FILES AND DATA*' : 'Failed to update.';
} // end of else append
} // end of else if ($jsonOutput == '')
}
if (isset($_POST['validate_variables'])) {
$variablesJson = $_POST['variables_output'] ?? '';
$variablesJson = stripslashes($variablesJson);
$message = checkVars($variablesJson) ;
}
if (isset($_POST['update_variables'])) {
$variablesJson = $_POST['variables_output'] ?? '';
$variablesJson = stripslashes($variablesJson);
$updated = update_post_meta($postid , '_elementor_global_variables', $variablesJson);
$message = $updated !== false ? 'Elementor global variables updated.' : 'Failed to update.';
}
if (isset($_POST['reset'])) {
$default = '{"items":[],"order":[]}';
if (get_post_meta($postid , '_elementor_global_classes', true) != $default){
$updated = update_post_meta($postid , '_elementor_global_classes', $default);
$message = $updated !== false ? 'Reset successful.' : 'Failed to reset.';
}
else {
$message = 'Reset not necessary.';
}
}
if (isset($_POST['resetvars'])) {
$default = ''; // set meta_value to null
if (get_post_meta($postid , '_elementor_global_variables', true) != $default){
$updated = update_post_meta($postid , '_elementor_global_variables', $default);
$message = $updated !== false ? 'Reset successful.' : 'Failed to reset.';
}
else {
$message = 'Reset not necessary.';
}
}
}
// ** Start of the form **//
?><div style="width:100%;">
<h1>CSS to Elementor Global Classes</h1>
<p>Requires Elementor to be installed and Editor v4 active.</p>
<p>Backup your database before updating. Maximum of 100 classes.</p>
<p>Supports :hover :focus :active provided they immediately follow the class - no support for multiple classes like .toggle-icon .middle-bar{}.
Supports properties with a single value - either a value like 10px or a variable like var(--space-s).
Limited support for shorthand properties. Not supported: {padding: 0px 1px;} {border:solid 1px #CCCCCC;} Supported: box-shadow and background-image: linear-gradient().
No support for id classes like #mybtn, element classes like body or h1, h2, h3, pseudo classes like ::before ::after, @media queries or @container</p>
<?php if (!empty($message)): ?>
<div style="padding:10px; background-color: #fff3cd; border-left: 4px solid #ffeeba; margin-bottom: 15px;">
<?php echo esc_html($message); ?>
</div>
<?php endif; ?>
<form method="post">
<div style="display: flex; width:98%;column-gap: 1%;padding-right: 1%;">
<div style="display:inline-block;width:33%;">
<h2>Variables Input (one variable per line):</h2>
<div style="display: inline">
<input type="radio" id="color" name="vargroup" value="global-color-variable" checked>
<label for="color" style="padding-right:20px;">Color</label>
<input type="radio" id="font-size" name="vargroup" value="global-font-variable" >
<label for="font-size" style="padding-right:20px;">Font family</label>
<input type="radio" id="space" name="vargroup" value="global-size-variable" >
<label for="space">Size</label>
</div>
<textarea name="variables_input" rows="10" style="width: 100%;"><?php echo esc_textarea($_POST['variables_input'] ?? ''); ?></textarea>
<p><input type="submit" name="generate_variables" class="button button-primary" value="Generate Variables JSON"> <input name="varorder" id="varorder" type=checkbox checked=checked><label for="varorder">Sort A-Z</label></p>
<h2>Variables JSON Output:</h2>
<textarea readonly name="variables_output" rows="10" style="width: 100%;"><?php echo esc_textarea($variablesJson); ?></textarea>
<p>
<input type="submit" name="validate_variables" class="button button-secondary" value="Validate Json Variables">
<input type="submit" name="update_variables" class="button button-secondary" value="Update Elementor Global Variables">
<input type="submit" name="resetvars" class="button button-danger" value="Reset Variables" onclick="return confirm('Are you sure you want to reset the global variables?')">
</p>
</div>
<div style="display:inline-block;width:33%;">
<h2>CSS classes Input</h2>
<div style="display: inline">
<input type="radio" id="overwrite" name="mode" value="overwrite">
<label for="overwrite" style="color:red;padding-right:20px;">Overwrite</label>
<input type="radio" id="append" name="mode" value="append" checked>
<label for="append">Append</label>
</div>
<textarea name="css_input" rows="10" style="width: 100%;"><?php echo esc_textarea($_POST['css_input'] ?? ''); ?></textarea>
<p><input type="submit" name="generate_json" class="button button-primary" value="Generate JSON"> <input name="clsorder" id="clsorder" type=checkbox checked=checked><label for="clsorder">Sort A-Z</label></p>
<h2>Generated JSON Output:</h2>
<textarea readonly name="json_output" rows="10" style="width: 100%;"><?php echo esc_textarea($jsonOutput); ?></textarea>
<p>
<input type="submit" name="validate_classes" class="button button-secondary" value="Validate Json Classes">
<input type="submit" name="update_elementor" class="button button-secondary" value="Update Elementor Global Classes" onclick="return confirm('Are you sure you want to amend the global classes? Check if you selected Overwrite or Append.')">
<input type="submit" name="reset" class="button button-danger" value="Reset" onclick="return confirm('Are you sure you want to reset the global classes? ')">
</p>
</div>
<div style="display:inline-block;width:33%;">
<h2>Log of Convert CSS -> JSON</h2>
<span> </span>
<textarea readonly name="log_output" rows="25" style="width: 100%;"><?php echo esc_textarea($logOutput); ?></textarea>
</div>
</div>
</form>
</div>
<?php
}
// ** End of the form **//
// utility function to echo messages in red
function print_red($message){
print "<font color ='red'>" . $message . "<br/></font>";
}
// utility function to validate JSON variables
function checkVars($jsonInput){
$validator = new SimpleJsonValidator();
$validator->registerSchema('schema1', 'validateVariables');
$errors = $validator->validate($jsonInput, 'schema1');
if (empty($errors)) {
return "Validation passed.\n";
} else {
return "Validation failed:\n" . implode("\n", $errors);
}
}
// utility function to validate JSON classes
function checkClasses($jsonInput){
$validator = new SimpleJsonValidator();
$validator->registerSchema('schema2', 'validateClasses');
$errors = $validator->validate($jsonInput, 'schema2');
if (empty($errors)) {
return "Validation passed.\n";
} else {
return "Validation failed:\n" . implode("\n", $errors);
}
}
// utility function to get the post id
function get_post_id_function(){
$postid = (int) get_option('elementor_active_kit');
// Fallback if the above does not work
if ($postid <= 0) {
$q = new WP_Query([
'post_type' => 'elementor_library',
'post_status' => 'publish',
'posts_per_page' => 1,
'tax_query' => [[
'taxonomy' => 'elementor_library_type',
'field' => 'slug',
'terms' => ['kit'],
] ],
'fields' => 'ids',
]);
if (!empty($q->posts[0])) {
$postid = (int) $q->posts[0];
}
else { // no rows found
$message = 'Unable to find row in posts table for Default Kit';
print_red($message);
}
}
return $postid ;
}
// main class to do the parsing of the CSS to JSON format
class CSSParserToJson {
public function parse(string $cssString, string $clsSort , ImportGlobalsLogger $logger ) {
$logger->log('Begin parse of CSS string length ' . strlen($cssString) );
$cssString = preg_replace("/\s+/", "", $cssString); // removes all the whitespace - CR and space
preg_match_all('/\.([a-zA-Z0-9_-]+)(:hover|:active|:focus)?\{([^}]*)\}/', $cssString, $matches, PREG_SET_ORDER);
$classMap = [];
$lastBaseClass = null;
$classcount = 0;
foreach ($matches as $match) {
$classname = $match[1];
$logger->log('Class name ' . $classname);
$pseudo = $match[2] ?? null;
$rules = $match[3];
$state = $pseudo ? ltrim($pseudo, ':') : null;
if ($state === null) {
$lastBaseClass = $classname;
$id = 'g-' . $this->generate_unique_hex_string(7);
$logger->log(' global class id ' . $id );
$classMap[$classname] = [
'id' => $id,
'type' => 'class',
'label' => $classname,
'variants' => []
];
}
$baseClass = $state ? $lastBaseClass : $classname;
if (!isset($classMap[$baseClass])) continue;
if (!empty($rules)) { // if there are no properties then we get an empty variants[] array
$rawProps = [];
foreach (explode(';', $rules) as $rule) {
if (trim($rule) === '') continue;
$property = substr($rule,0,strpos($rule,":"));
$logger->log(' property = ' . $property );
if ($this->unsupportedProperties($property)){
$logger->log('*** Unsupported property = ' . $property . ' ignored ***' );
}
else {
list($propName, $propValue) = array_map('trim', explode(':', $rule, 2));
if ($propName=='top'){ $propName = 'inset-block-start';}
if ($propName=='bottom'){ $propName = 'inset-block-end';}
if ($propName=='left'){ $propName = 'inset-inline-start';}
if ($propName=='right'){ $propName = 'inset-inline-end';}
if ($propValue=='left'){ $propValue = 'start';}
if ($propValue=='right'){ $propValue = 'end';}
$rawProps[$propName] = $propValue;
}
} // end of for each property
$props = $this->normalizeProps($rawProps, $logger);
$props = $this->rename_array_key($props, 'background-image', 'background');
$classMap[$baseClass]['variants'][] = [
'meta' => [
'breakpoint' => 'desktop',
'state' => $state
],
'props' => $props,
'custom_css' => null
];
} // end of if (!empty($rules))
//increment class counter
$classcount = $classcount + 1;
if ($classcount==100) {
print_red('Maximum class count of 100 reached.') ;
$logger->log('****Maximum class count of 100 reached. Processing aborted.');
break;
}
} // end of for each class
if ($clsSort == "on") {
usort($classMap, fn($a, $b) => strcmp($a['label'], $b['label'])); // sort A - Z
}
$result = ['items' => [], 'order' => []];
foreach ($classMap as $item) {
$result['items'][$item['id']] = $item;
$result['order'][] = $item['id'];
}
$logger->log('End - CSS classes parsed ' . $classcount);
return json_encode($result, JSON_UNESCAPED_SLASHES);
}
private function rename_array_key(array $array, string $oldKey, string $newKey): array {
$newArray = [];
foreach ($array as $key => $value) {
// Replace the key if it matches
if ($key === $oldKey) {
$key = $newKey;
}
// Recursively handle nested arrays
// if (is_array($value)) {
// $value = $this->rename_array_key($value, $oldKey, $newKey);
// }
$newArray[$key] = $value;
}
return $newArray;
}
private function inferType($prop, $value) {
$size_properties = ['font-size', 'padding', 'margin', 'width', 'left', 'right', 'top', 'bottom', 'spacing', 'height', 'gap', 'radius', 'opacity', 'inset', 'inline', 'block'];
$size_property_match = false;
foreach ($size_properties as $keyword) {
if (str_contains($prop, $keyword)) {
$size_property_match = true;
break;
}
}
if ($size_property_match) {
if (preg_match('/^var\(--[a-zA-Z0-9_-]+\)$/', $value)) { //it might be a size variable
$variableId = $this->getElementorVariableIdFromLabel($value);
if ($variableId) { return 'global-size-variable'; } else {return 'size';}
}
else {
return 'size';
}
}
if (str_contains($prop, 'background-color')) {
if (preg_match('/^var\(--[a-zA-Z0-9_-]+\)$/', $value)) { //its a color variable
return 'global-color-variable';
}
else {
return 'background';
}
}
if (str_contains($prop, 'background-image')) return 'background';
if (str_contains($prop, 'stroke')) return 'stroke';
if (str_contains($prop, 'box-shadow')) return 'box-shadow';
if ($prop=='z-index' || $prop=='column-count') return 'number';
if (str_contains($prop, 'color')) {
if (preg_match('/^var\(--[a-zA-Z0-9_-]+\)$/', $value)) { //its a color variable
return 'global-color-variable';
}
else {
return 'color';
}
}
if (str_contains($prop, 'font-family')) {
if (preg_match('/^var\(--[a-zA-Z0-9_-]+\)$/', $value)) { //its a font variable
return 'global-font-variable';
}
else {
return 'string';
}
}
return 'string'; // catch all for all the other CSS properties
}
private function normalizeProps($rawProps, ImportGlobalsLogger $logger ) {
$output = [];
$logicalGroups = [
'padding' => [
'props' => [
'padding-top' => 'block-start',
'padding-bottom' => 'block-end',
'padding-left' => 'inline-start',
'padding-right' => 'inline-end'
],
'$$type' => 'dimensions'
],
'margin' => [
'props' => [
'margin-top' => 'block-start',
'margin-bottom' => 'block-end',
'margin-left' => 'inline-start',
'margin-right' => 'inline-end'
],
'$$type' => 'dimensions'
],
'border-radius' => [
'props' => [
'border-radius-top' => 'start-start',
'border-radius-bottom' => 'start-end',
'border-radius-left' => 'end-start',
'border-radius-right' => 'end-end'
],
'$$type' => 'border-radius'
],
'border-width' => [
'props' => [
'border-width-top' => 'block-start',
'border-width-bottom' => 'block-end',
'border-width-left' => 'inline-start',
'border-width-right' => 'inline-end'
],
'$$type' => 'border-width'
],
'gap' => [
'props' => [
'column-gap' => 'column',
'row-gap' => 'row',
],
'$$type' => 'layout-direction'
],
'stroke' => [
'props' => [
'stroke' => 'color' ,
'stroke-width' => 'width'
],
'$$type' => 'stroke'
],
'background' => [
'props' => [
'background-color' => 'color'
],
'$$type' => 'background'
]
];
foreach ($logicalGroups as $groupKey => $config) {
$valueObj = [];
foreach ($config['props'] as $cssProp => $logicalKey) {
if (isset($rawProps[$cssProp])) {
$raw = trim($rawProps[$cssProp]);
unset($rawProps[$cssProp]);
if (in_array($raw, ['auto', 'inherit', 'initial'])) {
return [
'size' => '',
'unit' => $raw
];
}
if (preg_match('/^(\d+(?:\.\d+)?)(px|em|rem|%)$/', $raw, $m)) {
$logger->log(' ' . $logicalKey . ', $$type = size, value = ' . $raw . ' (527)');
$valueObj[$logicalKey] = ['$$type' => 'size', 'value' => ['size' => floatval($m[1]), 'unit' => $m[2]]];
}
if (preg_match('/^var\(--[a-zA-Z0-9-]+\)$/', $raw)) {
//check to see if its a GLOBAL variable
$variableid = $this->getElementorVariableIdFromLabel($raw);
if ($variableid !== ''){
$typeVar = $this->inferType($cssProp, $raw);
$logger->log(' $typeVar ' . $typeVar);
$valueObj[$logicalKey] = ['$$type' => $typeVar, 'value' => $variableid];
$logger->log(' ' . $logicalKey . ', $$type = ' . $typeVar . ', value = ' . $raw . ', var id = ' . $variableid . ' (537)');
}
else { // its not a global variable so assign it anyway as custom
$logger->log(' ' . $logicalKey . ', $$type = size, variable = ' . $raw . ' (540)');
$valueObj[$logicalKey] = ['$$type' => 'size', 'value' => ['size' => $raw, 'unit' => 'custom']];
}
}
elseif (preg_match('/^(#|hsl|hsla|rgb|rgba)/', $raw)){ //stroke is a color
$logger->log(' ' . $logicalKey . ', $$type = color, value = ' . $raw . ' (545)' );
$valueObj[$logicalKey] = ['$$type' => 'color', 'value' => $raw];
}
elseif ($raw == 0) { //minify removes the unit when value is 0
$logger->log(' ' . $logicalKey . ', $$type = size, variable = ' . $raw . ' (549)');
$valueObj[$logicalKey] = ['$$type' => 'size', 'value' => ['size' => 0, 'unit' => 'px']];
}
else {
$valueObj[$logicalKey] = null;
}
} else { // (isset($rawProps[$cssProp]) is false
$valueObj[$logicalKey] = null;
}
}
if (!empty(array_filter($valueObj, fn($v) => $v !== null))) {
$output[$groupKey] = ['$$type' => $config['$$type'], 'value' => $valueObj];
}
}
foreach ($rawProps as $name => $value) {
$logger->log(' $$type ' . $this->inferType($name, $value) . ' (565)');
$output[$name] = ['$$type' => $this->inferType($name, $value), 'value' => $this->normalizeValue($value,$logger, $name)];
}
return $output;
}
private function normalizeValue($value, ImportGlobalsLogger $logger, $propName = '') {
$value = trim($value, '"');
// Detect variable and resolve its ID
if (preg_match('/^var\(--[a-zA-Z0-9_-]+\)$/', $value)) {
// applies for color, font and size variables
$variableId = $this->getElementorVariableIdFromLabel($value);
$logger->log(' ' . $propName . ', variable = ' . $variableId . ' (578)');
if ($variableId) {
return $variableId;
}
$logger->log(' ' . $propName . ', no global size variable called ' . $value . ' (583)');
return [
'size' => $value,
'unit' => 'custom'
];
}
// Regular color like #00ff00 or hsla()
if (str_contains($propName, 'color') && preg_match('/^(#|hsl|hsla|rgb|rgba)/', $value)) {
$logger->log(' ' . $propName . ', value = ' . $value . ' (592)');
return $value;
}
// Size value
if (preg_match('/^(\d+(?:\.\d+)?)(px|em|rem|%)$/', $value, $m)) {
$logger->log(' ' . $propName . ', size = ' . $value . ' (598)');
return [
'size' => floatval($m[1]),
'unit' => $m[2]
];
}
if ($propName == 'box-shadow') {
$boxvalues = (explode(" ",$value));
if (count($boxvalues) >= 5){
$hOffset = $this->get_size_values_function($boxvalues[0]);
$vOffset = $this->get_size_values_function($boxvalues[1]);
$blur = $this->get_size_values_function($boxvalues[2]);
$spread = $this->get_size_values_function($boxvalues[3]);
$color = $this->get_color_function($value); // it will be exploded if using rgba or hsla
$logger->log(' hOffset = ' . $boxvalues[0] . ', vOffset = ' . $boxvalues[1] . ', blur = ' . $boxvalues[2] . ', spread = ' . $boxvalues[3] . ', color = ' . $boxvalues[3] . ', position = null (612)' );
return [
['$$type' => 'shadow',
'value' => [
'hOffset' => $hOffset,
'vOffset' => $vOffset,
'blur' => $blur,
'spread' => $spread,
'color' => $color,
'position' => null
]
]
];
}
else { //count($boxvalues)
$logger->log('** To few parameters in box-shadow - requires at least 5 - needs fixing (627)');
print_red('To few parameters in box-shadow - requires at least 5.');
}
}
if ($propName == 'background-image') {
if (str_contains($value, 'linear')){
$gradient_type = ['$$type' => 'string', 'value' => 'linear'];
if (preg_match('/\b(0|[1-9]\d?|[12]\d\d|3[0-5]\d|360)deg\b/', $value, $degs)) {
$degrees = ['$$type' => 'number', 'value' => $degs[1]]; // $degs[1]; Output: 180
$logger->log(' linear-gradient, degrees = '. $degs[1] . ' (636)');
}
else {
$logger->log('**Deg value not found in background-image property, needs fixing (639)');
print_red('Deg value not found in background-image property');
return;
}
if (preg_match_all('/(?:rgb|rgba|hsl|hsla)\([^)]*\)\s+(100|[1-9]?\d)%/', $value, $matches)){
$stop = $matches[1];
}
else {
$logger->log('**Stop % not found in background-image property, needs fixing (648)');
print_red('Stop % not found in background-image property');
return;
}
// if (preg_match_all('/(?:rgba?|hsla?)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)|var\(--[a-zA-Z0-9_-]+\)/', $value, $rgbs)) {
// if (preg_match_all('/rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)/', $value, $rgbs)) {
if (preg_match_all('/(?:hsla?|rgba?)\(\s*[^)]+\)/', $value, $rgbs)) {
$colortype1 = 'color';
$colortype2 = 'color';
// $stopcolor = $rgbs[0];
$stopcolor1 = $rgbs[0][0];
$stopcolor2 = $rgbs[0][1];
// repeat for $stopcolor2
if (str_contains($stopcolor1, 'var')){
// replace with color variable id
// not supported by editor yet
// $colortype1 = 'global-color-variable';
// $stopcolor1 = $this->getElementorVariableIdFromLabel($stopcolor1)
}
$logger->log(' color1 = ' . $stopcolor1 . ', offset1 = ' . $stop[0] . ', color2 = ' . $stopcolor2 . ', offset2 = ' . $stop[1] . ' (648)' );
$arrstop1 = [
'$$type' => 'color-stop' ,
'value'=> [
'color' => [
'$$type' => $colortype1,
'value' => $stopcolor1
],
'offset' => [
'$$type' => 'number',
'value' => $stop[0]
]
]
];
$arrstop2 = [
'$$type' => 'color-stop' ,
'value'=> [
'color' => [
'$$type' => $colortype2,
'value' => $stopcolor2
],
'offset' => [
'$$type' => 'number',
'value' => $stop[1]
]
]];
}
else {
$logger->log('**Color values not found in background-image property, needs fixing (695)');
print_red('Color values not found in background-image property');
return;
}
return [
'background-overlay' => [
'$$type'=> 'background-overlay',
'value' => [[
'$$type' => 'background-gradient-overlay',
'value' => [
'type' => $gradient_type,
'angle' => $degrees,
'stops' => ['$$type' => 'gradient-color-stop', 'value' => [$arrstop1, $arrstop2] ]
]
]]
]
];
}
else {
$logger->log('**Linear gradient not found in background-image property, needs fixing (716)');
print_red('Linear gradient not found in background-image property');
return;
}
}
if (in_array($value, ['auto', 'inherit', 'initial'])) {
return [
'size' => '',
'unit' => $value
];
}
if ($value == 0){ // minify removes the px so put it back
return [
'size' => 0,
'unit' => 'px'
];
}
// fix for properties that just have numbers
if (($propName == 'line-height') || ($propName == 'z-index') || ($propName == 'column-count') || ($propName == 'grid-column-start') || ($propName == 'grid-column-end') || ($propName == 'grid-row-start') || ($propName == 'grid-row-end')) {
if (strval($value) == strval(floatval($value))){
return floatval($value);
}
}
return $value;
}
private function get_size_values_function($value) {
// Size value
if (preg_match('/^(\d+(?:\.\d+)?)(px|em|rem|%)$/', $value, $m)) {
return [
'$$type' => 'size',
'value' => [
'size' => floatval($m[1]),
'unit' => $m[2]
]
];
}
if (preg_match('/^var\(--[a-zA-Z0-9_-]+\)$/', $value)) {
// not yet implemented for global size vars
}
}
private function get_color_function($value){
$color = '';
$variable = '';
// Regex patterns for hex, rgba, and hsla
$patterns = [
// Hex (#RGB, #RRGGBB, #RRGGBBAA)
'/#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b/',
// RGBA (rgba(255,255,255,1))
'/rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0?\.\d+)\s*\)/i',
// HSLA (hsla(120, 100%, 50%, 0.5))
'/hsla\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*(0|1|0?\.\d+)\s*\)/i'
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $value, $matches)) {
$color = $matches[0]; // Return the first match
}
}
if ($color == '') { // if that was no match look for a variable
if (preg_match('/var\(\s*--[a-zA-Z0-9_-]+\s*\)/', $value, $matches)) {
$variable = $matches[0]; // Return the first match
}
}
if ($color != '') {
return [
'$$type' => 'color',
'value' => $color
];
} /// end check for rgb
if ($variable != '') {
$variableId = $this->getElementorVariableIdFromLabel($variable);
if ($variableId) {
return [
'$$type' => 'global-color-variable',
'value' => $variableId
];
}
} // end check for color var
}
// merge two arrays
public function mergeClassMaps(array $arr_json, array $classMap): array {
// Ensure required keys exist
$arr_json['items'] = $arr_json['items'] ?? [];
$arr_json['order'] = $arr_json['order'] ?? [];
if (!isset($classMap['items'], $classMap['order'])) {
return $arr_json;
}
// Merge items (classMap items are appended)
foreach ($classMap['items'] as $id => $item) {
$arr_json['items'][$id] = $item;
}
// Prepend new order IDs (keeping existing ones)
$arr_json['order'] = array_values(array_unique(
array_merge($classMap['order'], $arr_json['order'])
));
return $arr_json;
}
private function unsupportedProperties($searchstr) {
$unsupported = array("text-decoration-line", "text-decoration-color","transition", "transform", "scroll-margin", "scroll-padding");
if (in_array($searchstr, $unsupported)){
return true;
}
else {
return false;
}
}
private function getElementorVariableIdFromLabel($varString) {
if (!preg_match('/var\(--([a-zA-Z0-9_-]+)\)/', $varString, $matches)) {
return null;
}
$label = $matches[1];
$postid = get_post_id_function();
$json =get_post_meta($postid , '_elementor_global_variables', true);
if (!$json) return null;
$data = json_decode($json, true);
if (!isset($data['data'])) return null;
foreach ($data['data'] as $id => $variable) {
if ($variable['label'] === $label) {
return $id;
}
}
return null;
}
private function generate_unique_hex_string($length) {
$byte_length = ceil($length / 2);
$random_bytes = random_bytes($byte_length);
return substr(bin2hex($random_bytes), 0, $length);
}
public function parseVariables($input, $sort, $vartype, array &$data) {
$lines = explode("\n", $input);
if ($sort == "on") {
sort($lines); //sort A-Z
}
$maxOrder = 0;
if (!empty($data)){
foreach ($data['data'] as $item) {
if (isset($item['order']) && $item['order'] > $maxOrder) {
$maxOrder = $item['order'];
}
}
}
$varcount = $maxOrder + 1; // if its zero set to 1, or set it to next highest number
foreach ($lines as $line) {
$line = trim($line);
if (preg_match('/^--([a-zA-Z0-9-_]+)\s*:\s*([^;]+);?$/', $line, $matches)) {
$label = $matches[1];
$value = trim($matches[2]);
// if (str_length($value) == 4){ // minify shortened #cccccc to #ccc
// $value = $value . substr($value,1); // reinstate the 6 character hex code
// }
$id = 'e-gv-' . $this->generate_unique_hex_string(7);
if ($vartype =='global-size-variable'){
$data['data'][$id] = [
'type' => $vartype,
'label' => $label,
'value' => [
'$$type' => 'size',
'value' => [
'size' => $value,
'unit' => 'custom'
]
] ,
'order' => $varcount
];
}
else { //its either a color or font global variable
$data['data'][$id] = [
'type' => $vartype,
'label' => $label,
'value' => $value,
'order' => $varcount
];
}
}
$varcount = $varcount + 1;
}
// Add watermark and version if not already set
if (!isset($data['watermark'])) {
$data['watermark'] = 1;
}
if (!isset($data['version'])) {
$data['version'] = 1;
}
return json_encode($data, JSON_UNESCAPED_SLASHES);
} // end public function
} // end of class CSSParserToJSON
// class to validate the JSON output
class SimpleJsonValidator {
/**
* Holds registered schema validators.
* @var array<string, callable>
*/
private array $schemas = [];
/**
* Register a named schema with its validation function
*/
public function registerSchema(string $name, callable $validator): void {
$this->schemas[$name] = $validator;
}
/*** Validate a JSON string against a named schema
*
* @param string $jsonString The JSON string to validate
* @param string $schemaName The name of the schema to use
* @return array An array of error messages (empty if valid)
*/
public function validate(string $jsonString, string $schemaName): array
{
if (!isset($this->schemas[$schemaName])) {
throw new InvalidArgumentException("Schema '$schemaName' is not registered.");
}
$data = json_decode($jsonString, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return ['Invalid JSON: ' . json_last_error_msg()];
}
// Call the validator function with decoded data
return ($this->schemas[$schemaName])($data);
}
} // end of Class
function validateVariables(array $data): array {
$errors = [];
foreach (['data', 'watermark', 'version'] as $key) {
if (!isset($data[$key])) {
$errors[] = "Missing required field: $key";
}
}
if (isset($data['data']) && is_array($data['data'])) {
foreach ($data['data'] as $key => $entry) {
if (!preg_match('/^e-gv-[a-f0-9]{7}$/', $key)) { // /^g-[a-f0-9]{7}$/
$errors[] = "Invalid key format: $key (must match e-gv-XXXXXXX)";
}
foreach (['type', 'label', 'value'] as $field) {
if (empty($entry[$field])) {
$errors[] = "Missing '$field' in $key";
}
}
if (isset($entry['type']) && !in_array($entry['type'], ['global-font-variable', 'global-color-variable', 'global-size-variable'])) {
$errors[] = "Invalid type '{$entry['type']}' in $key";
}
}
}
return $errors;
}
function validateClasses(array $data): array {
$errors = [];
$validTypes = [
'size', 'color', 'string', 'number', 'box-shadow', 'dimensions', 'background', 'global-font-variable', 'global-color-variable', 'global-size-variable', 'border-radius', 'border-width', 'stroke'
];
$validUnits = ['px', 'em', 'rem', '%', 'vw', 'vh', 'auto', 'initial', 'inherit', 'custom'];
if (!isset($data['items']) || !is_array($data['items'])) {
$errors[] = "'items' must be a valid object";
return $errors;
}
if (!isset($data['order']) || !is_array($data['order'])) {
$errors[] = "'order' must be an array";
}
foreach ($data['items'] as $id => $item) {
if (!preg_match('/^g-[a-f0-9]{7}$/', $id)) {
$errors[] = "Invalid item ID format: $id (must match g-XXXXXXX)";
}
foreach (['id', 'type', 'label'] as $field) {
if (!isset($item[$field])) {
$errors[] = "Missing '$field' in item $id";
}
}
if (isset($item['variants']) && is_array($item['variants'])) {
foreach ($item['variants'] as $variantIndex => $variant) {
$variantPath = "$id.variant[$variantIndex]";
if (!isset($variant['meta']) || !isset($variant['props'])) {
$errors[] = "Missing 'meta' or 'props' in $variantPath";
continue;
}
foreach ($variant['props'] as $propName => $prop) {
$propPath = "$variantPath.props.$propName";
if (!isset($prop['$$type'])) {
$errors[] = "Missing '\$\$type' in $propPath";
} elseif (!in_array($prop['$$type'], $validTypes)) {
$errors[] = "Invalid '\$\$type' '{$prop['$$type']}' in $propPath";
}
// Check for "unit" inside value
if (isset($prop['value']) && is_array($prop['value'])) {
// Handle both: single size object or nested objects (like background -> color -> value)
$unitPaths = extractUnits($prop['value']);
foreach ($unitPaths as $unitPath => $unitValue) {
if (!in_array($unitValue, $validUnits)) {
$errors[] = "Invalid unit '$unitValue' at $propPath.value.$unitPath";
}
}
}
}
}
}
}
return $errors;
}
function extractUnits(array $value, string $path = ''): array {
$units = [];
foreach ($value as $key => $val) {
$currentPath = $path === '' ? $key : "$path.$key";
if ($key === 'unit' && is_string($val)) {
$units[$currentPath] = $val;
} elseif (is_array($val)) {
$units += extractUnits($val, $currentPath);
}
}
return $units;
}
class ImportGlobalsLogger {
public $logs = [];
public function log(string $message){
$logEntry = [
'message' => $message
];
$this->logs[] = $logEntry;
}
}
function currentLog(ImportGlobalsLogger $obj) {
$output = '';
$arr = $obj->logs;
foreach($arr as $item){
$output .= $item['message'] ."\n";
}
return $output;
} JSON Schema for Elementor Global Classes
Use this tool to validate JSON against the schema
Warning: Schema is based on Elementor Version 3.35.0-beta1 but likely to change as Editor v4 develops.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"items": {
"type": "object",
"patternProperties": {
"^g-[a-fA-F0-9]{7}$": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^g-[a-fA-F0-9]{7}$"
},
"type": { "type": "string" },
"label": { "type": "string" },
"variants": {
"type": "array",
"items": {
"type": "object",
"properties": {
"meta": {
"type": "object",
"properties": {
"breakpoint": { "type": "string" },
"state": { "type": ["string", "null"] }
},
"required": ["breakpoint"]
},
"props": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/PropDefinition"
}
}
},
"required": ["meta", "props"]
}
}
},
"required": ["id", "type", "label", "variants"]
}
}
},
"order": {
"type": "array",
"items": {
"type": "string",
"pattern": "^g-[a-fA-F0-9]{7}$"
}
}
},
"required": ["items", "order"],
"definitions": {
"PropDefinition": {
"type": "object",
"properties": {
"$$type": {
"type": "string",
"enum": [
"size",
"color",
"string",
"box-shadow",
"background",
"global-color-variable",
"global-font-variable",
"global-size-variable",
"dimensions",
"shadow",
"border-width",
"border-radius",
"number",
"stroke"
]
},
"value": {
"anyOf": [
{
"type": "object",
"properties": {
"size": { "type": ["number", "string"] },
"unit": {
"type": "string",
"enum": ["px", "em", "ch", "rem", "%", "vw", "vh", "custom"]
}
},
"required": ["size", "unit"],
"additionalProperties": true
},
{
"type": "object",
"additionalProperties": true
},
{ "type": "string" },
{ "type": "number" },
{ "type": "array" }
]
}
},
"required": ["$$type", "value"]
}
}
}
JSON Schema for Elementor Global Variables
Use this tool to validate JSON against the schema
Warning: Schema is based on Elementor Version 3.35.0-beta1 but likely to change as Editor v4 develops.
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "object",
"properties": {
"data": {
"type": "object",
"patternProperties": {
"^e-gv-[a-fA-F0-9]{7}$": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["global-font-variable", "global-color-variable", "global-size-variable"]
},
"label": {
"type": "string"
},
"value": {
"type": ["object", "string"],
"properties": {
"$$type": {
"type": "string"
},
"value": {
"type": "object",
"properties": {
"size": {
"type": "string"
},
"unit": {
"type": "string"
}
},
"required": [
"size",
"unit"
]
}
},
"required": [
"$$type",
"value"
]
},
"order": {
"type": "number"
}
},
"required": ["type", "label", "value", "order"],
"additionalProperties": false
}
},
"additionalProperties": false
},
"watermark": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["data", "watermark", "version"],
"additionalProperties": false
} 