Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/aarkaynegi/link-guardian/archive/refs/heads/main.zipKeep your internal links healthy. Change a slug and Link Guardian creates the 301 redirect and rewrites the old links inside your content — automatically. With multi-hop loop protection and a one-call REST audit.
A WordPress plugin built as a portfolio piece to solve a real, recurring gap: existing redirect plugins catch the old URL after a slug change but leave the broken links sitting inside your other posts. Link Guardian fixes both halves.
Why it exists
When you rename a post/page slug, three things should happen — but most tools only do the first:
- Redirect the old URL → new URL (a 301).
- Rewrite every internal link that pointed at the old URL so readers and crawlers get a clean link, not a redirect hop.
- Stay loop-free — never create
A → B → Aor longA → B → C → Achains.
Link Guardian does all three.
Features
| Feature | What it does |
|---|---|
| Auto-redirect on slug change | Hooks post_updated, detects slug/parent changes on published content, writes a 301 (configurable). |
| Auto internal-link rewrite | Finds the old URL across all content and replaces it with the new URL (absolute + root-relative forms), offloaded to a background cron job in batches so saves stay fast. |
| Redirect manager | List / add / edit / pause / delete redirects, with hit counters, search, and auto/manual + match-type badges. |
| Pattern redirects | Wildcard (/old/* → /new/*) and regex ($1/$2 capture refs), with per-pattern exceptions ("match all except…"). ReDoS-guarded (bounded backtrack/recursion limits, compile validation) and open-redirect-safe (a capture can never set the redirect host). |
| Broken-link scanner | AJAX, batched scan of all published content for internal links that 404. One-click "create redirect" for any hit. |
| Loop & chain protection | Refuses loop-forming rules at save time (any cycle length); collapses multi-hop chains to a single hop at serve time with a visited-set + hop ceiling. |
| REST audit API | GET /audit returns every loop, chain, connected link, and dead-end in one request. |
Architecture
link-guardian.php Bootstrap: constants, autoloads, activation hooks
includes/
class-link-guardian.php Orchestrator — wires the components together
class-link-guardian-activator.php dbDelta table creation + default settings
class-link-guardian-redirects.php Redirect store, path normalisation, chain
resolution, loop detection, serve-time handler
class-link-guardian-slug-watcher.php Detects slug changes → redirect + link rewrite
class-link-guardian-scanner.php Broken internal-link scanner (AJAX, batched, cached)
class-link-guardian-rest.php REST API: /redirects, /audit, /trace
class-link-guardian-admin.php Admin menu, list UI, scanner UI, audit UI, settings
assets/
admin.css admin.js
uninstall.php Drops the table + options on delete
The chain logic lives in one place — Link_Guardian_Redirects::walk_chain() — and is reused by the front-end handler, the audit API, and the single-URL tracer, so loop behaviour is consistent everywhere.
Loop protection in detail
- Save time —
would_create_cycle( $source, $target )walks forward from the proposed target through existing active rules. If it returns to$source(or exceedsMAX_HOPS), the rule is rejected. This catchesA → A,A → B → A,A → B → C → A, and longer cycles. - Serve time —
walk_chain()follows the chain with avisitedset and a hard hop ceiling. A cycle (e.g. one that arrived via import) is detected and the redirect is aborted rather than looping the browser. Otherwise the chain is collapsed so the visitor makes a single hop straight to the terminal destination (better for SEO than 301 chains). - Rename-back — when a slug returns to a previous value, any stale rule whose source is now a live URL is deleted, so the live page is never redirected away from itself.
REST API
All routes require the manage_options capability and a REST nonce.
GET /wp-json/link-guardian/v1/redirects # every rule
GET /wp-json/link-guardian/v1/audit # loops, chains, connected links, dead-ends
GET /wp-json/link-guardian/v1/trace?url=/x # follow one URL through every hop
Example audit response (abridged):
{
"summary": { "total": 7, "active": 6, "loops": 1, "chains": 2, "connected": 3 },
"loops": [ ["/a", "/b", "/a"] ],
"chains": [ { "source": "/a", "path": ["/a", "/b", "/c"], "terminal": "https://site/c", "hops": 2 } ],
"connected": [ { "source": "/a", "target": "/b" } ],
"broken_dest": []
}
Hooks for developers
link_guardian_before_slug_change( $post_id, $old_url, $new_url ) — actionlink_guardian_links_rewritten( $post_id, $rewritten_count ) — actionlink_guardian_can_rewrite_links( $can, $post_id ) — filter, gate cross-content rewritinglink_guardian_scannable_post_types( $types ) — filterlink_guardian_max_links_per_post( $max ) — filter, scannerlink_guardian_probe_budget( $budget ) — filter, scanner HTTP probes per batch
Requirements
WordPress 6.2+ · PHP 7.4+
License
GPL-2.0-or-later