Repository navigation
Releases: magicsunday/webtrees-module-base
Release list
3.4.0
Added
Model\NodeData, the person DTO that the pedigree and descendants chart modules keep as identical copies today. It serialises the ordered name asnameSegmentsin place of the given-name list, the surname list, the preferred name and the nickname.Model\NameSegmentand the enumModel\NameSegmentKind(given, surname, nickname, married), andNameProcessor::getNameSegments(), which returns the segments in display order.
Changed
- Nothing existing changes. The existing name accessors of the name processor stay for other consumers.
Migration
- A module that adopts
NodeDatareadsnameSegmentsinstead of the four name fields of its own DTO copy.
3.3.0
3.3.0 adds two shared tools for the translation catalogues of a module, so a module no longer needs a private script for either job. Existing code keeps working.
New
-
bin/resolve-fuzzyclears the fuzzy entries of amessages.powhose source text only changed in punctuation or spacing, such as a swap between a dash and a middle dot.msgmergemarks such an entry fuzzy andmsgfmtleaves it out of the compiled catalogue, so the English source shows instead of a translation that is still right. The same swap is applied to the translation. Every other fuzzy entry stays fuzzy and is listed for a person to judge, with its context in front when it has one. The gettext tools write the catalogue, so its layout stays the onemsgmergeproduces. -
bin/write-translationswrites the translations a module keeps in one JSON file into its catalogues, with the right number of plural forms for each locale. The form count comes from the plural rule webtrees uses, the same source asAbstractCatalogueStructureTestCase. Only an entry without a translation is written. Fuzzy entries, translated entries and the texts webtrees core translates itself (--core-owned=<file.pot>) are left alone. A list with the wrong number of forms, and a catalogue whose header declares another number of plural forms, are refused and named. A second run changes nothing.
Notes for consumers
- Both tools need
msgcat,msgattribandmsgmergeon the path. Composer installs them as binaries of this package. - A catalogue has to be what a merge with the template writes. One that is not is named and left as it is. To bring it into that layout, write it once with
msgmerge --previous --output-file=<new file> <catalogue> <template>and replace the catalogue with the new file. - Run as root in a container, a rewritten catalogue keeps its owner and group where the process may set them.
- The README and AGENTS.md describe both tools and their limits.
3.2.0
3.2.0 lets modules without plural strings use the shared catalogue structure test case. Existing code keeps working.
New
-
AbstractCatalogueStructureTestCase::shipsPluralEntries()is an overridable declaration with the defaulttrue. A module whose catalogues carry no plural entry overrides it and returnsfalse.The case demands at least one plural entry by default, so an empty scan cannot hide a reader that stops recognising plural keys. A module without any plural string failed two checks in every locale because of that, although nothing was wrong. With the declaration set to
falsethe same two checks demand the absence of plural entries instead. A plural entry added later fails until the override is removed.
Notes for consumers
- Subclasses that do not override anything behave exactly as before.
- Subclasses of modules that ship plural entries should leave the declaration alone, the default keeps the anchor against a silently broken reader.
3.1.0
3.1.0 adds a shared test case that locks the structure of the translation catalogues a webtrees module ships. Nothing else changes, existing code keeps working.
New
-
Testing\AbstractCatalogueStructureTestCaseis an abstract PHPUnit case. A module extends it in its owntests/and implementslanguageDirectory(), the directory with one subdirectory per locale that holdsmessages.poand its compiledmessages.mo.webtrees merges the compiled catalogue of a module over its own, so a plural entry with the wrong number of forms or an empty form damages the translation of the same text everywhere in the installation. The case guards against that:
- the form count of every compiled plural entry against the plural rule class of the locale, never against the PO header
- the slot count of the source catalogue, including entries the compiler drops
- empty forms
- the
%sand%dplaceholders, numbered ones included. Unnumbered placeholders keep their order, numbered ones may change position. - the compiled catalogue against its source
- a complete
Plural-Formsheader line - a render of every plural entry for a range of numbers
A subclass needs
#[CoversNothing]when itsphpunit.xmlsetsrequireCoverageMetadata.
Notes for consumers
- The class ships with the library but extends
PHPUnit\Framework\TestCase, so it is only loaded when a module extends it. PHPUnit is asuggest, not a requirement. - The case sits in its own deptrac layer that depends on no other
src/code. - Modules that carry their own copy of these checks can replace it with a subclass that names the catalogue directory.
3.0.1
3.0.1 completes the 3.0 line. 3.0.0 was published from an early cut that carried only the place-format subsystem (PlaceProcessor, IsoCountryMap, the PlaceFormat* / PlaceStyle models — see the 3.0.0 notes); this release adds the two remaining API changes and the hardening/quality work that landed afterwards. Coming from 2.x, adopt this version rather than 3.0.0.
Breaking changes (since 3.0.0)
-
The legacy marriage-date methods were removed.
DateProcessor::getMarriageDate()andgetMarriageDateOfParents()are gone; no consumer called them. Marriage dates are served by the compact API only (getFormattedMarriageDate(),getFormattedMarriageDateOfParents(),getMarriageDateFull()). -
Model\NameAbbreviationis now a backed enum. The hand-rolledAUTO/GIVEN/SURNAMEstring constants, theCHOICESarray and the staticresolve(string, string): stringare replaced by enum cases (Auto/Given/Surname, backing values unchanged) with an instanceresolve(string $surnameTradition): self. A consumer validates a persisted preference withtryFrom()and resolves with$case->resolve(...). Stored preference values keep resolving, since the backing strings are unchanged.
Hardening and quality
-
VersionInformation's release-version check was hardened. It now rejects a non-http(s) update URL before the request, pins and bounds redirects (at most three hops, http(s) only), caps the response body it will buffer, validates the wholetag_nameagainst an anchored version pattern so nothing but a version reaches the admin upgrade notice, and bounds the request with a total timeout. Every operational failure degrades to the bundled version rather than breaking the control panel. (The URL is a first-party constant, so a host-level SSRF cannot manifest; that guard is deferred until the URL is ever made configurable.) -
IsoCountryMap's resolution cache is bounded. Only alpha-3-shaped tokens are memoised, so a whole-tree scan of ordinary place names no longer grows the cache to every distinct segment. -
The test suite exercises the published API. Reflection was removed throughout — every processor is constructed for real over stubbed collaborators and driven through its public methods, so the shipped contract three chart modules consume is what the tests pin. Coverage metadata is required on every test, the
tests/tree mirrorssrc/under an enforced gate, and every test class isfinal. -
Architecture and mutation gates.
phpatenforces the layer boundaries (no baseline, no exclusions); Infection gates the mutation score with a floor, measured with timeouts counted honestly. -
Documentation. The date accessors' plain-text-sink contract is documented on the public methods, and the README/AGENTS component inventory is current with the source tree.
Upgrading
Consumers pin ^2.x and are not moved by this release; adopt it when convenient. Where a PlaceProcessor is constructed directly, resolve the desired formatting into a spec first (unchanged from 3.0.0):
$spec = PlaceFormatChoice::CityCountry->toSpec($treeLevels, $treeSuffix);
$processor = new PlaceProcessor($individual, $spec, new IsoCountryMap());Replace any use of the removed NameAbbreviation constants/CHOICES/static resolve() with the enum cases (NameAbbreviation::tryFrom($stored) + $case->resolve($surnameTradition)), and any call to the removed getMarriageDate*() methods with the compact getFormattedMarriageDate*() / getMarriageDateFull() API.
3.0.0
Breaking changes
-
PlaceProcessor::__construct()now takes(Individual $individual, PlaceFormatSpec $format, IsoCountryMap $countryMap). Place shortening is no longer configured through loose parameters; a consumer resolves its own settings into aPlaceFormatSpecvalue object once and hands that in, together with the country resolver. Consumers that construct aPlaceProcessordirectly must adapt; nothing else in the public API changed. -
New runtime requirements:
ext-intlandext-mbstring. The country resolver is built on ICU (Locale::getDisplayRegion) and folds its display names withmbstring. Both are declared incomposer.json, so an installation without them is rejected during resolution instead of failing at runtime.
New
-
Support\Locale\IsoCountryMap— resolves free-text country names from GEDCOMPLAClines to ISO-3166-1 alpha-2 codes (Germany/Deutschland→DE), converts alpha-2 to alpha-3 through ICU's supplementalcodeMappingstable (DE→DEU), and renders localised country labels. Nine widely used locales are pre-seeded alongside the active webtrees locale, with the reverse lookup memoised so repeat queries stay O(1). Common informal spellings and abbreviations (USA,UK,Great Britain, the UK home nations) are carried as explicit aliases. Shared here so the chart modules stop re-deriving it. -
A typed place-format model —
Model\PlaceStyle(the axis a name is shortened along),Model\PlaceFormatSpec(afinal readonlyvalue object carrying the resolved parameters, rejecting an invalid level count at construction) andModel\PlaceFormatChoice(a backed enum mapping a stored preference onto a spec, including an "automatic" source that reads the tree's own settings). -
Five place-shortening styles in
PlaceProcessor—Full(as recorded),Levels(keep N hierarchy levels from either end),CityCountry(keep the locality and the country, spelling a three-letter country code out in the interface language), plusCityIso2andCityIso3, which render the country as its ISO-3166-1 alpha-2 or alpha-3 code.
Notes on the place-code behaviour
-
Only three-letter country codes are expanded. A bare two-letter segment is kept verbatim, because those collide with US and German state abbreviations —
Dover, DEis Delaware andUlm, BWis Baden-Württemberg, not Germany and Botswana. -
For a place recorded as a single segment, the ISO styles read it as the country, except when its resolved code is one of a small curated set of countries whose name is also a well-known city (
Luxembourg,Monaco, …), where the recorded text is kept. A lone alpha-3 code still resolves, since a code is never a city name. -
Every resolution failure degrades to the recorded text. A place is never dropped or mangled.
Upgrading from 2.x
Consumers pin ^2.x and are not moved by this release; adopt it when convenient. Where a PlaceProcessor is constructed directly, resolve the desired formatting into a spec first:
// The tree's own settings are always passed; every choice except Automatic
// is already fully determined and ignores them.
$spec = PlaceFormatChoice::CityCountry->toSpec($treeLevels, $treeSuffix);
$processor = new PlaceProcessor($individual, $spec, new IsoCountryMap());PlaceFormatChoice::Automatic->toSpec($treeLevels, $treeSuffix) reproduces the previous tree-driven behaviour. IsoCountryMap takes an optional locale; left empty it follows the active webtrees locale.
2.6.1
Changes
CompactDateFormat::FALLBACKis now ISO 8601 (%Y-%m-%d) instead of the German-centric%d.%m.%Y. The fallback is only reached when ICU cannot resolve a locale at all (ext-intl absent, empty/unparseable tag, or an incomplete short-date pattern) — every real webtrees locale still gets its own ICU-derived compact format — so ISO is a safer, locale-neutral, unambiguous default there (and for theDateProcessordefault parameter).
No API changes; backward-compatible patch.
2.6.0
Highlights
- Locale-aware compact date format — new
Support\CompactDateFormatderives a compact date format from the active locale's ICU (CLDR) short-date pattern (field order, separators, native digits) and applies it through webtrees' own calendar formatter, so non-Gregorian calendars and B.C.E. years keep rendering correctly.DateProcessorgains an optionalcompactDateFormatparameter. - Place-suffix support —
PlaceProcessorgains an optionalplaceSuffixflag to keep the last (country-end) place parts instead of the first, mirroring theSHOW_PEDIGREE_PLACES_SUFFIXtree preference.
Both additions are backward-compatible: trailing optional parameters that default to the previous behaviour.
Deprecations
DateProcessor::getMarriageDateOfParents()(the legacydisplay()-based variant) is unused by the bundled chart modules and is now@deprecated; usegetFormattedMarriageDateOfParents()instead. Scheduled for removal in the next major release.
2.5.2
Removed
Facade\ModuleAwareDataFacadeTrait::isRtl(). The wrapper calledSupport\TextDirection::isRtl()without touching$this->module, so it carried no module-aware behaviour and lived on the trait only as a single-line forwarder. Consumers should callSupport\TextDirection::isRtl($text)directly at the call site.
2.5.1
Behind the scenes
- Split
RouteAwareDataFacadeTraitinto a lightweightFacade\ModuleAwareDataFacadeTrait(just$module+setModule()+isRtl()) plus a composedRouteAwareDataFacadeTrait(adds$route+setRoute()+chartUrl()). Chart facades that don't drive AJAX updates through the canonical chart route can opt into the smaller trait without inheriting the unused$routeproperty. Backward-compatible: existingRouteAwareDataFacadeTraitconsumers continue to receivesetModule()+isRtl()via trait composition.