Safe SVG - Version 1.5.2

Version Description

  • Tested with 4.9.0
  • Fixed an issue with SVGs when regenerating media
Download this release

Release Info

Developer enshrined
Plugin Icon 128x128 Safe SVG
Version 1.5.2
Comparing to
See all releases

Code changes from version 1.5.1 to 1.5.2

Files changed (2) hide show
  1. readme.txt +6 -2
  2. safe-svg.php +286 -253
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: enshrined
3
  Donate link: https://wpsvg.com/
4
  Tags: svg, sanitize, upload, sanitise, security, svg upload, image, vector, file, graphic, media, mime
5
  Requires at least: 4.0
6
- Tested up to: 4.8.1
7
- Stable tag: 1.5.1
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
@@ -39,6 +39,10 @@ Install through the WordPress directory or download, unzip and upload the files
39
 
40
  == Changelog ==
41
 
 
 
 
 
42
  = 1.5.1 =
43
  * Fix PHP strict standards warning
44
 
3
  Donate link: https://wpsvg.com/
4
  Tags: svg, sanitize, upload, sanitise, security, svg upload, image, vector, file, graphic, media, mime
5
  Requires at least: 4.0
6
+ Tested up to: 4.9.0
7
+ Stable tag: 1.5.2
8
  License: GPLv2 or later
9
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
 
39
 
40
  == Changelog ==
41
 
42
+ = 1.5.2 =
43
+ * Tested with 4.9.0
44
+ * Fixed an issue with SVGs when regenerating media
45
+
46
  = 1.5.1 =
47
  * Fix PHP strict standards warning
48
 
safe-svg.php CHANGED
@@ -3,7 +3,7 @@
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.1
7
  Author: Daryll Doyle
8
  Author URI: http://enshrined.co.uk
9
  Text Domain: safe-svg
@@ -16,258 +16,291 @@ 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
- }
47
-
48
- /**
49
- * Allow SVG Uploads
50
- *
51
- * @param $mimes
52
- *
53
- * @return mixed
54
- */
55
- public function allow_svg( $mimes ) {
56
- $mimes['svg'] = 'image/svg+xml';
57
- $mimes['svgz'] = 'image/svg+xml';
58
-
59
- return $mimes;
60
- }
61
-
62
- /**
63
- * Fixes the issue in WordPress 4.7.1 being unable to correctly identify SVGs
64
- *
65
- * @thanks @lewiscowles
66
- *
67
- * @param null $data
68
- * @param null $file
69
- * @param null $filename
70
- * @param null $mimes
71
- *
72
- * @return null
73
- */
74
- public function fix_mime_type_svg( $data = null, $file = null, $filename = null, $mimes = null ) {
75
- $ext = isset( $data['ext'] ) ? $data['ext'] : '';
76
- if ( strlen( $ext ) < 1 ) {
77
- $exploded = explode( '.', $filename );
78
- $ext = strtolower( end( $exploded ) );
79
- }
80
- if ( $ext === 'svg' ) {
81
- $data['type'] = 'image/svg+xml';
82
- $data['ext'] = 'svg';
83
- } elseif ( $ext === 'svgz' ) {
84
- $data['type'] = 'image/svg+xml';
85
- $data['ext'] = 'svgz';
86
- }
87
-
88
- return $data;
89
- }
90
-
91
- /**
92
- * Check if the file is an SVG, if so handle appropriately
93
- *
94
- * @param $file
95
- *
96
- * @return mixed
97
- */
98
- public function check_for_svg( $file ) {
99
-
100
- if ( $file['type'] === 'image/svg+xml' ) {
101
- if ( ! $this->sanitize( $file['tmp_name'] ) ) {
102
- $file['error'] = __( "Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded",
103
- 'safe-svg' );
104
- }
105
- }
106
-
107
- return $file;
108
- }
109
-
110
- /**
111
- * Sanitize the SVG
112
- *
113
- * @param $file
114
- *
115
- * @return bool|int
116
- */
117
- protected function sanitize( $file ) {
118
- $dirty = file_get_contents( $file );
119
-
120
- // Is the SVG gzipped? If so we try and decode the string
121
- if ( $is_zipped = $this->is_gzipped( $dirty ) ) {
122
- $dirty = gzdecode( $dirty );
123
-
124
- // If decoding fails, bail as we're not secure
125
- if ( $dirty === false ) {
126
- return false;
127
- }
128
- }
129
-
130
- $clean = $this->sanitizer->sanitize( $dirty );
131
-
132
- if ( $clean === false ) {
133
- return false;
134
- }
135
-
136
- // If we were gzipped, we need to re-zip
137
- if ( $is_zipped ) {
138
- $clean = gzencode( $clean );
139
- }
140
-
141
- file_put_contents( $file, $clean );
142
-
143
- return true;
144
- }
145
-
146
- /**
147
- * Check if the contents are gzipped
148
- * @see http://www.gzip.org/zlib/rfc-gzip.html#member-format
149
- *
150
- * @param $contents
151
- *
152
- * @return bool
153
- */
154
- protected function is_gzipped( $contents ) {
155
- if ( function_exists( 'mb_strpos' ) ) {
156
- return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" );
157
- } else {
158
- return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" );
159
- }
160
- }
161
-
162
- /**
163
- * Filters the attachment data prepared for JavaScript to add the sizes array to the response
164
- *
165
- * @param array $response Array of prepared attachment data.
166
- * @param int|object $attachment Attachment ID or object.
167
- * @param array $meta Array of attachment meta data.
168
- *
169
- * @return array
170
- */
171
- public function fix_admin_preview( $response, $attachment, $meta ) {
172
-
173
- if ( $response['mime'] == 'image/svg+xml' ) {
174
- $possible_sizes = apply_filters( 'image_size_names_choose', array(
175
- 'thumbnail' => __( 'Thumbnail' ),
176
- 'medium' => __( 'Medium' ),
177
- 'large' => __( 'Large' ),
178
- 'full' => __( 'Full Size' ),
179
- ) );
180
-
181
- $sizes = array();
182
-
183
- foreach ( $possible_sizes as $size => $label ) {
184
- $sizes[ $size ] = array(
185
- 'height' => 2000,
186
- 'width' => 2000,
187
- 'url' => $response['url'],
188
- 'orientation' => 'portrait',
189
- );
190
- }
191
-
192
- $response['sizes'] = $sizes;
193
- $response['icon'] = $response['url'];
194
- }
195
-
196
- return $response;
197
- }
198
-
199
- /**
200
- * Filters the image src result.
201
- * Here we're gonna spoof the image size and set it to 100 width and height
202
- *
203
- * @param array|false $image Either array with src, width & height, icon src, or false.
204
- * @param int $attachment_id Image attachment ID.
205
- * @param string|array $size Size of image. Image size or array of width and height values
206
- * (in that order). Default 'thumbnail'.
207
- * @param bool $icon Whether the image should be treated as an icon. Default false.
208
- *
209
- * @return array
210
- */
211
- public function one_pixel_fix( $image, $attachment_id, $size, $icon ) {
212
- if ( get_post_mime_type( $attachment_id ) == 'image/svg+xml' ) {
213
- $image['1'] = false;
214
- $image['2'] = false;
215
- }
216
-
217
- return $image;
218
- }
219
-
220
- /**
221
- * If the featured image is an SVG we wrap it in an SVG class so we can apply our CSS fix.
222
- *
223
- * @param string $content Admin post thumbnail HTML markup.
224
- * @param int $post_id Post ID.
225
- * @param int $thumbnail_id Thumbnail ID.
226
- *
227
- * @return string
228
- */
229
- public function featured_image_fix( $content, $post_id, $thumbnail_id) {
230
- $mime = get_post_mime_type( $thumbnail_id );
231
-
232
- if( 'image/svg+xml' === $mime ) {
233
- $content = sprintf( '<span class="svg">%s</span>', $content );
234
- }
235
-
236
- return $content;
237
- }
238
-
239
- /**
240
- * Load our custom CSS sheet.
241
- */
242
- function load_custom_admin_style() {
243
- wp_enqueue_style( 'safe-svg-css', plugins_url( 'assets/safe-svg.css', __FILE__ ), array() );
244
- }
245
-
246
- /**
247
- * Override the default height and width string on an SVG
248
- *
249
- * @param string $html HTML content for the image.
250
- * @param int $id Attachment ID.
251
- * @param string $alt Alternate text.
252
- * @param string $title Attachment title.
253
- * @param string $align Part of the class name for aligning the image.
254
- * @param string|array $size Size of image. Image size or array of width and height values (in that order).
255
- * Default 'medium'.
256
- *
257
- * @return mixed
258
- */
259
- function get_image_tag_override( $html, $id, $alt, $title, $align, $size ) {
260
- $mime = get_post_mime_type( $id );
261
-
262
- if( 'image/svg+xml' === $mime ) {
263
- $html = str_replace('width="1" ', '', $html);
264
- $html = str_replace('height="1" ', '', $html);
265
- }
266
-
267
- return $html;
268
- }
269
-
270
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  }
272
 
273
  $safe_svg = new safe_svg();
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.2
7
  Author: Daryll Doyle
8
  Author URI: http://enshrined.co.uk
9
  Text Domain: safe-svg
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
+ }
49
+
50
+ /**
51
+ * Allow SVG Uploads
52
+ *
53
+ * @param $mimes
54
+ *
55
+ * @return mixed
56
+ */
57
+ public function allow_svg( $mimes ) {
58
+ $mimes['svg'] = 'image/svg+xml';
59
+ $mimes['svgz'] = 'image/svg+xml';
60
+
61
+ return $mimes;
62
+ }
63
+
64
+ /**
65
+ * Fixes the issue in WordPress 4.7.1 being unable to correctly identify SVGs
66
+ *
67
+ * @thanks @lewiscowles
68
+ *
69
+ * @param null $data
70
+ * @param null $file
71
+ * @param null $filename
72
+ * @param null $mimes
73
+ *
74
+ * @return null
75
+ */
76
+ public function fix_mime_type_svg( $data = null, $file = null, $filename = null, $mimes = null ) {
77
+ $ext = isset( $data['ext'] ) ? $data['ext'] : '';
78
+ if ( strlen( $ext ) < 1 ) {
79
+ $exploded = explode( '.', $filename );
80
+ $ext = strtolower( end( $exploded ) );
81
+ }
82
+ if ( $ext === 'svg' ) {
83
+ $data['type'] = 'image/svg+xml';
84
+ $data['ext'] = 'svg';
85
+ } elseif ( $ext === 'svgz' ) {
86
+ $data['type'] = 'image/svg+xml';
87
+ $data['ext'] = 'svgz';
88
+ }
89
+
90
+ return $data;
91
+ }
92
+
93
+ /**
94
+ * Check if the file is an SVG, if so handle appropriately
95
+ *
96
+ * @param $file
97
+ *
98
+ * @return mixed
99
+ */
100
+ public function check_for_svg( $file ) {
101
+
102
+ if ( $file['type'] === 'image/svg+xml' ) {
103
+ if ( ! $this->sanitize( $file['tmp_name'] ) ) {
104
+ $file['error'] = __( "Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded", 'safe-svg' );
105
+ }
106
+ }
107
+
108
+ return $file;
109
+ }
110
+
111
+ /**
112
+ * Sanitize the SVG
113
+ *
114
+ * @param $file
115
+ *
116
+ * @return bool|int
117
+ */
118
+ protected function sanitize( $file ) {
119
+ $dirty = file_get_contents( $file );
120
+
121
+ // Is the SVG gzipped? If so we try and decode the string
122
+ if ( $is_zipped = $this->is_gzipped( $dirty ) ) {
123
+ $dirty = gzdecode( $dirty );
124
+
125
+ // If decoding fails, bail as we're not secure
126
+ if ( $dirty === false ) {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ $clean = $this->sanitizer->sanitize( $dirty );
132
+
133
+ if ( $clean === false ) {
134
+ return false;
135
+ }
136
+
137
+ // If we were gzipped, we need to re-zip
138
+ if ( $is_zipped ) {
139
+ $clean = gzencode( $clean );
140
+ }
141
+
142
+ file_put_contents( $file, $clean );
143
+
144
+ return true;
145
+ }
146
+
147
+ /**
148
+ * Check if the contents are gzipped
149
+ *
150
+ * @see http://www.gzip.org/zlib/rfc-gzip.html#member-format
151
+ *
152
+ * @param $contents
153
+ *
154
+ * @return bool
155
+ */
156
+ protected function is_gzipped( $contents ) {
157
+ if ( function_exists( 'mb_strpos' ) ) {
158
+ return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" );
159
+ } else {
160
+ return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" );
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Filters the attachment data prepared for JavaScript to add the sizes array to the response
166
+ *
167
+ * @param array $response Array of prepared attachment data.
168
+ * @param int|object $attachment Attachment ID or object.
169
+ * @param array $meta Array of attachment meta data.
170
+ *
171
+ * @return array
172
+ */
173
+ public function fix_admin_preview( $response, $attachment, $meta ) {
174
+
175
+ if ( $response['mime'] == 'image/svg+xml' ) {
176
+ $possible_sizes = apply_filters( 'image_size_names_choose', array(
177
+ 'thumbnail' => __( 'Thumbnail' ),
178
+ 'medium' => __( 'Medium' ),
179
+ 'large' => __( 'Large' ),
180
+ 'full' => __( 'Full Size' ),
181
+ ) );
182
+
183
+ $sizes = array();
184
+
185
+ foreach ( $possible_sizes as $size => $label ) {
186
+ $sizes[ $size ] = array(
187
+ 'height' => 2000,
188
+ 'width' => 2000,
189
+ 'url' => $response['url'],
190
+ 'orientation' => 'portrait',
191
+ );
192
+ }
193
+
194
+ $response['sizes'] = $sizes;
195
+ $response['icon'] = $response['url'];
196
+ }
197
+
198
+ return $response;
199
+ }
200
+
201
+ /**
202
+ * Filters the image src result.
203
+ * Here we're gonna spoof the image size and set it to 100 width and height
204
+ *
205
+ * @param array|false $image Either array with src, width & height, icon src, or false.
206
+ * @param int $attachment_id Image attachment ID.
207
+ * @param string|array $size Size of image. Image size or array of width and height values
208
+ * (in that order). Default 'thumbnail'.
209
+ * @param bool $icon Whether the image should be treated as an icon. Default false.
210
+ *
211
+ * @return array
212
+ */
213
+ public function one_pixel_fix( $image, $attachment_id, $size, $icon ) {
214
+ if ( get_post_mime_type( $attachment_id ) == 'image/svg+xml' ) {
215
+ $image['1'] = false;
216
+ $image['2'] = false;
217
+ }
218
+
219
+ return $image;
220
+ }
221
+
222
+ /**
223
+ * If the featured image is an SVG we wrap it in an SVG class so we can apply our CSS fix.
224
+ *
225
+ * @param string $content Admin post thumbnail HTML markup.
226
+ * @param int $post_id Post ID.
227
+ * @param int $thumbnail_id Thumbnail ID.
228
+ *
229
+ * @return string
230
+ */
231
+ public function featured_image_fix( $content, $post_id, $thumbnail_id ) {
232
+ $mime = get_post_mime_type( $thumbnail_id );
233
+
234
+ if ( 'image/svg+xml' === $mime ) {
235
+ $content = sprintf( '<span class="svg">%s</span>', $content );
236
+ }
237
+
238
+ return $content;
239
+ }
240
+
241
+ /**
242
+ * Load our custom CSS sheet.
243
+ */
244
+ function load_custom_admin_style() {
245
+ wp_enqueue_style( 'safe-svg-css', plugins_url( 'assets/safe-svg.css', __FILE__ ), array() );
246
+ }
247
+
248
+ /**
249
+ * Override the default height and width string on an SVG
250
+ *
251
+ * @param string $html HTML content for the image.
252
+ * @param int $id Attachment ID.
253
+ * @param string $alt Alternate text.
254
+ * @param string $title Attachment title.
255
+ * @param string $align Part of the class name for aligning the image.
256
+ * @param string|array $size Size of image. Image size or array of width and height values (in that order).
257
+ * Default 'medium'.
258
+ *
259
+ * @return mixed
260
+ */
261
+ function get_image_tag_override( $html, $id, $alt, $title, $align, $size ) {
262
+ $mime = get_post_mime_type( $id );
263
+
264
+ if ( 'image/svg+xml' === $mime ) {
265
+ $html = str_replace( 'width="1" ', '', $html );
266
+ $html = str_replace( 'height="1" ', '', $html );
267
+ }
268
+
269
+ return $html;
270
+ }
271
+
272
+ /**
273
+ * Skip regenerating SVGs
274
+ *
275
+ * @param int $attachment_id Attachment Id to process.
276
+ * @param string $file Filepath of the Attached image.
277
+ *
278
+ * @return mixed Metadata for attachment.
279
+ */
280
+ function skip_svg_regeneration( $metadata, $attachment_id ) {
281
+ if ( 'image/svg+xml' === get_post_mime_type( $attachment_id ) ) {
282
+ return new WP_Error( 'skip_svg_generate', __( 'Skipping SVG file.', 'safe-svg' ) );
283
+ }
284
+
285
+ return $metadata;
286
+ }
287
+
288
+ /**
289
+ * Add in an upgrade link for Safe SVG
290
+ *
291
+ * @param $links
292
+ *
293
+ * @return array
294
+ */
295
+ function add_upgrade_link( $links ) {
296
+ $mylinks = array(
297
+ '<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>',
298
+ );
299
+
300
+ return array_merge( $links, $mylinks );
301
+ }
302
+
303
+ }
304
  }
305
 
306
  $safe_svg = new safe_svg();