Создание Провайдера для Карты сайта

Система Карты сайта WordPress, как и многие другие части, позволяет довольно просто расширять функциональность и добавлять произвольные ссылки в существующую Карту сайта. Ниже рассмотрим как это делается.

Что кастомное автоматически добавляется в Карту сайта?

Если вы создаете дополнительные (кастомные) типы записей или таксономии, то эти сущности автоматически попадут в Карту сайта WordPress. Единственное что для этого нужно, это чтобы новый тип записи или таксономия были «публичные»: имели параметры public = true и publicly_queryable = true. Эти параметры указываются при регистрации типа записи или таксономии.

Создание своего провайдера для Карты сайта

Но если ваш плагин или тема, имеют какие-то свои сущности, которые нужно добавить в карту сайта, то для них нужно будет создать нового провайдера. Это пользовательский класс PHP, который расширяет абстрактный класс WP_Sitemaps_Provider.

Созданный Класс, нужно зарегистрировать (подключить) с помощью функции wp_register_sitemap_provider():

add_filter( 'init', function(){

	$provider = new Awesome_Plugin_Sitemaps_Provider();

	wp_register_sitemap_provider( 'awesome-plugin', $provider );
} );

Этот провайдер будет отвечать за отображение всех новых Карт сайта, их пагинацию вывод карт для отдельных элементов (ссылок).

Примеры создания Провайдеров

#1 Свой провайдер с двумя типами и кастомной таблицей

Допустим у нас в БД есть таблица, в которой хранятся медиа данные об audio и video сущностях. Каждая такая сущность имеет свою страницу на фронте. Нам нужно добавить все эти страницы в Карту сайта.

Создадим своего провайдера, назовем его megamedia. Для этого создадим в теме файл class-Megamedia_Sitemaps_Provider.php со следующим кодом:

class Media_Sitemaps_Provider extends WP_Sitemaps_Provider {

	// make visibility not protected
	public $name;

	/**
	 * Constructor. Sets name, object_type properties.
	 *
	 * $name         Provider name. Uses in URL (must be unique).
	 * $object_type  The object name that the provider works with.
	 *               Passes into the hooks (must be unique).
	 */
	public function __construct() {

		$this->name        = 'megamedia';
		$this->object_type = 'megamedia';
	}

	/**
	 * Returns the list of supported object subtypes exposed by the provider.
	 *
	 * @return array List of object subtypes objects keyed by their name.
	 */
	public function get_object_subtypes() {
		return array(
			'audio' => new stdClass(),
			'video' => new stdClass()
		);
	}

	/**
	 * Gets a URL list for a sitemap.
	 *
	 * @param int    $page_num       Page of results.
	 * @param string $subtype Optional. Object subtype name. Default empty.
	 *
	 * @return array Array of URLs for a sitemap.
	 */
	public function get_url_list( $page_num, $subtype = '' ) {

		$result = $this->db_query( [
			'subtype' => $subtype,
			'paged'   => $page_num,
		] );

		$url_list = array();

		foreach ( $result as $megamedia ) {
			$sitemap_entry = [
				'loc' => home_url( "/megamedia/$subtype/$megamedia->id" ),
				// 'priority'   => 0.5,
				// 'changefreq' => 'monthly',
			];

			$url_list[] = $sitemap_entry;
		}

		return $url_list;
	}

	/**
	 * Gets the max number of pages available for the object type.
	 *
	 * @param string $subtype Optional. Object subtype. Default empty.
	 * @return int Total number of pages.
	 */
	public function get_max_num_pages( $subtype = '' ) {

		$total = $this->db_query( [
			'subtype' => $subtype,
			'count'   => true,
		] );

		return (int) ceil( $total / wp_sitemaps_get_max_urls( $this->object_type ) );
	}

	/**
	 * Returns the SQL query result.
	 *
	 * @return array Array of query arguments.
	 */
	protected function db_query( $args ) {
		global $wpdb;

		$arg = (object) array_merge( [
			'paged'   => 1,
			'subtype' => 'audio',
			'count'   => false,
		], $args );

		$SELECT = $arg->count ? 'count(*)' : '*';

		$WHERE = [];
		$WHERE[] = 'post_id = 0';
		$WHERE[] = $wpdb->prepare( "media_type = %s", $arg->subtype );
		$WHERE = implode( ' AND ', $WHERE );

		$per_page = wp_sitemaps_get_max_urls( $this->object_type );
		$offset = ( $arg->paged - 1 ) * $per_page;
		$LIMIT = sprintf( "LIMIT %d, %d", $offset,$per_page );

		$sql = "SELECT $SELECT FROM $wpdb->wp_core_data WHERE $WHERE $LIMIT";

		$result = $arg->count ? $wpdb->get_var( $sql ) : $wpdb->get_results( $sql );

		return $result;
	}

}

Теперь подключим (зарегистрируем) созданного провайдера в файле functions.php.

add_filter( 'init', 'wpkama_register_sitemap_providers' );
function wpkama_register_sitemap_providers(){

	require_once __DIR__ .'/class-Megamedia_Sitemaps_Provider.php';

	$provider = new Megamedia_Sitemaps_Provider();

	wp_register_sitemap_provider( $provider->name, $provider );
}

Переходим в Карту сайта и видим:

Заметки:

ВАЖНО: первый параметр функции wp_register_sitemap_provider() должен быть такой же как свойство $name у провайдера! И состоять это имя может только из символов a-z!

Свойства класса WP_Sitemaps_Provider:

$this->name

Имя провайдера. Используется в URL карты сайта. Должно быть уникальным.

ВАЖНО! Разрешаются только символы a-z. Т.е. нельзя использовать тире, пробелы и строчные. Неправильно: similar_posts, similar-posts, Similar. Правильно similarposts.

$this->object_type
Имя объекта с которым работает провайдер (post, term, user). Используется в хуках. Должно быть уникальным.

Методы класса Media_Sitemaps_Provider и WP_Sitemaps_Provider:

get_object_subtypes()
Должен вернуть список подтипов, например у постов это типы постов. Возвращает массив объектов, в ключах находятся имена подтипов.
get_url_list( $page_num, $subtype = '' )

Должен вернуть список ссылок для каждой карты — список данных для XML тегов <url>. Выглядит этот список как массив массивов, каждый вложенный массив - это каждая ссылка. Например:

$url_list = [
	[ 'loc'=> 'https://example.com/megamedia/audio/2610735' ],
	[ 'loc'=> 'https://example.com/megamedia/audio/9514241' ],
	...
];
get_max_num_pages( $subtype = '' )
Должен вернуть число - общее кол-во элементов для переданного подтипа. Например для записей WP - это сколько всего записей указанного типа записи, например, сколько всего страниц (page).
db_query( $args )
Это произвольный свой метод класса, который создает запрос в БД и получает данные по переданным параметрам, в том числе тут учитывается подтип и пагинация.

Используемые Функции:

wp_sitemaps_get_max_urls()
Внешняя функция, которая возвращает макс. кол-во элементов в каждой карте сайта. По умолчанию 2000.

#2 Провайдер на основе постов

Допустим мы доработали URL постов и теперь если в конец ссылки поста дописать /similar_posts/ мы попадаем на страницу с похожими записями. Нам нужно добавить все такие ссылки в Карту сайта.

Назовем провайдера similarposts (в названии нельзя указывать пробелы и тире!) и создадим класс провайдера, так:

class Similar_Posts_Sitemaps_Provider extends WP_Sitemaps_Provider {

	// make visibility not protected
	public $name;

	public function __construct() {

		$this->name        = 'similarposts';
		$this->object_type = 'similarposts';
	}

	public function get_url_list( $page_num, $subtype = '' ) {

		$args = $this->wp_query_args();
		$args['paged'] = $page_num;

		$query = new WP_Query( $args );

		$url_list = array();

		foreach ( $query->posts as $post ) {
			$sitemap_entry = [
				'loc' => user_trailingslashit( untrailingslashit( get_permalink( $post ) ) .'/similar_posts/' ),
			];

			$url_list[] = $sitemap_entry;
		}

		return $url_list;
	}

	public function get_max_num_pages( $subtype = '' ) {

		$args                  = $this->wp_query_args();
		$args['fields']        = 'ids';
		$args['no_found_rows'] = false;

		$query = new WP_Query( $args );

		return $query->max_num_pages;
	}

	protected function wp_query_args(){

		return array(
			'orderby'                => 'ID',
			'order'                  => 'ASC',
			'post_type'              => 'post',
			'posts_per_page'         => wp_sitemaps_get_max_urls( $this->object_type ),
			'post_status'            => array( 'publish' ),
			'no_found_rows'          => true,
			'update_post_term_cache' => false,
			'update_post_meta_cache' => false,
		);
	}

}

Теперь зарегистрируем его:

add_filter( 'init', 'wpkama_register_sitemap_providers' );
function wpkama_register_sitemap_providers(){

	require_once __DIR__ .'/class-Similar_Posts_Sitemaps_Provider.php';
	$provider = new Similar_Posts_Sitemaps_Provider();

	wp_register_sitemap_provider( $provider->name, $provider );
}

Готово! Идем в карту сайта:

#3 Примеры из ядра

Из коробки в WP есть три провайдера (поставщика) карт сайтов для разных типов. Их код можно взять за основу для создания своего провайдера.