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
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
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",
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 {
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/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..a24a6a8b 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,74 @@ 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;
+ justify-content: space-between;
+
+ 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;
+ align-items: flex-end;
+
+ .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: end;
+
+ li {
+ list-style: none;
+ }
+ }
}
.cloud-connect-wrap {
@@ -264,6 +319,7 @@ td.column-download {
background-color: #ce0000;
border-radius: 50%;
+
.cloud-connect-active & {
background-color: #25a349;
}
@@ -282,33 +338,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 {
@@ -323,22 +362,22 @@ td.column-download {
@keyframes thumb {
0% {
- transform: rotate(0)
+ transform: rotate(0);
}
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% {
- transform: rotate(0)
+ transform: rotate(0);
}
}
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/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)
- ]
+ ],
})
})
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
',
+ '%s
',
esc_html__( 'Loading edit pageā¦', 'code-snippets' )
);
}
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;
diff --git a/src/php/cloud/class-cloud-search-list-table.php b/src/php/cloud/class-cloud-search-list-table.php
index fdc70425..af1051c7 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,
]
@@ -92,15 +100,15 @@ 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'] ) )
- );
- }
+ 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'] ) ),
+ );
+ }
}
}
@@ -126,15 +134,25 @@ public function display_rows() {
*/
foreach ( $this->items as $item ) {
?>
-
+
-
+
tags ) > 0 ? strtolower( esc_attr( $item->tags[0] ) ) : 'general';
+
+ printf(
+ '
',
+ esc_url( "https://codesnippets.cloud/images/plugin-icons/$category-logo.png" ),
+ esc_attr( $category )
+ );
+
$link = code_snippets()->cloud_api->get_link_for_cloud_snippet( $item );
if ( $link ) {
@@ -151,25 +169,16 @@ public function display_rows() {
echo esc_html( $item->name );
- // Grab first tag in array of tags.
- $category = count( $item->tags ) > 0 ? strtolower( esc_attr( $item->tags[0] ) ) : 'general';
- printf(
- '
',
- esc_url( "https://codesnippets.cloud/images/plugin-icons/$category-logo.png" ),
- esc_attr( $category )
- );
echo '';
?>
-
-
-
+
process_description( $item->description ) ); ?>
@@ -186,64 +195,42 @@ public function display_rows() {
-
-
-
- vote_count, 'vote count', 'code-snippets' );
- $votes_text = sprintf( $votes_text, number_format_i18n( $item->vote_count ) );
+
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 +319,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.
+ echo wp_kses_post( $this->_pagination );
}
}
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;
+ }
}
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';
+ $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 );
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.
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,
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;
}