monorepo/native/wordpress/maple-fonts-wp/CLAUDE.md
2026-01-30 22:33:40 -05:00

264 lines
9.4 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md — Maple Local Fonts WordPress Plugin
## Quick Reference
| Document | When to Reference |
|----------|-------------------|
| **CLAUDE.md** (this file) | Architecture, file structure, build order |
| **SECURITY.md** | Writing ANY PHP code |
| **GOOGLE_FONTS_API.md** | Building the font downloader class |
| **WORDPRESS_COMPATIBILITY.md** | Building the font registry, FSE integration |
---
## Project Overview
Build a WordPress plugin called **Maple Local Fonts** that:
1. Imports Google Fonts to local storage (one-time download)
2. Registers them with WordPress's native Font Library API
3. Fonts appear in FSE typography dropdowns alongside theme fonts
**Key principle:** Work WITH WordPress, not around it. We're a font importer — WordPress handles everything else.
---
## Requirements
- **Minimum PHP:** 7.4
- **Minimum WordPress:** 6.5 (required for Font Library API)
- **License:** GPL-2.0-or-later
---
## User Flow
```
Admin enters "Open Sans" → selects weights (400, 700) → selects styles (normal, italic)
Plugin hits Google Fonts CSS2 API (ONE TIME)
Downloads WOFF2 files to wp-content/fonts/
Registers font via WP Font Library API
Font appears in Appearance → Editor → Styles → Typography dropdown
User applies font using standard WordPress FSE controls
GOOGLE NEVER CONTACTED AGAIN — fonts served locally
```
---
## File Structure
```
maple-local-fonts/
├── maple-local-fonts.php # Main plugin file
├── index.php # Silence is golden
├── uninstall.php # Clean removal
├── readme.txt # WordPress.org readme
├── includes/
│ ├── index.php # Silence is golden
│ ├── class-mlf-font-downloader.php
│ ├── class-mlf-font-registry.php
│ ├── class-mlf-admin-page.php
│ └── class-mlf-ajax-handler.php
├── assets/
│ ├── index.php # Silence is golden
│ ├── admin.css
│ └── admin.js
└── languages/
├── index.php # Silence is golden
└── maple-local-fonts.pot
```
---
## Class Responsibilities
### MLF_Font_Downloader
- Build Google Fonts CSS2 URL
- Fetch CSS (with correct user-agent for WOFF2)
- Parse CSS to extract font face data
- Download WOFF2 files to wp-content/fonts/
- **Reference:** GOOGLE_FONTS_API.md
### MLF_Font_Registry
- Register fonts with WP Font Library (wp_font_family, wp_font_face post types)
- Delete fonts (remove posts and files)
- List imported fonts
- **Reference:** WORDPRESS_COMPATIBILITY.md
### MLF_Admin_Page
- Render settings page under Appearance menu
- Font name input, weight checkboxes, style checkboxes
- Display installed fonts with delete buttons
- **Reference:** SECURITY.md for output escaping
### MLF_Ajax_Handler
- Handle download requests
- Handle delete requests
- **Reference:** SECURITY.md for nonce/capability checks
---
## Admin Page UI
```
┌─────────────────────────────────────────────────────────┐
│ Maple Local Fonts │
├─────────────────────────────────────────────────────────┤
│ IMPORT FROM GOOGLE FONTS │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Font Name: [Open Sans_________________________] │ │
│ │ │ │
│ │ Weights: │ │
│ │ ☑ 400 (Regular) ☐ 300 (Light) ☐ 500 (Medium) │ │
│ │ ☑ 700 (Bold) ☐ 600 (Semi) ☐ 800 (Extra) │ │
│ │ │ │
│ │ Styles: │ │
│ │ ☑ Normal ☑ Italic │ │
│ │ │ │
│ │ Files to download: 4 │ │
│ │ [Download & Install] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ INSTALLED FONTS │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Open Sans │ │
│ │ 400 normal, 400 italic, 700 normal, 700 italic │ │
│ │ [Delete] │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ Roboto │ │
│ │ 400 normal, 500 normal, 700 normal │ │
│ │ [Delete] │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
Use Appearance → Editor → Styles → Typography to │
│ apply fonts to your site. │
└─────────────────────────────────────────────────────────┘
```
---
## Build Order
### Phase 1: Foundation
1. `maple-local-fonts.php` — Plugin header, constants, autoloader, activation hook
2. `index.php` files in all directories (silence is golden)
3. `includes/class-mlf-ajax-handler.php` — With full security checks (nonce, capability, validation)
### Phase 2: Core Functionality
4. `includes/class-mlf-font-downloader.php` — Google Fonts fetching/parsing/downloading
5. `includes/class-mlf-font-registry.php` — WP Font Library integration
### Phase 3: Admin Interface
6. `includes/class-mlf-admin-page.php` — Settings page render
7. `assets/admin.css` — Admin page styles
8. `assets/admin.js` — AJAX handling for download/delete
### Phase 4: Cleanup & Polish
9. `uninstall.php` — Clean removal of fonts and data
10. `readme.txt` — WordPress.org readme with privacy section
11. Testing against all compatibility targets
---
## Critical Reminders
### Security (see SECURITY.md)
- ABSPATH check on EVERY PHP file
- index.php in EVERY directory
- Nonce verification FIRST in every AJAX handler
- Capability check SECOND
- Input validation THIRD
- Escape ALL output
### Performance
- Zero JavaScript on frontend
- Zero CSS on frontend
- All plugin code is admin-side only (except lightweight font registration)
- Timeout handling on all external requests
- Maximum limits to prevent infinite loops
### GDPR
- No user data collected
- External requests only during admin import
- Fonts served locally after import
- Document in readme.txt
### Compatibility (see WORDPRESS_COMPATIBILITY.md)
- Declare WooCommerce HPOS compatibility
- Use wp_get_font_dir() not hardcoded paths
- Use Font Library API (post types) not theme.json filtering
- Don't touch frontend, let Global Styles handle everything
---
## Constants
```php
define('MLF_VERSION', '1.0.0');
define('MLF_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('MLF_PLUGIN_URL', plugin_dir_url(__FILE__));
define('MLF_PLUGIN_BASENAME', plugin_basename(__FILE__));
// Limits
define('MLF_MAX_FONTS_PER_REQUEST', 10);
define('MLF_MAX_WEIGHTS_PER_FONT', 9);
define('MLF_REQUEST_TIMEOUT', 30);
```
---
## WordPress Hooks Used
```php
// Activation
register_activation_hook(__FILE__, 'mlf_activate');
// Admin menu
add_action('admin_menu', 'mlf_register_menu');
// Admin assets (our page only)
add_action('admin_enqueue_scripts', 'mlf_enqueue_admin_assets');
// AJAX (admin only, no nopriv)
add_action('wp_ajax_mlf_download_font', 'mlf_ajax_download');
add_action('wp_ajax_mlf_delete_font', 'mlf_ajax_delete');
// WooCommerce HPOS
add_action('before_woocommerce_init', 'mlf_declare_hpos_compatibility');
```
---
## Testing Checklist
### Functional
- [ ] Import Open Sans 400, 700 normal + italic → 4 files created
- [ ] Font appears in FSE typography dropdown
- [ ] Apply font in Global Styles → frontend shows local font
- [ ] Delete font → files and posts removed
- [ ] Re-import same font → appropriate error message
### Security
- [ ] AJAX without nonce → 403
- [ ] AJAX as subscriber → 403
- [ ] Path traversal in font name → rejected
- [ ] XSS in font name → sanitized
- [ ] Direct PHP file access → blank/exit
### Performance
- [ ] No frontend network requests from plugin
- [ ] Import 5 fonts → completes without timeout
- [ ] Admin page load < 500ms added time
### Compatibility
- [ ] WordPress 6.5, 6.6+
- [ ] Twenty Twenty-Five theme
- [ ] WooCommerce (HPOS enabled)
- [ ] Wordfence (enabled)
- [ ] LearnDash
- [ ] WPForms