oEmbed в WordPress

oEmbed — это открытый формат, созданный для упрощения встраивания содержимого одной веб-страницы в другую. В роли контента могут выступать: фотографии, видеоролики, ссылки и другие типы контента.

oEmbed контент — это видео, аудио, HTML и другие коды на вашем сайте, которые были встроены из другого сайта. Например, если в WP вставить ссылку на видео youtube она превратиться в iframe с видео роликом.

Читайте также: Шорткоды в WordPress.

Как это работает

В основе работы класса WP_Embed{} лежит класс WP_oEmbed{} - именно он содержит список зарегистрированных oEmbed провайдеров и он занимается запросом (получением), обработкой Discovery ссылки в head части HTML.

На очень ранней стадии еще до хука mu_plugin_loaded инициализируется класс WP_Embed:

$GLOBALS['wp_embed'] = new WP_Embed();

В конструкторе WP_Embed::__construct() создаются следующие хуки:

function __construct() {

	// Hack to get the [embed] shortcode to run before wpautop().
	add_filter( 'the_content', array( $this, 'run_shortcode' ), 8 );
	add_filter( 'widget_text_content', array( $this, 'run_shortcode' ), 8 );

	// Shortcode placeholder for strip_shortcodes().
	add_shortcode( 'embed', '__return_false' );

	// Attempts to embed all URLs in a post.
	add_filter( 'the_content', array( $this, 'autoembed' ), 8 );
	add_filter( 'widget_text_content', array( $this, 'autoembed' ), 8 );

	// After a post is saved, cache oEmbed items via Ajax.
	add_action( 'edit_form_advanced', array( $this, 'maybe_run_ajax_cache' ) );
	add_action( 'edit_page_form', array( $this, 'maybe_run_ajax_cache' ) );
}

Обработка oEmbed URL на фронте

Контент записи или виджета text проверяется на наличие шоткода [embed] или на наличие URL на отдельной строке. Для этого, как видно из кода выше, на хуке the_content запускаются два метода:

  1. WP_Embed::run_shortcode( $post_content ) — обрабатывает шоткод [embed].

  2. WP_Embed::autoembed( $post_content ) — обрабатывает URL (на отдельной строке), как шорткод [embed].

    Далее каждый найденный URL (на отдельной строке или в шорткоде) передается в метод WP_Embed::shortcode( $attr, $url ). Далее:

    1. Проверяется внутренний обработчик | Embed

      URL передается в метод WP_Embed::get_embed_handler_html( $rawattr, $url ). Этот метод проверяет переданный URL, на внутренние обработчики, которые регистрируются функцией wp_embed_register_handler().

      1. Если обработчик найден, то URL передается в функцию-обработчик и результат возвращается. Работа метода WP_Embed::shortcode() на этом прекращается.
      2. Если обработчик не найден, то WP_Embed::shortcode() продолжает проверку oEmbed (внешних) обработчиков.
    2. Проверяется внешний обработчик | oEmbed

      1. Если для URL есть кэш, то парсинг прерывается - результат берется из кэша.
      2. Если кэша нет, то URL передается в функцию wp_oembed_get( $url, $attr ), затем в метод WP_oEmbed::get_provider(), далее с этим URL сравниваются все зарегистрированные провайдеры (WP_oEmbed::$providers) и если есть совпадение, делается запрос чтобы получить oEmbed вставку с другого сайта (за этот запрос отвечает класс WP_oEmbed). В конце, неважно какой результат, обработка URL кэшируется.

        При таком варианте кэш создается только при первом запросе (когда его нет) и он не обновляется. Обновление кэша происходит только при обновлении записи в админке.

Обработка oEmbed URL в админке

Также oEmbed кэш создается и (важно) обновляется при обновлении записи на странице редактирования записи в админке. Делается это AJAX запросом. Создается такой AJAX запрос только в момент обновления записи в адмнике - см. WP_Embed::maybe_run_ajax_cache().

Для примера, рассмотрим как WordPress обрабатывает ссылки на другие сайты на WordPress, чтобы встроить контент другого сайта. Общий принцип всего этого описан выше, а ниже рассмотрим как это работает в админке.

Блочный редактор

При вставке ссылки в блочный редактор срабатывает js событие, отправляющее ajax запрос методом GET на роут домен/wp-json/oembed/1.0/proxy, который регистрирует метод WP_oEmbed_Controller::register_routes(). Пример отправляемых данных:

url: https://oddstyle.ru/instrukciya-po-rabote-s-wordpress-rukovodstvo-dlya-novichkov
_locale: user

Ответ генерирует метод WP_oEmbed_Controller::get_proxy_item():

{
	"version":"1.0",
	"provider_name":"Блог про WordPress",
	"provider_url":"https://oddstyle.ru",
	"author_name":"Дмитрий",
	"author_url":"https://oddstyle.ru/author/admin",
	"title":"Инструкция по работе с WordPress. Руководство для новичков",
	"type":"rich",
	"width":600,
	"height":338,
	"html":"
		<blockquote class=\"wp-embedded-content\" data-secret=\"X2b9CmjdiG\">
			<a href=\"https://oddstyle.ru/instrukciya-po-rabote-s-wordpress-rukovodstvo-dlya-novichkov\">Инструкция по работе с WordPress. Руководство для новичков</a>
		</blockquote>
		<iframe
			class=\"wp-embedded-content\"
			sandbox=\"allow-scripts\"
			security=\"restricted\"
			style=\"position: absolute; clip: rect(1px, 1px, 1px, 1px);\"
			title=\"«Инструкция по работе с WordPress. Руководство для новичков» — Блог про WordPress\"
			src=\"https://oddstyle.ru/instrukciya-po-rabote-s-wordpress-rukovodstvo-dlya-novichkov/embed#?secret=X2b9CmjdiG\"
			data-secret=\"X2b9CmjdiG\"
			width=\"600\"
			height=\"338\"
			frameborder=\"0\"
			marginwidth=\"0\"
			marginheight=\"0\"
			scrolling=\"no\"
		></iframe>
	"
}

Данные кешируются в таблицу *_options.

Визуальный редактор

При использовании визуального редактора при вставки ссылки срабатывает JS событие, запускающее ajax запрос методом post на файл admin-ajax.php, где на хуке wp_ajax_parse_embed отрабатывает одноимённая функция wp_ajax_parse_embed().

Пример отправляемых данных:

post_ID: 31
type: embed
shortcode: [embed]https://oddstyle.ru/instrukciya-po-rabote-s-wordpress-rukovodstvo-dlya-novichkov[/embed]
maxwidth: 549
action: parse-embed

Содержимое поля shortcode передаётся в WP_Embed::run_shortcode(). Что происходит дальше читайте выше в секции "Как это работает".

Пример возвращаемых данных:

{
	"success":true,
	"data":{
		"body":"
			<blockquote class=\"wp-embedded-content\" data-secret=\"mf3DjZabsV\">
				<a href=\"https://oddstyle.ru/instrukciya-po-rabote-s-wordpress-rukovodstvo-dlya-novichkov\">Инструкция по работе с WordPress. Руководство для новичков</a>
			</blockquote>
			<iframe
				class=\"wp-embedded-content\"
				sandbox=\"allow-scripts\"
				security=\"restricted\"
				style=\"position: absolute; clip: rect(1px, 1px, 1px, 1px);\"
				title=\"«Инструкция по работе с WordPress. Руководство для новичков» — Блог про WordPress\"
				src=\"https://oddstyle.ru/instrukciya-po-rabote-s-wordpress-rukovodstvo-dlya-novichkov/embed#?secret=mf3DjZabsV\"
				data-secret=\"mf3DjZabsV\"
				width=\"549\"
				height=\"309\"
				frameborder=\"0\"
				marginwidth=\"0\"
				marginheight=\"0\"
				scrolling=\"no\"
			></iframe>
		",
		"attr":{
			"width":549,
			"height":824
		},
		"head":"<script src=\"https://wp-test.ru/wp-includes/js/wp-embed.js\"></script>",
		"sandbox":true
	}
}

Данные кешируются в таблицу wp_postmeta или wp_posts.

Кэширование oEmbed запроса

Для кэширования запросов могут быть использованы метаполя записи (поста) или таблица wp_posts.

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

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

Кэш актуален 1 день (86400 секунд). Это значение можно изменить через хук oembed_ttl.

ВАЖНО! Проверка времени жизни и обновление кэша происходит только при обновлении записи со страницы редактирования записи в админке. С фронта кэш не обновляется. Запуск обновления кэша в админке инициализируют эти хуки:

add_action( 'edit_form_advanced', array( $this, 'maybe_run_ajax_cache' ) );
add_action( 'edit_page_form', array( $this, 'maybe_run_ajax_cache' ) );

Если по какой-то причине не удалось получить HTML встраивания, например вернулся 404 ответ или другой отличный от 200, то в кэш вместо html, будет добавлен маркер {{unknown}}.

oEmbed Безопасность

Встраивание чужого кода на свой сайт открывает возможность XSS атаки. Так, например, полученный код может содержать хакерский код, который может получить куки вашего сайта да и вообще, может делать кучу всего.

Чтобы обезопасить себя от подобного рода дыр в безопасности, WordPress выводит встраивание в теге <iframe>. А также очищает полученный код iframe от нежелательных атрибутов. В частности код встраивания допускает следующие HTML теги, которые проверяются функцией wp_kses():

$allowed_html = array(
	'a'          => array(
		'href' => true,
	),
	'blockquote' => array(),
	'iframe'     => array(
		'src'          => true,
		'width'        => true,
		'height'       => true,
		'frameborder'  => true,
		'marginwidth'  => true,
		'marginheight' => true,
		'scrolling'    => true,
		'title'        => true,
	),
);

$html = wp_kses( $html, $allowed_html );

Как мы видим код встраивания может содержать только три тега и ограниченное число параметров для этих тегов.

Смотрите код функции wp_filter_oembed_result(), которая по умолчанию повешена на хук oembed_dataparse и применяется абсолютно ко всем полученным кодам для встраивания.

WordPress как поставщик oEmbed

C версии 4.4. WordPress сам стал поставщиком/провайдером oEmbed формата. Теперь, записи одного сайта WP можно встроить в другой сайт.

Чтобы это сделать, нужно в контент поста добавить URL записи. Сделать это можно двумя способами:

  1. Добавить URL на отдельную строку.
  2. Вставить URL в шоткод [embed]. Например [embed]http://dom.com/adress[/embed].

WordPress автоматически обработает указанный URL (определит является ли тот oEmbed провайдером), встроит данные и создаст кэш запроса, чтобы не делать этот запрос каждый раз при загрузке страницы.

Встраивания представляют из себя краткую версию страницы. Например если добавить /embed в конец URL любой записи, то мы попадем на страницу встраивания (которая должна появится на другом сайте при встраивании). Вот пример такой страницы: https://wp-kama.ru/handbook/codex/oembed/embed.

Пример того, как выглядит встраивание:

oEmbed в WordPress

Как изменить HTML код встраивания?

Для этого нужно создать в теме файл embed.php - подробнее про иерархию шаблона.

Если такого файла в теме нет, то за код встраивания отвечают файлы:

В каждом из этих файлов есть хуки, которые можно использовать для изменения отдельных частей HTML страницы встраивания.

Функции oEmbed

  • get_post_embed_url() — Получает URL, который нужно использовать в iframe для встраивания указанной записи на другом сайте (oEmbed формат).
  • get_post_embed_html() — Получает готовый HTML код oEmbed встраивания указанной записи. Предполагается использовать этот код для встраивания записи на другом ресурсе.
  • is_embed() — Проверят является ли запрос запросом на страницу встраивания записи (embed).
  • wp_embed_register_handler() — Регистрирует Embed обработчик. Это обработчик, который превращает ссылку в контенте в HTML код.
  • wp_oembed_get() — Встраивает объект по УРЛ. Пытается получить HTML код из переданного УРЛ на основе поддерживаемых oEmbed WordPress.
  • wp_oembed_add_provider() — Добавляет oEmbed провайдера. Это URL который будет парсится в контенте для вывода HTML кода с другого сайта.
  • wp_oembed_remove_provider() — Removes an oEmbed provider.

См. Полный список Embed функций.

Хуки oEmbed

  • embed_cache_oembed_types - позволяет изменить типы записей для которых нужно обрабатывать oembed ссылки (шоткоды).
  • oembed_ttl — позволяет изменить TTL (time to live) время жизни кэша.
  • embed_oembed_html — позволяет изменить уже закэшированный HTML.
  • oembed_dataparse — Позволяет изменить контент (HTML) создаваемый при встраивании URL, поддерживаемых oEmbed.
  • embed_oembed_discover — Позволяет указать нужно ли переходить по URL и искать <link> тег встраивания на удаленном сайте.

См. Полный список хуков.

Поддерживаемые провайдеры

Ниже список oEmbed провайдеров, которых WordPress поддерживает из коробки. Дополнительные провайдеры можно добавить в этот список с помощью функции wp_oembed_add_provider().

Provider Flavor Since
Dailymotion dailymotion.com 2.9.0
Flickr flickr.com 2.9.0
Scribd scribd.com 2.9.0
Vimeo vimeo.com 2.9.0
WordPress.tv wordpress.tv 2.9.0
YouTube youtube.com/watch 2.9.0
Crowdsignal polldaddy.com 3.0.0
SmugMug smugmug.com 3.0.0
YouTube youtu.be 3.0.0
Twitter twitter.com 3.4.0
Instagram instagram.com 3.5.0
Instagram instagr.am 3.5.0
Slideshare slideshare.net 3.5.0
SoundCloud soundcloud.com 3.5.0
Dailymotion dai.ly 3.6.0
Flickr flic.kr 3.6.0
Spotify spotify.com 3.6.0
Imgur imgur.com 3.9.0
Meetup.com meetup.com 3.9.0
Meetup.com meetu.ps 3.9.0
Animoto animoto.com 4.0.0
Animoto video214.com 4.0.0
Issuu issuu.com 4.0.0
Mixcloud mixcloud.com 4.0.0
Crowdsignal poll.fm 4.0.0
TED ted.com 4.0.0
YouTube youtube.com/playlist 4.0.0
Tumblr tumblr.com 4.2.0
Kickstarter kickstarter.com 4.2.0
Kickstarter kck.st 4.2.0
Cloudup cloudup.com 4.3.0
ReverbNation reverbnation.com 4.4.0
VideoPress videopress.com 4.4.0
Reddit reddit.com 4.4.0
Speaker Deck speakerdeck.com 4.4.0
Twitter twitter.com/timelines 4.5.0
Twitter twitter.com/moments 4.5.0
Facebook facebook.com 4.7.0
Twitter twitter.com/user 4.7.0
Twitter twitter.com/likes 4.7.0
Twitter twitter.com/lists 4.7.0
Screencast screencast.com 4.8.0
Amazon amazon.com (com.mx, com.br, ca) 4.9.0
Amazon amazon.de (fr, it, es, in, nl, ru, co.uk) 4.9.0
Amazon amazon.co.jp (com.au) 4.9.0
Amazon amazon.cn 4.9.0
Amazon a.co 4.9.0
Amazon amzn.to (eu, in, asia) 4.9.0
Amazon z.cn 4.9.0
Someecards someecards.com 4.9.0
Someecards some.ly 4.9.0
Crowdsignal survey.fm 5.1.0
Instagram TV instagram.com 5.1.0
Instagram TV instagr.am 5.1.0
TikTok tiktok.com 5.4.0

Удаление просроченного oEmbed Кэша

Для очистки Базы Данных на больших сайтах возможно есть смысл периодически запускать такую функцию, чтобы удалять просроченный кэш.

/**
 * Remove expired oEmbed Cache.
 *
 * @param int $ttl The cache lifetime in seconds.
 *
 */
function kama_delete_expired_oembed_cache( $ttl = MONTH_IN_SECONDS ){
	global $wpdb;

	// META
	$query_data = $wpdb->get_results(
		"SELECT * FROM $wpdb->postmeta WHERE meta_key LIKE '_oembed_time_%' ORDER BY meta_value+0 DESC"
	);

	$res = [];
	foreach( $query_data as $data ){

		$post = get_post( $data->post_id );
		$info = date( 'd-m-Y', $data->meta_value ) ." - $post->ID: $post->post_title";

		if( time() > $data->meta_value + $ttl ){

			$oembed_meta_key = str_replace( '_oembed_time_', '_oembed_', $data->meta_key );

			delete_post_meta( $data->post_id, $data->meta_key );
			delete_post_meta( $data->post_id, $oembed_meta_key );

			$res['DELETED'][] = $info;
		}
		else {
			$res['NOT DELETED'][] = $info;
		}

	}

	// POSTS
	$min_allowed_date = date( 'Y-m-d H:i:59', time() - $ttl );

	$posts = $wpdb->get_results(
		"SELECT * FROM $wpdb->posts WHERE post_type = 'oembed_cache' AND post_modified_gmt < '$min_allowed_date'
		ORDER BY post_modified_gmt DESC"
	);

	foreach( $posts as $post ){
		$res['DELETED POSTS'][] = $post->post_modified_gmt;

		wp_delete_post( $post->ID, 'force_delete' );
	}

	return $res;

}

$res = kama_delete_expired_oembed_cache();
print_r( $res );

Внутренний embed обработчик (с кэшированием)

Для начала замечу, что это хак - WP на такое не рассчитан. Но благодаря хукам, можно это сделать.

Для этого используем хук pre_oembed_result из метода WP_oEmbed::get_html():

// ...
$pre = apply_filters( 'pre_oembed_result', null, $url, $args );

if ( null !== $pre ) {
	return $pre;
}
// ...

Нужно зарегистрировать внутренний обработчик через функцию wp_embed_register_handler(). Но в функции обработчике вернуть false и перенести функцию обработчик в упомянутый хук pre_oembed_result.

Т.е. код будет примерно такой:

add_action( 'init', 'myembed_provider_register' );
add_filter( 'pre_oembed_result', 'myembed_provider_handler', 10, 3 );

function myembed_provider_register(){
	wp_embed_register_handler(
		'myembed',
		'~https://foo\.bar\.com/(\w+)~i',
		'__return_false'
	);
}

function myembed_provider_handler( $null, $url, $args ){

	$html = '{{unknown}}';

	// обрабатываем $url делаем HTTP запрос. См. WP HTTP API

	// Возвращаем iframe или HTML код

	return $html;
}

oEmbed для произвольного текста

Если нам нужно обработать шорткод [embed] или авто-встраивание ссылки в тексте. То этот текст нужно будет обработать отдельно. Базовая обработка с помощью функций do_shortcodes() или apply_shortcodes() oEmbed не включает. И по умолчанию такая обработка делается только для хука the_content.

Соответственно у нас есть 2 варианта:

Вариант 1

Простой, но может не подходить из-за излишней нагруженности хука the_content - на нём обычно висит куча всего, что может оказаться лишним.

$text = '
Some text to check custom shortcode adding.

[embed]https://my-youtube.com/watch?v=lWzMBLoLIAc[/embed]

https://my-youtube.com/watch?v=uDQwKtkXV-0
';

$text = apply_filters( 'the_content', $text );

echo $text;

Вариант 2: Точечный подход

Делаем с текстом только то что нам нужно:

$text = '
Some text to check custom shortcode adding.

[embed]https://my-youtube.com/watch?v=lWzMBLoLIAc[/embed]

https://my-youtube.com/watch?v=uDQwKtkXV-0
';

$text = $GLOBALS['wp_embed']->run_shortcode( $text ); //  shortcode
$text = $GLOBALS['wp_embed']->autoembed( $text );     // oEmbed URLs

//$text = apply_shortcodes( $text );

$text = wpautop( $text );

echo $text;

oEmbed в комментариях WordPress

Код ниже позволяет использовать oEmbed в комментариях WordPress.

GitHub

<?php

/**
 * Plugin Name: oEmbed in Comments
 * Description: Allow oEmbeds in comment text. A fork of http://wordpress.org/plugins/oembed-in-comments/
 * Version: 1.2
 * Author: Evan Solomon, modified by Shea Bunge
 */

class oEmbed_Comments {

  /**
	 * Setup filter with correct priority to do oEmbed in comments
	 * @since  1.0
	 * @uses   is_admin()   To make sure we don't do anything in the admin
	 * @uses   add_filter   To register a filter hook
	 * @uses   has_filter() To check if a filter is registered
	 * @return void
	 */
	static function add_filter() {
		if ( is_admin() )
			return;

		/* make_clickable breaks oEmbed regex, make sure we go earlier */
		$clickable = has_filter( 'comment_text', 'make_clickable' );
		$priority = ( $clickable ) ? $clickable - 1 : 10;

		add_filter( 'comment_text', array( __CLASS__, 'oembed_filter' ), $priority );
	}

	/**
	 * Safely add oEmbed media to a comment
	 * @since  1.0
	 * @param  string $comment_text The current comment test
	 * @return string               The modified comment text
	 */
	static function oembed_filter( $comment_text ) {
		global $wp_embed;

		/* Automatic discovery would be a security risk, safety first */
		add_filter( 'embed_oembed_discover', '__return_false', 999 );
		$comment_text = $wp_embed->autoembed( $comment_text );

		/* ...but don't break your posts if you use it */
		remove_filter( 'embed_oembed_discover', '__return_false', 999 );

		return $comment_text;
	}

}

add_action( 'init', array( 'oEmbed_Comments', 'add_filter' ) );

Правка для работы с AJAX:

<?php

/**
 * Class oEmbed_Comments
 * @package RusDTP
 *
 * @see     https://gist.github.com/sheabunge/6018753
 */
class oEmbed_Comments {

	public function __construct() {
		add_action( 'init', [ $this, 'add_filter' ] );
	}

	/**
	 * Setup filter with correct priority to do oEmbed in comments
	 */
	public function add_filter() {
		global $pagenow;

		$access_page = [ 'index.php', 'admin-ajax.php' ];

		if ( ! in_array( $pagenow, $access_page ) ) {
			return;
		}

		/* make_clickable breaks oEmbed regex, make sure we go earlier */
		remove_filter( 'comment_text', 'make_clickable', 9 );
		add_filter( 'comment_text', 'make_clickable', 12 );

		add_filter( 'comment_text', [ $this, 'oembed_filter' ], 11 );
	}

	/**
	 * Safely add oEmbed media to a comment
	 *
	 * @param string $comment_text The current comment test
	 *
	 * @return string               The modified comment text
	 * @since  1.0
	 */
	public function oembed_filter( $comment_text ) {
		global $wp_embed;

		/* Automatic discovery would be a security risk, safety first */
		add_filter( 'embed_oembed_discover', '__return_false', 999 );
		$comment_text = $wp_embed->autoembed( $comment_text );

		/* ...but don't break your posts if you use it */
		remove_filter( 'embed_oembed_discover', '__return_false', 999 );

		return $comment_text;
	}

}

new oEmbed_Comments();

Отключение oEmbed

Читайте в отдельной статье

9 комментариев
    Войти