Code Snippet: Admin Quick Nav
Einführung
Bei der Erstellung und Pflege von Websites mit WordPress muss man regelmäßig zwischen den Inhaltstypen (Seiten, Beiträge, Custom Posts) hin- und herspringen. In der normalen WordPress Admin Navigation springt man hierzu zunächst auf die Liste, wartet bis die Liste angezeigt wird, und klickt dann auf den gewünschten Eintrag zur Bearbeitung.
Der Wechsel zwischen verschiedenen Inhalten benötigt dadurch einige Klicks und auch Zeit.
Lösung
Das hier beschriebene Code Snippet stellt eine Schnellnavigation in der oberen Admin-Leiste zur Verfügung.
Die Navigation ist sehr individuell konfigurierbar, unterstützt neben den WordPress Standard-Inhaltstypen "Beiträge" und "Seiten" auch eigene Inhaltstypen (Custom Posts), und bietet auch erweiterte Funktionalität für die Builder Bricks und Oxygen.
Einstellungen
Die Konfiguration erfolgt über Einstellungen > Quick Nav in der deutschen WordPress Oberfläche, bzw. Settings > Quick Nav in der englischen.
Inhaltstypen
Es werden alle Inhaltstypen aufgelistet, die auch in der linken Admin-Leiste auftauchen.
Die Inhaltstypen können individuell zur Anzeige in der Schnellnavigation ausgewählt werden.
Reihenfolge ändern
Mit dem Symbol können die Inhaltstypen per Drag & Drop in die gewünschte Reihenfolge gebracht werden.
Einfach das Symbol mit der Maus halten und an die gewünschte Stelle in der Liste ziehen.
Sortierung der Einträge
Die Auswahllisten "Sort Order" und "Order" erlauben die Festlegung der gewünschten Sortierung für die Einträge in der Schnellnavigation. Es kann nach Titel, Erstellungsdatum, oder auch der im Inhaltstyp selbst festgelegten Reihenfolge, auf- oder absteigend sortiert werden.
Limit
Mit der Einstellung "Limit" kann die Anzahl der in der Schnellnavigation angezeigten Einträge begrenzt werden.
Ein leeres Feld erlaubt die Auflistung aller Einträge im System. Ein gesetzter Wert begrenzt die Anzahl der gelisteten Einträge, und zwar in Bezug auf die aktuell gewählte Sortierung.
Hierarchische Darstellung
Einige Inhaltstypen, wie beispielsweise "Seiten", erlauben die Festlegung einer Hierarchie.
Mit der Option "Show hierarchy" werden die Einträge in der Schnellnavigation entsprechend ihrer Hierarchie-Ebene eingerückt dargestellt.
Allgemeine Einstellungen
Menü verschlanken
Die Einstellung "Collapse" legt fest, ob die Inhaltstypen nebeneinander in der Menüzeile angezeigt werden, oder in einem gemeinsamen Menü "Quick Nav" zusammen gefasst werden.
Hier der Vergleich zwischen den Einstellungsvarianten:
Admin-Leiste in Builder
Falls Sie Bricks oder Oxygen installiert haben, können Sie mit dieser Einstellung festlegen, ob die obere WordPress Admin-Leiste auch im Builder angezeigt werden soll.
Status
In der Standard-Konfiguration werden nur Inhalte mit Status "Veröffentlicht" in der Navigation aufgelistet.
Sie können hier festlegen, ob auch Inhalte mit Status "Entwurf" (draft), "Ausstehende Überprüfung" (pending) und "Privat" (private) angezeigt werden sollen.
In den Listen werden diese Einträge entsprechend gekennzeichnet.
Unterstützung von Bricks und Oxygen
Das Snippet unterstützt die beiden Builder Bricks und Oxygen.
Für unterstützte Inhaltstypen kann die Bearbeitung mit dem jeweiligen Builder aktiviert werden.
Hier der Vergleich der Einstellungsmöglichkeiten bei Verwendung von Bricks und Oxygen:
In den allgemeinen Einstellungen gibt es die Option, die obere Admin Leiste auch im Builder anzuzeigen.
So sieht das in Bricks bzw. Oxygen aus:
Bei aktivierter Builder Unterstützung zeigen die Listen in der Schnellnavigation Symbole für die Bearbeitung in WordPress und im jeweiligen Builder.
Dunklere oder helle Symbole zeigen an, ob Inhalt in WordPress bzw. dem Builder vorhanden ist.
Hier zum Vergleich:
FAQ – Häufig gestellte Fragen
Download
Das Code Snippet steht hier zum Download zur Verfügung:
ma-admin-quick-nav.code-snippets..json
Version 1.3.0, 2024-04-09
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.
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 mit den aktuellsten WordPress und Builder Version entwickelt und getestet.
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 "Version History" in Source Code
Source Code
<?php /* Plugin Name: MA Admin Quick Nav Description: Admin Bar Quick Navigation for Post Types Author: <a href="https://www.altmann.de/">Matthias Altmann</a> Project: Code Snippet: MA Admin Quick Nav Version: 1.3.0 Plugin URI: https://www.altmann.de/en/blog-en/code-snippet-admin-quick-nav/ Description: en: https://www.altmann.de/en/blog-en/code-snippet-admin-quick-nav/ de: https://www.altmann.de/blog/code-snippet-admin-schnellnavigation/ Copyright: © 2024, Matthias Altmann TESTED WITH: Product Versions -------------------------------------------------------------------------------------------------------------- PHP 8.1, 8.2 WordPress 6.4.2, 6.4.3, 6.5 Bricks 1.9.5, 1.9.6, 1.9.7 Oxygen 4.8.1 -------------------------------------------------------------------------------------------------------------- VERSION HISTORY: Date Version Description -------------------------------------------------------------------------------------------------------------- 2024-04-09 1.3.0 New Features: - Support for selecting status of posts to be listed 2024-04-05 1.2.0 Changes: - Disabled Polylang language filter to show all languages, also in frontend - Changed some direct Bricks function calls to call_user_func() to avoid warnings in code editor on non-Bricks setups 2024-02-07 1.1.0 New Features: - New action "View" (except for Oxygen/Bricks templates) 2024-02-03 1.0.2 Fixes: - Corrected semantical issue by moving svg symbols from head to footer - Corrected action icon width. 2024-01-30 1.0.1 Fixes: - Added additional permission check for Bricks templates Tested: - PHP 8.2 2024-01-28 1.0.0 Changes: - Stretch sub menu item links - Improved translation - Empty limit now means unlimited Inspired by Michael Pucher: - Sortable post types - Collapsed or individual menus for post types - Allow sorting by menu_order - Removed title attribute from top menu because that tooltip is distracting Tested: - PHP 8.1 - WordPress 6.4.2 - Bricks 1.9.5 - Oxygen 4.8.1 2024-01-26 0.0.1 Internal test version supporting Oxygen and Bricks 2024-01-20 0.0.0 Development start -------------------------------------------------------------------------------------------------------------- Icon Arrow: https://css.gg/arrow-top-right-o Icon Eye: https://css.gg/eye */ if (!class_exists('MA_Admin_Quick_Nav')) : class MA_Admin_Quick_Nav { const TITLE = 'Quick Nav'; const SLUG = 'ma-admin-quick-nav'; const SHRT = 'maqn'; const VERSION = '1.3.0'; // ===== CONFIGURATION ============================================================================================== public static $timing = false; // Write timing info to wordpress debug.log if WP_DEBUG also enabled. // false/0: Disabled, true/1: Enabled, 2: Extended. public static $expert_mode = false; // enables some additional settings for the experts, like menu_position // ===== INTERNAL =================================================================================================== private const DEFAULT_MENU_POSITION = 80; private const DEFAULT_POST_TYPES = ['page'=>[],'post'=>[]]; private const DEFAULT_LIMIT = 20; private const DEFAULT_ORDERBY = 'post_date'; private const DEFAULT_ORDER = 'DESC'; private const DEFAULT_STATUS = ['publish']; private const VALID_ORDERBY = ['post_date','post_title','menu_order']; private const VALID_ORDER = ['ASC','DESC']; public static $total_runtime = 0; private $settings = null; //------------------------------------------------------------------------------------------------------------------- function __construct() { $st = microtime(true); // read settings from db and merge to default settings $this->settings = array_merge([ 'menu_position' => self::DEFAULT_MENU_POSITION, 'post_types' => [], 'status' => self::DEFAULT_STATUS, ], get_option(self::SLUG, ['post_types'=>self::DEFAULT_POST_TYPES])); // emit CSS for backend and frontend add_action('admin_enqueue_scripts', [$this, 'css']); add_action('wp_enqueue_scripts', [$this, 'css']); // emit SVG for backend and frontend add_action('admin_footer', [$this, 'svg']); add_action('wp_footer', [$this, 'svg']); add_action('admin_enqueue_scripts', [$this, 'admin_scripts']); // display menu only for wp-admin area if (is_admin()) { // register admin menus (must be registered with hook, otherwise appears on top of menu) add_action('admin_menu', [$this, 'admin_menu'], 100 ); // register settings add_action('admin_init', [$this, 'settings_register'] ); // TODO: custom translations //add_action('after_setup_theme',[$this,'l10n']); } // builder admin bar? if ($this->settings['builder_admin_bar']??false) { // must hook before init add_action('after_setup_theme',[$this,'builder_admin_bar']); } // register admin bar menu items add_action('admin_bar_menu',[$this, 'admin_bar_items'], intval($this->settings['menu_position']??self::DEFAULT_MENU_POSITION)); // add a handler for logging total runtime add_action('shutdown', [$this,'total_runtime']); $et = microtime(true); if (WP_DEBUG && self::$timing) {error_log(sprintf('%s%s::%s() Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, $et-$st));} self::$total_runtime += $et-$st; } //------------------------------------------------------------------------------------------------------------------- /** * Logs total timing. */ public static function total_runtime(){ if (WP_DEBUG && self::$timing) {error_log(sprintf('%s%s::%s() Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, self::$total_runtime));} } //------------------------------------------------------------------------------------------------------------------- public function l10n(){ global $l10n; // TODO: custom translations } //=================================================================================================================== // SETTINGS //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- /** * Register the settings and validation method. */ public function settings_register() { register_setting( self::SLUG, // option group self::SLUG, // option name [ 'type' => 'array', 'sanitize_callback' => [$this, 'settings_validate'], ] ); } //------------------------------------------------------------------------------------------------------------------- /** * Creates the Settings menu. */ public function admin_menu(){ add_options_page( self::TITLE.' '.__('Settings'), // page title self::TITLE, // menu title 'manage_options', // capabilitiy self::SLUG, // menu slug [$this, 'settings'] // function ); } //------------------------------------------------------------------------------------------------------------------- /** * Outputs the settings page. */ public function settings() { // check user capabilities if (!current_user_can('manage_options')) return; // show error/update messages settings_errors(self::SLUG . '_messages'); // get the registered post types $registered_post_types = get_post_types([],'OBJECT'); // get the registered post statuses $post_statuses = get_post_statuses(); ?> <div class="wrap <?php echo self::SLUG;?>-settings" id="<?php echo self::SLUG;?>-settings"> <h1><?php echo esc_html(get_admin_page_title()); ?></h1> <style> .ptl {border:1px solid lightgray; border-collapse:collapse;} .ptl thead {background-color:lightgray;} .ptl tr {border-bottom:1px solid lightgray;} .ptl :where(th,td) {text-align:left; vertical-align:top; padding:5px;} .ptl :where(th,td).sort {width:20px;} .ptl :where(th,td).sort svg.sort-handle {color:gray; width:1em; height:1em; padding-top:5px; cursor:ns-resize;} .ptl input[type='number'] {width:10ch;} .ptl .details * {visibility:hidden;} .ptl .active .details * {visibility:visible;} .general input[type='number'] {width:10ch;} </style> <form method="post" action="options.php"> <?php settings_fields(self::SLUG); ?> <h2><?php _e('Post Types'); ?></h2> <p>Enable Admin Bar Menu for Post Types:</p> <table class="ptl" role="presentation"> <thead> <tr> <th class="sort"></th> <th><?php _e('Post Type'); ?></th> <th><?php _e('Sort Order:'); ?></th> <th><?php _e('Order'); ?></th> <th><?php _ex('Count','Number/count of items'); ?></th> <th><?php _e('Advanced Options'); ?></th> </tr> </thead> <tbody> <?php // get post types from settings plus other registered post types $post_types = array_unique(array_merge(array_keys($this->settings['post_types']), array_keys(wp_list_pluck($registered_post_types,'name')))); foreach ($post_types as $post_type): // check if configured post type is still a registered post type if (!$reg_post_type = $registered_post_types[$post_type]) continue; // skip post types that are not configured to be listed in nav menus if (!$reg_post_type->show_in_nav_menus) continue; // loop valid post types $ptn = $reg_post_type->name; $ptl = $reg_post_type->label; $pts = $this->settings['post_types']??[]; $pto = array_merge([ 'orderby' => self::DEFAULT_ORDERBY, 'order' => self::DEFAULT_ORDER, 'limit' => self::DEFAULT_LIMIT, 'hierarchical' => false, 'builder' => false, ],$pts[$ptn] ?? []); $label = $ptl . ' (<code>'.$ptn.'</code>)'; // label adaption for special cases if ($ptn == 'ct_template') {$label = 'Oxygen '.$label;} ?> <tr class="post_type_row <?php echo in_array($ptn,array_keys($pts)) ? 'active' : '';?>"> <td class="sort"> <svg class="sort-handle"><use xlink:href="#icon-sort"></use></svg> </td> <td> <input type="checkbox" name="post_types[]" id="post_type_<?php echo $ptn;?>" value="<?php echo $ptn;?>" <?php checked(in_array($ptn,array_keys($pts)), true);?>> <label for="post_type_<?php echo $ptn;?>"><?php echo $label;?></label> </td> <td class="details"> <select name="<?php echo $ptn;?>-orderby" id="<?php echo $ptn;?>-orderby"> <option value="post_date" <?php selected($pto['orderby'], 'post_date'); ?>><?php _e('Created');?></option> <option value="post_title" <?php selected($pto['orderby'], 'post_title'); ?>><?php _e('Title');?></option> <option value="menu_order" <?php selected($pto['orderby'], 'menu_order'); ?>><?php _e('Menu Order');?></option> </select> </td> <td class="details"> <select name="<?php echo $ptn;?>-order" id="<?php echo $ptn;?>-order"> <option value="ASC" <?php selected($pto['order'], 'ASC'); ?>><?php _e('Ascending');?></option> <option value="DESC" <?php selected($pto['order'], 'DESC'); ?>><?php _e('Descending');?></option> </select> </td> <td class="details"> <input type="number" name="<?php echo $ptn;?>-limit" id="<?php echo $ptn;?>-limit" value="<?php echo $pto['limit'];?>"> </td> <td class="details"> <?php if ($reg_post_type->hierarchical === true): $hint = 'Show hierarchy by indenting list'; ?> <input type="checkbox" name="<?php echo $ptn;?>-hierarchical" id="<?php echo $ptn;?>-hierarchical" title="<?php echo $hint;?>" value="1" <?php checked($pto['hierarchical'],true);?>> <label for="<?php echo $ptn;?>-hierarchical" title="<?php echo $hint;?>"><?php _e('Show hierarchy');?></label> <br> <?php endif; ?> <?php if ($this->builder()): $hint = 'Show icon to allow editing in '.$this->builder(); ?> <input type="checkbox" name="<?php echo $ptn;?>-builder" id="<?php echo $ptn;?>-builder" title="<?php echo $hint;?>" value="1" <?php checked($pto['builder'],true);?>> <label for="<?php echo $ptn;?>-builder" title="<?php echo $hint;?>"><?php printf(__('Edit').' in %s Builder',$this->builder());?></label> <?php endif; ?> </td> </tr> <?php endforeach; ?> </tbody> </table> <script> document.querySelectorAll('input[name^="post_types[]"]').forEach($c=>{ $c.addEventListener('change',($e)=>{ let $r = $e.currentTarget.closest('.post_type_row'); if ($e.currentTarget.checked) {$r.classList.add('active');} else {$r.classList.remove('active');} }); }); jQuery(document).ready(function(){ jQuery('table.ptl tbody').sortable({ handle: '.sort-handle', cursor: 'ns-resize', axis: 'y', }); }); </script> <h2><?php _e('General'); ?></h2> <div class="general"> <?php if (self::$expert_mode): ?> <div> <?php $hint = sprintf('Set the position for the %s menu(s)', self::TITLE); ?> <label for="menu_position" title="<?php echo $hint;?>"><?php printf(__('Menu Location'), self::TITLE);?></label> <input type="number" name="menu_position" id="menu_position" value="<?php echo $this->settings['menu_position']?>"> </div> <?php endif; ?> <div> <?php $hint = sprintf('Collapse all post type menus into one menu "%s"', self::TITLE); ?> <input type="checkbox" name="collapsed_menu" id="collapsed_menu" title="<?php echo $hint;?>" value="1" <?php checked($this->settings['collapsed_menu']??false,true);?>> <label for="collapsed_menu" title="<?php echo $hint;?>"><?php printf(__('Collapse menu').' in "%s"', self::TITLE);?></label> </div> <?php if ($this->builder()): ?> <div> <?php $hint = sprintf('Enable admin bar in %s builder',$this->builder()); ?> <input type="checkbox" name="builder_admin_bar" id="builder_admin_bar" title="<?php echo $hint;?>" value="1" <?php checked($this->settings['builder_admin_bar']??false,true);?>> <label for="builder_admin_bar" title="<?php echo $hint;?>"><?php printf(__('Admin Bar in %s Builder'),$this->builder());?></label> </div> <?php endif; ?> <div> <h3>Status</h3> <?php foreach ($post_statuses as $status => $status_label): ?> <?php $hint = sprintf('List items with status “%s“', $status_label); ?> <input type="checkbox" name="status[]" title="<?php echo $hint;?>" value="<?php echo $status;?>" <?php checked(in_array($status,$this->settings['status']),true);?>> <?php echo $status_label;?><br/> <?php endforeach;?> </div> </div> <?php submit_button(); ?> </form> </div> <div style="margin-top:3em;border-top:1px solid lightgray;font-size:70%;"> © 2024, <a href="https://www.altmann.de/" target="_blank">Matthias Altmann</a> • <?php echo basename(__FILE__) == 'ma-admin-quick-nav.php' ? 'Plugin' : 'Code Snippet';?> Version </php><?php echo self::VERSION;?> • <a href="https://www.altmann.de/en/blog-en/code-snippet-admin-quick-nav/" target="_blank">Downloads & Documentation</a> </div> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1"> <defs> <symbol id="icon-sort" viewBox="0 0 500 500"> <title><?php _e('Reorder'); ?></title> <path fill="currentColor" d="M285.714 303.571q0 7.254-5.301 12.556l-125 125q-5.301 5.301-12.556 5.301t-12.556-5.301l-125-125q-5.301-5.301-5.301-12.556t5.301-12.556 12.556-5.301h250q7.254 0 12.556 5.301t5.301 12.556zM285.714 196.429q0 7.254-5.301 12.556t-12.556 5.301h-250q-7.254 0-12.556-5.301t-5.301-12.556 5.301-12.556l125-125q5.301-5.301 12.556-5.301t12.556 5.301l125 125q5.301 5.301 5.301 12.556z" /> </symbol> </defs> </svg> <?php } //------------------------------------------------------------------------------------------------------------------- /** * Validates the submitted settings. */ public function settings_validate() { $input = $_REQUEST; $valid = ['post_types'=>[]]; // check selected post types $allowed_post_types = wp_list_pluck(get_post_types([],'OBJECT'),'name'); foreach(($input['post_types']??[]) as $ptn) { if (in_array($ptn, $allowed_post_types)) { $pto = []; if (in_array($input[$ptn.'-orderby']??'',self::VALID_ORDERBY)) { $pto['orderby'] = $input[$ptn.'-orderby']; } if (in_array($input[$ptn.'-order']??'',self::VALID_ORDER)) { $pto['order'] = $input[$ptn.'-order']; } $limit = $input[$ptn.'-limit'] ?? null; if (isset($limit)) { $pto['limit'] = ''; if ($limit !== '') { $limit = intval($limit); if ($limit !== 0) { $pto['limit'] = $limit; } } } if ($input[$ptn.'-hierarchical']??'') { $pto['hierarchical'] = true; } if ($input[$ptn.'-builder']??'') { $pto['builder'] = true; } $valid['post_types'][$ptn] = $pto; } } if ($menu_position = $input['menu_position'] ?? null) { $valid['menu_position'] = intval($menu_position); } if ($input['builder_admin_bar']??'' == '1') { $valid['builder_admin_bar'] = true; } if ($input['collapsed_menu']??'' == '1') { $valid['collapsed_menu'] = true; } if ($input['status']??null) { $valid['status'] = $input['status']; } return $valid; } //=================================================================================================================== // ADMIN BAR //------------------------------------------------------------------------------------------------------------------- /** * Enables admin bar in builders. */ public function builder_admin_bar() { if ($this->builder('Oxygen')) { // disable Oxygen's admin bar removal remove_action('init','ct_hide_admin_bar'); // adds Oxygen builder style adaptions if necessary add_action('wp_enqueue_scripts', [$this,'builder_css_oxygen']); add_action('init', function(){ // don't show admin bar in builder iframe if (defined('OXYGEN_IFRAME')) { add_filter('show_admin_bar','__return_false'); } }); } if ($this->builder('Bricks')) { // adds Bricks builder style adaptions if necessary add_action('wp_head', [$this,'builder_css_bricks']); // show admin bar in Bricks add_action('init', function () { // only if Bricks editor active if (function_exists('bricks_is_builder_main') && call_user_func('bricks_is_builder_main')) { add_filter('show_admin_bar', '__return_true'); } }); } } //------------------------------------------------------------------------------------------------------------------- /** * Emits CSS to adapt Oxygen builder. */ public function builder_css_oxygen() { // only for loading Oxygen UI, but not in preview iframe if (!defined('SHOW_CT_BUILDER')) return; if (defined('OXYGEN_IFRAME')) return; // checked with Oxygen 4.8.1 $builder_css = <<<STYLE_END /* new top */ #oxygen-ui .oxygen-add-section-library-flyout-panel, #oxygen-ui .ct-panel-elements-managers, #oxygen-ui .oxygen-global-settings { top: calc(40px + var(--wp-admin--admin-bar--height,0px)); } #ct-viewport-container #ct-viewport-ruller-wrap { top: calc(100vh - 40px - var(--wp-admin--admin-bar--height,0px)); } /* new height */ #oxygen-ui .oxygen-add-section-library-flyout-panel, #oxygen-ui #oxygen-sidebar, #ct-viewport-container #ct-viewport-ruller-wrap { height: calc(100vh - 40px - var(--wp-admin--admin-bar--height,0px)); } #ct-dialog-window .oxygen-data-dialog { height: 80%; } #oxygen-ui #ct-dom-tree-2 { height: calc(100vh - 119px - var(--wp-admin--admin-bar--height,0px)); } STYLE_END; echo '<style id="'.self::SLUG.'-css-oxygen">'.$builder_css.'</style>'; } //------------------------------------------------------------------------------------------------------------------- /** * Emits CSS to adapt Bricks builder. */ public function builder_css_bricks() { if (!function_exists('bricks_is_builder_main') || !call_user_func('bricks_is_builder_main')) return; // checked with Bricks 1.9.5 $builder_css = <<<STYLE_END body.admin-bar #bricks-toolbar { top: var(--wp-admin--admin-bar--height,0px); } #bricks-panel, #bricks-preview, #bricks-structure { height: calc(100vh - 40px - var(--wp-admin--admin-bar--height,0px)); } body.admin-bar #bricks-structure { top: calc(40px + var(--wp-admin--admin-bar--height,0px)); } STYLE_END; echo '<style id="'.self::SLUG.'-css-bricks">'.$builder_css.'</style>'; } //------------------------------------------------------------------------------------------------------------------- /** * Enqueues jQuery sortable for settings page. */ public function admin_scripts(){ if (function_exists('get_current_screen')) { $screen = get_current_screen(); if ($screen->id == 'settings_page_'.self::SLUG) { wp_enqueue_script('jquery-ui-sortable'); } } } //------------------------------------------------------------------------------------------------------------------- /** * Emits CSS for menu styling. */ public function css(){ $st = microtime(true); do_action('qm/start', __METHOD__); // Query Monitor Profiling // Get user's color scheme to pick some colors $color_scheme = get_user_option('admin_color', get_current_user_id()); $color_hover = $GLOBALS['_wp_admin_css_colors'][$color_scheme]->colors[0]??''; $color_line = $GLOBALS['_wp_admin_css_colors'][$color_scheme]->colors[2]??''; $css_props_status = 'font-family:monospace;font-style:italic;'; $icon_size = '16px'; $slug = self::SLUG; $shrt = self::SHRT; $style=<<<STYLE_END <style id="{$slug}-css"> /* highlight row on hover */ #wpadminbar .{$shrt} ul li:hover {background-color:{$color_hover}; } /* flexbox for icon+label */ #wpadminbar .{$shrt} .{$shrt}-flexer {display:flex; flex-direction:row; align-items:center;} #wpadminbar .{$shrt} ul ul .{$shrt}-flexer {justify-content:space-between;} #wpadminbar .{$shrt} .ab-item {width:100%;} /* post-list */ #wpadminbar .{$shrt}-post-list .ab-sub-wrapper ul {max-height:80vh; overflow:hidden auto; padding-bottom:10px;} /* status */ #wpadminbar .{$shrt}-post-list .ab-sub-wrapper ul li span.status {{$css_props_status};} /* action links */ #wpadminbar .{$shrt}-post-list .{$shrt}-action-links {display:flex; flex-direction:row; margin:0 1em;} #wpadminbar .{$shrt}-post-list .{$shrt}-action-links a {width:{$icon_size}; height:{$icon_size}; line-height:normal; padding:0 5px; opacity:.5;} #wpadminbar .{$shrt}-post-list .{$shrt}-action-links a.{$shrt}-has-content {opacity:1;} /* icons & svg */ #wpadminbar .{$shrt} .dashicons {font:{$icon_size} dashicons; line-height:normal; color:inherit;} #wpadminbar .{$shrt} svg {width:{$icon_size}; height:{$icon_size}; margin-right:6px;} #wpadminbar li.{$shrt} span.dashicon-before {color:inherit;} #wpadminbar li.{$shrt} span.dashicon-before::before {width:{$icon_size}; height:{$icon_size}; color:inherit;} /* divider */ #wpadminbar :is(.{$shrt}-divider,#incspec) {margin-top: .5em; border-top:1px dotted {$color_line}; height:26px;} </style> STYLE_END; // minimize $style = preg_replace('/\/\*.*?\*\//', '', $style); // remove comments $style = preg_replace('/\r?\n */', '', $style); // remove line breaks $style = preg_replace('/\t/', '', $style); // remove tabs echo $style.PHP_EOL; do_action('qm/stop', __METHOD__); // Query Monitor Profiling $et = microtime(true); if (WP_DEBUG && self::$timing) {error_log(sprintf(' %s%s::%s() Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, $et-$st));} self::$total_runtime += $et-$st; } //------------------------------------------------------------------------------------------------------------------- /** * Emits SVG for menu icons. */ public function svg(){ $st = microtime(true); do_action('qm/start', __METHOD__); // Query Monitor Profiling $slug = self::SLUG; $shrt = self::SHRT; $builder_logo = ''; if ($this->builder('Oxygen')) { $builder_logo = <<<LOGO_END <symbol id="logo-oxygen" viewBox="0 0 381 385"> <path fill="currentColor" d="M297.508,349.748 C275.443,349.748 257.556,331.86 257.556,309.796 C257.556,287.731 275.443,269.844 297.508,269.844 C319.573,269.844 337.46,287.731 337.46,309.796 C337.46,331.86 319.573,349.748 297.508,349.748 L297.508,349.748 Z M222.304,309.796 C222.304,312.039 222.447,314.247 222.639,316.441 C212.33,319.092 201.528,320.505 190.403,320.505 C119.01,320.505 60.929,262.423 60.929,191.031 C60.929,119.638 119.01,61.557 190.403,61.557 C261.794,61.557 319.877,119.638 319.877,191.031 C319.877,206.833 317.02,221.978 311.815,235.99 C307.179,235.097 302.404,234.592 297.508,234.592 C255.974,234.592 222.304,268.262 222.304,309.796 L222.304,309.796 Z M380.805,191.031 C380.805,86.042 295.392,0.628 190.403,0.628 C85.414,0.628 0,86.042 0,191.031 C0,296.02 85.414,381.433 190.403,381.433 C212.498,381.433 233.708,377.609 253.456,370.657 C265.845,379.641 281.034,385 297.508,385 C339.042,385 372.712,351.33 372.712,309.796 C372.712,296.092 368.988,283.283 362.584,272.219 C374.251,247.575 380.805,220.058 380.805,191.031 L380.805,191.031 Z"/> </symbol> LOGO_END; } if ($this->builder('Bricks')) { $builder_logo = <<<LOGO_END <symbol id="logo-bricks" viewBox="0 0 35 45"> <path fill="currentColor" transform="translate(-16.000000, -11.000000)" d="M25.1875,11.34375 L25.9375,11.8125 L25.9375,24.84375 C28.5833466,23.0937413 31.5104006,22.21875 34.71875,22.21875 C39.3437731,22.21875 43.1770681,23.8333172 46.21875,27.0625 C49.218765,30.2916828 50.71875,34.2708097 50.71875,39 C50.71875,43.7500237 49.2083484,47.7291506 46.1875,50.9375 C43.1458181,54.1666828 39.3229397,55.78125 34.71875,55.78125 C30.6978966,55.78125 27.2604309,54.3437644 24.40625,51.46875 L24.40625,55 L16.03125,55 L16.03125,12.375 L25.1875,11.34375 Z M33.125,30.6875 C30.9166556,30.6875 29.0729241,31.4374925 27.59375,32.9375 C26.1145759,34.4791744 25.375,36.4999875 25.375,39 C25.375,41.5000125 26.1145759,43.5104091 27.59375,45.03125 C29.0520906,46.5520909 30.8958222,47.3125 33.125,47.3125 C35.4791784,47.3125 37.3854094,46.5208413 38.84375,44.9375 C40.2812572,43.3749922 41,41.3958453 41,39 C41,36.6041547 40.2708406,34.6145913 38.8125,33.03125 C37.3541594,31.4687422 35.458345,30.6875 33.125,30.6875 Z"/> </symbol> LOGO_END; } $svg=<<<SVG_END <svg id="{$slug}-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1"> <defs> <symbol id="logo-{$slug}" viewBox="0 0 24 24"> <path fill="currentColor" d="M14 13.9633H16V7.96331H10V9.96331H12.5858L7.25623 15.2929L8.67044 16.7071L14 11.3775L14 13.9633Z"/> <path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12ZM3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z"/> </symbol> <symbol id="icon-{$slug}-eye" viewBox="0 0 24 24"> <path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M16 12C16 14.2091 14.2091 16 12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12ZM14 12C14 13.1046 13.1046 14 12 14C10.8954 14 10 13.1046 10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12Z"/> <path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M12 3C17.5915 3 22.2898 6.82432 23.6219 12C22.2898 17.1757 17.5915 21 12 21C6.40848 21 1.71018 17.1757 0.378052 12C1.71018 6.82432 6.40848 3 12 3ZM12 19C7.52443 19 3.73132 16.0581 2.45723 12C3.73132 7.94186 7.52443 5 12 5C16.4756 5 20.2687 7.94186 21.5428 12C20.2687 16.0581 16.4756 19 12 19Z"/> </symbol> {$builder_logo} </defs> </svg> SVG_END; // minimize $svg = preg_replace('/>.*?</s', '><', $svg); // remove formatting content (line breaks, tabs) between tags $svg = preg_replace('/\r?\n\t+/', ' ', $svg); // remove remaining line breaks and tabs echo $svg.PHP_EOL; do_action('qm/stop', __METHOD__); // Query Monitor Profiling $et = microtime(true); if (WP_DEBUG && self::$timing) {error_log(sprintf(' %s%s::%s() Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, $et-$st));} self::$total_runtime += $et-$st; } //------------------------------------------------------------------------------------------------------------------- /** * Adds items to the admin bar. */ public function admin_bar_items (WP_Admin_Bar $wp_admin_bar) { $st = microtime(true); do_action('qm/start', __METHOD__); // Query Monitor Profiling // basic permission check if (!current_user_can('edit_posts')) return; // get the registered post types $registered_post_types = get_post_types([],'OBJECT'); // get post statuses $post_statuses = get_post_statuses(); $parent_menu_slug = null; if ($this->settings['collapsed_menu']??false) { $parent_menu_slug = self::SLUG; // create admin bar main item $label = 'Quick Nav'; $icon = sprintf('<svg><use xlink:href="#logo-%1$s"></use></svg>', self::SLUG); $wp_admin_bar->add_menu([ 'id' => $parent_menu_slug, 'title' => sprintf('<span class="%s-flexer">%s</span>', self::SHRT, $icon.$label), 'meta' => [ 'class' => self::SHRT, // 'title' => self::TITLE, // don't show because it's distracting ] ]); } // create sub menus for post types foreach ($this->settings['post_types']??[] as $ptn => $pto) { $stpt = microtime(true); do_action('qm/start', __METHOD__.' Post Type "'.$ptn.'"'); // Query Monitor Profiling if (WP_DEBUG && self::$timing>1) {error_log(sprintf(' %s%s::%s() Post Type "%s" Loop Start', '', __CLASS__, __FUNCTION__, $ptn));} // permission check, either specific for this post type, or generic for posts $capability = $registered_post_types[$ptn]->capability_type ?? $ptn; if (!current_user_can('edit_'.$capability.'s')) goto DONE_POSTTYPE; // check permission to access Oxygen templates if ($this->builder('Oxygen') && ($ptn=='ct_template') && function_exists('oxygen_vsb_current_user_can_access') && !oxygen_vsb_current_user_can_access()) { if (WP_DEBUG && self::$timing>1) {error_log(sprintf(' %s%s::%s() User has no permission to access "%s"', '', __CLASS__, __FUNCTION__, $ptn));} goto DONE_POSTTYPE; } // check permission to access Bricks templates if ($this->builder('Bricks') && ($ptn=='bricks_template') && method_exists('\Bricks\Capabilities','current_user_can_use_builder') && !call_user_func('\Bricks\Capabilities::current_user_can_use_builder')) { if (WP_DEBUG && self::$timing>1) {error_log(sprintf(' %s%s::%s() User has no permission to access "%s"', '', __CLASS__, __FUNCTION__, $ptn));} goto DONE_POSTTYPE; } // prepare label, title, icon $label = $registered_post_types[$ptn]->label; $title = $registered_post_types[$ptn]->labels->all_items ?? $label; $icon_label = '<span class="ab-item dashicon-before '.$registered_post_types[$ptn]->menu_icon.'">'.$label.'</span>'; if ($this->builder('Oxygen') && $ptn == 'ct_template') { //$label = 'Oxygen Templates'; $icon = '<svg><use xlink:href="#logo-oxygen"></use></svg>'; $icon_label = $icon.$label; } if ($this->builder('Bricks') && $ptn == 'bricks_template') { //$label = 'Bricks Templates'; $icon = '<svg><use xlink:href="#logo-bricks"></use></svg>'; $icon_label = $icon.$label; } // create menu item for post type $wp_admin_bar->add_menu([ 'id' => sprintf('%s-%s', self::SLUG, $ptn), 'parent'=> $parent_menu_slug, 'title' => sprintf('<span class="%s-flexer">%s</span>', self::SHRT, $icon_label), 'href' => admin_url('edit.php?post_type='.$ptn), 'meta' => [ 'class' => self::SHRT.' '.self::SHRT.'-post-list', 'title' => $title, ] ]); // get config for post type list $pto = array_merge([ 'orderby' => self::DEFAULT_ORDERBY, 'order' => self::DEFAULT_ORDER, 'limit' => self::DEFAULT_LIMIT, 'hierarchical' => false, ],$this->settings['post_types'][$ptn] ?? []); if ($pto['hierarchical']) { // get_pages() returns hierachical results $args = [ 'post_type' => $ptn, 'post_status' => $this->settings['status'], 'sort_column' => $pto['orderby'], 'sort_order' => $pto['order'], 'number' => $pto['limit'] === '' ? -1 : $pto['limit'], ]; if (defined('POLYLANG_VERSION') ) { $args['lang'] = ''; // Deactivate Polylamg language filter, @see https://polylang.pro/doc/developpers-how-to/ } $posts = get_pages($args); } else { // get_posts() doesn't provide hierachical results $args = [ 'post_type' => $ptn, 'post_status' => $this->settings['status'], 'orderby' => $pto['orderby'], 'order' => $pto['order'], 'posts_per_page' => $pto['limit'] === '' ? -1 : $pto['limit'], ]; if (defined('POLYLANG_VERSION') ) { $args['lang'] = ''; // Deactivate Polylang language filter, @see https://polylang.pro/doc/developpers-how-to/ } $posts = get_posts($args); } $hierarchy_parents = []; // loop posts foreach ($posts as $post) { // permission check if (!current_user_can('edit_'.$capability, $post->ID)) continue; // hierarchy view if ($pto['hierarchical']) { // indent post titles as necessary if ($post->post_parent??0) { $parent_level = array_search($post->post_parent, $hierarchy_parents); if ($parent_level !== false) { $post->post_title = str_replace(' ','–',str_pad('',$parent_level+1,' ')).' '.$post->post_title; if ($parent_level < count($hierarchy_parents)-1) { $hierarchy_parents = array_slice($hierarchy_parents, 0, $parent_level+1); } $hierarchy_parents[] = $post->ID; } } else { $hierarchy_parents = [$post->ID]; } } $actions = array_merge([], $this->actions_wordpress($post)); if ($pto['builder']??false) { if ($this->builder('Oxygen')) {$actions = array_merge($actions, $this->actions_oxygen($post));} if ($this->builder('Bricks')) {$actions = array_merge($actions, $this->actions_bricks($post));} } // provide view action, except for templates if (!in_array($ptn, ['ct_template','bricks_template'])) { $actions = array_merge($actions, $this->actions_frontend($post)); } // create builder links $action_links = ''; foreach ($actions as $link) { $action_links .= sprintf('<a href="%s" title="%s" class="%s">%s</a>', $link->href??'', $link->title??'', $link->has_content?self::SHRT.'-has-content':'', $link->icon??''); } if ($action_links) { $action_links = sprintf('<span class="%s-action-links">%s</span>', self::SHRT, $action_links); } $title_appendix = ($post->post_status == 'publish') ? '' : ' <span class="status">['.$post_statuses[$post->post_status].']</span>'; // create menu item for post $wp_admin_bar->add_menu([ 'id' => sprintf('%s-%s-%s', self::SLUG, $ptn, $post->ID), 'parent'=> sprintf('%s-%s', self::SLUG, $ptn), 'group' => null, // 'title' => sprintf('<span class="%s-flexer">%s</span>', self::SHRT, $post->post_title), 'title' => $post->post_title . $title_appendix, 'href' => admin_url(sprintf('post.php?post=%d&action=edit', $post->ID)), 'meta' => [ 'title' => __('Edit').' in WordPress', 'html' => $action_links, 'class' => self::SHRT.'-flexer', ] ]); } if ($pto['limit'] && wp_count_posts($ptn)->publish > $pto['limit']) { // add a hint about limits $link = false; $hint = __('Number of entries exceeds limit.'); if (current_user_can('manage_options')) { $link = admin_url('options-general.php?page='.self::SLUG); $hint .= ' '.__('Click to change the limit'); } $wp_admin_bar->add_node([ 'id' => sprintf('%s-%s-%s', self::SLUG, $ptn, 'limit-hint'), 'parent'=> sprintf('%s-%s', self::SLUG, $ptn), 'group' => null, 'title' => 'Limit: '.$pto['limit'], 'href' => $link, 'meta' => [ 'title' => $hint, 'class' => sprintf('%1$s-divider %1$s-limit', self::SHRT), ] ]); } DONE_POSTTYPE: do_action('qm/stop', __METHOD__.' Post Type "'.$ptn.'"'); // Query Monitor Profiling $etpt = microtime(true); if (WP_DEBUG && self::$timing) {error_log(sprintf(' %s%s::%s() Post Type "%s" Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, $ptn, $etpt-$stpt));} } // create menu item for settings if (current_user_can('manage_options')) { $wp_admin_bar->add_menu([ 'id' => sprintf('%s-%s', self::SLUG, 'settings'), 'parent'=> self::SLUG, 'title' => sprintf('<span class="%s-flexer">%s</span>', self::SHRT, __('Settings')), 'href' => admin_url('options-general.php?page='.self::SLUG), 'meta' => [ 'class' => sprintf('%1$s-divider', self::SHRT), 'title' => __('Settings'), ] ]); } do_action('qm/stop', __METHOD__); // Query Monitor Profiling $et = microtime(true); if (WP_DEBUG && self::$timing) {error_log(sprintf(' %s%s::%s() Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, $et-$st));} self::$total_runtime += $et-$st; } //------------------------------------------------------------------------------------------------------------------- /** * Returns action links for WordPress. * @param WP_Post $post The WP_Post to generate action links for. * @return array An array of objects containing properties `href`, `icon`, `title`, `has_content`. */ private function actions_wordpress(WP_Post $post):array { $st = microtime(true); $retval = []; $link = admin_url(sprintf('post.php?post=%d&action=edit', $post->ID)); $icon = '<span class="dashicons dashicons-wordpress" ></span>'; $retval[] = (object)['href'=>esc_url($link), 'icon'=>$icon, 'title'=>__('Edit').' in WordPress', 'has_content' => $post->post_content?1:0]; DONE: $et = microtime(true); if (WP_DEBUG && self::$timing>1) {error_log(sprintf(' %s%s::%s(%s) Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, ($post->post_type??'').' '.($post->ID??''), $et-$st));} return $retval; } //------------------------------------------------------------------------------------------------------------------- /** * Returns action link for view in frontend. * @param WP_Post $post The WP_Post to generate action links for. * @return array An array of objects containing properties `href`, `icon`, `title`, `has_content`. */ private function actions_frontend(WP_Post $post):array { $st = microtime(true); $retval = []; $link = get_permalink($post->ID); $icon = sprintf('<svg><use xlink:href="#icon-%s-eye"></use></svg>',self::SLUG); $retval[] = (object)['href'=>esc_url($link), 'icon'=>$icon, 'title'=>__('View'), 'has_content' => 1]; DONE: $et = microtime(true); if (WP_DEBUG && self::$timing>1) {error_log(sprintf(' %s%s::%s(%s) Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, ($post->post_type??'').' '.($post->ID??''), $et-$st));} return $retval; } //------------------------------------------------------------------------------------------------------------------- /** * Returns action links for Oxygen. * @param WP_Post $post The WP_Post to generate action links for. * @return array An array of objects containing properties `href`, `icon`, `title`, `has_content`. */ private function actions_oxygen(WP_Post $post):array { $st = microtime(true); $retval = []; if (!$this->builder('Oxygen')) goto DONE; // Oxygen hidden for this post type? if (get_option('oxygen_vsb_ignore_post_type_'.$post->post_type) == 'true') goto DONE; // permission check if (!function_exists('oxygen_vsb_current_user_can_access') || !oxygen_vsb_current_user_can_access()) goto DONE; // Edit a template or a post? if ($post->post_type == 'ct_template') { $parent_template_id = get_post_meta($post->ID, 'ct_parent_template', true); $inner_content = false; if ($parent_template_id) { if ($json = get_post_meta($parent_template_id, 'ct_builder_json', true)) { $inner_content = (strpos($json, '"name":"ct_inner_content"') !== false); } else { if ($shortcodes = get_post_meta($parent_template_id, 'ct_builder_shortcodes', true)) { $inner_content = (strpos($shortcodes, '[ct_inner_content') !== false); } } } $edit_url = ct_get_post_builder_link($post->ID) . ($inner_content ? '&ct_inner=true' : ''); $icon = '<svg><use xlink:href="#logo-oxygen"></use></svg>'; $json = get_post_meta($post->ID, 'ct_builder_json', true); $retval[] = (object)['href'=>esc_url($edit_url), 'icon'=>$icon, 'title'=>sprintf(__('Edit').' in %s Builder',$this->builder()), 'has_content'=>!empty($json)]; } else { // -1: None, undefined or 0: Automatic, > 0: Manually assigned $template_id = (int)get_post_meta($post->ID, 'ct_other_template', true); if ($template_id == -1) { // None // nothing to do } elseif ($template_id > 0) { // manually assigned // get specified template post $template_post = get_post($template_id); $template_id = $template_post->ID ?? -1; // if template not found, assume None } else { // get template for single $template_post = ct_get_posts_template($post->ID); $template_id = $template_post->ID ?? -1; // if template not found, assume None } // if we have a template, check for inner content element $inner_content = false; if ($template_id && $template_id != -1) { if ($json = get_post_meta($template_id, 'ct_builder_json', true)) { $inner_content = (strpos($json, '"name":"ct_inner_content"') !== false); } else { // only read shortcodes if we didn't find json; might be an old unmigrated post if ($shortcodes = get_post_meta($template_id, 'ct_builder_shortcodes', true)) { $inner_content = (strpos($shortcodes, '[ct_inner_content') !== false); } } } // Check if post has Oxygen content $json = get_post_meta($post->ID, 'ct_builder_json', true); // only read shortcodes if we didn't find json; might be an old unmigrated post $shortcodes = ''; //$shortcodes = $json ? true : get_post_meta($post->ID, 'ct_builder_shortcodes', true); $icon = '<svg><use xlink:href="#logo-oxygen"></use></svg>'; $link = ct_get_post_builder_link($post->ID) . ($inner_content ? '&ct_inner=true' : ''); $retval[] = (object)['href'=>esc_url($link), 'icon'=>$icon, 'title'=>sprintf(__('Edit').' in %s Builder',$this->builder()), 'has_content'=>($json||$shortcodes)]; } DONE: $et = microtime(true); if (WP_DEBUG && self::$timing>1) {error_log(sprintf(' %s%s::%s(%s) Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, ($post->post_type??'').' '.($post->ID??''), $et-$st));} return $retval; } //------------------------------------------------------------------------------------------------------------------- /** * Returns action links for Bricks. * @param WP_Post $post The WP_Post to generate action links for. * @return array An array of objects containing properties `href`, `icon`, `title`, `has_content`. */ private function actions_bricks(WP_Post $post):array { $st = microtime(true); $retval = []; if (!$this->builder('Bricks')) goto DONE; // permission check if (!method_exists('\Bricks\Capabilities','current_user_can_use_builder') || !call_user_func('\Bricks\Capabilities::current_user_can_use_builder')) goto DONE; // create action link $icon = '<svg><use xlink:href="#logo-bricks"></use></svg>'; $link = call_user_func('\Bricks\Helpers::get_builder_edit_link',$post->ID); $template_type = call_user_func('\Bricks\Templates::get_template_type',$post->ID); $template_data = call_user_func('\Bricks\Database::get_data',$post->ID, $template_type); $retval[] = (object)['href'=>esc_url($link), 'icon'=>$icon, 'title'=>sprintf(__('Edit').' in %s Builder',$this->builder()), 'has_content'=>!empty($template_data)]; DONE: $et = microtime(true); if (WP_DEBUG && self::$timing>1) {error_log(sprintf(' %s%s::%s(%s) Timing: %.5f sec.', '', __CLASS__, __FUNCTION__, ($post->post_type??'').' '.($post->ID??''), $et-$st));} return $retval; } //=================================================================================================================== // UTILITIES //------------------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------------------- /** * Returns builder name or boolean if builder matches specified name `Bricks` or `Oxygen`. * @param string $builder [Optional] Checks if builder matches. * @return mixed Builder name, or true/false for builder name match */ private function builder(string $builder = null) { $retval = ''; if (defined('BRICKS_VERSION')) $retval = 'Bricks'; if (defined('CT_VERSION')) $retval = 'Oxygen'; if (isset($builder)) $retval = strtolower($retval) == strtolower($builder); return $retval; } } //=================================================================================================================== // Initialize new MA_Admin_Quick_Nav(); endif;