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 = '' . $this->get_scissors_svg() . ' ';
$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(
+ '',
+ 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 = '' . $this->get_scissors_svg() . ' ';
$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' ),