*/ private $active_snippets = []; /** * Get instance. * * @return self */ public static function instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Constructor. */ public function __construct() { add_action( 'admin_bar_menu', [ $this, 'add_admin_bar_menu' ], 100 ); add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] ); add_action( 'wp_footer', [ $this, 'update_admin_bar_with_js' ], 9999 ); add_action( 'admin_footer', [ $this, 'update_admin_bar_with_js' ], 9999 ); } /** * Enqueue admin bar styles. * * @return void */ public function enqueue_styles() { if ( ! is_admin_bar_showing() ) { return; } wp_add_inline_style( 'admin-bar', ' /* Main menu item */ #wp-admin-bar-woody-snippets .ab-icon:before { content: "{ }"; font-family: monospace; font-size: 16px; font-weight: bold; top: -2px; } #wp-admin-bar-woody-snippets > .ab-item .ab-label { padding-left: 4px; } #wp-admin-bar-woody-snippets .woody-count { display: inline-block; background: #10b981; color: #fff; border-radius: 50%; width: 20px; height: 20px; line-height: 20px; text-align: center; font-size: 11px; font-weight: 600; margin-left: 6px; vertical-align: middle; } #wp-admin-bar-woody-snippets .woody-safe-mode { display: inline-block; background: #dba617; color: #fff; border-radius: 4px; padding: 0px 6px; font-size: 8px; font-weight: 600; margin-left: 6px; vertical-align: middle; text-transform: uppercase; letter-spacing: 0.5px; } /* Dropdown submenu */ #wp-admin-bar-woody-snippets > .ab-sub-wrapper { width: 320px; } #wp-admin-bar-woody-snippets .ab-submenu { background: #23272a; } /* Header section */ #wp-admin-bar-woody-snippets-header { background: #23272a !important; border-bottom: 2px solid #3b82f6 !important; } #wp-admin-bar-woody-snippets-header > .ab-item { color: #60a5fa !important; background: transparent !important; font-weight: 600; text-transform: uppercase; font-size: 11px; letter-spacing: 0.1em; cursor: default !important; padding: 4px 16px !important; line-height: 1.2; min-height: auto !important; } #wp-admin-bar-woody-snippets-header:hover > .ab-item { background: transparent !important; color: #60a5fa !important; } /* Snippet items */ #wp-admin-bar-woody-snippets .woody-snippet-item > .ab-item { padding: 8px 16px !important; height: auto !important; line-height: 1.4; min-height: auto !important; background: #2c3338 !important; border-bottom: 1px solid rgb(60 67 74); } #wp-admin-bar-woody-snippets .woody-snippet-item { border-bottom: 1px solid rgba(255, 255, 255, 0.05); } #wp-admin-bar-woody-snippets .woody-snippet-item:hover > .ab-item { background: #32373c !important; } #wp-admin-bar-woody-snippets li.woody-snippet-item:last-of-type { border-bottom: none; } /* Snippet content wrapper */ #wp-admin-bar-woody-snippets .woody-snippet-content { display: flex; flex-direction: column; gap: 8px; } #wp-admin-bar-woody-snippets .woody-snippet-name { font-weight: 400; font-size: 15px; color: #e8eaed; white-space: wrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.3; } /* Snippet meta (type + location) */ #wp-admin-bar-woody-snippets .woody-snippet-meta { display: flex; align-items: center; gap: 10px; } /* Type badge */ #wp-admin-bar-woody-snippets .woody-snippet-type { display: inline-flex; align-items: center; justify-content: center; padding: 5px 11px; border-radius: 4px; font-weight: 700; font-size: 10px; text-transform: uppercase; letter-spacing: 0.02em; flex-shrink: 0; line-height: 1; } #wp-admin-bar-woody-snippets .woody-snippet-type.type-js { background: #f7df1e; color: #655b0e; } #wp-admin-bar-woody-snippets .woody-snippet-type.type-html { background: #e44d26; color: #fffffd; } #wp-admin-bar-woody-snippets .woody-snippet-type.type-php { background: #777bb3; color: #ffffff; } #wp-admin-bar-woody-snippets .woody-snippet-type.type-universal { background: #607D8B; color: #ffffff; } #wp-admin-bar-woody-snippets .woody-snippet-type.type-css { background: #1572b6; color: #ffffff; } #wp-admin-bar-woody-snippets .woody-snippet-type.type-text { background: #4CAF50; color: #fffffd; } #wp-admin-bar-woody-snippets .woody-snippet-type.type-ad { background: #26aa5d; color: #fffffd; } #wp-admin-bar-woody-snippets .woody-snippet-location { color: #a0a5aa; font-size: 12px; font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.3; } /* View all link */ #wp-admin-bar-woody-snippets-view-all { border-top: 1px solid rgba(255, 255, 255, 0.05); background: #23272a !important; } #wp-admin-bar-woody-snippets-view-all > .ab-item { text-align: center; color: #60a5fa !important; font-weight: 500; padding: 13px 16px !important; min-height: auto !important; background: transparent !important; padding: 3px 0 !important; } #wp-admin-bar-woody-snippets-view-all > .ab-item:after { content: " →"; } #wp-admin-bar-woody-snippets-view-all:hover > .ab-item { color: #93c5fd !important; background: rgba(59, 130, 246, 0.1) !important; } /* Empty state */ #wp-admin-bar-woody-snippets-empty > .ab-item { color: #a0a5aa !important; font-style: italic; padding: 16px !important; min-height: auto !important; background: #2c3338 !important; } /* Safe mode message */ #wp-admin-bar-woody-snippets-safe-mode-message > .ab-item { padding: 16px !important; min-height: auto !important; background: #2c3338 !important; line-height: 1.5; color: #e8eaed; font-size: 14px; white-space: wrap !important; height: auto !important; } /* Disable safe mode button (styled like view all) */ #wp-admin-bar-woody-snippets-disable-safe-mode { border-top: 1px solid rgba(255, 255, 255, 0.05); background: #23272a !important; } #wp-admin-bar-woody-snippets-disable-safe-mode > .ab-item { text-align: center; color: #60a5fa !important; font-weight: 500; padding: 13px 16px !important; min-height: auto !important; background: transparent !important; padding: 3px 0 !important; } #wp-admin-bar-woody-snippets-disable-safe-mode:hover > .ab-item { color: #93c5fd !important; background: rgba(59, 130, 246, 0.1) !important; } /* Pagination */ #wp-admin-bar-woody-snippets-pagination { background: #23272a !important; border-top: 1px solid rgba(255, 255, 255, 0.05); border-bottom: 1px solid rgba(255, 255, 255, 0.05); } #wp-admin-bar-woody-snippets-pagination > .ab-item { display: flex !important; align-items: center; justify-content: space-between; padding: 8px 16px !important; min-height: auto !important; background: transparent !important; cursor: default !important; } #wp-admin-bar-woody-snippets-pagination .woody-pagination-buttons { display: flex; gap: 8px; } #wp-admin-bar-woody-snippets-pagination .woody-pagination-btn { background: #32373c; color: #a0a5aa; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 3px; padding: 4px 10px; font-size: 11px; font-weight: 500; cursor: pointer; transition: all 0.2s; } #wp-admin-bar-woody-snippets-pagination .woody-pagination-btn:hover:not(:disabled) { background: #3b82f6; color: #fff; border-color: #3b82f6; } #wp-admin-bar-woody-snippets-pagination .woody-pagination-btn:disabled { opacity: 0.4; cursor: not-allowed; } #wp-admin-bar-woody-snippets-pagination .woody-pagination-info { color: #a0a5aa; font-size: 12px; } ' ); } /** * Add admin bar menu. * * @param WP_Admin_Bar $wp_admin_bar Admin bar instance. * @return void */ public function add_admin_bar_menu( $wp_admin_bar ) { if ( ! current_user_can( 'manage_options' ) ) { return; } // Add a filter to allow disabling the admin bar menu. if ( ! apply_filters( 'winp_show_admin_bar_menu', true ) ) { return; } // Get active snippets (static for now). $this->collect_active_snippets(); $count = count( $this->active_snippets ); // Check if safe mode is enabled. $is_safe_mode = WINP_Helper::is_safe_mode(); // Add parent menu item. $wp_admin_bar->add_node( [ 'id' => 'woody-snippets', 'parent' => 'top-secondary', 'title' => $is_safe_mode ? sprintf( '%s%s', esc_html__( 'Woody', 'insert-php' ), esc_html__( 'Safe Mode', 'insert-php' ) ) : sprintf( '%s%d', esc_html__( 'Woody', 'insert-php' ), $count ), 'href' => admin_url( 'edit.php?post_type=' . WINP_SNIPPETS_POST_TYPE ), 'meta' => [ 'title' => $is_safe_mode ? __( 'Safe mode is enabled - snippets are not executing', 'insert-php' ) : __( 'Active snippets on this page', 'insert-php' ), ], ] ); // Add header. $wp_admin_bar->add_node( [ 'parent' => 'woody-snippets', 'id' => 'woody-snippets-header', 'title' => $is_safe_mode ? __( 'Safe Mode Enabled', 'insert-php' ) : __( 'Active Snippets on This Page', 'insert-php' ), 'meta' => [ 'class' => 'woody-snippets-header', ], ] ); // Add safe mode message if enabled. if ( $is_safe_mode ) { $wp_admin_bar->add_node( [ 'parent' => 'woody-snippets', 'id' => 'woody-snippets-safe-mode-message', 'title' => __( 'Safe Mode is active. All snippets are temporarily disabled. You can re-enable them after fixing any issues.', 'insert-php' ), 'meta' => [ 'class' => 'woody-safe-mode-message', ], ] ); } // Add snippet items (limited to 5 per page). if ( ! empty( $this->active_snippets ) ) { $snippets_to_show = array_slice( $this->active_snippets, 0, 5 ); foreach ( $snippets_to_show as $snippet ) { $wp_admin_bar->add_node( [ 'parent' => 'woody-snippets', 'id' => 'woody-snippet-' . $snippet['id'], 'title' => $this->render_snippet_item( $snippet ), 'href' => admin_url( 'post.php?post=' . $snippet['id'] . '&action=edit' ), 'meta' => [ 'class' => 'woody-snippet-item', ], ] ); } // Add pagination if more than 5 snippets. if ( count( $this->active_snippets ) > 5 ) { $wp_admin_bar->add_node( [ 'parent' => 'woody-snippets', 'id' => 'woody-snippets-pagination', 'title' => $this->render_pagination( 1, count( $this->active_snippets ) ), ] ); } } elseif ( ! $is_safe_mode ) { // Only show empty state if not in safe mode. $wp_admin_bar->add_node( [ 'parent' => 'woody-snippets', 'id' => 'woody-snippets-empty', 'title' => __( 'No active snippets on this page', 'insert-php' ), ] ); } // Add disable safe mode button or view all link. if ( $is_safe_mode ) { $disable_url = admin_url( 'edit.php?post_type=wbcr-snippets&wbcr-php-snippets-disable-safe-mode=1' ); $wp_admin_bar->add_node( [ 'parent' => 'woody-snippets', 'id' => 'woody-snippets-disable-safe-mode', 'title' => __( 'Disable Safe Mode', 'insert-php' ), 'href' => $disable_url, ] ); } else { $wp_admin_bar->add_node( [ 'parent' => 'woody-snippets', 'id' => 'woody-snippets-view-all', 'title' => __( 'View All Snippets', 'insert-php' ), 'href' => admin_url( 'edit.php?post_type=' . WINP_SNIPPETS_POST_TYPE ), ] ); } } /** * Render snippet item HTML. * * @param array{id: int, name: string, type: string, location: string, scope: string} $snippet Snippet data. * @return string */ private function render_snippet_item( $snippet ) { $type_class = 'type-' . strtolower( $snippet['type'] ); return sprintf( '
%s
%s %s
', esc_html( $snippet['name'] ), esc_attr( $type_class ), esc_html( $snippet['type'] ), esc_html( $snippet['location'] ) ); } /** * Render pagination controls. * * @param int $current_page Current page number. * @param int $total_count Total number of snippets. * @return string */ private function render_pagination( $current_page, $total_count ) { $total_pages = ceil( $total_count / 5 ); $start = ( ( $current_page - 1 ) * 5 ) + 1; $end = min( $current_page * 5, $total_count ); return sprintf( '
%s
', // translators: 1: start number, 2: end number, 3: total count. sprintf( __( '%1$d-%2$d of %3$d', 'insert-php' ), $start, $end, $total_count ), $current_page <= 1 ? 'disabled' : '', esc_html__( 'Previous', 'insert-php' ), $current_page >= $total_pages ? 'disabled' : '', esc_html__( 'Next', 'insert-php' ) ); } /** * Collect active snippets on current page. * * Gets the actually executed snippets from the execution tracker. * * @return void */ private function collect_active_snippets() { // Get executed snippets from the execution tracker. if ( class_exists( 'WINP_Execute_Snippet' ) ) { $execute_instance = WINP_Execute_Snippet::app(); if ( $execute_instance ) { $executed = $execute_instance->get_executed_snippets(); $this->active_snippets = array_values( $executed ); } } } /** * Update admin bar with JavaScript after all snippets have executed. * * Outputs inline JavaScript that updates the admin bar with the final * list of executed snippets, including footer snippets and shortcodes. * * @return void */ public function update_admin_bar_with_js() { if ( ! is_admin_bar_showing() || ! current_user_can( 'manage_options' ) ) { return; } // Check if safe mode is enabled. $is_safe_mode = WINP_Helper::is_safe_mode(); // If safe mode is enabled, don't update the snippets list. if ( $is_safe_mode ) { return; } // Get the final list of executed snippets. $this->collect_active_snippets(); $count = count( $this->active_snippets ); $total_pages = ceil( $count / 5 ); // Prepare snippet data for JavaScript. $snippets_data = []; foreach ( $this->active_snippets as $snippet ) { $snippets_data[] = [ 'id' => $snippet['id'], 'name' => $snippet['name'], 'type' => $snippet['type'], 'location' => $snippet['location'], 'edit_url' => admin_url( 'post.php?post=' . $snippet['id'] . '&action=edit' ), ]; } ?>