Blog

Code Snippet: Oxygen Globale Farben Umbenennen

(2020)

Inhalt

Einführung

Oxygen ist ein mächtiges Plugin für WordPress, welches das WordPress Theme deaktiviert, die Erstellung eigener Templates mit einem visuellen Builder unterstützt, und damit ein ganz individuelles Design für die Website ermöglicht.

Innerhalb des Oxygen Builders kann man globale Farben mit Farbwert und Namen definieren, die dann in allen Farbauswahl-Feldern des Builders zur Verfügung stehen. Für eine bessere Organisation kann man auch eigene Farb-Ordner ("Color Sets") anlegen, um die definierten Farben beispielsweise thematisch zusammen zu fassen.

Leider kann man in Oxygen einen einmal vergebenen Namen für Farb-Ordner oder Farben nicht mehr ändern.

Lösung

Ich habe ein Code Snippet entwickelt, das die Umbenennung von Farb-Ordnern und Farben erlaubt.

Im WordPress Admin Menü im Abschnitt Oxygen findet sich hierzu ein neuer Eintrag "Global Colors".

Zur Beachtung:
Mein Code Snippet verhindert das Speichern der Farb-Definitionen, wenn in einem anderen Browser Fenster (auch durch einen anderen Benutzer) ein Oxygen Builder geöffnet ist. Gleichzeitige Änderungen würden sich gegenseitig überschreiben.
Zu diesem Zweck prüft mein Code Snippet im Abstand weniger Sekunden, ob ein Oxygen Builder in einem anderen Tab oder von einem anderen Benutzer geöffnet wurde. In diesem Fall erfolgt eine Warnung und eine Speicherung wird verhindert.
Mein Code Snippet überprüft gleichzeitig, ob die angezeigten Farb-Definitionen noch dem Stand in der Datenbank entspricht. Sollten Abweichungen bestehen, weil beispielsweise in einem anderen Browser Fenster oder durch einen anderen Benutzer Änderungen vorgenommen wurden, wird ein erneutes Laden der Farb-Definitionen empfohlen.

Warnung!
Wenn Du Elemente aus den Design-Sets von Oxygen verwendest, fügen diese Farb-Ordner hinzu, die wie das Design-Set benannt sind (z. B. "atomic"), und fügen diesem Farb-Ordner Farben hinzu.
Du solltest diese Farb-Ordner und Farben besser NICHT umbenennen. Andernfalls wird, wenn Du ein weiteres Element aus diesem Design-Set hinzufügst, ein neuer Farb-Ordner mit dem ursprünglichen Design-Set-Namen erstellt und Farben werden hier hinzugefügt.

Download

Das Code Snippet steht hier zum Download zur Verfügung:

oxygen-rename-global-color-sets-and-colors.code-snippets.json
Version 1.0.2, 2021-03-21

Zur Installation und Nutzung dieser JSON Datei wird das Plugin Code Snippets oder Advanced Scripts benötigt.
Dort kann diese JSON Datei mit der Funktion "Import" hochgeladen und anschließend aktiviert werden.

Alternativ: Am Ende dieser Seite kann der vollständige Source Code des Snippets eingesehen und kopiert werden.

Im Change Log sind neue Funktionalitäten und Fehlerbehebungen dokumentiert.

Disclaimer

Das Code Snippet habe ich nach bestem Wissen und Gewissen unter WordPress 5.7 und Oxygen 3.7.1 entwickelt und getestet.
Dieses Snippet läuft NICHT unter Oxygen Versionen älter als 3.6.
Ich stelle das Code Snippet zur freien Verwendung zur Verfügung.
Eine Garantie für die Funktionalität in allen denkbaren WordPress Umgebungen kann ich nicht geben. 
Download und Nutzung dieses Code Snippets erfolgen auf eigene Gefahr und Verantwortung.

Change Log

Siehe Source Code.

Source Code

<?php 
/*
Plugin Name:  MA Oxygen Global Colors
Description:  Rename Oxygen Global Color Sets and Colors
Author:       <a href="https://www.altmann.de/">Matthias Altmann</a>
Version:      1.0.2
Plugin URI:   https://www.altmann.de/en/blog-en/code-snippet-oxygen-rename-global-colors/
Description:  en: https://www.altmann.de/en/blog-en/code-snippet-oxygen-rename-global-colors/
              de: https://www.altmann.de/blog/code-snippet-oxygen-globale-farben-umbenennen/
Copyright:    © 2020-2021, Matthias Altmann

Version History:
Date		Version		Description
---------------------------------------------------------------------------------------------------------------------
2020-12-28				Development start
2020-12-29 	1.0.0		Initial Release 
2021-01-09	1.0.1		New Feature: Show color values and allow copy to clipboard
2021-03-21	1.0.2		Bug fix: Check Oxygen plugin state before adding admin menu or accessing global colors
						(Thanks to Adrien Robert for reporting!)

*/

class MA_Oxygen_Global_Colors {

	const TITLE     	= 'MA Oxygen Global Colors';
	const SLUG	    	= 'ma_oxygen_global_colors';
	const VERSION   	= '1.0.2';
	
	// Configuration
	private static $debug				= false; // caution! may produce a lot of output
	private static $timing				= false; // caution! may produce a lot of output
	private static $status_interval 	= 5000; // interval (ms) to refresh  "builder open" and "colors changed" status
	
	// Internal data
	private static $oxygen_message 		= ''; // temp storage for succes/error message from oxygen

	//-------------------------------------------------------------------------------------------------------------------
	static function init() {
		add_action('admin_menu', array( __CLASS__, 'admin_menu' ), 20 );
		add_action('wp_ajax_'.self::SLUG.'_status', [__CLASS__, 'status' ]);
	}

	//-------------------------------------------------------------------------------------------------------------------
	static function admin_menu() {
		// check Oxygen plugin state and user access
		if (!function_exists('oxygen_vsb_current_user_can_full_access') || !oxygen_vsb_current_user_can_full_access()) {return;}

		// Add submenu page to the Oxygen menu.
		add_submenu_page(	'ct_dashboard_page', 						// parent slug of "Oxygen"
							_x('Global Colors','admin page title','ma_oxygen_global_colors'), 	// page title
							_x('Global Colors','admin menu title','ma_oxygen_global_colors'), 	// menu title
							'manage_options',							// capabilitiy
							self::SLUG.'_page',							// menu slug
							[__CLASS__,'global_colors']					// method
		);
		if (WP_DEBUG && self::$debug) {error_log(sprintf('%s::%s() admin subpage added.',self::TITLE,__FUNCTION__));}
	}
	//-------------------------------------------------------------------------------------------------------------------
	// Ajax handler to check 1) Oxygen Builder active, 2) Colors changed in database
	static function status() {
		$st = microtime(true);
		// get parameters
		$colset_id 		= isset($_REQUEST['colset-id'])		? $_REQUEST['colset-id']	: null; if (isset($colset_id)) {$colset_id = intval($colset_id);}
		$colors_hash 	= isset($_REQUEST['colors-hash'])	? $_REQUEST['colors-hash']	: null;
		// prepare data object to be returned
		$retval = (object)[
			'oxygen_builder_active' => false,
			'oxygen_colors_changed'	=> false,
		];
		// check if Oxygen Builder is active
		$retval->oxygen_builder_active = get_transient('oxygen_post_edit_lock');
		// if we currently have a color selected...
		if (isset($colset_id) && $colors_hash) {
			// ... check if this color has changed
			$colors = get_option('oxygen_vsb_global_colors');
			$new_hash = sha1(serialize($colors));
			$retval->oxygen_colors_changed = ($colors_hash !== $new_hash);
		}

		if (WP_DEBUG && self::$debug) {error_log(sprintf('%s::%s() retval: %s',self::TITLE,__FUNCTION__,print_r($retval,true)));}
		$et = microtime(true);
		if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() Timing: %.5f sec.',self::TITLE,__FUNCTION__,$et-$st));}

		// return JSON encoded data object
		echo json_encode($retval);
		wp_die();
	}
	//-------------------------------------------------------------------------------------------------------------------
	static function global_colors() {
		$st = microtime(true);

		if (WP_DEBUG && self::$debug) {error_log(sprintf('%s::%s() request: %s',self::TITLE,__FUNCTION__,print_r($_REQUEST,true)));}
		
		// check Oxygen plugin state and user access
		if (!function_exists('oxygen_vsb_current_user_can_full_access') || !oxygen_vsb_current_user_can_full_access()) {return;}

		// check if action = save 
		if (isset($_REQUEST['action']) && ($_REQUEST['action'] == 'save-colset') && isset($_REQUEST['colset-id']) && isset($_REQUEST['colset-name'])) {
			// save
			$colset_id = @$_REQUEST['colset-id'] ?? null; if (isset($colset_id)) {$colset_id = intval($colset_id);}
			if (!isset($_REQUEST['save-colset-nonce']) || !wp_verify_nonce($_REQUEST['save-colset-nonce'], 'save-colset-'.$colset_id)) {
				echo '<div class="notice notice-error"><p>Invalid request (Nonce error)</p></div>';
			} else {
				// read current colors
				$colors = get_option('oxygen_vsb_global_colors');
				// cange color set name
				$colset_name = trim(stripslashes(@$_REQUEST['colset-name']));
				foreach ($colors['sets'] as &$colset) {
					if ($colset['id'] == $colset_id) {
						$colset['name'] = $colset_name; 
						break;
					}
				}
				// change color names
				foreach ($colors['colors'] as &$color) {
					$color_name = @$_REQUEST['color-name-'.$color['id']];
					if (isset($color_name)) {
						$color_name = trim(stripslashes($color_name));
						$color['name'] = $color_name;
					}
				}
				update_option('oxygen_vsb_global_colors',$colors);
				?>
				<script>
					jQuery(document).ready(function() {
						jQuery('#notice-colors-saved').show();
						setTimeout(function(){jQuery('#notice-colors-saved').fadeOut('slow');},5000);
					});
				</script>
				<?php
			}
		}

		// colors, current color set
		$colset_id = @$_REQUEST['colset-id'] ?? null; if (isset($colset_id)) {$colset_id = intval($colset_id);}
		$colset_name = '';
		$colors = get_option('oxygen_vsb_global_colors');
		$colors_hash = sha1(serialize($colors));
		$colset_tree = '<ul id="colset-tree">';
		if ($colors) {
			// loop through sets
			foreach ($colors['sets'] as $colset) {
				$colset_active = false;
				if ($colset_id === $colset['id']) {
					$colset_name = $colset['name'];
					$colset_active = true;
				}
				$colset_tree .=	sprintf('<li %s><a onclick="ma_oxygen_global_colors_switch_set(%d)">%s</a></li>', $colset_active ? 'class="active"':'', $colset['id'], $colset['name']);
			}
		}
		$colset_tree .= '</ul>';
		?>
		<script>
		var $builder_open 	= false;	// builder open flag
		var $data_valid 	= true; 	// data valid flag
		var $has_changed 	= false; 	// gets true if colors have been edited
		var $colors 		= JSON.parse('<?php echo addslashes(json_encode($colors)); ?>'); // colors for duplicate check
		var $colset_id 		= <?php echo $colset_id ?? 'null'; ?>; // current color set id
		//-------------------------------------------------------------------------------------------------------------------
		function ma_oxygen_global_colors_change($elm) {
			var $elm_name = $elm.attr('name');
			var $elm_id = $elm_name == 'colset-name' 
						? $elm.closest('form').find('input[name="colset-id"]').val() 
						: $elm_name.match(/\-(\d+)$/)[1];
			var $elm_value = $elm.val().trim();
			var $elm_orgval = $elm.data('orgval');
			var $message = null;
			// check changed
			if ($elm_value != $elm_orgval) {$has_changed = true;}
			// check empty
			if ($elm_value.trim() == '') {$message = 'Name can not be empty.'}
			// check for duplicates
			var $is_duplicate = false
			if ($elm_name == 'colset-name') {
				// check for duplicate color set name
				for (var $set of $colors.sets) {
					if ($set.id == $elm_id) 		{continue;}	// skip current color set
					if ($set.name == $elm_value) 	{$is_duplicate = true; break;}
				}
			} else {
				// check for duplicate color name in set
				for (var $col of $colors.colors) {
					if ($col.id == $elm_id) 		{continue;} // skip current color
					if ($col.set != $colset_id) 	{continue;} // skip if not current set
					if ($col.name == $elm_value)	{$is_duplicate = true; break;}
				}
			}
			if ($is_duplicate) {$message = 'Duplicate name.';}
			$elm.siblings('.validity-message').remove();
			// show message
			if ($message) {
				$data_valid = false;
				$elm.after('<span class="validity-message"><br>'+$message+'</span>');
				jQuery('#but-colors-save').attr('disabled','disabled');
			} else {
				$data_valid = true;	
				if ($data_valid && !$builder_open) {jQuery('#but-colors-save').removeAttr('disabled');}
			}
		}
		//-------------------------------------------------------------------------------------------------------------------
		function ma_oxygen_global_colors_switch_set($id) {
			var $switch = true;
			if ($has_changed) {
				$switch = confirm('<?php _ex('Unsaved changes. Switch color set?','unsaved changes notice','ma_oxygen_global_colors')?>');
			}
			if ($switch) {
				document.location.href='?page=<?php echo self::SLUG.'_page'; ?>&colset-id='+$id;
			}
		}
		//-------------------------------------------------------------------------------------------------------------------
		function ma_oxygen_global_colors_status() {
			var $ajax_data = {
				'action': '<?php echo self::SLUG.'_status'; ?>', // name of the AJAX function
				'colset-id': '<?php echo $colset_id; ?>',
				'colors-hash': '<?php echo $colors_hash; ?>',
			};
			jQuery.ajax({
				url: ajaxurl, // this will point to admin-ajax.php
				type: 'POST',
				data: $ajax_data, 
				success: function ($response) {
					var $response = JSON.parse($response);
					//console.log('ma_oxygen_global_colors_status',$response);
					$notice = [];
					if ($response.oxygen_builder_active) {
						jQuery('#notice-builder-open').show();
						$builder_open = true;
						jQuery('#but-colors-save').attr('disabled','disabled');
					} else {
						jQuery('#notice-builder-open').hide();
						$builder_open = false;
						if ($data_valid && !$builder_open) {jQuery('#but-colors-save').removeAttr('disabled');}
					}
					if ($response.oxygen_colors_changed) {
						jQuery('#notice-colors-changed').show();
					} else {
						jQuery('#notice-colors-changed').hide();
					}
				}
			});
			setTimeout(ma_oxygen_global_colors_status,<?php echo self::$status_interval; ?>);
		}
		(function($){
			$(document).ready(function(){
				// assign copy function
				$('span.color-copy-source').each(function(){
					$(this).on('click',function(){
						var $temp = $('<input>');
		   				$('body').append($temp);
						var $color = $(this).text();
						console.log('color',$color);
						$temp.val($color).select();
						document.execCommand("copy");
						$temp.remove();
						// inform in tooltip
						$(this).closest('.color-value').find('.color-copy-tooltip').html('Copied '+$color);
					});
					$(this).on('mouseout',function(){
						// reset tooltip text
						$(this).closest('.color-value').find('.color-copy-tooltip').html('Copy to clipboard');
					});
				});
			});

		})(jQuery);
		</script>
		<style>
		h1 {margin-bottom:30px;}
		#notices {margin-top: 30px;}
		#edit-screen {display:flex;flex-direction:row;flex-wrap:wrap;}
		.edit-screen__control {margin-bottom:30px;}
		.edit-screen__content {flex-grow:1;}
		.edit-screen__content #code {display:none;}
		.edit-screen__title {font-size:18px;margin-bottom:10px;}
		#colset-tree {margin:0 20px 0 0;width:300px;border:1px solid darkgray;border-radius:3px;padding:5px;}
		#colset-tree li {margin:0 0margin-bottom:5px;}
		#colset-tree li a {display:inline-block;width:calc(100% - 10px);padding:5px;border-radius:3px;background-color:lightgray;color:black;cursor:pointer;}
		#colset-tree li.active a {background-color:lightblue;color:black;}
		#notice-designset {margin-bottom:10px;border:1px solid lightgray;border-left:4px solid red;padding:1px 12px;background-color:white;color:red;}
		#colset-editor {width:600px;border:1px solid darkgray;border-radius:3px;padding:5px;}
		#colset-editor .color-row {display:flex;flex-direction:row;align-items:center;margin-bottom:3px;}
		#colset-editor label {display:inline-block;}
		#colset-editor label.colset {width:60px;}
		#colset-editor label.color {width:80px;}
		#colset-editor input.colset {width:300px;}
		#colset-editor input.color {width:280px;}
		#colset-editor .color-sample {display:inline-block;width:1.5em;height:1.5em;margin:0 5px;border:1px solid darkgray;border-radius:100%;}
		#colset-editor .color-value {font-family:monospace;}
		#colset-editor .validity-message {position:absolute;line-height:0;top:14px;right:5px;color:red;background-color:rgba(255,255,255,.75);}
		.color-value  {position:relative;display:inline-block;cursor:grab;}
		.color-copy-tooltip {visibility:hidden;width:140px;background-color:#555;color:#fff;text-align:center;border-radius:6px;padding:5px;position:absolute;z-index:1;bottom:150%;left:50%;margin-left:-75px;opacity:0;transition:opacity 0.3s;}
		.color-copy-tooltip::after {content:'';position:absolute;top:100%;left:50%;margin-left:-5px;border-width:5px;border-style:solid;border-color:#555 transparent transparent transparent;}
		.color-value:hover .color-copy-tooltip {visibility:visible;opacity:1;}
		</style>
		<div class="wrap">
			<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
			<div id="notices">
				<div id="notice-builder-open" class="notice notice-error" style="display:<?php echo get_transient('oxygen_post_edit_lock')?'block':'none'?>;">
					<p><?php _ex('Oxygen Builder is open in another browser tab. Changes are not allowed.','builder open warning','ma_oxygen_global_colors'); ?></p>
				</div>
				<div id="notice-colors-changed" class="notice notice-warning" style="display:none">
					<p><?php _ex('Colors have been changed in database. Please <a href="" onclick="location.reload()">reload</a>!','colors changed warning','ma_oxygen_global_colors'); ?></p>
				</div>
				<div id="notice-colors-saved" class="notice notice-success is-dismissible" style="display:none;">
					<p><?php _ex('Colors saved successfully','colors saved notice','ma_oxygen_global_colors'); ?></p>
				</div>
			<div id="edit-screen">
				<div class="edit-screen__control">
					<div class="edit-screen__title"><?php _ex('Global Color Sets','color sets headline','ma_oxygen_global_colors'); ?>:</div>
					<?php echo $colset_tree; ?>
				</div>
				<?php if (isset($colset_id)) : ?>
					<div class="edit-screen__content">
						<div class="edit-screen__title"><?php _ex('Current Color Set','current set headline','ma_oxygen_global_colors'); ?>: <?php echo $colset_name; ?></div>
						<div id="colset-editor">
							<div id="notice-designset" style="display:none;">
								<p><?php _ex('This color set seems to belong to a Design Set.<br>Please be aware that changes to the color set or color names could lead to duplicate color sets or colors.','design set warning','ma_oxygen_global_colors'); ?></p>
							</div>
							<form method="post">
								<input type="hidden" name="action" value="save-colset"/>
								<input type="hidden" name="colset-id" value="<?php echo $colset_id ; ?>"/>
								<?php wp_nonce_field('save-colset-'.$colset_id,'save-colset-nonce'); ?>
								<div class="color-row">
									<label class="colset" for="colset-name"><?php _ex('Color Set','label for color set name','ma_oxygen_global_colors'); ?></label>
										<div style="position:relative">
											<input class="colset" name="colset-name" type="text" value="<?php echo htmlspecialchars($colset_name) ; ?>" data-orgval="<?php echo htmlspecialchars($colset_name) ; ?>" onkeyup="ma_oxygen_global_colors_change(jQuery(this));" />
										</div>
								</div>
								<?php
								$is_designset = false;
								foreach ($colors['colors'] as $color) {
									if ($color['set'] === $colset_id) {
										if (array_key_exists('sourceVal',$color)) {$is_designset = true;}
										?>
										<div class="color-row">
										<label class="color" for="color-name-<?php echo $color['id']; ?>">ID <?php echo $color['id']; ?></label>
											<div style="position:relative">
												<input class="color" name="color-name-<?php echo $color['id']; ?>" type="text" value="<?php echo htmlspecialchars($color['name']); ?>" data-orgval="<?php echo htmlspecialchars($color['name']) ; ?>" onkeyup="ma_oxygen_global_colors_change(jQuery(this));"/>
											</div>
											<div class="color-sample" style="background-color:<?php echo $color['value']; ?>" title="<?php echo htmlentities($color['value']); ?>"></div>
											<div class="color-value">
												<span class="color-copy-tooltip">Copy to clipboard</span>
												<span class="color-copy-source"><?php echo htmlentities($color['value']); ?></span>
											</div>
										</div>
										<?php
									}
								}
								if ($is_designset) { ?>
									<script>
									//jQuery('input[name="colset-name"]').attr('disabled','disabled');
									jQuery('#notice-designset').show();
									</script>
								<?php }
								?>
								<p><button id="but-colors-save" type="submit" class="button button-primary" disabled >Save</button></p>
							</form>
						</div>
					</div>
				<?php endif; ?>
			</div>
		</div>
		<script>ma_oxygen_global_colors_status();</script>
		<?php
		$et = microtime(true);
		if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() Timing: %.5f sec.',self::TITLE,__FUNCTION__,$et-$st));}
	}
}
// only initialize if Oxygen plugin is initialized
if (defined('CT_VERSION')) {
	MA_Oxygen_Global_Colors::init();
}


magnifier