Skip to content

Releases: magicsunday/webtrees-module-base

3.4.0

Choose a tag to compare

@magicsunday magicsunday released this 21 Oct 18:50
3.4.0
9fefe93

Added

  • Model\NodeData, the person DTO that the pedigree and descendants chart modules keep as identical copies today. It serialises the ordered name as nameSegments in place of the given-name list, the surname list, the preferred name and the nickname.
  • Model\NameSegment and the enum Model\NameSegmentKind (given, surname, nickname, married), and NameProcessor::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 NodeData reads nameSegments instead of the four name fields of its own DTO copy.

3.3.0

Choose a tag to compare

@magicsunday magicsunday released this 12 Oct 14:56
3.3.0
bd058bd

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-fuzzy clears the fuzzy entries of a messages.po whose source text only changed in punctuation or spacing, such as a swap between a dash and a middle dot. msgmerge marks such an entry fuzzy and msgfmt leaves 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 one msgmerge produces.

  • bin/write-translations writes 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 as AbstractCatalogueStructureTestCase. 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, msgattrib and msgmerge on 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

Choose a tag to compare

@magicsunday magicsunday released this 06 Oct 11:58
3.2.0
eb3b2af

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 default true. A module whose catalogues carry no plural entry overrides it and returns false.

    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 false the 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

Choose a tag to compare

@magicsunday magicsunday released this 06 Oct 11:26
3.1.0
fa31d8e

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\AbstractCatalogueStructureTestCase is an abstract PHPUnit case. A module extends it in its own tests/ and implements languageDirectory(), the directory with one subdirectory per locale that holds messages.po and its compiled messages.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 %s and %d placeholders, numbered ones included. Unnumbered placeholders keep their order, numbered ones may change position.
    • the compiled catalogue against its source
    • a complete Plural-Forms header line
    • a render of every plural entry for a range of numbers

    A subclass needs #[CoversNothing] when its phpunit.xml sets requireCoverageMetadata.

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 a suggest, 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

Choose a tag to compare

@magicsunday magicsunday released this 23 Jul 17:29
4472e51

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() and getMarriageDateOfParents() are gone; no consumer called them. Marriage dates are served by the compact API only (getFormattedMarriageDate(), getFormattedMarriageDateOfParents(), getMarriageDateFull()).

  • Model\NameAbbreviation is now a backed enum. The hand-rolled AUTO / GIVEN / SURNAME string constants, the CHOICES array and the static resolve(string, string): string are replaced by enum cases (Auto / Given / Surname, backing values unchanged) with an instance resolve(string $surnameTradition): self. A consumer validates a persisted preference with tryFrom() 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 whole tag_name against 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 mirrors src/ under an enforced gate, and every test class is final.

  • Architecture and mutation gates. phpat enforces 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

Choose a tag to compare

@magicsunday magicsunday released this 23 Jul 17:28
bf90c48

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 a PlaceFormatSpec value object once and hands that in, together with the country resolver. Consumers that construct a PlaceProcessor directly must adapt; nothing else in the public API changed.

  • New runtime requirements: ext-intl and ext-mbstring. The country resolver is built on ICU (Locale::getDisplayRegion) and folds its display names with mbstring. Both are declared in composer.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 GEDCOM PLAC lines to ISO-3166-1 alpha-2 codes (Germany/Deutschland → DE), converts alpha-2 to alpha-3 through ICU's supplemental codeMappings table (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 (a final readonly value object carrying the resolved parameters, rejecting an invalid level count at construction) and Model\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), plus CityIso2 and CityIso3, 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, DE is Delaware and Ulm, BW is 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

Choose a tag to compare

@magicsunday magicsunday released this 06 Jul 12:22
929936f

Changes

  • CompactDateFormat::FALLBACK is 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 the DateProcessor default parameter).

No API changes; backward-compatible patch.

2.6.0

Choose a tag to compare

@magicsunday magicsunday released this 06 Jul 10:28
051c04f

Highlights

  • Locale-aware compact date format — new Support\CompactDateFormat derives 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. DateProcessor gains an optional compactDateFormat parameter.
  • Place-suffix support — PlaceProcessor gains an optional placeSuffix flag to keep the last (country-end) place parts instead of the first, mirroring the SHOW_PEDIGREE_PLACES_SUFFIX tree preference.

Both additions are backward-compatible: trailing optional parameters that default to the previous behaviour.

Deprecations

  • DateProcessor::getMarriageDateOfParents() (the legacy display()-based variant) is unused by the bundled chart modules and is now @deprecated; use getFormattedMarriageDateOfParents() instead. Scheduled for removal in the next major release.

2.5.2

Choose a tag to compare

@magicsunday magicsunday released this 27 May 11:25

Removed

  • Facade\ModuleAwareDataFacadeTrait::isRtl(). The wrapper called Support\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 call Support\TextDirection::isRtl($text) directly at the call site.

2.5.1

Choose a tag to compare

@magicsunday magicsunday released this 27 May 10:20

Behind the scenes

  • Split RouteAwareDataFacadeTrait into a lightweight Facade\ModuleAwareDataFacadeTrait (just $module + setModule() + isRtl()) plus a composed RouteAwareDataFacadeTrait (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 $route property. Backward-compatible: existing RouteAwareDataFacadeTrait consumers continue to receive setModule() + isRtl() via trait composition.