WordPress как на ладони

Как в WordPress загрузить SVG

По умолчанию в WordPress нельзя загружать SVG в целях безопасности. Но что делать, когда очень надо? Решение есть!

При попытке загрузить SVG появляется сообщение "Извините, этот тип файла недопустим по соображениям безопасности."

О том, будет ли разрешена загрузка SVG в WordPress по умолчанию и почему разработчики этого избегают, читайте в тикете #24251 Reconsider SVG inclusion to get_allowed_mime_types.

Плагин ― включить загрузку svg можно также через плагин Safe SVG. Он кроме прочего очищает код загружаемого файла.

Шаг 1 - Включение SVG в список разрешенных для загрузки файлов

Для этого воспользуемся хуком upload_mimes, позволяющим изменять список доступных для загрузки файлов по MIME-типу.

add_filter( 'upload_mimes', 'svg_upload_allow' );

# Добавляет SVG в список разрешенных для загрузки файлов.
function svg_upload_allow( $mimes ) {
	$mimes['svg']  = 'image/svg+xml';

	return $mimes;
}

Иногда этого кода достаточно, но не для всех svg файлов. Разберемся почему так.

Реальный MIME тип svg файла, который в WP определятся php функцией finfo_file() (см. код wp_check_filetype_and_ext() ) может быть двух вариантов:

  • image/svg+xml — когда в коде svg есть xml заголовок.
  • image/svg — когда в коде svg есть только тег <svg>, без каких либо заголовков.

Сюда нужно добавить оба эти варианта, но добавить можно только один, поэтому только этого кода недостаточно и нужен второй шаг.

Шаг 2 - Подмена mime типа SVG

Как описано выше предыдущего кода для решения проблемы недостаточно. Точнее, в результате проверки и несовпадения указанного MIME типа и реального (полученного самим WP) MIME тип будет просто обнулен. Вернуть его обратно нам поможет хук wp_check_filetype_and_ext.

add_filter( 'wp_check_filetype_and_ext', 'fix_svg_mime_type', 10, 5 );

# Исправление MIME типа для SVG файлов.
function fix_svg_mime_type( $data, $file, $filename, $mimes, $real_mime = '' ){

	// WP 5.1 +
	if( version_compare( $GLOBALS['wp_version'], '5.1.0', '>=' ) ){
		$dosvg = in_array( $real_mime, [ 'image/svg', 'image/svg+xml' ] );
	}
	else {
		$dosvg = ( '.svg' === strtolower( substr( $filename, -4 ) ) );
	}

	// mime тип был обнулен, поправим его
	// а также проверим право пользователя
	if( $dosvg ){

		// разрешим
		if( current_user_can('manage_options') ){

			$data['ext']  = 'svg';
			$data['type'] = 'image/svg+xml';
		}
		// запретим
		else {
			$data['ext']  = false;
			$data['type'] = false;
		}

	}

	return $data;
}

Если из кода выше убрать проверку на право администратора, то код станет небезопасным и потенциально открывает дыру в защите сайта.

В коде выше добавлена проверка на версию WP. С WP 5.1.0 благодаря тикету автора этого сайта в хуке появился параметр $real_mime. Он позволяет сделать более надежную проверку файла — на уровне определения MIME типа файла по его коду, а не расширению.

Также рекомендую ограничить размер загружаемого svg файла, например не более 50кб. SVG файлы как правило маленькие и большой размер может говорить о том, что в файле какой-либо нехороший код.

Кода выше достаточно, чтобы WоrdPress разрешил загружать SVG в медиабиблиотеку. Следующий шаг чисто визуальный.

Отображение SVG в медиабиблиотеке

После проделанных шагов SVG файлы будут отображаться как документы, а не изображения:

За разметку этих блоков с изображениями отвечает код, генерируемый функцией wp_print_media_templates() из файла wp-includes/media-template.php:

<div class="thumbnail">
	<# if ( data.uploading ) { #>
		<div class="media-progress-bar"><div style="width: {{ data.percent }}%"></div></div>
	<# } else if ( 'image' === data.type && data.sizes ) { #>
		<div class="centered">
			<img src="{{ data.size.url }}" draggable="false" alt="" />
		</div>
	<# } else { #>
		<div class="centered">
			<# if ( data.image && data.image.src && data.image.src !== data.icon ) { #>
				<img src="{{ data.image.src }}" class="thumbnail" draggable="false" alt="" />
			<# } else if ( data.sizes && data.sizes.medium ) { #>
				<img src="{{ data.sizes.medium.url }}" class="thumbnail" draggable="false" alt="" />
			<# } else { #>
				<img src="{{ data.icon }}" class="icon" draggable="false" alt="" />
			<# } #>
		</div>
		<div class="filename">
			<div>{{ data.filename }}</div>
		</div>
	<# } #>
</div>

Смысл кода в том, что он отобразит изображение, если

  • у него есть свойство src (ссылка на изображение) и оно не равно ссылке на иконку документа (любой файл по сути документ);
  • или если у изображения есть размер medium (svg файл вообще не кропается).

Файл SVG не имеет ни первого ни второго, поэтому нам нужно сымитировать самостоятельно один из этих вариантов и в этом нам поможет фильтр wp_prepare_attachment_for_js, используйте любой из вариантов по вкусу.

Вариант 1 - С выводом названия файла

add_filter( 'wp_prepare_attachment_for_js', 'show_svg_in_media_library' );

# Формирует данные для отображения SVG как изображения в медиабиблиотеке.
function show_svg_in_media_library( $response ) {

	if ( $response['mime'] === 'image/svg+xml' ) {

		// С выводом названия файла
		$response['image'] = [
			'src' => $response['url'],
		];
	}

	return $response;
}

Вариант 2 - Без вывода названия файла

add_filter( 'wp_prepare_attachment_for_js', 'show_svg_in_media_library' );

# Формирует данные для отображения SVG как изображения в медиабиблиотеке.
function show_svg_in_media_library( $response ) {

	if ( $response['mime'] === 'image/svg+xml' ) {

		// Без вывода названия файла
		$response['sizes'] = [
			'medium' => [
				'url' => $response['url'],
			],
			// при редактирования картинки
			'full' => [
				'url' => $response['url'],
			],
		];
	}

	return $response;
}

Готовый класс

GitHub
<?php

/**
 * Allow uploading svg webp file types
 *
 * @version 1.1
 */
final class Allow_SVG_Upload {

	public static function init(): void {

		// Allow file types to be uploaded
		add_filter( 'upload_mimes', [ __CLASS__, 'upload_allow_types' ] );
		add_filter( 'wp_check_filetype_and_ext', [ __CLASS__, 'fix_svg_mime_type' ], 10, 5 );
		add_filter( 'wp_prepare_attachment_for_js', [ __CLASS__, 'show_svg_in_media_library' ] );

		add_filter( 'wp_handle_sideload_prefilter', [ __CLASS__, 'check_upload_file_size' ] );
		add_filter( 'wp_handle_upload_prefilter', [ __CLASS__, 'check_upload_file_size' ] );

		add_filter( 'getimagesize_mimes_to_exts', [ __CLASS__, 'more_mimes_to_exts' ] );
		add_filter( 'image_sideload_extensions', [ __CLASS__, 'more_image_sideload_extensions' ] );
	}

	public static function upload_allow_types( $mimes ){

		if( current_user_can( 'upload_files' ) ){
			$mimes['svg']  = 'image/svg+xml';
		}

		$mimes['webp'] = 'image/webp';

		// deactivate existing
		unset( $mimes['mp4a'] );

		return $mimes;
	}

	public static function more_image_sideload_extensions( $allowed ){
		$allowed[] = 'svg';

		return $allowed;
	}

	public static function more_mimes_to_exts( $mime_to_ext ){
		$mime_to_ext['image/webp'] = 'webp';

		return $mime_to_ext;
	}

	public static function fix_svg_mime_type( $data, $file, $filename, $mimes, $real_mime = '' ){

		// WP 5.1 +
		if( version_compare( $GLOBALS['wp_version'], '5.1.0', '>=' ) ){
			$dosvg = in_array( $real_mime, [ 'image/svg', 'image/svg+xml' ] );
		}
		else{
			$dosvg = ( '.svg' === strtolower( substr( $filename, -4 ) ) );
		}

		// mime type was reset, let's fix it
		// and also check file size and user permissions
		if( $dosvg ){
			if( current_user_can( 'upload_files' ) ){
				$data['ext']  = 'svg';
				$data['type'] = 'image/svg+xml';
			}
			// prohibit
			else {
				$data['ext'] = $type_and_ext['type'] = false;
			}
		}

		return $data;
	}

	/**
	 * Generates data for displaying SVGs as images in the media library.
	 */
	public static function show_svg_in_media_library( $response ) {

		if ( $response['mime'] === 'image/svg+xml' ) {
			$response['sizes'] = [
				'medium' => [
					'url' => $response['url'],
				],
				// when editing a image
				'full' => [
					'url' => $response['url'],
				],
			];
		}

		return $response;
	}

	/**
	 * Limit the size of uploaded files by type
	 */
	public static function check_upload_file_size( $file ){

		if( ! $file ){
			return $file;
		}

		// for SVG
		if( str_contains( ( $file['type'] ?? '' ), 'image/svg+xml' ) ){
			$size_limit = 500 * 1024; // max size in KB

			if( (int) $file['size'] > $size_limit ){
				$file['error'] = sprintf(
					__( 'ERROR: Размер этого типа файлов не может превышать %s', 'km' ),
					size_format( $size_limit )
				);
			}
		}

		return $file;
	}

}
campusboy 4742youtube.com/c/wpplus
Создатель YouTube канала wp-plus, на котором делюсь своим опытом. Активный пользователь wp-kama.ru. WordPress-разработчик. Разработка сайтов и лендингов. Доработка существующих проектов. Сопровождение ресурсов.
Редакторы: Kama 9601
15 комментариев
    Войти