From d668820d5d06cdedff67b45ba82c428e0d1fb9e1 Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 19:15:22 +0200 Subject: [PATCH 01/16] fix: remove .browserslistrc file --- .browserslistrc | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .browserslistrc diff --git a/.browserslistrc b/.browserslistrc deleted file mode 100644 index 65f1ce16..00000000 --- a/.browserslistrc +++ /dev/null @@ -1,10 +0,0 @@ -last 2 versions -not dead -Chrome >= 111 -Edge >= 111 -Firefox >= 112 -Safari >= 16.4 -Android >= 111 -ChromeAndroid >= 111 -FirefoxAndroid >= 112 -iOS >= 16.4 \ No newline at end of file From ec7bcca7741020a46a341f982f1dc61662513c8d Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 19:15:37 +0200 Subject: [PATCH 02/16] feat: add custom scissors icon to code snippets plugin and button title --- src/js/mce.ts | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/js/mce.ts b/src/js/mce.ts index 9927cce4..27274837 100644 --- a/src/js/mce.ts +++ b/src/js/mce.ts @@ -93,16 +93,40 @@ export const insertSourceMenu = (editor: Editor, ed: LocalisedEditor) => ({ } }) -tinymce.PluginManager.add('code_snippets', function (editor) { +// Custom scissors icon as base64-encoded SVG (same as used in WP admin menu) +// Base64-encoded version of menu-icon.svg +const scissorsIcon = + 'data:image/svg+xml;base64,' + + 'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZX' + + 'dCb3g9IjAgMCAyNiAyNSI+PHBhdGggZmlsbD0iIzUxNTc1ZiIgZD0iTTYuMTI3IDExLjk2Nmgz' + + 'LjQxNGEuOTEuOTEgMCAwIDEgLjg0NC41NjMuOTMuOTMgMCAwIDEtLjE4NSAxLjAwNGwuMDA1Lj' + + 'AxLTIuMzM4IDIuMzU1YTQuMTkgNC4xOSAwIDAgMCAwIDUuODg1QTQuMTEgNC4xMSAwIDAgMCAx' + + 'MC43ODQgMjNhNC4xMSA0LjExIDAgMCAwIDIuOTE3LTEuMjE3IDQuMiA0LjIgMCAwIDAgMS4wND' + + 'gtMS44MDIgNC4yIDQuMiAwIDAgMC0uOTE1LTMuOTQgNC4xIDQuMSAwIDAgMC0xLjczMi0xLjE0' + + 'NWwuNjE0LS42MTloNy44NDZjMS44NyAwIDMuMzkxLTEuNjA0IDMuNDM2LTMuNjA2IDAtLjAzMy' + + '4wMDQtLjA2IDAtLjA5MmExLjAyIDEuMDIgMCAwIDAtLjMyNi0uNjYgMSAxIDAgMCAwLS42ODEt' + + 'LjI2NmgtNS42OTJsNC4xMS00LjE0NWExLjAyNSAxLjAyNSAwIDAgMCAuMDY4LTEuMzc0Yy0uMD' + + 'IyLS4wMjctLjA0NC0uMDQ2LS4wNjgtLjA2OC0xLjQzLTEuMzc4LTMuNjM0LTEuNDMtNC45NTMt' + + 'LjA5OGwtNS42MzUgNS42ODVIOS44MmMuMzk4LS44MS41MjQtMS43My4zNTgtMi42MTlhNC4xNy' + + 'A0LjE3IDAgMCAwLTEuMjc5LTIuMzA4IDQuMDk2IDQuMDk2IDAgMCAwLTQuOTUtLjQ1NyA0LjE1' + + 'IDQuMTUgMCAwIDAtMS42NzIgMi4wMzYgNC4yIDQuMiAwIDAgMC0uMTE5IDIuNjQyYy4yNDYuOD' + + 'cuNzY3IDEuNjM1IDEuNDgzIDIuMThzMS41OS44NCAyLjQ4Ni44MzlNNy45NiA3LjgwNWMwIC40' + + 'OS0uMTkzLjk2LS41MzYgMS4zMDhhMS44MjUgMS44MjUgMCAwIDEtMi41OTIuMDAxQTEuODU3ID' + + 'EuODU3IDAgMCAxIDQuODMgNi41YTEuODI1IDEuODI1IDAgMCAxIDIuNTkzIDBjLjM0My4zNDYu' + + 'NTM3LjgxNi41MzcgMS4zMDZtNC4xMTkgOS43MzNhMS44NiAxLjg2IDAgMCAxLS41OTUgMy4wMT' + + 'QgMS44MSAxLjgxIDAgMCAxLTEuOTk5LS40MDIgMS44NSAxLjg1IDAgMCAxLS41MzYtMS4zMDYg' + + 'MS44NiAxLjg2IDAgMCAxIDEuMTMtMS43MSAxLjgxIDEuODEgMCAwIDEgMiAuNDA0Ii8+PC9zdmc+' + +tinymce.PluginManager.add('code_snippets', editor => { const activeEditor = tinymce.activeEditor - // Create the menu button with inline menu items editor.addButton('code_snippets', { - icon: 'code', type: 'menubutton', + title: 'Code Snippets', + image: scissorsIcon, menu: [ - insertContentMenu(editor, activeEditor), + insertContentMenu(editor, activeEditor), insertSourceMenu(editor, activeEditor) - ] + ], }) }) From 51865f8df187d67b874272ef5b6ba656e298129b Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 19:19:10 +0200 Subject: [PATCH 03/16] fix: ensure 'enable_flat_files' setting is included in default settings --- src/php/settings/settings-fields.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/php/settings/settings-fields.php b/src/php/settings/settings-fields.php index 50312ac0..72262cc7 100644 --- a/src/php/settings/settings-fields.php +++ b/src/php/settings/settings-fields.php @@ -32,6 +32,7 @@ function get_default_settings(): array { 'disable_prism' => false, 'hide_upgrade_menu' => false, 'complete_uninstall' => false, + 'enable_flat_files' => false, ], 'editor' => [ 'indent_with_tabs' => true, From 5856d7c496de837250d389976743392d84d9f6ee Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 19:19:17 +0200 Subject: [PATCH 04/16] fix: improve setting sanitization logic in sanitize_settings function --- src/php/settings/settings.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/php/settings/settings.php b/src/php/settings/settings.php index 4d906483..1f870560 100644 --- a/src/php/settings/settings.php +++ b/src/php/settings/settings.php @@ -327,7 +327,8 @@ function sanitize_settings( array $input ): array { // Attempt to sanitize the setting value. $sanitized_value = sanitize_setting_value( $field, $input_value ); - if ( ! is_null( $sanitized_value ) && $settings[ $section_id ][ $field_id ] !== $sanitized_value ) { + $current_value = $settings[ $section_id ][ $field_id ] ?? null; + if ( ! is_null( $sanitized_value ) && $current_value !== $sanitized_value ) { $settings[ $section_id ][ $field_id ] = $sanitized_value; $updated = true; } From 98965b846a10448fdabcddc4befe0aa70c0d3177 Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 19:19:29 +0200 Subject: [PATCH 05/16] fix: prevent vendor namespace collisions by removing non-prefixed mappings --- src/php/load.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/php/load.php b/src/php/load.php index 142ae033..de46e383 100644 --- a/src/php/load.php +++ b/src/php/load.php @@ -44,7 +44,25 @@ const REST_API_NAMESPACE = 'code-snippets/v'; // Load dependencies with Composer. -require_once dirname( __DIR__ ) . '/vendor/autoload.php'; +$code_snippets_autoloader = require dirname( __DIR__ ) . '/vendor/autoload.php'; + +// Remove all original (non-prefixed) vendor namespace mappings to prevent collisions with other plugins. +// Since Imposter rewrites namespaces to Code_Snippets\Vendor\*, we need to remove the original PSR-4 +// mappings that Composer generates so other plugins can load their own copies of these libraries. +if ( $code_snippets_autoloader instanceof \Composer\Autoload\ClassLoader ) { + $prefixes = $code_snippets_autoloader->getPrefixesPsr4(); + $our_prefix = 'Code_Snippets\\Vendor\\'; + + foreach ( $prefixes as $namespace => $paths ) { + // Remove any non-Code_Snippets namespace that has a corresponding prefixed version + if ( strpos( $namespace, $our_prefix ) === false ) { + $prefixed_namespace = $our_prefix . $namespace; + if ( isset( $prefixes[ $prefixed_namespace ] ) ) { + $code_snippets_autoloader->setPsr4( $namespace, [] ); + } + } + } +} /** * Retrieve the instance of the main plugin class. From 6846c57c0ddc6916f21ca4d88ff9a5c0ae566700 Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 19:20:52 +0200 Subject: [PATCH 06/16] feat: add psr-4 autoload configuration placeholder --- src/composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/composer.json b/src/composer.json index 882d16a0..be43b794 100644 --- a/src/composer.json +++ b/src/composer.json @@ -23,7 +23,9 @@ "autoload": { "classmap": [ "php/" - ] + ], + "psr-4": { + } }, "require": { "php": ">=7.4", From ab27ae9df0e094a5fd2200a2a1f500f354cf5f96 Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 19:21:56 +0200 Subject: [PATCH 07/16] docs: add guidelines for managing Composer dependencies to prevent conflicts --- CONTRIBUTING.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 796eef73..0b543a3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,6 +69,45 @@ command: npm run watch ``` +## Managing Composer dependencies + +Code Snippets uses the [Imposter plugin](https://github.com/TypistTech/imposter) to namespace-prefix all vendor +dependencies under `Code_Snippets\Vendor\`. This prevents conflicts with other WordPress plugins that might use the +same libraries (e.g., Guzzle, Minify, Monolog). + +### Adding a new dependency + +When adding a new Composer dependency that might exist in other plugins: + +1. Add the package to `src/composer.json` as usual: + ```shell + cd src + composer require vendor/package + ``` + +2. Add corresponding PSR-4 autoload entries for the prefixed namespace in `src/composer.json`: + ```json + "autoload": { + "psr-4": { + "Code_Snippets\\Vendor\\OriginalVendor\\PackageName\\": "vendor/vendor-name/package-name/src/" + } + } + ``` + +3. Run `composer dump-autoload -o` to regenerate autoload files. + +4. The Imposter plugin will automatically rewrite the namespaces during `post-install-cmd` and `post-update-cmd` hooks. + +5. Our autoloader in `src/php/load.php` automatically removes original (non-prefixed) namespace mappings to prevent + collisions, so no code changes are needed. + +### How it works + +- Imposter rewrites all vendor code from `Vendor\Package\Class` to `Code_Snippets\Vendor\Vendor\Package\Class` +- The `load.php` file dynamically detects and removes original namespace PSR-4 mappings at runtime +- Other plugins can load their own versions of the same libraries without conflicts +- Your code should always use the prefixed namespace: `use Code_Snippets\Vendor\Vendor\Package\Class;` + ## Preparing for release The plugin repository includes a number of files that are unnecessary when distributing the plugin files for From a72d08b5f59b344a305f3ea14113a0eefe8e47cf Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 20:10:10 +0200 Subject: [PATCH 08/16] feat: add back navigation styling to edit page --- src/css/edit.scss | 10 ++++++++++ src/js/components/SnippetForm/SnippetForm.tsx | 2 +- src/php/admin-menus/class-edit-menu.php | 9 ++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/css/edit.scss b/src/css/edit.scss index 862af07f..2485a6d4 100644 --- a/src/css/edit.scss +++ b/src/css/edit.scss @@ -95,3 +95,13 @@ form.condition-snippet .snippet-code-container { display: none; } + +.cs-back { + cursor: pointer; + + &::before { + content: '<'; + color: #2271b1; + margin-inline-end: 3px; + } +} \ No newline at end of file diff --git a/src/js/components/SnippetForm/SnippetForm.tsx b/src/js/components/SnippetForm/SnippetForm.tsx index 2291636b..10ac3ff6 100644 --- a/src/js/components/SnippetForm/SnippetForm.tsx +++ b/src/js/components/SnippetForm/SnippetForm.tsx @@ -145,7 +145,7 @@ const EditFormWrap: React.FC = () => { return (
-

+

{isCondition(snippet) ? {__('Back to all conditions', 'code-snippets')} diff --git a/src/php/admin-menus/class-edit-menu.php b/src/php/admin-menus/class-edit-menu.php index e6f99c73..59962cca 100644 --- a/src/php/admin-menus/class-edit-menu.php +++ b/src/php/admin-menus/class-edit-menu.php @@ -97,8 +97,11 @@ protected function ensure_correct_page() { $edit_hook .= $screen->in_admin( 'network' ) ? '-network' : ''; // Disallow visiting the edit snippet page without a valid ID. - if ( $screen->base === $edit_hook && ( empty( $_REQUEST['id'] ) || 0 === $this->snippet->id || null === $this->snippet->id ) && - ! isset( $_REQUEST['preview'] ) ) { + if ( + $screen->base === $edit_hook + && ( empty( $_REQUEST['id'] ) || 0 === $this->snippet->id || null === $this->snippet->id ) + && ! isset( $_REQUEST['preview'] ) + ) { wp_safe_redirect( code_snippets()->get_menu_url( 'add' ) ); exit; } @@ -111,7 +114,7 @@ protected function ensure_correct_page() { */ public function render() { printf( - '

', + '
%s
', esc_html__( 'Loading edit page…', 'code-snippets' ) ); } From 0b158740aa70d53852410ed491d9876072f23182 Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 20:10:38 +0200 Subject: [PATCH 09/16] fix: enhance styling for cloud related elements --- src/css/edit/_gpt.scss | 56 ++++++++++++++++---- src/css/manage/_cloud.scss | 104 +++++++++++++++++++++++++------------ 2 files changed, 117 insertions(+), 43 deletions(-) diff --git a/src/css/edit/_gpt.scss b/src/css/edit/_gpt.scss index 9adb6802..70702bb7 100644 --- a/src/css/edit/_gpt.scss +++ b/src/css/edit/_gpt.scss @@ -20,30 +20,66 @@ .generate-button { display: flex; align-items: center; + gap: 5px; .dashicons-warning { color: #b32d2e; } + + .snippet-tags-container &, + .snippet-description-container & { + float: inline-end; + } } .code-line-explanation { display: flex; - align-items: center; - font-size: 13px; + cursor: default; + font-size: inherit; margin: 0; - padding-block: 0; - padding-inline: 8px; - background-color: #fff; - border: 1px solid #bbb; - border-inline-start: 0; - border-inline-end: 0; + padding-inline: 6px; + border-inline-start: none; + border-inline-end: none; + border-block-start: 1px solid rgb(0 0 0 / 15%); + border-block-end: 1px solid rgb(0 0 0 / 15%); + border-image-slice: 1; + border-image-width: 1; + border-image-repeat: stretch; color: #666; font-style: italic; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; + font-family: monospace; + gap: 5px; + align-items: center; img { block-size: 1rem; - padding-inline-end: 5px; + opacity: 0.7; + } + + .code-line-actions { + cursor: default; + gap: 7px; + display: inline-flex; + margin-inline-start: 5px; + font-family: system-ui; + font-style: normal; + + .commit { + color: #3d9970; + } + + .remove { + color: #b32d2e; + } + + .action { + cursor: pointer; + opacity: 0.6; + + &:hover { + opacity: 1; + } + } } } diff --git a/src/css/manage/_cloud.scss b/src/css/manage/_cloud.scss index 14e01376..8a26dccf 100644 --- a/src/css/manage/_cloud.scss +++ b/src/css/manage/_cloud.scss @@ -69,13 +69,6 @@ td.column-name { } } -td.column-download { - display: flex; - gap: 0.5em; - flex-flow: column; - text-align: center; -} - .cloud-snippet-download { color: theme.$accent !important; } @@ -218,12 +211,72 @@ td.column-download { display: flex; flex-wrap: wrap; justify-content: center; + + .plugin-card { + display: flex; + flex-direction: column; + justify-content: space-between; + + .cloud-meta-row { + display: flex; + justify-content: space-between; + align-items: center; + flex-grow: 1; + } + + .column-name { + display: flex; + + h3 { + display: inline-flex; + flex-shrink: 1; + } + + .title-icon { + block-size: 90px; + margin-block-start: -7px; + } + } + + .column-votes { + display: inline-flex; + gap: 3px; + + &:hover { + .thumbs-up { + stroke: #059669; + fill: #6ee7b7; + animation: thumb 1s ease-in-out infinite; + } + } + + .num-votes { + display: inline-flex; + align-items: flex-end; + } + } + } + + .action-buttons { + margin: 0; + + .button { + inline-size: 100%; + text-align: center; + } + } } -.cloud-snippets .plugin-card { - display: flex; - flex-direction: column; - justify-content: space-between; +.cloud-snippets #the-list{ + .column-download { + display: flex; + flex-flow: column; + text-align: right; + + li { + list-style: none; + } + } } .cloud-connect-wrap { @@ -237,6 +290,7 @@ td.column-download { gap: 5px; } + .cloud-table > tbody > tr { block-size: 80px; box-shadow: inset 0 -1px 0 rgb(0 0 0 / 10%); @@ -264,6 +318,7 @@ td.column-download { background-color: #ce0000; border-radius: 50%; + .cloud-connect-active & { background-color: #25a349; } @@ -282,33 +337,16 @@ td.column-download { block-size: 1.25rem; /* 20px */ transform-origin: bottom left; - [dir="rtl"] & { - transform-origin: bottom right; - } - &:hover { stroke: #059669; fill: #6ee7b7; } } -.voted-info { - display: inline-flex; - gap: 3px; - align-items: center; - margin-block-end: 6px !important; - - &:hover { - .thumbs-up { - stroke: #059669; - fill: #6ee7b7; - animation: thumb 1s ease-in-out infinite; - } - } -} - .plugin-card-bottom { overflow: visible !important; + display: flex; + align-items: center; } .beta-test-notice { @@ -327,15 +365,15 @@ td.column-download { } 33% { - transform: rotate(calc(7deg * var(--cs-direction-multiplier))); + transform: rotate(7deg) } 66% { - transform: rotate(calc(-15deg * var(--cs-direction-multiplier))); + transform: rotate(-15deg) } 90% { - transform: rotate(calc(5deg * var(--cs-direction-multiplier))); + transform: rotate(5deg) } 100% { From c6f7141e38ef786c0fba7d04465e1b7d44e1314c Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 20:41:42 +0200 Subject: [PATCH 10/16] fix: improve pagination and snippet fetching in cloud search list table --- .../cloud/class-cloud-search-list-table.php | 161 +++++++++--------- src/php/cloud/list-table-shared-ops.php | 58 +++++-- src/php/front-end/class-front-end.php | 4 +- 3 files changed, 120 insertions(+), 103 deletions(-) diff --git a/src/php/cloud/class-cloud-search-list-table.php b/src/php/cloud/class-cloud-search-list-table.php index fdc70425..8760c330 100644 --- a/src/php/cloud/class-cloud-search-list-table.php +++ b/src/php/cloud/class-cloud-search-list-table.php @@ -68,14 +68,22 @@ public function __construct() { * @return void */ public function prepare_items() { - $this->cloud_snippets = $this->fetch_snippets(); + $per_page = $this->get_items_per_page( 'snippets_per_page', 10 ); + $user_per_page = (int) get_user_option( 'snippets_per_page', get_current_user_id() ); + if ( $user_per_page > 0 ) { + $per_page = $user_per_page; + } + + // Fetch snippets, passing a 0-based page index to the Cloud API (WP list tables are 1-based). + $page_index = max( 0, $this->get_pagenum() - 1 ); + $this->cloud_snippets = $this->fetch_snippets( $per_page, $page_index ); $this->items = $this->cloud_snippets->snippets; $this->process_actions(); $this->set_pagination_args( [ - 'per_page' => count( $this->cloud_snippets->snippets ), + 'per_page' => $per_page, 'total_items' => $this->cloud_snippets->total_snippets, 'total_pages' => $this->cloud_snippets->total_pages, ] @@ -94,13 +102,14 @@ public function process_actions() { // Check request is coming form the cloud search page. if ( isset( $_REQUEST['type'] ) && 'cloud_search' === $_REQUEST['type'] ) { - if ( isset( $_REQUEST['action'], $_REQUEST['snippet'], $_REQUEST['source'] ) ) { - cloud_lts_process_download_action( - sanitize_key( wp_unslash( $_REQUEST['action'] ) ), - sanitize_key( wp_unslash( $_REQUEST['source'] ) ), - sanitize_key( wp_unslash( $_REQUEST['snippet'] ) ) - ); - } + if ( isset( $_REQUEST['action'], $_REQUEST['snippet'], $_REQUEST['source'] ) ) { + cloud_lts_process_download_action( + sanitize_key( wp_unslash( $_REQUEST['action'] ) ), + sanitize_key( wp_unslash( $_REQUEST['source'] ) ), + sanitize_key( wp_unslash( $_REQUEST['snippet'] ) ), + $this->get_pagenum() + ); + } } } @@ -126,15 +135,25 @@ public function display_rows() { */ foreach ( $this->items as $item ) { ?> -
+
-
+ - -
+

process_description( $item->description ) ); ?>

@@ -186,64 +196,42 @@ public function display_rows() {

-
- - - -
get_pagenum() - 1 ); + // Pass the provided 0-based page index to the API. + return Cloud_API::fetch_search_results( $search_by, $search_query, $page_index ); } // If no search results, then return empty object. @@ -331,23 +320,25 @@ public function display() { * @return void */ protected function pagination( $which ) { - $total_items = $this->_pagination_args['total_items']; - $total_pages = $this->_pagination_args['total_pages']; - $pagenum = $this->get_pagenum(); + if ( empty( $this->_pagination_args ) ) { + return; + } - if ( 'top' === $which && $total_pages > 1 ) { + $total_items = $this->_pagination_args['total_items'] ?? 0; + $total_pages = $this->_pagination_args['total_pages'] ?? 0; + // get_pagenum already returns a 1-based page number used for display. + $pagenum_display = $this->get_pagenum(); + + if ( 'top' === $which && $total_pages >= 1 ) { $this->screen->render_screen_reader_content( 'heading_pagination' ); } - $paginate = cloud_lts_pagination( $which, 'search', $total_items, $total_pages, $pagenum ); + $paginate = cloud_lts_pagination( $which, 'search', $total_items, $total_pages, $pagenum_display ); $page_class = $paginate['page_class']; $output = $paginate['output']; $this->_pagination = "
$output
"; - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $this->_pagination; - - // echo wp_kses_post( $this->_pagination ); TODO: This removes the top input box for page number. } } diff --git a/src/php/cloud/list-table-shared-ops.php b/src/php/cloud/list-table-shared-ops.php index 05bba4bc..7126f28e 100644 --- a/src/php/cloud/list-table-shared-ops.php +++ b/src/php/cloud/list-table-shared-ops.php @@ -27,6 +27,24 @@ function cloud_lts_display_column_hidden_input( string $column_name, Cloud_Snipp ); } +/** + * Display a hidden input field for a certain column and snippet value. + * + * @param string $column_name Column name. + * @param Cloud_Snippet $snippet Column item. + * + * @return string HTML + */ +function cloud_lts_build_column_hidden_input( string $column_name, Cloud_Snippet $snippet ): string { + return sprintf( + '', + esc_attr( $column_name ), + esc_attr( $snippet->id ), + esc_attr( $column_name ), + esc_attr( $snippet->$column_name ) + ); +} + /** * Process the download snippet action * @@ -57,9 +75,9 @@ function cloud_lts_process_download_action( string $action, string $source, stri * @param Cloud_Snippet $cloud_snippet Snippet/Column item. * @param string $source Source - 'search' or 'codevault'. * - * @return void + * @return string Action link HTML. */ -function cloud_lts_render_action_buttons( Cloud_Snippet $cloud_snippet, string $source ) { +function cloud_lts_build_action_links( Cloud_Snippet $cloud_snippet, string $source ): string { $lang = Cloud_API::get_type_from_scope( $cloud_snippet->scope ); $link = code_snippets()->cloud_api->get_link_for_cloud_snippet( $cloud_snippet ); $is_licensed = code_snippets()->licensing->is_licensed(); @@ -74,38 +92,41 @@ function cloud_lts_render_action_buttons( Cloud_Snippet $cloud_snippet, string $ 'source' => $source, ] ); - printf( + return sprintf( '
  • %s
  • ', esc_url( $update_url ), esc_html__( 'Update Available', 'code-snippets' ) ); } else { - printf( + return sprintf( '
  • %s
  • ', esc_url( code_snippets()->get_snippet_edit_url( $link->local_id ) ), esc_html__( 'View', 'code-snippets' ) ); } - - return; } if ( $download ) { - $download_url = add_query_arg( - [ + $download_query = [ 'action' => 'download', 'snippet' => $cloud_snippet->id, 'source' => $source, - ] - ); + ]; - printf( + // Preserve current cloud page if present so downstream handlers receive pagination context. + if ( isset( $_REQUEST['cloud_page'] ) ) { + $download_query['cloud_page'] = (int) wp_unslash( $_REQUEST['cloud_page'] ); + } + + $download_url = add_query_arg( $download_query ); + + $download_button = sprintf( '
  • %s
  • ', esc_url( $download_url ), esc_html__( 'Download', 'code-snippets' ) ); } else { - printf( + $download_button = sprintf( '
  • %s %s
  • ', 'button button-primary button-disabled tooltip tooltip-block tooltip-end', esc_html__( 'Download', 'code-snippets' ), @@ -113,15 +134,17 @@ function cloud_lts_render_action_buttons( Cloud_Snippet $cloud_snippet, string $ ); } - printf( + $preview_button = sprintf( '
  • %s
  • ', '#TB_inline?&width=700&height=500&inlineId=show-code-preview', esc_attr( $cloud_snippet->name ), - 'cloud-snippet-preview thickbox', + 'cloud-snippet-preview thickbox button', esc_attr( $cloud_snippet->id ), esc_attr( $lang ), esc_html__( 'Preview', 'code-snippets' ) ); + + return $download_button . $preview_button; } /** @@ -140,7 +163,8 @@ function cloud_lts_pagination( string $which, string $source, int $total_items, $num = sprintf( _n( '%s item', '%s items', $total_items, 'code-snippets' ), number_format_i18n( $total_items ) ); $output = '' . $num . ''; - $current = isset( $_REQUEST['cloud_page'] ) ? (int) $_REQUEST['cloud_page'] : $pagenum; + $param_key = $source . '_page'; + $current = isset( $_REQUEST[ $param_key ] ) ? (int) $_REQUEST[ $param_key ] : $pagenum; $current_url = remove_query_arg( wp_removable_query_args() ) . '#' . $source; $page_links = array(); @@ -234,8 +258,10 @@ function cloud_lts_pagination( string $which, string $source, int $total_items, $output .= "\n" . implode( "\n", $page_links ) . ''; + $page_class = $total_pages ? '' : ' no-pages'; + return [ 'output' => $output, - 'page_class' => $total_pages ? ( $total_pages < 2 ? ' one-page' : '' ) : ' no-pages', + 'page_class' => $page_class, ]; } diff --git a/src/php/front-end/class-front-end.php b/src/php/front-end/class-front-end.php index 72003164..cc33a3b4 100644 --- a/src/php/front-end/class-front-end.php +++ b/src/php/front-end/class-front-end.php @@ -274,7 +274,7 @@ private function evaluate_shortcode_from_db( Snippet $snippet, array $atts ): st * * @phpcs:disable WordPress.PHP.DontExtract.extract_extract */ - extract( $atts ); + extract( $atts, EXTR_SKIP ); ob_start(); eval( "?>\n\n" . $snippet->code ); @@ -292,7 +292,7 @@ private function evaluate_shortcode_from_flat_file( $filepath, array $atts ): st * * @phpcs:disable WordPress.PHP.DontExtract.extract_extract */ - extract( $atts ); + extract( $atts, EXTR_SKIP ); require_once $filepath; } )( $atts ); From 050ba7d88aaeb224bc581a66ace31b95ace91a1d Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 20:41:53 +0200 Subject: [PATCH 11/16] fix: normalize cloud API payloads in Cloud_Snippets constructor --- src/php/cloud/class-cloud-snippets.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/php/cloud/class-cloud-snippets.php b/src/php/cloud/class-cloud-snippets.php index 5805e832..43d62ce9 100644 --- a/src/php/cloud/class-cloud-snippets.php +++ b/src/php/cloud/class-cloud-snippets.php @@ -25,6 +25,7 @@ class Cloud_Snippets extends Data_Item { * @param array $initial_data Initial data. */ public function __construct( $initial_data = null ) { + $initial_data = $this->normalize_cloud_api( $initial_data ); parent::__construct( [ 'snippets' => [], @@ -80,4 +81,27 @@ protected function prepare_snippets( $snippets ): array { return $result; } + + /** + * Normalize payloads returned by the cloud API into the shape expected by this class. + * + * @param mixed $initial_data Raw data passed into the constructor. + * + * @return mixed Normalized data array or original value when no normalization is required. + */ + private function normalize_cloud_api( $initial_data ) { + // pagination metadata is nested under a 'meta' key. + if ( is_array( $initial_data ) && isset( $initial_data['meta'] ) ) { + $meta = $initial_data['meta']; + $normalized = []; + $normalized['snippets'] = $initial_data['snippets'] ?? $initial_data['data'] ?? []; + $normalized['total_snippets'] = isset( $meta['total'] ) ? (int) $meta['total'] : 0; + $normalized['total_pages'] = isset( $meta['total_pages'] ) ? (int) $meta['total_pages'] : 0; + $normalized['page'] = isset( $meta['page'] ) ? max( 0, (int) $meta['page'] - 1 ) : 0; + $normalized['cloud_id_rev'] = $initial_data['cloud_id_rev'] ?? []; + $initial_data = $normalized; + } + + return $initial_data; + } } From 18276f7e6f84a806b67bbfd806f76d9d72d92745 Mon Sep 17 00:00:00 2001 From: Imants Date: Sun, 16 Nov 2025 20:42:01 +0200 Subject: [PATCH 12/16] fix: update variable name for clarity in fetch_search_results method --- src/php/cloud/class-cloud-api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/php/cloud/class-cloud-api.php b/src/php/cloud/class-cloud-api.php index 7faef703..788ce8ca 100644 --- a/src/php/cloud/class-cloud-api.php +++ b/src/php/cloud/class-cloud-api.php @@ -223,9 +223,9 @@ public static function fetch_search_results( string $search_method, string $sear self::get_cloud_api_url() . 'public/search' ); - $results = self::unpack_request_json( wp_remote_get( $api_url ) ); + $raw = self::unpack_request_json( wp_remote_get( $api_url ) ); - $results = new Cloud_Snippets( $results ); + $results = new Cloud_Snippets( $raw ); $results->page = $page; return $results; From fc13b6d0ab916315af9ed7cc7a124818ff7819d4 Mon Sep 17 00:00:00 2001 From: Imants Date: Mon, 17 Nov 2025 00:48:07 +0200 Subject: [PATCH 13/16] fix: adjust layout for column names and action buttons in cloud snippets --- src/css/manage/_cloud.scss | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/css/manage/_cloud.scss b/src/css/manage/_cloud.scss index 8a26dccf..6c8330b4 100644 --- a/src/css/manage/_cloud.scss +++ b/src/css/manage/_cloud.scss @@ -226,6 +226,7 @@ td.column-name { .column-name { display: flex; + justify-content: space-between; h3 { display: inline-flex; @@ -259,6 +260,7 @@ td.column-name { .action-buttons { margin: 0; + align-items: flex-end; .button { inline-size: 100%; @@ -267,7 +269,7 @@ td.column-name { } } -.cloud-snippets #the-list{ +.cloud-snippets #the-list { .column-download { display: flex; flex-flow: column; @@ -290,7 +292,6 @@ td.column-name { gap: 5px; } - .cloud-table > tbody > tr { block-size: 80px; box-shadow: inset 0 -1px 0 rgb(0 0 0 / 10%); @@ -361,22 +362,22 @@ td.column-name { @keyframes thumb { 0% { - transform: rotate(0) + transform: rotate(0); } 33% { - transform: rotate(7deg) + transform: rotate(7deg); } 66% { - transform: rotate(-15deg) + transform: rotate(-15deg); } 90% { - transform: rotate(5deg) + transform: rotate(5deg); } 100% { - transform: rotate(0) + transform: rotate(0); } } From fce3d0683bde1f00d0353ceb02d089f69a98a1fc Mon Sep 17 00:00:00 2001 From: Imants Date: Mon, 17 Nov 2025 00:49:51 +0200 Subject: [PATCH 14/16] fix: standardize indentation --- src/css/common/_badges.scss | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/css/common/_badges.scss b/src/css/common/_badges.scss index 4cfbe15f..9d842b45 100644 --- a/src/css/common/_badges.scss +++ b/src/css/common/_badges.scss @@ -20,12 +20,12 @@ gap: 5px; line-height: 1; - @at-root .row-actions & { - color: #8c8c8c; - padding-inline: 0px; - text-transform: capitalize; - font-weight: 500; - } + @at-root .row-actions & { + color: #8c8c8c; + padding-inline: 0; + text-transform: capitalize; + font-weight: 500; + } .dashicons { font-size: 18px; @@ -37,7 +37,7 @@ .network-shared { color: #2271b1; font-size: 22px; - width: 100%; + inline-size: 100%; cursor: help; } @@ -89,9 +89,9 @@ background-color: #a7aaad; border-color: #fff !important; - .dashicons { - color: #fff; - } + .dashicons { + color: #fff; + } } .nav-tab-inactive { From 5c803d619c59081c8bcdec85374fd101109efe25 Mon Sep 17 00:00:00 2001 From: Imants Date: Mon, 17 Nov 2025 00:49:58 +0200 Subject: [PATCH 15/16] fix: standardize indentation --- src/css/manage/_cloud.scss | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/css/manage/_cloud.scss b/src/css/manage/_cloud.scss index 6c8330b4..a24a6a8b 100644 --- a/src/css/manage/_cloud.scss +++ b/src/css/manage/_cloud.scss @@ -226,7 +226,7 @@ td.column-name { .column-name { display: flex; - justify-content: space-between; + justify-content: space-between; h3 { display: inline-flex; @@ -260,7 +260,7 @@ td.column-name { .action-buttons { margin: 0; - align-items: flex-end; + align-items: flex-end; .button { inline-size: 100%; @@ -270,15 +270,15 @@ td.column-name { } .cloud-snippets #the-list { - .column-download { - display: flex; - flex-flow: column; - text-align: right; + .column-download { + display: flex; + flex-flow: column; + text-align: end; - li { - list-style: none; - } - } + li { + list-style: none; + } + } } .cloud-connect-wrap { From bb303238fa287797e3cbf1ba5f1503aad343fa29 Mon Sep 17 00:00:00 2001 From: Imants Date: Mon, 17 Nov 2025 00:50:15 +0200 Subject: [PATCH 16/16] fix: correct typo in comment and sanitize pagination output --- src/php/cloud/class-cloud-search-list-table.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/php/cloud/class-cloud-search-list-table.php b/src/php/cloud/class-cloud-search-list-table.php index 8760c330..af1051c7 100644 --- a/src/php/cloud/class-cloud-search-list-table.php +++ b/src/php/cloud/class-cloud-search-list-table.php @@ -100,14 +100,13 @@ public function process_actions() { [ 'action', 'snippet', '_wpnonce', 'source', 'cloud-bundle-run', 'cloud-bundle-show', 'bundle_share_name', 'cloud_bundles' ] ); - // Check request is coming form the cloud search page. + // Check request is coming from the cloud search page. if ( isset( $_REQUEST['type'] ) && 'cloud_search' === $_REQUEST['type'] ) { if ( isset( $_REQUEST['action'], $_REQUEST['snippet'], $_REQUEST['source'] ) ) { cloud_lts_process_download_action( sanitize_key( wp_unslash( $_REQUEST['action'] ) ), sanitize_key( wp_unslash( $_REQUEST['source'] ) ), sanitize_key( wp_unslash( $_REQUEST['snippet'] ) ), - $this->get_pagenum() ); } } @@ -339,6 +338,6 @@ protected function pagination( $which ) { $this->_pagination = "
    $output
    "; - echo $this->_pagination; + echo wp_kses_post( $this->_pagination ); } }