Skip to content

Commit 11ec528

Browse files
committed
Fix path traversal in get_color_scheme_css via whitelist validation
Validate the color-scheme option against the known $color_schemes array before using it to construct a filesystem path, preventing a crafted DB value from traversing outside the plugin assets directory. Also updates CLAUDE.md with current architecture documentation and removes /includes/build/ from .gitignore.
1 parent 1831ba0 commit 11ec528

3 files changed

Lines changed: 158 additions & 64 deletions

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/vendor/
22
/node_modules/
3-
/includes/build/
43
/phpcompat-tools/
54

65
# Lock files

CLAUDE.md

Lines changed: 154 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,187 @@
11
# CLAUDE.md
22

3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3+
WordPress plugin that extends the native Gutenberg `core/code` block with Prism.js syntax highlighting. The editor integration is implemented by replacing the `core/code` block edit/save behaviour via a JS block filter, while frontend output is normalized and enhanced through the `render_block_core/code` PHP filter.
44

55
## Commands
66

77
```bash
8-
npm install # Install dependencies (includes prismjs + prism-themes)
9-
npm run build # Production build → build/ (compiles index.js + frontend.js)
10-
npm run build:assets # Copy Prism theme CSS from node_modules → assets/
11-
npm run start # Development watch mode
12-
npm run lint:js # Lint JS source
13-
npm run lint:css # Lint CSS/SCSS source
14-
npm run format # Auto-format source files
15-
npm run zip # Build distributable zip
16-
```
17-
18-
First-time setup requires both: `npm install && npm run build && npm run build:assets`
19-
20-
PHP linting (requires phpcs + WordPress Coding Standards):
21-
```bash
8+
# PHP
229
composer install
23-
vendor/bin/phpcs
10+
composer test
11+
composer phpcs
12+
composer phpstan
13+
14+
# JS
15+
npm install
16+
npm run build # Production build -> includes/blocks/build/
17+
npm run build:prism # Copy Prism themes -> includes/assets/
18+
npm run build:assets # Minify generated CSS/JS assets
19+
npm run start # Watch mode for block/editor/frontend bundles
20+
npm run zip # Plugin zip
2421
```
2522

23+
## Current feature scope
24+
25+
The current implementation follows the active plan in `PLAN.md`, not the broader experimental ideas in `OLD-FEATURE-PLAN.md`.
26+
27+
- Extends `core/code` with per-block attributes:
28+
- `language`
29+
- `lineNumbers`
30+
- `lineNumbersStart`
31+
- `wordWrap`
32+
- `title`
33+
- `highlightLines` — maps to `data-line` on `<pre>`, consumed by Prism line-highlight plugin
34+
- `maxHeight` — inline `style="max-height:{n}px;overflow-y:auto"` on `<pre>` (CSS only, no Prism)
35+
- Adds Inspector Controls for:
36+
- language selection
37+
- file name/title
38+
- line numbers toggle + start line
39+
- word wrap
40+
- highlight lines (e.g. `1,3-5`)
41+
- max height in px
42+
- save current settings as defaults
43+
- Saves defaults through the REST route `wz-cbh/v1/default-settings`
44+
- Frontend integrates Prism plugins for:
45+
- line numbers
46+
- line highlight
47+
- toolbar
48+
- show language
49+
- copy to clipboard
50+
- Registers a custom Prism toolbar label that reads from `data-title`
51+
- Registers a `wz-cbh-expand` Prism toolbar button that appears when `max-height` is set; toggles the block between collapsed (original inline style) and expanded (inline style cleared), updating `aria-expanded` on each toggle
52+
- Supports global settings for:
53+
- color scheme
54+
- copy to clipboard
55+
- show language label
56+
- default language
57+
- Ships with `One Dark` as the default theme slug: `prism-onedark`
58+
59+
Do not assume features from `OLD-FEATURE-PLAN.md` exist unless they are implemented in code.
60+
2661
## Architecture
2762

28-
This is a WordPress plugin that **extends** the native Gutenberg `core/code` block with Prism.js syntax highlighting. It does not replace the block — it uses filters to avoid block validation errors on existing posts.
63+
**Namespace:** `WebberZone\Code_Block_Highlighting`
2964

30-
### PHP namespace & autoloader
65+
**Autoloader:** `includes/autoloader.php`
3166

32-
All classes live under the `WebberZone\Code_Block_Highlighting` namespace. A custom PSR-4-style autoloader (`includes/autoloader.php`) maps the namespace to `includes/`, converting class names to files:
67+
**Bootstrap flow:**
3368

34-
- `Main``includes/class-main.php`
35-
- `Admin\Settings``includes/admin/class-settings.php`
36-
- `Frontend\Blocks``includes/frontend/class-blocks.php`
37-
- `Frontend\Styles_Handler``includes/frontend/class-styles-handler.php`
69+
- Main plugin file loads the autoloader.
70+
- `wz_cbh()` resolves the `Main` singleton on `plugins_loaded`.
71+
- `Main` loads `includes/options-api.php`.
72+
- `Main` instantiates:
73+
- `Frontend\Blocks`
74+
- `Frontend\Styles_Handler`
75+
- `Admin\Admin` is created on `init` only when `is_admin()`.
3876

39-
Underscores in class names become dashes in filenames; all lowercase.
77+
**Key PHP classes:**
4078

41-
### Bootstrap flow
79+
- `includes/class-main.php` — plugin bootstrap and object wiring
80+
- `includes/frontend/class-blocks.php` — editor asset registration, REST route, `render_block_core/code`
81+
- `includes/frontend/class-styles-handler.php` — conditional frontend Prism asset loading
82+
- `includes/admin/class-settings.php` — settings registration, theme resolution, language autocomplete wiring
4283

43-
`webberzone-code-block-highlighting.php` defines constants (`WZ_CBH_VERSION`, `WZ_CBH_PLUGIN_FILE`, `WZ_CBH_PLUGIN_DIR`, `WZ_CBH_PLUGIN_URL`), loads the autoloader, then calls `Main::get_instance()` on the `plugins_loaded` action.
84+
**JS entry points:**
4485

45-
`Main` (singleton) instantiates `Frontend\Blocks` and `Frontend\Styles_Handler` immediately, and `Admin\Settings` only inside `is_admin()`.
86+
- `includes/blocks/src/js/index.js` — replaces `core/code` edit/save and adds Inspector Controls
87+
- `includes/blocks/src/js/frontend.js` — Prism core, supported grammars, toolbar/copy/show-language plugins, frontend toolbar behaviour
4688

47-
### Three-layer highlighting strategy
89+
**Build output:**
4890

49-
| Layer | Where | What it does |
50-
|---|---|---|
51-
| `blocks.registerBlockType` JS filter | Editor | Adds `language`, `lineNumbers`, `title` attributes to `core/code` |
52-
| `editor.BlockEdit` JS filter (HOC) | Editor | Adds InspectorControls panel with language picker, line number toggle, title field |
53-
| `blocks.getSaveContent.extraProps` JS filter | Editor save | Adds `language-*` and `line-numbers` classes to the serialized `<pre>` |
54-
| `render_block_core/code` PHP filter | Frontend | Injects `language-*` class onto `<code>` element in rendered HTML |
91+
- `includes/blocks/build/index.js`
92+
- `includes/blocks/build/frontend.js`
93+
- corresponding `.asset.php` manifests and extracted CSS files
5594

56-
### Attribute storage
95+
Always `require` the generated `.asset.php` file before enqueueing block scripts.
5796

58-
Selected language is stored in the block comment:
59-
```html
60-
<!-- wp:code {"language":"javascript","lineNumbers":true,"title":"index.js"} -->
61-
<pre class="wp-block-code language-javascript line-numbers" data-label="index.js">
62-
<code class="language-javascript">...</code>
63-
</pre>
64-
<!-- /wp:code -->
65-
```
97+
## Data flow
98+
99+
- Language list is provided by `Frontend\Blocks::get_languages()`
100+
- Editor globals are injected with `wp_add_inline_script()`:
101+
- `cbhLanguages`
102+
- `cbhDefaultLang`
103+
- `cbhDefaultSettings`
104+
- Frontend globals are injected with `wp_add_inline_script()`:
105+
- `cbhSettings`
106+
- Block attributes are saved in block markup and normalized again in `render_code_block()`
107+
- Default settings are stored in plugin options through the REST endpoint
108+
109+
## Asset loading
110+
111+
`Frontend\Styles_Handler::enqueue_assets()` only loads Prism on the frontend when at least one `core/code` block is present in the current queried posts.
112+
113+
Use `wz_cbh_force_load_assets` to bypass conditional loading.
114+
115+
The editor canvas styling is handled separately in `Frontend\Blocks::enqueue_editor_canvas_styles()`, which:
116+
117+
- enqueues editor CSS into the block editor iframe
118+
- extracts only `background` and `color` declarations from the active Prism theme
119+
- re-injects them with stronger selectors so the editor canvas matches the chosen frontend theme without breaking editor layout
120+
121+
## Key filters, options, and routes
122+
123+
- `wz_cbh_languages` — filter supported Prism languages (`slug => label`)
124+
- `wz_cbh_color_scheme_css_url` — filter the resolved Prism theme CSS URL
125+
- `wz_cbh_force_load_assets` — force frontend Prism assets to load
126+
- REST route: `wz-cbh/v1/default-settings`
127+
- Settings option key: `wz_cbh_settings`
128+
- Settings prefix: `wz_cbh`
129+
- Settings page slug: `wz_cbh_settings`
130+
131+
## Current option IDs
132+
133+
These option IDs are registered in `includes/admin/class-settings.php`:
134+
135+
- `color-scheme`
136+
- `copy-to-clipboard`
137+
- `show-language-label`
138+
- `show-file-name`
139+
- `default-lang`
140+
- `default-line-numbers`
141+
- `default-line-numbers-start`
142+
- `default-word-wrap`
143+
- `font-size`
144+
145+
The default color scheme is `prism-onedark`.
146+
147+
## Frontend rendering rules
66148

67-
### JS build
149+
`Frontend\Blocks::render_code_block()` currently:
68150

69-
Two entry points, both built by `wp-scripts build`:
70-
- `includes/blocks/js/index.js``build/index.js` + `build/index.asset.php` — editor block filters
71-
- `includes/frontend/js/frontend.js``build/frontend.js` + `build/frontend.css` + `build/frontend.asset.php` — Prism core, all language grammars, line-numbers plugin (CSS extracted by webpack)
151+
- adds `language-{slug}` to `<code>`
152+
- adds `line-numbers` to `<pre>` when enabled
153+
- adds `data-start` to `<pre>` when line numbering starts from a value other than `1`
154+
- adds `word-wrap` to `<pre>` when enabled
155+
- adds `data-title` to `<pre>` for the custom toolbar label
156+
- adds `data-line` to `<pre>` from `highlightLines` attribute (consumed by Prism line-highlight plugin)
157+
- `maxHeight` is CSS-only: serialized as inline `style` by the block save function, not touched by PHP
72158

73-
Always `require` the `.asset.php` manifest in PHP before calling `wp_enqueue_script` — it provides the correct dependency array and cache-busting version.
159+
If you change block attributes in JS, update the PHP rendering logic and defaults flow as well.
74160

75-
Language list is passed from PHP → JS via `wp_add_inline_script` as globals `cbhLanguages` (object) and `cbhDefaultLang` (string) before the editor script loads.
161+
## Accessibility notes
76162

77-
### Frontend asset loading
163+
The active plan targets strong accessibility support. Current frontend code already includes:
78164

79-
`Styles_Handler` uses `array_reduce` over `$posts` with `has_block( 'core/code', $post )` to only enqueue Prism assets when code blocks are present. Override with the `wz_cbh_force_load_assets` filter.
165+
- decorative toolbar language labels marked `aria-hidden`
166+
- a custom title label in the toolbar
167+
- Prism copy-to-clipboard integration controlled by plugin settings
168+
- expand/collapse button with `aria-expanded` state management
80169

81-
### Key filters & options
170+
If you extend toolbar behaviour, preserve keyboard access and screen reader behaviour.
82171

83-
- `wz_cbh_languages` — filter the language list array (`slug => label`)
84-
- `wz_cbh_color_scheme_css_url` — override the Prism theme CSS URL
85-
- `wz_cbh_force_load_assets` — force Prism to load on every page
86-
- `wz-cbh-color-scheme` WP option — active theme slug (default: `prism-a11y-dark`)
87-
- `wz-cbh-default-lang` WP option — auto-applied language on new code block inserts
172+
## Adding a Prism theme
88173

89-
### Assets (not committed, generated by npm scripts)
174+
1. Add the theme mapping in `build-prism.js`
175+
2. Ensure the generated CSS file is copied to `includes/assets/`
176+
3. Register the theme slug in `includes/admin/class-settings.php`
177+
4. Run `npm run build:prism`
90178

91-
- `build/frontend.js` — Prism core + all languages + line-numbers plugin (via `npm run build`)
92-
- `build/frontend.css` — line-numbers plugin CSS (extracted from `src/frontend.js` by webpack)
93-
- `assets/prism-*.css` — color scheme theme files copied from `prism-themes` package (via `npm run build:assets`)
179+
## Notes for future work
94180

95-
Bundled themes (defined in `build-assets.js`): `prism-a11y-dark`, `prism-atom-dark`, `prism-darcula`, `prism-dracula`, `prism-ghcolors`, `prism-gruvbox-dark`, `prism-gruvbox-light`, `prism-material-dark`, `prism-material-oceanic`, `prism-night-owl`, `prism-nord`, `prism-onedark`, `prism-one-light`, `prism-shades-of-purple`, `prism-solarized-dark-atom`, `prism-synthwave84`, `prism-vs`, `prism-vsc-dark-plus`. To add a new theme, add an entry to `build-assets.js`, register it in `includes/admin/class-settings.php`, then run `npm run build:assets`.
181+
- Prefer `PLAN.md` as the source of truth for current implementation direction
182+
- Treat `OLD-FEATURE-PLAN.md` as backlog/reference only
183+
- Before adding new per-block controls, verify:
184+
- the JS attribute schema
185+
- the save output
186+
- the PHP render filter
187+
- frontend Prism plugin support

includes/admin/class-settings.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,10 @@ public function enqueue_language_data( string $hook ): void {
317317
* @return string
318318
*/
319319
public static function get_color_scheme_css( bool $return_path = false ): string {
320-
$option = wz_cbh_get_option( 'color-scheme', 'prism-onedark' );
320+
$option = wz_cbh_get_option( 'color-scheme', 'prism-onedark' );
321+
if ( ! array_key_exists( $option, self::$color_schemes ) ) {
322+
$option = 'prism-onedark';
323+
}
321324
$rel_path = "includes/assets/{$option}.css";
322325

323326
if ( ! file_exists( WZ_CBH_PLUGIN_DIR . $rel_path ) ) {

0 commit comments

Comments
 (0)