fix: Standalone nav + REST URL + SVG upload support
## ✅ Issue 1: Standalone Mode Navigation **Problem:** Standalone mode not getting WNW_NAV_TREE from PHP **Fixed:** Added WNW_NAV_TREE injection to StandaloneAdmin.php **Result:** Navigation now works in standalone mode with PHP as single source ## ✅ Issue 2: 404 Errors for branding and customer-settings **Problem:** REST URLs had trailing slashes causing double slashes **Root Cause:** - `rest_url("woonoow/v1")` returns `https://site.com/wp-json/woonoow/v1/` - Frontend: `restUrl + "/store/branding"` = double slash - WP-admin missing WNW_CONFIG entirely **Fixed:** 1. **Removed trailing slashes** from all REST URLs using `untrailingslashit()` - StandaloneAdmin.php - Assets.php (dev and prod modes) 2. **Added WNW_CONFIG to wp-admin** for API compatibility - Dev mode: Added WNW_CONFIG with restUrl, nonce, standaloneMode, etc. - Prod mode: Added WNW_CONFIG to localize_runtime() - Now both modes use same config structure **Result:** - ✅ `/store/branding` works in all modes - ✅ `/store/customer-settings` works in all modes - ✅ Consistent API access across standalone and wp-admin ## ✅ Issue 3: SVG Upload Error 500 **Problem:** WordPress blocks SVG uploads by default **Security:** "Sorry, you are not allowed to upload this file type" **Fixed:** Created MediaUpload.php with: 1. **Allow SVG uploads** for users with upload_files capability 2. **Fix SVG mime type detection** (WordPress issue) 3. **Sanitize SVG on upload** - reject files with: - `<script>` tags - `javascript:` protocols - Event handlers (onclick, onload, etc.) **Result:** - ✅ SVG uploads work securely - ✅ Dangerous SVG content blocked - ✅ Only authorized users can upload --- ## Files Modified: - `StandaloneAdmin.php` - Add nav tree + fix REST URL - `Assets.php` - Add WNW_CONFIG + fix REST URLs - `Bootstrap.php` - Initialize MediaUpload - `MediaUpload.php` - NEW: SVG upload support with security ## Testing: 1. ✅ Navigation works in standalone mode 2. ✅ Branding endpoint works in all modes 3. ✅ Customer settings endpoint works in all modes 4. ✅ SVG logo upload works 5. ✅ Dangerous SVG files rejected
This commit is contained in:
@@ -39,7 +39,7 @@ class Assets {
|
||||
// Attach runtime config (before module loader runs)
|
||||
// If you prefer, keep using self::localize_runtime($handle)
|
||||
wp_localize_script($handle, 'WNW_API', [
|
||||
'root' => esc_url_raw(rest_url('woonoow/v1/')),
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'isDev' => true,
|
||||
'devServer' => $dev_url,
|
||||
@@ -48,9 +48,19 @@ class Assets {
|
||||
]);
|
||||
wp_add_inline_script($handle, 'window.WNW_API = window.WNW_API || WNW_API;', 'after');
|
||||
|
||||
// WNW_CONFIG for compatibility with standalone mode code
|
||||
wp_localize_script($handle, 'WNW_CONFIG', [
|
||||
'restUrl' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'standaloneMode' => false,
|
||||
'wpAdminUrl' => admin_url('admin.php?page=woonoow'),
|
||||
'isAuthenticated' => is_user_logged_in(),
|
||||
]);
|
||||
wp_add_inline_script($handle, 'window.WNW_CONFIG = window.WNW_CONFIG || WNW_CONFIG;', 'after');
|
||||
|
||||
// WordPress REST API settings (for media upload compatibility)
|
||||
wp_localize_script($handle, 'wpApiSettings', [
|
||||
'root' => esc_url_raw(rest_url()),
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url())),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
]);
|
||||
wp_add_inline_script($handle, 'window.wpApiSettings = window.wpApiSettings || wpApiSettings;', 'after');
|
||||
@@ -134,7 +144,7 @@ class Assets {
|
||||
/** Attach runtime config to a handle */
|
||||
private static function localize_runtime(string $handle): void {
|
||||
wp_localize_script($handle, 'WNW_API', [
|
||||
'root' => esc_url_raw(rest_url('woonoow/v1/')),
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'isDev' => self::is_dev_mode(),
|
||||
'devServer' => self::dev_server_url(),
|
||||
@@ -142,9 +152,18 @@ class Assets {
|
||||
'adminUrl' => admin_url('admin.php'),
|
||||
]);
|
||||
|
||||
// WNW_CONFIG for compatibility with standalone mode code
|
||||
wp_localize_script($handle, 'WNW_CONFIG', [
|
||||
'restUrl' => untrailingslashit(esc_url_raw(rest_url('woonoow/v1'))),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
'standaloneMode' => false,
|
||||
'wpAdminUrl' => admin_url('admin.php?page=woonoow'),
|
||||
'isAuthenticated' => is_user_logged_in(),
|
||||
]);
|
||||
|
||||
// WordPress REST API settings (for media upload compatibility)
|
||||
wp_localize_script($handle, 'wpApiSettings', [
|
||||
'root' => esc_url_raw(rest_url()),
|
||||
'root' => untrailingslashit(esc_url_raw(rest_url())),
|
||||
'nonce' => wp_create_nonce('wp_rest'),
|
||||
]);
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class StandaloneAdmin {
|
||||
|
||||
// Get nonce for REST API
|
||||
$nonce = wp_create_nonce( 'wp_rest' );
|
||||
$rest_url = rest_url( 'woonoow/v1' );
|
||||
$rest_url = untrailingslashit( rest_url( 'woonoow/v1' ) );
|
||||
$wp_admin_url = admin_url( 'admin.php?page=woonoow' );
|
||||
|
||||
// Get current user data if authenticated
|
||||
@@ -134,6 +134,9 @@ class StandaloneAdmin {
|
||||
|
||||
// WooCommerce store settings (currency, formatting, etc.)
|
||||
window.WNW_STORE = <?php echo wp_json_encode( $store_settings ); ?>;
|
||||
|
||||
// Navigation tree (single source of truth from PHP)
|
||||
window.WNW_NAV_TREE = <?php echo wp_json_encode( \WooNooW\Compat\NavigationRegistry::get_frontend_nav_tree() ); ?>;
|
||||
</script>
|
||||
|
||||
<script type="module" src="<?php echo esc_url( $js_url ); ?>"></script>
|
||||
|
||||
@@ -18,6 +18,7 @@ use WooNooW\Api\Routes;
|
||||
use WooNooW\Core\Mail\MailQueue;
|
||||
use WooNooW\Core\Mail\WooEmailOverride;
|
||||
use WooNooW\Core\DataStores\OrderStore;
|
||||
use WooNooW\Core\MediaUpload;
|
||||
use WooNooW\Branding;
|
||||
|
||||
class Bootstrap {
|
||||
@@ -28,6 +29,7 @@ class Bootstrap {
|
||||
Assets::init();
|
||||
StandaloneAdmin::init();
|
||||
Branding::init();
|
||||
MediaUpload::init();
|
||||
|
||||
// Addon system (order matters: Registry → Routes → Navigation)
|
||||
AddonRegistry::init();
|
||||
|
||||
107
includes/Core/MediaUpload.php
Normal file
107
includes/Core/MediaUpload.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* Media Upload Handler
|
||||
*
|
||||
* Handles file uploads including SVG support with security checks.
|
||||
*
|
||||
* @package WooNooW
|
||||
*/
|
||||
|
||||
namespace WooNooW\Core;
|
||||
|
||||
class MediaUpload {
|
||||
|
||||
/**
|
||||
* Initialize media upload hooks
|
||||
*/
|
||||
public static function init() {
|
||||
// Allow SVG uploads
|
||||
add_filter( 'upload_mimes', [ __CLASS__, 'allow_svg_upload' ] );
|
||||
|
||||
// Fix SVG mime type detection
|
||||
add_filter( 'wp_check_filetype_and_ext', [ __CLASS__, 'fix_svg_mime_type' ], 10, 5 );
|
||||
|
||||
// Sanitize SVG on upload
|
||||
add_filter( 'wp_handle_upload_prefilter', [ __CLASS__, 'sanitize_svg_upload' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow SVG file uploads
|
||||
*
|
||||
* @param array $mimes Allowed mime types
|
||||
* @return array Modified mime types
|
||||
*/
|
||||
public static function allow_svg_upload( $mimes ) {
|
||||
// Only allow for users with upload_files capability
|
||||
if ( ! current_user_can( 'upload_files' ) ) {
|
||||
return $mimes;
|
||||
}
|
||||
|
||||
$mimes['svg'] = 'image/svg+xml';
|
||||
$mimes['svgz'] = 'image/svg+xml';
|
||||
|
||||
return $mimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix SVG mime type detection
|
||||
*
|
||||
* @param array $data File data
|
||||
* @param string $file File path
|
||||
* @param string $filename File name
|
||||
* @param array $mimes Allowed mimes
|
||||
* @param string $real_mime Real mime type
|
||||
* @return array Modified file data
|
||||
*/
|
||||
public static function fix_svg_mime_type( $data, $file, $filename, $mimes, $real_mime ) {
|
||||
if ( ! $data['ext'] && ! $data['type'] ) {
|
||||
$wp_filetype = wp_check_filetype( $filename, $mimes );
|
||||
$ext = $wp_filetype['ext'];
|
||||
$type = $wp_filetype['type'];
|
||||
|
||||
if ( $ext === 'svg' || $ext === 'svgz' ) {
|
||||
$data['ext'] = $ext;
|
||||
$data['type'] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize SVG files on upload
|
||||
*
|
||||
* @param array $file Upload file data
|
||||
* @return array Modified file data
|
||||
*/
|
||||
public static function sanitize_svg_upload( $file ) {
|
||||
// Only process SVG files
|
||||
if ( $file['type'] !== 'image/svg+xml' ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
// Read file content
|
||||
$svg_content = file_get_contents( $file['tmp_name'] );
|
||||
|
||||
if ( $svg_content === false ) {
|
||||
$file['error'] = __( 'Could not read SVG file', 'woonoow' );
|
||||
return $file;
|
||||
}
|
||||
|
||||
// Basic security check - reject if contains script tags or event handlers
|
||||
$dangerous_patterns = [
|
||||
'/<script/i',
|
||||
'/javascript:/i',
|
||||
'/on\w+\s*=/i', // onclick, onload, etc.
|
||||
];
|
||||
|
||||
foreach ( $dangerous_patterns as $pattern ) {
|
||||
if ( preg_match( $pattern, $svg_content ) ) {
|
||||
$file['error'] = __( 'SVG file contains potentially dangerous content', 'woonoow' );
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user