<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>wt-modules</title><link>https://bschwede.codeberg.page/wt-modules/en/</link><description>Recent content on wt-modules</description><generator>Hugo</generator><language>en</language><atom:link href="https://bschwede.codeberg.page/wt-modules/en/index.xml" rel="self" type="application/rss+xml"/><item><title>CLI Scripts</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/cli-scripts/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/cli-scripts/</guid><description>&lt;p&gt;The module ships CLI-only scripts. They are guarded: requested over HTTP (the &lt;code&gt;modules_v4&lt;/code&gt; directory is inside the web root) they answer &lt;code&gt;403&lt;/code&gt; instead of running.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Script&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Purpose&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Needs a webtrees install + DB?&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cli/build-link-index.php [--limit=N] [--tree=&amp;lt;id&amp;gt;] [--rebuild] [--flush]&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;build/update the link index for the XREF overview&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;yes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cli/build-uid-index.php [--limit=N] [--tree=&amp;lt;id&amp;gt;] [--rebuild] [--flush] [--force]&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;build/update the UID index for UID lookup&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;yes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cli/refresh-wthb-toc.php [--all] [--lang=&amp;lt;list&amp;gt;] [--clear] [--force]&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;refresh the WTHB table of contents cache (&lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/wthb-toc-lang/"&gt;production&lt;/a&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;yes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-text-tag-collector.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-text-tag-collector.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;standalone unit tests for the text-tag collector&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;no&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-link-classifier.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-link-classifier.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;standalone unit tests for the link classifier (incl. target extraction)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;no&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-uid-tag-collector.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-uid-tag-collector.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;standalone unit tests for the UID tag collector&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;no&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-uid-index-service.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-uid-index-service.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;standalone unit tests for the UID index service&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;no&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;tests/smoke-text-tag-collector.php [limit]&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;scan records containing enhanced links, print every captured value&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;yes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/smoke-migration5.php" target="_blank" rel="noopener"&gt;&lt;code&gt;smoke-migration5.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;SQLite smoke test for Migration5 (UID index schema)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;yes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;tests/p1-measure.php [--tree=&amp;lt;id&amp;gt;]&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;read-only scaling measurement for the XREF overview&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;yes&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Run them from the webtrees root with the &lt;strong&gt;same PHP version&lt;/strong&gt; the instance runs on (webtrees requires PHP 8.3+):&lt;/p&gt;</description></item><item><title>Requirements</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/setup/requirements/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/setup/requirements/</guid><description>&lt;ul&gt;&#10;&lt;li&gt;webtrees 2.2.x (PHP 8.3+)&lt;/li&gt;&#10;&lt;li&gt;PHP CLI with &lt;a href="https://www.php.net/manual/en/function.proc-open.php"&gt;&lt;code&gt;proc_open&lt;/code&gt;&lt;/a&gt; available (standard)&lt;/li&gt;&#10;&lt;li&gt;A trigger: classic cron, a systemd timer, a Windows Task Scheduler task, &lt;strong&gt;or&lt;/strong&gt; the built-in watch daemon (no OS timer needed — see &amp;ldquo;Trigger installation&amp;rdquo;)&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>Requirements</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/setup/requirements/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/setup/requirements/</guid><description>&lt;p&gt;This module requires &lt;strong&gt;webtrees&lt;/strong&gt; version 2.2 or 2.3.&#10;This module has the same requirements as &lt;a href="https://github.com/fisharebest/webtrees#system-requirements"&gt;webtrees#system-requirements&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;It was initially created for version 2.2.4; since linkenhancer 1.6.1 it should also work with webtrees 2.3. Tests were usually run with built-in themes and some other custom modules.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; It&amp;rsquo;s recommended installing the &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/"&gt;Cronjob&lt;/a&gt; module as well. This makes it easy to run the CLI scripts for maintaining the indexes.&lt;/p&gt;&#10;&lt;/blockquote&gt;</description></item><item><title>Service API</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/howto-use/service-api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/howto-use/service-api/</guid><description>&lt;p&gt;Neighboring modules query the module&amp;rsquo;s state through a stable, read-only facade&#10;(&lt;code&gt;CronjobService&lt;/code&gt;) instead of its internal services. Check availability first -&#10;the module may not be installed, and the class is only autoloadable when its&#10;&lt;a href="https://github.com/bschwede/wt-cronjob/tree/main/module.php" target="_blank" rel="noopener"&gt;&lt;code&gt;module.php&lt;/code&gt;&lt;/a&gt;&#10; has been loaded:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#91d7e3;font-weight:bold"&gt;&amp;lt;?&lt;/span&gt;php&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#c6a0f6"&gt;use&lt;/span&gt; Schwendinger\Webtrees\Module\Cronjob\Services\CronjobService;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#c6a0f6"&gt;if&lt;/span&gt; (class_exists(CronjobService&lt;span style="color:#91d7e3;font-weight:bold"&gt;::&lt;/span&gt;&lt;span style="color:#8aadf4"&gt;class&lt;/span&gt;) &lt;span style="color:#91d7e3;font-weight:bold"&gt;&amp;amp;&amp;amp;&lt;/span&gt; CronjobService&lt;span style="color:#91d7e3;font-weight:bold"&gt;::&lt;/span&gt;&lt;span style="color:#8aadf4"&gt;isFunctional&lt;/span&gt;()) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f4dbd6"&gt;$status&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&lt;/span&gt; CronjobService&lt;span style="color:#91d7e3;font-weight:bold"&gt;::&lt;/span&gt;&lt;span style="color:#8aadf4"&gt;jobStatus&lt;/span&gt;(&lt;span style="color:#a6da95"&gt;&amp;#39;mymodule:backup&amp;#39;&lt;/span&gt;); &lt;span style="color:#6e738d;font-style:italic"&gt;// null = unknown job&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f4dbd6"&gt;$age&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&lt;/span&gt; CronjobService&lt;span style="color:#91d7e3;font-weight:bold"&gt;::&lt;/span&gt;&lt;span style="color:#8aadf4"&gt;lastTickAge&lt;/span&gt;(); &lt;span style="color:#6e738d;font-style:italic"&gt;// seconds since last tick&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f4dbd6"&gt;$listened&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&lt;/span&gt; CronjobService&lt;span style="color:#91d7e3;font-weight:bold"&gt;::&lt;/span&gt;&lt;span style="color:#8aadf4"&gt;listenedEvents&lt;/span&gt;(); &lt;span style="color:#6e738d;font-style:italic"&gt;// event name =&amp;gt; job names&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Question&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Method(s)&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Is the service functional?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;isFunctional()&lt;/code&gt;, &lt;code&gt;problems()&lt;/code&gt; (codes: &lt;code&gt;module_disabled&lt;/code&gt;, &lt;code&gt;not_migrated&lt;/code&gt;, &lt;code&gt;cron_library_missing&lt;/code&gt;, &lt;code&gt;tick_stale&lt;/code&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Which features are enabled?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;features()&lt;/code&gt; (&lt;code&gt;watch&lt;/code&gt;, &lt;code&gt;pseudo_events&lt;/code&gt;, &lt;code&gt;route_events&lt;/code&gt;), &lt;code&gt;watchStatus()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;When did the last tick run?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;lastTick()&lt;/code&gt; / &lt;code&gt;lastTickAge()&lt;/code&gt; - both trigger types (marker file &lt;code&gt;data/cronjob/tick.last&lt;/code&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Is a job enabled / when did it last run?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;isJobEnabled($name)&lt;/code&gt;, &lt;code&gt;jobStatus($name)&lt;/code&gt;, &lt;code&gt;lastRun($name)&lt;/code&gt; - job names are namespaced (&lt;code&gt;&amp;lt;module&amp;gt;:&amp;lt;name&amp;gt;&lt;/code&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Which events are listened for?&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;listenedEvents()&lt;/code&gt;, &lt;code&gt;listenersFor($event)&lt;/code&gt;; catalogs: &lt;code&gt;eventCatalog()&lt;/code&gt;, &lt;code&gt;pseudoEvents()&lt;/code&gt;, &lt;code&gt;routeEvents()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Actions (the only two)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;runJobNow($name)&lt;/code&gt; (queued at the next tick), &lt;code&gt;pushEvent($name, $payload)&lt;/code&gt; (event queue)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Contract: read-only methods never throw when the schema is missing (safe defaults),&#10;descriptions are source strings (never pre-translated), and &lt;code&gt;lastRun()&lt;/code&gt; deliberately&#10;excludes the job output, which may contain personal data.&lt;/p&gt;</description></item><item><title>Contributing</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/contributing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/contributing/</guid><description>&lt;p&gt;If you&amp;rsquo;d like to contribute to this module, great! You can contribute by:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Contributing code&lt;/strong&gt; — check out the &lt;a href="https://codeberg.org/bschwede/linkenhancer/issues"&gt;issues&lt;/a&gt; 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.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Testing&lt;/strong&gt; — it&amp;rsquo;s all manual currently, please &lt;a href="https://codeberg.org/bschwede/linkenhancer/issues"&gt;create an issue&lt;/a&gt; for any bugs you find.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="translation"&gt;Translation&lt;a class="td-heading-self-link" href="#translation" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;You can contribute to this project on &lt;a href="https://translate.codeberg.org/projects/linkenhancer/"&gt;Codeberg Weblate&lt;/a&gt;. Updated translations will be included in the next release of this module.&lt;/p&gt;</description></item><item><title>Installation</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/setup/installation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/setup/installation/</guid><description>&lt;ol&gt;&#10;&lt;li&gt;Copy the module folder to &lt;code&gt;modules_v4/cronjob/&lt;/code&gt; (ZIP install works too). The cron library ships inside the module — nothing to fetch.&lt;/li&gt;&#10;&lt;li&gt;Open webtrees once (any page as an admin) so the module&amp;rsquo;s &lt;code&gt;boot()&lt;/code&gt; creates its tables.&lt;/li&gt;&#10;&lt;li&gt;Install the trigger (classic cron, systemd timer, or Windows Task Scheduler — the module&amp;rsquo;s admin page generates the block for your OS with the paths of &lt;em&gt;this&lt;/em&gt; installation already filled in).&lt;/li&gt;&#10;&lt;li&gt;Trigger installation (once) — see below.&lt;/li&gt;&#10;&lt;li&gt;Create your first job in the admin UI or configure and activate registered jobs.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="trigger-installation-once"&gt;Trigger installation (once)&lt;a class="td-heading-self-link" href="#trigger-installation-once" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10; &#10;&#10; &#10;&#10; &#10; &#10; &#10;&#10; &#10; &#10; &#10;&#10;&#10;&lt;img&#10; src="https://bschwede.codeberg.page/wt-modules/img/cj/installation.png"&#10; alt="Installation"&#10; &#10; loading="lazy"&#10;&gt;&lt;/p&gt;</description></item><item><title>Installation</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/setup/installation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/setup/installation/</guid><description>&lt;h3 id="via-custom-module-manager-recommended"&gt;Via Custom Module Manager (recommended)&lt;a class="td-heading-self-link" href="#via-custom-module-manager-recommended" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Install and use &lt;a href="https://github.com/Jefferson49/CustomModuleManager"&gt;Custom Module Manager&lt;/a&gt; for an easy and convenient installation of webtrees custom modules.&lt;/li&gt;&#10;&lt;li&gt;Open the Custom Module Manager view in webtrees, select &amp;ldquo;linkenhancer&amp;rdquo;, and click on the &amp;ldquo;Install Module&amp;rdquo; button.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="manual-installation"&gt;Manual installation&lt;a class="td-heading-self-link" href="#manual-installation" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h3&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Download the &lt;a href="https://codeberg.org/bschwede/linkenhancer/releases/latest"&gt;latest release&lt;/a&gt; of the module.&lt;/li&gt;&#10;&lt;li&gt;Upload the downloaded file to your web server.&lt;/li&gt;&#10;&lt;li&gt;Unzip the package into your &lt;code&gt;modules_v4&lt;/code&gt; directory.&lt;/li&gt;&#10;&lt;li&gt;Rename the folder to &lt;code&gt;linkenhancer&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;If everything was successful, you should see a subdirectory &lt;code&gt;linkenhancer&lt;/code&gt; with the unpacked content in the &lt;code&gt;modules_v4&lt;/code&gt; directory.&lt;/p&gt;</description></item><item><title>Project Structure</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/project-structure/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/project-structure/</guid><description>&lt;h2 id="directory-layout"&gt;Directory layout&lt;a class="td-heading-self-link" href="#directory-layout" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;linkenhancer/&#10;├── module.php ← webtrees entry point&#10;├── autoload.php ← PSR-4 autoloader&#10;├── cron-jobs.php ← cronjob self-registration manifest&#10;├── latest-version.txt ← current version (synced to LinkEnhancerModule.php)&#10;├── composer.json ← PHP deps (wt-shared-libs, phpstan)&#10;├── package.json ← JS deps (gulp, rollup, mocha)&#10;├── gulpfile.mjs ← build tasks&#10;├── phpstan.neon ← static analysis config&#10;├── phpstan-bootstrap.php ← PSR-4 bootstrap for PHPStan&#10;├── phpstan-baseline.neon ← known issues&#10;├── src/ ← PHP (PSR-4)&#10;│ ├── LinkEnhancerModule.php&#10;│ ├── LinkEnhancerModuleExt.php&#10;│ ├── LinkEnhancerUtils.php&#10;│ ├── SettingInterface.php&#10;│ ├── CommonMark/ ← CommonMark extensions&#10;│ ├── DataFix/ ← data-fix handlers + dispatcher&#10;│ ├── Factories/ ← CustomMarkdownFactory&#10;│ ├── Http/ ← request handlers&#10;│ ├── Schema/ ← Migrations + SeedHelpTable&#10;│ └── Services/ ← business logic&#10;├── resources/&#10;│ ├── js/ ← JS source (src/) + build output (bundle-*)&#10;│ ├── css/ ← CSS source + build output&#10;│ ├── lang/ ← .pot / .po / .php (translations)&#10;│ ├── views/ ← PHTML views&#10;│ └── img/ ← screenshots&#10;├── cli/ ← CLI maintenance scripts&#10;├── tests/ ← standalone + smoke tests&#10;└── util/ ← build/maintenance shell scripts&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="dependencies"&gt;Dependencies&lt;a class="td-heading-self-link" href="#dependencies" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;PHP&lt;/strong&gt; (&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/composer.json" target="_blank" rel="noopener"&gt;&lt;code&gt;composer.json&lt;/code&gt;&lt;/a&gt;&#10;):&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;bschwede/wt-shared-libs&lt;/code&gt; (path repo &lt;code&gt;../wt-shared-libs&lt;/code&gt;) and&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/phpstan/"&gt;&lt;code&gt;phpstan&lt;/code&gt;&lt;/a&gt; (dev).&lt;/li&gt;&#10;&lt;li&gt;Conflicts with:&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;webtrees &amp;lt;2.2&lt;/code&gt; and&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/elysch/webtrees-mitalteli-misc-features"&gt;&lt;code&gt;elysch/webtrees-mitalteli-misc-features&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;JS&lt;/strong&gt; (&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/package.json" target="_blank" rel="noopener"&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt;&#10;):&#10;&lt;ul&gt;&#10;&lt;li&gt;runtime dependency &lt;a href="https://github.com/jefago/tiny-markdown-editor"&gt;&lt;code&gt;tiny-markdown-editor&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;dev tooling — Gulp, Rollup, PostCSS (autoprefixer, cssnano), Mocha + chai + sinon, jsdom.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="database-schema"&gt;Database schema&lt;a class="td-heading-self-link" href="#database-schema" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The module owns five tables (prefix &lt;code&gt;le_&lt;/code&gt;), created incrementally by &lt;code&gt;src/Schema/Migration#.php&lt;/code&gt;. Every migration is additive and idempotent, so the schema converges on each module load.&lt;/p&gt;</description></item><item><title>Pseudo-events</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/events/pseudo/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/events/pseudo/</guid><description>&lt;p&gt;webtrees has no event bus, so core actions (logins, failed logins, logouts, record edits, errors, searches) are detected by &lt;strong&gt;polling the webtrees log table&lt;/strong&gt; (&lt;code&gt;app/Log.php&lt;/code&gt;, table &lt;code&gt;log&lt;/code&gt;) and firing an event per new matching log entry. It is &lt;strong&gt;off by default&lt;/strong&gt;: nothing runs until you enable the offered &lt;code&gt;cronjob:pseudo-events&lt;/code&gt; job.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Built-in events&lt;/strong&gt; (one &lt;code&gt;cj_event&lt;/code&gt; per new log row, payload = the row):&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Event&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Log filter (log_type, message prefix)&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Fires when&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;cronjob:log-auth-failed&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;Login failed&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;a login attempt failed (wrong password, unknown user, unverified, not approved, no session cookies)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;cronjob:log-auth-login&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;Login:&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;a user logged in&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;cronjob:log-auth-logout&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;Logout:&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;a user logged out&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;cronjob:log-error&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;error&lt;/code&gt;, –&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;an exception was caught and logged (the message is the trace)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;cronjob:log-edit-update&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;edit&lt;/code&gt;, &lt;code&gt;Update:&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;a record was updated (&lt;code&gt;GedcomRecord::updateRecord()&lt;/code&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;cronjob:log-edit-delete&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;edit&lt;/code&gt;, &lt;code&gt;Delete:&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;a record was deleted (&lt;code&gt;GedcomRecord::deleteRecord()&lt;/code&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;cronjob:log-search&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;code&gt;search&lt;/code&gt;, –&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;a search ran (&lt;strong&gt;one log row per searched tree&lt;/strong&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;strong&gt;Payload:&lt;/strong&gt; &lt;code&gt;log_id&lt;/code&gt;, &lt;code&gt;log_time&lt;/code&gt;, &lt;code&gt;log_message&lt;/code&gt; (truncated to 250 characters, UTF-8-safe) plus &lt;code&gt;gedcom_id&lt;/code&gt; / &lt;code&gt;user_id&lt;/code&gt; when the log row carries them. The message prefixes are the hard-coded English strings of the current core; a core update that changes them silently stops the matching event (no fallback by design) — the event catalog descriptions and the &lt;code&gt;test-pseudo-events&lt;/code&gt; suite pin the current mapping.&lt;/p&gt;</description></item><item><title>Self-registration</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/howto-use/self-registration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/howto-use/self-registration/</guid><description>&lt;p&gt;Other modules can &lt;em&gt;advertise&lt;/em&gt; their &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/cli-scripts/"&gt;scheduled jobs&lt;/a&gt; so they show up in the admin UI without manual registry editing. Two ways (a module may use both); both are discovered automatically on each &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/tick/"&gt;tick&lt;/a&gt;, only for &lt;strong&gt;enabled&lt;/strong&gt; modules:&lt;/p&gt;&#10;&lt;h2 id="marker-method"&gt;Marker method&lt;a class="td-heading-self-link" href="#marker-method" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;getCronJobs(): array&lt;/code&gt; on the module&amp;rsquo;s &lt;a href="https://github.com/bschwede/wt-cronjob/tree/main/module.php" target="_blank" rel="noopener"&gt;&lt;code&gt;module.php&lt;/code&gt;&lt;/a&gt;&#10; class, returning the same shape. Equivalent: &lt;code&gt;getModuleEvents(): array&lt;/code&gt;, &lt;code&gt;getModuleCommands(): array&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="manifest-file-recommended"&gt;Manifest file (recommended)&lt;a class="td-heading-self-link" href="#manifest-file-recommended" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;modules_v4/&amp;lt;module&amp;gt;/cron-jobs.php&lt;/code&gt; that returns an array of job specs:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#91d7e3;font-weight:bold"&gt;&amp;lt;?&lt;/span&gt;php&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e738d;font-style:italic"&gt;// modules_v4/linkenhancer/cron-jobs.php&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#c6a0f6"&gt;declare&lt;/span&gt;(strict_types&lt;span style="color:#91d7e3;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#f5a97f"&gt;1&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#c6a0f6"&gt;return&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;name&amp;#39;&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;link-index&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;title&amp;#39;&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;Update the link index&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;triggers&amp;#39;&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&amp;gt;&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#a6da95"&gt;&amp;#39;type&amp;#39;&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;time&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6da95"&gt;&amp;#39;cron&amp;#39;&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;*/30 * * * *&amp;#39;&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;command_type&amp;#39;&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;module&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;command&amp;#39;&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;modules_v4/linkenhancer/cli/build-link-index.php&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;args&amp;#39;&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6da95"&gt;&amp;#39;--limit=5000&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#10;&lt;p&gt;Translatable &lt;code&gt;title&lt;/code&gt; / &lt;code&gt;description&lt;/code&gt; literals in a manifest should be wrapped in&#10;&lt;code&gt;MoreI18N::translate()&lt;/code&gt; (identity marker)&#10;so &lt;code&gt;xgettext&lt;/code&gt; picks them up - translation happens at render time, never at manifest&#10;load.&lt;/p&gt;</description></item><item><title>Admin settings</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/setup/settings/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/setup/settings/</guid><description>&lt;p&gt;Since this is a service module, the only way to access its functions is through the module settings page. Strictly speaking, the only settings available are:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;the optional path to the PHP CLI binary (if it is not automatically detected) and&lt;/li&gt;&#10;&lt;li&gt;the option to enable additional debug logging in the error log.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="event-catalog"&gt;Event catalog&lt;a class="td-heading-self-link" href="#event-catalog" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The admin page (Events section) and the job form&amp;rsquo;s event-name suggestions are fed by an &lt;strong&gt;event inventory&lt;/strong&gt; (&lt;code&gt;cj_event_catalog&lt;/code&gt;, synced by the &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/tick/"&gt;tick&lt;/a&gt; like the job registry). It merges, in this priority order:&lt;/p&gt;</description></item><item><title>Admin settings</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/setup/settings/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/setup/settings/</guid><description>&lt;p&gt;At the top of the admin page, you can access the administrative &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/enhanced-links/xref-overview/"&gt;Cross-Reference Overview&lt;/a&gt; and the &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/enhanced-links/renumber/"&gt;Renumber xref_IDs (with references)&lt;/a&gt; function. &lt;br&gt;&#10;You can also enable or disable the basic features:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/enhanced-links/"&gt;Enhanced Links&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/markdown/"&gt;Markdown enhancements&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Help functions (&lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/wthb/"&gt;German Webtrees Manual&lt;/a&gt;)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/home-link/"&gt;Home Link&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/uid-lookup/"&gt;UID Lookup&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;When the first four basic features are enabled, more detailed settings can be configured further down. There are also two global settings for debug output in the browser’s JavaScript console and the web server error log file, and you can choose whether links should always open in a new browser tab (it’s also possible to set different options for each core feature).&lt;/p&gt;</description></item><item><title>Class Hierarchy</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/class-hierarchy/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/class-hierarchy/</guid><description>&lt;p&gt;&lt;em&gt;Auto-generated by &lt;code&gt;docs/tools/generate-class-hierarchy.php&lt;/code&gt; — do not edit manually.&lt;/em&gt;&lt;/p&gt;&#10;&lt;h2 id="module"&gt;Module&lt;a class="td-heading-self-link" href="#module" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;LinkEnhancerModule&lt;/code&gt; extends &lt;code&gt;AbstractModule&lt;/code&gt; implements &lt;code&gt;MiddlewareInterface&lt;/code&gt;, &lt;code&gt;ModuleCustomInterface&lt;/code&gt;, &lt;code&gt;ModuleGlobalInterface&lt;/code&gt;, &lt;code&gt;ModuleConfigInterface&lt;/code&gt;, &lt;code&gt;ModuleDataFixInterface&lt;/code&gt;, &lt;code&gt;ModuleListInterface&lt;/code&gt;, &lt;code&gt;ModuleTabInterface&lt;/code&gt;, &lt;code&gt;SettingInterface&lt;/code&gt; — &lt;code&gt;LinkEnhancerModule.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;SettingInterface&lt;/code&gt; — &lt;code&gt;SettingInterface.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;LinkEnhancerUtils&lt;/code&gt; — &lt;code&gt;LinkEnhancerUtils.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;LinkEnhancerModuleExt&lt;/code&gt; extends &lt;code&gt;LinkEnhancerModule&lt;/code&gt; implements &lt;code&gt;ModuleExtGlobalInterface&lt;/code&gt; — &lt;code&gt;LinkEnhancerModuleExt.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="commonmark-extensions"&gt;CommonMark (Extensions)&lt;a class="td-heading-self-link" href="#commonmark-extensions" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;LeImageRenderer&lt;/code&gt; (final) implements &lt;code&gt;NodeRendererInterface&lt;/code&gt;, &lt;code&gt;XmlNodeRendererInterface&lt;/code&gt;, &lt;code&gt;ConfigurationAwareInterface&lt;/code&gt; — &lt;code&gt;CommonMark/LeImageRenderer.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;SmartPunctHelper&lt;/code&gt; — &lt;code&gt;CommonMark/SmartPunctHelper.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;LeTableOfContentsRenderer&lt;/code&gt; (final) implements &lt;code&gt;NodeRendererInterface&lt;/code&gt;, &lt;code&gt;XmlNodeRendererInterface&lt;/code&gt; — &lt;code&gt;CommonMark/LeTableOfContentsRenderer.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="factories"&gt;Factories&lt;a class="td-heading-self-link" href="#factories" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;CustomMarkdownFactory&lt;/code&gt; extends &lt;code&gt;MarkdownFactory&lt;/code&gt; — &lt;code&gt;Factories/CustomMarkdownFactory.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="request-handlers"&gt;Request Handlers&lt;a class="td-heading-self-link" href="#request-handlers" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;DataFixBlocksAction&lt;/code&gt; implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/DataFixBlocksAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;HelpWthbAction&lt;/code&gt; implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/HelpWthbAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;XrefDetailData&lt;/code&gt; (final) implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/XrefDetailData.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;XrefOverviewListData&lt;/code&gt; (final) implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/XrefOverviewListData.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;GotoIdTreeAction&lt;/code&gt; extends &lt;code&gt;GotoIdAction&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/GotoIdTreeAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;GotoUidTreeAction&lt;/code&gt; extends &lt;code&gt;GotoUidAction&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/GotoUidTreeAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;GotoXrefAction&lt;/code&gt; implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/GotoXrefAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;DataFixRebuildIndexAction&lt;/code&gt; implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/DataFixRebuildIndexAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;RenumberActionHandler&lt;/code&gt; (final) — &lt;code&gt;Http/RequestHandlers/RenumberActionHandler.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;HelpMdAction&lt;/code&gt; implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/HelpMdAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;AdminXrefOverviewData&lt;/code&gt; (final) implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/AdminXrefOverviewData.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;GotoIdAction&lt;/code&gt; extends &lt;code&gt;AbstractGotoAction&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/GotoIdAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;HelpWtCoreAction&lt;/code&gt; implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/HelpWtCoreAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;WthbAdminHandler&lt;/code&gt; (final) — &lt;code&gt;Http/RequestHandlers/WthbAdminHandler.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;GotoUidAction&lt;/code&gt; extends &lt;code&gt;AbstractGotoAction&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/GotoUidAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;AbstractGotoAction&lt;/code&gt; (abstract) implements &lt;code&gt;RequestHandlerInterface&lt;/code&gt; — &lt;code&gt;Http/RequestHandlers/AbstractGotoAction.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="schema"&gt;Schema&lt;a class="td-heading-self-link" href="#schema" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;SeedHelpTable&lt;/code&gt; implements &lt;code&gt;SeedInterface&lt;/code&gt; — &lt;code&gt;Schema/SeedHelpTable.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Migration6&lt;/code&gt; implements &lt;code&gt;MigrationInterface&lt;/code&gt; — &lt;code&gt;Schema/Migration6.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Migration2&lt;/code&gt; implements &lt;code&gt;MigrationInterface&lt;/code&gt; — &lt;code&gt;Schema/Migration2.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Migration7&lt;/code&gt; implements &lt;code&gt;MigrationInterface&lt;/code&gt; — &lt;code&gt;Schema/Migration7.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Migration3&lt;/code&gt; implements &lt;code&gt;MigrationInterface&lt;/code&gt; — &lt;code&gt;Schema/Migration3.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Migration4&lt;/code&gt; implements &lt;code&gt;MigrationInterface&lt;/code&gt; — &lt;code&gt;Schema/Migration4.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Migration1&lt;/code&gt; implements &lt;code&gt;MigrationInterface&lt;/code&gt; — &lt;code&gt;Schema/Migration1.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Migration5&lt;/code&gt; implements &lt;code&gt;MigrationInterface&lt;/code&gt; — &lt;code&gt;Schema/Migration5.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;Migration0&lt;/code&gt; implements &lt;code&gt;MigrationInterface&lt;/code&gt; — &lt;code&gt;Schema/Migration0.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="services"&gt;Services&lt;a class="td-heading-self-link" href="#services" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;AdminSettingsBuilder&lt;/code&gt; (final) — &lt;code&gt;Services/AdminSettingsBuilder.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;MarkdownEditorActivationService&lt;/code&gt; (final) — &lt;code&gt;Services/MarkdownEditorActivationService.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;IndexRebuildScheduler&lt;/code&gt; (final) — &lt;code&gt;Services/IndexRebuildScheduler.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;UidTagCollector&lt;/code&gt; (final) — &lt;code&gt;Services/UidTagCollector.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;TextTagCollector&lt;/code&gt; (final) — &lt;code&gt;Services/TextTagCollector.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;ContentBuilder&lt;/code&gt; (final) — &lt;code&gt;Services/ContentBuilder.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;RenumberWithLinksService&lt;/code&gt; (final) — &lt;code&gt;Services/RenumberWithLinksService.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;DataFiles&lt;/code&gt; (final) — &lt;code&gt;Services/DataFiles.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;XrefOverviewColumns&lt;/code&gt; (final) — &lt;code&gt;Services/XrefOverviewColumns.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;LinkIndexService&lt;/code&gt; (final) — &lt;code&gt;Services/LinkIndexService.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;XrefsService&lt;/code&gt; (final) — &lt;code&gt;Services/XrefsService.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;XrefDetailService&lt;/code&gt; (final) — &lt;code&gt;Services/XrefDetailService.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;WthbService&lt;/code&gt; — &lt;code&gt;Services/WthbService.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;IdResolver&lt;/code&gt; (final) — &lt;code&gt;Services/IdResolver.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;LinkRenderer&lt;/code&gt; (final) — &lt;code&gt;Services/LinkRenderer.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;WthbTocService&lt;/code&gt; (final) — &lt;code&gt;Services/WthbTocService.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;UidIndexService&lt;/code&gt; (final) — &lt;code&gt;Services/UidIndexService.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="datafix"&gt;DataFix&lt;a class="td-heading-self-link" href="#datafix" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;FixHandlerInterface&lt;/code&gt; — &lt;code&gt;DataFix/FixHandlerInterface.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;DataFixDispatcher&lt;/code&gt; — &lt;code&gt;DataFix/DataFixDispatcher.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;TreeLookupTrait&lt;/code&gt; — &lt;code&gt;DataFix/TreeLookupTrait.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;DanglingLinksFix&lt;/code&gt; (final) implements &lt;code&gt;FixHandlerInterface&lt;/code&gt; — &lt;code&gt;DataFix/Handlers/DanglingLinksFix.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;DuplicateUidFix&lt;/code&gt; (final) implements &lt;code&gt;FixHandlerInterface&lt;/code&gt; — &lt;code&gt;DataFix/Handlers/DuplicateUidFix.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;WtTypeFix&lt;/code&gt; (final) implements &lt;code&gt;FixHandlerInterface&lt;/code&gt; — &lt;code&gt;DataFix/Handlers/WtTypeFix.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;XrefUidSwapFix&lt;/code&gt; (final) implements &lt;code&gt;FixHandlerInterface&lt;/code&gt; — &lt;code&gt;DataFix/Handlers/XrefUidSwapFix.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;AmbiguousUidFix&lt;/code&gt; (final) implements &lt;code&gt;FixHandlerInterface&lt;/code&gt; — &lt;code&gt;DataFix/Handlers/AmbiguousUidFix.php&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>Route events</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/events/route/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/events/route/</guid><description>&lt;p&gt;A complementary, &lt;em&gt;immediate&lt;/em&gt; detection mechanism: instead of polling state on a schedule, the module observes the HTTP request itself. When a request to a &lt;strong&gt;mapped mutating route&lt;/strong&gt; has been handled successfully (2xx/3xx), it queues the matching event into the same &lt;code&gt;cj_event&lt;/code&gt; queue — a listening event job then reacts at the next tick (~60 s), with no polling latency.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Which routes are mapped&lt;/strong&gt; — two rules, both derived from the live webtrees route table. Common prerequisites: path starts with &lt;code&gt;/tree/{tree}/&lt;/code&gt;, the handler is in webtrees&amp;rsquo; request-handler namespace (&lt;code&gt;RequestHandlers&lt;/code&gt; in 2.2.6, &lt;code&gt;Controllers&lt;/code&gt; in 2.3), and the request is a &lt;strong&gt;POST&lt;/strong&gt; mutation. In 2.2.6 this is enforced at map time (GET page routes are not mapped); in 2.3 the route carries no HTTP method, so the POST check happens at fire-time and the GET form-load of the same route is ignored.&lt;/p&gt;</description></item><item><title>Usage</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/usage/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/usage/</guid><description>&lt;h2 id="creating-a-job"&gt;Creating a job&lt;a class="td-heading-self-link" href="#creating-a-job" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;Control panel → Cron Job Scheduler → New Job&lt;/code&gt;:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Field&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Meaning&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Name (slug)&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;technical identifier, &lt;code&gt;a-z 0-9 _ -&lt;/code&gt;. &lt;strong&gt;Read-only for &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/howto-use/self-registration/"&gt;module-offered jobs&lt;/a&gt;&lt;/strong&gt; (their &lt;code&gt;&amp;lt;module&amp;gt;:&amp;lt;name&amp;gt;&lt;/code&gt; key is fixed); editable for jobs you create yourself.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Title&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;human readable name&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Triggers&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;one or more &lt;strong&gt;time triggers&lt;/strong&gt; (cron schedules) and/or &lt;strong&gt;event triggers&lt;/strong&gt; — the job fires when &lt;em&gt;any&lt;/em&gt; trigger fires. At least one is required.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Cron expression&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;(time trigger) standard 5-field cron or a macro (&lt;code&gt;@daily&lt;/code&gt;, &lt;code&gt;@weekly&lt;/code&gt;, …). &lt;strong&gt;Times are UTC.&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Event name&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;(event trigger) the &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/events/"&gt;event&lt;/a&gt; this job reacts to. Slug or &lt;code&gt;&amp;lt;domain&amp;gt;:slug&lt;/code&gt;, max 64 chars.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Command&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;a &lt;code&gt;modules_v4/&amp;lt;module&amp;gt;/cli/&amp;lt;script&amp;gt;.php&lt;/code&gt; path or an allowlisted core command.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Arguments&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;plain options/values only (e.g. &lt;code&gt;--limit=5000&lt;/code&gt;), max 10 tokens&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Timeout&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;30 - 3600 s&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Notify on failure&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;opt-in: alert the administrator accounts when this job fails&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Available event names and commands are provided as datalists to make data entry easier.&lt;/p&gt;</description></item><item><title>Markdown Image Support</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/markdown/md-image/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/markdown/md-image/</guid><description>&lt;p&gt;&#10;&#10;&#10;&#10; &#10;&#10; &#10;&#10; &#10; &#10; &#10;&#10; &#10; &#10; &#10;&#10;&#10;&lt;img&#10; src="https://bschwede.codeberg.page/wt-modules/img/le/screenshot_note-with-links-and-img.png"&#10; alt="note with links"&#10; &#10; loading="lazy"&#10;&gt;&lt;/p&gt;&#10;&lt;p&gt;This feature allows you to easily embed images from GEDCOM media records (which reside behind the media firewall) or from the public directory into notes. If restriction rules apply to the record, instead of the image, a message is displayed.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Syntax:&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;![alternate text for gedcom object](#@id=@M1@)&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;![alternate text for picture in public folder](#@public=webtrees.png &amp;quot;title&amp;quot;)&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;![picture with defined height, width and additional css class](#@id=@M1@&amp;amp;h=200&amp;amp;w=200&amp;amp;cname=float-right)&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Technically the images are packed into a div container together with an image subtitle — which is also a link to the media data set for GEDCOM objects. The display can be customized as required using the standard CSS classes or per-image additional CSS classes (e.g. &lt;code&gt;float-start&lt;/code&gt; or &lt;code&gt;float-end&lt;/code&gt; from webtrees vendor.css).&lt;/p&gt;</description></item><item><title>Markdown Editor</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/markdown/mde/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/markdown/mde/</guid><description>&lt;p&gt;You can enable a visual &lt;strong&gt;markdown editor&lt;/strong&gt; (mde) for note textareas. Under the hood the project &lt;a href="https://github.com/jefago/tiny-markdown-editor"&gt;TinyMDE - A tiny, dependency-free embeddable HTML/JavaScript Markdown editor&lt;/a&gt; is used.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10; &#10;&#10; &#10;&#10; &#10; &#10; &#10;&#10; &#10; &#10; &#10;&#10;&#10;&lt;img&#10; src="https://bschwede.codeberg.page/wt-modules/img/le/screenshot_tinymde-sharednote.png"&#10; alt="Markdown editor"&#10; &#10; loading="lazy"&#10;&gt;&lt;/p&gt;&#10;&lt;p&gt;Besides syntax highlighting it ships with an icon bar for common format commands, a help popup and line numbering.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10; &#10;&#10; &#10;&#10; &#10; &#10; &#10;&#10; &#10; &#10; &#10;&#10;&#10;&lt;img&#10; src="https://bschwede.codeberg.page/wt-modules/img/le/screenshot_markdown-help.png"&#10; alt="Markdown Help"&#10; &#10; loading="lazy"&#10;&gt;&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unfortunately, the on-screen keyboard does NOT work as before with the previous text input field. The selected characters end up as an intermediate step in the small text field below the Markdown editor and then must be copied manually to the desired position.&lt;/p&gt;</description></item><item><title>Markdown Extensions</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/markdown/md-extensions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/markdown/md-extensions/</guid><description>&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Please keep in mind when using these extensions, you are deviating from the smallest common markdown dialect set. If the library used to render markdown in HTML needs to be changed for any reason, there is a possibility that not all of the extended markup used here will be supported (see also &lt;a href="https://codeberg.org/bschwede/linkenhancer/issues/32"&gt;#32&lt;/a&gt;). Since the markup is not intrusive, the impact should not be serious — of course, it&amp;rsquo;s a minor drawback.&lt;/p&gt;</description></item><item><title>The Tick</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/tick/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/tick/</guid><description>&lt;p&gt;The &lt;strong&gt;tick&lt;/strong&gt; is the heart of the module: once a minute it looks at every enabled job, decides what is due, and runs it. Neither the webtrees core nor the operating system does any scheduling itself — they only start &lt;a href="https://github.com/bschwede/wt-cronjob/tree/main/cli/tick.php" target="_blank" rel="noopener"&gt;&lt;code&gt;tick.php&lt;/code&gt;&lt;/a&gt;&#10;.&lt;/p&gt;&#10;&#10;&#10;&#10;&lt;pre class="mermaid"&gt;flowchart TD&#10; subgraph Triggers[&amp;#34;`⏱ Triggers *(once per minute)*`&amp;#34;]&#10; CRON[&amp;#34;`🕑 OS cron / systemd&amp;lt;br/&amp;gt;*every minute*`&amp;#34;]&#10; WATCH[&amp;#34;`👁 watch daemon&amp;lt;br/&amp;gt;*(in-module)*`&amp;#34;]&#10; end&#10;&#10; subgraph Guard[&amp;#34;🛡 Guard&amp;#34;]&#10; TP[&amp;#34;`📄 cli/tick.php · **cron:tick**&amp;lt;br/&amp;gt;*guard(): 403 over HTTP*`&amp;#34;]&#10; OFF{&amp;#34;`⚠ site offline?&amp;lt;br/&amp;gt;*data/offline.txt*`&amp;#34;}&#10; BOOT[&amp;#34;`🚀 boot()&amp;lt;br/&amp;gt;*webtrees &amp;#43; DB*`&amp;#34;]&#10; end&#10;&#10; subgraph Selection[&amp;#34;🔎 Select work&amp;#34;]&#10; TIME[&amp;#34;`📅 time-based jobs&amp;lt;br/&amp;gt;**next_run_at due** *(UTC)*`&amp;#34;]&#10; CE[(&amp;#34;`📦 cj_event&amp;lt;br/&amp;gt;*event queue*`&amp;#34;)]&#10; DRAIN[&amp;#34;`🧮 drain queue&amp;lt;br/&amp;gt;*coalesced · 60 s budget*`&amp;#34;]&#10; end&#10;&#10; subgraph Execution[&amp;#34;`⚙ Execution *(isolated child)*`&amp;#34;]&#10; RUN[&amp;#34;`🧪 JobRunner&amp;lt;br/&amp;gt;**proc_open**`&amp;#34;]&#10; LIM[&amp;#34;`⏲ timeout 30–3600 s&amp;lt;br/&amp;gt;last **64 KB** · exit code`&amp;#34;]&#10; end&#10;&#10; subgraph Storage[&amp;#34;💾 Storage&amp;#34;]&#10; CJRUN[(&amp;#34;`cj_run&amp;lt;br/&amp;gt;*run history*`&amp;#34;)]&#10; MARK[&amp;#34;`🏷 tick.last&amp;lt;br/&amp;gt;*stale &amp;gt; 300 s → tick_stale*`&amp;#34;]&#10; end&#10;&#10; CRON --&amp;gt; Guard&#10; WATCH --&amp;gt; Guard&#10; TP --&amp;gt; OFF&#10; OFF --&amp;gt;|&amp;#34;offline&amp;#34;| SKIP[&amp;#34;⏭ skip tick&amp;#34;]&#10; OFF --&amp;gt;|&amp;#34;online&amp;#34;| BOOT&#10; BOOT --&amp;gt; TIME&#10; BOOT --&amp;gt; DRAIN&#10; CE --&amp;gt; DRAIN&#10; TIME --&amp;gt; Execution&#10; DRAIN --&amp;gt; Execution&#10; RUN --&amp;gt; LIM&#10; LIM --&amp;gt; CJRUN&#10; BOOT --&amp;gt; MARK&#10;&#10; classDef sourceStyle fill:#e3f2fd,stroke:#1565C0,stroke-width:2px&#10; classDef queueStyle fill:#fff3e0,stroke:#E65100,stroke-width:2px&#10; classDef tickStyle fill:#e8f5e9,stroke:#2E7D32,stroke-width:2px&#10; classDef execStyle fill:#fce4ec,stroke:#C2185B,stroke-width:2px&#10; classDef storageStyle fill:#f5f5f5,stroke:#616161,stroke-width:1px&#10;&#10; class CRON,WATCH sourceStyle&#10; class OFF,CE queueStyle&#10; class TP,TIME,DRAIN tickStyle&#10; class RUN,LIM execStyle&#10; class CJRUN,MARK,SKIP storageStyle&#10;&lt;/pre&gt;&#10;&lt;h2 id="what-starts-a-tick"&gt;What starts a tick&lt;a class="td-heading-self-link" href="#what-starts-a-tick" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;Pick &lt;strong&gt;one&lt;/strong&gt; trigger (once):&lt;/p&gt;</description></item><item><title>Development Environment</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/dev-environment/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/dev-environment/</guid><description>&lt;p&gt;What you need to build and test the module locally.&lt;/p&gt;&#10;&lt;h2 id="requirements"&gt;Requirements&lt;a class="td-heading-self-link" href="#requirements" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Component&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Version&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Notes&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;PHP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8.3+&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;same version the webtrees instance runs on&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Composer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.x&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;installs &lt;code&gt;wt-shared-libs&lt;/code&gt; + &lt;code&gt;phpstan&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Node.js&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;18+ (LTS)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;for Gulp / Rollup / Mocha&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;webtrees&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.2.x or 2.3.x&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;needed for PHPStan (&lt;code&gt;app/&lt;/code&gt; classes) and the smoke tests&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/wt-shared-libs/"&gt;wt-shared-libs&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;newest version&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;sibling repo at &lt;code&gt;modules_v4/wt-shared-libs&lt;/code&gt; (composer path repo)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="location"&gt;Location&lt;a class="td-heading-self-link" href="#location" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The module lives inside a webtrees installation at &lt;code&gt;modules_v4/linkenhancer/&lt;/code&gt;. Its &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/composer.json" target="_blank" rel="noopener"&gt;&lt;code&gt;composer.json&lt;/code&gt;&lt;/a&gt;&#10; pulls in the sibling shared library as a local path repository:&lt;/p&gt;</description></item><item><title>German webtrees manual (WTHB)</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/wthb/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/wthb/</guid><description>&lt;figure class="logo-figure float-end m-3"&gt;&#10; &lt;img src="https://bschwede.codeberg.page/wt-modules/img/heros/CG-Logo-2018_135px.png" alt="CompGen logo" style="width: 160px; height: auto;" loading="lazy" title=""&gt;&#10;&#10; &#10;&lt;/figure&gt;&#10;&lt;p&gt;A &lt;strong&gt;context sensitive help link&lt;/strong&gt; to the &lt;a href="https://wiki.genealogy.net/Webtrees_Handbuch"&gt;German webtrees manual&lt;/a&gt; can be added to the small navigation menu. Context links are also available for components on pages provided by custom extensions (e.g. on the individuals page, the start page or items in the main menu) — these can be identified by the i symbols in a circle.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10; &#10;&#10; &#10;&#10; &#10; &#10; &#10;&#10; &#10; &#10; &#10;&#10;&#10;&lt;img&#10; src="https://bschwede.codeberg.page/wt-modules/img/le/screenshot_small-menu-wthb-link.png"&#10; alt="small menu with help link"&#10; &#10; loading="lazy"&#10;&gt;&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10; &#10;&#10; &#10;&#10; &#10; &#10; &#10;&#10; &#10; &#10; &#10;&#10;&#10;&lt;img&#10; src="https://bschwede.codeberg.page/wt-modules/img/le/screenshot_wthb-subcontext.png"&#10; alt="WTHB subcontext help"&#10; &#10; loading="lazy"&#10;&gt;&lt;/p&gt;&#10;&lt;p&gt;To ensure that this function is also available in the admin backend, a modified version of &lt;a href="https://github.com/fisharebest/webtrees/blob/main/resources/views/layouts/administration.phtml"&gt;views/layouts/administration.phtml&lt;/a&gt; must be registered. This module attempts to use the custom module &lt;a href="https://github.com/vesta-webtrees-2-custom-modules/vesta_common"&gt;vesta_common&lt;/a&gt; (if installed and enabled) or its own version of the view layout file. Alternatively, patch P002 could also be applied to the core code.&lt;/p&gt;</description></item><item><title>Table cell height control</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/markdown/cell-height/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/markdown/cell-height/</guid><description>&lt;p&gt;For longer markdown note texts it&amp;rsquo;s sometimes a pain to scroll through if you are interested in the next facts beneath them at the moment. This feature should come in handy here: By activating the checkbox in the top right corner of the title cell (first table column), the maximum height of the content cell with the long note is set to about 80% of the viewport height. This makes the content itself scrollable.&lt;/p&gt;</description></item><item><title>Home Link</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/home-link/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/home-link/</guid><description>&lt;p&gt;The site title can be a link to the tree homepage or the user my page. The webmaster can also set a user-defined URL.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10; &#10;&#10; &#10;&#10; &#10; &#10; &#10;&#10; &#10; &#10; &#10;&#10;&#10;&lt;img&#10; src="https://bschwede.codeberg.page/wt-modules/img/le/screenshot_sitetitle-as-link.png"&#10; alt="site title as link"&#10; &#10; loading="lazy"&#10;&gt;&lt;/p&gt;&#10;&lt;p&gt;Dynamic CSS styles can be applied to match the current theme, ensuring that the display contrasts well. Of course it is not limited to the home link use case - for details see &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/setup/settings/"&gt;Admin settings&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>CLI Scripts</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/cli-scripts/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/cli-scripts/</guid><description>&lt;h2 id="scripts"&gt;Scripts&lt;a class="td-heading-self-link" href="#scripts" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php &lt;span style="color:#6e738d;font-style:italic"&gt;# run all due jobs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php --dry-run &lt;span style="color:#6e738d;font-style:italic"&gt;# show what would run&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php --job&lt;span style="color:#91d7e3;font-weight:bold"&gt;=&lt;/span&gt;&amp;lt;name&amp;gt; &lt;span style="color:#6e738d;font-style:italic"&gt;# run one specific job now&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php --strict &lt;span style="color:#6e738d;font-style:italic"&gt;# exit 1 if any job failed&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/tick.php --full-output &lt;span style="color:#6e738d;font-style:italic"&gt;# echo full job output (debugging!)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php modules_v4/cronjob/cli/watch.php &lt;span style="color:#6e738d;font-style:italic"&gt;# run the resident watch daemon&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Exit codes: &lt;code&gt;0&lt;/code&gt; = tick ran (job failures are recorded in the DB), &lt;code&gt;1&lt;/code&gt; = environment problem or (with &lt;code&gt;--strict&lt;/code&gt;) at least one job failed.&lt;/p&gt;</description></item><item><title>Cross-Reference Overview</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/enhanced-links/xref-overview/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/enhanced-links/xref-overview/</guid><description>&lt;p&gt;There are three types of overviews:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;overview about all data for the admin (&lt;strong&gt;admin overview&lt;/strong&gt;),&lt;/li&gt;&#10;&lt;li&gt;to one tree limited overview (&lt;strong&gt;user overview&lt;/strong&gt;), and&lt;/li&gt;&#10;&lt;li&gt;record-specific overview on the record details view (&lt;strong&gt;record overview&lt;/strong&gt;, as tab or modal).&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;The &lt;strong&gt;admin overview&lt;/strong&gt; (Control panel → LinkEnhancer → Cross-reference overview) is a server-side paginated DataTable. It lists all records that contain classic &lt;code&gt;@XREF@&lt;/code&gt; cross-references or linkenhancer links, with a per-record link inventory.&lt;/p&gt;&#10;&lt;p&gt;Optional filters:&lt;/p&gt;</description></item><item><title>Build System</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/build-system/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/build-system/</guid><description>&lt;p&gt;Bundling, translation compilation and versioning are driven by &lt;a href="https://gulpjs.com/"&gt;&lt;strong&gt;Gulp&lt;/strong&gt;&lt;/a&gt; tasks in &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/gulpfile.mjs" target="_blank" rel="noopener"&gt;&lt;code&gt;gulpfile.mjs&lt;/code&gt;&lt;/a&gt;&#10;, invoked with &lt;code&gt;npx gulp &amp;lt;task&amp;gt;&lt;/code&gt;. The &lt;code&gt;npm run *&lt;/code&gt; scripts cover testing only — see the &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/testing/"&gt;Testing&lt;/a&gt; page.&lt;/p&gt;&#10;&lt;h2 id="tasks"&gt;Tasks&lt;a class="td-heading-self-link" href="#tasks" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Task&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Command&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;What it does&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;build&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;npx gulp build&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;clean&lt;/code&gt; → JS (&lt;a href="https://rollupjs.org/"&gt;Rollup&lt;/a&gt;) + CSS (&lt;a href="https://postcss.org/"&gt;PostCSS&lt;/a&gt;)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;devbuild&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;npx gulp devbuild&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;like &lt;code&gt;build&lt;/code&gt;, but Dev-Mode (no minify, source maps)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;updatepo&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;npx gulp updatepo&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;runs &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/util/update-po-files.sh" target="_blank" rel="noopener"&gt;&lt;code&gt;update-po-files.sh&lt;/code&gt;&lt;/a&gt;&#10; → &lt;code&gt;xgettext&lt;/code&gt; over &lt;code&gt;src/&lt;/code&gt; → &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/resources/lang/messages.pot" target="_blank" rel="noopener"&gt;&lt;code&gt;messages.pot&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;po2php&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;npx gulp po2php&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;compiles &lt;code&gt;resources/lang/*.po&lt;/code&gt; → &lt;code&gt;resources/lang/*.php&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;syncversion&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;npx gulp syncversion&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;keeps &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/latest-version.txt" target="_blank" rel="noopener"&gt;&lt;code&gt;latest-version.txt&lt;/code&gt;&lt;/a&gt;&#10; ↔ &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/src/LinkEnhancerModule.php" target="_blank" rel="noopener"&gt;&lt;code&gt;LinkEnhancerModule.php&lt;/code&gt;&lt;/a&gt;&#10; in sync&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;bumpversion&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;npx gulp bumpversion&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;requires a clean git tree → version bump → &lt;code&gt;syncversion&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;archive&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;npx gulp archive&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;build&lt;/code&gt; + &lt;code&gt;po2php&lt;/code&gt; + &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/util/create-archive.sh" target="_blank" rel="noopener"&gt;&lt;code&gt;create-archive.sh&lt;/code&gt;&lt;/a&gt;&#10; (release ZIP)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;forgejo:issue-refs&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;npx gulp forgejo:issue-refs[:dry]&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;adds issue refs to commit messages (needs &lt;code&gt;FORGEJO_TOKEN&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt;) - was used only once to migrate the repo from GitHub to Codeberg&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="js--css-bundling"&gt;JS / CSS bundling&lt;a class="td-heading-self-link" href="#js--css-bundling" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;build&lt;/code&gt; = &lt;code&gt;clean&lt;/code&gt; → (in parallel) JS + CSS.&lt;/p&gt;</description></item><item><title>Renumber XREFs (with links)</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/enhanced-links/renumber/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/enhanced-links/renumber/</guid><description>&lt;p&gt;The standard webtrees renumbering (Control panel&#10;Manage family trees → [Family tree] → Renumber XREFs) resolves cross-tree XREF collisions but does not update linkenhancer or classic links. This module provides an extended renumber that repairs all affected links in one step.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Access:&lt;/strong&gt; Control panel → LinkEnhancer → Renumber XREFs (with links)&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Workflow:&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Select the tree that has XREF collisions (duplicate XREFs with other trees).&lt;/li&gt;&#10;&lt;li&gt;The page shows each conflicting XREF, its record type, and the number of inbound linkenhancer references from the link index.&lt;/li&gt;&#10;&lt;li&gt;On submit, each conflicting XREF is:&#10;&lt;ul&gt;&#10;&lt;li&gt;renamed in the GEDCOM core tables (same as the standard renumber)&lt;/li&gt;&#10;&lt;li&gt;all &lt;code&gt;wt=@XREF@&lt;/code&gt; / &lt;code&gt;id=@XREF@&lt;/code&gt; links in notes and blocks are updated&lt;/li&gt;&#10;&lt;li&gt;the link index (&lt;code&gt;le_link_index&lt;/code&gt;) and UID index (&lt;code&gt;le_uid_index&lt;/code&gt;) are repaired inline or deferred to cron&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Each renumbered record is logged in the edit log (Admin → Changes log), e.g. &lt;code&gt;Renumbered @I456@ → @I457@ (INDI, 3 core rows, 2 links)&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;If the PHP time limit is reached, the run stops with partial progress — the remaining conflicts are renumbered on the next run.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The standard webtrees renumber (without links) remains available and is unaffected. Use this module&amp;rsquo;s renumber when your notes or blocks contain linkenhancer links.&#10;See also: &lt;a href="https://github.com/fisharebest/webtrees/issues/5156"&gt;fisharebest/webtrees#5156&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Security</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/security/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/security/</guid><description>&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;No shell, ever.&lt;/strong&gt; Commands are executed via &lt;a href="https://www.php.net/manual/en/function.proc-open.php"&gt;&lt;code&gt;proc_open&lt;/code&gt;&lt;/a&gt; with an &lt;a href="https://www.php.net/manual/en/reserved.variables.argv.php"&gt;argv array&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Command whitelist.&lt;/strong&gt; &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/cli-scripts/"&gt;Module scripts&lt;/a&gt; must match &lt;code&gt;modules_v4/&amp;lt;module&amp;gt;/cli/&amp;lt;script&amp;gt;.php&lt;/code&gt;, exist, and resolve inside a &lt;code&gt;modules_v4/&amp;lt;module&amp;gt;/cli/&lt;/code&gt; directory. Core commands are limited to the read-only/export allowlist.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Argument validation.&lt;/strong&gt; Every argument token must be a plain option or value — no shell metacharacters, no spaces inside tokens, max 10 tokens.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Working directories.&lt;/strong&gt; Module scripts run from the webtrees root; core commands run from &lt;code&gt;data/&lt;/code&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;No sensitive data in stdout.&lt;/strong&gt; The &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/tick/"&gt;tick&lt;/a&gt; echoes summary lines only; full output stays in the admin-only run history.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;UI actions&lt;/strong&gt; are all named &lt;code&gt;*Admin*&lt;/code&gt; (admin-only, &lt;a href="https://developer.mozilla.org/en/docs/Web/Security/Attacks/CSRF"&gt;CSRF&lt;/a&gt;-protected).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Kill switch:&lt;/strong&gt; disable the module — the tick then does nothing.&lt;/li&gt;&#10;&lt;li&gt;The tick is single-instance (flock), and a job&amp;rsquo;s child process is hard-killed after its timeout (&lt;a href="https://en.wikipedia.org/wiki/SIGKILL"&gt;&lt;code&gt;SIGKILL&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Event-queue coalescing.&lt;/strong&gt; A flood of identical &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/cronjob/events/"&gt;events&lt;/a&gt; cannot amplify into one job run per queued row.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Webhook token header-only.&lt;/strong&gt; Send the token exclusively in the &lt;code&gt;X-Cronjob-Token&lt;/code&gt; header.&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>Translation Pipeline</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/translation-pipeline/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/translation-pipeline/</guid><description>&lt;p&gt;The module ships its own translations through the standard gettext chain, hosted on &lt;a href="https://translate.codeberg.org/projects/wt-modules/linkenhancer/"&gt;&lt;strong&gt;Codeberg Weblate&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="flow"&gt;Flow&lt;a class="td-heading-self-link" href="#flow" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&#10;&#10;&#10;&lt;pre class="mermaid"&gt;flowchart TB&#10; subgraph Extraction[&amp;#34;1️⃣ Code → POT&amp;#34;]&#10; direction LR&#10; PHP[&amp;#34;📝 src/*.php&amp;#34;] --&amp;gt;|npx gulp updatepo| POT[&amp;#34;📄 messages.pot&amp;#34;]&#10; end&#10;&#10; subgraph Translation[&amp;#34;2️⃣ Translation&amp;#34;]&#10; direction LR&#10; WEBLATE[&amp;#34;🌐 Weblate&amp;#34;] --&amp;gt;|download| PO[&amp;#34;🗝 &amp;lt;lang&amp;gt;.po&amp;#34;]&#10; end&#10;&#10; subgraph Rendering[&amp;#34;3️⃣ Webtrees&amp;#34;]&#10; direction LR&#10; PHPARR[&amp;#34;📦 lang.php&amp;#34;] --&amp;gt;|I18N| RENDER[&amp;#34;✅ Render&amp;#34;]&#10; end&#10;&#10; POT --&amp;gt;|upload| Translation&#10; PO --&amp;gt;|npx gulp po2php| Rendering&#10;&#10; classDef sourceStyle fill:#e3f2fd,stroke:#1565C0,stroke-width:2px&#10; classDef fileStyle fill:#e8f5e9,stroke:#2E7D32,stroke-width:2px&#10; classDef weblateStyle fill:#f3e5f5,stroke:#7B1FA2,stroke-width:2px&#10; classDef renderStyle fill:#fce4ec,stroke:#C2185B,stroke-width:2px&#10;&#10; class PHP sourceStyle&#10; class POT,PO,PHPARR fileStyle&#10; class WEBLATE weblateStyle&#10; class RENDER renderStyle&#10;&lt;/pre&gt;&#10;&lt;h2 id="extracting-strings"&gt;Extracting strings&lt;a class="td-heading-self-link" href="#extracting-strings" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/util/update-po-files.sh" target="_blank" rel="noopener"&gt;&lt;code&gt;update-po-files.sh&lt;/code&gt;&lt;/a&gt;&#10; runs &lt;code&gt;xgettext&lt;/code&gt; over every &lt;code&gt;*.php&lt;/code&gt; / &lt;code&gt;*.phtml&lt;/code&gt; except &lt;code&gt;util/&lt;/code&gt;, &lt;code&gt;vendor/&lt;/code&gt;, &lt;code&gt;node_modules/&lt;/code&gt; and &lt;code&gt;tests/&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>WTHB TOC Language Pipeline</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/wthb-toc-lang/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/wthb-toc-lang/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;a class="td-heading-self-link" href="#overview" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;The &lt;strong&gt;&lt;code&gt;linkenhancer-wthb-toc-lang&lt;/code&gt;&lt;/strong&gt; package delivers the table of contents (TOC) of the German webtrees manual — and its translations — to the &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/wthb/"&gt;WTHB help feature&lt;/a&gt;. Its production is a &lt;strong&gt;round trip&lt;/strong&gt; that lives entirely outside the webtrees install: it reads the manual from &lt;strong&gt;GenWiki&lt;/strong&gt;, regenerates the TOC, writes the result back to GenWiki as a &lt;strong&gt;MediaWiki template&lt;/strong&gt;, and — in parallel — ships the structured data to an &lt;strong&gt;external Codeberg repo&lt;/strong&gt; that feeds &lt;strong&gt;Codeberg Pages&lt;/strong&gt;, &lt;strong&gt;Weblate&lt;/strong&gt; (translations) and, finally, the &lt;strong&gt;module&lt;/strong&gt;. This page documents the &lt;em&gt;production&lt;/em&gt; side; the &lt;em&gt;consumer&lt;/em&gt; side (cache, CLI, rendering) is covered on the &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/wthb/"&gt;WTHB feature page&lt;/a&gt; and the &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/cli-scripts/"&gt;CLI scripts&lt;/a&gt; page.&lt;/p&gt;</description></item><item><title>Data Fixes</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/enhanced-links/data-fixes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/enhanced-links/data-fixes/</guid><description>&lt;p&gt;The module provides a set of data-fix handlers accessible from the webtrees &lt;a href="https://wiki.genealogy.net/Webtrees_Handbuch/Anleitung_f%C3%BCr_Verwalter#Datenkorrekturen"&gt;data-fix&lt;/a&gt; interface (Control panel → Data fixes → LinkEnhancer). Each handler addresses a specific link integrity issue:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Fix&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;What it does&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;XREF ⇄ UID reference swap&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Converts linkenhancer link targets between XREF and UID form. Direction is selectable (XREF→UID or UID→XREF). Requires the UID index to be built.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;Fix &lt;code&gt;wt=&lt;/code&gt; type letter&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Sets, repairs, or removes the legacy type letter in &lt;code&gt;wt=&amp;lt;type&amp;gt;@REF@&lt;/code&gt; links. Modes: &lt;em&gt;set&lt;/em&gt; (add missing type), &lt;em&gt;repair&lt;/em&gt; (fix wrong type), &lt;em&gt;remove&lt;/em&gt; (strip type letter).&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;Report dangling link targets&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;em&gt;Report-only&lt;/em&gt;: lists linkenhancer links whose target record no longer exists. No changes are made.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;Report ambiguous UID targets&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;em&gt;Report-only&lt;/em&gt;: lists links whose UID target resolves to more than one record. No changes are made.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;Report duplicate UIDs in index&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;em&gt;Report-only&lt;/em&gt;: lists UIDs assigned to multiple records in the UID index. No changes are made.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Each fix follows the webtrees data-fix workflow: select fix type → preview → apply. The data-fix page also shows the current status of the link and UID indexes, and whether index updates will be deferred to the cron job.&lt;/p&gt;</description></item><item><title>Testing</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/testing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/testing/</guid><description>&lt;p&gt;The full chain runs with:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm &lt;span style="color:#91d7e3"&gt;test&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;which executes &lt;code&gt;test:php&lt;/code&gt; → &lt;code&gt;analyse:php&lt;/code&gt; → &lt;code&gt;test:js&lt;/code&gt;, each step required to pass before the next runs.&lt;/p&gt;&#10;&lt;h2 id="javascript-mocha--jsdom"&gt;JavaScript (Mocha + jsdom)&lt;a class="td-heading-self-link" href="#javascript-mocha--jsdom" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm run test:js&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e738d;font-style:italic"&gt;# mocha &amp;#39;resources/js/tests/**/*.test.js&amp;#39; --require jsdom-global/register&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="php-standalone-unit-tests"&gt;PHP standalone unit tests&lt;a class="td-heading-self-link" href="#php-standalone-unit-tests" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm run test:php&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e738d;font-style:italic"&gt;# runs every tests/test-*.php with the system PHP — no webtrees install, no DB&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Test&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Covers&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-text-tag-collector.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-text-tag-collector.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;text-tag collector&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-link-classifier.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-link-classifier.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;link classifier (incl. target extraction)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-uid-tag-collector.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-uid-tag-collector.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;UID tag collector&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-uid-index-service.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-uid-index-service.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;UID index service&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-i18n-mark.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-i18n-mark.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;i18n markers&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/tests/test-wthb-toc-service.php" target="_blank" rel="noopener"&gt;&lt;code&gt;test-wthb-toc-service.php&lt;/code&gt;&lt;/a&gt;&#10;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;WTHB TOC service&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="php-static-analysis"&gt;PHP static analysis&lt;a class="td-heading-self-link" href="#php-static-analysis" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm run analyse:php &lt;span style="color:#6e738d;font-style:italic"&gt;# or: composer analyse&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e738d;font-style:italic"&gt;# PHPStan Level 6 — see the PHPStan page&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="smoke-tests-need-webtrees--db"&gt;Smoke tests (need webtrees + DB)&lt;a class="td-heading-self-link" href="#smoke-tests-need-webtrees--db" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;These exercise a real installation and are &lt;strong&gt;not&lt;/strong&gt; part of &lt;code&gt;npm test&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>PHPStan</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/phpstan/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/phpstan/</guid><description>&lt;p&gt;Required: &lt;a href="https://phpstan.org/"&gt;PHPstan&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Static analysis is configured via &lt;code&gt;phpstan.neon&lt;/code&gt; (Level 6, baseline in &lt;code&gt;phpstan-baseline.neon&lt;/code&gt;).&lt;/p&gt;&#10;&lt;h3 id="run"&gt;Run&lt;a class="td-heading-self-link" href="#run" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;composer analyse&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The analysis requires a webtrees installation (for &lt;code&gt;app/&lt;/code&gt; classes + &lt;code&gt;vendor/autoload.php&lt;/code&gt;). Run it from the bind-mounted path:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#91d7e3"&gt;cd&lt;/span&gt; /path/to/webtrees/modules_v4/linkenhancer&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;composer analyse&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="configuration"&gt;Configuration&lt;a class="td-heading-self-link" href="#configuration" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Level&lt;/strong&gt;: 6&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Baseline&lt;/strong&gt;: &lt;code&gt;phpstan-baseline.neon&lt;/code&gt; (known issues)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Bootstrap&lt;/strong&gt;: &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/phpstan-bootstrap.php" target="_blank" rel="noopener"&gt;&lt;code&gt;phpstan-bootstrap.php&lt;/code&gt;&lt;/a&gt;&#10; registers PSR-4 namespaces directly (bypasses the version-gated autoloader in &lt;a href="https://bschwede.codeberg.page/wt-modules/en/docs/wt-shared-libs/"&gt;&lt;code&gt;wt-shared-libs&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Scan directories&lt;/strong&gt;: &lt;code&gt;../../app/&lt;/code&gt; (webtrees core classes)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Exclude paths&lt;/strong&gt;: &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/src/LinkEnhancerModuleExt.php" target="_blank" rel="noopener"&gt;&lt;code&gt;LinkEnhancerModuleExt.php&lt;/code&gt;&lt;/a&gt;&#10; (use in conjunction with &lt;a href="https://github.com/vesta-webtrees-2-custom-modules"&gt;Vesta modules&lt;/a&gt;)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Memory limit&lt;/strong&gt;: 1G&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Parallel&lt;/strong&gt;: disabled (single process, more reliable in CI)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="key-settings"&gt;Key settings&lt;a class="td-heading-self-link" href="#key-settings" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;treatPhpDocTypesAsCertain: false&lt;/code&gt; — prevents false positives from &lt;code&gt;@var&lt;/code&gt; assertions&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;ignoreErrors&lt;/code&gt; for:&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;AccessLevel&lt;/code&gt; Enum (WT 2.3-only class, not available in 2.2 runtime)&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;DB::prefix('')&lt;/code&gt; — intentional (getting prefix without identifier)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="regenerating-the-baseline"&gt;Regenerating the baseline&lt;a class="td-heading-self-link" href="#regenerating-the-baseline" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;After intentional changes that introduce new (acceptable) errors:&lt;/p&gt;</description></item><item><title>UID Lookup</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/uid-lookup/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/uid-lookup/</guid><description>&lt;p&gt;A &lt;strong&gt;UID&lt;/strong&gt; (&lt;code&gt;_UID&lt;/code&gt; in GEDCOM 5.5.1, &lt;code&gt;UID&lt;/code&gt; in GEDCOM 7.0) is a persistent identifier assigned to a record. The UID survives XREF renumbering and tree migrations, making it a stable reference point.&lt;/p&gt;&#10;&lt;p&gt;This feature allows navigating directly to a record by its UID. The URL pattern is:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;/tree/{tree}/goto-uid/{uid}&lt;/code&gt; — scoped to one tree&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;/goto-uid/{uid}&lt;/code&gt; — global (searches all trees)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;Behavior:&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;0 hits&lt;/strong&gt; → 404&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;1 visible hit&lt;/strong&gt; → redirect to the record&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;gt;1 visible hit&lt;/strong&gt; → selection list (case-variant UIDs or the same UID in multiple records)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;UID comparison is &lt;strong&gt;case-insensitive&lt;/strong&gt; (GEDCOM specs do not define UID as case-sensitive). The stored value is verbatim; matching is done case-insensitively so that case-variant UIDs return a selection list rather than a possibly-wrong single redirect.&lt;/p&gt;</description></item><item><title>Patches</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/patches/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/features/patches/</guid><description>&lt;p&gt;&lt;strong&gt;The patches can be applied additionally — the module also works without them!&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;The &lt;code&gt;util&lt;/code&gt; subfolder contains minor patches for the webtrees core. These are &lt;a href="https://www.gnu.org/software/diffutils/"&gt;diff-files&lt;/a&gt; that can be easily applied or removed using shell from within the installed module folder (necessary for auto-detecting webtrees sources):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Usage: util/wt-patch.sh &lt;span style="color:#91d7e3;font-weight:bold"&gt;[&lt;/span&gt;-R&lt;span style="color:#91d7e3;font-weight:bold"&gt;]&lt;/span&gt; &lt;span style="color:#91d7e3;font-weight:bold"&gt;[&lt;/span&gt;FILTER&lt;span style="color:#91d7e3;font-weight:bold"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -R undo Patch &lt;span style="color:#91d7e3;font-weight:bold"&gt;(&lt;/span&gt;patch -R&lt;span style="color:#91d7e3;font-weight:bold"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FILTER &lt;span style="color:#a6da95"&gt;&amp;#39;*&amp;#39;&lt;/span&gt; &lt;span style="color:#c6a0f6"&gt;for&lt;/span&gt; all or specific &lt;span style="color:#91d7e3;font-weight:bold"&gt;(&lt;/span&gt;e.g. &lt;span style="color:#a6da95"&gt;&amp;#39;01&amp;#39;&lt;/span&gt;&lt;span style="color:#91d7e3;font-weight:bold"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Don&amp;rsquo;t forget to reapply the patches after updating webtrees.&lt;/p&gt;&#10;&lt;p&gt;These are minor bug fixes or functional enhancements — usually in a single file — that are intended to bridge the gap until they are officially fixed/implemented in the webtrees core.&lt;/p&gt;</description></item><item><title>Releasing</title><link>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/release/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/docs/linkenhancer/dev/release/</guid><description>&lt;h2 id="steps"&gt;Steps&lt;a class="td-heading-self-link" href="#steps" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#cad3f5;background-color:#24273a;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e738d;font-style:italic"&gt;# 1. Bump the version (requires a clean git tree)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npx gulp bumpversion&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e738d;font-style:italic"&gt;# 2. Build + compile translations + create the release archive&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npx gulp archive&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="what-archive-does"&gt;What &lt;code&gt;archive&lt;/code&gt; does&lt;a class="td-heading-self-link" href="#what-archive-does" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;npx gulp archive&lt;/code&gt; runs &lt;code&gt;build&lt;/code&gt; → &lt;code&gt;po2php&lt;/code&gt; → &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/util/create-archive.sh" target="_blank" rel="noopener"&gt;&lt;code&gt;create-archive.sh&lt;/code&gt;&lt;/a&gt;&#10;. The script:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Reads the version from &lt;a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/latest-version.txt" target="_blank" rel="noopener"&gt;&lt;code&gt;latest-version.txt&lt;/code&gt;&lt;/a&gt;&#10;.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;rsync&lt;/code&gt;s the module into &lt;code&gt;dist/linkenhancer/&lt;/code&gt;, excluding everything that is not shipped (below).&lt;/li&gt;&#10;&lt;li&gt;Zips it to &lt;code&gt;dist/linkenhancer_v&amp;lt;version&amp;gt;.zip&lt;/code&gt;.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="excluded-from-the-archive"&gt;Excluded from the archive&lt;a class="td-heading-self-link" href="#excluded-from-the-archive" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;&#10;&lt;p&gt;&lt;code&gt;create-archive.sh&lt;/code&gt; drops, among others: &lt;code&gt;node_modules/&lt;/code&gt;, &lt;code&gt;.git*&lt;/code&gt;, &lt;code&gt;gulpfile.mjs&lt;/code&gt;, &lt;code&gt;package*.json&lt;/code&gt;, &lt;code&gt;phpstan*&lt;/code&gt;, &lt;code&gt;tests/&lt;/code&gt;, the &lt;code&gt;util/*&lt;/code&gt; build scripts, &lt;code&gt;resources/js/src&lt;/code&gt;, &lt;code&gt;resources/js/tests&lt;/code&gt;, &lt;code&gt;resources/img/&lt;/code&gt;, &lt;code&gt;resources/lang/*.po&lt;/code&gt; / &lt;code&gt;*.mo&lt;/code&gt;, and &lt;code&gt;.env&lt;/code&gt;. Only the built &lt;code&gt;bundle*&lt;/code&gt; JS/CSS, the compiled &lt;code&gt;resources/lang/*.php&lt;/code&gt;, &lt;code&gt;src/&lt;/code&gt;, &lt;code&gt;resources/views/&lt;/code&gt;, &lt;code&gt;resources/css/&lt;/code&gt; and the module entry files are shipped.&lt;/p&gt;</description></item><item><title>Search Results</title><link>https://bschwede.codeberg.page/wt-modules/en/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/search/</guid><description/></item><item><title>Website information</title><link>https://bschwede.codeberg.page/wt-modules/en/site/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://bschwede.codeberg.page/wt-modules/en/site/</guid><description>&lt;p&gt;Site built with &lt;a href="https://github.com/docsy/docsy/releases/v0.18.0"&gt;&lt;span class="badge bg-primary text-bg-primary fs-6"&gt;Docsy&#10;v0.18.0 &lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="build-information"&gt;Build information&lt;a class="td-heading-self-link" href="#build-information" aria-label="Heading self-link"&gt;&lt;/a&gt;&lt;/h2&gt;</description></item></channel></rss>