Repository navigation
fix(seo): correct crashes and wrong output on the meta tag rendering path - #60
Merged
Merged
Conversation
`keywords_length: 0` is the shipped default and means the keywords field is disabled in the editor. It was still passed to Html::trimText(), which treats 0 as a real limit: it chopped the last character off and appended an ellipsis, so `default.keywords` and `override_default.*.keywords` were emitted as "keyword one, keyword tw…". Only truncate when a positive limit is set.
json_decode() returns null for malformed JSON and a scalar for payloads like `5` or `"text"`, and both were assigned straight into the array-typed $seoData property — a TypeError that took down every frontend page rendering that record. Only arrays are accepted now; anything else degrades to "no SEO data" and the regular fallback chain takes over.
preg_replace() with the /u modifier returns null on malformed UTF-8 rather than throwing, and cleanUp() returned it straight from a `string` method. One bad byte anywhere in a title or description — a legacy import, a mangled paste — was a TypeError that took the whole page down. Fall back to the uncollapsed string instead.
Bolt copies an extension's default config to config/extensions/ exactly once and never merges it again, so a site's config file can legitimately lack any key — an older copy, or one the user commented out. A missing `description_length` reached Html::trimText() as null, which is a TypeError under strict_types and a 500 on every page. Both length keys are now read via Collection::get() with the documented defaults and coerced to int.
ogtype(), robots(), image() and canonical() guarded their configured default with isset(), so an empty string counted as "configured" and short-circuited the rest of the chain. The commented override examples shipped in config/config.yaml use `canonical: ''` and `image: ''`, so following the documentation produced an empty <link rel="canonical">, og:url and twitter:url. Use !empty() to match how title() and description() already treat empty strings.
The keywords tag was nested inside the description `if`, so a page with keywords but no description emitted neither. They are independent tags. Adds tests/Twig/MetaTagsTemplateTest.php, which renders the template through a real Twig environment with stubs for the Bolt-provided `htmllang()` function and `config` global — the Seo tests mock Twig\\Environment, so nothing previously exercised the template itself.
configLength() fell back to hardcoded 158/0, duplicating config.yaml's documented defaults. If a site's config file lacks the key, just skip trimming instead of guessing at a value that belongs in one place.
Contributor
|
@Vondry Hi, thanks for PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes six bugs affecting the path from loading the
seofield through to generating the final meta tags. Three caused hard 500 errors on the public frontend, while the others silently produced incorrect or missing metadata.Each fix was developed test-first: a failing test demonstrating the bug, followed by the implementation and a dedicated commit. The test suite grows from 88 to 112 tests, with all tests passing and ECS and PHPStan clean throughout.
What's fixed
keywords_length: 0mangled configured keywords (a449c8e)0is the shipped default and means the keywords field is disabled in the editor. However, it was still passed toHtml::trimText(), which interprets0as a real length limit, truncating the final character and appending an ellipsis.As a result, values from
default.keywordsandoverride_default.*.keywordswere emitted as:Truncation is now applied only when the configured limit is greater than zero.
A non-JSON
seofield value caused a fatal error (9a1115a)json_decode()returnsnullfor malformed JSON and scalar values for payloads such as5or"text". Those values were assigned directly to the array-typed$seoDataproperty, triggering aTypeErrorand taking down every frontend page rendering the affected record.Although the field is normally maintained by the editor's JavaScript, fixtures, imports, and API writes can store arbitrary values. The code now accepts only decoded arrays; everything else is treated as "no SEO data", allowing the normal fallback chain to take over.
Malformed UTF-8 caused a fatal error (
f6fb427)preg_replace('/\s+/u', ...)returnsnullwhen given malformed UTF-8 instead of throwing an exception. Thatnullvalue was returned from a method declared to returnstring, causing a fatal error.A single invalid byte in a title or description—for example from a legacy import or malformed paste—could previously take down the entire page.
Missing length configuration keys caused fatal errors (
ba1076a)Bolt copies an extension's default configuration into
config/extensions/only once and never merges it again (ConfigTrait::initializeConfig). As a result, older installations or manually edited configuration files can legitimately be missing newer keys.A missing
description_lengthwas passed toHtml::trimText()asnull, producing aTypeErrorunderstrict_typesand a frontend 500.Both length settings are now read via
Collection::get()with documented defaults.This also highlights an important behaviour for future releases: newly added configuration keys will not automatically appear in existing installations.
Empty configuration defaults short-circuited the fallback chain (
1f72662)ogtype(),robots(),image(), andcanonical()checked configured defaults withisset(), so an empty string counted as a configured value.The commented examples shipped in
config/config.yamlinclude:Following those examples resulted in empty
<link rel="canonical">,og:url, andtwitter:urltags being emitted.The checks now use
!empty(), matching the existing behaviour oftitle()anddescription().Keywords were nested inside the description tag (
84d1067)In
_metatags.html.twig, the keywords<meta>tag was mistakenly nested inside the description{% if %}block. As a result, pages with keywords but no description emitted neither tag.Testing
vendor/bin/phpunit— 112 tests, 166 assertions, all passing.make cscheck— ECS and PHPStan clean.Two improvements were made to the test infrastructure:
tests/Twig/MetaTagsTemplateTest.php. The existing suite mockedTwig\Environment, so_metatags.html.twighad never actually been rendered during testing. The new tests render the template through a real Twig environment with minimal stubs for Bolt'shtmllang()function andconfigglobal. Five of the six tests passed immediately, serving as regression tests for behaviour that was already correct.mergeDefaultsflag toSeoTestCase::makeSeo(). The helper previously always re-mergeddefaultConfig(), making it impossible to test configurations with genuinely missing keys. The flag defaults totrue, so all existing tests remain unchanged.Behaviour change
Installations currently relying on:
to suppress output will now receive the normal fallback values (the current request URI and the record image, respectively). This is the intended fix, but it results in a visible change to the generated metadata.