composer install'
)
);
?>
sanitizer = new Sanitizer();
$this->sanitizer->minify( true );
add_action( 'init', array( $this, 'setup_blocks' ) );
add_filter( 'upload_mimes', array( $this, 'allow_svg' ) );
add_filter( 'wp_handle_sideload_prefilter', array( $this, 'check_for_svg' ) );
add_filter( 'wp_handle_upload_prefilter', array( $this, 'check_for_svg' ) );
add_filter( 'wp_check_filetype_and_ext', array( $this, 'fix_mime_type_svg' ), 75, 4 );
add_filter( 'wp_prepare_attachment_for_js', array( $this, 'fix_admin_preview' ), 10, 3 );
add_filter( 'wp_get_attachment_image_src', array( $this, 'one_pixel_fix' ), 10, 4 );
add_filter( 'admin_post_thumbnail_html', array( $this, 'featured_image_fix' ), 10, 3 );
add_action( 'admin_enqueue_scripts', array( $this, 'load_custom_admin_style' ) );
add_action( 'get_image_tag', array( $this, 'get_image_tag_override' ), 10, 6 );
add_filter( 'wp_generate_attachment_metadata', array( $this, 'skip_svg_regeneration' ), 10, 2 );
add_filter( 'wp_get_attachment_metadata', array( $this, 'metadata_error_fix' ), 10, 2 );
add_filter( 'wp_calculate_image_srcset_meta', array( $this, 'disable_srcset' ), 10, 4 );
new safe_svg_settings();
}
/**
* Custom function to check if user can upload svg.
*
* Use core caps if setting hasn't every been updated.
*
* @return bool
*/
public function current_user_can_upload_svg() {
$upload_roles = get_option( 'safe_svg_upload_roles', [] );
$can_upload = false;
if ( empty( $upload_roles ) ) {
// Fallback to upload_files check for backwards compatibility.
$can_upload = current_user_can( 'upload_files' );
} else {
// Use our custom capability if some upload roles are set.
$can_upload = current_user_can( 'safe_svg_upload_svg' );
}
/**
* Determine if the current user can upload an svg.
*
* @param bool $can_upload Can the current user upload an svg?
*
* @return bool
*/
return (bool) apply_filters( 'safe_svg_current_user_can_upload', $can_upload );
}
/**
* Setup the blocks.
*/
public function setup_blocks() {
// Setup blocks.
Blocks\setup();
}
/**
* Allow SVG Uploads
*
* @param array $mimes Mime types keyed by the file extension regex corresponding to those types.
*
* @return mixed
*/
public function allow_svg( $mimes ) {
if ( $this->current_user_can_upload_svg() ) {
$mimes['svg'] = 'image/svg+xml';
$mimes['svgz'] = 'image/svg+xml';
}
return $mimes;
}
/**
* Fixes the issue in WordPress 4.7.1 being unable to correctly identify SVGs
*
* @thanks @lewiscowles
*
* @param array $data Values for the extension, mime type, and corrected filename.
* @param string $file Full path to the file.
* @param string $filename The name of the file.
* @param string[] $mimes Array of mime types keyed by their file extension regex.
*
* @return null
*/
public function fix_mime_type_svg( $data = null, $file = null, $filename = null, $mimes = null ) {
$ext = isset( $data['ext'] ) ? $data['ext'] : '';
if ( strlen( $ext ) < 1 ) {
$exploded = explode( '.', $filename );
$ext = strtolower( end( $exploded ) );
}
if ( 'svg' === $ext ) {
$data['type'] = 'image/svg+xml';
$data['ext'] = 'svg';
} elseif ( 'svgz' === $ext ) {
$data['type'] = 'image/svg+xml';
$data['ext'] = 'svgz';
}
return $data;
}
/**
* Check if the file is an SVG, if so handle appropriately
*
* @param array $file An array of data for a single file.
*
* @return mixed
*/
public function check_for_svg( $file ) {
// Ensure we have a proper file path before processing
if ( ! isset( $file['tmp_name'] ) ) {
return $file;
}
$file_name = isset( $file['name'] ) ? $file['name'] : '';
$wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file_name );
$type = ! empty( $wp_filetype['type'] ) ? $wp_filetype['type'] : '';
if ( 'image/svg+xml' === $type ) {
if ( ! $this->current_user_can_upload_svg() ) {
$file['error'] = __(
'Sorry, you are not allowed to upload SVG files.',
'safe-svg'
);
return $file;
}
if ( ! $this->sanitize( $file['tmp_name'] ) ) {
$file['error'] = __(
"Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded",
'safe-svg'
);
}
}
return $file;
}
/**
* Sanitize the SVG
*
* @param string $file Temp file path.
*
* @return bool|int
*/
protected function sanitize( $file ) {
$dirty = file_get_contents( $file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
// Is the SVG gzipped? If so we try and decode the string
$is_zipped = $this->is_gzipped( $dirty );
if ( $is_zipped ) {
$dirty = gzdecode( $dirty );
// If decoding fails, bail as we're not secure
if ( false === $dirty ) {
return false;
}
}
/**
* Load extra filters to allow devs to access the safe tags and attrs by themselves.
*/
$this->sanitizer->setAllowedTags( new SafeSvgTags\safe_svg_tags() );
$this->sanitizer->setAllowedAttrs( new SafeSvgAttr\safe_svg_attributes() );
$clean = $this->sanitizer->sanitize( $dirty );
if ( false === $clean ) {
return false;
}
// If we were gzipped, we need to re-zip
if ( $is_zipped ) {
$clean = gzencode( $clean );
}
file_put_contents( $file, $clean ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
return true;
}
/**
* Check if the contents are gzipped
*
* @see http://www.gzip.org/zlib/rfc-gzip.html#member-format
*
* @param string $contents Content to check.
*
* @return bool
*/
protected function is_gzipped( $contents ) {
// phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found
if ( function_exists( 'mb_strpos' ) ) {
return 0 === mb_strpos( $contents, "\x1f" . "\x8b" . "\x08" );
} else {
return 0 === strpos( $contents, "\x1f" . "\x8b" . "\x08" );
}
// phpcs:enable
}
/**
* Filters the attachment data prepared for JavaScript to add the sizes array to the response
*
* @param array $response Array of prepared attachment data.
* @param int|object $attachment Attachment ID or object.
* @param array $meta Array of attachment meta data.
*
* @return array
*/
public function fix_admin_preview( $response, $attachment, $meta ) {
if ( 'image/svg+xml' === $response['mime'] ) {
$dimensions = $this->svg_dimensions( $attachment->ID );
if ( $dimensions ) {
$response = array_merge( $response, $dimensions );
}
$possible_sizes = apply_filters(
'image_size_names_choose',
array(
'full' => __( 'Full Size' ),
'thumbnail' => __( 'Thumbnail' ),
'medium' => __( 'Medium' ),
'large' => __( 'Large' ),
)
);
$sizes = array();
foreach ( $possible_sizes as $size => $label ) {
$default_height = 2000;
$default_width = 2000;
if ( 'full' === $size && $dimensions ) {
$default_height = $dimensions['height'];
$default_width = $dimensions['width'];
}
$sizes[ $size ] = array(
'height' => get_option( "{$size}_size_w", $default_height ),
'width' => get_option( "{$size}_size_h", $default_width ),
'url' => $response['url'],
'orientation' => 'portrait',
);
}
$response['sizes'] = $sizes;
$response['icon'] = $response['url'];
}
return $response;
}
/**
* Filters the image src result.
* If the image size doesn't exist, set a default size of 100 for width and height
*
* @param array|false $image Either array with src, width & height, icon src, or false.
* @param int $attachment_id Image attachment ID.
* @param string|array $size Size of image. Image size or array of width and height values
* (in that order). Default 'thumbnail'.
* @param bool $icon Whether the image should be treated as an icon. Default false.
*
* @return array
*/
public function one_pixel_fix( $image, $attachment_id, $size, $icon ) {
if ( get_post_mime_type( $attachment_id ) === 'image/svg+xml' ) {
$dimensions = $this->svg_dimensions( $attachment_id );
if ( $dimensions ) {
$image[1] = $dimensions['width'];
$image[2] = $dimensions['height'];
} else {
$image[1] = 100;
$image[2] = 100;
}
}
return $image;
}
/**
* If the featured image is an SVG we wrap it in an SVG class so we can apply our CSS fix.
*
* @param string $content Admin post thumbnail HTML markup.
* @param int $post_id Post ID.
* @param int|null $thumbnail_id Thumbnail attachment ID, or null if there isn't one.
*
* @return string
*/
public function featured_image_fix( $content, $post_id, $thumbnail_id = null ) {
$mime = get_post_mime_type( $thumbnail_id );
if ( 'image/svg+xml' === $mime ) {
$content = sprintf( '