Code Snippet: Oxygen Stylesheets außerhalb des Builders bearbeiten
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 eigenes CSS hinterlegen, um spezielle Layouts und Formatierungen zu realisieren. Dies ist sehr praktisch und komfortabel.
Da jedoch das Öffnen des Builders je nach Plattform und Browser einige Zeit in Anspruch nehmen kann, ist schon alleine die Prüfung von CSS Code ein mitunter langwieriger Vorgang.
Lösung
Ich habe ein Code Snippet entwickelt, das die Bearbeitung der Oxygen Stylesheets auch außerhalb des Builders erlaubt. Dies ermöglicht einen sehr schnellen Zugriff auf das benutzerdefinierte CSS, ohne zuvor den Oxygen Builder starten zu müssen.
Im WordPress Admin Menü im Abschnitt Oxygen findet sich ein neuer Eintrag "Edit Stylesheets".
Zur Beachtung:
Mein Code Snippet verhindert das Speichern eines Stylesheets, 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 der Stand des gerade anzeigten Stylesheets 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 des Stylesheets empfohlen.
Download
Das Code Snippet steht hier zum Download zur Verfügung:
ma-oxygen-edit-stylesheets.code-snippets.json
Version 1.0.9, 2022-05-31
Zur Beachtung: Der Titel des Code Snippets hat sich geändert von früher "Oxygen: Edit Oxygen Stylesheets Outside Builder" zu jetzt "MA Oxygen Edit Stylesheets".
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.
Falls noch mein vorheriges Snippet "Spalte 'Application' in Oxygen Template Liste" installiert ist, muss dieses bitte gelöscht 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.
Spenden
Es macht mir viel Freude, Code Snippets zu entwickeln und damit Anforderungen zu lösen. Die Snippets stelle ich kostenfrei zur Verfügung.
Wenn Du möchtest, kannst Du meine vielen Stunden Arbeit mit einer kleinen Kaffee-Spende über PayPal honorieren.
Spenden werden selbstverständlich ordnungsgemäß durch mich versteuert.
Disclaimer
Das Code Snippet habe ich nach bestem Wissen und Gewissen entwickelt und getestet unter:
- PHP 7.4, WordPress 6.0, Oxygen 3.9
- PHP 8.0, WordPress 6.0, Oxygen 4.0
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 Edit Stylesheets Description: Direct access to Oxygen Stylesheet Editor Author: <a href="https://www.altmann.de/">Matthias Altmann</a> Version: 1.0.9 Plugin URI: https://www.altmann.de/en/blog-en/code-snippet-edit-oxygen-stylesheets-outside-builder/ Description: en: https://www.altmann.de/en/blog-en/code-snippet-edit-oxygen-stylesheets-outside-builder/ de: https://www.altmann.de/blog/code-snippet-oxygen-stylesheets-ausserhalb-des-builders-bearbeiten/ Copyright: © 2020-2022, Matthias Altmann Version History: Date Version Description --------------------------------------------------------------------------------------------------------------------- 2022-05-31 1.0.9 Fix: - Corrected folder/stylesheet hierarchy Changes: - Renamed snippet from "Oxygen: Edit Oxygen Stylesheets Outside Builder" to "MA Oxygen Edit Stylesheets" - Removed configuration and all code for debugging. Tested: - PHP 7.4, WordPress 6.0, Oxygen 3.9 - PHP 8.0, WordPress 6.0, Oxygen 4.0 2022-02-11 Tested: - PHP 8.0, WordPress 5.9, Oxygen 4.0 beta 1 Changes: - Added hint to select a stylesheet to edit - Added notice if no global stylesheets defined 2021-07-26 1.0.8 Bug fix: Restored compatibility with WP 5.8 (wp_add_inline_script() did not work anymore as expected) (Thanks to André Babiak for reporting!) 2021-04-13 1.0.7 Bug fix: Postponed Oxygen check to init action to also support plugin mode. 2021-03-21 1.0.6 Bug fix: Check Oxygen plugin state before adding admin menu or accessing global colors (Thanks to Adrien Robert for reporting!) 2021-02-20 1.0.5 Tweak: Full height editor (Safari, finally?), scrollable stylesheets list 2021-01-08 1.0.4 Tweak: Full height editor 2021-01-07 1.0.3 Bug fix: Corrected changed flag logic 2021-01-06 1.0.2 Bug fix: Correct handling of quotes that get backslashed during transfer Tweak: Implemented confirmation dialog for switching stylesheets when current one has been changed 2020-12-30 1.0.1 Bug fix: Fresh Oxygen site threw error since no stylesheets defined 2020-12-19 1.0.0 Initial Release 2020-12-17 Development start */ class MA_Oxygen_Edit_Stylesheets { const TITLE = 'MA Oxygen Edit Stylesheets'; const SLUG = 'ma_oxygen_edit_stylesheets'; const VERSION = '1.0.9'; // Configuration private static $timing = false; // caution! may produce a lot of output private static $status_interval = 5000; // interval (ms) to refresh "builder open" and "stylesheet changed" status // Internal data private static $oxygen_universal_message = ''; // temp storage for succes/error message from oxygen //------------------------------------------------------------------------------------------------------------------- static function init() { $st = microtime(true); add_action('admin_menu', array( __CLASS__, 'admin_menu' ), 20 ); add_action('wp_ajax_'.self::SLUG.'_status', [__CLASS__, 'status' ]); $et = microtime(true); if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() Timing: %.5f sec.', self::TITLE, __FUNCTION__, $et-$st));} } //------------------------------------------------------------------------------------------------------------------- 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('Edit Stylesheets','admin page title','ma_oxygen_edit_stylesheets'), // page title _x('Edit Stylesheets','admin menu title','ma_oxygen_edit_stylesheets'), // menu title 'manage_options', // capabilitiy self::SLUG.'_page', // menu slug [__CLASS__,'edit_stylesheets'] // method ); } //------------------------------------------------------------------------------------------------------------------- // Ajax handler to check 1) Oxygen Builder active, 2) Stylesheets changed in database static function status() { $st = microtime(true); // get parameters $ssid = isset($_REQUEST['ssid']) ? $_REQUEST['ssid'] : null; $hash = isset($_REQUEST['hash']) ? $_REQUEST['hash'] : null; // prepare data object to be returned $retval = (object)[ 'ct_builder_active' => false, 'ct_stylesheet_changed' => false, ]; // check if Oxygen Builder is active $retval->ct_builder_active = get_transient('oxygen_post_edit_lock'); // if we currently have a stylesheet selected... if ($ssid && $hash) { // ... check if this stylesheet has changed $stylesheets = get_option('ct_style_sheets'); $new_hash = null; // loop through stylesheets to find current one foreach ($stylesheets as $stylesheet) { if ($stylesheet['id']==$ssid) { $sscode = base64_decode($stylesheet['css']); $new_hash = sha1($sscode); break; } } $retval->ct_stylesheet_changed = ($hash !== $new_hash); } $et = microtime(true); if (WP_DEBUG && self::$timing) {error_log(sprintf('%s::%s() => %s Timing: %.5f sec.', self::TITLE, __FUNCTION__, json_encode($retval), $et-$st));} // return JSON encoded data object echo json_encode($retval); wp_die(); } //------------------------------------------------------------------------------------------------------------------- static function edit_stylesheets() { $st = microtime(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-css') && isset($_REQUEST['ssid']) && isset($_REQUEST['code'])) { // save $ssid = $_REQUEST['ssid']; $code = $_REQUEST['code']; if (!isset($_REQUEST['save-css-nonce']) || !wp_verify_nonce($_REQUEST['save-css-nonce'], 'save-css-'.$ssid)) { echo '<div class="notice notice-error"><p>Invalid request (Nonce error)</p></div>'; } else { // read current stylesheets $stylesheets = get_option('ct_style_sheets'); if (!is_array($stylesheets)) $stylesheets = []; foreach ($stylesheets as &$stylesheet) { if ($stylesheet['id']==$ssid) { // remove backslashes from quotes that might be added during transfer $code = stripslashes($code); // override css code $stylesheet['css'] = base64_encode($code); update_option('ct_style_sheets',$stylesheets); // regenerate universal cache and show Oxygen's result message $result = oxygen_vsb_cache_universal_css(); if ($result) {self::$oxygen_universal_message = __('Universal CSS cache generated successfully.','oxygen');} else {self::$oxygen_universal_message = __('Universal CSS cache not generated.','oxygen');} break; } } add_action('admin_notices',function(){ echo '<div id="notice-saved" class="notice notice-success is-dismissible"><p>Saved successfully. Oxygen '.self::$oxygen_universal_message.'</p></div>'. '<script>setTimeout(function(){jQuery("#notice-saved").fadeOut("slow");},5000);</script>'; }); } } // stylesheet selection, current stylesheet code $ssid = @$_REQUEST['ssid'] ?? null; $ssdata = null; $sscode = ''; $sshash = ''; $stylesheets = get_option('ct_style_sheets'); // @since 1.0.9 if (!is_array($stylesheets)) $stylesheets = []; // build stylesheet hierarchy (may be out of order) $hierarchy = [ '0' => ['id'=>0, 'parent'=>null, 'folder'=>1, 'name'=>'Uncategorized'], ]; // 1) find folders foreach ($stylesheets as $stylesheet) { if (array_key_exists('folder',$stylesheet)) { $hierarchy[$stylesheet['id']] = $stylesheet; continue; } } // 2) find stylesheets foreach ($stylesheets as $stylesheet) { if (array_key_exists('parent',$stylesheet)) { $parent = $stylesheet['parent']; if (array_key_exists($parent,$hierarchy)) { if (!array_key_exists('stylesheets',$hierarchy[$parent])) $hierarchy[$parent]['stylesheets'] = []; } $hierarchy[$parent]['stylesheets'][] = $stylesheet; continue; } } if (!count($hierarchy)) { // TODO: emit message? } // list enabled folders and stylesheets $tree = '<div class="ss-list">'; foreach (['Enabled','Disabled'] as $endis) { $tree .= sprintf('<h3>%s</h3><ul class="ss-tree">', $endis); foreach ($hierarchy as $folder) { if (($endis == 'Enabled') && (array_key_exists('status',$folder) && $folder['status']==0)) continue; if (($endis == 'Disabled') && (!array_key_exists('status',$folder) || $folder['status']!=0)) continue; $tree .= sprintf('<li><span>%s</span><ul>', $folder['name']); if (array_key_exists('stylesheets',$folder)) { foreach ($folder['stylesheets'] as $stylesheet) { $tree .= sprintf('<li %s><a onclick="ma_oxygen_edit_stylesheets_switch(%d)">%s</a></li>',$stylesheet['id']==$ssid?'class="active"':'', $stylesheet['id'], $stylesheet['name']); if ($stylesheet['id']==$ssid) { $ssdata = $stylesheet; $sscode = base64_decode($stylesheet['css']); $sshash = sha1($sscode); } } } $tree .= '</ul></li>'; } $tree .= '</ul>'; } $tree .= '</div>'; ?> <script> var $can_change = false; // var $has_changed = false; // gets true if css code has been edited function sscode_changed() { $has_changed = true; //if ($can_change) {jQuery('#but-css-save').removeAttr('disabled');} } function ma_oxygen_edit_stylesheets_switch($id) { if (!$has_changed || confirm('Stylesheet has been changed. Switch to another stylesheet anyways?')) { jQuery('.edit-screen__content').first().html('<div class="edit-screen__title">Loading...</div>'); setTimeout(function(){document.location.href='?page=<?php echo self::SLUG.'_page'; ?>&ssid='+$id;}, 50); } } function ma_oxygen_edit_stylesheets_status() { var $ajax_data = { 'action': '<?php echo self::SLUG.'_status'; ?>', // name of the AJAX function 'ssid': '<?php echo $ssid; ?>', 'hash': '<?php echo $sshash; ?>', }; jQuery.ajax({ url: ajaxurl, // this will point to admin-ajax.php type: 'POST', data: $ajax_data, success: function ($response) { $response = JSON.parse($response); //console.log('ma_oxygen_edit_stylesheets_status',$response); $notice = []; if ($response.ct_builder_active) { jQuery('#notice-builder-open').show(); $can_change = false; jQuery('#but-css-save').attr('disabled','disabled'); } else { jQuery('#notice-builder-open').hide(); $can_change = true; jQuery('#but-css-save').removeAttr('disabled'); } if ($response.ct_stylesheet_changed) { jQuery('#notice-stylesheet-changed').show(); } else { jQuery('#notice-stylesheet-changed').hide(); } } }); setTimeout(ma_oxygen_edit_stylesheets_status,<?php echo self::$status_interval; ?>); } </script> <style id="ma_edit_stylesheet"> #wpbody-content * {box-sizing:border-box;} #wpbody-content {position:relative;height:calc(100vh - 32px - 40px);max-width:100%;padding-bottom:0;margin-right:20px;} #wpbody-content > .wrap {width:100%;height:100%;display:flex;flex-direction:column;margin:0;} #edit-screen {margin-top:2em;flex-grow:1;display:flex;flex-direction:row;max-height:100%;} @media (max-width:900px) {#edit-screen {display:flex;flex-direction:column;}} /* left column */ .edit-screen__control {display:flex;flex-direction:column;} .ss-list {margin:0 20px 0 0;width:300px;flex-grow:1;overflow-y:auto;} .ss-tree {} .ss-tree li {margin:0 0margin-bottom:5px;} .ss-tree li span {display:block;padding:5px;font-weight:bold;background-color:darkgray;border-radius:3px;margin-bottom:5px;} .ss-tree ul {margin-left:20px;} .ss-tree ul li {} .ss-tree ul li a {display:inline-block;width:calc(100% - 10px);padding:5px;border-radius:3px;background-color:lightgray;color:black;cursor:pointer;} .ss-tree ul li.active a {background-color:lightblue;color:black;} /* right column */ .edit-screen__content {flex-grow:1;display:flex;flex-direction:column;} .edit-screen__content #code {display:none;} .edit-screen__title {font-size:18px;margin-bottom:10px;} .ma_oxygen_edit_stylesheets_code {border: 1px solid lightgray;flex-grow:1;margin-bottom:20px;margin-right:20px;display:flex;flex-direction:column;} .ma_oxygen_edit_stylesheets_code form {flex-grow:1;display:flex;flex-direction:column;} .ma_oxygen_edit_stylesheets_code .CodeMirror {flex-grow:1;display:flex;flex-direction:column;} .ma_oxygen_edit_stylesheets_code .CodeMirror-scroll {flex-grow:1;} </style> <div class="wrap"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <?php do_action( 'admin_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. CSS changes not allowed.','builder open warning','ma_oxygen_edit_stylesheets'); ?></p> </div> <div id="notice-stylesheet-changed" class="notice notice-warning" style="display:none"> <p><?php _ex('This stylesheet has been changed in database. Please <a href="" onclick="location.reload()">reload</a>!','stylesheet changed warning','ma_oxygen_edit_stylesheets'); ?></p> </div> <div id="edit-screen"> <div class="edit-screen__control"> <div class="edit-screen__title">Stylesheets:</div> <?php if (!is_array($stylesheets) || !count($stylesheets)) : ?> <p>No global stylesheets defined in Oxygen.</p> <?php else :?> <?php echo $tree; ?> <?php endif; ?> </div> <div class="edit-screen__content" > <?php if (!$ssid) : ?> <?php if (is_array($stylesheets) && count($stylesheets)) : ?> <p style="margin-top:4em;text-align:center;">◀︎ Please select a stylesheet.</p> <?php else :?> <p>No stylesheets defined in Oxygen.</p> <?php endif; ?> <?php else : ?> <div class="edit-screen__title">Current Stylesheet: <?php echo (isset($ssdata['name']) ? $ssdata['name'] : ''); ?></div> <div class="ma_oxygen_edit_stylesheets_code"> <form method="post"> <input type="hidden" name="action" value="save-css"/> <input type="hidden" name="id" value="<?php echo $ssid ; ?>"/> <?php wp_nonce_field('save-css-'.$ssid,'save-css-nonce'); ?> <textarea id="code" name="code"><?php echo $sscode; ?></textarea> <p style="text-align:center;"><button id="but-css-save" type="submit" class="button button-primary" disabled >Save</button></p> </form> </div> <?php endif; ?> </div> </div> </div> <script>ma_oxygen_edit_stylesheets_status();</script> <?php $editor = wp_enqueue_code_editor(['type'=>'text/css']); ?> <script> jQuery( function($) { if ( $('#code').length ) { let editor = wp.codeEditor.initialize('code', <?php wp_json_encode( $editor ) ?>); editor.codemirror.on('change',function(codemirror) {sscode_changed();}); } } ); </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 add_action('init',function(){ if (defined('CT_VERSION')) { MA_Oxygen_Edit_Stylesheets::init(); } });