<?php
/**
 * Name: SVG Support
 * Description: Enable SVG file uploads and proper display in media library with sanitization for security to use vector graphics, logos, and icons in posts
 * Category: Media
 * Icon: -
  * UseCases: [
 *   {
 *     "title": "Upload Vector Graphics",
 *     "description": "Enable SVG file uploads for crisp logos and icons that scale perfectly on any screen size or resolution."
 *   },
 *   {
 *     "title": "Smaller File Sizes",
 *     "description": "Use SVG files instead of multiple PNG sizes for responsive images, reducing bandwidth and improving load times."
 *   }
 * ]
 * Added: 2025-10-10
 * Tags: svg, upload, media, vector, graphics, file-type
 * WhereToFind: {
 *   "location": "Media Library",
 *   "url": "upload.php",
 *   "instructions": "Upload SVG files - they will be accepted and sanitized",
 *   "newTab": false
 * }
 */

// Prevent direct access
if (!defined('ABSPATH')) exit;

class SwitchboardSvgSupport {

    public function __construct() {
        add_filter('upload_mimes', [$this, 'add_svg_mime_type']);
        add_filter('wp_check_filetype_and_ext', [$this, 'fix_svg_mime_type'], 10, 5);
        add_filter('wp_handle_upload_prefilter', [$this, 'sanitize_svg_upload']);
        add_action('admin_head', [$this, 'fix_svg_display']);
    }

    /**
     * Register data locations for cleanup system
     *
     * @return array Data storage locations
     */
    public function register_data_locations() {
        return [
            'options' => [],
            'option_patterns' => [],
            'tables' => [],
            'post_meta' => [],
            'user_meta' => [],
        ];
    }

    public function add_svg_mime_type($mimes) {
        $mimes['svg'] = 'image/svg+xml';
        return $mimes;
    }

    public function fix_svg_mime_type($data, $file, $filename, $mimes, $real_mime = '') {
        $wp_file_type = wp_check_filetype($filename, $mimes);

        if (is_array($wp_file_type) && isset($wp_file_type['ext']) && $wp_file_type['ext'] === 'svg') {
            $data['ext'] = 'svg';
            $data['type'] = 'image/svg+xml';
        }

        return $data;
    }

    /**
     * Sanitize SVG file on upload to prevent XSS attacks
     *
     * @param array $file File upload data
     * @return array Modified file data or error
     */
    public function sanitize_svg_upload($file) {
        // Only process SVG files
        if (!isset($file['type']) || $file['type'] !== 'image/svg+xml') {
            return $file;
        }

        // Check if file exists
        if (!isset($file['tmp_name']) || !file_exists($file['tmp_name'])) {
            return $file;
        }

        // Read SVG content
        $svg_content = file_get_contents($file['tmp_name']);

        if ($svg_content === false) {
            $file['error'] = esc_html__('Unable to read SVG file.', 'wpswitchboard');
            return $file;
        }

        // Sanitize the SVG content
        $sanitized = $this->sanitize_svg_content($svg_content);

        if ($sanitized === false) {
            $file['error'] = esc_html__('SVG file contains potentially dangerous content and cannot be uploaded.', 'wpswitchboard');
            return $file;
        }

        // Write sanitized content back
        if (file_put_contents($file['tmp_name'], $sanitized) === false) {
            $file['error'] = esc_html__('Unable to sanitize SVG file.', 'wpswitchboard');
            return $file;
        }

        return $file;
    }

    /**
     * Sanitize SVG content to remove dangerous elements
     *
     * @param string $svg_content Raw SVG content
     * @return string|false Sanitized content or false on failure
     */
    private function sanitize_svg_content($svg_content) {
        // Load XML safely
        $use_errors = libxml_use_internal_errors(true);
        $dom = new DOMDocument();
        $dom->formatOutput = false;
        $dom->preserveWhiteSpace = true;
        $dom->strictErrorChecking = false;

        // Try to load the SVG
        if (!$dom->loadXML($svg_content, LIBXML_NONET | LIBXML_NOENT)) {
            libxml_use_internal_errors($use_errors);
            return false;
        }

        // Dangerous tags to remove
        $dangerous_tags = [
            'script',
            'object',
            'embed',
            'iframe',
            'link',
            'style', // Remove style tags (can contain CSS with expressions)
            'base',
            'meta',
            'import'
        ];

        // Remove dangerous tags
        foreach ($dangerous_tags as $tag) {
            $nodes = $dom->getElementsByTagName($tag);
            $remove_nodes = [];

            foreach ($nodes as $node) {
                $remove_nodes[] = $node;
            }

            foreach ($remove_nodes as $node) {
                if ($node->parentNode) {
                    $node->parentNode->removeChild($node);
                }
            }
        }

        // Get all elements to check attributes
        $all_elements = $dom->getElementsByTagName('*');

        // Dangerous attributes to remove
        $dangerous_attrs = [
            'onload',
            'onerror',
            'onclick',
            'onmouseover',
            'onmouseout',
            'onmousemove',
            'onmouseenter',
            'onmouseleave',
            'onfocus',
            'onblur',
            'onchange',
            'onsubmit',
            'onkeydown',
            'onkeyup',
            'onkeypress',
            'onanimationend',
            'onanimationstart',
            'ontransitionend'
        ];

        // Remove dangerous attributes
        foreach ($all_elements as $element) {
            foreach ($dangerous_attrs as $attr) {
                if ($element->hasAttribute($attr)) {
                    $element->removeAttribute($attr);
                }
            }

            // Check href and xlink:href for javascript: protocol
            if ($element->hasAttribute('href')) {
                $href = $element->getAttribute('href');
                if (stripos($href, 'javascript:') === 0 || stripos($href, 'data:') === 0) {
                    $element->removeAttribute('href');
                }
            }

            if ($element->hasAttribute('xlink:href')) {
                $href = $element->getAttribute('xlink:href');
                if (stripos($href, 'javascript:') === 0 || stripos($href, 'data:') === 0) {
                    $element->removeAttribute('xlink:href');
                }
            }
        }

        libxml_use_internal_errors($use_errors);

        // Return sanitized XML
        return $dom->saveXML();
    }

    public function fix_svg_display() {
        echo '<style>
            .attachment-266x266, .thumbnail img {
                width: 100% !important;
                height: auto !important;
            }
            .media-icon img[src$=".svg"], img[src$=".svg"].attachment-post-thumbnail {
                width: 100% !important;
                height: auto !important;
            }
        </style>';
    }
}

// Initialize and register with data system
$switchboard_instance = new SwitchboardSvgSupport();
Switchboard_Data_Registry::get_instance()->auto_register($switchboard_instance, __FILE__);
