Plugin Directory

Changeset 3192812


Ignore:
Timestamp:
11/20/2024 07:00:11 AM (4 months ago)
Author:
Petrichorpost
Message:

Release version 1.2.2: Added blocks support, improved security and logging

Location:
svgplus
Files:
127 added
8 edited

Legend:

Unmodified
Added
Removed
  • svgplus/trunk/assets/css/svgplus.css

    r3165060 r3192812  
    11.svgplus-widget {
    2     display: block !important; /* Ensure it is displayed */
    3     width: 100%; /* Adjust as necessary */
     2    display: inline-block !important; /* Change to inline-block */
     3    max-width: 100%; /* Use max-width instead of width */
    44    height: auto; /* Maintain aspect ratio */
     5    position: relative; /* For resize handles */
    56}
     7
     8.svgplus-widget img {
     9    display: block; /* Remove any inline spacing */
     10    width: 100%;
     11    height: auto;
     12    object-fit: contain; /* Maintain aspect ratio */
     13}
     14
     15/* SVG Resize Handles */
     16.svgplus-resize-wrapper {
     17    position: relative;
     18    display: inline-block;
     19}
     20
     21.svgplus-resize-handles {
     22    position: absolute;
     23    top: 0;
     24    left: 0;
     25    right: 0;
     26    bottom: 0;
     27    pointer-events: none;
     28}
     29
     30.svgplus-handle {
     31    position: absolute;
     32    width: 10px;
     33    height: 10px;
     34    background: #fff;
     35    border: 1px solid #007cba;
     36    pointer-events: all;
     37    z-index: 1000;
     38}
     39
     40/* Position the handles */
     41.svgplus-handle.nw { top: -5px; left: -5px; cursor: nw-resize; }
     42.svgplus-handle.ne { top: -5px; right: -5px; cursor: ne-resize; }
     43.svgplus-handle.sw { bottom: -5px; left: -5px; cursor: sw-resize; }
     44.svgplus-handle.se { bottom: -5px; right: -5px; cursor: se-resize; }
     45.svgplus-handle.n { top: -5px; left: 50%; margin-left: -5px; cursor: n-resize; }
     46.svgplus-handle.s { bottom: -5px; left: 50%; margin-left: -5px; cursor: s-resize; }
     47.svgplus-handle.e { right: -5px; top: 50%; margin-top: -5px; cursor: e-resize; }
     48.svgplus-handle.w { left: -5px; top: 50%; margin-top: -5px; cursor: w-resize; }
    649
    750/* Animation on hover */
  • svgplus/trunk/assets/js/svgplus.js

    r3165060 r3192812  
    11// SVGPlus Plugin JavaScript
     2jQuery(document).ready(function($) {
     3    'use strict';
    24
    3 // Currently no additional JavaScript functionality is required.
    4 // Future enhancements can add interactivity here.
     5    // Initialize SVG Plus
     6    var SVGPlus = {
     7        init: function() {
     8            this.initResizeHandles();
     9            this.bindEvents();
     10        },
    511
    6 jQuery(document).ready(function($){
    7     // Example: Handle click events on SVG images
    8     $('.svgplus-widget img').on('click', function(){
    9         // Placeholder for future interactivity
    10         console.log('SVG clicked:', $(this).attr('src'));
     12        initResizeHandles: function() {
     13            $('.svgplus-widget').each(function() {
     14                if (!$(this).find('.svgplus-resize-handles').length) {
     15                    $(this).wrap('<div class="svgplus-resize-wrapper"></div>');
     16                    var handles = '<div class="svgplus-resize-handles">' +
     17                        '<div class="svgplus-handle nw"></div>' +
     18                        '<div class="svgplus-handle n"></div>' +
     19                        '<div class="svgplus-handle ne"></div>' +
     20                        '<div class="svgplus-handle w"></div>' +
     21                        '<div class="svgplus-handle e"></div>' +
     22                        '<div class="svgplus-handle sw"></div>' +
     23                        '<div class="svgplus-handle s"></div>' +
     24                        '<div class="svgplus-handle se"></div>' +
     25                        '</div>';
     26                    $(this).after(handles);
     27                }
     28            });
     29        },
     30
     31        bindEvents: function() {
     32            var self = this;
     33           
     34            // Handle resize events
     35            $('.svgplus-handle').on('mousedown', function(e) {
     36                e.preventDefault();
     37                var $handle = $(this);
     38                var $wrapper = $handle.closest('.svgplus-resize-wrapper');
     39                var $svg = $wrapper.find('.svgplus-widget');
     40                var direction = self.getResizeDirection($handle);
     41               
     42                var startX = e.pageX;
     43                var startY = e.pageY;
     44                var startWidth = $svg.width();
     45                var startHeight = $svg.height();
     46                var aspectRatio = startWidth / startHeight;
     47               
     48                $(document).on('mousemove.svgResize', function(e) {
     49                    var deltaX = e.pageX - startX;
     50                    var deltaY = e.pageY - startY;
     51                   
     52                    var newWidth, newHeight;
     53                   
     54                    // Calculate new dimensions based on direction
     55                    switch(direction) {
     56                        case 'nw':
     57                        case 'se':
     58                            newWidth = startWidth + deltaX;
     59                            newHeight = newWidth / aspectRatio;
     60                            break;
     61                        case 'ne':
     62                        case 'sw':
     63                            newWidth = startWidth - deltaX;
     64                            newHeight = newWidth / aspectRatio;
     65                            break;
     66                        case 'n':
     67                        case 's':
     68                            newHeight = startHeight + deltaY;
     69                            newWidth = newHeight * aspectRatio;
     70                            break;
     71                        case 'e':
     72                        case 'w':
     73                            newWidth = startWidth + deltaX;
     74                            newHeight = newWidth / aspectRatio;
     75                            break;
     76                    }
     77                   
     78                    // Ensure minimum dimensions
     79                    newWidth = Math.max(50, newWidth);
     80                    newHeight = Math.max(50, newHeight);
     81                   
     82                    // Update dimensions
     83                    $svg.css({
     84                        width: newWidth + 'px',
     85                        height: newHeight + 'px'
     86                    });
     87                   
     88                    // Update WordPress media data
     89                    if ($svg.closest('.media-frame').length) {
     90                        var attachment = wp.media.frame.state().get('selection').first();
     91                        if (attachment) {
     92                            attachment.set({
     93                                width: Math.round(newWidth),
     94                                height: Math.round(newHeight)
     95                            });
     96                        }
     97                    }
     98                });
     99               
     100                $(document).on('mouseup.svgResize', function() {
     101                    $(document).off('mousemove.svgResize mouseup.svgResize');
     102                });
     103            });
     104        },
     105
     106        getResizeDirection: function($handle) {
     107            var classes = $handle.attr('class').split(' ');
     108            for (var i = 0; i < classes.length; i++) {
     109                if (['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'].indexOf(classes[i]) !== -1) {
     110                    return classes[i];
     111                }
     112            }
     113            return '';
     114        }
     115    };
     116
     117    // Initialize when document is ready
     118    SVGPlus.init();
     119   
     120    // Re-initialize when new content is added (for dynamically loaded content)
     121    $(document).on('ajaxComplete', function() {
     122        SVGPlus.init();
    11123    });
    12124});
  • svgplus/trunk/composer.json

    r3165214 r3192812  
    22    "name": "derickpayne/svgplus",
    33    "description": "A WordPress plugin to securely upload and display SVG files.",
    4     "type": "library",
     4    "type": "wordpress-plugin",
    55    "license": "GPL-2.0-or-later",
    6     "minimum-stability": "stable",
    76    "require": {
    8         "enshrined/svg-sanitize": "^0.20.0"
     7        "php": ">=7.4"
    98    }
    109}
  • svgplus/trunk/includes/class-svgplus-sanitizer.php

    r3168748 r3192812  
    66}
    77
    8 use enshrined\svgSanitize\Sanitizer;
    9 
    108class SVGPlus_Sanitizer {
    119
    1210    /**
    13      * Sanitizes the uploaded SVG content using the enshrined/svg-sanitize library.
     11     * List of allowed SVG tags
     12     */
     13    public static function get_allowed_tags() {
     14        return array(
     15            // Structure
     16            'svg', 'g', 'defs', 'use', 'symbol', 'mask', 'clipPath',
     17           
     18            // Shapes
     19            'path', 'rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon',
     20           
     21            // Text
     22            'text', 'tspan', 'textPath',
     23           
     24            // Other visual elements
     25            'image', 'title', 'desc',
     26           
     27            // Gradients and Patterns
     28            'linearGradient', 'radialGradient', 'stop', 'pattern',
     29           
     30            // Filters
     31            'filter', 'feBlend', 'feColorMatrix', 'feComponentTransfer',
     32            'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
     33            'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA',
     34            'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage',
     35            'feMerge', 'feMergeNode', 'feMorphology', 'feOffset',
     36            'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile',
     37            'feTurbulence'
     38        );
     39    }
     40
     41    /**
     42     * List of allowed SVG attributes
     43     */
     44    public static function get_allowed_attributes() {
     45        return array(
     46            // Core attributes
     47            'id', 'class', 'style', 'data-name',
     48           
     49            // Presentation attributes
     50            'fill', 'fill-rule', 'stroke', 'stroke-width', 'stroke-linecap',
     51            'stroke-linejoin', 'stroke-miterlimit', 'stroke-dasharray',
     52            'stroke-dashoffset', 'stroke-opacity', 'fill-opacity', 'opacity',
     53            'transform', 'offset', 'stop-color', 'stop-opacity',
     54            'xmlns', 'xmlns:xlink', 'xmlns:svg',
     55
     56            // Gradient attributes
     57            'gradientUnits', 'gradientTransform', 'href', 'xlink:href',
     58            'x1', 'x2', 'y1', 'y2', 'cx', 'cy', 'r', 'fx', 'fy',
     59           
     60            // Dimension attributes
     61            'x', 'y', 'width', 'height', 'viewBox', 'preserveAspectRatio',
     62           
     63            // Path attributes
     64            'd', 'pathLength',
     65           
     66            // Text attributes
     67            'text-anchor', 'font-family', 'font-size', 'font-weight',
     68            'letter-spacing', 'word-spacing', 'text-decoration',
     69           
     70            // Filter attributes
     71            'filterUnits', 'primitiveUnits', 'in', 'in2', 'result',
     72            'stdDeviation', 'mode', 'operator', 'scale', 'values',
     73            'flood-color', 'flood-opacity'
     74        );
     75    }
     76
     77    private $logger = null;
     78
     79    public function __construct() {
     80        $this->logger = SVGPlus_Logger::get_instance();
     81    }
     82
     83    /**
     84     * Sanitize SVG content
     85     */
     86    public function sanitize_svg($content) {
     87        try {
     88            $this->logger->log_message('Starting SVG sanitization');
     89           
     90            // Load the SVG content
     91            $dom = new DOMDocument();
     92            libxml_use_internal_errors(true);
     93           
     94            // Try to load the SVG content with LIBXML_NONET to prevent external entities
     95            $this->logger->log_message('Loading XML content');
     96            if (!$dom->loadXML($content, LIBXML_NONET)) {
     97                $this->logger->log_message('Failed to load XML content', 'ERROR');
     98                $this->logger->log_xml_errors();
     99                return false;
     100            }
     101           
     102            // Get the root SVG element
     103            $svg = $dom->getElementsByTagName('svg')->item(0);
     104            if (!$svg) {
     105                $this->logger->log_message('No SVG element found', 'ERROR');
     106                return false;
     107            }
     108           
     109            $this->logger->log_message('Found SVG element, checking namespaces');
     110           
     111            // Get allowed tags and attributes
     112            $allowed_tags = self::get_allowed_tags();
     113            $allowed_attrs = self::get_allowed_attributes();
     114           
     115            $this->logger->log_message('Cleaning SVG element');
     116            // Clean the SVG
     117            $this->clean_svg_element($svg, $allowed_tags, $allowed_attrs);
     118           
     119            // Ensure root SVG element has proper namespaces
     120            if (!$svg->hasAttribute('xmlns')) {
     121                $this->logger->log_message('Adding xmlns namespace');
     122                $svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
     123            }
     124            if (strpos($dom->saveXML(), 'xlink:') !== false && !$svg->hasAttribute('xmlns:xlink')) {
     125                $this->logger->log_message('Adding xmlns:xlink namespace');
     126                $svg->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
     127            }
     128           
     129            // Return the cleaned SVG
     130            $result = $dom->saveXML($svg);
     131            $this->logger->log_message('SVG sanitization completed successfully');
     132            $this->logger->log_message('Final SVG size: ' . strlen($result) . ' bytes');
     133            return $result;
     134           
     135        } catch (Exception $e) {
     136            $this->logger->log_message('Error during sanitization: ' . $e->getMessage(), 'ERROR');
     137            return false;
     138        }
     139    }
     140   
     141    /**
     142     * Clean SVG element recursively
     143     */
     144    private function clean_svg_element($element, $allowed_tags, $allowed_attrs) {
     145        try {
     146            $children = array();
     147            foreach ($element->childNodes as $child) {
     148                if ($child->nodeType === XML_ELEMENT_NODE) {
     149                    $children[] = $child;
     150                }
     151            }
     152           
     153            foreach ($children as $child) {
     154                // Remove if tag not allowed
     155                if (!in_array($child->tagName, $allowed_tags)) {
     156                    $this->logger->log_message("Removing disallowed tag: {$child->tagName}", 'WARNING');
     157                    $element->removeChild($child);
     158                    continue;
     159                }
     160               
     161                // Clean attributes
     162                $attributes = array();
     163                foreach ($child->attributes as $attr) {
     164                    $attributes[] = $attr;
     165                }
     166               
     167                foreach ($attributes as $attr) {
     168                    if (!in_array($attr->name, $allowed_attrs)) {
     169                        $this->logger->log_message("Removing disallowed attribute: {$attr->name} from {$child->tagName}", 'WARNING');
     170                        $child->removeAttribute($attr->name);
     171                    }
     172                }
     173               
     174                // Clean child elements
     175                $this->clean_svg_element($child, $allowed_tags, $allowed_attrs);
     176            }
     177        } catch (Exception $e) {
     178            $this->logger->log_message('Error cleaning element: ' . $e->getMessage(), 'ERROR');
     179        }
     180    }
     181
     182    /**
     183     * Sanitizes the uploaded SVG content.
    14184     *
    15185     * @param string $svg_content The raw SVG content.
    16186     * @return string|false The sanitized SVG content or false on failure.
    17187     */
    18     public static function sanitize_svg($svg_content) {
    19         // Initialize the sanitizer
    20         $sanitizer = new Sanitizer();
    21 
    22         // Sanitize the SVG
    23         $sanitized_svg = $sanitizer->sanitize($svg_content);
    24 
    25         if ($sanitized_svg === false) {
    26             error_log('SVGPlus_Sanitizer: Failed to sanitize SVG.');
     188    public static function sanitize_svg_content($svg_content) {
     189        if (empty($svg_content)) {
    27190            return false;
    28191        }
    29192
    30         return $sanitized_svg;
     193        // Load the SVG content into a DOMDocument
     194        $dom = new DOMDocument();
     195        $dom->preserveWhiteSpace = true;
     196        $dom->formatOutput = true;
     197
     198        // Suppress warnings during loading
     199        libxml_use_internal_errors(true);
     200        if (!$dom->loadXML($svg_content, LIBXML_NOBLANKS | LIBXML_NONET)) {
     201            libxml_clear_errors();
     202            return false;
     203        }
     204        libxml_clear_errors();
     205
     206        // Get all elements
     207        $allElements = $dom->getElementsByTagName('*');
     208        $allowed_tags = self::get_allowed_tags();
     209        $allowed_attributes = self::get_allowed_attributes();
     210
     211        // Check each element
     212        for ($i = $allElements->length - 1; $i >= 0; $i--) {
     213            $element = $allElements->item($i);
     214           
     215            // Remove if not in allowed tags
     216            if (!in_array($element->tagName, $allowed_tags)) {
     217                $element->parentNode->removeChild($element);
     218                continue;
     219            }
     220
     221            // Check attributes
     222            if ($element->hasAttributes()) {
     223                $attributes = $element->attributes;
     224                for ($j = $attributes->length - 1; $j >= 0; $j--) {
     225                    $attr = $attributes->item($j);
     226                    if (!in_array($attr->name, $allowed_attributes)) {
     227                        $element->removeAttributeNode($attr);
     228                    }
     229                }
     230            }
     231        }
     232
     233        // Ensure root SVG element has proper namespaces
     234        $svg = $dom->getElementsByTagName('svg')->item(0);
     235        if ($svg) {
     236            if (!$svg->hasAttribute('xmlns')) {
     237                $svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
     238            }
     239            if (strpos($dom->saveXML(), 'xlink:') !== false && !$svg->hasAttribute('xmlns:xlink')) {
     240                $svg->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
     241            }
     242        }
     243
     244        // Convert back to XML string
     245        $clean = $dom->saveXML($dom->documentElement);
     246
     247        // Add XML declaration if missing
     248        if (strpos($clean, '<?xml') === false) {
     249            $clean = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' . $clean;
     250        }
     251
     252        return $clean;
    31253    }
    32254}
  • svgplus/trunk/includes/class-svgplus-settings.php

    r3168748 r3192812  
    77
    88class SVGPlus_Settings {
    9 
    10     public function __construct() {
     9    private static $instance = null;
     10
     11    /**
     12     * Get the singleton instance.
     13     */
     14    public static function init() {
     15        if (null === self::$instance) {
     16            self::$instance = new self();
     17        }
     18        return self::$instance;
     19    }
     20
     21    private function __construct() {
    1122        add_action('admin_menu', array($this, 'add_settings_menu'));
    1223        add_action('admin_init', array($this, 'register_settings'));
     
    1930    public function add_settings_menu() {
    2031        add_options_page(
    21             __('SVGPlus Settings', 'svgplus'), // Page title
    22             __('SVGPlus', 'svgplus'),          // Menu title
     32            __('SVG Plus Settings', 'svgplus'), // Page title
     33            __('SVG Plus', 'svgplus'),          // Menu title
    2334            'manage_options',                  // Capability
    2435            'svgplus-settings',                // Menu slug
     
    114125     */
    115126    public function main_section_callback() {
    116         echo esc_html__('Configure the main settings for SVGPlus.', 'svgplus');
     127        echo esc_html__('Configure the main settings for SVG Plus.', 'svgplus');
    117128    }
    118129
     
    224235        <div class="wrap">
    225236            <h1>
    226                 <img src="<?php echo esc_url($icon_url); ?>" alt="SVGPlus Icon" class="svgplus-settings-icon" />
    227                 <?php esc_html_e('SVGPlus Settings', 'svgplus'); ?>
     237                <img src="<?php echo esc_url($icon_url); ?>" alt="SVG Plus Icon" class="svgplus-settings-icon" />
     238                <?php esc_html_e('SVG Plus Settings', 'svgplus'); ?>
    228239            </h1>
    229240
  • svgplus/trunk/includes/class-svgplus-upload.php

    r3168748 r3192812  
    77
    88class SVGPlus_Upload {
    9 
     9    private static $sanitizer = null;
     10    private static $logger = null;
     11
     12    /**
     13     * Initialize the upload handler
     14     */
    1015    public static function init() {
    11         // Modified section starts here
     16        // Initialize security features
     17        SVGPlus_Security::init();
     18
     19        // Initialize logger
     20        self::$logger = SVGPlus_Logger::get_instance();
     21        self::$logger->log_message('SVGPlus Upload Handler Initialized');
     22
    1223        $options = get_option('svgplus_settings');
    1324        $is_svg_enabled = isset($options['enable_svg_support']) ? $options['enable_svg_support'] : 0;
    1425
    1526        if ($is_svg_enabled) {
    16             // Allow SVG mime types
     27            // Register SVG block
     28            add_action('init', array(__CLASS__, 'register_svg_block'));
     29
    1730            add_filter('upload_mimes', array(__CLASS__, 'add_svg_mime_type'));
    18             // Fix MIME type and file extension checks for SVGs
    1931            add_filter('wp_check_filetype_and_ext', array(__CLASS__, 'fix_mime_type_svg'), 10, 4);
    20             // Handle file upload prefilter for SVG sanitization
    2132            add_filter('wp_handle_upload_prefilter', array(__CLASS__, 'handle_upload_prefilter'));
    22         }
    23         // Modified section ends here
    24     }
    25 
    26     /**
    27      * Adds SVG to the list of allowed mime types with the custom MIME type svgplus/svg+xml.
    28      *
    29      * @param array $mimes Existing mime types.
    30      * @return array Modified mime types.
     33            add_filter('wp_prepare_attachment_for_js', array(__CLASS__, 'fix_admin_preview'), 10, 3);
     34            add_filter('wp_get_attachment_image_src', array(__CLASS__, 'fix_thumbnail_display'), 10, 4);
     35           
     36            // Add filters for alt text handling
     37            add_filter('post_thumbnail_html', array(__CLASS__, 'filter_post_thumbnail_html'), 10, 5);
     38            add_filter('the_content', array(__CLASS__, 'filter_content_images'), 10, 1);
     39            add_filter('wp_get_attachment_image_attributes', array(__CLASS__, 'filter_image_attributes'), 10, 2);
     40           
     41            // Add media editor support
     42            add_filter('image_send_to_editor', array(__CLASS__, 'svg_media_send_to_editor'), 10, 8);
     43            add_filter('wp_ajax_image-editor', array(__CLASS__, 'svg_media_editor_response'), 1);
     44            add_filter('wp_get_attachment_metadata', array(__CLASS__, 'svg_get_attachment_metadata'), 10, 2);
     45            add_filter('wp_generate_attachment_metadata', array(__CLASS__, 'svg_generate_metadata'), 10, 2);
     46
     47            // Bypass image processing for SVGs
     48            add_filter('wp_image_editors', array(__CLASS__, 'disable_svg_image_editors'));
     49
     50            self::$logger->log_message('SVG support enabled and filters registered');
     51        } else {
     52            self::$logger->log_message('SVG support is disabled');
     53        }
     54    }
     55
     56    /**
     57     * Add SVG mime type to allowed upload types
    3158     */
    3259    public static function add_svg_mime_type($mimes) {
    33         // Add the custom MIME type for SVGPlus
    34         $mimes['svg'] = 'image/svg+xml';  // Standard SVG MIME type
    35         $mimes['svgz'] = 'image/svg+xml'; // Compressed SVG
    36         return $mimes;
    37     }
    38 
    39     /**
    40      * Fixes the MIME type and extension checks for SVG files to ensure they pass WordPress validation.
    41      *
    42      * @param array  $data
    43      * @param string $file
    44      * @param string $filename
    45      * @param array  $mimes
    46      * @return array
     60        try {
     61            self::$logger->log_message('Adding SVG mime type to allowed types');
     62            $mimes['svg'] = 'image/svg+xml';
     63            return $mimes;
     64        } catch (Exception $e) {
     65            self::$logger->log_message('Error adding mime type: ' . $e->getMessage(), 'ERROR');
     66            return $mimes;
     67        }
     68    }
     69
     70    /**
     71     * Fix mime type for SVG files
    4772     */
    4873    public static function fix_mime_type_svg($data, $file, $filename, $mimes) {
    49         $ext = pathinfo($filename, PATHINFO_EXTENSION);
    50 
    51         if ($ext === 'svg' || $ext === 'svgz') {
    52             $data['ext'] = 'svg';
    53             $data['type'] = 'image/svg+xml';  // Ensure the proper MIME type is set
    54             $data['proper_filename'] = $data['proper_filename'] ?? $filename;
    55         }
    56 
     74        try {
     75            self::$logger->log_message("Checking mime type for file: $filename");
     76            self::$logger->log_array($data, 'Current data: ');
     77           
     78            $ext = isset($data['ext']) ? $data['ext'] : '';
     79            if (strlen($ext) < 1) {
     80                $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
     81            }
     82           
     83            if ($ext === 'svg') {
     84                self::$logger->log_message('File is SVG, checking content');
     85               
     86                // Check file content
     87                $content = file_get_contents($file);
     88                if ($content && strpos($content, '<svg') !== false) {
     89                    self::$logger->log_message('Valid SVG content found');
     90                    $data['type'] = 'image/svg+xml';
     91                    $data['ext'] = 'svg';
     92                } else {
     93                    self::$logger->log_message('Invalid SVG content', 'WARNING');
     94                    self::$logger->log_message('File content: ' . substr($content, 0, 100) . '...', 'DEBUG');
     95                    $data['type'] = false;
     96                    $data['ext'] = false;
     97                }
     98            }
     99           
     100            self::$logger->log_array($data, 'Final data: ');
     101            return $data;
     102           
     103        } catch (Exception $e) {
     104            self::$logger->log_message('Error in mime type check: ' . $e->getMessage(), 'ERROR');
     105            return $data;
     106        }
     107    }
     108
     109    /**
     110     * Handle SVG upload and sanitization
     111     */
     112    public static function handle_upload_prefilter($file) {
     113        try {
     114            self::$logger->log_message("Processing upload: {$file['name']}");
     115            self::$logger->log_array($file, 'Upload data: ');
     116           
     117            // Check if this is an SVG upload
     118            if (!in_array($file['type'], array('image/svg+xml', 'image/svg'))) {
     119                self::$logger->log_message("Not an SVG file: {$file['type']}", 'DEBUG');
     120                return $file;
     121            }
     122
     123            // Read file content
     124            $content = file_get_contents($file['tmp_name']);
     125            if ($content === false) {
     126                self::$logger->log_message("Could not read file: {$file['tmp_name']}", 'ERROR');
     127                $file['error'] = __('Could not read SVG file.', 'svgplus');
     128                return $file;
     129            }
     130
     131            self::$logger->log_message('File content length: ' . strlen($content));
     132            self::$logger->log_message('First 100 bytes: ' . substr($content, 0, 100));
     133
     134            // Quick validation check
     135            if (strpos($content, '<svg') === false) {
     136                self::$logger->log_message('No SVG tag found in file', 'ERROR');
     137                $file['error'] = __('Invalid SVG file format.', 'svgplus');
     138                return $file;
     139            }
     140
     141            // Initialize sanitizer if needed
     142            if (self::$sanitizer === null) {
     143                self::$sanitizer = new SVGPlus_Sanitizer();
     144            }
     145
     146            // Sanitize SVG content
     147            $sanitized = self::$sanitizer->sanitize_svg($content);
     148            if ($sanitized === false) {
     149                self::$logger->log_message('SVG sanitization failed', 'ERROR');
     150                $file['error'] = __('Invalid SVG file format.', 'svgplus');
     151                return $file;
     152            }
     153
     154            self::$logger->log_message('Sanitized content length: ' . strlen($sanitized));
     155
     156            // Write sanitized content back to file
     157            if (file_put_contents($file['tmp_name'], $sanitized) === false) {
     158                self::$logger->log_message('Could not write sanitized content', 'ERROR');
     159                $file['error'] = __('Could not write sanitized SVG file.', 'svgplus');
     160                return $file;
     161            }
     162
     163            // Add a filter to prevent WordPress from trying to create image subsizes
     164            add_filter('wp_image_editors', function($editors) use ($file) {
     165                if (in_array($file['type'], array('image/svg+xml', 'image/svg'))) {
     166                    self::$logger->log_message('Disabled image editors for SVG');
     167                    return array();
     168                }
     169                return $editors;
     170            });
     171
     172            self::$logger->log_message("Successfully processed SVG: {$file['name']}");
     173            return $file;
     174
     175        } catch (Exception $e) {
     176            self::$logger->log_message('Error processing SVG: ' . $e->getMessage(), 'ERROR');
     177            $file['error'] = __('Error processing SVG file: ', 'svgplus') . $e->getMessage();
     178            return $file;
     179        }
     180    }
     181
     182    /**
     183     * Fix SVG preview in media library
     184     */
     185    public static function fix_admin_preview($response, $attachment, $meta) {
     186        if ($response['mime'] === 'image/svg+xml') {
     187            $svg_path = get_attached_file($attachment->ID);
     188           
     189            if (!file_exists($svg_path)) {
     190                return $response;
     191            }
     192
     193            try {
     194                $dimensions = self::get_svg_dimensions($svg_path);
     195               
     196                if ($dimensions) {
     197                    // Get original dimensions
     198                    $original_width = $dimensions['width'];
     199                    $original_height = $dimensions['height'];
     200                    $aspect_ratio = $original_width / $original_height;
     201                   
     202                    // Get current dimensions (if being resized)
     203                    $width = isset($_POST['width']) ? intval($_POST['width']) : $original_width;
     204                    $height = isset($_POST['height']) ? intval($_POST['height']) : $original_height;
     205                   
     206                    // If width is being changed, adjust height to maintain aspect ratio
     207                    if (isset($_POST['width']) && !isset($_POST['height'])) {
     208                        $height = round($width / $aspect_ratio);
     209                    }
     210                    // If height is being changed, adjust width to maintain aspect ratio
     211                    else if (!isset($_POST['width']) && isset($_POST['height'])) {
     212                        $width = round($height * $aspect_ratio);
     213                    }
     214                   
     215                    // Update response with new dimensions
     216                    $response['width'] = $width;
     217                    $response['height'] = $height;
     218                   
     219                    // For SVGs, we don't need multiple sizes
     220                    $response['sizes'] = array(
     221                        'full' => array(
     222                            'url' => $response['url'],
     223                            'width' => $width,
     224                            'height' => $height,
     225                            'orientation' => $width >= $height ? 'landscape' : 'portrait'
     226                        )
     227                    );
     228                   
     229                    // Add SVG-specific metadata
     230                    $response['svgplus'] = array(
     231                        'originalWidth' => $original_width,
     232                        'originalHeight' => $original_height,
     233                        'aspectRatio' => $aspect_ratio,
     234                        'preserveAspectRatio' => true
     235                    );
     236                   
     237                    // Add custom CSS class for SVG handling
     238                    $response['classes'] = isset($response['classes']) ? $response['classes'] . ' svgplus-widget' : 'svgplus-widget';
     239                }
     240            } catch (Exception $e) {
     241                // Log error but don't break the upload
     242                self::$logger->log_message('Error processing SVG preview: ' . $e->getMessage(), 'ERROR');
     243            }
     244        }
     245        return $response;
     246    }
     247
     248    /**
     249     * Get SVG dimensions from file
     250     */
     251    private static function get_svg_dimensions($file_path) {
     252        if (!file_exists($file_path)) {
     253            return false;
     254        }
     255
     256        $svg = file_get_contents($file_path);
     257        if (!$svg) {
     258            return false;
     259        }
     260
     261        $dimensions = array('width' => null, 'height' => null);
     262
     263        // Create new DOMDocument instance
     264        $xml = new DOMDocument();
     265       
     266        // Disable errors temporarily
     267        $internal_errors = libxml_use_internal_errors(true);
     268       
     269        // Load SVG content
     270        if ($xml->loadXML($svg)) {
     271            $svg_element = $xml->documentElement;
     272           
     273            // Get width and height attributes
     274            $width = $svg_element->getAttribute('width');
     275            $height = $svg_element->getAttribute('height');
     276           
     277            // If width/height are percentages or missing, try viewBox
     278            if (empty($width) || empty($height) || strpos($width, '%') !== false || strpos($height, '%') !== false) {
     279                $viewBox = $svg_element->getAttribute('viewBox');
     280                if ($viewBox) {
     281                    $viewBoxValues = preg_split('/[\s,]+/', trim($viewBox));
     282                    if (count($viewBoxValues) === 4) {
     283                        $width = $viewBoxValues[2];
     284                        $height = $viewBoxValues[3];
     285                    }
     286                }
     287            }
     288           
     289            // Convert to numeric values
     290            $width = $width ? floatval($width) : 150;  // Default width if not specified
     291            $height = $height ? floatval($height) : 150;  // Default height if not specified
     292           
     293            // Ensure minimum dimensions
     294            $width = max(50, $width);
     295            $height = max(50, $height);
     296           
     297            $dimensions['width'] = round($width);
     298            $dimensions['height'] = round($height);
     299        }
     300       
     301        // Restore error handling
     302        libxml_use_internal_errors($internal_errors);
     303
     304        return $dimensions;
     305    }
     306
     307    /**
     308     * Fix SVG thumbnail display
     309     */
     310    public static function fix_thumbnail_display($image, $attachment_id, $size, $icon) {
     311        if (get_post_mime_type($attachment_id) === 'image/svg+xml') {
     312            $image[0] = wp_get_attachment_url($attachment_id);
     313        }
     314        return $image;
     315    }
     316
     317    /**
     318     * Filter post thumbnail HTML for SVG images
     319     */
     320    public static function filter_post_thumbnail_html($html, $post_id, $post_thumbnail_id, $size, $attr) {
     321        if (get_post_mime_type($post_thumbnail_id) === 'image/svg+xml') {
     322            $attr = wp_parse_args($attr, array());
     323            $url = wp_get_attachment_url($post_thumbnail_id);
     324            $html = sprintf(
     325                '<img src="%s" %s>',
     326                esc_url($url),
     327                self::build_attributes($attr)
     328            );
     329        }
     330        return $html;
     331    }
     332
     333    /**
     334     * Filter content images for SVG
     335     */
     336    public static function filter_content_images($content) {
     337        if (!has_blocks($content)) {
     338            return $content;
     339        }
     340        return $content;
     341    }
     342
     343    /**
     344     * Filter image attributes for SVG
     345     */
     346    public static function filter_image_attributes($attr, $attachment) {
     347        if (get_post_mime_type($attachment->ID) === 'image/svg+xml') {
     348            $attr['class'] = isset($attr['class']) ? $attr['class'] . ' svg-image' : 'svg-image';
     349        }
     350        return $attr;
     351    }
     352
     353    /**
     354     * Handle SVG in media editor
     355     */
     356    public static function svg_media_editor_response() {
     357        if (isset($_POST['postid'])) {
     358            $post_id = intval($_POST['postid']);
     359            if (get_post_mime_type($post_id) === 'image/svg+xml') {
     360                wp_send_json_error(array(
     361                    'message' => __('SVG files cannot be edited directly in WordPress. Please use an SVG editor.', 'svgplus')
     362                ));
     363            }
     364        }
     365    }
     366
     367    /**
     368     * Handle SVG metadata generation
     369     */
     370    public static function svg_generate_metadata($metadata, $attachment_id) {
     371        if (get_post_mime_type($attachment_id) === 'image/svg+xml') {
     372            $svg_path = get_attached_file($attachment_id);
     373            $dimensions = self::get_svg_dimensions($svg_path);
     374           
     375            if ($dimensions) {
     376                $metadata = array(
     377                    'width' => $dimensions['width'],
     378                    'height' => $dimensions['height'],
     379                    'file' => _wp_relative_upload_path($svg_path),
     380                    'filesize' => filesize($svg_path),
     381                    'sizes' => array(),
     382                    'svg' => true
     383                );
     384            }
     385        }
     386        return $metadata;
     387    }
     388
     389    /**
     390     * Get SVG metadata for existing attachments
     391     */
     392    public static function svg_get_attachment_metadata($data, $attachment_id) {
     393        if (get_post_mime_type($attachment_id) === 'image/svg+xml') {
     394            if (empty($data)) {
     395                $data = self::svg_generate_metadata(array(), $attachment_id);
     396            }
     397            // Ensure we have the filesize
     398            if (empty($data['filesize'])) {
     399                $file = get_attached_file($attachment_id);
     400                if (file_exists($file)) {
     401                    $data['filesize'] = filesize($file);
     402                }
     403            }
     404        }
    57405        return $data;
    58406    }
    59407
    60408    /**
    61      * Handles the upload prefilter for SVGs, sanitizing the uploaded file.
    62      *
    63      * @param array $file The uploaded file data.
    64      * @return array Modified file data.
    65      */
    66     public static function handle_upload_prefilter($file) {
    67         // Make sure we're dealing with an SVG
    68         if ($file['type'] === 'image/svg+xml') {
    69 
    70             // Check user permissions
    71             $current_user = wp_get_current_user();
    72             $settings = get_option('svgplus_settings');
    73             $allowed_roles = isset($settings['allowed_roles']) ? $settings['allowed_roles'] : array();
    74 
    75             $has_allowed_role = false;
    76             foreach ($current_user->roles as $role) {
    77                 if (in_array($role, $allowed_roles)) {
    78                     $has_allowed_role = true;
    79                     break;
    80                 }
    81             }
    82 
    83             if (!$has_allowed_role) {
    84                 $file['error'] = __('You do not have permission to upload SVG files.', 'svgplus');
    85                 return $file;
    86             }
    87 
    88             global $wp_filesystem;
    89 
    90             // Initialize WP_Filesystem if not already done
    91             if (empty($wp_filesystem)) {
    92                 require_once ABSPATH . 'wp-admin/includes/file.php';
    93                 WP_Filesystem();
    94             }
    95 
    96             // Get SVG content using WP_Filesystem
    97             $svg_content = $wp_filesystem->get_contents($file['tmp_name']);
    98 
    99             if ($svg_content === false) {
    100                 $file['error'] = __('Unable to read SVG file.', 'svgplus');
    101                 return $file;
    102             }
    103 
    104             // Sanitize SVG
    105             $sanitized_svg = SVGPlus_Sanitizer::sanitize_svg($svg_content);
    106 
    107             if ($sanitized_svg === false) {
    108                 $file['error'] = __('Invalid SVG file.', 'svgplus');
    109                 return $file;
    110             }
    111 
    112             // Overwrite the temporary file with sanitized SVG using WP_Filesystem
    113             $result = $wp_filesystem->put_contents($file['tmp_name'], $sanitized_svg, FS_CHMOD_FILE);
    114 
    115             if ($result === false) {
    116                 $file['error'] = __('Failed to sanitize SVG file.', 'svgplus');
    117                 return $file;
    118             }
    119         }
    120 
    121         return $file;
     409     * Handle SVG in media send to editor
     410     */
     411    public static function svg_media_send_to_editor($html, $id, $caption, $title, $align, $url, $size, $alt) {
     412        if (get_post_mime_type($id) === 'image/svg+xml') {
     413            $dimensions = self::get_svg_dimensions(get_attached_file($id));
     414            if ($dimensions) {
     415                // Get the original aspect ratio
     416                $aspect_ratio = $dimensions['width'] / $dimensions['height'];
     417               
     418                // Get current dimensions
     419                $width = isset($_POST['width']) ? intval($_POST['width']) : $dimensions['width'];
     420                $height = isset($_POST['height']) ? intval($_POST['height']) : $dimensions['height'];
     421               
     422                // Maintain aspect ratio
     423                if (isset($_POST['width']) && !isset($_POST['height'])) {
     424                    $height = round($width / $aspect_ratio);
     425                } elseif (!isset($_POST['width']) && isset($_POST['height'])) {
     426                    $width = round($height * $aspect_ratio);
     427                }
     428               
     429                // Build attributes array
     430                $attributes = array(
     431                    'src' => $url,
     432                    'alt' => $alt,
     433                    'width' => $width,
     434                    'height' => $height,
     435                    'class' => 'svgplus-widget' . ($align ? " align$align" : ''),
     436                    'preserveAspectRatio' => 'xMidYMid meet'
     437                );
     438               
     439                // Create new HTML with proper attributes
     440                $html = sprintf(
     441                    '<img %s />',
     442                    self::build_attributes($attributes)
     443                );
     444               
     445                // Handle caption if present
     446                if ($caption) {
     447                    $html = "[caption id='attachment_$id' align='align$align' width='{$width}']" . $html . ' ' . $caption . '[/caption]';
     448                }
     449            }
     450        }
     451        return $html;
     452    }
     453
     454    /**
     455     * Build HTML attributes string
     456     */
     457    private static function build_attributes($attributes) {
     458        $html = '';
     459        foreach ($attributes as $name => $value) {
     460            if ($value === true) {
     461                $html .= ' ' . $name;
     462            } elseif ($value !== false && $value !== '') {
     463                $html .= sprintf(' %s="%s"', $name, esc_attr($value));
     464            }
     465        }
     466        return $html;
     467    }
     468
     469    /**
     470     * Disable image editors for SVG files
     471     */
     472    public static function disable_svg_image_editors($editors) {
     473        // Get the current file being processed
     474        $current_filter = current_filter();
     475       
     476        if ($current_filter === 'wp_image_editors') {
     477            // Check if we're processing an SVG file
     478            if (isset($_REQUEST['post_id'])) {
     479                $post_id = intval($_REQUEST['post_id']);
     480                if (get_post_mime_type($post_id) === 'image/svg+xml') {
     481                    return array();
     482                }
     483            }
     484           
     485            // Check file extension in upload if available
     486            if (isset($_FILES['async-upload']['name'])) {
     487                if (preg_match('/\.svg$/i', $_FILES['async-upload']['name'])) {
     488                    return array();
     489                }
     490            }
     491        }
     492       
     493        return $editors;
     494    }
     495
     496    /**
     497     * Register SVG block
     498     */
     499    public static function register_svg_block() {
     500        register_block_type(plugin_dir_path(SVGPLUS_FILE) . 'blocks/svg-block/block.json');
    122501    }
    123502}
  • svgplus/trunk/readme.txt

    r3168748 r3192812  
    1 === SVGPlus ===
     1=== SVG Plus ===
    22Contributors: Rizonepress
    3 Tags: svg, vector graphics, media upload, sanitization
    4 Requires at least: 5.0
    5 Tested up to: 6.6
    6 Stable tag: 1.1.0
     3Tags: svg, image, upload, media, gutenberg, block
     4Requires at least: 5.8
     5Tested up to: 6.4
     6Requires PHP: 7.4
     7Stable tag: 1.2.2
    78License: GPLv2 or later
    8 License URI: http://www.gnu.org/licenses/gpl-2.0.html
    9 Home Page: https://rizonepress.com
     9License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Short Description: Upload, sanitize, and display SVG files securely in WordPress with role-based upload permissions and custom CSS support.
     11Enhanced SVG upload and management for WordPress with sanitization, resizing, and Gutenberg block support.
    1212
    1313== Description ==
    1414
    15 **SVGPlus** is a WordPress plugin designed to securely manage SVG (Scalable Vector Graphics) files on your website. It allows for safe SVG uploads, automatic sanitization, and provides options to control which user roles can upload SVGs.
     15SVG Plus provides comprehensive SVG support for WordPress, allowing you to safely upload and manage SVG files in your media library. The plugin includes a custom Gutenberg block for enhanced SVG handling and maintains aspect ratios during resizing.
    1616
    17 ### Key Features
     17= Key Features =
    1818
    19 1. **Secure SVG Uploads with Automatic Sanitization**: Upload SVG files directly to your WordPress media library, with automatic sanitization to remove potentially harmful code.
    20 2. **Role-Based Upload Permissions**: Control which user roles are permitted to upload SVG files. Only administrators can modify these settings.
    21 3. **Option to Enable or Disable SVG Support**: Easily enable or disable SVG support across your site with a single switch in the settings.
    22 4. **Centralized Settings for Consistency and Control**: Access a dedicated settings page (`Settings > SVGPlus`) in the WordPress admin dashboard to configure plugin options.
    23 5. **Custom CSS Support**: Add global custom CSS to style all SVGs managed by SVGPlus, maintaining a consistent design aesthetic across your site.
     19* Safe SVG upload with comprehensive sanitization
     20* Custom Gutenberg block for SVG images
     21* Aspect ratio preservation during resize
     22* Media library integration
     23* Detailed error reporting and logging
     24* Comprehensive security features
     25
     26= Security Features =
     27
     28* SVG content sanitization
     29* Removal of potentially harmful elements and attributes
     30* Prevention of external entity loading
     31* Comprehensive error logging and reporting
     32
     33= Technical Features =
     34
     35* Custom Gutenberg block with resize controls
     36* Automatic dimension detection
     37* Proper MIME type handling
     38* Detailed debugging capabilities
     39* WordPress coding standards compliant
    2440
    2541== Installation ==
    2642
    27 1. **Upload the Plugin:**
    28    - Upload the `svgplus` folder to the `/wp-content/plugins/` directory.
    29 2. **Activate the Plugin:**
    30    - Activate the plugin through the 'Plugins' menu in WordPress.
    31 3. **Configure Settings:**
    32    - Navigate to `Settings > SVGPlus` in the WordPress admin dashboard to configure your SVG preferences.
     431. Upload the plugin files to `/wp-content/plugins/svgplus`
     442. Activate the plugin through the 'Plugins' screen in WordPress
     453. Use the Settings->SVG Plus screen to configure the plugin
    3346
    34 ### Usage
     47== Frequently Asked Questions ==
    3548
    36 #### Uploading and Managing SVGs
     49= Is it safe to upload SVG files? =
    3750
    38 1. **Upload SVGs:** Go to the WordPress media library (`Media > Add New`) and upload your SVG files as you would with any other media type.
    39 2. **Sanitized SVGs:** SVGPlus automatically sanitizes your SVG uploads to ensure they are safe and optimized for use on your website.
     51Yes, SVG Plus includes comprehensive security features that sanitize SVG files before allowing them to be uploaded.
    4052
    41 #### Configuring Plugin Settings
     53= Can I resize SVG files? =
    4254
    43 1. **Access Settings:** Navigate to `Settings > SVGPlus` in the WordPress admin dashboard.
    44 2. **Enable SVG Support:** Toggle the option to enable or disable SVG support across your site.
    45 3. **Select Allowed User Roles:** (Administrators only) Choose which user roles are permitted to upload SVG files to your site.
    46 4. **Add Custom CSS:** Input any custom CSS to style your SVGs globally.
     55Yes, the plugin includes a custom Gutenberg block that allows you to resize SVG files while maintaining their aspect ratio.
    4756
    48 ## Changelog
     57= Where can I find error logs? =
     58
     59The plugin creates detailed logs in wp-content/uploads/svgplus-debug.log for troubleshooting purposes.
     60
     61== Changelog ==
     62
     63= 1.2.2 =
     64* Added comprehensive logging system for better debugging
     65* Enhanced SVG file validation and sanitization
     66* Improved error handling and reporting
     67* Added detailed upload process logging
     68* Enhanced mime type detection with content validation
     69* Added proper namespace handling for SVG files
     70* Improved security with LIBXML_NONET flag
     71* Added better error messages for users
     72* Fixed various upload handling issues
     73* Added file permission handling for logs
     74
     75= 1.1.2 =
     76* Added custom Gutenberg block for SVG handling
     77* Improved SVG sanitization process
     78* Enhanced media library integration
     79* Added aspect ratio preservation
     80* Fixed dimension detection issues
     81
     82= 1.1.1 =
     83* Initial security improvements
     84* Basic SVG upload handling
     85* Simple media library integration
    4986
    5087= 1.1.0 =
     88* Initial release
    5189
    52 - Fixed issue where the blue background of switches did not change to orange when SVG support was disabled.
    53 - Restricted modification of allowed roles to Administrators only.
    54 - Removed the "Custom CSS" main label and adjusted the codebox to span the full width of the row.
    55 - Updated plugin version and aligned documentation to reflect current features.
     90== Upgrade Notice ==
    5691
    57 = 1.0.14 =
     92= 1.2.2 =
     93This version adds comprehensive logging and improved security features. It's recommended for all users to update for better SVG handling and troubleshooting capabilities.
    5894
    59 - General UI Enhancements
    60 - Improved responsiveness of the settings page to ensure better usability across different devices.
     95= 1.1.2 =
     96This version adds Gutenberg block support and improves SVG handling. Upgrade for better SVG management capabilities.
    6197
    62 = 1.0.13 =
     98== Additional Information ==
    6399
    64 - Switched to using the `enshrined/svg-sanitize` library for SVG sanitization.
    65 - Ensured "Allow SVG Animations" setting functions correctly with the new sanitizer.
    66 - Default allowed roles for SVG uploads are Administrator, Editor, and Author.
    67 - Confirmed custom CSS settings are applied correctly to SVG images.
    68 
    69 = 1.0.12 =
    70 
    71 - Comprehensive SVG Element Support: Expanded the list of allowed SVG elements and attributes in the sanitizer to include a complete set of SVG elements, including all filter elements and animation elements. This ensures compatibility with a wider range of SVG files and features.
    72 - Enhanced Animation Support: Completed the inclusion of all animation elements and their attributes, allowing for full support of SVG animations when enabled in the settings.
    73 - Improved Sanitization Logic: Updated the SVG sanitizer to support advanced SVG features like filters and animations while maintaining strict security measures. The sanitizer now properly handles a more extensive range of elements and attributes.
    74 - Security Enhancements: Ensured that the expanded support does not compromise security by maintaining robust sanitization and validation of SVG content.
    75 
    76 = 1.0.8 =
    77 
    78 * Shortcode Enhancement: Added support for the lazy attribute in the shortcode to control lazy loading of SVGs. Users can now enable or disable lazy loading per SVG by setting lazy="true" or lazy="false" in the shortcode.
    79 * Global Custom CSS Application: Modified the plugin to enqueue custom CSS globally from the settings page. This ensures that custom styles are applied consistently to all SVGs without needing to append CSS in each shortcode instance.
    80 * Conditional Animation Support: Updated the SVG sanitization process to conditionally allow animation elements and attributes based on the 'Allow SVG Animations' setting in the plugin settings. When enabled, the sanitizer permits elements like <animate>, <animateTransform>, and their associated attributes.
    81 * Improved Sanitization Logic: Enhanced the SVG sanitizer to dynamically adjust allowed elements and attributes based on settings, improving security and flexibility.
    82 * Code Optimizations: Refactored code for better performance and maintainability, including optimizing the shortcode rendering process and reducing redundant code.
    83 * Documentation Updates: Updated the readme file and usage instructions to reflect the new features and provide clearer guidance on how to use the plugin.
    84 
    85 = 1.0.7 =
    86 
    87 * Security Enhancements: Escaped output functions to prevent security vulnerabilities.
    88 * Filesystem Operations: Replaced `file_get_contents()` with `WP_Filesystem::get_contents()` and Replaced `file_put_contents()` with `WP_Filesystem::put_contents()`.
    89 
    90 = 1.0.6 =
    91 
    92 * Refined plugin to remove the dedicated Elementor widget while enhancing SVG upload compatibility with Elementor's native widgets.
    93 * Improved sanitization process for SVG uploads.
    94 * Enhanced shortcode functionality with additional customization options.
    95 * Updated settings page for better user experience.
    96 * Fixed minor bugs and improved performance.
    97 
    98 = 1.0.3 =
    99 
    100 * Added lazy loading support for SVG images.
    101 * Introduced custom CSS options in the settings page.
    102 * Enhanced compatibility with the latest WordPress and Elementor versions.
    103 
    104 = 1.0.2 =
    105 
    106 * Initial release with core functionalities.
    107 
    108 ## Upgrade Notice
    109 
    110 = 1.1.0 =
    111 
    112 Please update to this version to fix the switch background color issue, improve settings notifications, and enhance security by restricting role modifications to administrators.
    113 
    114 == License ==
    115 
    116 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2.
     100For support or feature requests, please visit our website at https://rizonetech.com
  • svgplus/trunk/svgplus.php

    r3168748 r3192812  
    11<?php
    22/**
    3  * Plugin Name: SVGPlus
    4  * Description: Upload, sanitize, and display SVG files securely in WordPress.
    5  * Version: 1.1.0
    6  * Author: Rizonepress
    7  * License: GPL2
     3 * Plugin Name: SVG Plus
     4 * Plugin URI: https://wordpress.org/plugins/svgplus/
     5 * Description: Enhanced SVG upload and management for WordPress with sanitization, resizing, and Gutenberg block support.
     6 * Version: 1.2.2
     7 * Requires at least: 5.8
     8 * Requires PHP: 7.4
     9 * Author: Rizone
     10 * Author URI: https://rizonetech.com
     11 * License: GPL v2 or later
     12 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
     13 * Text Domain: svgplus
     14 * Domain Path: /languages
    815 */
    916
     17// If this file is called directly, abort
    1018if (!defined('ABSPATH')) {
    1119    exit;
    1220}
    1321
    14 // Include Composer's autoloader if it exists
    15 if (file_exists(__DIR__ . '/vendor/autoload.php')) {
    16     require_once __DIR__ . '/vendor/autoload.php';
    17 } else {
    18     error_log('SVGPlus: Composer autoloader not found. Please ensure dependencies are installed.');
    19     return;
     22// Define plugin constants
     23define('SVGPLUS_VERSION', '1.2.2');
     24define('SVGPLUS_FILE', __FILE__);
     25define('SVGPLUS_PATH', plugin_dir_path(__FILE__));
     26define('SVGPLUS_URL', plugin_dir_url(__FILE__));
     27
     28// Load required files
     29require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-logger.php';
     30require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-security.php';
     31require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-sanitizer.php';
     32require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-upload.php';
     33require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-settings.php';
     34
     35// Initialize plugin
     36add_action('plugins_loaded', 'svgplus_init');
     37
     38function svgplus_init() {
     39    // Initialize components
     40    SVGPlus_Security::init();
     41    SVGPlus_Upload::init();
     42    SVGPlus_Settings::init();
     43
     44    // Load translations
     45    load_plugin_textdomain('svgplus', false, dirname(plugin_basename(__FILE__)) . '/languages');
    2046}
    2147
    22 // Include necessary classes
    23 $required_classes = [
    24     'includes/class-svgplus-sanitizer.php',
    25     'includes/class-svgplus-upload.php',
    26     'includes/class-svgplus-settings.php',  // Ensure settings class is included
    27 ];
     48// Register block scripts and styles
     49function svgplus_register_block_assets() {
     50    $asset_file = include(SVGPLUS_PATH . 'blocks/svg-block/build/index.asset.php');
     51   
     52    wp_register_script(
     53        'svgplus-block-editor',
     54        SVGPLUS_URL . 'blocks/svg-block/build/index.js',
     55        $asset_file['dependencies'],
     56        $asset_file['version']
     57    );
    2858
    29 foreach ($required_classes as $class_file) {
    30     $file_path = plugin_dir_path(__FILE__) . $class_file;
    31     if (file_exists($file_path)) {
    32         require_once $file_path;
    33     } else {
    34         error_log('SVGPlus: ' . basename($class_file) . ' file not found.');
    35         return;
    36     }
     59    wp_register_style(
     60        'svgplus-block-style',
     61        SVGPLUS_URL . 'blocks/svg-block/build/style-style.css',
     62        array(),
     63        $asset_file['version']
     64    );
    3765}
     66add_action('init', 'svgplus_register_block_assets');
    3867
    39 // Add the default settings function
    40 function svgplus_default_settings() {
    41     return [
    42         'allowed_roles' => ['administrator', 'editor'],  // Default allowed roles for uploads
    43         'allow_animations' => true,  // Enable animations by default
    44         'custom_css' => '',  // Custom CSS field, initially empty
    45         'enable_svg_support' => 1, // Added default setting for SVG support
    46     ];
    47 }
     68// Register activation hook
     69register_activation_hook(__FILE__, 'svgplus_activate');
    4870
    49 // Set up the plugin activation hook to ensure default settings are added
    50 function svgplus_activate_plugin() {
    51     $default_settings = svgplus_default_settings();
     71function svgplus_activate() {
     72    // Add default options
     73    $default_settings = array(
     74        'enable_svg_support' => 1,
     75        'sanitize_uploads' => 1,
     76        'preserve_aspect_ratio' => 1
     77    );
     78   
    5279    if (!get_option('svgplus_settings')) {
    5380        add_option('svgplus_settings', $default_settings);
    5481    }
    55 }
    56 register_activation_hook(__FILE__, 'svgplus_activate_plugin');
    57 
    58 // Initialize the Settings class to make sure the settings page is loaded
    59 if (class_exists('SVGPlus_Settings')) {
    60     new SVGPlus_Settings();  // Load the settings page
     82   
     83    // Clear rewrite rules
     84    flush_rewrite_rules();
    6185}
    6286
    63 // Initialize the upload process
    64 SVGPlus_Upload::init();
     87// Register deactivation hook
     88register_deactivation_hook(__FILE__, 'svgplus_deactivate');
    6589
    66 // Force allow SVG uploads based on the 'Enable SVG Support' setting
    67 function svgplus_allow_svg_uploads($existing_mimes) {
    68     $options = get_option('svgplus_settings');
    69     $is_svg_enabled = isset($options['enable_svg_support']) ? $options['enable_svg_support'] : 0;
    70 
    71     if ($is_svg_enabled) {
    72         // Add the SVG mime type
    73         $existing_mimes['svg'] = 'image/svg+xml';
    74         $existing_mimes['svgz'] = 'image/svg+xml'; // For compressed SVG
    75     } else {
    76         // Remove SVG mime types if present
    77         unset($existing_mimes['svg']);
    78         unset($existing_mimes['svgz']);
    79     }
    80 
    81     return $existing_mimes;
    82 }
    83 add_filter('upload_mimes', 'svgplus_allow_svg_uploads');
    84 
    85 // Bypass MIME type checks only when SVG support is enabled
    86 function svgplus_disable_real_mime_check($data, $file, $filename, $mimes) {
    87     $options = get_option('svgplus_settings');
    88     $is_svg_enabled = isset($options['enable_svg_support']) ? $options['enable_svg_support'] : 0;
    89 
    90     if (!$is_svg_enabled) {
    91         return $data;
    92     }
    93 
    94     $ext = pathinfo($filename, PATHINFO_EXTENSION);
    95    
    96     if ($ext === 'svg' || $ext === 'svgz') {
    97         $data['ext'] = 'svg';
    98         $data['type'] = 'image/svg+xml';
    99     }
    100 
    101     return $data;
    102 }
    103 add_filter('wp_check_filetype_and_ext', 'svgplus_disable_real_mime_check', 10, 4);
    104 
    105 // Update user roles based on settings
    106 function svgplus_user_roles_can_upload($user) {
    107     $options = get_option('svgplus_settings');
    108     $allowed_roles = isset($options['allowed_roles']) ? $options['allowed_roles'] : array();
    109    
    110     foreach ($allowed_roles as $role) {
    111         if (in_array($role, $user->roles)) {
    112             return true;
    113         }
    114     }
    115    
    116     return false;
     90function svgplus_deactivate() {
     91    // Clear rewrite rules
     92    flush_rewrite_rules();
    11793}
    11894
    119 // Adjust permissions only when SVG support is enabled
    120 add_action('admin_init', function() {
    121     $options = get_option('svgplus_settings');
    122     $is_svg_enabled = isset($options['enable_svg_support']) ? $options['enable_svg_support'] : 0;
     95// Add settings link to plugins page
     96add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'svgplus_add_plugin_links');
    12397
    124     if ($is_svg_enabled) {
    125         if (!current_user_can('upload_files')) {
    126             add_filter('user_has_cap', function($caps, $cap, $user_id) {
    127                 $user = new WP_User($user_id);
    128                 if (svgplus_user_roles_can_upload($user)) {
    129                     $caps['upload_files'] = true;
    130                 }
    131                 return $caps;
    132             }, 10, 3);
    133         }
    134     }
    135 });
     98function svgplus_add_plugin_links($links) {
     99    $settings_link = '<a href="' . admin_url('options-general.php?page=svgplus-settings') . '">' . __('Settings', 'svgplus') . '</a>';
     100    array_unshift($links, $settings_link);
     101    return $links;
     102}
Note: See TracChangeset for help on using the changeset viewer.