This is the multi-page printable view of this section. Click here to print.
Developer
1 - CLI Scripts
The module ships CLI-only scripts. They are guarded: requested over HTTP (the modules_v4 directory is inside the web root) they answer 403 instead of running.
| Script | Purpose | Needs a webtrees install + DB? |
|---|---|---|
cli/build-link-index.php [--limit=N] [--tree=<id>] [--rebuild] [--flush] | build/update the link index for the XREF overview | yes |
cli/build-uid-index.php [--limit=N] [--tree=<id>] [--rebuild] [--flush] [--force] | build/update the UID index for UID lookup | yes |
cli/refresh-wthb-toc.php [--all] [--lang=<list>] [--clear] [--force] | refresh the WTHB table of contents cache (production) | yes |
test-text-tag-collector.php | standalone unit tests for the text-tag collector | no |
test-link-classifier.php | standalone unit tests for the link classifier (incl. target extraction) | no |
test-uid-tag-collector.php | standalone unit tests for the UID tag collector | no |
test-uid-index-service.php | standalone unit tests for the UID index service | no |
tests/smoke-text-tag-collector.php [limit] | scan records containing enhanced links, print every captured value | yes |
smoke-migration5.php | SQLite smoke test for Migration5 (UID index schema) | yes |
tests/p1-measure.php [--tree=<id>] | read-only scaling measurement for the XREF overview | yes |
Run them from the webtrees root with the same PHP version the instance runs on (webtrees requires PHP 8.3+):
php modules_v4/linkenhancer/tests/test-text-tag-collector.php
php modules_v4/linkenhancer/tests/smoke-text-tag-collector.php 10
Cron example (every 2 hours, 500 records per run):
15 */2 * * * cd /path/to/webtrees && php modules_v4/linkenhancer/cli/<name>.php --limit=500 >> /var/log/linkenhancer-<name>.log 2>&1
2 - Contributing
If you’d like to contribute to this module, great! You can contribute by:
- Contributing code — check out the issues for things that need attention. If you have changes you want to make not listed in an issue, please create one, then you can link your pull request.
- Testing — it’s all manual currently, please create an issue for any bugs you find.
Translation
You can contribute to this project on Codeberg Weblate. Updated translations will be included in the next release of this module.
Beside English the following languages are available:
- Catalan (by Bernat Josep Banyuls i Sala)
- Dutch (by TheDutchJewel)
- Español (by Bernat Josep Banyuls i Sala)
- German
Repository
Primary Repository: https://codeberg.org/bschwede/linkenhancer
All activity happens on Codeberg:
- Issues → Open on Codeberg
- Pull Requests → Open on Codeberg
- Translations → Contribute on Codeberg Weblate
Mirror Repository: https://github.com/bschwede/linkenhancer
3 - Project Structure
Directory layout
linkenhancer/
├── module.php ← webtrees entry point
├── autoload.php ← PSR-4 autoloader
├── cron-jobs.php ← cronjob self-registration manifest
├── latest-version.txt ← current version (synced to LinkEnhancerModule.php)
├── composer.json ← PHP deps (wt-shared-libs, phpstan)
├── package.json ← JS deps (gulp, rollup, mocha)
├── gulpfile.mjs ← build tasks
├── phpstan.neon ← static analysis config
├── phpstan-bootstrap.php ← PSR-4 bootstrap for PHPStan
├── phpstan-baseline.neon ← known issues
├── src/ ← PHP (PSR-4)
│ ├── LinkEnhancerModule.php
│ ├── LinkEnhancerModuleExt.php
│ ├── LinkEnhancerUtils.php
│ ├── SettingInterface.php
│ ├── CommonMark/ ← CommonMark extensions
│ ├── DataFix/ ← data-fix handlers + dispatcher
│ ├── Factories/ ← CustomMarkdownFactory
│ ├── Http/ ← request handlers
│ ├── Schema/ ← Migrations + SeedHelpTable
│ └── Services/ ← business logic
├── resources/
│ ├── js/ ← JS source (src/) + build output (bundle-*)
│ ├── css/ ← CSS source + build output
│ ├── lang/ ← .pot / .po / .php (translations)
│ ├── views/ ← PHTML views
│ └── img/ ← screenshots
├── cli/ ← CLI maintenance scripts
├── tests/ ← standalone + smoke tests
└── util/ ← build/maintenance shell scripts
Dependencies
- PHP (
composer.json):bschwede/wt-shared-libs(path repo../wt-shared-libs) andphpstan(dev).- Conflicts with:
webtrees <2.2andelysch/webtrees-mitalteli-misc-features
- JS (
package.json):- runtime dependency
tiny-markdown-editor - dev tooling — Gulp, Rollup, PostCSS (autoprefixer, cssnano), Mocha + chai + sinon, jsdom.
- runtime dependency
Database schema
The module owns five tables (prefix le_), created incrementally by src/Schema/Migration#.php. Every migration is additive and idempotent, so the schema converges on each module load.
le_route_help_map
Maps a webtrees route (path / handler / method) to a help entry — the backing store for context-sensitive manual help. Seeded by SeedHelpTable.
| Column | Type | Notes |
|---|---|---|
id | int, PK (auto) | |
path | varchar(255) | route path |
handler | varchar(150) | fully-qualified handler class |
handler_key | varchar(150), index | version-neutral key (2.2.6 + 2.3 compatible) |
method | varchar(20) | |
extras | varchar(60) | |
subcontext | varchar(250) | |
category | varchar(60) | standard / custom module / generic … |
order | int, default 10 | |
url | varchar(250) | |
updated_at | timestamp, nullable |
Index on (path, handler) and on handler_key.
le_record_scan
One row per scanned GEDCOM record, with an MD5 fingerprint of the record text — lets the CLI index diff incrementally.
| Column | Type | Notes |
|---|---|---|
file | int | tree id (part of PK) |
xref | varchar(20) | (part of PK) |
rectype | varchar(15) | GEDCOM tag, e.g. INDI, OBJE (part of PK) |
hash | varchar(32) | MD5 of the record text |
scanned_at | timestamp, nullable |
Primary key (file, xref, rectype).
le_link_index
One row per (link, target) found in a record — the foundation for the planned Backlink feature.
| Column | Type | Notes |
|---|---|---|
id | int, PK (auto) | |
file | int | tree id |
xref | varchar(20) | |
rectype | varchar(15) | |
tag_path | varchar(255) | where the link sits in the record |
link_class | varchar(10) | xref / ext / pic / classic / other |
token | text | the raw link token |
snippet | text, nullable | display context captured at build time |
target_xref | varchar(20), nullable | XREF link target |
target_tree | varchar(255), nullable | |
target_uid | varchar(255), nullable, index | UID link target (filled by a length heuristic) |
Indexes on (file, xref, rectype), target_xref and target_uid.
le_uid_index
One row per UID tag found in a record (record-level and fact-level). No unique constraint — UIDs are not guaranteed to be unique.
| Column | Type | Notes |
|---|---|---|
id | int, PK (auto) | |
file | int | tree id |
xref | varchar(20) | |
uid | varchar(255), index | verbatim UID value (no normalization) |
rectype | varchar(15) | |
tag_path | varchar(255) | |
hash | varchar(32) | MD5 fingerprint (incremental change detection) |
Indexes on uid and (file, xref, rectype).
le_index_meta
A single row (id = 1) holding the freshness state of both the link index and the UID index.
| Column | Type | Notes |
|---|---|---|
id | int, PK, default 1 | single row |
last_run | timestamp, nullable | last complete link-index run |
rows | int, default 0 | link-index row count |
uid_last_run | timestamp, nullable | last complete UID-index run |
uid_rows | int, default 0 | UID-index row count |
4 - Class Hierarchy
Auto-generated by docs/tools/generate-class-hierarchy.php — do not edit manually.
Module
LinkEnhancerModuleextendsAbstractModuleimplementsMiddlewareInterface,ModuleCustomInterface,ModuleGlobalInterface,ModuleConfigInterface,ModuleDataFixInterface,ModuleListInterface,ModuleTabInterface,SettingInterface—LinkEnhancerModule.phpSettingInterface—SettingInterface.phpLinkEnhancerUtils—LinkEnhancerUtils.phpLinkEnhancerModuleExtextendsLinkEnhancerModuleimplementsModuleExtGlobalInterface—LinkEnhancerModuleExt.php
CommonMark (Extensions)
LeImageRenderer(final) implementsNodeRendererInterface,XmlNodeRendererInterface,ConfigurationAwareInterface—CommonMark/LeImageRenderer.phpSmartPunctHelper—CommonMark/SmartPunctHelper.phpLeTableOfContentsRenderer(final) implementsNodeRendererInterface,XmlNodeRendererInterface—CommonMark/LeTableOfContentsRenderer.php
Factories
CustomMarkdownFactoryextendsMarkdownFactory—Factories/CustomMarkdownFactory.php
Request Handlers
DataFixBlocksActionimplementsRequestHandlerInterface—Http/RequestHandlers/DataFixBlocksAction.phpHelpWthbActionimplementsRequestHandlerInterface—Http/RequestHandlers/HelpWthbAction.phpXrefDetailData(final) implementsRequestHandlerInterface—Http/RequestHandlers/XrefDetailData.phpXrefOverviewListData(final) implementsRequestHandlerInterface—Http/RequestHandlers/XrefOverviewListData.phpGotoIdTreeActionextendsGotoIdAction—Http/RequestHandlers/GotoIdTreeAction.phpGotoUidTreeActionextendsGotoUidAction—Http/RequestHandlers/GotoUidTreeAction.phpGotoXrefActionimplementsRequestHandlerInterface—Http/RequestHandlers/GotoXrefAction.phpDataFixRebuildIndexActionimplementsRequestHandlerInterface—Http/RequestHandlers/DataFixRebuildIndexAction.phpRenumberActionHandler(final) —Http/RequestHandlers/RenumberActionHandler.phpHelpMdActionimplementsRequestHandlerInterface—Http/RequestHandlers/HelpMdAction.phpAdminXrefOverviewData(final) implementsRequestHandlerInterface—Http/RequestHandlers/AdminXrefOverviewData.phpGotoIdActionextendsAbstractGotoAction—Http/RequestHandlers/GotoIdAction.phpHelpWtCoreActionimplementsRequestHandlerInterface—Http/RequestHandlers/HelpWtCoreAction.phpWthbAdminHandler(final) —Http/RequestHandlers/WthbAdminHandler.phpGotoUidActionextendsAbstractGotoAction—Http/RequestHandlers/GotoUidAction.phpAbstractGotoAction(abstract) implementsRequestHandlerInterface—Http/RequestHandlers/AbstractGotoAction.php
Schema
SeedHelpTableimplementsSeedInterface—Schema/SeedHelpTable.phpMigration6implementsMigrationInterface—Schema/Migration6.phpMigration2implementsMigrationInterface—Schema/Migration2.phpMigration7implementsMigrationInterface—Schema/Migration7.phpMigration3implementsMigrationInterface—Schema/Migration3.phpMigration4implementsMigrationInterface—Schema/Migration4.phpMigration1implementsMigrationInterface—Schema/Migration1.phpMigration5implementsMigrationInterface—Schema/Migration5.phpMigration0implementsMigrationInterface—Schema/Migration0.php
Services
AdminSettingsBuilder(final) —Services/AdminSettingsBuilder.phpMarkdownEditorActivationService(final) —Services/MarkdownEditorActivationService.phpIndexRebuildScheduler(final) —Services/IndexRebuildScheduler.phpUidTagCollector(final) —Services/UidTagCollector.phpTextTagCollector(final) —Services/TextTagCollector.phpContentBuilder(final) —Services/ContentBuilder.phpRenumberWithLinksService(final) —Services/RenumberWithLinksService.phpDataFiles(final) —Services/DataFiles.phpXrefOverviewColumns(final) —Services/XrefOverviewColumns.phpLinkIndexService(final) —Services/LinkIndexService.phpXrefsService(final) —Services/XrefsService.phpXrefDetailService(final) —Services/XrefDetailService.phpWthbService—Services/WthbService.phpIdResolver(final) —Services/IdResolver.phpLinkRenderer(final) —Services/LinkRenderer.phpWthbTocService(final) —Services/WthbTocService.phpUidIndexService(final) —Services/UidIndexService.php
DataFix
FixHandlerInterface—DataFix/FixHandlerInterface.phpDataFixDispatcher—DataFix/DataFixDispatcher.phpTreeLookupTrait—DataFix/TreeLookupTrait.phpDanglingLinksFix(final) implementsFixHandlerInterface—DataFix/Handlers/DanglingLinksFix.phpDuplicateUidFix(final) implementsFixHandlerInterface—DataFix/Handlers/DuplicateUidFix.phpWtTypeFix(final) implementsFixHandlerInterface—DataFix/Handlers/WtTypeFix.phpXrefUidSwapFix(final) implementsFixHandlerInterface—DataFix/Handlers/XrefUidSwapFix.phpAmbiguousUidFix(final) implementsFixHandlerInterface—DataFix/Handlers/AmbiguousUidFix.php
5 - Development Environment
What you need to build and test the module locally.
Requirements
| Component | Version | Notes |
|---|---|---|
| PHP | 8.3+ | same version the webtrees instance runs on |
| Composer | 2.x | installs wt-shared-libs + phpstan |
| Node.js | 18+ (LTS) | for Gulp / Rollup / Mocha |
| webtrees | 2.2.x or 2.3.x | needed for PHPStan (app/ classes) and the smoke tests |
| wt-shared-libs | newest version | sibling repo at modules_v4/wt-shared-libs (composer path repo) |
Location
The module lives inside a webtrees installation at modules_v4/linkenhancer/. Its composer.json
pulls in the sibling shared library as a local path repository:
"repositories": [
{ "type": "path", "url": "../wt-shared-libs", "options": { "symlink": false } }
]
Setup
# PHP dependencies (installs wt-shared-libs + phpstan)
composer install
# JS dependencies (gulp, rollup, mocha, …)
npm ci
Note:
composer.jsonsetsprepend-autoloader: false— the module’s autoloader is attached to (not replacing) webtrees’ autoloader.
Bind mount
In the current setup, development is being done on Linux with the Apache web server installed natively.The source tree is exposed to the PHP runtime through a bind mount of the rpo folders into modules_v4. Run the CLI tools and PHPStan from the mount path (the path the webtrees instance sees), not the source path, so app/ and vendor/autoload.php resolve:
cd /path/to/webtrees/modules_v4/linkenhancer
composer analyse
See the CLI Scripts and PHPStan pages for the exact commands.
6 - Build System
Bundling, translation compilation and versioning are driven by Gulp tasks in gulpfile.mjs
, invoked with npx gulp <task>. The npm run * scripts cover testing only — see the Testing page.
Tasks
| Task | Command | What it does |
|---|---|---|
build | npx gulp build | clean → JS (Rollup) + CSS (PostCSS) |
devbuild | npx gulp devbuild | like build, but Dev-Mode (no minify, source maps) |
updatepo | npx gulp updatepo | runs update-po-files.sh
→ xgettext over src/ → messages.pot |
po2php | npx gulp po2php | compiles resources/lang/*.po → resources/lang/*.php |
syncversion | npx gulp syncversion | keeps latest-version.txt
↔ LinkEnhancerModule.php
in sync |
bumpversion | npx gulp bumpversion | requires a clean git tree → version bump → syncversion |
archive | npx gulp archive | build + po2php + create-archive.sh
(release ZIP) |
forgejo:issue-refs | npx gulp forgejo:issue-refs[:dry] | adds issue refs to commit messages (needs FORGEJO_TOKEN in .env) - was used only once to migrate the repo from GitHub to Codeberg |
JS / CSS bundling
build = clean → (in parallel) JS + CSS.
- clean deletes
resources/js/bundle*andresources/css/bundle*first. - JS (Rollup): two entry bundles —
jsIndex(the mainindex-le.js) andjsExtractLeConfig(le-config.js). Output toresources/js/bundle*. - CSS (PostCSS): per-file subtasks from
getCssSubTasks(), with autoprefixer + cssnano. Output toresources/css/bundle*.
Dev-Mode (devbuild) toggles a flag via toggleDevMode(true) — it disables minification and emits source maps.
Build & archive pipeline
flowchart LR
subgraph B["🔧 npx gulp build"]
C["🧹 clean<br/>(bundle*)"]
J["`📜 Rollup<br/>**index-le.js** · le-config.js`"]
P["🎨 PostCSS<br/>autoprefixer · cssnano"]
C --> J
C --> P
end
subgraph A["📦 npx gulp archive"]
A1["build"]
A2["po2php"]
A3["`create-archive.sh<br/>**dist/linkenhancer_v<ver>.zip**`"]
A1 --> A2 --> A3
end
classDef bStyle fill:#e3f2fd,stroke:#1565C0,stroke-width:2px
classDef aStyle fill:#e8f5e9,stroke:#2E7D32,stroke-width:2px
class C,J,P bStyle
class A1,A2,A3 aStyle
Translation files
updatepoonly regenerates the template (messages.pot). The.pofiles themselves are updated via Weblate, not locally, to avoid conflicts — see the Translation Pipeline page.po2phpconverts the committed.pofiles into the PHP arrays webtrees loads at runtime.
7 - Translation Pipeline
The module ships its own translations through the standard gettext chain, hosted on Codeberg Weblate.
Flow
flowchart TB
subgraph Extraction["1️⃣ Code → POT"]
direction LR
PHP["📝 src/*.php"] -->|npx gulp updatepo| POT["📄 messages.pot"]
end
subgraph Translation["2️⃣ Translation"]
direction LR
WEBLATE["🌐 Weblate"] -->|download| PO["🗝 <lang>.po"]
end
subgraph Rendering["3️⃣ Webtrees"]
direction LR
PHPARR["📦 lang.php"] -->|I18N| RENDER["✅ Render"]
end
POT -->|upload| Translation
PO -->|npx gulp po2php| Rendering
classDef sourceStyle fill:#e3f2fd,stroke:#1565C0,stroke-width:2px
classDef fileStyle fill:#e8f5e9,stroke:#2E7D32,stroke-width:2px
classDef weblateStyle fill:#f3e5f5,stroke:#7B1FA2,stroke-width:2px
classDef renderStyle fill:#fce4ec,stroke:#C2185B,stroke-width:2px
class PHP sourceStyle
class POT,PO,PHPARR fileStyle
class WEBLATE weblateStyle
class RENDER renderStyle
Extracting strings
update-po-files.sh
runs xgettext over every *.php / *.phtml except util/, vendor/, node_modules/ and tests/:
xgettext -L PHP \
--keyword=translate --keyword=plural:1,2 --keyword=translateContext:1c,2 \
--add-comments=I18N --from-code=utf-8 \
--output=resources/lang/messages.pot …
Two marker helpers control what gets extracted:
I18N::translate()/MoreI18N::translate()— module-specific strings.translateis an xgettext keyword, so these are picked up.MoreI18N::xlate()— strings already covered by the webtrees core POT. Same runtime behaviour, different name, therefore invisible to xgettext. After a core update, re-comparemessages.potwith the core POT and switch any newly-covered msgid toxlate().
Add /* I18N: … */ comments to give translators context (picked up via --add-comments=I18N).
PO → PHP
.po files are not merged locally — that is Weblate’s job, to prevent conflicts. Once Weblate delivers updated .po files, compile them:
npx gulp po2php
Note: a former
.po→.mostep is obsolete (translation format switch, issue #88).
8 - WTHB TOC Language Pipeline
Overview
The linkenhancer-wthb-toc-lang package delivers the table of contents (TOC) of the German webtrees manual — and its translations — to the WTHB help feature. Its production is a round trip that lives entirely outside the webtrees install: it reads the manual from GenWiki, regenerates the TOC, writes the result back to GenWiki as a MediaWiki template, and — in parallel — ships the structured data to an external Codeberg repo that feeds Codeberg Pages, Weblate (translations) and, finally, the module. This page documents the production side; the consumer side (cache, CLI, rendering) is covered on the WTHB feature page and the CLI scripts page.
Ressources
- GenWiki German Webtrees Manual: https://wiki.genealogy.net/Webtrees_Handbuch
- Code Repositories:
- Module: https://codeberg.org/bschwede/linkenhancer
- TOC-Translation: https://codeberg.org/bschwede/linkenhancer-wthb-toc-lang
- Weblate translation component: https://translate.codeberg.org/projects/wt-modules/linkenhancer-wthb-toc
- GenWiki Bot project: https://gitlab.genealogy.net/genwiki/wikibots (gw-skripte / pywikibot documentation)
Pipeline
The round trip has two halves: the GenWiki round trip (read the manual → generate → write the TOC template back) and the delivery pipeline (structured data → external repo → Pages / Weblate → module).
The GenWiki round trip:
flowchart TB
subgraph HOST["🖥️ Host · gw-skripte/"]
ORCH["`🚦 **wt-maintain.sh**<br/>*Orchestration*`"]
PREAD["`🤖 **pywikibot** · *wt-maintain.py*<br/>SectionsExportBot — *READ*`"]
GEN["`⚙️ **wt-gen-toc.py** *(pandas)*`"]
PREP["`📄 *pagefromfile file*<br/>vorlage_wt-verzeichnis-toc.txt`"]
PWRITE["`🤖 **pywikibot** · *pagefromfile*<br/>*WRITE* — Bot-Summary`"]
end
GW["`📖 **GenWiki** · wiki.genealogy.net<br/>*webtrees handbook pages*`"]
CSV["`📄 *3 CSV*<br/>pageinfo · sections · images`"]
OUT["`📦 *Outputs*<br/>wthb-toc.wiki · .html · .json · de.json`"]
TPL["`📖 **Vorlage:WT-Verzeichnis-TOC**<br/>*noinclude + includeonly TOC*`"]
API["`🔍 *API action=parse* → HTML<br/>(curl + jq)`"]
GW -->|"allpages prefix 'Webtrees Handbuch'<br/>API parse/sections + imagelinks"| PREAD
ORCH -.->|"drives"| PREAD
PREAD --> CSV
CSV -->|"+ wthb-pageinfo_ext.csv (target)"| GEN
GEN --> OUT
OUT --> PREP
PREP --> PWRITE
PWRITE -->|"pagefromfile -force"| TPL
TPL -->|"curl API parse"| API
classDef srcStyle fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
classDef coreStyle fill:#ede7f6,stroke:#5e35b1,stroke-width:2px
classDef okStyle fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
classDef pwbStyle fill:#fff8e1,stroke:#f57f17,stroke-width:2px
classDef targetStyle fill:#fce4ec,stroke:#ad1457,stroke-width:2px
class GW srcStyle
class ORCH,GEN coreStyle
class CSV,OUT,API okStyle
class PREAD,PWRITE pwbStyle
class TPL targetStyle
Orchestration — wt-maintain.sh
Everything runs from one host script (in gw-skripte/):
wt-maintain.sh <wthb-pageinfo_ext.csv> <DATADIR>
It depends on setenv.sh exporting PWB_PY, PWB_CONF and SCRIPTDIR, and drives these steps in order:
- query the GenWiki data (pywikibot READ)
- generate the TOC
- build the
pagefromfilewrapper file - write the template back to GenWiki (pywikibot WRITE)
- parse-check the rendered template (curl + jq)
- deploy the JSON to the external repo
- downstream: repo CI, Weblate and the module pick the data up
GenWiki data query (pywikibot READ)
Step 1 is wt-maintain.py, a pywikibot bot (SectionsExportBot, extending ExistingPageBot). It calls site.login(), iterates allpages(prefix="Webtrees Handbuch") and, per page, queries the MediaWiki API (action=parse&prop=sections) plus imagelinks() (FilePage info).
The result is three CSV files:
wthb-pageinfo.csv,wthb-sections.csvandwthb-images.csv.
Two manual follow-ups remain (per the script header): (b) maintaining the image directory and (c) adding the images to the Kategorie:Webtrees category — done by hand (helper: wt-webtrees-cat-files.sh).
TOC generation — wt-gen-toc.py
Step 2 is a pandas script that combines the IST state (the three CSVs) with the SOLL target structure in wthb-pageinfo_ext.csv. classify_prefix() assigns chapter/section prefixes. It writes four outputs:
wthb-toc.wiki— the MediaWiki TOC sourcewthb-toc.json— the structured data the module consumesde.json— the German language file for Weblatewthb-toc.html— a rendered HTML copy (legacy/parallel — the current consumer renders from JSON)
GenWiki template (pywikibot WRITE)
Steps 3–4 wrap the generated TOC in the MediaWiki template Vorlage:WT-Verzeichnis-TOC and write it back:
- the
pagefromfilefile combines a<noinclude>part ({{Dokumentation}}+[[Kategorie:Webtrees]]) with an<includeonly>part (Stand: <datum>+<div class="wthbtoc">…</div>). pywikibot pagefromfile -force -pt:1 -summary:"Bot: Webtrees Handbuch Inhaltsverzeichnis aktualisiert"performs the write.
Step 5 verifies the result by parsing it back through the GenWiki API (action=parse&format=json) and extracting the HTML with jq -r '.parse.text["*"]'.
Note: this is automatic writing to GenWiki via pywikibot — the opposite of the “produce review output, no bot” approach used for the GenWiki help-docs export.
Vorlage:WT-Verzeichnis-TOCandKategorie:Webtreesare GenWiki-specific German names and are kept verbatim.
External repo, CI & Pages
The second half of the pipeline (step 6 onward) ships the structured data out of the host:
flowchart TB
OUT["`📦 *Outputs*<br/>wthb-toc.json + de.json`"]
DEP["`🚀 **wt-deploy-toc-lang.sh**`"]
subgraph EXTERNAL["🌐 External infrastructure"]
REPO["`📦 **linkenhancer-wthb-toc-lang**<br/>(Codeberg)`"]
META["`🔧 *tools/gen_meta.py*<br/>languages.json + version.json`"]
CI["`⚙️ *CI regen-meta.yml*<br/>gen_toc_html.py`"]
PG["`🌐 **Codeberg Pages**`"]
WLB["`🌐 **Weblate** · linkenhancer/wthb-toc`"]
TNG["🗝 en.json · fr.json · …"]
end
subgraph MODULE["📦 Module (linkenhancer)"]
SVC["`**WthbTocService**`"]
DS["`💾 *data/…/wthb-toc-lang/*<br/>2-Layer-Cache`"]
RDR["`📄 **help-wthb-toc.phtml**<br/>TOC-Modal`"]
end
OUT -->|"cp wthb-toc.json + de.json"| DEP
DEP -->|"git add / commit / push"| REPO
REPO -->|"regen meta"| META
REPO --> CI --> PG
REPO -->|"de.json (Source)"| WLB
WLB --> TNG
TNG -->|"[lang].json"| REPO
PG -->|"fetch JSON"| MODULE
SVC --> DS --> RDR
classDef coreStyle fill:#ede7f6,stroke:#5e35b1,stroke-width:2px
classDef okStyle fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
classDef queueStyle fill:#fff3e0,stroke:#e65100,stroke-width:2px
class OUT okStyle
class DEP,REPO,META,CI,SVC coreStyle
class PG,TNG,DS,RDR okStyle
class WLB queueStyle
wt-deploy-toc-lang.shcopieswthb-toc.jsonandde.jsoninto thelinkenhancer-wthb-toc-langrepo, runstools/gen_meta.py(regenerateslanguages.json+version.json) and doesgit add/commit/push. It is standalone-callable and soft-skips (warning +exit 0) when the repo or JSON is missing, so it never aborts theset -eorchestrator.- Repo CI (
regen-meta.yml) runsgen_toc_html.pyand publishes to Codeberg Pages.
Weblate
The repo’s de.json is the source of the Weblate component wt-modules/linkenhancer-wthb-toc; the other [lang].json files are its translation targets. Translations flow back into the repo and are served via Codeberg Pages.
Consumer (module)
Briefly — see the WTHB feature page and the CLI scripts page for details:
WthbTocService.phppulls the language JSON from Codeberg Pages and serves it through a two-layer cache.refresh-wthb-toc.php(andcli/validate-wthb-toc.php) refresh and verify that cache, typically on a cron schedule.help-wthb-toc.phtmlrenders the TOC in the help dialog.
Configuration & prerequisites
setenv.sh— exportsPWB_PY,PWB_CONF,SCRIPTDIR.- pywikibot config —
user-config-test.py/user-config-prod.py. wthb-toc-lang.env—TOC_LANG_REPO=$HOME/dev/linkenhancer-wthb-toc-lang,TOC_LANG_DATADIR=$HOME/dev/wthb-toc-datadir.- Prerequisites: pywikibot, pandas, jq, python3.
Ops & caveats
- Manual image follow-up — the image directory and
Kategorie:Webtreesmust be maintained by hand. pagefromfilelogin — the script comment notes thatsite.login()may need to be inserted intopagefromfile.py.- Soft-skip deploy — a missing repo or JSON is a warning, not a failure (keeps
set -esafe).
9 - Testing
The full chain runs with:
npm test
which executes test:php → analyse:php → test:js, each step required to pass before the next runs.
JavaScript (Mocha + jsdom)
npm run test:js
# mocha 'resources/js/tests/**/*.test.js' --require jsdom-global/register
PHP standalone unit tests
npm run test:php
# runs every tests/test-*.php with the system PHP — no webtrees install, no DB
| Test | Covers |
|---|---|
test-text-tag-collector.php | text-tag collector |
test-link-classifier.php | link classifier (incl. target extraction) |
test-uid-tag-collector.php | UID tag collector |
test-uid-index-service.php | UID index service |
test-i18n-mark.php | i18n markers |
test-wthb-toc-service.php | WTHB TOC service |
PHP static analysis
npm run analyse:php # or: composer analyse
# PHPStan Level 6 — see the PHPStan page
Smoke tests (need webtrees + DB)
These exercise a real installation and are not part of npm test:
php tests/smoke-text-tag-collector.php [limit]
php tests/smoke-migration5.php
php tests/p1-measure.php [--tree=<id>]
10 - PHPStan
Required: PHPstan
Static analysis is configured via phpstan.neon (Level 6, baseline in phpstan-baseline.neon).
Run
composer analyse
The analysis requires a webtrees installation (for app/ classes + vendor/autoload.php). Run it from the bind-mounted path:
cd /path/to/webtrees/modules_v4/linkenhancer
composer analyse
Configuration
- Level: 6
- Baseline:
phpstan-baseline.neon(known issues) - Bootstrap:
phpstan-bootstrap.phpregisters PSR-4 namespaces directly (bypasses the version-gated autoloader inwt-shared-libs) - Scan directories:
../../app/(webtrees core classes) - Exclude paths:
LinkEnhancerModuleExt.php(use in conjunction with Vesta modules) - Memory limit: 1G
- Parallel: disabled (single process, more reliable in CI)
Key settings
treatPhpDocTypesAsCertain: false— prevents false positives from@varassertionsignoreErrorsfor:AccessLevelEnum (WT 2.3-only class, not available in 2.2 runtime)DB::prefix('')— intentional (getting prefix without identifier)
Regenerating the baseline
After intentional changes that introduce new (acceptable) errors:
vendor/bin/phpstan analyse -c phpstan.neon --memory-limit=1G --generate-baseline=phpstan-baseline.neon
Note: Run this via the direct
vendor/bin/phpstancall — thecomposerscript wrapper does not forward extra arguments.
11 - Releasing
Steps
# 1. Bump the version (requires a clean git tree)
npx gulp bumpversion
# 2. Build + compile translations + create the release archive
npx gulp archive
What archive does
npx gulp archive runs build → po2php → create-archive.sh
. The script:
- Reads the version from
latest-version.txt. rsyncs the module intodist/linkenhancer/, excluding everything that is not shipped (below).- Zips it to
dist/linkenhancer_v<version>.zip.
Excluded from the archive
create-archive.sh drops, among others: node_modules/, .git*, gulpfile.mjs, package*.json, phpstan*, tests/, the util/* build scripts, resources/js/src, resources/js/tests, resources/img/, resources/lang/*.po / *.mo, and .env. Only the built bundle* JS/CSS, the compiled resources/lang/*.php, src/, resources/views/, resources/css/ and the module entry files are shipped.
Publishing
- Tag the release in git.
- Upload
dist/linkenhancer_v<version>.zipto a Codeberg / GitHub release with release notes.
The archive is not committed to the repo — it is produced on demand and attached to the release.