560 lines
15 KiB
Markdown
560 lines
15 KiB
Markdown
# WORDPRESS_COMPATIBILITY.md — WordPress & Plugin Compatibility
|
|
|
|
## Overview
|
|
|
|
This document covers compatibility requirements for WordPress core systems and popular plugins. Reference this when building the font registry class and integration points.
|
|
|
|
---
|
|
|
|
## WordPress Version Requirements
|
|
|
|
**Minimum: WordPress 6.5**
|
|
|
|
WordPress 6.5 introduced the Font Library API which this plugin depends on. Earlier versions will not work.
|
|
|
|
```php
|
|
// Check on activation
|
|
register_activation_hook(__FILE__, function() {
|
|
if (version_compare(get_bloginfo('version'), '6.5', '<')) {
|
|
deactivate_plugins(plugin_basename(__FILE__));
|
|
wp_die(
|
|
'Maple Local Fonts requires WordPress 6.5 or higher for Font Library support.',
|
|
'Plugin Activation Error',
|
|
['back_link' => true]
|
|
);
|
|
}
|
|
});
|
|
|
|
// Also check on admin init (in case WP was downgraded)
|
|
add_action('admin_init', function() {
|
|
if (version_compare(get_bloginfo('version'), '6.5', '<')) {
|
|
deactivate_plugins(plugin_basename(__FILE__));
|
|
add_action('admin_notices', function() {
|
|
echo '<div class="error"><p>Maple Local Fonts has been deactivated. It requires WordPress 6.5 or higher.</p></div>';
|
|
});
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## WordPress Font Library API
|
|
|
|
### How It Works
|
|
|
|
WordPress 6.5+ stores fonts using custom post types:
|
|
|
|
| Post Type | Purpose |
|
|
|-----------|---------|
|
|
| `wp_font_family` | Font family (e.g., "Open Sans") |
|
|
| `wp_font_face` | Individual weight/style variant (child of family) |
|
|
|
|
Fonts are stored in `wp-content/fonts/` by default.
|
|
|
|
### Getting the Fonts Directory
|
|
|
|
```php
|
|
// ALWAYS use this function, never hardcode paths
|
|
$font_dir = wp_get_font_dir();
|
|
|
|
// Returns:
|
|
[
|
|
'path' => '/var/www/html/wp-content/fonts',
|
|
'url' => 'https://example.com/wp-content/fonts',
|
|
'subdir' => '',
|
|
'basedir' => '/var/www/html/wp-content/fonts',
|
|
'baseurl' => 'https://example.com/wp-content/fonts',
|
|
]
|
|
```
|
|
|
|
### Registering a Font Family
|
|
|
|
```php
|
|
/**
|
|
* Register a font family with WordPress Font Library.
|
|
*
|
|
* @param string $font_name Display name (e.g., "Open Sans")
|
|
* @param string $font_slug Slug (e.g., "open-sans")
|
|
* @param array $files Array of downloaded file data
|
|
* @return int|WP_Error Font family post ID or error
|
|
*/
|
|
function mlf_register_font_family($font_name, $font_slug, $files) {
|
|
// Check if font already exists
|
|
$existing = get_posts([
|
|
'post_type' => 'wp_font_family',
|
|
'name' => $font_slug,
|
|
'posts_per_page' => 1,
|
|
'post_status' => 'publish',
|
|
]);
|
|
|
|
if (!empty($existing)) {
|
|
return new WP_Error('font_exists', 'Font family already installed');
|
|
}
|
|
|
|
// Build font family settings
|
|
$font_family_settings = [
|
|
'name' => $font_name,
|
|
'slug' => $font_slug,
|
|
'fontFamily' => sprintf('"%s", sans-serif', $font_name),
|
|
'fontFace' => [],
|
|
];
|
|
|
|
// Add each font face
|
|
foreach ($files as $file) {
|
|
$font_dir = wp_get_font_dir();
|
|
$relative_path = str_replace($font_dir['path'], '', $file['path']);
|
|
|
|
$font_family_settings['fontFace'][] = [
|
|
'fontFamily' => $font_name,
|
|
'fontWeight' => $file['weight'],
|
|
'fontStyle' => $file['style'],
|
|
'src' => 'file:.' . $font_dir['basedir'] . $relative_path,
|
|
];
|
|
}
|
|
|
|
// Create font family post
|
|
$family_id = wp_insert_post([
|
|
'post_type' => 'wp_font_family',
|
|
'post_title' => $font_name,
|
|
'post_name' => $font_slug,
|
|
'post_status' => 'publish',
|
|
'post_content' => wp_json_encode($font_family_settings),
|
|
]);
|
|
|
|
if (is_wp_error($family_id)) {
|
|
return $family_id;
|
|
}
|
|
|
|
// Mark as imported by our plugin (for identification)
|
|
update_post_meta($family_id, '_mlf_imported', '1');
|
|
update_post_meta($family_id, '_mlf_import_date', current_time('mysql'));
|
|
|
|
// Create font face posts (children)
|
|
foreach ($files as $file) {
|
|
$font_dir = wp_get_font_dir();
|
|
|
|
$face_settings = [
|
|
'fontFamily' => $font_name,
|
|
'fontWeight' => $file['weight'],
|
|
'fontStyle' => $file['style'],
|
|
'src' => 'file:.' . $font_dir['baseurl'] . '/' . basename($file['path']),
|
|
];
|
|
|
|
wp_insert_post([
|
|
'post_type' => 'wp_font_face',
|
|
'post_parent' => $family_id,
|
|
'post_status' => 'publish',
|
|
'post_content' => wp_json_encode($face_settings),
|
|
]);
|
|
}
|
|
|
|
// Clear font caches
|
|
delete_transient('wp_font_library_fonts');
|
|
|
|
return $family_id;
|
|
}
|
|
```
|
|
|
|
### Deleting a Font Family
|
|
|
|
```php
|
|
/**
|
|
* Delete a font family and its files.
|
|
*
|
|
* @param int $family_id Font family post ID
|
|
* @return bool|WP_Error True on success, error on failure
|
|
*/
|
|
function mlf_delete_font_family($family_id) {
|
|
$family = get_post($family_id);
|
|
|
|
if (!$family || $family->post_type !== 'wp_font_family') {
|
|
return new WP_Error('not_found', 'Font family not found');
|
|
}
|
|
|
|
// Verify it's one we imported
|
|
if (get_post_meta($family_id, '_mlf_imported', true) !== '1') {
|
|
return new WP_Error('not_ours', 'Cannot delete fonts not imported by this plugin');
|
|
}
|
|
|
|
// Get font faces
|
|
$faces = get_children([
|
|
'post_parent' => $family_id,
|
|
'post_type' => 'wp_font_face',
|
|
]);
|
|
|
|
$font_dir = wp_get_font_dir();
|
|
|
|
// Delete font face files and posts
|
|
foreach ($faces as $face) {
|
|
$settings = json_decode($face->post_content, true);
|
|
|
|
if (isset($settings['src'])) {
|
|
// Convert file:. URL to path
|
|
$src = $settings['src'];
|
|
$src = str_replace('file:.', '', $src);
|
|
|
|
// Handle both URL and path formats
|
|
if (strpos($src, $font_dir['baseurl']) !== false) {
|
|
$file_path = str_replace($font_dir['baseurl'], $font_dir['path'], $src);
|
|
} else {
|
|
$file_path = $font_dir['path'] . '/' . basename($src);
|
|
}
|
|
|
|
// Validate path before deletion
|
|
if (mlf_validate_font_path($file_path) && file_exists($file_path)) {
|
|
wp_delete_file($file_path);
|
|
}
|
|
}
|
|
|
|
wp_delete_post($face->ID, true);
|
|
}
|
|
|
|
// Delete family post
|
|
wp_delete_post($family_id, true);
|
|
|
|
// Clear caches
|
|
delete_transient('wp_font_library_fonts');
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Listing Installed Fonts
|
|
|
|
```php
|
|
/**
|
|
* Get all fonts imported by this plugin.
|
|
*
|
|
* @return array Array of font data
|
|
*/
|
|
function mlf_get_imported_fonts() {
|
|
$fonts = get_posts([
|
|
'post_type' => 'wp_font_family',
|
|
'posts_per_page' => 100,
|
|
'post_status' => 'publish',
|
|
'meta_key' => '_mlf_imported',
|
|
'meta_value' => '1',
|
|
]);
|
|
|
|
$result = [];
|
|
|
|
foreach ($fonts as $font) {
|
|
$settings = json_decode($font->post_content, true);
|
|
|
|
// Get variants
|
|
$faces = get_children([
|
|
'post_parent' => $font->ID,
|
|
'post_type' => 'wp_font_face',
|
|
]);
|
|
|
|
$variants = [];
|
|
foreach ($faces as $face) {
|
|
$face_settings = json_decode($face->post_content, true);
|
|
$variants[] = [
|
|
'weight' => $face_settings['fontWeight'] ?? '400',
|
|
'style' => $face_settings['fontStyle'] ?? 'normal',
|
|
];
|
|
}
|
|
|
|
$result[] = [
|
|
'id' => $font->ID,
|
|
'name' => $settings['name'] ?? $font->post_title,
|
|
'slug' => $settings['slug'] ?? $font->post_name,
|
|
'variants' => $variants,
|
|
'import_date' => get_post_meta($font->ID, '_mlf_import_date', true),
|
|
];
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Gutenberg FSE Integration
|
|
|
|
### How Fonts Appear in the Editor
|
|
|
|
Once registered via the Font Library API, fonts automatically appear in:
|
|
|
|
1. **Global Styles** → Typography → Font dropdown
|
|
2. **Block settings** → Typography → Font dropdown (when per-block typography is enabled)
|
|
|
|
No additional integration code is needed — WordPress handles this automatically.
|
|
|
|
### Theme.json Compatibility
|
|
|
|
**DO NOT:**
|
|
- Directly modify theme.json
|
|
- Filter `wp_theme_json_data_theme` to inject fonts (let Font Library handle it)
|
|
- Override global styles CSS directly
|
|
|
|
**DO:**
|
|
- Use the Font Library API (post types)
|
|
- Let WordPress generate CSS custom properties
|
|
- Trust the system
|
|
|
|
### CSS Custom Properties
|
|
|
|
When a font is applied in Global Styles, WordPress generates:
|
|
|
|
```css
|
|
body {
|
|
--wp--preset--font-family--open-sans: "Open Sans", sans-serif;
|
|
}
|
|
```
|
|
|
|
And applies it:
|
|
|
|
```css
|
|
body {
|
|
font-family: var(--wp--preset--font-family--open-sans);
|
|
}
|
|
```
|
|
|
|
Our plugin doesn't need to touch this — it's automatic.
|
|
|
|
---
|
|
|
|
## WooCommerce Compatibility
|
|
|
|
### HPOS (High-Performance Order Storage)
|
|
|
|
WooCommerce's HPOS moves order data from post meta to custom tables. We must declare compatibility.
|
|
|
|
```php
|
|
// Declare HPOS compatibility
|
|
add_action('before_woocommerce_init', function() {
|
|
if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
|
|
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
|
|
'custom_order_tables',
|
|
__FILE__,
|
|
true
|
|
);
|
|
}
|
|
});
|
|
```
|
|
|
|
### Why We're Compatible
|
|
|
|
Our plugin:
|
|
- Does NOT interact with orders at all
|
|
- Does NOT query wp_posts for order data
|
|
- Does NOT use wp_postmeta for order data
|
|
- Only uses wp_font_family and wp_font_face post types
|
|
|
|
We're inherently compatible because we don't touch WooCommerce data.
|
|
|
|
### Frontend Considerations
|
|
|
|
**DO NOT:**
|
|
- Override `.woocommerce` class styles
|
|
- Override `.wc-block-*` styles
|
|
- Target cart/checkout elements specifically
|
|
|
|
**DO:**
|
|
- Let WooCommerce elements inherit from body/heading fonts
|
|
- Let global styles cascade naturally
|
|
|
|
WooCommerce product titles, descriptions, and other text will naturally inherit the fonts set via Global Styles. No special handling needed.
|
|
|
|
---
|
|
|
|
## Wordfence Compatibility
|
|
|
|
### Potential Concerns
|
|
|
|
1. **Outbound requests** to Google Fonts during import
|
|
2. **AJAX endpoints** for admin actions
|
|
3. **File operations** in wp-content
|
|
|
|
### Why We're Compatible
|
|
|
|
**Outbound Requests:**
|
|
- Only occur during admin import (user-initiated action)
|
|
- Target well-known domains (fonts.googleapis.com, fonts.gstatic.com)
|
|
- Use standard `wp_remote_get()` which Wordfence allows
|
|
- No runtime external requests on frontend
|
|
|
|
**AJAX Endpoints:**
|
|
- Use standard `admin-ajax.php` (not custom endpoints)
|
|
- Include proper nonces
|
|
- Follow WordPress patterns that Wordfence expects
|
|
|
|
**File Operations:**
|
|
- Write only to `wp-content/fonts/` (WordPress default directory)
|
|
- Use WordPress Filesystem API
|
|
- Don't create executable files
|
|
|
|
### Testing with Wordfence
|
|
|
|
Test these scenarios with Wordfence active:
|
|
|
|
- [ ] Learning Mode: Import should succeed
|
|
- [ ] Enabled Mode: Import should succeed
|
|
- [ ] Rate Limiting: Admin AJAX not blocked
|
|
- [ ] Firewall: No false positives on font download
|
|
|
|
---
|
|
|
|
## LearnDash Compatibility
|
|
|
|
### Overview
|
|
|
|
LearnDash is a WordPress LMS that uses:
|
|
- Custom post types (courses, lessons, topics, quizzes)
|
|
- Custom templates
|
|
- Focus Mode (distraction-free learning)
|
|
|
|
### Why We're Compatible
|
|
|
|
Our plugin:
|
|
- Doesn't touch LearnDash post types
|
|
- Doesn't modify LearnDash templates
|
|
- Doesn't inject CSS on frontend
|
|
- Lets Global Styles cascade to LearnDash content
|
|
|
|
LearnDash course content, lesson text, and quiz questions will inherit the fonts set in Global Styles automatically.
|
|
|
|
### Focus Mode Consideration
|
|
|
|
LearnDash Focus Mode uses its own template. Fonts set via Global Styles will apply because:
|
|
- Focus Mode still loads theme.json styles
|
|
- CSS custom properties cascade to all content
|
|
- No special handling needed
|
|
|
|
**DO NOT:**
|
|
- Target `.learndash-*` classes specifically
|
|
- Override Focus Mode styles
|
|
- Inject custom CSS for LearnDash
|
|
|
|
---
|
|
|
|
## WPForms Compatibility
|
|
|
|
### Overview
|
|
|
|
WPForms renders forms via shortcodes and blocks. Form styling is handled by WPForms.
|
|
|
|
### Why We're Compatible
|
|
|
|
- Form labels and text inherit from body font
|
|
- We don't override `.wpforms-*` classes
|
|
- No JavaScript conflicts (we have no frontend JS)
|
|
|
|
### Consideration
|
|
|
|
If a user wants form text in a different font, they should use WPForms' built-in styling options or custom CSS — not expect our plugin to handle it.
|
|
|
|
---
|
|
|
|
## General Best Practices
|
|
|
|
### What We Hook Into
|
|
|
|
```php
|
|
// Admin menu
|
|
add_action('admin_menu', [$this, 'register_menu']);
|
|
|
|
// Admin assets (only on our page)
|
|
add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
|
|
|
|
// AJAX handlers
|
|
add_action('wp_ajax_mlf_download_font', [$this, 'handle_download']);
|
|
add_action('wp_ajax_mlf_delete_font', [$this, 'handle_delete']);
|
|
|
|
// WooCommerce HPOS compatibility
|
|
add_action('before_woocommerce_init', [$this, 'declare_hpos_compatibility']);
|
|
```
|
|
|
|
### What We DON'T Hook Into
|
|
|
|
```php
|
|
// NO frontend hooks
|
|
// add_action('wp_enqueue_scripts', ...); // DON'T DO THIS
|
|
// add_action('wp_head', ...); // DON'T DO THIS
|
|
// add_action('wp_footer', ...); // DON'T DO THIS
|
|
|
|
// NO theme modification hooks
|
|
// add_filter('wp_theme_json_data_theme', ...); // Let Font Library handle it
|
|
|
|
// NO WooCommerce hooks
|
|
// add_action('woocommerce_*', ...); // DON'T DO THIS
|
|
|
|
// NO content filters
|
|
// add_filter('the_content', ...); // DON'T DO THIS
|
|
```
|
|
|
|
---
|
|
|
|
## Conflict Debugging
|
|
|
|
If a user reports a conflict, check:
|
|
|
|
### 1. Plugin Load Order
|
|
|
|
Our plugin should load with default priority. Check if another plugin is:
|
|
- Modifying the Font Library
|
|
- Overriding font CSS
|
|
- Filtering theme.json
|
|
|
|
### 2. CSS Specificity
|
|
|
|
If fonts aren't applying:
|
|
- Check browser DevTools for CSS cascade
|
|
- Look for more specific selectors overriding global styles
|
|
- Check for `!important` declarations
|
|
|
|
### 3. Cache Issues
|
|
|
|
Font changes not appearing:
|
|
- Clear browser cache
|
|
- Clear any caching plugins (WP Rocket, W3TC, etc.)
|
|
- Clear CDN cache if applicable
|
|
- WordPress transients: `delete_transient('wp_font_library_fonts')`
|
|
|
|
### 4. JavaScript Errors
|
|
|
|
If admin page isn't working:
|
|
- Check browser console for JS errors
|
|
- Look for conflicts with other admin scripts
|
|
- Verify jQuery isn't being dequeued
|
|
|
|
---
|
|
|
|
## Compatibility Checklist
|
|
|
|
Before releasing:
|
|
|
|
### WordPress Core
|
|
- [ ] Works on WordPress 6.5
|
|
- [ ] Works on WordPress 6.6+
|
|
- [ ] Font Library API integration works
|
|
- [ ] Fonts appear in Global Styles
|
|
- [ ] Fonts apply correctly on frontend
|
|
|
|
### WooCommerce
|
|
- [ ] HPOS compatibility declared
|
|
- [ ] No errors in WooCommerce status page
|
|
- [ ] Product pages render correctly with custom fonts
|
|
- [ ] Cart/Checkout not affected
|
|
|
|
### Wordfence
|
|
- [ ] Import works with firewall enabled
|
|
- [ ] No blocked requests
|
|
- [ ] No false positive security alerts
|
|
|
|
### LearnDash
|
|
- [ ] Course content inherits fonts
|
|
- [ ] Focus Mode renders correctly
|
|
- [ ] No JavaScript conflicts
|
|
|
|
### WPForms
|
|
- [ ] Forms render correctly
|
|
- [ ] No styling conflicts
|
|
|
|
### Other
|
|
- [ ] No PHP errors in debug.log
|
|
- [ ] No JavaScript errors in console
|
|
- [ ] Admin page loads correctly
|
|
- [ ] No memory issues during import
|