PluginProbe ʕ •ᴥ•ʔ
Safe SVG / 1.5.3
Safe SVG v1.5.3
trunk 1.0.0 1.1.0 1.1.1 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5.0 1.5.1 1.5.2 1.5.3 1.6.0 1.6.1 1.7.1 1.8.0 1.8.1 1.9.0 1.9.1 1.9.10 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0
safe-svg / safe-svg.php
safe-svg Last commit date
assets 8 years ago languages 8 years ago lib 8 years ago licence.txt 8 years ago readme.txt 8 years ago safe-svg.php 8 years ago
safe-svg.php
326 lines
1 <?php
2 /*
3 Plugin Name: Safe SVG
4 Plugin URI: https://wpsvg.com/
5 Description: Allows SVG uploads into WordPress and sanitizes the SVG before saving it
6 Version: 1.5.3
7 Author: Daryll Doyle
8 Author URI: http://enshrined.co.uk
9 Text Domain: safe-svg
10 Domain Path: /languages
11 */
12
13 defined( 'ABSPATH' ) or die( 'Really?' );
14
15 require 'lib/vendor/autoload.php';
16
17 if ( ! class_exists( 'safe_svg' ) ) {
18
19 /**
20 * Class safe_svg
21 */
22 Class safe_svg {
23
24 /**
25 * The sanitizer
26 *
27 * @var \enshrined\svgSanitize\Sanitizer
28 */
29 protected $sanitizer;
30
31 /**
32 * Set up the class
33 */
34 function __construct() {
35 $this->sanitizer = new enshrined\svgSanitize\Sanitizer();
36 $this->sanitizer->minify( true );
37
38 add_filter( 'upload_mimes', array( $this, 'allow_svg' ) );
39 add_filter( 'wp_handle_upload_prefilter', array( $this, 'check_for_svg' ) );
40 add_filter( 'wp_check_filetype_and_ext', array( $this, 'fix_mime_type_svg' ), 75, 4 );
41 add_filter( 'wp_prepare_attachment_for_js', array( $this, 'fix_admin_preview' ), 10, 3 );
42 add_filter( 'wp_get_attachment_image_src', array( $this, 'one_pixel_fix' ), 10, 4 );
43 add_filter( 'admin_post_thumbnail_html', array( $this, 'featured_image_fix' ), 10, 3 );
44 add_action( 'admin_enqueue_scripts', array( $this, 'load_custom_admin_style' ) );
45 add_action( 'get_image_tag', array( $this, 'get_image_tag_override' ), 10, 6 );
46 add_filter( 'wp_generate_attachment_metadata', array( $this, 'skip_svg_regeneration' ), 10, 2 );
47 add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), array( $this, 'add_upgrade_link' ) );
48 add_filter( 'wp_get_attachment_metadata', array( $this, 'metadata_error_fix' ), 10, 2 );
49 }
50
51 /**
52 * Allow SVG Uploads
53 *
54 * @param $mimes
55 *
56 * @return mixed
57 */
58 public function allow_svg( $mimes ) {
59 $mimes['svg'] = 'image/svg+xml';
60 $mimes['svgz'] = 'image/svg+xml';
61
62 return $mimes;
63 }
64
65 /**
66 * Fixes the issue in WordPress 4.7.1 being unable to correctly identify SVGs
67 *
68 * @thanks @lewiscowles
69 *
70 * @param null $data
71 * @param null $file
72 * @param null $filename
73 * @param null $mimes
74 *
75 * @return null
76 */
77 public function fix_mime_type_svg( $data = null, $file = null, $filename = null, $mimes = null ) {
78 $ext = isset( $data['ext'] ) ? $data['ext'] : '';
79 if ( strlen( $ext ) < 1 ) {
80 $exploded = explode( '.', $filename );
81 $ext = strtolower( end( $exploded ) );
82 }
83 if ( $ext === 'svg' ) {
84 $data['type'] = 'image/svg+xml';
85 $data['ext'] = 'svg';
86 } elseif ( $ext === 'svgz' ) {
87 $data['type'] = 'image/svg+xml';
88 $data['ext'] = 'svgz';
89 }
90
91 return $data;
92 }
93
94 /**
95 * Check if the file is an SVG, if so handle appropriately
96 *
97 * @param $file
98 *
99 * @return mixed
100 */
101 public function check_for_svg( $file ) {
102
103 if ( $file['type'] === 'image/svg+xml' ) {
104 if ( ! $this->sanitize( $file['tmp_name'] ) ) {
105 $file['error'] = __( "Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded",
106 'safe-svg' );
107 }
108 }
109
110 return $file;
111 }
112
113 /**
114 * Sanitize the SVG
115 *
116 * @param $file
117 *
118 * @return bool|int
119 */
120 protected function sanitize( $file ) {
121 $dirty = file_get_contents( $file );
122
123 // Is the SVG gzipped? If so we try and decode the string
124 if ( $is_zipped = $this->is_gzipped( $dirty ) ) {
125 $dirty = gzdecode( $dirty );
126
127 // If decoding fails, bail as we're not secure
128 if ( $dirty === false ) {
129 return false;
130 }
131 }
132
133 $clean = $this->sanitizer->sanitize( $dirty );
134
135 if ( $clean === false ) {
136 return false;
137 }
138
139 // If we were gzipped, we need to re-zip
140 if ( $is_zipped ) {
141 $clean = gzencode( $clean );
142 }
143
144 file_put_contents( $file, $clean );
145
146 return true;
147 }
148
149 /**
150 * Check if the contents are gzipped
151 *
152 * @see http://www.gzip.org/zlib/rfc-gzip.html#member-format
153 *
154 * @param $contents
155 *
156 * @return bool
157 */
158 protected function is_gzipped( $contents ) {
159 if ( function_exists( 'mb_strpos' ) ) {
160 return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" );
161 } else {
162 return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" );
163 }
164 }
165
166 /**
167 * Filters the attachment data prepared for JavaScript to add the sizes array to the response
168 *
169 * @param array $response Array of prepared attachment data.
170 * @param int|object $attachment Attachment ID or object.
171 * @param array $meta Array of attachment meta data.
172 *
173 * @return array
174 */
175 public function fix_admin_preview( $response, $attachment, $meta ) {
176
177 if ( $response['mime'] == 'image/svg+xml' ) {
178 $possible_sizes = apply_filters( 'image_size_names_choose', array(
179 'thumbnail' => __( 'Thumbnail' ),
180 'medium' => __( 'Medium' ),
181 'large' => __( 'Large' ),
182 'full' => __( 'Full Size' ),
183 ) );
184
185 $sizes = array();
186
187 foreach ( $possible_sizes as $size => $label ) {
188 $sizes[ $size ] = array(
189 'height' => 2000,
190 'width' => 2000,
191 'url' => $response['url'],
192 'orientation' => 'portrait',
193 );
194 }
195
196 $response['sizes'] = $sizes;
197 $response['icon'] = $response['url'];
198 }
199
200 return $response;
201 }
202
203 /**
204 * Filters the image src result.
205 * Here we're gonna spoof the image size and set it to 100 width and height
206 *
207 * @param array|false $image Either array with src, width & height, icon src, or false.
208 * @param int $attachment_id Image attachment ID.
209 * @param string|array $size Size of image. Image size or array of width and height values
210 * (in that order). Default 'thumbnail'.
211 * @param bool $icon Whether the image should be treated as an icon. Default false.
212 *
213 * @return array
214 */
215 public function one_pixel_fix( $image, $attachment_id, $size, $icon ) {
216 if ( get_post_mime_type( $attachment_id ) == 'image/svg+xml' ) {
217 $image['1'] = false;
218 $image['2'] = false;
219 }
220
221 return $image;
222 }
223
224 /**
225 * If the featured image is an SVG we wrap it in an SVG class so we can apply our CSS fix.
226 *
227 * @param string $content Admin post thumbnail HTML markup.
228 * @param int $post_id Post ID.
229 * @param int $thumbnail_id Thumbnail ID.
230 *
231 * @return string
232 */
233 public function featured_image_fix( $content, $post_id, $thumbnail_id ) {
234 $mime = get_post_mime_type( $thumbnail_id );
235
236 if ( 'image/svg+xml' === $mime ) {
237 $content = sprintf( '<span class="svg">%s</span>', $content );
238 }
239
240 return $content;
241 }
242
243 /**
244 * Load our custom CSS sheet.
245 */
246 function load_custom_admin_style() {
247 wp_enqueue_style( 'safe-svg-css', plugins_url( 'assets/safe-svg.css', __FILE__ ), array() );
248 }
249
250 /**
251 * Override the default height and width string on an SVG
252 *
253 * @param string $html HTML content for the image.
254 * @param int $id Attachment ID.
255 * @param string $alt Alternate text.
256 * @param string $title Attachment title.
257 * @param string $align Part of the class name for aligning the image.
258 * @param string|array $size Size of image. Image size or array of width and height values (in that order).
259 * Default 'medium'.
260 *
261 * @return mixed
262 */
263 function get_image_tag_override( $html, $id, $alt, $title, $align, $size ) {
264 $mime = get_post_mime_type( $id );
265
266 if ( 'image/svg+xml' === $mime ) {
267 $html = str_replace( 'width="1" ', '', $html );
268 $html = str_replace( 'height="1" ', '', $html );
269 }
270
271 return $html;
272 }
273
274 /**
275 * Skip regenerating SVGs
276 *
277 * @param int $attachment_id Attachment Id to process.
278 * @param string $file Filepath of the Attached image.
279 *
280 * @return mixed Metadata for attachment.
281 */
282 function skip_svg_regeneration( $metadata, $attachment_id ) {
283 if ( 'image/svg+xml' === get_post_mime_type( $attachment_id ) ) {
284 // return new WP_Error( 'skip_svg_generate', __( 'Skipping SVG file.', 'safe-svg' ) );
285 }
286
287 return $metadata;
288 }
289
290 /**
291 * Add in an upgrade link for Safe SVG
292 *
293 * @param $links
294 *
295 * @return array
296 */
297 function add_upgrade_link( $links ) {
298 $mylinks = array(
299 '<a target="_blank" style="color:#3db634;" href="https://wpsvg.com/?utm_source=plugin-list&utm_medium=upgrade-link&utm_campaign=plugin-list&utm_content=action-link">Upgrade</a>',
300 );
301
302 return array_merge( $links, $mylinks );
303 }
304
305 /**
306 * Filters the attachment meta data.
307 *
308 * @param array|bool $data Array of meta data for the given attachment, or false
309 * if the object does not exist.
310 * @param int $post_id Attachment ID.
311 */
312 function metadata_error_fix( $data, $post_id ) {
313
314 // If it's a WP_Error regenerate metadata and save it
315 if ( is_wp_error( $data ) ) {
316 $data = wp_generate_attachment_metadata( $post_id, get_attached_file( $post_id ) );
317 wp_update_attachment_metadata( $post_id, $data );
318 }
319
320 return $data;
321 }
322
323 }
324 }
325
326 $safe_svg = new safe_svg();