Appearance
Link Checker
Checks every link in the posts for 404s, dead hosts and redirects, as Broken Link Checker does.
kitloom/link-checks · admins: kitloom/link-checks-filament, kitloom/link-checks-nova
Broken Link Checker for a kitloom site: every link in the posts is asked what it answers — on a schedule, and as soon as a post is saved — and the ones that fail are listed with the posts that carry them. Links come from kitloom/internal-links, which already finds and counts them; this module only follows them.
The admin is kitloom/link-checks-filament or kitloom/link-checks-nova: the Link Checker section with Broken Links, All Links, Settings and this page.
What is checked, and how
Which links. Every
<a href>of every counted post type (see kitloom/internal-links: public types, drafts included, trashed posts not), as the counter keeps them.mailto:,tel:,javascript:and#anchorsare not addresses to ask. Addresses containing a text from Never check addresses containing are left out.The site's own addresses (
/about/, the site's host) are asked the site itself, without a request: the page the main query shows there. One the site does not know goes out over HTTP like the others — a redirect rule may still answer it.Over HTTP. HEAD first, GET when the server minds HEAD. Redirects are followed by hand, so the destination is written down.
Public hosts only. Anyone who writes a post chooses its links, so a host that resolves to a private or reserved address — the server itself, the office network, a cloud's metadata service — gets a warning instead of a request, at every redirect too; each request goes to the address that was checked. A site whose links lead into its own intranet turns on
allow_private_hosts.What the answers mean.
Status When OK 2xx Redirect 3xx that ends on a page; the destination is shown Broken 404, 410, a host that does not exist — at once; a warning repeated Broken after times in a row Warning a timeout, a 5xx, a 403 or 429 (a bot wall, a server having a bad hour), a redirect that never ends, a host that is not public When. New links are checked as soon as the post is saved. A working link is asked again every Check each link every hours (72 by default); a warning after an hour, a broken link after a day.
php artisan link-checks:checkruns from the schedule every hour and picks up what is due;--allasks every address again.Dismiss says "I know": the link leaves Broken Links and comes back when its answer changes.
Replace URL and Unlink fix the posts themselves: every post that carries the address is rewritten — the new address where the old one was written, or the
<a>dropped and its text kept — and saved as you, so the links are counted again and the new address is checked like a new link. The old address leaves the table with its last post.The digest. With Email a daily digest of newly broken links on (Settings),
php artisan link-checks:digestruns from the schedule once a day and emails the links that broke since the last digest — each with its answer and the posts that carry it — to the administration email address (Settings → General). A link is told about once per spell of being broken; dismissed ones are left out.
In the admin
- Broken Links — what needs a hand: broken or warned about, not dismissed. Each row: the address, the status, the HTTP code, how many posts carry it, since when it fails. Actions on a row or the selection: Check again, Dismiss, Undo dismiss, Unlink; on a row, Replace URL asks for the new address (a redirect's destination is offered). Check now picks up new links and checks what is due; Check all links asks every address again.
- A link's page — its last answer in full and the posts that carry it, each opening the post's form, where one link is fixed by hand; the same actions fix it in every post at once.
- All Links — every checked address with its status; filters by status and by dismissal.
- Settings — how often links are checked again, the timeout, when a warning becomes broken, the exclusions, and whether the daily digest is emailed.
Where things are kept
| What | Where |
|---|---|
| Checked addresses | table link_checks (the site's table prefix in front), one row per distinct address as written in the posts; model Kitloom\LinkChecks\Models\LinkCheck, joined to rank_math_internal_links on url |
| Settings | options link_checks_interval_hours, link_checks_timeout, link_checks_broken_after, link_checks_exclude, link_checks_digest (off by default) |
| Configuration | config/kitloom-link-checks.php |
Configuration
config/kitloom-link-checks.php of the package, merged into the application (php artisan vendor:publish --tag=kitloom-link-checks-config).
| Key | Environment | Default | What |
|---|---|---|---|
schedule | KITLOOM_LINK_CHECKS_SCHEDULE | true | link-checks:check every hour and link-checks:digest every day from the application's schedule |
queue | KITLOOM_LINK_CHECKS_QUEUE | — | The queue the checks run on; the default one when empty, inline on the sync driver |
batch | — | 25 | Links a queue job takes |
user_agent | KITLOOM_LINK_CHECKS_USER_AGENT | a Mozilla-compatible kitloom string | How the checker introduces itself |
max_redirects | — | 10 | Redirects followed before an address is a warning |
allow_private_hosts | KITLOOM_LINK_CHECKS_ALLOW_PRIVATE_HOSTS | false | Whether hosts on private and reserved addresses are asked too |
recheck.warning, recheck.broken | — | 60, 1440 | Minutes until a warned-about and a broken link are asked again |
filament.register, nova.register | — | true | Whether the admin section is added |
ai.register | — | true | Whether an agent gets the module's tools (see below) |
For developers
php
use Kitloom\LinkChecks\LinkChecks;
$checks = app(LinkChecks::class);
$checks->problems()->get(); // broken or warned about, not dismissed
$checks->checkNow($user); // new links picked up, due ones checked
$checks->recheck([$linkId], $user); // chosen links now
$checks->dismiss([$linkId], $user);
$checks->replace($linkId, 'https://example.org/new', $user); // in every post that carries it
$checks->unlink($linkId, $user); // the <a> dropped, its text kept
app(Kitloom\LinkChecks\Digest::class)->send(); // the daily email, when the setting is onEverything takes the acting user and asks the manage_link_checks permission; without one it acts as the system. Replacing and unlinking save each post through Kitloom\WpSchema\Content\PostWriter as that user, who must be allowed to edit it. Kitloom\LinkChecks\LinkChecker::probe($url) asks one address and returns the outcome without writing anything; Kitloom\LinkChecks\LinkRewriter rewrites a text without touching a post.
Tools for AI agents
With kitloom/ai installed, an agent whose user has manage_link_checks gets these tools (ai.register turns them off):
| Tool | What |
|---|---|
link_checks_list | The broken links (only: broken, the default) or every checked address (only: all), filtered by status or text in the address, paged; each with the posts that carry it |
link_checks_recheck | The given links checked again now |
link_checks_dismiss | The given links dismissed — undo: true brings them back |
link_checks_replace | The address replaced by url in every post that carries it, or its links dropped with unlink: true |
The last three write; a read-only key does not get them.
The model: ModelRegistry::use(LinkCheck::KEY, App\Models\LinkCheck::class) in a provider of the application swaps it; the admins read the class from the registry.
Not yet
Images and other files, links in menus, widgets, comments and custom fields, a link to the admin in the digest, and the import of Broken Link Checker's own tables (blc_links) from a moved site.
Tests
From the application that installs the package: php vendor/bin/phpunit --testsuite LinkChecks.