Important when updating from 2.2.x
The update does not require re-saving settings and does not break the public API: all previously existing methods work with the same signatures. However, the markup output will change—all changes are aimed at improving validity:
The inLanguage field will appear for WebSite, WebPage, and articles—Previously, the language was not output at all
The last item—the current page—will be added to breadcrumbs—Previously, it was silently lost
The @id of a product and an article will receive an anchor #product / #article—Previously, the @id of a product matched the page URL
INN will move from vatID to taxID, OGRN to identifier—According to schema.org, taxID is indeed INN
publisher for WebSite will disappear if the organization name is not filled in—Previously, the link pointed to a non-existent node
og:locale will disappear for languages where the region is unknown—Previously, an invalid value such as sv_SV was output
A product without a numeric price will remain without an offers block — Previously, text was used instead of a number in the markup
What to do after updating:
Open any page with ?microdata_debug=Y as an administrator — a new panel will display the graph status and indicate what is missing.
Check the "Base URL" block in the panel. If the site is behind a reverse proxy and the markup uses the HTTP protocol, set base_scheme = https in the settings.
Open the module settings at least once: this triggers the settings migration and registers the button in the admin panel for sites updated by copying files.
Run one product page and one news page through the Google Rich Results Test.
If the site is behind a reverse proxy and the markup uses the HTTP protocol, enable the "Site behind a reverse proxy" option or set base_scheme = https.
If the logo was in SVG format, replace it with PNG or WebP.
2.7.0
Security audit issues from the Marketplace resolved.
Markup can no longer close the script tag. JSON-LD is encoded with flags JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT: the less-than and greater-than symbols become \u003C and \u003E. Previously, a value containing the script closing tag from an information block property, review, or 1C export would close the tag, causing the browser to execute the remainder of the line as JavaScript on a public page. For search engine robots, JSON remains valid, and URLs remain readable.
The insertion point is separately safeguarded: GraphRegistry::render() additionally replaces the script closing tag sequence with a backslash-escaped version.
FAQ answers are cleaned selectively. The new Normalizer::safeHtml() preserves markup allowed by Google for acceptedAnswer.text (headings, lists, links, emphasis) and removes script tags, iframe tags, form tags, svg tags, event attributes starting with on, and javascript: / vbscript: / data: schemes. The question is now always stripped of tags.
Review body and brand name are normalized: Review::setBody() and HasBrand::setBrand() run text through Normalizer::text(), as setName() and setHeadline() have long done.
The Host header is no longer accepted without a whitelist. Previously, with empty settings, it was used as-is, and a request with a foreign Host could poison the page cache with markup from a foreign domain. Now the host is accepted only if it matches a trusted source; SERVER_NAME from the web server configuration has been added to this list so that typical sites do not lose markup.
Proxy headers are accepted only via explicit configuration. X-Forwarded-Proto and X-Forwarded-Ssl are sent by the client, and without a proxy, anyone can forge them, forcing the HTTP protocol into canonical links. A new option "Site behind reverse proxy" has been added, disabled by default.
SVG is excluded from logo formats. Such a file executes an embedded script when opened via a direct link from the /upload/ directory. Google already requires a raster image for Organization.logo.
The debug panel warns if the base URL is not defined and markup is therefore not output.
2.6.2
Pre-release audit. Errors fixed, some of which existed since early versions.
Incorrect UTF-8 in infoblock data no longer causes text loss. Previously, string cleaning returned an empty value and logged a PHP deprecation: the product name and description disappeared entirely from the markup. Corrupted bytes are typical for 1C exports and imports.
The price is checked for numeric format. A non-numeric value no longer enters offers.price; such a product is displayed without the offers block. Formatted strings like 1 990,00 are parsed into numbers. Standard numeric prices remain unchanged.
A dangling link has been removed: WebSite.publisher pointed to the organization even when the organization itself was not added to the graph (name not filled).
The debug panel no longer writes to the event log. Previously, each opening of the panel duplicated validator warnings in b_event_log.
An author identifier collision has been fixed: an author named "123" received the same @id as a user with ID 123.
The author identifier no longer depends on the presence of the mbstring extension on the server.
An unrecognized author (e.g., ID of a non-existent user) no longer generates an empty Person node in the markup.
The parameter ?microdata_debug passed as an array no longer triggers a PHP warning.
2.6.1
Multilingual support.
The inLanguage field no longer appends the region automatically. Previously, a fallback option produced invalid tags for many languages: sv-SV instead of sv-SE, cs-CS instead of cs-CZ, da-DA instead of da-DK. Per BCP 47, a bare de is a valid tag, while a guessed region would be incorrect for an Austrian or Swiss site.
A new "Content Language" option (language_tag) allows explicit tag setting: ru-RU, en-GB, de-AT, zh-Hans-CN. It is stored separately for each site, as required for a multilingual portal.
Language tag normalization: pt_br → pt-BR, EN-gb → en-GB, support for scripts and macro-regions (zh-Hans-CN, es-419).
Bitrix language identifiers that are actually country codes are translated correctly: ua → uk-UA, by → be-BY, kz → kk-KZ, br → pt-BR, la → es-419.
The og:locale logic has been separated: the region is mandatory per the Open Graph specification, so it is inserted from a table of 50 languages. If the language is unknown, the tag is not output at all, which is better than an invalid value.
2.6.0
Entity linking, authors, classification, diagnostics.
Organization linking. The 'Profiles and directories (sameAs)' field has been supplemented with a new 'Company topics' (knowsAbout) field. This allows linking the site to a Wikidata entry and a Wikipedia article so that search engines understand exactly which company is being discussed. Step-by-step instructions are in the manual.
Author as a linked entity. The new Schema::author() method creates an author as a separate graph node with a permanent @id and registers it; articles reference it by ID rather than copying data on every page. It accepts a Bitrix user ID (which retrieves full name, photo, and position), an array of fields, or a name. Person::setWorksFor() and Person::setKnowsAbout() have been added.
Classification fields for articles: setSection(), setKeywords(), setWordCount(), setWordCountFromText(), setTimeRequired(), setArticleBody(). These are automatically filled from $arResult: word count from DETAIL_TEXT, section from the category, and keywords from TAGS.
The 'Warnings' tab in the module settings: the last 50 validator records from the event log with date, text, and page address.
New event OnBeforeMicrodataBuild — triggered before Open Graph generation, so a handler can replace the entity to reflect changes in meta tags as well.
Removing entities from the graph: GraphRegistry::remove() by @id or by object, and removeByType(). The documentation previously promised this capability, but the API did not provide it.
Automatic inLanguage for WebSite, WebPage, and articles — taken from the site language.
The 'Microdata' button in the admin panel on the public part: opens the current page with a debug panel.
2.5.2
Access to the debug panel has been restricted to administrators. Previously, rights to the module via GetGroupRight were also considered, and the default level for a module with unconfigured rights depends on the version and portal settings.
The page with the open panel is served with the Cache-Control: no-store header to prevent it from being cached and served to visitors.
2.5.1
The parsing of infoblock properties has been fixed. List properties were read from VALUE instead of VALUE_ENUM: if a brand is formatted as a list, the numeric identifier was included in the markup instead of the name. HTML properties were flattened into a string like 'Text, HTML' — the word HTML appeared directly in the markup.
Multiple properties and nested HTML values are now supported.
2.5.0
Debug panel.
The parameter ?microdata_debug=Y on a public page for an administrator displays: the composition of the graph with types and @id, a list of missing required fields, the parsing of the base URL definition (scheme, host, trusted hosts), values of main settings, the ready JSON-LD, and links to Google and Yandex validators.
The panel works even when the module is disabled, when the graph is empty, and in manual output mode — that is, precisely when diagnostics are most needed.
It does not load external styles or scripts, does not appear in REST responses, and silently disables itself on any internal error.
The validator has been updated to return findings as structured data, not just write to the log.
2.4.0
Eight new Schema.org types.
VideoObject — Video snippet, Video tab, video chapters
Recipe — Recipe card: time, calories, rating
JobPosting — Google Jobs vacancy block
Course — Course card
QAPage — Q&A block
SoftwareApplication — App rating and price
Service — Service pages
HowTo — Step-by-step instructions
General duration parsing: methods accept seconds, the string '01:30:00', or a ready ISO 8601 format.
Open Graph strategy for video: og:type = video.other, player tags, duration in seconds.
The validator checks required fields for all new types.
Constants VideoObject and Service were present in the type dictionary from early versions, but no classes existed for them.
2.3.0
Logic fixes and cleanup.
Breadcrumbs no longer lose the last item. Bitrix returns the current page with an empty link, which was silently discarded, causing the markup chain to always be shorter than the actual one.
Re-calling setMainEntity() no longer overwrites the page. Previously, a second entity (e.g., from another component) would erase the first one along with its data.
The OnAfterMicrodataBuild event now works in manual mode. Previously, in this mode, handlers triggered after the JSON output, rendering them useless.
Property mapping has been fixed: INN is mapped to taxID (which is the correct schema.org term for INN), and OGRN is mapped to identifier via PropertyValue. Setting keys are preserved.
The @id for products and articles now includes anchors #product and #article: previously, the product identifier matched the page address.
og:locale for languages other than Russian no longer outputs invalid bare language codes.
Exception masks are now checked against paths without the query string: the mask /search/ now excludes /search/?q=....
Short PHP opening tags short PHP opening tag have been removed from the settings form, so it no longer depends on the short_open_tag directive.
Validator texts have been moved to language files.
An unnecessary layer of file caching for the schedule has been removed (data is already cached by the core, and a daily TTL could return outdated schedules).
Tests without external dependencies have been added: php tests/run.php.
2.2.2
Critical fixes.
Lists in JSON no longer convert to objects. When clearing empty values in arrays, gaps in indices remained, causing json_encode to output {"0":...,"2":...} instead of [...]. Such values for sameAs, image, and itemListElement are rejected by Schema.org validators.
Correct protocol detection behind a reverse proxy. Behind nginx, Cloudflare, or a load balancer, the entire graph was previously sent as HTTP protocol even when canonical. An option "Protocol in absolute links" (base_scheme) has been added, along with support for X-Forwarded-Proto and X-Forwarded-Ssl headers.
Protection against Host header spoofing. The request host is included in the markup only if it matches a trusted source (org_url, the site's server_name, or SITE_SERVER_NAME). Without this check, a request with a foreign Host could poison the page cache with markup containing a foreign domain.
The settings migration during updates has been fixed. Bitrix does not call CModule::DoUpdate(), so transferring settings to per-site records was not performed. An install/updater.php template has been added, and the process is now idempotent when the settings form is opened.
Compatibility with PHP 7.4 has been restored. One construct required PHP 8.0: on PHP 7.4 the module installed but crashed on the first request.
The base URL is now calculated once per request instead of being recalculated on every access.