Бинды
Бинд подставляет значение внутрь тега фрейма — href, src или текст элемента — из токена вроде site.login_url или page.title. Здесь синтаксис, полный словарь и то, что бывает с токеном без значения.
Зачем нужны бинды
Вставка вносит кусок разметки в поток, но внутрь тега попасть не может. А значение, которое надо менять, часто живёт именно там: href кнопки входа, src логотипа, текст заголовка, дата в datetime. Поэтому тег помечается зарезервированным атрибутом data-op-bind, а при печати атрибуты переписывает ядровый WP_HTML_Tag_Processor — потоковый переписыватель HTML из WordPress, без регулярок и без разбора документа целиком. Имя взято у ядрового Block Bindings API, который делает то же для атрибутов зарегистрированных блоков.
Сам атрибут в выводе остаётся. Для браузера это обычный data-атрибут, он ничего не ломает и показывает, что было подставлено. Печать всегда идёт из исходника, так что ничего не накапливается.
Синтаксис
Значение data-op-bind — JSON-объект: ключ — атрибут, который надо выставить, значение — токен.
<a class="site-footer__login" href="/wp-login.php" data-op-bind='{"href":"site.login_url"}'>Войти</a>
<img class="site-header__logo-img" alt="" data-op-bind='{"src":"site.logo_url","alt":"site.name"}'>
Одинарные кавычки вокруг атрибута обязательны. JSON состоит из двойных кавычек; в атрибуте с двойными кавычками он рвётся на первой же из них, и бинд молча теряется целиком. По той же причине в токене и в литерале не должно быть двойной кавычки. POST /frames предупреждает о data-op-bind в двойных кавычках полем warnings, но фрейм всё равно сохраняет, поэтому предупреждения надо читать:
"warnings": ["data-op-bind is written with double quotes: JSON inside an attribute must be kept in single quotes, otherwise the attribute breaks at the first double quote of the value"]
Два ключа — не атрибуты, а содержимое элемента:
| Ключ | Что выставляет |
|---|---|
text | содержимое элемента, с экранированием HTML |
html | содержимое элемента как есть; для значений-разметки, например ответа в FAQ |
| любой другой ключ | атрибут с этим именем: href, src, alt, datetime, data-page-title… |
text и html заменяют всё, что лежит в элементе в исходнике. На пустых элементах (img, input, meta и подобных) они не работают, в отчёте печати будет не удалось подставить содержимое элемента #N.
Формы токена
| Форма | Пример | Значение |
|---|---|---|
| токен словаря | site.home_url, page.title | из словаря ниже |
raw:<значение> | raw:/promo/ | литерал после raw:, как есть |
item.<поле> | item.q, item.prev.title, item.children.0.url | поле текущего элемента внутри op/repeat, вложенность через точку |
option.<имя> | option.blogdescription | скалярная опция текущего сайта; несуществующая опция даёт пустую строку |
item.* существует только внутри повторителя: поля каждого элемента подставляет сам повторитель. Скаляры приводятся к строке, true — к "1", false — к ""; у массива, объекта, null и отсутствующего поля значения нет. Вне повторителя токен item.* молча пропускается. Как списки попадают в повторитель — в разделе Данные.
Те же токены работают значениями match у op/repeat, так что фильтр и бинд понимают page.slug одинаково.
Словарь токенов
Словарь собирается по печатаемому сайту на языке печати. Токена, у которого в текущей ситуации нет значения, в словаре просто нет — см. «Неразрешённые токены» ниже.
site
| Токен | Значение |
|---|---|
site.home_url, site.url | адрес главной со слешем на конце |
site.site_url | адрес WordPress |
site.name, site.description | название и описание сайта |
site.charset | кодировка, UTF-8 |
site.login_url | страница входа |
site.logout_url | ссылка выхода с возвратом на главную; содержит nonce |
site.admin_url | админка |
site.host | хост адреса главной |
site.blog_id | номер сайта в сети |
site.lang | слаг текущего языка из Polylang, иначе локаль сайта |
site.year | текущий год |
site.logo_url | логотип в том же порядке, что у темы: свой логотип, картинка из настроек шапки, иконка сайта; нет ничего — токена нет |
frame
| Токен | Значение |
|---|---|
frame.id | id фрейма, который печатается на верхнем уровне; вложенные фреймы видят то же значение |
frame.url | его путь публикации, у оболочки пусто |
frame.type | легаси-тип из старой записи реестра, иначе пусто |
page
Есть только тогда, когда печатается страница. Любой токен page.* делает фрейм печатаемым на запросе, см. ниже.
| Токен | Значение |
|---|---|
page.id, page.url, page.title | страница |
page.slug, page.status, page.type | её слаг, статус и тип записи |
page.parent_id, page.parent_title, page.parent_url | родитель; у страницы верхнего уровня этих токенов нет |
page.date, page.date_modified | даты публикации и изменения в формате даты из настроек сайта |
page.date_iso, page.date_modified_iso | те же даты в ISO 8601 — для datetime у time |
page.excerpt | выдержка; если она пуста, ядро собирает её из текста |
page.description | выдержка как она записана, без неё пусто; для лида, чтобы он не повторял первый абзац |
page.author_id, page.author_name | id автора и отображаемое имя |
page.author_url | архив автора; только когда это безопасно, см. «Приватность» |
page.author_avatar | адрес аватара; только локального, см. «Приватность» |
page.thumbnail_id, page.thumbnail_url, page.thumbnail_alt | картинка записи; без неё токенов нет |
page.categories, page.tags | имена всех рубрик или меток через запятую |
page.category_name, page.category_slug, page.category_url | первая рубрика — для подписи над заголовком |
Полный список терминов ссылками печатает вставка op/terms, картинку как picture — вставка op/featured-image.
header и footer
Значения настроек шапки и подвала старой темы — tech-записи Header и Footer на языке страницы. Фрейм читает то же место, что и тема, поэтому шапка-фрейм и шапка темы не расходятся. Флаги — "1" или пусто.
| Токен | Значение |
|---|---|
header.button_title, header.button_url, header.button_target, header.button_icon | кнопка шапки |
header.show_button, header.show_logo | флаги |
footer.description, footer.copyright | текст подвала; 2026 в копирайте заменяется годом |
footer.cta, footer.cta_head, footer.cta_subhead | блок призыва: флаг, заголовок, подзаголовок |
footer.cta_button_title, footer.cta_button_url, footer.cta_button_target, footer.cta_button_icon | его кнопка |
footer.show_logo, footer.just_copyrights | флаги |
Приватность токенов автора
Два токена публикуют больше, чем кажется, поэтому умолчание у них строгое.
page.author_url раскрыл бы логин учётной записи. Архив автора WordPress адресует по user_nicename, а он у большинства аккаунтов совпадает с логином или выведен из него (john.smith превращается в john-smith, при коллизии добавляется -2). Форма с id /?author=5 не спасает: как только у автора есть опубликованная запись, ядро перенаправляет её на адрес с nicename. Поэтому токен отдаётся, только если nicename задан осознанно и логин не выдаёт.
page.author_avatar с Gravatar несёт хеш почты автора, а хеш проверяется по спискам известных адресов. Поэтому отдаётся только локальный аватар — на своём хосте сайта.
Невыданный токен попадает в unresolved, а отчёт печати объясняет причину в warnings, по разу на токен. Увидеть это можно в bake.response у POST /frames/{ref}/assign — эта ручка печатает фрейм для конкретной страницы. page.author_name отдаётся всегда, но если у аккаунта не задано отображаемое имя, WordPress держит там логин, и отчёт об этом скажет: поставьте автору «Отображать как» в профиле. Кому эти значения всё же нужны, ставит токены сам фильтром ниже — осознанно и на одном сайте.
Когда подставляются бинды
Бинды подставляются при печати фрейма. Фрейм страницы тема печатает на каждый запрос, поэтому его бинды всегда свежие: новый заголовок или новая дата видны сразу, без перепечки. Испечённая копия, которую использует фрейм, опубликованный по url, хранит значения момента выпечки.
Токен page.* в любом data-op-bind фрейма или в match его повторителя делает фрейм страницезависимым: renders_on_request: true, испечённой копии нет, причина — в отчёте заливки:
"renders_on_request": true,
"live_reasons": ["data-op-bind uses page.* tokens"]
Фрейм, который вставляет такой фрейм через op/frame, тоже печатается на запросе. У site.*, header.* и footer.* такого эффекта нет: настоящая шапка сайта 235 биндит только site.* и печётся.
На странице с фреймом проход биндов идёт по всему, что напечатал фрейм, включая текст страницы из op/content. На странице без фрейма data-op-bind в контенте не обрабатывается — такую разметку заворачивают во фрейм и вставляют через op/frame.
Неразрешённые токены
Токен без значения ничего не ломает и ничего не обнуляет. Атрибут сохраняет значение, записанное в исходнике, а элемент с text или html — своё исходное содержимое. Поэтому в исходник стоит писать разумный запасной вариант, как в шапке ниже: href="/" и текст Pomelli.
Отчёт печати собирает такие токены в unresolved: опечатка в имени токена, site.logo_url на сайте без логотипа, page.parent_title у страницы верхнего уровня, page.author_url, скрытый ради приватности. Там же предупреждения о битом JSON в атрибуте (битый JSON в data-op-bind: …, тег пропускается) и о паре, которая не состоит из двух строк (пара атрибут/токен должна быть строками).
Расширение словаря
Перед использованием словарь проходит через фильтр onpress/frames/bind_values. Фильтр получает массив значений и контекст печати (frame_id, frame_url, page_id) и может добавлять токены или заменять существующие.
add_filter('onpress/frames/bind_values', function (array $values, array $context): array {
$values['shop.phone'] = get_option('shop_phone', '');
$page_id = (int) ($context['page_id'] ?? 0);
if ($page_id > 0) {
$values['shop.price'] = (string) get_post_meta($page_id, 'price', true);
}
return $values;
}, 10, 2);
Пространства site., page., frame., header., footer., item., option. и префикс raw: принадлежат плагину: свои токены заводите в своём пространстве (выше это shop.), чтобы они не столкнулись с токенами, которые появятся позже. Замена токена плагина через фильтр — осознанное переопределение, именно так на отдельном сайте возвращают page.author_url. Свой токен, зависящий от страницы, сам по себе печать на запросе не включает — это делает только page.*, поэтому такие токены держат во фреймах, которые и так страницезависимы.
Пример: шапка и подвал сайта 235
Шапка site__header. Три бинда, все site.*, поэтому фрейм печётся; вставка op/lang-switcher добавляет языки:
<div class="site-header site-header--pomelli">
<div class="site-header__brand">
<a class="site-header__logo" href="/" data-op-bind='{"href":"site.home_url"}'>
<img class="site-header__logo-img" width="24" height="24" alt="" decoding="async" data-op-bind='{"src":"site.logo_url","alt":"site.name"}'>
<span class="site-header__logo-text" data-op-bind='{"text":"site.name"}'>Pomelli</span>
</a>
</div>
<div class="site-header__actions">
<div class="site-header__lang"><!-- wp:op/lang-switcher {"class":"oplang","show":"name","display":"dropdown"} /--></div>
</div>
</div>
Подвал site__footer в сокращении: бинды item.* внутри повторителей по наборам footer и footer_legal (с вариантами en и ru), html для текста с разметкой и site.login_url на иконке входа:
<footer class="site-footer site-footer--pomelli js-footer">
<div class="site-footer__brand">
<!-- wp:op/repeat {"source":"footer"} -->
<div class="site-footer__about">
<div class="site-footer__brand-name" data-op-bind='{"text":"item.brand"}'></div>
<div class="site-footer__disclaimer" data-op-bind='{"html":"item.about"}'></div>
</div>
<!-- /wp:op/repeat -->
</div>
<div class="site-footer__columns"><!-- wp:op/footer-columns /--></div>
<div class="site-footer__copyright"><!-- wp:op/copyright {"text":"© {year} AI Marketing"} /--></div>
<nav class="site-footer__legal" aria-label="Legal">
<!-- wp:op/repeat {"source":"footer_legal"} -->
<a class="site-footer__legal-link" data-op-bind='{"text":"item.text","href":"item.url"}'></a>
<!-- /wp:op/repeat -->
</nav>
<a class="site-footer__login" href="/wp-login.php" aria-label="Login" data-op-bind='{"href":"site.login_url"}'>…</a>
</footer>
А оболочка статьи биндит саму страницу — поэтому article печатается на запросе:
<h1 class="opdoc__title" data-op-bind='{"text":"page.title"}'></h1>
<div class="opdoc__body" data-op-bind='{"data-page-title":"page.title"}'><!-- wp:op/content /--></div>
Оболочка с этими фреймами по шагам: Оболочка со шапкой и подвалом.