From a04ae51263d64a5278854def26afac6a2b720258 Mon Sep 17 00:00:00 2001 From: Imants Date: Tue, 6 Jan 2026 23:57:19 +0200 Subject: [PATCH 01/24] feat: add Admin Bar integration for Code Snippets with quick links and safe mode support --- src/php/class-admin-bar.php | 337 ++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 src/php/class-admin-bar.php diff --git a/src/php/class-admin-bar.php b/src/php/class-admin-bar.php new file mode 100644 index 00000000..ce6ff026 --- /dev/null +++ b/src/php/class-admin-bar.php @@ -0,0 +1,337 @@ + .ab-item, + #wpadminbar .code-snippets-safe-mode.ab-item { + background: #b32d2e; + color: #fff; + font-weight: 600; + } + #wpadminbar .code-snippets-safe-mode:hover > .ab-item, + #wpadminbar .code-snippets-safe-mode.hover > .ab-item { + background: #d63638; + color: #fff; + } + '; + + wp_add_inline_style( 'admin-bar', $css ); + } + + /** + * Register admin bar nodes. + * + * @param WP_Admin_Bar $wp_admin_bar Admin bar instance. + * + * @return void + */ + public function register_nodes( WP_Admin_Bar $wp_admin_bar ): void { + if ( ! is_admin_bar_showing() ) { + return; + } + + if ( ! apply_filters( 'code_snippets/admin_bar/enabled', true ) ) { + return; + } + + if ( ! code_snippets()->current_user_can() ) { + return; + } + + $title = sprintf( + '%s', + esc_html__( 'Snippets', 'code-snippets' ) + ); + + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID, + 'title' => wp_kses( $title, [ 'span' => [ 'class' => [] ] ] ), + 'href' => code_snippets()->get_menu_url( 'manage' ), + ] + ); + + $this->add_safe_mode_nodes( $wp_admin_bar ); + $this->add_quick_links( $wp_admin_bar ); + $this->add_snippet_listings( $wp_admin_bar ); + } + + /** + * Add menu items for safe mode, if active. + * + * @param WP_Admin_Bar $wp_admin_bar Admin bar instance. + * + * @return void + */ + private function add_safe_mode_nodes( WP_Admin_Bar $wp_admin_bar ): void { + if ( ! code_snippets()->evaluate_functions->is_safe_mode_active() ) { + return; + } + + $wp_admin_bar->add_node( + [ + 'id' => self::SAFE_MODE_NODE_ID, + 'title' => esc_html__( 'Snippets Safe Mode Active', 'code-snippets' ), + 'href' => 'https://help.codesnippets.pro/article/12-safe-mode', + 'parent' => 'top-secondary', + 'meta' => [ + 'class' => 'code-snippets-safe-mode', + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + ], + ] + ); + + $wp_admin_bar->add_node( + [ + 'id' => self::SAFE_MODE_NODE_ID . '-submenu', + 'title' => esc_html__( 'Safe Mode Active', 'code-snippets' ), + 'href' => 'https://help.codesnippets.pro/article/12-safe-mode', + 'parent' => self::ROOT_NODE_ID, + 'meta' => [ + 'class' => 'code-snippets-safe-mode', + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + ], + ] + ); + } + + /** + * Add quick links to common Code Snippets screens. + * + * @param WP_Admin_Bar $wp_admin_bar Admin bar instance. + * + * @return void + */ + private function add_quick_links( WP_Admin_Bar $wp_admin_bar ): void { + $plugin = code_snippets(); + + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-manage', + 'title' => esc_html_x( 'Manage', 'snippets', 'code-snippets' ), + 'href' => $plugin->get_menu_url( 'manage' ), + 'parent' => self::ROOT_NODE_ID, + ] + ); + + $statuses = [ + 'all' => _x( 'All Snippets', 'snippets', 'code-snippets' ), + 'active' => _x( 'Active Snippets', 'snippets', 'code-snippets' ), + 'inactive' => _x( 'Inactive Snippets', 'snippets', 'code-snippets' ), + ]; + + foreach ( $statuses as $status => $label ) { + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . "-status-$status", + 'title' => esc_html( $label ), + 'href' => esc_url( add_query_arg( 'status', $status, $plugin->get_menu_url( 'manage' ) ) ), + 'parent' => self::ROOT_NODE_ID . '-manage', + ] + ); + } + + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-add', + 'title' => esc_html_x( 'Add New', 'snippet', 'code-snippets' ), + 'href' => $plugin->get_menu_url( 'add' ), + 'parent' => self::ROOT_NODE_ID, + ] + ); + + $types = [ + 'php' => _x( 'Function', 'snippet type', 'code-snippets' ), + 'html' => _x( 'Content', 'snippet type', 'code-snippets' ), + 'css' => _x( 'Style', 'snippet type', 'code-snippets' ), + 'js' => _x( 'Script', 'snippet type', 'code-snippets' ), + 'cond' => _x( 'Condition', 'snippet type', 'code-snippets' ), + ]; + + $types = array_intersect_key( $types, array_flip( Snippet::get_types() ) ); + + foreach ( $types as $type => $label ) { + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . "-add-$type", + 'title' => esc_html( $label ), + 'href' => esc_url( add_query_arg( 'type', $type, $plugin->get_menu_url( 'add' ) ) ), + 'parent' => self::ROOT_NODE_ID . '-add', + ] + ); + } + + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-import', + 'title' => esc_html_x( 'Import', 'snippets', 'code-snippets' ), + 'href' => $plugin->get_menu_url( 'import' ), + 'parent' => self::ROOT_NODE_ID, + ] + ); + + $settings_context = Settings\are_settings_unified() ? 'network' : 'admin'; + + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-settings', + 'title' => esc_html_x( 'Settings', 'snippets', 'code-snippets' ), + 'href' => $plugin->get_menu_url( 'settings', $settings_context ), + 'parent' => self::ROOT_NODE_ID, + ] + ); + } + + /** + * Add a list of snippets under the active and inactive statuses. + * + * @param WP_Admin_Bar $wp_admin_bar Admin bar instance. + * + * @return void + */ + private function add_snippet_listings( WP_Admin_Bar $wp_admin_bar ): void { + $max_items = (int) apply_filters( 'code_snippets/admin_bar/snippet_limit', 25 ); + if ( $max_items < 1 ) { + return; + } + + $plugin = code_snippets(); + + $snippets = array_filter( + get_snippets(), + static function ( Snippet $snippet ): bool { + return ! $snippet->is_trashed(); + } + ); + + $active_snippets = array_values( + array_filter( + $snippets, + static function ( Snippet $snippet ): bool { + return $snippet->active; + } + ) + ); + + $inactive_snippets = array_values( + array_filter( + $snippets, + static function ( Snippet $snippet ): bool { + return ! $snippet->active; + } + ) + ); + + usort( + $active_snippets, + static function ( Snippet $a, Snippet $b ): int { + return strcasecmp( $a->display_name, $b->display_name ); + } + ); + + usort( + $inactive_snippets, + static function ( Snippet $a, Snippet $b ): int { + return strcasecmp( $a->display_name, $b->display_name ); + } + ); + + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-active-snippets', + 'title' => sprintf( + /* translators: %d: number of active snippets. */ + esc_html__( 'Active Snippets (%d)', 'code-snippets' ), + count( $active_snippets ) + ), + 'href' => esc_url( add_query_arg( 'status', 'active', $plugin->get_menu_url( 'manage' ) ) ), + 'parent' => self::ROOT_NODE_ID, + ] + ); + + foreach ( array_slice( $active_snippets, 0, $max_items ) as $snippet ) { + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-snippet-' . $snippet->id, + 'title' => esc_html( $snippet->display_name ), + 'href' => esc_url( $plugin->get_snippet_edit_url( $snippet->id ) ), + 'parent' => self::ROOT_NODE_ID . '-active-snippets', + ] + ); + } + + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-inactive-snippets', + 'title' => sprintf( + /* translators: %d: number of inactive snippets. */ + esc_html__( 'Inactive Snippets (%d)', 'code-snippets' ), + count( $inactive_snippets ) + ), + 'href' => esc_url( add_query_arg( 'status', 'inactive', $plugin->get_menu_url( 'manage' ) ) ), + 'parent' => self::ROOT_NODE_ID, + ] + ); + + foreach ( array_slice( $inactive_snippets, 0, $max_items ) as $snippet ) { + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-snippet-' . $snippet->id, + 'title' => esc_html( $snippet->display_name ), + 'href' => esc_url( $plugin->get_snippet_edit_url( $snippet->id ) ), + 'parent' => self::ROOT_NODE_ID . '-inactive-snippets', + ] + ); + } + } +} From d22434238f4ee61aabfd422ff7d7b93ef287f064 Mon Sep 17 00:00:00 2001 From: Imants Date: Tue, 6 Jan 2026 23:57:23 +0200 Subject: [PATCH 02/24] feat: add Admin Bar integration to the Plugin class --- src/php/class-plugin.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index 7f5390c6..318aeb8b 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -84,6 +84,13 @@ class Plugin { */ public Snippet_Handler_Registry $snippet_handler_registry; + /** + * Admin bar integration class. + * + * @var Admin_Bar + */ + public Admin_Bar $admin_bar; + /** * Class constructor * @@ -121,6 +128,11 @@ public function load_plugin() { $this->evaluate_content = new Evaluate_Content( $this->db ); $this->evaluate_functions = new Evaluate_Functions( $this->db ); + // Admin bar integration. + require_once $includes_path . '/class-admin-bar.php'; + $this->admin_bar = new Admin_Bar(); + $this->admin_bar->register_hooks(); + // CodeMirror editor functions. require_once $includes_path . '/editor.php'; From e3da8f4e64c8403f3f27ebc3d2b5fcfbdca0b386 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 15:59:07 +0200 Subject: [PATCH 03/24] feat: update snippet type labels --- src/php/class-admin-bar.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/php/class-admin-bar.php b/src/php/class-admin-bar.php index ce6ff026..a0fab814 100644 --- a/src/php/class-admin-bar.php +++ b/src/php/class-admin-bar.php @@ -191,11 +191,11 @@ private function add_quick_links( WP_Admin_Bar $wp_admin_bar ): void { ); $types = [ - 'php' => _x( 'Function', 'snippet type', 'code-snippets' ), - 'html' => _x( 'Content', 'snippet type', 'code-snippets' ), - 'css' => _x( 'Style', 'snippet type', 'code-snippets' ), - 'js' => _x( 'Script', 'snippet type', 'code-snippets' ), - 'cond' => _x( 'Condition', 'snippet type', 'code-snippets' ), + 'php' => _x( 'Functions (PHP)', 'snippet type', 'code-snippets' ), + 'html' => _x( 'Content (HTML)', 'snippet type', 'code-snippets' ), + 'css' => _x( 'Style (CSS)', 'snippet type', 'code-snippets' ), + 'js' => _x( 'Script (JS)', 'snippet type', 'code-snippets' ), + 'cond' => _x( 'Condition (COND)', 'snippet type', 'code-snippets' ), ]; $types = array_intersect_key( $types, array_flip( Snippet::get_types() ) ); From 8f9813617e2c0d2ee858ba28bd898ef6dcfb4f2e Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 19:29:01 +0200 Subject: [PATCH 04/24] feat: add admin bar snippet limit setting --- src/php/settings/settings-fields.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/php/settings/settings-fields.php b/src/php/settings/settings-fields.php index 72262cc7..77466d79 100644 --- a/src/php/settings/settings-fields.php +++ b/src/php/settings/settings-fields.php @@ -29,6 +29,7 @@ function get_default_settings(): array { 'enable_description' => true, 'visual_editor_rows' => 5, 'list_order' => 'priority-asc', + 'admin_bar_snippet_limit' => 20, 'disable_prism' => false, 'hide_upgrade_menu' => false, 'complete_uninstall' => false, @@ -146,6 +147,14 @@ function get_settings_fields(): array { 'modified-asc' => __( 'Modified (oldest first)', 'code-snippets' ), ], ], + 'admin_bar_snippet_limit' => [ + 'name' => __( 'Admin Bar Snippets Per Page', 'code-snippets' ), + 'type' => 'number', + 'desc' => __( 'Number of snippets to show in the admin bar Active/Inactive menus before paginating.', 'code-snippets' ), + 'label' => __( 'snippets', 'code-snippets' ), + 'min' => 1, + 'max' => 100, + ], 'disable_prism' => [ 'name' => __( 'Disable Syntax Highlighter', 'code-snippets' ), 'type' => 'checkbox', From bc7776493534d59ed9d89b94918c07d629caf682 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 19:29:11 +0200 Subject: [PATCH 05/24] feat: enhance admin bar with pagination and snippet limit features --- src/php/class-admin-bar.php | 289 +++++++++++++++++++++++++++++++----- 1 file changed, 252 insertions(+), 37 deletions(-) diff --git a/src/php/class-admin-bar.php b/src/php/class-admin-bar.php index a0fab814..9bc6ae09 100644 --- a/src/php/class-admin-bar.php +++ b/src/php/class-admin-bar.php @@ -2,6 +2,7 @@ namespace Code_Snippets; +use Code_Snippets\REST_API\Snippets_REST_Controller; use WP_Admin_Bar; /** @@ -18,6 +19,20 @@ class Admin_Bar { */ private const ROOT_NODE_ID = 'code-snippets'; + /** + * Active snippets pagination query arg. + * + * @var string + */ + private const ACTIVE_PAGE_QUERY_ARG = 'code_snippets_ab_active_page'; + + /** + * Inactive snippets pagination query arg. + * + * @var string + */ + private const INACTIVE_PAGE_QUERY_ARG = 'code_snippets_ab_inactive_page'; + /** * Safe mode node ID. * @@ -25,6 +40,20 @@ class Admin_Bar { */ private const SAFE_MODE_NODE_ID = 'code-snippets-safe-mode'; + /** + * Script handle. + * + * @var string + */ + private const SCRIPT_HANDLE = 'code-snippets-admin-bar'; + + /** + * Stylesheet handle. + * + * @var string + */ + private const STYLE_HANDLE = 'code-snippets-admin-bar'; + /** * Register WordPress hooks. * @@ -33,37 +62,53 @@ class Admin_Bar { public function register_hooks(): void { add_action( 'admin_bar_menu', [ $this, 'register_nodes' ], 80 ); - add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] ); - add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); + add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] ); } /** - * Enqueue styles for admin bar nodes. + * Enqueue scripts and styles for admin bar nodes. * * @return void */ - public function enqueue_styles(): void { + public function enqueue_assets(): void { if ( ! is_admin_bar_showing() ) { return; } wp_enqueue_style( 'admin-bar' ); + wp_enqueue_style( 'dashicons' ); - $css = ' - #wpadminbar .code-snippets-safe-mode > .ab-item, - #wpadminbar .code-snippets-safe-mode.ab-item { - background: #b32d2e; - color: #fff; - font-weight: 600; - } - #wpadminbar .code-snippets-safe-mode:hover > .ab-item, - #wpadminbar .code-snippets-safe-mode.hover > .ab-item { - background: #d63638; - color: #fff; - } - '; + wp_enqueue_style( + self::STYLE_HANDLE, + plugins_url( 'dist/admin-bar.css', PLUGIN_FILE ), + [ 'admin-bar' ], + PLUGIN_VERSION + ); - wp_add_inline_style( 'admin-bar', $css ); + wp_enqueue_script( + self::SCRIPT_HANDLE, + plugins_url( 'dist/admin-bar.js', PLUGIN_FILE ), + [], + PLUGIN_VERSION, + true + ); + + wp_localize_script( + self::SCRIPT_HANDLE, + 'CODE_SNIPPETS_ADMIN_BAR', + [ + 'restUrl' => esc_url_raw( rest_url( Snippets_REST_Controller::get_base_route() ) ), + 'nonce' => wp_create_nonce( 'wp_rest' ), + 'perPage' => $this->get_snippet_limit(), + 'isNetwork' => is_network_admin(), + 'excludeTypes' => [ 'cond' ], + 'snippetPlaceholder' => esc_html__( 'Snippet #%d', 'code-snippets' ), + 'editUrlBase' => code_snippets()->get_menu_url( 'edit' ), + 'activeNodeId' => 'wp-admin-bar-' . self::ROOT_NODE_ID . '-active-snippets', + 'inactiveNodeId' => 'wp-admin-bar-' . self::ROOT_NODE_ID . '-inactive-snippets', + ] + ); } /** @@ -86,33 +131,48 @@ public function register_nodes( WP_Admin_Bar $wp_admin_bar ): void { return; } + $is_safe_mode_active = code_snippets()->evaluate_functions->is_safe_mode_active(); + + $icon = ''; $title = sprintf( - '%s', + '%s%s', + $icon, esc_html__( 'Snippets', 'code-snippets' ) ); $wp_admin_bar->add_node( [ 'id' => self::ROOT_NODE_ID, - 'title' => wp_kses( $title, [ 'span' => [ 'class' => [] ] ] ), + 'title' => $title, 'href' => code_snippets()->get_menu_url( 'manage' ), ] ); - $this->add_safe_mode_nodes( $wp_admin_bar ); + $this->add_safe_mode_nodes( $wp_admin_bar, $is_safe_mode_active ); $this->add_quick_links( $wp_admin_bar ); $this->add_snippet_listings( $wp_admin_bar ); + $this->add_safe_mode_link( $wp_admin_bar ); + } + + /** + * Retrieve the Code Snippets scissors SVG icon markup. + * + * @return string + */ + private function get_scissors_svg(): string { + return ''; } /** - * Add menu items for safe mode, if active. + * Add menu item for safe mode status. * * @param WP_Admin_Bar $wp_admin_bar Admin bar instance. + * @param bool $is_safe_mode_active Whether safe mode is active. * * @return void */ - private function add_safe_mode_nodes( WP_Admin_Bar $wp_admin_bar ): void { - if ( ! code_snippets()->evaluate_functions->is_safe_mode_active() ) { + private function add_safe_mode_nodes( WP_Admin_Bar $wp_admin_bar, bool $is_safe_mode_active ): void { + if ( ! $is_safe_mode_active ) { return; } @@ -120,21 +180,35 @@ private function add_safe_mode_nodes( WP_Admin_Bar $wp_admin_bar ): void { [ 'id' => self::SAFE_MODE_NODE_ID, 'title' => esc_html__( 'Snippets Safe Mode Active', 'code-snippets' ), - 'href' => 'https://help.codesnippets.pro/article/12-safe-mode', + 'href' => 'https://snipco.de/safe-mode', 'parent' => 'top-secondary', 'meta' => [ - 'class' => 'code-snippets-safe-mode', + 'class' => 'code-snippets-safe-mode code-snippets-safe-mode-active', 'target' => '_blank', 'rel' => 'noopener noreferrer', ], ] ); + } + + /** + * Add a safe mode documentation link under the Code Snippets menu. + * + * @param WP_Admin_Bar $wp_admin_bar Admin bar instance. + * + * @return void + */ + private function add_safe_mode_link( WP_Admin_Bar $wp_admin_bar ): void { + $title = sprintf( + ' %s', + esc_html__( 'Safe Mode', 'code-snippets' ) + ); $wp_admin_bar->add_node( [ - 'id' => self::SAFE_MODE_NODE_ID . '-submenu', - 'title' => esc_html__( 'Safe Mode Active', 'code-snippets' ), - 'href' => 'https://help.codesnippets.pro/article/12-safe-mode', + 'id' => self::ROOT_NODE_ID . '-safe-mode-doc', + 'title' => $title, + 'href' => 'https://snipco.de/safe-mode', 'parent' => self::ROOT_NODE_ID, 'meta' => [ 'class' => 'code-snippets-safe-mode', @@ -154,6 +228,8 @@ private function add_safe_mode_nodes( WP_Admin_Bar $wp_admin_bar ): void { */ private function add_quick_links( WP_Admin_Bar $wp_admin_bar ): void { $plugin = code_snippets(); + $is_licensed = $plugin->licensing->is_licensed(); + $upgrade_url = self_admin_url( 'admin.php?page=code_snippets_upgrade' ); $wp_admin_bar->add_node( [ @@ -199,14 +275,22 @@ private function add_quick_links( WP_Admin_Bar $wp_admin_bar ): void { ]; $types = array_intersect_key( $types, array_flip( Snippet::get_types() ) ); + $pro_types = [ 'css', 'js', 'cond' ]; foreach ( $types as $type => $label ) { + $is_disabled = in_array( $type, $pro_types, true ) && ! $is_licensed; + + $url = $is_disabled ? + $upgrade_url : + add_query_arg( 'type', $type, $plugin->get_menu_url( 'add' ) ); + $wp_admin_bar->add_node( [ 'id' => self::ROOT_NODE_ID . "-add-$type", 'title' => esc_html( $label ), - 'href' => esc_url( add_query_arg( 'type', $type, $plugin->get_menu_url( 'add' ) ) ), + 'href' => esc_url( $url ), 'parent' => self::ROOT_NODE_ID . '-add', + 'meta' => $is_disabled ? [ 'class' => 'code-snippets-disabled' ] : [], ] ); } @@ -240,8 +324,8 @@ private function add_quick_links( WP_Admin_Bar $wp_admin_bar ): void { * @return void */ private function add_snippet_listings( WP_Admin_Bar $wp_admin_bar ): void { - $max_items = (int) apply_filters( 'code_snippets/admin_bar/snippet_limit', 25 ); - if ( $max_items < 1 ) { + $items_per_page = $this->get_snippet_limit(); + if ( $items_per_page < 1 ) { return; } @@ -250,7 +334,7 @@ private function add_snippet_listings( WP_Admin_Bar $wp_admin_bar ): void { $snippets = array_filter( get_snippets(), static function ( Snippet $snippet ): bool { - return ! $snippet->is_trashed(); + return ! $snippet->is_trashed() && ! $snippet->is_condition(); } ); @@ -286,6 +370,21 @@ static function ( Snippet $a, Snippet $b ): int { } ); + $active_page = isset( $_GET[ self::ACTIVE_PAGE_QUERY_ARG ] ) ? absint( $_GET[ self::ACTIVE_PAGE_QUERY_ARG ] ) : 1; + $inactive_page = isset( $_GET[ self::INACTIVE_PAGE_QUERY_ARG ] ) ? absint( $_GET[ self::INACTIVE_PAGE_QUERY_ARG ] ) : 1; + + $active_total_pages = max( 1, (int) ceil( count( $active_snippets ) / $items_per_page ) ); + $inactive_total_pages = max( 1, (int) ceil( count( $inactive_snippets ) / $items_per_page ) ); + + $active_page = max( 1, min( $active_page, $active_total_pages ) ); + $inactive_page = max( 1, min( $inactive_page, $inactive_total_pages ) ); + + $active_offset = ( $active_page - 1 ) * $items_per_page; + $inactive_offset = ( $inactive_page - 1 ) * $items_per_page; + + $active_page_snippets = array_slice( $active_snippets, $active_offset, $items_per_page ); + $inactive_page_snippets = array_slice( $inactive_snippets, $inactive_offset, $items_per_page ); + $wp_admin_bar->add_node( [ 'id' => self::ROOT_NODE_ID . '-active-snippets', @@ -299,13 +398,25 @@ static function ( Snippet $a, Snippet $b ): int { ] ); - foreach ( array_slice( $active_snippets, 0, $max_items ) as $snippet ) { + if ( $active_total_pages > 1 ) { + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-active-pagination', + 'title' => $this->get_pagination_controls_html( 'active', $active_page, $active_total_pages, self::ACTIVE_PAGE_QUERY_ARG ), + 'parent' => self::ROOT_NODE_ID . '-active-snippets', + 'meta' => [ 'class' => 'code-snippets-pagination-node' ], + ] + ); + } + + foreach ( $active_page_snippets as $snippet ) { $wp_admin_bar->add_node( [ 'id' => self::ROOT_NODE_ID . '-snippet-' . $snippet->id, - 'title' => esc_html( $snippet->display_name ), + 'title' => esc_html( $this->format_snippet_title( $snippet ) ), 'href' => esc_url( $plugin->get_snippet_edit_url( $snippet->id ) ), 'parent' => self::ROOT_NODE_ID . '-active-snippets', + 'meta' => [ 'class' => 'code-snippets-snippet-item' ], ] ); } @@ -323,15 +434,119 @@ static function ( Snippet $a, Snippet $b ): int { ] ); - foreach ( array_slice( $inactive_snippets, 0, $max_items ) as $snippet ) { + if ( $inactive_total_pages > 1 ) { + $wp_admin_bar->add_node( + [ + 'id' => self::ROOT_NODE_ID . '-inactive-pagination', + 'title' => $this->get_pagination_controls_html( 'inactive', $inactive_page, $inactive_total_pages, self::INACTIVE_PAGE_QUERY_ARG ), + 'parent' => self::ROOT_NODE_ID . '-inactive-snippets', + 'meta' => [ 'class' => 'code-snippets-pagination-node' ], + ] + ); + } + + foreach ( $inactive_page_snippets as $snippet ) { $wp_admin_bar->add_node( [ 'id' => self::ROOT_NODE_ID . '-snippet-' . $snippet->id, - 'title' => esc_html( $snippet->display_name ), + 'title' => esc_html( $this->format_snippet_title( $snippet ) ), 'href' => esc_url( $plugin->get_snippet_edit_url( $snippet->id ) ), 'parent' => self::ROOT_NODE_ID . '-inactive-snippets', + 'meta' => [ 'class' => 'code-snippets-snippet-item' ], ] ); } } + + /** + * Build an admin bar snippet title including type prefix. + * + * @param Snippet $snippet Snippet object. + * + * @return string + */ + private function format_snippet_title( Snippet $snippet ): string { + return sprintf( '(%s) %s', strtoupper( $snippet->type ), $snippet->display_name ); + } + + /** + * Retrieve the number of snippets to show per page in the admin bar. + * + * @return int + */ + private function get_snippet_limit(): int { + $limit = (int) Settings\get_setting( 'general', 'admin_bar_snippet_limit' ); + + if ( $limit < 1 ) { + $limit = 20; + } + + return max( 1, (int) apply_filters( 'code_snippets/admin_bar/snippet_limit', $limit ) ); + } + + /** + * Build pagination controls HTML for a snippet listing submenu. + * + * @param string $status Snippet status: "active" or "inactive". + * @param int $page Current page. + * @param int $total_pages Total pages. + * @param string $page_query_arg Query arg used for progressive enhancement. + * + * @return string + */ + private function get_pagination_controls_html( string $status, int $page, int $total_pages, string $page_query_arg ): string { + $first_url = remove_query_arg( $page_query_arg ); + $prev_url = $page > 2 ? add_query_arg( $page_query_arg, $page - 1 ) : remove_query_arg( $page_query_arg ); + $next_url = add_query_arg( $page_query_arg, $page + 1 ); + $last_url = add_query_arg( $page_query_arg, $total_pages ); + + $disabled_first = $page <= 1 ? 'true' : 'false'; + $disabled_prev = $page <= 1 ? 'true' : 'false'; + $disabled_next = $page >= $total_pages ? 'true' : 'false'; + $disabled_last = $page >= $total_pages ? 'true' : 'false'; + + $html = sprintf( + '' . + '«' . + '‹ %6$s' . + '%7$s (%2$d/%3$d)' . + '%12$s ›' . + '»' . + '', + esc_attr( $status ), + $page, + $total_pages, + esc_url( $first_url ), + esc_url( $prev_url ), + esc_html__( 'Back', 'code-snippets' ), + esc_html__( 'Page', 'code-snippets' ), + esc_url( $next_url ), + $disabled_first, + $disabled_prev, + $disabled_next, + esc_html__( 'Next', 'code-snippets' ), + esc_url( $last_url ), + $disabled_last, + esc_attr( $page_query_arg ) + ); + + return wp_kses( + $html, + [ + 'span' => [ + 'class' => [], + 'data-status' => [], + 'data-page' => [], + 'data-total-pages' => [], + 'data-query-arg' => [], + ], + 'a' => [ + 'class' => [], + 'href' => [], + 'data-action' => [], + 'aria-disabled' => [], + ], + ] + ); + } } From ce40ffad6aece2b2f7e0fbd85d12e2799cfac35e Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 19:29:15 +0200 Subject: [PATCH 06/24] feat: add filtering and sorting options to snippets REST API endpoint --- .../class-snippets-rest-controller.php | 118 ++++++++++++++++-- 1 file changed, 110 insertions(+), 8 deletions(-) diff --git a/src/php/rest-api/class-snippets-rest-controller.php b/src/php/rest-api/class-snippets-rest-controller.php index bf37e60b..dac50a87 100644 --- a/src/php/rest-api/class-snippets-rest-controller.php +++ b/src/php/rest-api/class-snippets-rest-controller.php @@ -83,6 +83,42 @@ public function register_routes() { // Allow standard collection parameters (page, per_page, etc.) on the collection route. $collection_args = array_merge( $network_args, $this->get_collection_params() ); + $collection_args['status'] = [ + 'description' => esc_html__( 'Filter snippets by activation status.', 'code-snippets' ), + 'type' => 'string', + 'enum' => [ 'all', 'active', 'inactive' ], + 'default' => 'all', + 'sanitize_callback' => 'sanitize_key', + ]; + + $collection_args['exclude_types'] = [ + 'description' => esc_html__( 'List of snippet types to exclude from the response.', 'code-snippets' ), + 'type' => 'array', + 'items' => [ + 'type' => 'string', + ], + 'default' => [], + 'sanitize_callback' => static function ( $value ): array { + $values = is_array( $value ) ? $value : [ $value ]; + return array_values( array_filter( array_map( 'sanitize_key', $values ) ) ); + }, + ]; + + $collection_args['orderby'] = [ + 'description' => esc_html__( 'Sort collection by object attribute.', 'code-snippets' ), + 'type' => 'string', + 'enum' => [ 'id', 'name', 'display_name' ], + 'sanitize_callback' => 'sanitize_key', + ]; + + $collection_args['order'] = [ + 'description' => esc_html__( 'Sort direction.', 'code-snippets' ), + 'type' => 'string', + 'enum' => [ 'asc', 'desc' ], + 'default' => 'asc', + 'sanitize_callback' => 'sanitize_key', + ]; + register_rest_route( $this->namespace, $route, @@ -200,6 +236,72 @@ public function get_items( $request ): WP_REST_Response { $all_snippets = get_snippets( [], $network ); $all_snippets = $this->get_network_items( $all_snippets, $network ); + $status = sanitize_key( (string) $request->get_param( 'status' ) ); + + if ( in_array( $status, [ 'active', 'inactive' ], true ) ) { + $all_snippets = array_filter( + $all_snippets, + static function ( Snippet $snippet ): bool { + return ! $snippet->is_trashed(); + } + ); + } + + $exclude_types = $request->get_param( 'exclude_types' ); + $exclude_types = is_array( $exclude_types ) ? array_map( 'sanitize_key', $exclude_types ) : []; + + if ( $exclude_types ) { + $all_snippets = array_filter( + $all_snippets, + static function ( Snippet $snippet ) use ( $exclude_types ): bool { + return ! in_array( $snippet->type, $exclude_types, true ); + } + ); + } + + if ( 'active' === $status ) { + $all_snippets = array_filter( + $all_snippets, + static function ( Snippet $snippet ): bool { + return $snippet->active; + } + ); + } elseif ( 'inactive' === $status ) { + $all_snippets = array_filter( + $all_snippets, + static function ( Snippet $snippet ): bool { + return ! $snippet->active; + } + ); + } + + $orderby = sanitize_key( (string) $request->get_param( 'orderby' ) ); + $order = sanitize_key( (string) $request->get_param( 'order' ) ); + + if ( $orderby ) { + $direction = 'desc' === $order ? -1 : 1; + + usort( + $all_snippets, + static function ( Snippet $a, Snippet $b ) use ( $orderby, $direction ): int { + switch ( $orderby ) { + case 'display_name': + $cmp = strcasecmp( $a->display_name, $b->display_name ); + break; + case 'name': + $cmp = strcasecmp( (string) $a->name, (string) $b->name ); + break; + case 'id': + default: + $cmp = $a->id <=> $b->id; + break; + } + + return 0 === $cmp ? ( $a->id <=> $b->id ) * $direction : $cmp * $direction; + } + ); + } + $total_items = count( $all_snippets ); $query_params = $request->get_query_params(); @@ -559,14 +661,14 @@ public function get_item_schema(): array { 'description' => esc_html__( 'Descriptive title for the snippet.', 'code-snippets' ), 'type' => 'string', ], - 'desc' => [ - 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ), - 'type' => 'string', - ], - 'code' => [ - 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ), - 'type' => 'string', - ], + 'desc' => [ + 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ), + 'type' => 'string', + ], + 'code' => [ + 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ), + 'type' => 'string', + ], 'tags' => [ 'description' => esc_html__( 'List of tag categories the snippet belongs to.', 'code-snippets' ), 'type' => 'array', From 1a6bfc043cd5e1a101cb9f063bca80bd4758e8f0 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 19:29:25 +0200 Subject: [PATCH 07/24] feat: add admin bar pagination functionality with snippet fetching --- config/webpack-js.ts | 1 + src/js/admin-bar.ts | 288 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 src/js/admin-bar.ts diff --git a/config/webpack-js.ts b/config/webpack-js.ts index 3e2a19ce..0ccacc43 100644 --- a/config/webpack-js.ts +++ b/config/webpack-js.ts @@ -24,6 +24,7 @@ const babelConfig = { export const jsWebpackConfig: Configuration = { entry: { + 'admin-bar': `${SOURCE_DIR}/admin-bar.ts`, edit: { import: `${SOURCE_DIR}/edit.tsx`, dependOn: 'editor' }, editor: `${SOURCE_DIR}/editor.ts`, import: `${SOURCE_DIR}/import.tsx`, diff --git a/src/js/admin-bar.ts b/src/js/admin-bar.ts new file mode 100644 index 00000000..a0e47f91 --- /dev/null +++ b/src/js/admin-bar.ts @@ -0,0 +1,288 @@ +type PaginationStatus = 'active' | 'inactive' +type PaginationAction = 'first' | 'prev' | 'next' | 'last' + +type SnippetResponseItem = { + id: number + type: string + name?: string +} + +type AdminBarConfig = { + restUrl: string + nonce: string + perPage: number + isNetwork: boolean + excludeTypes: string[] + snippetPlaceholder: string + editUrlBase: string + activeNodeId: string + inactiveNodeId: string +} + +declare const CODE_SNIPPETS_ADMIN_BAR: AdminBarConfig | undefined + +const config = typeof CODE_SNIPPETS_ADMIN_BAR !== 'undefined' ? CODE_SNIPPETS_ADMIN_BAR : undefined + +const getMenuNode = (status: PaginationStatus): HTMLElement | null => { + if (!config) return null + const nodeId = status === 'active' ? config.activeNodeId : config.inactiveNodeId + return document.getElementById(nodeId) +} + +const getPaginationControls = (status: PaginationStatus): HTMLElement | null => { + const menuNode = getMenuNode(status) + if (!menuNode) return null + + return menuNode.querySelector(`.code-snippets-pagination-controls[data-status="${status}"]`) +} + +const getPaginationState = (controls: HTMLElement): { page: number; totalPages: number } => { + const page = Number.parseInt(controls.dataset.page ?? '1', 10) + const totalPages = Number.parseInt(controls.dataset.totalPages ?? '1', 10) + + return { + page: Number.isFinite(page) && page > 0 ? page : 1, + totalPages: Number.isFinite(totalPages) && totalPages > 0 ? totalPages : 1 + } +} + +const setLoading = (controls: HTMLElement, loading: boolean) => { + controls.dataset.loading = loading ? 'true' : 'false' +} + +const buildRequestUrl = (status: PaginationStatus, page: number): string => { + if (!config) return '' + + const url = new URL(config.restUrl) + url.searchParams.set('status', status) + url.searchParams.set('page', String(page)) + url.searchParams.set('per_page', String(config.perPage)) + url.searchParams.set('orderby', 'display_name') + url.searchParams.set('order', 'asc') + + if (config.isNetwork) { + url.searchParams.set('network', '1') + } + + for (const excluded of config.excludeTypes ?? []) { + url.searchParams.append('exclude_types[]', excluded) + } + + return url.toString() +} + +const fetchSnippetsPage = async (status: PaginationStatus, page: number) => { + if (!config) { + throw new Error('Missing CODE_SNIPPETS_ADMIN_BAR config') + } + + const response = await fetch(buildRequestUrl(status, page), { + credentials: 'same-origin', + headers: { + 'X-WP-Nonce': config.nonce + } + }) + + if (!response.ok) { + throw new Error(`Failed to fetch snippets (${response.status})`) + } + + const totalPagesHeader = response.headers.get('X-WP-TotalPages') ?? '1' + const totalPages = Number.parseInt(totalPagesHeader, 10) || 1 + + const snippets = (await response.json()) as SnippetResponseItem[] + + return { snippets, totalPages } +} + +const buildEditUrl = (snippetId: number): string => { + if (!config) return '#' + + try { + const url = new URL(config.editUrlBase, window.location.href) + url.searchParams.set('id', String(snippetId)) + return url.toString() + } catch { + return '#' + } +} + +const buildSnippetPlaceholder = (snippetId: number): string => { + if (!config?.snippetPlaceholder) return `Snippet #${snippetId}` + + return config.snippetPlaceholder.replace(/%(\d+\$)?d/, String(snippetId)) +} + +const formatSnippetTitle = (snippet: SnippetResponseItem): string => { + const typeLabel = (snippet.type || '').toUpperCase() + const name = snippet.name?.trim() + const title = name ? name : buildSnippetPlaceholder(snippet.id) + return `(${typeLabel}) ${title}` +} + +const updatePaginationControls = (controls: HTMLElement, page: number, totalPages: number) => { + controls.dataset.page = String(page) + controls.dataset.totalPages = String(totalPages) + + const pageLabel = controls.querySelector('.code-snippets-pagination-page') + if (pageLabel) { + pageLabel.textContent = pageLabel.textContent?.replace(/\(\d+\/\d+\)/, `(${page}/${totalPages})`) ?? pageLabel.textContent + } + + const disableFirstPrev = page <= 1 + const disableNextLast = page >= totalPages + + const setDisabled = (action: PaginationAction, disabled: boolean) => { + const link = controls.querySelector(`a[data-action="${action}"]`) + if (link) { + link.setAttribute('aria-disabled', disabled ? 'true' : 'false') + } + } + + setDisabled('first', disableFirstPrev) + setDisabled('prev', disableFirstPrev) + setDisabled('next', disableNextLast) + setDisabled('last', disableNextLast) + + const queryArg = controls.dataset.queryArg + if (queryArg) { + const firstLink = controls.querySelector('a[data-action="first"]') + const baseHref = firstLink?.href + + if (baseHref) { + const buildHref = (targetPage: number) => { + const url = new URL(baseHref) + + if (targetPage <= 1) { + url.searchParams.delete(queryArg) + } else { + url.searchParams.set(queryArg, String(targetPage)) + } + + return url.toString() + } + + const getLink = (action: PaginationAction) => + controls.querySelector(`a[data-action="${action}"]`) + + const first = getLink('first') + if (first) first.href = buildHref(1) + + const prev = getLink('prev') + if (prev) prev.href = buildHref(Math.max(1, page - 1)) + + const next = getLink('next') + if (next) next.href = buildHref(Math.min(totalPages, page + 1)) + + const last = getLink('last') + if (last) last.href = buildHref(totalPages) + } + } +} + +const replaceSnippetItems = (status: PaginationStatus, snippets: SnippetResponseItem[]) => { + const menuNode = getMenuNode(status) + if (!menuNode) return + + const subMenu = menuNode.querySelector('ul.ab-submenu') + if (!subMenu) return + + subMenu.querySelectorAll('li.code-snippets-snippet-item').forEach(node => node.remove()) + + const insertAfterId = status === 'active' + ? 'wp-admin-bar-code-snippets-active-pagination' + : 'wp-admin-bar-code-snippets-inactive-pagination' + + const insertAfter = subMenu.querySelector(`#${insertAfterId}`) + const fragment = document.createDocumentFragment() + + for (const snippet of snippets) { + const li = document.createElement('li') + li.id = `wp-admin-bar-code-snippets-snippet-${snippet.id}` + li.className = 'code-snippets-snippet-item' + + const a = document.createElement('a') + a.className = 'ab-item' + a.href = buildEditUrl(snippet.id) + a.textContent = formatSnippetTitle(snippet) + + li.appendChild(a) + fragment.appendChild(li) + } + + if (insertAfter && insertAfter.parentNode === subMenu) { + subMenu.insertBefore(fragment, insertAfter.nextSibling) + } else { + subMenu.appendChild(fragment) + } +} + +const navigateToPage = async (status: PaginationStatus, targetPage: number) => { + const controls = getPaginationControls(status) + if (!controls) return + + const { totalPages: currentTotalPages } = getPaginationState(controls) + const page = Math.max(1, Math.min(targetPage, currentTotalPages)) + + setLoading(controls, true) + + try { + const { snippets, totalPages } = await fetchSnippetsPage(status, page) + updatePaginationControls(controls, page, totalPages) + replaceSnippetItems(status, snippets) + } catch (error) { + // eslint-disable-next-line no-console + console.error(error) + } finally { + setLoading(controls, false) + } +} + +const handlePaginationClick = (event: MouseEvent) => { + const target = event.target as Element | null + if (!target) return + + const link = target.closest('.code-snippets-pagination-controls a[data-action]') + if (!link) return + + const controls = link.closest('.code-snippets-pagination-controls') + if (!controls) return + if (controls.dataset.loading === 'true') return + + const status = controls.dataset.status as PaginationStatus | undefined + const action = link.dataset.action as PaginationAction | undefined + + if (!status || !action) return + + event.preventDefault() + event.stopPropagation() + event.stopImmediatePropagation() + + const { page, totalPages } = getPaginationState(controls) + + let targetPage = page + switch (action) { + case 'first': + targetPage = 1 + break + case 'prev': + targetPage = page - 1 + break + case 'next': + targetPage = page + 1 + break + case 'last': + targetPage = totalPages + break + } + + if (targetPage === page || targetPage < 1 || targetPage > totalPages) { + return + } + + void navigateToPage(status, targetPage) +} + +if (config) { + document.addEventListener('click', handlePaginationClick, true) +} From ce45668476ef815648a9ae8f69a9f8fb886274a3 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 19:29:30 +0200 Subject: [PATCH 08/24] feat: add styles for admin bar code snippets pagination and safe mode --- src/css/admin-bar.scss | 129 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/css/admin-bar.scss diff --git a/src/css/admin-bar.scss b/src/css/admin-bar.scss new file mode 100644 index 00000000..566d12b7 --- /dev/null +++ b/src/css/admin-bar.scss @@ -0,0 +1,129 @@ +#wpadminbar { + #wp-admin-bar-code-snippets > .ab-item { + .code-snippets-admin-bar-icon { + display: inline-block; + top: 5px; + position: relative; + padding-right: 10px; + line-height: 1; + color: rgba(240, 245, 250, 0.6); + + svg { + width: 16px; + height: 16px; + display: block; + fill: currentColor; + } + } + + &:hover .code-snippets-admin-bar-icon, + &:focus .code-snippets-admin-bar-icon { + color: inherit; + } + } + + #wp-admin-bar-code-snippets.hover > .ab-item .code-snippets-admin-bar-icon { + color: inherit; + } + + .code-snippets-safe-mode-active { + > .ab-item, + &.ab-item { + background: #b32d2e; + color: #fff; + } + + &:hover > .ab-item, + &.hover > .ab-item { + background: #d63638; + color: #fff; + } + } + + .code-snippets-safe-mode.code-snippets-safe-mode-active > .ab-item, + .code-snippets-safe-mode.code-snippets-safe-mode-active.ab-item { + font-weight: 600; + } + + .code-snippets-safe-mode .code-snippets-external-icon { + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 4px; + opacity: 0.8; + font-size: 14px; + width: 14px; + height: 14px; + line-height: 1; + } + + .code-snippets-safe-mode:hover .code-snippets-external-icon, + .code-snippets-safe-mode.hover .code-snippets-external-icon { + opacity: 1; + } + + #wp-admin-bar-code-snippets-active-snippets > .ab-sub-wrapper > .ab-submenu, + #wp-admin-bar-code-snippets-inactive-snippets > .ab-sub-wrapper > .ab-submenu { + padding-top: 0; + } + + .code-snippets-disabled > .ab-item { + opacity: 0.6; + } + + .code-snippets-disabled:hover > .ab-item, + .code-snippets-disabled.hover > .ab-item { + opacity: 1; + } + + .code-snippets-pagination-node > .ab-item { + padding: 0; + margin-bottom: 6px; + } + + .code-snippets-pagination-controls { + display: flex; + align-items: stretch; + width: 100%; + white-space: nowrap; + + .code-snippets-pagination-button { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 1 1 0; + min-height: 32px; + padding: 0 10px; + background: rgba(240, 245, 250, 0.08); + color: inherit; + text-decoration: none; + line-height: 1.2; + box-sizing: border-box; + + & + .code-snippets-pagination-button { + border-left: 1px solid rgba(240, 245, 250, 0.15); + } + + &[aria-disabled='true'] { + opacity: 0.4; + pointer-events: none; + } + } + + a.code-snippets-pagination-button:hover { + background: rgba(240, 245, 250, 0.16); + } + + a[data-action='first'], + a[data-action='last'] { + flex: 0 0 36px; + padding: 0; + } + + .code-snippets-pagination-page { + flex: 0 0 auto; + cursor: default; + opacity: 0.85; + } + } +} From d16bc87f886a13ea8f40d02875298c0ccfdb45aa Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 20:12:03 +0200 Subject: [PATCH 09/24] feat: refactor admin bar icon styles --- src/css/admin-bar.scss | 48 +++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/css/admin-bar.scss b/src/css/admin-bar.scss index 566d12b7..3ace5391 100644 --- a/src/css/admin-bar.scss +++ b/src/css/admin-bar.scss @@ -1,31 +1,24 @@ #wpadminbar { #wp-admin-bar-code-snippets > .ab-item { .code-snippets-admin-bar-icon { - display: inline-block; - top: 5px; - position: relative; - padding-right: 10px; - line-height: 1; - color: rgba(240, 245, 250, 0.6); - - svg { + width: 16px; + height: 16px; + top: 5px; + + &::before { + content: ''; + display: block; width: 16px; height: 16px; - display: block; - fill: currentColor; + mask-image: url('../assets/menu-icon.svg'); + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + background-color: rgba(240, 245, 250, 0.6); } } - - &:hover .code-snippets-admin-bar-icon, - &:focus .code-snippets-admin-bar-icon { - color: inherit; - } - } - - #wp-admin-bar-code-snippets.hover > .ab-item .code-snippets-admin-bar-icon { - color: inherit; } - + .code-snippets-safe-mode-active { > .ab-item, &.ab-item { @@ -46,15 +39,12 @@ } .code-snippets-safe-mode .code-snippets-external-icon { - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 4px; - opacity: 0.8; - font-size: 14px; - width: 14px; - height: 14px; - line-height: 1; + font-family: dashicons; + display: inline-block; + position: relative; + line-height: 1; + opacity: .8; + top: 6px; } .code-snippets-safe-mode:hover .code-snippets-external-icon, From fd419f4d50a760d45aaff6b42f86309ef4c69423 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 20:12:30 +0200 Subject: [PATCH 10/24] feat: update admin bar icon handling and remove SVG function --- src/php/class-admin-bar.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/php/class-admin-bar.php b/src/php/class-admin-bar.php index 9bc6ae09..d5ab458f 100644 --- a/src/php/class-admin-bar.php +++ b/src/php/class-admin-bar.php @@ -133,10 +133,8 @@ public function register_nodes( WP_Admin_Bar $wp_admin_bar ): void { $is_safe_mode_active = code_snippets()->evaluate_functions->is_safe_mode_active(); - $icon = ''; $title = sprintf( - '%s%s', - $icon, + '%s', esc_html__( 'Snippets', 'code-snippets' ) ); @@ -154,15 +152,6 @@ public function register_nodes( WP_Admin_Bar $wp_admin_bar ): void { $this->add_safe_mode_link( $wp_admin_bar ); } - /** - * Retrieve the Code Snippets scissors SVG icon markup. - * - * @return string - */ - private function get_scissors_svg(): string { - return ''; - } - /** * Add menu item for safe mode status. * @@ -200,7 +189,7 @@ private function add_safe_mode_nodes( WP_Admin_Bar $wp_admin_bar, bool $is_safe_ */ private function add_safe_mode_link( WP_Admin_Bar $wp_admin_bar ): void { $title = sprintf( - ' %s', + '%s ', esc_html__( 'Safe Mode', 'code-snippets' ) ); From 4caef68a7d93eb40e62261bce8558f2dcd8391c7 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 20:22:12 +0200 Subject: [PATCH 11/24] fix: update snippet response item type handling --- src/js/admin-bar.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/js/admin-bar.ts b/src/js/admin-bar.ts index a0e47f91..29e9e309 100644 --- a/src/js/admin-bar.ts +++ b/src/js/admin-bar.ts @@ -3,7 +3,7 @@ type PaginationAction = 'first' | 'prev' | 'next' | 'last' type SnippetResponseItem = { id: number - type: string + scope: string name?: string } @@ -113,8 +113,16 @@ const buildSnippetPlaceholder = (snippetId: number): string => { return config.snippetPlaceholder.replace(/%(\d+\$)?d/, String(snippetId)) } +const getTypeFromScope = (scope: string): string => { + if (scope.endsWith('-css')) return 'css' + if (scope.endsWith('-js')) return 'js' + if (scope.endsWith('content')) return 'html' + if (scope === 'condition') return 'cond' + return 'php' +} + const formatSnippetTitle = (snippet: SnippetResponseItem): string => { - const typeLabel = (snippet.type || '').toUpperCase() + const typeLabel = getTypeFromScope(snippet.scope).toUpperCase() const name = snippet.name?.trim() const title = name ? name : buildSnippetPlaceholder(snippet.id) return `(${typeLabel}) ${title}` From 1cf61b72dffcedeb6c7e2478176edae4265febb9 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 20:32:47 +0200 Subject: [PATCH 12/24] feat: enhance admin bar settings with enable option and snippet limit --- src/php/class-admin-bar.php | 21 +++++++++++------- src/php/settings/settings-fields.php | 33 +++++++++++++++++++--------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/php/class-admin-bar.php b/src/php/class-admin-bar.php index d5ab458f..706a75e6 100644 --- a/src/php/class-admin-bar.php +++ b/src/php/class-admin-bar.php @@ -123,16 +123,22 @@ public function register_nodes( WP_Admin_Bar $wp_admin_bar ): void { return; } - if ( ! apply_filters( 'code_snippets/admin_bar/enabled', true ) ) { - return; - } - if ( ! code_snippets()->current_user_can() ) { return; } $is_safe_mode_active = code_snippets()->evaluate_functions->is_safe_mode_active(); + // Always show safe mode indicator regardless of setting. + $this->add_safe_mode_nodes( $wp_admin_bar, $is_safe_mode_active ); + + // Check if admin bar menu is enabled via settings. + $is_enabled = Settings\get_setting( 'general', 'enable_admin_bar' ); + + if ( ! $is_enabled && ! apply_filters( 'code_snippets/admin_bar/enabled', false ) ) { + return; + } + $title = sprintf( '%s', esc_html__( 'Snippets', 'code-snippets' ) @@ -146,7 +152,6 @@ public function register_nodes( WP_Admin_Bar $wp_admin_bar ): void { ] ); - $this->add_safe_mode_nodes( $wp_admin_bar, $is_safe_mode_active ); $this->add_quick_links( $wp_admin_bar ); $this->add_snippet_listings( $wp_admin_bar ); $this->add_safe_mode_link( $wp_admin_bar ); @@ -258,9 +263,9 @@ private function add_quick_links( WP_Admin_Bar $wp_admin_bar ): void { $types = [ 'php' => _x( 'Functions (PHP)', 'snippet type', 'code-snippets' ), 'html' => _x( 'Content (HTML)', 'snippet type', 'code-snippets' ), - 'css' => _x( 'Style (CSS)', 'snippet type', 'code-snippets' ), - 'js' => _x( 'Script (JS)', 'snippet type', 'code-snippets' ), - 'cond' => _x( 'Condition (COND)', 'snippet type', 'code-snippets' ), + 'css' => _x( 'Styles (CSS)', 'snippet type', 'code-snippets' ), + 'js' => _x( 'Scripts (JS)', 'snippet type', 'code-snippets' ), + 'cond' => _x( 'Conditions (COND)', 'snippet type', 'code-snippets' ), ]; $types = array_intersect_key( $types, array_flip( Snippet::get_types() ) ); diff --git a/src/php/settings/settings-fields.php b/src/php/settings/settings-fields.php index 77466d79..61b05f58 100644 --- a/src/php/settings/settings-fields.php +++ b/src/php/settings/settings-fields.php @@ -29,11 +29,12 @@ function get_default_settings(): array { 'enable_description' => true, 'visual_editor_rows' => 5, 'list_order' => 'priority-asc', - 'admin_bar_snippet_limit' => 20, 'disable_prism' => false, 'hide_upgrade_menu' => false, 'complete_uninstall' => false, - 'enable_flat_files' => false, + 'enable_flat_files' => false, + 'enable_admin_bar' => false, + 'admin_bar_snippet_limit' => 20, ], 'editor' => [ 'indent_with_tabs' => true, @@ -147,14 +148,7 @@ function get_settings_fields(): array { 'modified-asc' => __( 'Modified (oldest first)', 'code-snippets' ), ], ], - 'admin_bar_snippet_limit' => [ - 'name' => __( 'Admin Bar Snippets Per Page', 'code-snippets' ), - 'type' => 'number', - 'desc' => __( 'Number of snippets to show in the admin bar Active/Inactive menus before paginating.', 'code-snippets' ), - 'label' => __( 'snippets', 'code-snippets' ), - 'min' => 1, - 'max' => 100, - ], + 'disable_prism' => [ 'name' => __( 'Disable Syntax Highlighter', 'code-snippets' ), 'type' => 'checkbox', @@ -268,5 +262,24 @@ function get_settings_fields(): array { $fields = apply_filters( 'code_snippets_settings_fields', $fields ); + // Add Admin Bar settings after plugin-provided fields (e.g., file-based execution). + $fields['general']['enable_admin_bar'] = [ + 'name' => __( 'Enable Admin Bar Menu', 'code-snippets' ), + 'type' => 'checkbox', + 'label' => __( 'Show a Snippets menu in the admin bar for quick access to snippets.', 'code-snippets' ), + ]; + + // Only show per-page control if admin bar menu is enabled in settings. + if ( get_setting( 'general', 'enable_admin_bar' ) ) { + $fields['general']['admin_bar_snippet_limit'] = [ + 'name' => __( 'Admin Bar Snippets Per Page', 'code-snippets' ), + 'type' => 'number', + 'desc' => __( 'Number of snippets to show in the admin bar Active/Inactive menus before paginating.', 'code-snippets' ), + 'label' => __( 'snippets', 'code-snippets' ), + 'min' => 1, + 'max' => 100, + ]; + } + return $fields; } From cd2167d51944ddb19c7f79ae1eb36f76c956cab4 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 8 Jan 2026 20:54:51 +0200 Subject: [PATCH 13/24] feat: add 'Conditions' tab --- src/php/class-plugin.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index 318aeb8b..eb8f9d96 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -406,6 +406,7 @@ public static function get_types(): array { 'html' => __( 'Content', 'code-snippets' ), 'css' => __( 'Styles', 'code-snippets' ), 'js' => __( 'Scripts', 'code-snippets' ), + 'cond' => __( 'Conditions', 'code-snippets' ), 'cloud' => __( 'Codevault', 'code-snippets' ), 'cloud_search' => __( 'Cloud Search', 'code-snippets' ), 'bundles' => __( 'Bundles', 'code-snippets' ), From 4e7485117e6531a52175562dfea9b72e17a11f3a Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 9 Jan 2026 15:21:22 +0200 Subject: [PATCH 14/24] feat: enable admin bar by default in settings --- src/php/settings/settings-fields.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/php/settings/settings-fields.php b/src/php/settings/settings-fields.php index 61b05f58..5802adfc 100644 --- a/src/php/settings/settings-fields.php +++ b/src/php/settings/settings-fields.php @@ -33,7 +33,7 @@ function get_default_settings(): array { 'hide_upgrade_menu' => false, 'complete_uninstall' => false, 'enable_flat_files' => false, - 'enable_admin_bar' => false, + 'enable_admin_bar' => true, 'admin_bar_snippet_limit' => 20, ], 'editor' => [ From 11a3fb19b185ddfd7b06b85131ad912302cad366 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 14 Jan 2026 09:42:19 +0000 Subject: [PATCH 15/24] chore(release): update changelog for v3.9.4 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e7d50cb..28c34ae6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ # Changelog + +## [3.9.4] (2026-01-14) + +### Added +* New import functionality to migrate snippets from file uploads with drag-and-drop interface +* Support for importing snippets from other popular plugins (Header Footer Code Manager, Insert Headers and Footers, Insert PHP Code Snippet) +* Enhanced flat files support with improved multisite mode compatibility + +### Changed +* Improved multisite snippet handling with correct wpAdminbase path resolution +* Enhanced test infrastructure with multisite support and WP-CLI helpers +* Optimized Playwright test caching strategy to avoid collisions in concurrent runs +* Better workflow configuration with improved concurrency settings and build management +* Enhanced documentation and PHPDoc comments for Snippet_Files class + +### Fixed +* Fixed wpAdminbase path for multisite mode in flat files setup +* Fixed multisite capability checks in Plugin class +* Fixed snippet execution logic for multisite support by centralizing trashed snippet handling +* Fixed multisite snippet handling to ensure local snippets use correct table and filter out trashed snippets +* Fixed GitHub Actions workflow artifact naming and zip filename format in release workflow +* Fixed pull request workflow to properly handle labeled/unlabeled events and improve build comment management +* Fixed commit message handling in sync CI to prevent quoted content from breaking the workflow + ## [3.9.3] (2025-12-03) ### Added From 04837a6d42a6e20d11603618eaa98ab9dda65863 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 14 Jan 2026 09:42:19 +0000 Subject: [PATCH 16/24] chore(release): update readme for v3.9.4 --- src/readme.txt | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/readme.txt b/src/readme.txt index 344ab2c0..4b42faec 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -105,6 +105,33 @@ You can report security bugs found in the source code of this plugin through the == Changelog == + += 3.9.4 (2026-01-14) = + +__Added__ + +* New import functionality to migrate snippets from file uploads with drag-and-drop interface +* Support for importing snippets from other popular plugins (Header Footer Code Manager, Insert Headers and Footers, Insert PHP Code Snippet) +* Enhanced flat files support with improved multisite mode compatibility + +__Changed__ + +* Improved multisite snippet handling with correct wpAdminbase path resolution +* Enhanced test infrastructure with multisite support and WP-CLI helpers +* Optimized Playwright test caching strategy to avoid collisions in concurrent runs +* Better workflow configuration with improved concurrency settings and build management +* Enhanced documentation and PHPDoc comments for Snippet_Files class + +__Fixed__ + +* Fixed wpAdminbase path for multisite mode in flat files setup +* Fixed multisite capability checks in Plugin class +* Fixed snippet execution logic for multisite support by centralizing trashed snippet handling +* Fixed multisite snippet handling to ensure local snippets use correct table and filter out trashed snippets +* Fixed GitHub Actions workflow artifact naming and zip filename format in release workflow +* Fixed pull request workflow to properly handle labeled/unlabeled events and improve build comment management +* Fixed commit message handling in sync CI to prevent quoted content from breaking the workflow + = 3.9.3 (2025-12-03) = __Added__ From 83da39b4db2b7dc5ec091bb44bd3ee3ba9320579 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 14 Jan 2026 09:43:02 +0000 Subject: [PATCH 17/24] chore(release): bump version to v3.9.4 --- package-lock.json | 4 ++-- package.json | 2 +- src/code-snippets.php | 6 +++--- src/readme.txt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3260a843..72a75d7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-snippets", - "version": "3.9.3", + "version": "3.9.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-snippets", - "version": "3.9.3", + "version": "3.9.4", "license": "GPL-2.0-or-later", "dependencies": { "@codemirror/fold": "^0.19.4", diff --git a/package.json b/package.json index ff50f0f8..6ff42006 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "code-snippets", "description": "Manage code snippets running on a WordPress-powered site through a graphical interface.", "homepage": "https://codesnippets.pro", - "version": "3.9.3", + "version": "3.9.4", "main": "src/dist/edit.js", "directories": { "test": "tests" diff --git a/src/code-snippets.php b/src/code-snippets.php index ad9b318f..c1ce0349 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,11 +8,11 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.9.3 + * Version: 3.9.4 * Requires PHP: 7.4 * Requires at least: 5.0 * - * @version 3.9.3 + * @version 3.9.4 * @package Code_Snippets * @author Shea Bunge * @copyright 2012-2024 Code Snippets Pro @@ -37,7 +37,7 @@ * * @const string */ - define( 'CODE_SNIPPETS_VERSION', '3.9.3' ); + define( 'CODE_SNIPPETS_VERSION', '3.9.4' ); /** * The full path to the main file of this plugin. diff --git a/src/readme.txt b/src/readme.txt index 4b42faec..562b130b 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -4,7 +4,7 @@ Donate link: https://codesnippets.pro Tags: code, snippets, multisite, php, css License: GPL-2.0-or-later License URI: license.txt -Stable tag: 3.9.3 +Stable tag: 3.9.4 Tested up to: 6.8 An easy, clean and simple way to enhance your site with code snippets. From 3ab6ce4aa377de069c2e408434b7e60fa6561fde Mon Sep 17 00:00:00 2001 From: "A.R." Date: Wed, 14 Jan 2026 10:06:37 +0000 Subject: [PATCH 18/24] fixed changelog formatting --- CHANGELOG.md | 6 ------ src/readme.txt | 7 ------- 2 files changed, 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c34ae6..16ffff5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,18 +12,12 @@ ### Changed * Improved multisite snippet handling with correct wpAdminbase path resolution * Enhanced test infrastructure with multisite support and WP-CLI helpers -* Optimized Playwright test caching strategy to avoid collisions in concurrent runs -* Better workflow configuration with improved concurrency settings and build management * Enhanced documentation and PHPDoc comments for Snippet_Files class ### Fixed -* Fixed wpAdminbase path for multisite mode in flat files setup * Fixed multisite capability checks in Plugin class * Fixed snippet execution logic for multisite support by centralizing trashed snippet handling * Fixed multisite snippet handling to ensure local snippets use correct table and filter out trashed snippets -* Fixed GitHub Actions workflow artifact naming and zip filename format in release workflow -* Fixed pull request workflow to properly handle labeled/unlabeled events and improve build comment management -* Fixed commit message handling in sync CI to prevent quoted content from breaking the workflow ## [3.9.3] (2025-12-03) diff --git a/src/readme.txt b/src/readme.txt index 562b130b..97e5115f 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -118,19 +118,12 @@ __Changed__ * Improved multisite snippet handling with correct wpAdminbase path resolution * Enhanced test infrastructure with multisite support and WP-CLI helpers -* Optimized Playwright test caching strategy to avoid collisions in concurrent runs -* Better workflow configuration with improved concurrency settings and build management * Enhanced documentation and PHPDoc comments for Snippet_Files class - __Fixed__ -* Fixed wpAdminbase path for multisite mode in flat files setup * Fixed multisite capability checks in Plugin class * Fixed snippet execution logic for multisite support by centralizing trashed snippet handling * Fixed multisite snippet handling to ensure local snippets use correct table and filter out trashed snippets -* Fixed GitHub Actions workflow artifact naming and zip filename format in release workflow -* Fixed pull request workflow to properly handle labeled/unlabeled events and improve build comment management -* Fixed commit message handling in sync CI to prevent quoted content from breaking the workflow = 3.9.3 (2025-12-03) = From 6d8d3e51d09a66f606971f4a0432b041e06c6330 Mon Sep 17 00:00:00 2001 From: "A.R." Date: Wed, 14 Jan 2026 10:07:49 +0000 Subject: [PATCH 19/24] fixed changelog formatting --- src/readme.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/readme.txt b/src/readme.txt index 97e5115f..aa4c3908 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -119,6 +119,7 @@ __Changed__ * Improved multisite snippet handling with correct wpAdminbase path resolution * Enhanced test infrastructure with multisite support and WP-CLI helpers * Enhanced documentation and PHPDoc comments for Snippet_Files class + __Fixed__ * Fixed multisite capability checks in Plugin class From 10e052d4e6f992e668f9a3555252cd5b1ccb499d Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 14 Jan 2026 12:33:35 +0200 Subject: [PATCH 20/24] fix: better handle null tags in snippet data --- src/php/migration/importers/files/file-upload-importer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/php/migration/importers/files/file-upload-importer.php b/src/php/migration/importers/files/file-upload-importer.php index e4ec32f9..84d82005 100644 --- a/src/php/migration/importers/files/file-upload-importer.php +++ b/src/php/migration/importers/files/file-upload-importer.php @@ -280,7 +280,7 @@ private function parse_json_file( string $file_path, string $file_name ) { 'id' => $snippet_data['id'] ?? uniqid(), 'title' => $snippet_data['name'] ?? __( 'Untitled Snippet', 'code-snippets' ), 'scope' => $snippet_data['scope'] ?? 'global', - 'tags' => is_array( $snippet_data['tags'] ?? [] ) ? implode( ', ', $snippet_data['tags'] ) : '', + 'tags' => is_array( $snippet_data['tags'] ?? null ) ? implode( ', ', $snippet_data['tags'] ) : '', 'description' => $snippet_data['desc'] ?? $snippet_data['description'] ?? '', 'type' => Snippet::get_type_from_scope( $snippet_data['scope'] ?? 'global' ) ]; From 9aeab0851c5ea9556abff7eabe8f89e5b1bee52d Mon Sep 17 00:00:00 2001 From: Shea Bunge Date: Fri, 16 Jan 2026 19:56:14 +1100 Subject: [PATCH 21/24] Update help.codesnippets.pro links to instead point to codesnippets.pro/docs. --- .github/ISSUE_TEMPLATE/config.yml | 6 +- CHANGELOG.md | 2 +- README.md | 2 +- src/php/admin-menus/class-welcome-menu.php | 2 +- src/php/class-admin.php | 2 +- src/php/class-contextual-help.php | 10 +-- src/php/views/partials/list-table-notices.php | 66 +++++++++---------- src/readme.txt | 4 +- 8 files changed, 47 insertions(+), 47 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ff7bf39b..30e5bf94 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,14 +1,14 @@ blank_issues_enabled: true contact_links: - name: ❓ Support Question - url: https://help.codesnippets.pro/ + url: https://codesnippets.pro/support/ about: For Code Snippets users who need assistant and have general usage questions. - name: 💎 Premium Support - For customers only - url: https://help.codesnippets.pro/ + url: https://codesnippets.pro/support/ about: If you have an active license you are entitled to premium support. - name: 🗨️ Code Snippets Facebook Community url: https://www.facebook.com/groups/codesnippetsplugin about: The main Facebook group where all kinds of users come together to help each other. - name: 📚 Code Snippets Documentation - url: https://help.codesnippets.pro/ + url: https://codesnippets.pro/docs/ about: Anything you need to know about Code Snippets and what to do if you may have an issue. diff --git a/CHANGELOG.md b/CHANGELOG.md index 16ffff5e..e57a2fca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -396,7 +396,7 @@ ### Added * Added additional editor shortcuts to list in tooltip. -* Filter for changing Snippets admin menu position. [See this help article for more information.](https://help.codesnippets.pro/article/61-how-can-i-change-the-location-of-the-snippets-admin-menu) +* Filter for changing Snippets admin menu position. [See this help article for more information.](https://codesnippets.pro/doc/snippets-menu-location/) * Ability to filter shortcode output. Thanks to contributions from [Jack Szwergold](https://github.com/JackSzwergold). ### Fixed diff --git a/README.md b/README.md index 931134db..6a8240fd 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,6 @@ WordPress plugin for managing executable code snippets through a graphical inter - [Read more on WordPress.org](https://wordpress.org/plugins/code-snippets) - [Download the latest stable version](https://downloads.wordpress.org/plugin/code-snippets.latest-stable.zip) -- [Read the help documentation](https://help.codesnippets.pro) +- [Read the help documentation](https://codesnippets.pro/docs/) - [Leave a review](https://wordpress.org/support/plugin/code-snippets/reviews/#new-post) - [Translate into your language](https://translate.wordpress.org/projects/wp-plugins/code-snippets/) diff --git a/src/php/admin-menus/class-welcome-menu.php b/src/php/admin-menus/class-welcome-menu.php index ceb816ac..02788d4e 100644 --- a/src/php/admin-menus/class-welcome-menu.php +++ b/src/php/admin-menus/class-welcome-menu.php @@ -59,7 +59,7 @@ protected function get_header_links(): array { 'label' => __( 'Cloud', 'code-snippets' ), ], 'resources' => [ - 'url' => 'https://help.codesnippets.pro/', + 'url' => 'https://codesnippets.pro/support/', 'icon' => 'sos', 'label' => __( 'Support', 'code-snippets' ), ], diff --git a/src/php/class-admin.php b/src/php/class-admin.php index dfdf093d..9b109b5d 100644 --- a/src/php/class-admin.php +++ b/src/php/class-admin.php @@ -179,7 +179,7 @@ public function plugin_row_meta( array $plugin_meta, string $plugin_file ): arra array( sprintf( $format, - 'https://help.codesnippets.pro/', + 'https://codesnippets.pro/support/', esc_attr__( 'Find out how to get support with Code Snippets', 'code-snippets' ), esc_html__( 'Docs and Support', 'code-snippets' ) ), diff --git a/src/php/class-contextual-help.php b/src/php/class-contextual-help.php index 267d55ff..85b69e53 100644 --- a/src/php/class-contextual-help.php +++ b/src/php/class-contextual-help.php @@ -67,15 +67,15 @@ public function load() { private function load_help_sidebar() { $sidebar_links = [ 'https://wordpress.org/plugins/code-snippets' => __( 'About Plugin', 'code-snippets' ), - 'https://help.codesnippets.pro/collection/3-faq' => __( 'FAQ', 'code-snippets' ), + 'https://codesnippets.pro/docs/faq/' => __( 'FAQ', 'code-snippets' ), 'https://wordpress.org/support/plugin/code-snippets' => __( 'Support Forum', 'code-snippets' ), 'https://codesnippets.pro' => __( 'Plugin Website', 'code-snippets' ), ]; $kses = [ - 'p' => [], + 'p' => [], 'strong' => [], - 'a' => [ 'href' => [] ], + 'a' => [ 'href' => [] ], ]; $contents = sprintf( "

%s

\n", esc_html__( 'For more information:', 'code-snippets' ) ); @@ -143,7 +143,7 @@ private function load_manage_help() { __( 'Be sure to check your snippets for errors before you activate them, as a faulty snippet could bring your whole blog down. If your site starts doing strange things, deactivate all your snippets and activate them one at a time.', 'code-snippets' ), __( "If something goes wrong with a snippet, and you can't use WordPress, you can cause all snippets to stop executing by turning on safe mode.", 'code-snippets' ), /* translators: %s: URL to Code Snippets Pro Docs */ - sprintf( __( 'You can find out how to enable safe mode in the Code Snippets Pro Docs.', 'code-snippets' ), 'https://help.codesnippets.pro/article/12-safe-mode' ) + sprintf( __( 'You can find out how to enable safe mode in the Code Snippets Pro Docs.', 'code-snippets' ), 'https://codesnippets.pro/doc/safe-mode/' ), ] ); } @@ -159,7 +159,7 @@ private function load_edit_help() { $this->get_intro_text() . __( 'Here you can add a new snippet, or edit an existing one.', 'code-snippets' ), /* translators: %s: URL to Code Snippets Pro Docs */ - sprintf( __( "If you're not sure about the types of snippets you can add, take a look at the Code Snippets Pro Docs for inspiration.", 'code-snippets' ), 'https://help.codesnippets.pro/collection/2-adding-snippets' ), + sprintf( __( "If you're not sure about the types of snippets you can add, take a look at the Code Snippets Pro Docs for inspiration.", 'code-snippets' ), 'https://codesnippets.pro/docs/adding-snippets/' ), ] ); diff --git a/src/php/views/partials/list-table-notices.php b/src/php/views/partials/list-table-notices.php index c3d34561..ff6b16ea 100644 --- a/src/php/views/partials/list-table-notices.php +++ b/src/php/views/partials/list-table-notices.php @@ -30,7 +30,7 @@ printf( esc_html( $text ), 'CODE_SNIPPETS_SAFE_MODE', 'wp-config.php' ); ?> - +

@@ -45,54 +45,54 @@ $result = sanitize_key( $_REQUEST['result'] ); $result_messages = [ - 'executed' => __( 'Snippet executed.', 'code-snippets' ), - 'activated' => __( 'Snippet activated.', 'code-snippets' ), - 'activated-multi' => __( 'Selected snippets activated.', 'code-snippets' ), - 'deactivated' => __( 'Snippet deactivated.', 'code-snippets' ), - 'deactivated-multi' => __( 'Selected snippets deactivated.', 'code-snippets' ), - 'deleted' => __( 'Snippet trashed.', 'code-snippets' ), - 'deleted-multi' => __( 'Selected snippets trashed.', 'code-snippets' ), - 'deleted_permanently' => __( 'Snippet permanently deleted.', 'code-snippets' ), + 'executed' => __( 'Snippet executed.', 'code-snippets' ), + 'activated' => __( 'Snippet activated.', 'code-snippets' ), + 'activated-multi' => __( 'Selected snippets activated.', 'code-snippets' ), + 'deactivated' => __( 'Snippet deactivated.', 'code-snippets' ), + 'deactivated-multi' => __( 'Selected snippets deactivated.', 'code-snippets' ), + 'deleted' => __( 'Snippet trashed.', 'code-snippets' ), + 'deleted-multi' => __( 'Selected snippets trashed.', 'code-snippets' ), + 'deleted_permanently' => __( 'Snippet permanently deleted.', 'code-snippets' ), 'deleted-permanently-multi' => __( 'Selected snippets permanently deleted.', 'code-snippets' ), - 'restored' => __( 'Snippet restored.', 'code-snippets' ), - 'restored-multi' => __( 'Selected snippets restored.', 'code-snippets' ), - 'cloned' => __( 'Snippet cloned.', 'code-snippets' ), - 'cloned-multi' => __( 'Selected snippets cloned.', 'code-snippets' ), - 'cloud-refreshed' => __( 'Synced cloud data has been successfully refreshed.', 'code-snippets' ), + 'restored' => __( 'Snippet restored.', 'code-snippets' ), + 'restored-multi' => __( 'Selected snippets restored.', 'code-snippets' ), + 'cloned' => __( 'Snippet cloned.', 'code-snippets' ), + 'cloned-multi' => __( 'Selected snippets cloned.', 'code-snippets' ), + 'cloud-refreshed' => __( 'Synced cloud data has been successfully refreshed.', 'code-snippets' ), ]; // Add undo link for single snippet trash action if ( 'deleted' === $result && ! empty( $_REQUEST['ids'] ) ) { $deleted_ids = sanitize_text_field( $_REQUEST['ids'] ); - $undo_url = wp_nonce_url( - add_query_arg( array( - 'action' => 'restore', - 'ids' => $deleted_ids - ) ), + $undo_url = wp_nonce_url( + add_query_arg( + [ + 'action' => 'restore', + 'ids' => $deleted_ids, + ] + ), 'bulk-snippets' ); - $result_messages['deleted'] = sprintf( - __( 'Snippet trashed. Undo', 'code-snippets' ), - esc_url( $undo_url ) - ); + // translators: %s: Undo URL. + $undo_message = __( 'Snippet trashed. Undo', 'code-snippets' ); + $result_messages['deleted'] = sprintf( $undo_message, esc_url( $undo_url ) ); } // Add undo link for bulk snippet trash action if ( 'deleted-multi' === $result && ! empty( $_REQUEST['ids'] ) ) { $deleted_ids = sanitize_text_field( $_REQUEST['ids'] ); - $undo_url = wp_nonce_url( - add_query_arg( array( - 'action' => 'restore', - 'ids' => $deleted_ids - ) ), + $undo_url = wp_nonce_url( + add_query_arg( array( + 'action' => 'restore', + 'ids' => $deleted_ids, + ) ), 'bulk-snippets' ); - $result_messages['deleted-multi'] = sprintf( - __( 'Selected snippets trashed. Undo', 'code-snippets' ), - esc_url( $undo_url ) - ); + // translators: %s: Undo URL. + $undo_message = __( 'Selected snippets trashed. Undo', 'code-snippets' ); + $result_messages['deleted-multi'] = sprintf( $undo_message, esc_url( $undo_url ) ); } $result_messages = apply_filters( 'code_snippets/manage/result_messages', $result_messages ); @@ -100,7 +100,7 @@ if ( isset( $result_messages[ $result ] ) ) { $result_kses = [ 'strong' => [], - 'a' => [ + 'a' => [ 'href' => [], ], ]; diff --git a/src/readme.txt b/src/readme.txt index aa4c3908..2a937083 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -63,10 +63,10 @@ Network Activating Code Snippets through the Network Dashboard will enable a spe == Frequently Asked Questions == -A full list of our Frequently Asked Questions can be found at [help.codesnippets.pro](https://help.codesnippets.pro/collection/3-faq). +A full list of our Frequently Asked Questions can be found at [codesnippets.pro](https://codesnippets.pro/docs/faq/). = How can I recover my site if it is crashed by a buggy snippet? = -You can recover your site by enabling the Code Snippets safe mode feature. Instructions for how to turn it on are available here: . +You can recover your site by enabling the Code Snippets safe mode feature. Instructions for how to turn it on are available here: . = Will I lose my snippets if I change the theme or upgrade WordPress? = No, the snippets are stored in the WordPress database, independent of the theme and unaffected by WordPress upgrades. From d0e216c17693059f9db850a739d3a11fd6d288b1 Mon Sep 17 00:00:00 2001 From: Code Snippets <139164393+code-snippets-bot@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:04:19 +0200 Subject: [PATCH 22/24] fix: changelog entries --- CHANGELOG.md | 8 ++------ src/readme.txt | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e57a2fca..50ab5193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,14 @@ # Changelog - - ## [3.9.4] (2026-01-14) ### Added * New import functionality to migrate snippets from file uploads with drag-and-drop interface * Support for importing snippets from other popular plugins (Header Footer Code Manager, Insert Headers and Footers, Insert PHP Code Snippet) -* Enhanced flat files support with improved multisite mode compatibility +* Enhanced file based execution support with improved multisite mode compatibility ### Changed -* Improved multisite snippet handling with correct wpAdminbase path resolution -* Enhanced test infrastructure with multisite support and WP-CLI helpers -* Enhanced documentation and PHPDoc comments for Snippet_Files class +* Updated links to more recent documentation pages ### Fixed * Fixed multisite capability checks in Plugin class diff --git a/src/readme.txt b/src/readme.txt index 2a937083..16514bc4 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -104,21 +104,17 @@ You can report security bugs found in the source code of this plugin through the == Changelog == - - = 3.9.4 (2026-01-14) = __Added__ * New import functionality to migrate snippets from file uploads with drag-and-drop interface * Support for importing snippets from other popular plugins (Header Footer Code Manager, Insert Headers and Footers, Insert PHP Code Snippet) -* Enhanced flat files support with improved multisite mode compatibility +* Enhanced file based execution support with improved multisite mode compatibility __Changed__ -* Improved multisite snippet handling with correct wpAdminbase path resolution -* Enhanced test infrastructure with multisite support and WP-CLI helpers -* Enhanced documentation and PHPDoc comments for Snippet_Files class +* Updated links to more recent documentation pages __Fixed__ From f6929f1e4abbdfab465c05eae1508cbad401c992 Mon Sep 17 00:00:00 2001 From: Imants Date: Sat, 17 Jan 2026 23:45:54 +0200 Subject: [PATCH 23/24] fix: eslint @stylistic/quote-props --- config/webpack-js.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/config/webpack-js.ts b/config/webpack-js.ts index 0ccacc43..5d50210d 100644 --- a/config/webpack-js.ts +++ b/config/webpack-js.ts @@ -25,13 +25,13 @@ const babelConfig = { export const jsWebpackConfig: Configuration = { entry: { 'admin-bar': `${SOURCE_DIR}/admin-bar.ts`, - edit: { import: `${SOURCE_DIR}/edit.tsx`, dependOn: 'editor' }, - editor: `${SOURCE_DIR}/editor.ts`, - import: `${SOURCE_DIR}/import.tsx`, - manage: `${SOURCE_DIR}/manage.ts`, - mce: `${SOURCE_DIR}/mce.ts`, - prism: `${SOURCE_DIR}/prism.ts`, - settings: { import: `${SOURCE_DIR}/settings.ts`, dependOn: 'editor' } + 'edit': { import: `${SOURCE_DIR}/edit.tsx`, dependOn: 'editor' }, + 'editor': `${SOURCE_DIR}/editor.ts`, + 'import': `${SOURCE_DIR}/import.tsx`, + 'manage': `${SOURCE_DIR}/manage.ts`, + 'mce': `${SOURCE_DIR}/mce.ts`, + 'prism': `${SOURCE_DIR}/prism.ts`, + 'settings': { import: `${SOURCE_DIR}/settings.ts`, dependOn: 'editor' } }, output: { path: join(resolve(__dirname), '..', DEST_DIR), From b7e5a74b12186c28ec22a3d72135688dd3f98b73 Mon Sep 17 00:00:00 2001 From: Imants Date: Sat, 17 Jan 2026 23:50:48 +0200 Subject: [PATCH 24/24] fix: codestyle clean up and delint --- .../rest-api/class-snippets-rest-controller.php | 16 ++++++++-------- src/php/settings/settings-fields.php | 3 --- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/php/rest-api/class-snippets-rest-controller.php b/src/php/rest-api/class-snippets-rest-controller.php index dac50a87..f8478cb8 100644 --- a/src/php/rest-api/class-snippets-rest-controller.php +++ b/src/php/rest-api/class-snippets-rest-controller.php @@ -661,14 +661,14 @@ public function get_item_schema(): array { 'description' => esc_html__( 'Descriptive title for the snippet.', 'code-snippets' ), 'type' => 'string', ], - 'desc' => [ - 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ), - 'type' => 'string', - ], - 'code' => [ - 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ), - 'type' => 'string', - ], + 'desc' => [ + 'description' => esc_html__( 'Descriptive text associated with snippet.', 'code-snippets' ), + 'type' => 'string', + ], + 'code' => [ + 'description' => esc_html__( 'Executable snippet code.', 'code-snippets' ), + 'type' => 'string', + ], 'tags' => [ 'description' => esc_html__( 'List of tag categories the snippet belongs to.', 'code-snippets' ), 'type' => 'array', diff --git a/src/php/settings/settings-fields.php b/src/php/settings/settings-fields.php index 5802adfc..06033d14 100644 --- a/src/php/settings/settings-fields.php +++ b/src/php/settings/settings-fields.php @@ -148,7 +148,6 @@ function get_settings_fields(): array { 'modified-asc' => __( 'Modified (oldest first)', 'code-snippets' ), ], ], - 'disable_prism' => [ 'name' => __( 'Disable Syntax Highlighter', 'code-snippets' ), 'type' => 'checkbox', @@ -262,14 +261,12 @@ function get_settings_fields(): array { $fields = apply_filters( 'code_snippets_settings_fields', $fields ); - // Add Admin Bar settings after plugin-provided fields (e.g., file-based execution). $fields['general']['enable_admin_bar'] = [ 'name' => __( 'Enable Admin Bar Menu', 'code-snippets' ), 'type' => 'checkbox', 'label' => __( 'Show a Snippets menu in the admin bar for quick access to snippets.', 'code-snippets' ), ]; - // Only show per-page control if admin bar menu is enabled in settings. if ( get_setting( 'general', 'enable_admin_bar' ) ) { $fields['general']['admin_bar_snippet_limit'] = [ 'name' => __( 'Admin Bar Snippets Per Page', 'code-snippets' ),