Changeset 3192812
- Timestamp:
- 11/20/2024 07:00:11 AM (4 months ago)
- Location:
- svgplus
- Files:
-
- 127 added
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
svgplus/trunk/assets/css/svgplus.css
r3165060 r3192812 1 1 .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 */ 4 4 height: auto; /* Maintain aspect ratio */ 5 position: relative; /* For resize handles */ 5 6 } 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; } 6 49 7 50 /* Animation on hover */ -
svgplus/trunk/assets/js/svgplus.js
r3165060 r3192812 1 1 // SVGPlus Plugin JavaScript 2 jQuery(document).ready(function($) { 3 'use strict'; 2 4 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 }, 5 11 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(); 11 123 }); 12 124 }); -
svgplus/trunk/composer.json
r3165214 r3192812 2 2 "name": "derickpayne/svgplus", 3 3 "description": "A WordPress plugin to securely upload and display SVG files.", 4 "type": " library",4 "type": "wordpress-plugin", 5 5 "license": "GPL-2.0-or-later", 6 "minimum-stability": "stable",7 6 "require": { 8 " enshrined/svg-sanitize": "^0.20.0"7 "php": ">=7.4" 9 8 } 10 9 } -
svgplus/trunk/includes/class-svgplus-sanitizer.php
r3168748 r3192812 6 6 } 7 7 8 use enshrined\svgSanitize\Sanitizer;9 10 8 class SVGPlus_Sanitizer { 11 9 12 10 /** 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. 14 184 * 15 185 * @param string $svg_content The raw SVG content. 16 186 * @return string|false The sanitized SVG content or false on failure. 17 187 */ 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)) { 27 190 return false; 28 191 } 29 192 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; 31 253 } 32 254 } -
svgplus/trunk/includes/class-svgplus-settings.php
r3168748 r3192812 7 7 8 8 class 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() { 11 22 add_action('admin_menu', array($this, 'add_settings_menu')); 12 23 add_action('admin_init', array($this, 'register_settings')); … … 19 30 public function add_settings_menu() { 20 31 add_options_page( 21 __('SVG Plus Settings', 'svgplus'), // Page title22 __('SVG Plus', 'svgplus'), // Menu title32 __('SVG Plus Settings', 'svgplus'), // Page title 33 __('SVG Plus', 'svgplus'), // Menu title 23 34 'manage_options', // Capability 24 35 'svgplus-settings', // Menu slug … … 114 125 */ 115 126 public function main_section_callback() { 116 echo esc_html__('Configure the main settings for SVG Plus.', 'svgplus');127 echo esc_html__('Configure the main settings for SVG Plus.', 'svgplus'); 117 128 } 118 129 … … 224 235 <div class="wrap"> 225 236 <h1> 226 <img src="<?php echo esc_url($icon_url); ?>" alt="SVG Plus Icon" class="svgplus-settings-icon" />227 <?php esc_html_e('SVG Plus 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'); ?> 228 239 </h1> 229 240 -
svgplus/trunk/includes/class-svgplus-upload.php
r3168748 r3192812 7 7 8 8 class SVGPlus_Upload { 9 9 private static $sanitizer = null; 10 private static $logger = null; 11 12 /** 13 * Initialize the upload handler 14 */ 10 15 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 12 23 $options = get_option('svgplus_settings'); 13 24 $is_svg_enabled = isset($options['enable_svg_support']) ? $options['enable_svg_support'] : 0; 14 25 15 26 if ($is_svg_enabled) { 16 // Allow SVG mime types 27 // Register SVG block 28 add_action('init', array(__CLASS__, 'register_svg_block')); 29 17 30 add_filter('upload_mimes', array(__CLASS__, 'add_svg_mime_type')); 18 // Fix MIME type and file extension checks for SVGs19 31 add_filter('wp_check_filetype_and_ext', array(__CLASS__, 'fix_mime_type_svg'), 10, 4); 20 // Handle file upload prefilter for SVG sanitization21 32 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 31 58 */ 32 59 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 47 72 */ 48 73 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 } 57 405 return $data; 58 406 } 59 407 60 408 /** 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'); 122 501 } 123 502 } -
svgplus/trunk/readme.txt
r3168748 r3192812 1 === SVG Plus ===1 === SVG Plus === 2 2 Contributors: 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 3 Tags: svg, image, upload, media, gutenberg, block 4 Requires at least: 5.8 5 Tested up to: 6.4 6 Requires PHP: 7.4 7 Stable tag: 1.2.2 7 8 License: GPLv2 or later 8 License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 Home Page: https://rizonepress.com 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 Short Description: Upload, sanitize, and display SVG files securely in WordPress with role-based upload permissions and custom CSSsupport.11 Enhanced SVG upload and management for WordPress with sanitization, resizing, and Gutenberg block support. 12 12 13 13 == Description == 14 14 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.15 SVG 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. 16 16 17 ### Key Features 17 = Key Features = 18 18 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 24 40 25 41 == Installation == 26 42 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. 43 1. Upload the plugin files to `/wp-content/plugins/svgplus` 44 2. Activate the plugin through the 'Plugins' screen in WordPress 45 3. Use the Settings->SVG Plus screen to configure the plugin 33 46 34 ### Usage 47 == Frequently Asked Questions == 35 48 36 #### Uploading and Managing SVGs 49 = Is it safe to upload SVG files? = 37 50 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. 51 Yes, SVG Plus includes comprehensive security features that sanitize SVG files before allowing them to be uploaded. 40 52 41 #### Configuring Plugin Settings 53 = Can I resize SVG files? = 42 54 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. 55 Yes, the plugin includes a custom Gutenberg block that allows you to resize SVG files while maintaining their aspect ratio. 47 56 48 ## Changelog 57 = Where can I find error logs? = 58 59 The 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 49 86 50 87 = 1.1.0 = 88 * Initial release 51 89 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 == 56 91 57 = 1.0.14 = 92 = 1.2.2 = 93 This version adds comprehensive logging and improved security features. It's recommended for all users to update for better SVG handling and troubleshooting capabilities. 58 94 59 - General UI Enhancements 60 - Improved responsiveness of the settings page to ensure better usability across different devices.95 = 1.1.2 = 96 This version adds Gutenberg block support and improves SVG handling. Upgrade for better SVG management capabilities. 61 97 62 = 1.0.13=98 == Additional Information == 63 99 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. 100 For support or feature requests, please visit our website at https://rizonetech.com -
svgplus/trunk/svgplus.php
r3168748 r3192812 1 1 <?php 2 2 /** 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 8 15 */ 9 16 17 // If this file is called directly, abort 10 18 if (!defined('ABSPATH')) { 11 19 exit; 12 20 } 13 21 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 23 define('SVGPLUS_VERSION', '1.2.2'); 24 define('SVGPLUS_FILE', __FILE__); 25 define('SVGPLUS_PATH', plugin_dir_path(__FILE__)); 26 define('SVGPLUS_URL', plugin_dir_url(__FILE__)); 27 28 // Load required files 29 require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-logger.php'; 30 require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-security.php'; 31 require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-sanitizer.php'; 32 require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-upload.php'; 33 require_once plugin_dir_path(__FILE__) . 'includes/class-svgplus-settings.php'; 34 35 // Initialize plugin 36 add_action('plugins_loaded', 'svgplus_init'); 37 38 function 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'); 20 46 } 21 47 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 49 function 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 ); 28 58 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 ); 37 65 } 66 add_action('init', 'svgplus_register_block_assets'); 38 67 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 69 register_activation_hook(__FILE__, 'svgplus_activate'); 48 70 49 // Set up the plugin activation hook to ensure default settings are added 50 function svgplus_activate_plugin() { 51 $default_settings = svgplus_default_settings(); 71 function 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 52 79 if (!get_option('svgplus_settings')) { 53 80 add_option('svgplus_settings', $default_settings); 54 81 } 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(); 61 85 } 62 86 63 // Initialize the upload process64 SVGPlus_Upload::init();87 // Register deactivation hook 88 register_deactivation_hook(__FILE__, 'svgplus_deactivate'); 65 89 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; 90 function svgplus_deactivate() { 91 // Clear rewrite rules 92 flush_rewrite_rules(); 117 93 } 118 94 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 96 add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'svgplus_add_plugin_links'); 123 97 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 }); 98 function 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.