Ако някога сте поставяли фрагмент от „schema“ в заглавката и сте виждали Google да игнорира вашите богати резултати, проблемът често произтича от две неща: непълен (или невалиден) JSON-LD и инжектиране на грешното място в цикъла. WordPressЩе поправим това правилно, в код, за WordPress 6.9.4 и PHP 8.1+.

Какво ще изградим

Ще имплементирате структуриран слой данни (Schema.org) в JSON-LD инжектиран в <head>, без плъгин „бюрократичен кошмар“, с:

  • Статия/Публикация в блога върху статиите (заглавие, изображение, автор, дати, издател).
  • Списък с навигационна пътека последователно (полезно дори ако темата ви не показва навигационна пътека).
  • организация + Уеб сайт + Действие при търсене (поле за търсене на връзки към сайта).
  • Механизъм на дедупликация (избягвайте дублиращи се схеми с Yoast/RankMath/SEOPress).
  • Des филтри за персонализиране (лого, социални мрежи, типове съдържание).

Проектиран е за редакционни уебсайтове (блогове, медийни издания, уебсайтове за представяне с блог) и остава съвместим с Divi 5. Elementor и Avada, защото разчитаме на основните куки (не на конструктора).

В крайна сметка ще знаете: къде да инжектирате JSON-LD, как да изградите стабилна схематична графика, как да избегнете дублиране и как да тествате правилно.

Бързо обобщение

  • JSON-LD се инжектира чрез wp_head с MU-плъгин (зарежда се рано, стабилно).
  • Ние изграждаме @граф Уебсайт + Организация + Навигационен списък + Публикуване в блог.
  • Нормализираме URL адреси, дати по ISO 8601, изображения и добавяме... @документ за самоличност стабилен.
  • Избягваме конфликти с SEO плъгини, като откриваме техния изход (и деактивираме блокировките си, ако е необходимо).
  • Потвърждаваме с Тест за богати резултати et Le Валидатор на маркиране на схеми.

Кога да използвате това решение

  • Vous voulez да овладеят вашите структурирани данни (независими от плъгин, който променя логиката).
  • Вашата тема/конструктор не създава нищо (или създава непълна схема) и искате чиста основа.
  • Имате специфични нужди: гост-автор, множество лога, персонализирани breadcrumbs, CPT.
  • Вече имате SEO плъгин, но искате remplacer част от диаграмата му (или я допълнете) с ясна логика.

Според моя опит, това е особено полезно при мигрирани „стари“ Divi/Avada сайтове: SEO оптимизацията е налице, но схемата или липсва, или е дублирана, или е несъвместима между шаблоните.

Кога НЕ трябва да използвате това решение

  • Вие сте начинаещ в PHP и нямате тестова среда: забравена скоба може да наруши работата на сайта.
  • Вече използвате Yoast/RankMath/SEOPress и сте доволни от богатите резултати: не създавайте втора система.
  • Имате разширена схема за стратегия (Продукт, ЧЗВ, Как да, Местен бизнес), задвижвана от бизнес плъгин: по-добре е да разширите този плъгин чрез неговите куки.
  • Не можете да контролирате скривалище (Агресивна CDN): рискувате да тествате остарели версии и да „преследвате призрак“.

Преди да започнете (предварителни изисквания)

Технически предпоставки

  • WordPress 6.9.4 (април 2026 г.) или по-висока.
  • PHP 8.1 + (Препоръчва се 8.2/8.3, ако вашият хостинг доставчик го поддържа).
  • FTP/SSH достъп или файлов мениджър на хостинг доставчика.

Архивиране и среда

  • Направете резервно копие на файловете и базата данни (или моментна снимка, ако сте на услуга за управляван хостинг).
  • Първо тествайте на сайт в предпроизводствена/подготовка. Често съм виждал „прост“ фрагмент да повреди сайт, защото е бил поставен в грешен файл.

Полезни инструменти

  • Плъгин за дебъгване: Query Monitor (по избор, но полезен).
  • Достъп до Search Console (ако искате да измерите въздействието).

Официални източници (препратки)


Стъпка 1: Създайте мини MU плъгин за чисто инжектиране на JSON-LD

Повечето от „счупените“ фрагменти от схема, които отстранявам, идват от лошо място: заседнали са header.phpТова може да се случи в конструктор или в плъгин за фрагменти, който се изпълнява твърде късно. MU-плъгинът решава това: той се зарежда автоматично, преди обикновените плъгини.

Къде да кликна / къде да създам файла

  1. Отворете сайта си чрез FTP/SSH.
  2. отивам wp-content/.
  3. Създайте папката mu-plugins ако не съществува: wp-content/mu-plugins/.
  4. Създайте файла: wp-content/mu-plugins/bpcab-schema-jsonld.php.

Код (стъпка 1)

Поставете този код такъв, какъвто е. Той все още не извежда никаква схема; той настройва „двигателя“: събиране на данни, защитено JSON кодиране и инжектиране в wp_headи филтри.

<?php
/**
 * Plugin Name: BPCAB - Schema JSON-LD (MU)
 * Description: Injection contrôlée de données structurées JSON-LD (Schema.org) pour WordPress 6.9.4+.
 * Author: Votre nom / agence
 * Version: 1.0.0
 *
 * Emplacement: wp-content/mu-plugins/bpcab-schema-jsonld.php
 */

defined('ABSPATH') || exit;

final class BPCAB_Schema_JSONLD {

	/**
	 * Stocke les nœuds du graphe Schema.
	 *
	 * @var array<array>
	 */
	private array $graph = [];

	/**
	 * Singleton simple.
	 */
	private static ?self $instance = null;

	public static function instance(): self {
		if (null === self::$instance) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		// Injection dans le <head> : priorité 20 pour passer après certains plugins/thèmes.
		add_action('wp_head', [$this, 'render_jsonld'], 20);

		// Point d'extension : on construit le graphe quand WP est prêt (requête principale résolue).
		add_action('wp', [$this, 'build_graph'], 20);
	}

	/**
	 * Construit le graphe Schema en fonction du contexte (single, page, home, etc.).
	 */
	public function build_graph(): void {
		// Permet de désactiver totalement via filtre (utile si conflit avec un plugin SEO).
		$enabled = (bool) apply_filters('bpcab_schema_jsonld_enabled', true);
		if (!$enabled) {
			return;
		}

		// Reset à chaque requête.
		$this->graph = [];

		/**
		 * On laisse les étapes suivantes remplir $this->graph via des méthodes dédiées.
		 * Ici, on ne fait rien de plus.
		 */
		do_action('bpcab_schema_jsonld_build', $this);
	}

	/**
	 * Ajoute un nœud au graphe.
	 *
	 * @param array $node
	 */
	public function add_node(array $node): void {
		if (empty($node['@type'])) {
			return;
		}
		$this->graph[] = $node;
	}

	/**
	 * Rend le JSON-LD dans le head.
	 */
	public function render_jsonld(): void {
		if (empty($this->graph)) {
			return;
		}

		$payload = [
			'@context' => 'https://schema.org',
			'@graph'   => array_values($this->graph),
		];

		/**
		 * Filtre final pour ajuster le payload complet.
		 * Attention : ne mettez pas d'objets non sérialisables.
		 */
		$payload = apply_filters('bpcab_schema_jsonld_payload', $payload);

		// Encodage JSON sûr côté WP (gère mieux certains cas que json_encode direct).
		$json = wp_json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

		if (!$json) {
			return;
		}

		echo "n" . '<script type="application/ld+json" id="bpcab-schema-jsonld">' . "n";
		echo $json; // JSON-LD : sortie volontairement non-échappée (c'est du JSON, pas du HTML).
		echo "n" . '</script>' . "n";
	}
}

BPCAB_Schema_JSONLD::instance();

Очакван резултат

  • Няма видими промени отпред (засега).
  • Няма PHP грешки.
  • Кука bpcab_schema_jsonld_build готов за употреба в следващата стъпка.

Стъпка 2: Добавете статия/публикация в блога към статиите (с автор, изображение, дати)

Ще добавим възел Публикуване в блог на отделни публикации. Ключовата разлика: @id конюшниISO дати и правилно оразмерено изображение. Често съм виждал „валидни“, но безполезни JSON-LD файлове, защото изображението е празно или авторът сочи към неканоничен URL адрес.

Добавете този код към същия MU-plugin файл

Поставете следните методи в класната стая BPCAB_Schema_JSONLD (След render_jsonld() например), след което добавете действието, което ги извиква.

/**
 * Enregistre les builders par défaut.
 */
private function register_builders(): void {
	add_action('bpcab_schema_jsonld_build', [$this, 'add_blogposting_for_single'], 20);
}

/**
 * Petit helper : URL canonique de la requête.
 */
private function get_canonical_url(): string {
	// wp_get_canonical_url() existe sur WP modernes, mais on garde une approche simple et fiable.
	if (is_singular()) {
		$url = get_permalink(get_queried_object_id());
		return is_string($url) ? $url : home_url('/');
	}

	// Pour home/blog page/archives, on évite les constructions trop “magiques”.
	return home_url(add_query_arg([]));
}

/**
 * Ajoute BlogPosting sur les articles.
 */
public function add_blogposting_for_single(self $schema): void {
	if (!is_singular('post')) {
		return;
	}

	$post_id = get_queried_object_id();
	$post    = get_post($post_id);
	if (!$post instanceof WP_Post) {
		return;
	}

	$canonical = get_permalink($post_id);
	if (!is_string($canonical) || '' === $canonical) {
		return;
	}

	// Image : privilégiez une taille large (évite les warnings “image too small”).
	$image_url = get_the_post_thumbnail_url($post_id, 'full');
	if (!is_string($image_url)) {
		$image_url = '';
	}

	$author_id  = (int) $post->post_author;
	$author_url = get_author_posts_url($author_id);

	$site_name = get_bloginfo('name');
	$site_url  = home_url('/');

	$logo_id  = (int) get_theme_mod('custom_logo');
	$logo_url = $logo_id ? wp_get_attachment_image_url($logo_id, 'full') : '';

	// @id stables : on s'appuie sur l'URL canonique + fragment.
	$webpage_id     = $canonical . '#webpage';
	$blogposting_id = $canonical . '#blogposting';
	$author_id_uri  = is_string($author_url) ? $author_url . '#author' : $site_url . '#author';
	$publisher_id   = $site_url . '#organization';

	$node = [
		'@type'            => 'BlogPosting',
		'@id'              => $blogposting_id,
		'mainEntityOfPage' => [
			'@type' => 'WebPage',
			'@id'   => $webpage_id,
		],
		'headline'         => get_the_title($post_id),
		'description'      => wp_strip_all_tags(get_the_excerpt($post_id)),
		'datePublished'    => get_the_date(DATE_W3C, $post_id),
		'dateModified'     => get_the_modified_date(DATE_W3C, $post_id),
		'author'           => [
			'@type' => 'Person',
			'@id'   => $author_id_uri,
			'name'  => get_the_author_meta('display_name', $author_id),
			'url'   => is_string($author_url) ? $author_url : '',
		],
		'publisher'        => [
			'@type' => 'Organization',
			'@id'   => $publisher_id,
			'name'  => $site_name,
		],
		'isPartOf'         => [
			'@type' => 'Blog',
			'@id'   => $site_url . '#blog',
			'name'  => $site_name,
			'url'   => $site_url,
		],
		'url'              => $canonical,
	];

	if ('' !== $image_url) {
		$node['image'] = [
			'@type'  => 'ImageObject',
			'url'    => $image_url,
		];
	}

	if (is_string($logo_url) && '' !== $logo_url) {
		$node['publisher']['logo'] = [
			'@type' => 'ImageObject',
			'url'   => $logo_url,
		];
	}

	/**
	 * Filtre par post : permet d'ajuster le node BlogPosting (ajouter keywords, articleSection, etc.).
	 */
	$node = apply_filters('bpcab_schema_jsonld_blogposting_node', $node, $post_id);

	$schema->add_node($node);
}

Добавете обаждането към регистъра на строителите

В строителя __construct()Добавете ред:

private function __construct() {
	add_action('wp_head', [$this, 'render_jsonld'], 20);
	add_action('wp', [$this, 'build_graph'], 20);

	// Enregistre nos builders.
	$this->register_builders();
}

Очакван резултат

  • В дадена статия ще видите <script type="application/ld+json" id="bpcab-schema-jsonld"> в изходния код.
  • JSON съдържа @graph с Objet "@type":"BlogPosting".

Стъпка 3: Добавяне на breadcrumbs в JSON-LD (BreadcrumbList)

Навигационните хлебни трохи са добър „структурен сигнал“. Капанът: генериране на непоследователни URL адреси (http/https, наклонена черта в края) или включване на таксономии, които се променят в зависимост от статията. Предпочитам просто правило: Начало → Блог (ако е страницата със статии) → Главна категория (по избор) → Статия.

Код (навигационна пътека) за добавяне към класа

/**
 * Ajoute BreadcrumbList.
 */
public function add_breadcrumbs(self $schema): void {
	// On ne met pas de breadcrumbs sur la page d'accueil “front page”.
	if (is_front_page()) {
		return;
	}

	$items = [];
	$pos   = 1;

	$home_url = home_url('/');
	$items[] = [
		'@type'    => 'ListItem',
		'position' => $pos++,
		'name'     => 'Accueil',
		'item'     => $home_url,
	];

	// Si une page “Articles” est définie (Réglages > Lecture).
	$page_for_posts = (int) get_option('page_for_posts');
	if ($page_for_posts && !is_home()) {
		$blog_url = get_permalink($page_for_posts);
		if (is_string($blog_url) && '' !== $blog_url) {
			$items[] = [
				'@type'    => 'ListItem',
				'position' => $pos++,
				'name'     => get_the_title($page_for_posts),
				'item'     => $blog_url,
			];
		}
	}

	if (is_singular('post')) {
		// Catégorie “principale” : on prend la première, stable, sans prétendre faire du SEO.
		$cats = get_the_category(get_queried_object_id());
		if (!empty($cats) && $cats[0] instanceof WP_Term) {
			$cat = $cats[0];
			$cat_url = get_term_link($cat);
			if (is_string($cat_url)) {
				$items[] = [
					'@type'    => 'ListItem',
					'position' => $pos++,
					'name'     => $cat->name,
					'item'     => $cat_url,
				];
			}
		}

		$items[] = [
			'@type'    => 'ListItem',
			'position' => $pos++,
			'name'     => get_the_title(get_queried_object_id()),
			'item'     => get_permalink(get_queried_object_id()),
		];
	} elseif (is_page()) {
		$items[] = [
			'@type'    => 'ListItem',
			'position' => $pos++,
			'name'     => get_the_title(get_queried_object_id()),
			'item'     => get_permalink(get_queried_object_id()),
		];
	} elseif (is_category()) {
		$term = get_queried_object();
		if ($term instanceof WP_Term) {
			$items[] = [
				'@type'    => 'ListItem',
				'position' => $pos++,
				'name'     => single_cat_title('', false),
				'item'     => get_term_link($term),
			];
		}
	} elseif (is_tag()) {
		$term = get_queried_object();
		if ($term instanceof WP_Term) {
			$items[] = [
				'@type'    => 'ListItem',
				'position' => $pos++,
				'name'     => single_tag_title('', false),
				'item'     => get_term_link($term),
			];
		}
	} elseif (is_search()) {
		$items[] = [
			'@type'    => 'ListItem',
			'position' => $pos++,
			'name'     => 'Recherche',
			'item'     => home_url('/?s=' . rawurlencode(get_search_query(false))),
		];
	}

	$items = apply_filters('bpcab_schema_jsonld_breadcrumb_items', $items);

	if (count($items) < 2) {
		return;
	}

	$node = [
		'@type'           => 'BreadcrumbList',
		'@id'             => home_url('/') . '#breadcrumbs',
		'itemListElement' => array_values($items),
	];

	$schema->add_node($node);
}

Активирайте този конструктор

категория register_builders(), добавете:

add_action('bpcab_schema_jsonld_build', [$this, 'add_breadcrumbs'], 30);

Очакван резултат

  • В статия JSON-LD съдържа обект "@type":"BreadcrumbList".
  • Списъкът има позиции 1..N без пропуски.

Стъпка 4: Добавете Организация + Уебсайт (и SearchAction) на ниво сайт

без организация et Уеб сайтВъзлите ви „Статия“ често се носят без ясна връзка. Google се справя, но губите последователност. Ще добавим и Действие при търсене (полезно, ако вътрешното ви търсене е достъпно чрез ?s=).

Код за добавяне към класа

/**
 * Ajoute Organization + WebSite.
 */
public function add_site_entities(self $schema): void {
	$site_url  = home_url('/');
	$site_name = get_bloginfo('name');

	$org_type = (string) apply_filters('bpcab_schema_jsonld_org_type', 'Organization');

	$logo_id  = (int) get_theme_mod('custom_logo');
	$logo_url = $logo_id ? wp_get_attachment_image_url($logo_id, 'full') : '';

	// Réseaux sociaux : filtre pour éviter de coder en dur.
	$same_as = (array) apply_filters('bpcab_schema_jsonld_same_as', []);

	$org = [
		'@type' => $org_type,
		'@id'   => $site_url . '#organization',
		'name'  => $site_name,
		'url'   => $site_url,
	];

	if (is_string($logo_url) && '' !== $logo_url) {
		$org['logo'] = [
			'@type' => 'ImageObject',
			'url'   => $logo_url,
		];
	}

	// sameAs doit être un tableau d'URLs publiques (Facebook, LinkedIn, etc.).
	$same_as = array_values(array_filter(array_map('esc_url_raw', $same_as)));
	if (!empty($same_as)) {
		$org['sameAs'] = $same_as;
	}

	$website = [
		'@type'      => 'WebSite',
		'@id'        => $site_url . '#website',
		'url'        => $site_url,
		'name'       => $site_name,
		'publisher'  => [
			'@id' => $site_url . '#organization',
		],
		'inLanguage' => get_bloginfo('language'),
		'potentialAction' => [
			'@type'       => 'SearchAction',
			'target'      => [
				'@type'       => 'EntryPoint',
				'urlTemplate' => $site_url . '?s={search_term_string}',
			],
			'query-input' => 'required name=search_term_string',
		],
	];

	$schema->add_node(apply_filters('bpcab_schema_jsonld_org_node', $org));
	$schema->add_node(apply_filters('bpcab_schema_jsonld_website_node', $website));
}

Активирайте този конструктор

категория register_builders() :

add_action('bpcab_schema_jsonld_build', [$this, 'add_site_entities'], 10);

Очакван резултат

  • На всички страници (освен ако не филтрирате) имате Организация + Уебсайт в @graph.
  • Le publisher вашите статии сочат към #organization.

Стъпка 5: Управление на конкретни страници (начална страница, страница, архив, WooCommerce, ако има такава)

Ако инжектирате една и съща схема навсякъде, ще създадете шум. Ще го коригираме: никакви breadcrumbs на началната страница, никакви BlogPosting на която и да е страница и ще добавим прост WebPage възел за страниците.

Код: WebPage за страници (и резервен вариант)

/**
 * Ajoute un nœud WebPage sur les pages et en complément sur les singulars.
 */
public function add_webpage_node(self $schema): void {
	if (is_front_page()) {
		// Sur l'accueil, on peut aussi sortir WebPage, mais je préfère éviter les graph trop bavards.
		return;
	}

	if (!is_singular()) {
		return;
	}

	$post_id = get_queried_object_id();
	$url     = get_permalink($post_id);
	if (!is_string($url) || '' === $url) {
		return;
	}

	$type = is_page() ? 'WebPage' : 'WebPage';

	$node = [
		'@type'      => $type,
		'@id'        => $url . '#webpage',
		'url'        => $url,
		'name'       => get_the_title($post_id),
		'inLanguage' => get_bloginfo('language'),
		'isPartOf'   => [
			'@id' => home_url('/') . '#website',
		],
	];

	$schema->add_node(apply_filters('bpcab_schema_jsonld_webpage_node', $node, $post_id));
}

Активирайте този конструктор

add_action('bpcab_schema_jsonld_build', [$this, 'add_webpage_node'], 15);

WooCommerce (по избор)

Ако WooCommerce е активен, той често вече се отклонява от продуктовата схема чрез SEO разширения. Не го дублирайте. Ако искате да откриете WooCommerce и да деактивирате определени отклонения, можете да направите следното:

public function maybe_disable_on_products(): void {
	// Exemple : désactiver notre schema sur les pages produit si un plugin SEO gère déjà Product.
	if (function_exists('is_product') && is_product()) {
		add_filter('bpcab_schema_jsonld_enabled', '__return_false', 1);
	}
}

Ако използвате тази предпазна мярка, обадете се ѝ рано (в __construct() от add_action('wp', ... , 1)).


Пълният резултат

Ето една асемблирана (копируема) версия на MU-плъгина, с активирани конструктори. Можете да започнете отначало, като замените файла wp-content/mu-plugins/bpcab-schema-jsonld.php чрез това.

<?php
/**
 * Plugin Name: BPCAB - Schema JSON-LD (MU)
 * Description: Injection contrôlée de données structurées JSON-LD (Schema.org) pour WordPress 6.9.4+.
 * Author: Votre nom / agence
 * Version: 1.0.0
 */

defined('ABSPATH') || exit;

final class BPCAB_Schema_JSONLD {

	private array $graph = [];
	private static ?self $instance = null;

	public static function instance(): self {
		if (null === self::$instance) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		add_action('wp_head', [$this, 'render_jsonld'], 20);
		add_action('wp', [$this, 'build_graph'], 20);

		$this->register_builders();
	}

	private function register_builders(): void {
		add_action('bpcab_schema_jsonld_build', [$this, 'add_site_entities'], 10);
		add_action('bpcab_schema_jsonld_build', [$this, 'add_webpage_node'], 15);
		add_action('bpcab_schema_jsonld_build', [$this, 'add_blogposting_for_single'], 20);
		add_action('bpcab_schema_jsonld_build', [$this, 'add_breadcrumbs'], 30);
	}

	public function build_graph(): void {
		$enabled = (bool) apply_filters('bpcab_schema_jsonld_enabled', true);
		if (!$enabled) {
			return;
		}

		$this->graph = [];
		do_action('bpcab_schema_jsonld_build', $this);
	}

	public function add_node(array $node): void {
		if (empty($node['@type'])) {
			return;
		}
		$this->graph[] = $node;
	}

	public function render_jsonld(): void {
		if (empty($this->graph)) {
			return;
		}

		$payload = [
			'@context' => 'https://schema.org',
			'@graph'   => array_values($this->graph),
		];

		$payload = apply_filters('bpcab_schema_jsonld_payload', $payload);

		$json = wp_json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
		if (!$json) {
			return;
		}

		echo "n" . '<script type="application/ld+json" id="bpcab-schema-jsonld">' . "n";
		echo $json;
		echo "n" . '</script>' . "n";
	}

	public function add_site_entities(self $schema): void {
		$site_url  = home_url('/');
		$site_name = get_bloginfo('name');

		$org_type = (string) apply_filters('bpcab_schema_jsonld_org_type', 'Organization');

		$logo_id  = (int) get_theme_mod('custom_logo');
		$logo_url = $logo_id ? wp_get_attachment_image_url($logo_id, 'full') : '';

		$same_as = (array) apply_filters('bpcab_schema_jsonld_same_as', []);

		$org = [
			'@type' => $org_type,
			'@id'   => $site_url . '#organization',
			'name'  => $site_name,
			'url'   => $site_url,
		];

		if (is_string($logo_url) && '' !== $logo_url) {
			$org['logo'] = [
				'@type' => 'ImageObject',
				'url'   => $logo_url,
			];
		}

		$same_as = array_values(array_filter(array_map('esc_url_raw', $same_as)));
		if (!empty($same_as)) {
			$org['sameAs'] = $same_as;
		}

		$website = [
			'@type'      => 'WebSite',
			'@id'        => $site_url . '#website',
			'url'        => $site_url,
			'name'       => $site_name,
			'publisher'  => [
				'@id' => $site_url . '#organization',
			],
			'inLanguage' => get_bloginfo('language'),
			'potentialAction' => [
				'@type'       => 'SearchAction',
				'target'      => [
					'@type'       => 'EntryPoint',
					'urlTemplate' => $site_url . '?s={search_term_string}',
				],
				'query-input' => 'required name=search_term_string',
			],
		];

		$schema->add_node(apply_filters('bpcab_schema_jsonld_org_node', $org));
		$schema->add_node(apply_filters('bpcab_schema_jsonld_website_node', $website));
	}

	public function add_webpage_node(self $schema): void {
		if (is_front_page()) {
			return;
		}

		if (!is_singular()) {
			return;
		}

		$post_id = get_queried_object_id();
		$url     = get_permalink($post_id);
		if (!is_string($url) || '' === $url) {
			return;
		}

		$node = [
			'@type'      => 'WebPage',
			'@id'        => $url . '#webpage',
			'url'        => $url,
			'name'       => get_the_title($post_id),
			'inLanguage' => get_bloginfo('language'),
			'isPartOf'   => [
				'@id' => home_url('/') . '#website',
			],
		];

		$schema->add_node(apply_filters('bpcab_schema_jsonld_webpage_node', $node, $post_id));
	}

	public function add_blogposting_for_single(self $schema): void {
		if (!is_singular('post')) {
			return;
		}

		$post_id = get_queried_object_id();
		$post    = get_post($post_id);
		if (!$post instanceof WP_Post) {
			return;
		}

		$canonical = get_permalink($post_id);
		if (!is_string($canonical) || '' === $canonical) {
			return;
		}

		$image_url = get_the_post_thumbnail_url($post_id, 'full');
		if (!is_string($image_url)) {
			$image_url = '';
		}

		$author_id  = (int) $post->post_author;
		$author_url = get_author_posts_url($author_id);

		$site_name = get_bloginfo('name');
		$site_url  = home_url('/');

		$logo_id  = (int) get_theme_mod('custom_logo');
		$logo_url = $logo_id ? wp_get_attachment_image_url($logo_id, 'full') : '';

		$webpage_id     = $canonical . '#webpage';
		$blogposting_id = $canonical . '#blogposting';
		$author_id_uri  = is_string($author_url) ? $author_url . '#author' : $site_url . '#author';
		$publisher_id   = $site_url . '#organization';

		$node = [
			'@type'            => 'BlogPosting',
			'@id'              => $blogposting_id,
			'mainEntityOfPage' => [
				'@type' => 'WebPage',
				'@id'   => $webpage_id,
			],
			'headline'         => get_the_title($post_id),
			'description'      => wp_strip_all_tags(get_the_excerpt($post_id)),
			'datePublished'    => get_the_date(DATE_W3C, $post_id),
			'dateModified'     => get_the_modified_date(DATE_W3C, $post_id),
			'author'           => [
				'@type' => 'Person',
				'@id'   => $author_id_uri,
				'name'  => get_the_author_meta('display_name', $author_id),
				'url'   => is_string($author_url) ? $author_url : '',
			],
			'publisher'        => [
				'@type' => 'Organization',
				'@id'   => $publisher_id,
				'name'  => $site_name,
			],
			'isPartOf'         => [
				'@type' => 'Blog',
				'@id'   => $site_url . '#blog',
				'name'  => $site_name,
				'url'   => $site_url,
			],
			'url'              => $canonical,
		];

		if ('' !== $image_url) {
			$node['image'] = [
				'@type' => 'ImageObject',
				'url'   => $image_url,
			];
		}

		if (is_string($logo_url) && '' !== $logo_url) {
			$node['publisher']['logo'] = [
				'@type' => 'ImageObject',
				'url'   => $logo_url,
			];
		}

		$node = apply_filters('bpcab_schema_jsonld_blogposting_node', $node, $post_id);

		$schema->add_node($node);
	}

	public function add_breadcrumbs(self $schema): void {
		if (is_front_page()) {
			return;
		}

		$items = [];
		$pos   = 1;

		$home_url = home_url('/');
		$items[] = [
			'@type'    => 'ListItem',
			'position' => $pos++,
			'name'     => 'Accueil',
			'item'     => $home_url,
		];

		$page_for_posts = (int) get_option('page_for_posts');
		if ($page_for_posts && !is_home()) {
			$blog_url = get_permalink($page_for_posts);
			if (is_string($blog_url) && '' !== $blog_url) {
				$items[] = [
					'@type'    => 'ListItem',
					'position' => $pos++,
					'name'     => get_the_title($page_for_posts),
					'item'     => $blog_url,
				];
			}
		}

		if (is_singular('post')) {
			$cats = get_the_category(get_queried_object_id());
			if (!empty($cats) && $cats[0] instanceof WP_Term) {
				$cat = $cats[0];
				$cat_url = get_term_link($cat);
				if (is_string($cat_url)) {
					$items[] = [
						'@type'    => 'ListItem',
						'position' => $pos++,
						'name'     => $cat->name,
						'item'     => $cat_url,
					];
				}
			}

			$items[] = [
				'@type'    => 'ListItem',
				'position' => $pos++,
				'name'     => get_the_title(get_queried_object_id()),
				'item'     => get_permalink(get_queried_object_id()),
			];
		} elseif (is_page()) {
			$items[] = [
				'@type'    => 'ListItem',
				'position' => $pos++,
				'name'     => get_the_title(get_queried_object_id()),
				'item'     => get_permalink(get_queried_object_id()),
			];
		} elseif (is_category()) {
			$term = get_queried_object();
			if ($term instanceof WP_Term) {
				$items[] = [
					'@type'    => 'ListItem',
					'position' => $pos++,
					'name'     => single_cat_title('', false),
					'item'     => get_term_link($term),
				];
			}
		} elseif (is_tag()) {
			$term = get_queried_object();
			if ($term instanceof WP_Term) {
				$items[] = [
					'@type'    => 'ListItem',
					'position' => $pos++,
					'name'     => single_tag_title('', false),
					'item'     => get_term_link($term),
				];
			}
		} elseif (is_search()) {
			$items[] = [
				'@type'    => 'ListItem',
				'position' => $pos++,
				'name'     => 'Recherche',
				'item'     => home_url('/?s=' . rawurlencode(get_search_query(false))),
			];
		}

		$items = apply_filters('bpcab_schema_jsonld_breadcrumb_items', $items);

		if (count($items) < 2) {
			return;
		}

		$node = [
			'@type'           => 'BreadcrumbList',
			'@id'             => home_url('/') . '#breadcrumbs',
			'itemListElement' => array_values($items),
		];

		$schema->add_node($node);
	}
}

BPCAB_Schema_JSONLD::instance();

Бързо персонализиране (примери)

Добавете тези филтри в класически плъгин или в дъщерната си тема (не в MU-плъгина, ако искате да можете да го актуализирате лесно).

<?php
// Exemple : ajouter vos réseaux sociaux à sameAs.
add_filter('bpcab_schema_jsonld_same_as', function(array $same_as): array {
	$same_as[] = 'https://www.linkedin.com/company/votre-marque/';
	$same_as[] = 'https://www.youtube.com/@votre-chaine';
	return $same_as;
});

// Exemple : changer le type d'organisation.
add_filter('bpcab_schema_jsonld_org_type', function(string $type): string {
	return 'Organization'; // Ou 'NewsMediaOrganization' si pertinent.
});

Адаптиране за Divi 5 / Elementor / Avada

Добра новина: докато инжектираме чрез wp_headОбикновено работи „без адаптация“. Строителите имат значително влияние. съдържание (HTML кодът), а не WP заявката.

Диви 5

  • Divi може да добавя schema markup чрез модули/SEO инструменти на трети страни. Проверете за дубликати.
  • Ако използвате Divi модул, за да покажете заглавието/откъса по различен начин, това не променя нищо: ние четем данните от WordPress (заглавие, откъс, изображение).

Съвет за Divi, който използвам: ако изображението, което ви е представено, често липсва (Divi вмъква изображения в съдържанието), добавете изображение с помощта на филтър. bpcab_schema_jsonld_blogposting_node чрез търсене на първото изображение на съдържанието (методът трябва да се прилага с повишено внимание за по-добра производителност).

Elementor

  • Elementor може да обработва шаблони за една публикация, но is_singular('post') остава правилен.
  • В някои сайтове съм виждал празни откъси, защото са „визуални“. В този случай, заменете description чрез извадка, генерирана от съдържанието (с ограничена дължина) чрез филтър.

Avada

  • Avada/Fusion Builder понякога има SEO/схема опции. Деактивирайте ги, ако запазите този MU-плъгин, в противен случай ще имате два конкуриращи се JSON-LD.
  • Avada често показва логото чрез опциите си за тема. Ако custom_logo е празно, можете да инжектирате URL адреса на вашето лого чрез bpcab_schema_jsonld_org_node.

Последна проверка

1) Проверете изходния код

  1. Отворете статия в режим на частно сърфиране.
  2. Прегледайте изходния код (не инспектора, а „view-source“).
  3. Търсене bpcab-schema-jsonld.

Трябва да видите валиден JSON файл, с @context et @graph.

2) Валидиране на схемата

3) Проверете за дубликати

Ако видите множество JSON-LD блокове, това не е непременно „забранено“, но бързо става непоследователно. Потърсете:

  • plusieurs BlogPosting за същия URL адрес,
  • plusieurs Organization с различни имена,
  • на @id които не сочат към вашия каноничен домейн.

Ако резултатът не е такъв, какъвто се очаква

симптом Вероятна причина проверка Решение
Нищо не се появява в главата MU-плъгин файлът е на грешното място Проверка wp-content/mu-plugins/ и името на файла Поставете файла директно в mu-plugins (не е в подпапка)
Грешка 500 след добавяне на код PHP синтаксис (точка и запетая, къдрава скоба) Проверете PHP лог файловете на хостинг доставчика Връщане към предишното състояние, коригиране на маркирания ред.
Тестът на Google не открива актуализацията Кеш (плъгин, сървър, CDN) Сравнете „view-source“ с това, което Google извлича Изчистете кеша на плъгина + кеша на сървъра + CDN, след което тествайте в режим „инкогнито“.
Предупреждения: „дублирана“ или непоследователна схема SEO плъгинът също така инжектира JSON-LD търсене application/ld+json в източника Деактивирайте схемата на SEO плъгина или деактивирайте този MU-плъгин чрез филтър
Липсва изображение в BlogPubling Няма представено изображение Проверете за наличие на представено изображение Добавете препоръчително изображение или персонализирайте чрез филтър

Контролен списък за бързи отстраняване на неизправности

  • Поставихте кода правилно. в класната стая (и не след края на работното време) })?
  • Имате ли твърде стара версия на PHP (поне 8.1)?
  • Изчистихте ли кеша на браузъра си (или тествахте ли в частен режим)?
  • Не си поставил стар фрагмент, използвайки остарели функции, нали?

Често срещани капани и грешки

Грешка Причина Решение
Поставете JSON-LD скрипта в header.php Темата/конструкторът може да го дублира, а актуализацията го презаписва. Използвайте MU-плъгин или специален плъгин
употреба json_encode() без опции Непоследователно екраниране, Unicode символи, наклонени черти употреба wp_json_encode() (WP) с опции
Неподходяща кука (напр. init) Контекстът на заявката не е разрешен Постройте графиката върху wp, отидете на wp_head
Дублирана организация чрез SEO плъгин Два източника на истината Деактивирайте един от двата изхода (SEO плъгин или филтър) bpcab_schema_jsonld_enabled)
Дати, които не са по ISO Използвайте локализиран формат употреба DATE_W3C (ISO 8601)
Фрагмент, повреден от плъгин за фрагменти Ред на зареждане / тихи грешки Предпочитам MU-плъгин за основата

Вариант / алтернатива

Алтернатива без код (SEO плъгин)

Ако вашите нужди са „стандартни“, често е достатъчен скорошен SEO плъгин. Трите, които виждам най-често в продукцията, са:

  • Yoast SEO (доста изчерпателна схема, но понякога многословна)
  • Ранг математика (много опции, пазете се от дубликати)
  • SEOPress (добър компромис, по-лек в зависимост от конфигурацията)

Ако ще използвате плъгин, създайте правило: един схематичен генераторСмесването на плъгини и фрагменти почти винаги води до дублиране.

По-усъвършенствана алтернатива: Схема на тип съдържание (CPT)

Ако имате CPT-и (напр. „Подкаст“, ​​„Рецепта“, „Събитие“), запазете този MU-плъгин като основа и добавете конструктори на условия (напр. is_singular('event')) със специфични възли на схемата. Не претоварвайте BlogPosting за всичко.


Съвети за безопасност, производителност и поддръжка

  • Сигурност Никога не включвайте нефилтрирани потребителски данни (ACF полета, редактируеми от роли на ниско ниво, коментари и др.) в JSON-LD. JSON-LD е текст в секцията head: лошо управляваното инжектиране може да се превърне в XSS уязвимост, ако конкатенирате HTML. Тук разчитаме на функциите на WordPress и... wp_json_encode().
  • Изпълнение Избягвайте използването на тежки заявки (напр. извличане на 50 термина) за навигационни пътеки. Поддържайте ги прости. Заглавната секция се визуализира на всяка страница.
  • Кеш Ако имате HTML кеш, всяка промяна на схемата изисква изчистване. Често съм губил време за „бъг“, който просто е бил Cloudflare, обслужващ по-стара версия.
  • поддръжка Версия на този файл (Git). Неверсиран MU-плъгин бързо се превръща в „нещото, което вече не смееш да докоснеш“.

Деактивирайте бързо в случай на конфликт

Добавете това (плъгин или дъщерна тема), за да изрежете незабавно схемата:

<?php
add_filter('bpcab_schema_jsonld_enabled', '__return_false');

За да се отиде по-далеч

  • Прибавям Човек попълнете за автори (изображение, jobTitle, sameAs) и свържете чрез @id.
  • Прибавям Раздел на статията (категория) и ключови думи (етикети) чрез bpcab_schema_jsonld_blogposting_node (внимавайте да не спамите).
  • Прибавям Говорим ако правите аудио/асистенти (специфичен случай на употреба).
  • Добавете режим „редактор“: metabox, за да изберете основната категория на breadcrumb (избягва случайността на първата категория).

ресурси

Често задавани въпроси

JSON-LD гарантира ли богати фрагменти?

Не. Вие предоставяте структурирани сигнали, но Google решава. Какво контролирате вие: валидност, последователност и липса на дубликати.

Защо JSON-LD, а не микроданни в HTML?

JSON-LD е по-лесен за поддръжка и по-малко крехък при работа с конструктори. Микроданните се повреждат бързо веднага щом модул промени HTML структурата.

Проблем ли е да има няколко JSON-LD скрипта?

Не е задължително. Проблемът е: няколко възела, които описват едно и също нещо с различни стойности (две организации, две публикации в блогове, две различни breadcrumbs).

Къде да конфигурирам логото, използвано в Организация?

Кодът използва custom_logo (Външен вид > Персонализиране > Идентичност на сайта). Ако вашата тема не предоставя тази информация, инжектирайте лого, като използвате филтъра. bpcab_schema_jsonld_org_node.

Защо да градите на куката wp и да се върнем на wp_head ?

wp гарантира, че основната заявка е решена (знаете дали сте на статия, страница, категория). wp_head е стандартното място за извеждане на метаданни. Смесването на двете създава гранични случаи.

Откъсът ми е празен с Elementor/Divi, какво трябва да направя?

Заменете description от bpcab_schema_jsonld_blogposting_node Генерирайте извадка от съдържанието (премахнете етикетите за strip, ограничение на дължината). Направете го правилно, за да избегнете вмъкването на огромни количества текст.

Съвместим ли е с многоезичен уебсайт?

Да, но трябва да проверите това home_url() et get_permalink() Те правилно връщат URL адреса на текущия език (това зависи от Polylang/WPML). Настройте inLanguage ако вашият плъгин не се актуализира get_bloginfo('language') по език.

Можем ли да добавим страница с ЧЗВ или „Как да“?

Да, но ви съветвам да го правите на конкретни страници, а не глобално. Добавете конструктор на условия (за всеки идентификатор на страница или шаблон), за да избегнете замърсяване на целия сайт.

Този код замества ли SEO плъгин?

Не. Той само замества частта „JSON-LD схема“. SEO плъгинът управлява също Sitemap, meta robots, разширени канонични тагове, социални интеграции и др.

Ами ако SEO плъгин вече инжектира схема и искам да запазя плъгина?

Или деактивирате схематичния изход на плъгина (ако съществува опцията), или деактивирате този MU-плъгин чрез bpcab_schema_jsonld_enabledСмесването на двете без стратегия рядко завършва добре.