В корпоративной WordPress-разработке стандартных возможностей блоков ядра (core/image, core/paragraph, core/heading, core/group) часто недостаточно: бизнесу требуются анимации при скролле, всплывающие окна Lightbox, микроразметка Schema.org, аналитические data-атрибуты или кастомные бейджи.
Неопытные разработчики совершают грубейшую архитектурную ошибку — клонирование блоков ядра (создание плагина my-theme/custom-image или my-theme/custom-heading ради добавления одного чекбокса или поля). Это приводит к тяжелым последствиям:
loading="lazy".Архитектура Gutenberg решает эту проблему через реализацию принципа Open/Closed (OCP из SOLID): блоки ядра закрыты для прямой модификации исходного кода, но открыты для расширения через систему перехвата хуков (@wordpress/hooks).
Фильтры блоков позволяют внедрить новые атрибуты, добавить элементы управления в боковую панель настроек InspectorControls и модифицировать результирующий HTML-код стандартных блоков ядра без написания дублирующих компонентов с нуля.
Система фильтрации блоков построена на базе библиотеки @wordpress/hooks (зеркалирующей систему фильтров ядра WordPress в рантайме JavaScript) и компонентов высшего порядка (Higher-Order Components / HOC) библиотеки @wordpress/compose.
Жизненный цикл перехвата блока охватывает пять ключевых точек расширения:
┌────────────────────────────────────────────────────────────────────────┐
│ 1. РАСШИРЕНИЕ СХЕМЫ БЛОКА │
│ blocks.registerBlockType │
│ Инъекция новых атрибутов (attributes) в реестр метаданных │
└───────────────────────────────────┬────────────────────────────────────┘
│
┌──────────────────────────┴──────────────────────────┐
▼ ▼
┌─────────────────────────────────┐ ┌──────────────────────────────────┐
│ 2. РАСШИРЕНИЕ ИНТЕРФЕЙСА │ │ 3. ХОЛСТ РЕДАКТОРА │
│ editor.BlockEdit (HOC) │ │ editor.BlockListBlock (HOC) │
│ - Внедрение в InspectorControls│ │ - Классы обертки на холсте │
│ - Панели настроек (PanelBody) │ │ - Бейджи и маркеры для авторов │
└────────────────┬────────────────┘ └──────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────┐
│ 4. КЛИЕНТСКАЯ СЕРИАЛИЗАЦИЯ │
│ blocks.getSaveContent.extraProps │
│ Запись атрибутов в HTML-тег при сохранении (data-*, class, style) │
└───────────────────────────────────┬────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────┐
│ 5. СЕРВЕРНАЯ ПОСТ-ОБРАБОТКА (PHP) │
│ render_block / WP_HTML_Tag_Processor │
│ Безопасная модификация HTML перед отправкой в браузер клиента │
└────────────────────────────────────────────────────────────────────────┘extraProps vs Серверный render_block#Это главное архитектурное решение при расширении блоков:
| Метод модификации | Когда применять | Риски и ограничения |
| :--- | :--- | :--- |
| blocks.getSaveContent.extraProps (JS) | Новый проект, где блок создается с нуля или расширяется до первой публикации контента. | Опасность Block Validation Error: если применить фильтр на живом сайте, где блок уже сохранен в сотнях постов, старый HTML в базе перестанет совпадать со сгенерированным, вызвав ошибку валидации. |
| render_block + WP_HTML_Tag_Processor (PHP) | Расширение блоков на работающем продакшн-сайте с существующим контентом. | Выполняется на сервере в момент отдачи страницы. Полностью безопасен для валидатора Gutenberg: в базе сохраняются только JSON-атрибуты, а разметка обогащается динамически. |
Перехват схемы регистрации целевого блока (например, core/image или core/heading) и объявление новых атрибутов с типизацией (string, boolean, number).
Без этого шага редактор проигнорирует новые данные при попытке их сохранения через setAttributes.
Разработка обертки через createHigherOrderComponent. Проверка имени блока и добавление панели InspectorControls с компонентами ввода (SelectControl, ToggleControl).
Инкапсулирует оригинальный компонент BlockEdit и передает ему неизмененные пропсы через {...props}.
Опциональное добавление служебных CSS-классов и визуальных бейджей непосредственно в рабочую область редактирования статьи для наглядности.
Позволяет авторам видеть включенные спецэффекты (например, рамку анимации) прямо в процессе верстки.
Для новых блоков — внедрение data-атрибутов через JS-фильтр extraProps. Для существующих страниц — серверный фильтр render_block на PHP.
Использование современного стримингового парсера WP_HTML_Tag_Processor исключает поломку HTML-тегов.
Компиляция TypeScript-кода через @wordpress/scripts и подключение скрипта в админке с обязательным файлом index.asset.php.
Гарантирует загрузку скрипта строго после регистрации ядра Gutenberg и предотвращает ошибки undefined wp.hooks.
Хук blocks.registerBlockType проверяет if (settings.name === 'core/image'). В объект attributes добавляются поля enableLightbox и animationType.
Хук editor.BlockEdit монтирует HOC-обертку. В боковой панели появляется секция «Инженерные параметры Fullstack Agentic Lab» с селекторами анимации.
Пользователь выбирает значение 'fade-up' в селекторе. Срабатывает setAttributes({ animationType: 'fade-up' }). Атрибут сохраняется в JSON-комментарии блока.
При сохранении страницы ядро парсит атрибуты. Если используется JS-фильтр extraProps, тег <figure> получает класс has-animation и data-aos='fade-up'.
При загрузке страницы хук render_block с помощью WP_HTML_Tag_Processor находит тег <img>, проверяет атрибуты блока и внедряет атрибуты без регулярных выражений.
Разработчик создал HOC-компонент через editor.BlockEdit, добавил в InspectorControls переключатель ToggleControl и привязал его к setAttributes({ enableLazyAnimation: val }). Однако при перезагрузке страницы значение переключателя сбрасывается в false. В чем причина?
Ниже представлен продакшн-код расширения стандартного блока изображений core/image: добавление выбора эффекта анимации (AOS) и тумблера Lightbox с безопасной серверной пост-обработкой через WP_HTML_Tag_Processor.
src/filters/attributes.ts#import { addFilter } from '@wordpress/hooks';
import type { BlockSettings } from '@wordpress/blocks';
interface ExtendedAttributes {
agenticAnimation?: {
type: 'string';
default: string;
};
agenticLightbox?: {
type: 'boolean';
default: boolean;
};
}
/**
* Расширяем схему стандартных блоков core/image и core/group новыми атрибутами
*/
function addCustomBlockAttributes(
settings: BlockSettings<any>,
name: string
): BlockSettings<any> {
// Фильтруем только целевые блоки ядра
if (!['core/image', 'core/group', 'core/cover'].includes(name)) {
return settings;
}
const extendedAttributes: ExtendedAttributes = {
agenticAnimation: {
type: 'string',
default: 'none',
},
agenticLightbox: {
type: 'boolean',
default: false,
},
};
return {
...settings,
attributes: {
...settings.attributes,
...extendedAttributes,
},
};
}
addFilter(
'blocks.registerBlockType',
'agenticlab/extended-attributes',
addCustomBlockAttributes
);src/filters/with-animation-controls.tsx#import React from 'react';
import { createHigherOrderComponent } from '@wordpress/compose';
import { InspectorControls } from '@wordpress/block-editor';
import { PanelBody, SelectControl, ToggleControl } from '@wordpress/components';
import { addFilter } from '@wordpress/hooks';
import { __ } from '@wordpress/i18n';
interface BlockEditProps {
name: string;
attributes: {
agenticAnimation?: string;
agenticLightbox?: boolean;
[key: string]: any;
};
setAttributes: (attrs: Record<string, any>) => void;
[key: string]: any;
}
/**
* HOC-компонент, внедряющий панель «Эффекты и интерактивность» в InspectorControls
*/
const withCustomControls = createHigherOrderComponent(
(BlockEdit: React.ComponentType<any>) => {
return (props: BlockEditProps) => {
// Применяем настройки только к разрешенным блокам
if (!['core/image', 'core/group', 'core/cover'].includes(props.name)) {
return <BlockEdit {...props} />;
}
const { attributes, setAttributes } = props;
const { agenticAnimation, agenticLightbox } = attributes;
const animationOptions = [
{ label: __('Без анимации', 'agenticlab'), value: 'none' },
{ label: __('Плавное появление (Fade Up)', 'agenticlab'), value: 'fade-up' },
{ label: __('Масштабирование (Zoom In)', 'agenticlab'), value: 'zoom-in' },
{ label: __('Сдвиг слева (Slide Right)', 'agenticlab'), value: 'slide-right' },
];
return (
<>
{/* Рендерим оригинальный компонент блока ядра */}
<BlockEdit {...props} />
{/* Внедряем свою вкладку в боковую панель редактора */}
<InspectorControls>
<PanelBody
title={__('Спецэффекты Fullstack Agentic Lab', 'agenticlab')}
icon="star-filled"
initialOpen={false}
>
<SelectControl
label={__('Анимация при скролле', 'agenticlab')}
value={agenticAnimation || 'none'}
options={animationOptions}
onChange={(val: string) => setAttributes({ agenticAnimation: val })}
help={__('Анимация будет воспроизведена при появлении блока во Viewport.', 'agenticlab')}
/>
{props.name === 'core/image' && (
<ToggleControl
label={__('Всплывающее окно (Lightbox)', 'agenticlab')}
checked={Boolean(agenticLightbox)}
onChange={(val: boolean) => setAttributes({ agenticLightbox: val })}
help={__('Увеличение изображения по клику без перехода на новую страницу.', 'agenticlab')}
/>
)}
</PanelBody>
</InspectorControls>
</>
);
};
},
'withCustomControls'
);
addFilter(
'editor.BlockEdit',
'agenticlab/with-custom-controls',
withCustomControls
);WP_HTML_Tag_Processor: inc/render-filter.php#Самый безопасный способ для продакшн-сайта с существующим контентом. Мы перехватываем вывод блоков на хуке ядра render_block и применяем потоковый парсер WP_HTML_Tag_Processor (доступен с WordPress 6.2+). Это гарантирует корректную модификацию без регулярных выражений и риска ошибки валидации:
<?php
/**
* Серверная пост-обработка блоков с добавлением интерактивных атрибутов.
*/
declare(strict_types=1);
namespace AgenticLab\Filters;
use WP_HTML_Tag_Processor;
defined('ABSPATH') || exit;
add_filter('render_block', function (string $block_content, array $block): string {
// 1. Проверяем, есть ли сохраненные кастомные атрибуты
$attrs = $block['attrs'] ?? [];
$animation = $attrs['agenticAnimation'] ?? 'none';
$lightbox = $attrs['agenticLightbox'] ?? false;
if ($animation === 'none' && !$lightbox) {
return $block_content;
}
// 2. Инициализируем быстрый потоковый парсер тегов ядра
$processor = new WP_HTML_Tag_Processor($block_content);
// 3. Обработка блока изображения
if ($block['blockName'] === 'core/image') {
// Находим тег <figure> для анимации
if ($processor->next_tag(['tag_name' => 'FIGURE'])) {
if ($animation !== 'none') {
$processor->add_class('agentic-animated');
$processor->set_attribute('data-aos', esc_attr($animation));
}
}
// Если включен Lightbox, находим вложенный тег <img>
if ($lightbox && $processor->next_tag(['tag_name' => 'IMG'])) {
$processor->add_class('agentic-lightbox-trigger');
$processor->set_attribute('data-lightbox-enabled', 'true');
}
return $processor->get_updated_html();
}
// 4. Обработка контейнеров core/group и core/cover
if (in_array($block['blockName'], ['core/group', 'core/cover'], true)) {
if ($processor->next_tag()) {
if ($animation !== 'none') {
$processor->add_class('agentic-animated');
$processor->set_attribute('data-aos', esc_attr($animation));
}
return $processor->get_updated_html();
}
}
return $block_content;
}, 10, 2);src/filters/styles.ts#Gutenberg предоставляет легковесную альтернативу фильтрам разметки — регистрацию стилей блока через registerBlockStyle. Она добавляет CSS-класс is-style-{name} к блоку:
import { registerBlockStyle, unregisterBlockStyle } from '@wordpress/blocks';
import domReady from '@wordpress/dom-ready';
domReady(() => {
// Добавляем фирменный стиль карточки для стандартного блока цитаты
registerBlockStyle('core/quote', {
name: 'agentic-callout',
label: 'Инженерный акцент',
isDefault: false,
});
// Добавляем стиль градиентного бордера для кнопки
registerBlockStyle('core/button', {
name: 'agentic-gradient-glow',
label: 'Свечение',
isDefault: false,
});
// Отключаем ненужный стандартный стиль ядра, если он конфликтует с дизайном
unregisterBlockStyle('core/separator', 'dots');
});На интернет-портале с 15 000 опубликованных статей нужно добавить атрибут data-analytics='lead-quote' ко всем блокам core/quote, у которых включен определенный переключатель. Какой метод гарантирует отсутствие сбоев у редакторов?
Если в функции высшего порядка не написать условие выхода:
if (!['core/image'].includes(props.name)) {
return <BlockEdit {...props} />;
}Панель настроек будет внедрена абсолютно во все блоки страницы (в каждый абзац, заголовок, разделитель, шорткод). Это приведет к захламлению интерфейса, непредсказуемым мутациям атрибутов и зависанию редактора.
WP_HTML_Tag_Processor#Использование preg_replace('/<img (.*?)>/', ...) в фильтре render_block — опасный антипаттерн. Регулярные выражения в PHP ломают верстку, если внутри атрибутов alt или title встречаются кавычки, угловые скобки или многострочный текст. Парсер WP_HTML_Tag_Processor работает как детерминированный автомат и никогда не разрушит структуру HTML.
settings#В фильтре blocks.registerBlockType необходимо всегда возвращать новый объект через поверхностное копирование (return { ...settings, attributes: { ... } }), а не мутировать входящий объект settings.attributes.newAttr = ... напрямую. Прямая мутация приводит к эффектам гонки (Race Conditions) и сбоям в работе расширений от других плагинов.