{"id":293106,"date":"2026-04-21T14:52:39","date_gmt":"2026-04-21T14:52:39","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/smart-link-checker\/"},"modified":"2026-09-09T17:34:10","modified_gmt":"2026-09-09T17:34:10","slug":"muri-link-tracker","status":"publish","type":"plugin","link":"https:\/\/gd.wordpress.org\/plugins\/muri-link-tracker\/","author":18065108,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.3.0","stable_tag":"2.3.0","tested":"7.0.5","requires":"6.9","requires_php":"8.2","requires_plugins":null,"header_name":"Muri Link Tracker","header_author":"Nicolas Diolez","header_description":"A high-performance link checker for WordPress with affiliate detection, redirect tracking, and background processing.","assets_banners_color":"18222a","last_updated":"2026-09-09 17:34:10","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/github.com\/nicolasdiolez\/muri-link-tracker","header_author_uri":"https:\/\/nicolasdiolez.com","rating":0,"author_block_rating":0,"active_installs":0,"downloads":438,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"muri3","date":"2026-04-21 14:52:52","revision":3511977},"1.1.0":{"tag":"1.1.0","author":"muri3","date":"2026-05-21 18:27:14","revision":3542566},"2.0.0":{"tag":"2.0.0","author":"muri3","date":"2026-06-16 15:56:57","revision":3574739},"2.1.0":{"tag":"2.1.0","author":"muri3","date":"2026-07-15 14:34:36","revision":3609030},"2.3.0":{"tag":"2.3.0","author":"muri3","date":"2026-09-09 17:34:10","revision":3688765}},"upgrade_notice":{"2.3.0":"<p>Preserves article markup and rel values, fixes background completion and HTTP limits, and adds URL-scoped page evidence. External checks require PHP cURL; automated content edits require InnoDB. Run a Full Scan after upgrading to reconcile the existing inventory.<\/p>","2.2.0":"<p>Adds destination-health detection: flags links whose domain died or changed hands, with the evidence shown so you can judge. Everything runs locally \u2014 no third-party service. Adds a database table; the upgrade applies itself on the next admin page load.<\/p>","2.1.0":"<p>Adds bundled French, Spanish, German and Italian translations for the full admin interface (PHP + React). No functional or database changes \u2014 safe drop-in update from 2.0.0.<\/p>","2.0.0":"<p>Major release: dedicated Affiliates tab, bulk add rel=&quot;sponsored&quot;, FTC disclosure alerts, open-in-new-tab control, multilingual support (Polylang\/WPML), rebuilt export (CSV\/JSON, columns, date range, AI preset), new Errors and Timeout filters. Also hardens redirect\/SSRF handling, loads translations correctly, and scales link checking for very large sites. Existing integrations remain compatible.<\/p>","1.1.0":"<p>WordPress 7.0 compatibility, security hardening (DNS rebinding, REST validation, CSV rate-limit), ~90% I\/O reduction on bulk inserts, auto-applied composite index, accessibility improvements, and admin palette token support.<\/p>","1.0.0":"<p>Initial release of Muri Link Tracker.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3542565,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3511976,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3511976,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3542565,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.0","1.1.0","2.0.0","2.1.0","2.3.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3511976,"resolution":"1","location":"assets","locale":"","width":1440,"height":900},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3511976,"resolution":"2","location":"assets","locale":"","width":1440,"height":900},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3511976,"resolution":"3","location":"assets","locale":"","width":1440,"height":900},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3511976,"resolution":"4","location":"assets","locale":"","width":1440,"height":900},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3511976,"resolution":"5","location":"assets","locale":"","width":1440,"height":900},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3511976,"resolution":"6","location":"assets","locale":"","width":1440,"height":900},"screenshot-7.png":{"filename":"screenshot-7.png","revision":3511976,"resolution":"7","location":"assets","locale":"","width":1440,"height":900}},"screenshots":{"1":"<strong>Complete SEO Overview<\/strong> \u2014 After a scan, the Dashboard shows the full picture at a glance: total links, OK\/redirects\/broken\/errors counts, internal vs external vs affiliate vs cloaked breakdown, and a per-network affiliate tally (Amazon, CJ, Awin\u2026).","2":"<strong>Start in Seconds<\/strong> \u2014 A clean Dashboard greets you with three clear actions: Full Scan, Delta Scan, or Reset Data. Delta Scan saves time on large sites by only checking posts modified since your last scan.","3":"<strong>Focus on What's Broken<\/strong> \u2014 The Broken filter surfaces every dead link (404, 410, DNS errors) in one view, ready for bulk fix or deletion. Silent editing keeps your post modified dates untouched.","4":"<strong>Track Every Redirect<\/strong> \u2014 The Redirects filter lists every link that forwards through one or more hops, so you can update them to their final destinations and preserve link equity.","5":"<strong>Every Link at a Glance<\/strong> \u2014 The link inventory mixes OK, broken, redirected, and errored URLs in a single sortable table, color-coded by status for instant triage.","6":"<strong>Real-Time Scan Progress<\/strong> \u2014 Watch scans unfold live: progress bar, item counters, and summary stats update in real time. Cancel at any moment \u2014 partial results are preserved and the scan can be resumed later.","7":"<strong>Fine-Tune to Your Hosting<\/strong> \u2014 The Settings panel lets you define which post types to scan, adjust batch size, set HTTP request timeouts and delays, and configure the automatic recheck interval to match any hosting environment."}},"plugin_section":[],"plugin_tags":[5362,14279,14280,260720,186],"plugin_category":[55],"plugin_contributors":[260721],"plugin_business_model":[],"class_list":["post-293106","plugin","type-plugin","status-publish","hentry","plugin_tags-affiliate-links","plugin_tags-broken-links","plugin_tags-link-checker","plugin_tags-redirect-checker","plugin_tags-seo","plugin_category-seo-and-marketing","plugin_contributors-muri3","plugin_committers-muri3"],"banners":{"banner":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/banner-772x250.png?rev=3542565","banner_2x":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/banner-1544x500.png?rev=3511976","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/icon-128x128.png?rev=3542565","icon_2x":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/icon-256x256.png?rev=3511976","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/screenshot-1.png?rev=3511976","caption":"<strong>Complete SEO Overview<\/strong> \u2014 After a scan, the Dashboard shows the full picture at a glance: total links, OK\/redirects\/broken\/errors counts, internal vs external vs affiliate vs cloaked breakdown, and a per-network affiliate tally (Amazon, CJ, Awin\u2026)."},{"src":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/screenshot-2.png?rev=3511976","caption":"<strong>Start in Seconds<\/strong> \u2014 A clean Dashboard greets you with three clear actions: Full Scan, Delta Scan, or Reset Data. Delta Scan saves time on large sites by only checking posts modified since your last scan."},{"src":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/screenshot-3.png?rev=3511976","caption":"<strong>Focus on What's Broken<\/strong> \u2014 The Broken filter surfaces every dead link (404, 410, DNS errors) in one view, ready for bulk fix or deletion. Silent editing keeps your post modified dates untouched."},{"src":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/screenshot-4.png?rev=3511976","caption":"<strong>Track Every Redirect<\/strong> \u2014 The Redirects filter lists every link that forwards through one or more hops, so you can update them to their final destinations and preserve link equity."},{"src":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/screenshot-5.png?rev=3511976","caption":"<strong>Every Link at a Glance<\/strong> \u2014 The link inventory mixes OK, broken, redirected, and errored URLs in a single sortable table, color-coded by status for instant triage."},{"src":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/screenshot-6.png?rev=3511976","caption":"<strong>Real-Time Scan Progress<\/strong> \u2014 Watch scans unfold live: progress bar, item counters, and summary stats update in real time. Cancel at any moment \u2014 partial results are preserved and the scan can be resumed later."},{"src":"https:\/\/ps.w.org\/muri-link-tracker\/assets\/screenshot-7.png?rev=3511976","caption":"<strong>Fine-Tune to Your Hosting<\/strong> \u2014 The Settings panel lets you define which post types to scan, adjust batch size, set HTTP request timeouts and delays, and configure the automatic recheck interval to match any hosting environment."}],"raw_content":"<!--section=description-->\n<p><strong>Muri Link Tracker<\/strong> is a powerful WordPress plugin designed to help site owners maintain their SEO health and user experience by identifying broken links, analyzing redirect chains, and detecting affiliate patterns \u2014 with background jobs and configurable resource limits.<\/p>\n\n<p>Muri Link Tracker scans links in the background and provides a dashboard for broken URLs, redirect loops and missing rel attributes. Throughput depends on your server, the size of the inventory and destination response times.<\/p>\n\n<h4>Key Features<\/h4>\n\n<ul>\n<li><strong>Background Processing<\/strong> \u2014 Scans run silently via Action Scheduler, so your admin stays responsive and your front-end stays fast.<\/li>\n<li><strong>Three Scan Modes<\/strong> \u2014 Launch a <strong>Full Scan<\/strong> of all content, a <strong>Delta Scan<\/strong> (only posts modified since the last scan) to save resources, or a <strong>Reset Data<\/strong> to wipe and start fresh.<\/li>\n<li><strong>Live Progress Tracking<\/strong> \u2014 Watch the scan progress in real time with a progress bar, counters, and the ability to cancel mid-scan.<\/li>\n<li><strong>Affiliate Link Detection<\/strong> \u2014 Automatically identifies affiliate links across 14+ networks (Amazon, Awin, CJ, ShareASale, Impact, Rakuten, ClickBank, PartnerStack, and more), including URL patterns commonly used for internal affiliate redirects.<\/li>\n<li><strong>Redirect Chain Analysis<\/strong> \u2014 Classifies every redirect as a <strong>single hop<\/strong>, a <strong>chain of 2+ hops<\/strong>, or a <strong>loop<\/strong>, so you know exactly what to fix first.<\/li>\n<li><strong>Destination Health<\/strong> \u2014 Finds the links that still return 200 but no longer lead anywhere good: a domain that expired, one that changed hands and now serves casino or spam content, or a URL that quietly landed on a parking page. Shows you what it observed \u2014 the domain it lands on now, the page title it read, the anchor it was compared against \u2014 and leaves the decision to you. Computed entirely on your own server.<\/li>\n<li><strong>Affiliate Networks Breakdown<\/strong> \u2014 See at a glance how many links belong to each affiliate program (Amazon, CJ, Awin\u2026).<\/li>\n<li><strong>Modern React Dashboard<\/strong> \u2014 Real-time statistics, quick filter tabs (All \/ Broken \/ Redirects \/ OK \/ Pending), inline editing, and bulk actions in a polished admin interface built with the WordPress design system.<\/li>\n<li><strong>Smart Filtering &amp; Search<\/strong> \u2014 Filter by status, link type, or affiliate network, and search across URLs and anchor text instantly.<\/li>\n<li><strong>CSV Export<\/strong> \u2014 Export your full link inventory with active filters applied, perfect for reporting and auditing.<\/li>\n<li><strong>Silent Link Editing<\/strong> \u2014 Fix or remove a link directly from the dashboard without bumping the post's modified date, with native revisions where enabled.<\/li>\n<li><strong>Configurable Resource Limits<\/strong> \u2014 Adjustable batch sizes, timeouts, request delays, and recheck intervals to fit shared hosting or dedicated servers.<\/li>\n<li><strong>Scan Resume<\/strong> \u2014 Interrupted scans pick up exactly where they left off.<\/li>\n<li><strong>Gutenberg-Aware<\/strong> \u2014 Extracts links from Gutenberg blocks with block-level metadata, including button blocks and navigation links.<\/li>\n<\/ul>\n\n<h4>What It Detects<\/h4>\n\n<ul>\n<li>Broken links (4xx and 5xx HTTP status codes)<\/li>\n<li>Redirect chains and loops (single hop, multi-hop, infinite loops)<\/li>\n<li>Affiliate links via domain patterns, URL paths, query parameters, and <code>rel=\"sponsored\"<\/code><\/li>\n<li>Cloaked affiliate links (internal URLs that redirect to external affiliates)<\/li>\n<li>Missing rel attributes (nofollow, sponsored, ugc)<\/li>\n<li>Dead domains that no longer resolve, told apart from mere timeouts<\/li>\n<li>Destinations that changed hands: cross-domain redirects, parking and for-sale pages, pages whose identity changed since you linked them<\/li>\n<li>Timeout and SSL\/connection errors<\/li>\n<li>Both internal and external links, categorized automatically<\/li>\n<\/ul>\n\n<h4>Privacy<\/h4>\n\n<p>Muri Link Tracker makes outbound HTTP requests to verify the status of links found in your content, and \u2014 when destination health is enabled \u2014 to read the title of the pages those links lead to. These requests use a clearly identified User-Agent string of the form <code>Mozilla\/5.0 (compatible; MuriLinkTracker\/2.2.0; +https:\/\/your-site-url)<\/code>, which includes your own site's address so the remote servers being checked can identify the source of the request.<\/p>\n\n<p><strong>No third-party service is involved at any point.<\/strong> The plugin has no account, no API key and no cloud component: every check and every verdict is computed on your own server, and the only hosts contacted are the destinations your own content already links to. No personal user data is collected, stored, or transmitted to third parties.<\/p>\n\n<h4>Development<\/h4>\n\n<p>The release includes the matching React\/JavaScript sources in <code>admin\/src\/<\/code>, along with <code>package.json<\/code> and <code>package-lock.json<\/code>. To rebuild the interface, run <code>npm ci<\/code> and <code>npm run build<\/code> from the plugin directory. Project development is also hosted on GitHub:\n<a href=\"https:\/\/github.com\/nicolasdiolez\/muri-link-tracker\">https:\/\/github.com\/nicolasdiolez\/muri-link-tracker<\/a><\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>muri-link-tracker<\/code> folder to the <code>\/wp-content\/plugins\/<\/code> directory.<\/li>\n<li>Activate the plugin through the 'Plugins' menu in WordPress.<\/li>\n<li>Access the dashboard via <strong>Muri Link Tracker<\/strong> in the admin sidebar.<\/li>\n<li>Open the <strong>Settings<\/strong> tab to choose which post types to scan and tune timeouts to your hosting.<\/li>\n<li>Click <strong>Full Scan<\/strong> from the Dashboard to analyze all your published content.<\/li>\n<\/ol>\n\n<h4>Requirements<\/h4>\n\n<ul>\n<li>WordPress 6.9 or higher<\/li>\n<li>PHP 8.2 or higher<\/li>\n<\/ul>\n\n<!--section=faq-->\n<dl>\n<dt id=\"how%20does%20the%20scanning%20work%3F\"><h3>How does the scanning work?<\/h3><\/dt>\n<dd><p>Muri Link Tracker uses WordPress Action Scheduler to process scans in the background. Posts are scanned in configurable batches, and each link is verified via HTTP HEAD request with GET fallback. Background processing requires a working Action Scheduler runner or cron. External checks require PHP cURL; content changes require InnoDB tables.<\/p><\/dd>\n<dt id=\"what%27s%20the%20difference%20between%20full%20scan%20and%20delta%20scan%3F\"><h3>What's the difference between Full Scan and Delta Scan?<\/h3><\/dt>\n<dd><p><strong>Full Scan<\/strong> re-indexes every link on your site and re-checks each URL's HTTP status. <strong>Delta Scan<\/strong> only processes posts modified since the last successful scan \u2014 ideal for recurring maintenance on large sites. Use Full Scan after major content migrations, Delta Scan for weekly check-ups.<\/p><\/dd>\n<dt id=\"does%20it%20slow%20down%20my%20site%3F\"><h3>Does it slow down my site?<\/h3><\/dt>\n<dd><p>Background work still consumes CPU, memory and database resources. Configurable batch sizes, timeouts and delays help control load. Jobs yield between groups of work; a slow DNS lookup or redirect chain can exceed the nominal job time budget.<\/p><\/dd>\n<dt id=\"what%20affiliate%20networks%20does%20it%20detect%3F\"><h3>What affiliate networks does it detect?<\/h3><\/dt>\n<dd><p>Amazon, ShareASale, Commission Junction (CJ), Impact, Rakuten, ClickBank, Awin, Tradedoubler, Webgains, Commission Factory, FlexOffers, Skimlinks, Sovrn\/VigLink, and PartnerStack. You can extend detection patterns via the <code>mltr\/classifier\/affiliate_patterns<\/code> filter.<\/p><\/dd>\n<dt id=\"can%20i%20detect%20cloaked%20affiliate%20links%3F\"><h3>Can I detect cloaked affiliate links?<\/h3><\/dt>\n<dd><p>URL patterns such as <code>\/go\/product-name<\/code> identify possible affiliate links. Internal URLs are resolved locally where possible. An unresolved internal redirect is shown as unverified; pattern detection alone does not prove its destination.<\/p><\/dd>\n<dt id=\"can%20i%20scan%20only%20specific%20post%20types%3F\"><h3>Can I scan only specific post types?<\/h3><\/dt>\n<dd><p>Yes. In Settings, enter a comma-separated list of post types (e.g., <code>post, page, product<\/code>) to include in scans. Any custom post type is supported.<\/p><\/dd>\n<dt id=\"what%20happens%20when%20i%20delete%20or%20edit%20a%20link%3F\"><h3>What happens when I delete or edit a link?<\/h3><\/dt>\n<dd><p>When you edit or delete a link through the plugin, it updates the <code>&lt;a&gt;<\/code> tag directly in the post content. The post's modified date is preserved. A native revision is saved where WordPress revisions are enabled. Concurrent edits are rejected, and the content and link index are updated transactionally. Excerpts, custom fields, block attributes and relative source hrefs currently require editing in WordPress followed by a rescan; the plugin refuses unsupported automated edits. Remember to purge your page cache after bulk edits if you use a caching plugin.<\/p><\/dd>\n<dt id=\"can%20i%20cancel%20a%20scan%20that%27s%20already%20running%3F\"><h3>Can I cancel a scan that's already running?<\/h3><\/dt>\n<dd><p>Yes. A <strong>Cancel Scan<\/strong> button appears next to the progress bar during any active scan. Partial results are preserved. If a worker is currently writing, retry between batches.<\/p><\/dd>\n<dt id=\"can%20i%20resume%20an%20interrupted%20scan%3F\"><h3>Can I resume an interrupted scan?<\/h3><\/dt>\n<dd><p>Yes. If a scan is cancelled, crashes, or is interrupted by a server restart, you can resume it from the dashboard. The plugin tracks scan progress and picks up right where it left off.<\/p><\/dd>\n<dt id=\"how%20often%20are%20links%20re-checked%3F\"><h3>How often are links re-checked?<\/h3><\/dt>\n<dd><p>You can configure a <strong>Recheck Interval<\/strong> (in days) from the Settings panel. Default is 7 days. A daily background job schedules stale links when Action Scheduler runs. A Full or Delta scan also checks the selected inventory.<\/p><\/dd>\n<dt id=\"what%20languages%20is%20the%20plugin%20available%20in%3F\"><h3>What languages is the plugin available in?<\/h3><\/dt>\n<dd><p>Muri Link Tracker ships with interface translations for <strong>French (fr_FR)<\/strong>, <strong>Spanish (es_ES)<\/strong>, <strong>German (de_DE)<\/strong> and <strong>Italian (it_IT)<\/strong> \u2014 both the PHP side and the React admin app. The interface (and its notices) automatically follows your site language under <strong>Settings \u2192 General \u2192 Site Language<\/strong>.<\/p><\/dd>\n<dt id=\"is%20the%20plugin%20translation-ready%20for%20other%20languages%3F\"><h3>Is the plugin translation-ready for other languages?<\/h3><\/dt>\n<dd><p>Yes. The interface uses the text domain <code>muri-link-tracker<\/code>. Some technical failure diagnostics are currently in English. A POT file ships in <code>\/languages\/<\/code> and translations are also open for the community on translate.wordpress.org. Technical SEO tokens (<code>nofollow<\/code>, <code>sponsored<\/code>, <code>ugc<\/code>, <code>dofollow<\/code>) and affiliate network names are intentionally kept untranslated.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>2.3.0<\/h4>\n\n<ul>\n<li>Preserve images, Gutenberg markup and existing rel values when editing links.<\/li>\n<li>Detect concurrent article edits; keep revisions and reconcile URL collisions transactionally.<\/li>\n<li>Keep scan state durably, support bounded retries, and complete scans without dashboard polling.<\/li>\n<li>Correct Requests limits and pin validated public IPs for external HTTP checks (PHP cURL required).<\/li>\n<li>Distinguish unverified internal URLs, apply URL exclusions and explicit custom-field keys.<\/li>\n<li>Store page reputation evidence per URL with a stable baseline and shared probe budget.<\/li>\n<li>Stream CSV\/JSON exports, include all active filters and retain REST errors.<\/li>\n<li>Update dependencies and rebuild release tooling in isolated staging.<\/li>\n<\/ul>\n\n<h4>2.2.0<\/h4>\n\n<ul>\n<li>New (Destination health): the scanner now reports links whose <strong>destination went bad<\/strong> \u2014 the domain expired, the site changed hands, or the URL now lands on a parking page or unrelated content. A 200 OK is no longer treated as proof a link is still good.<\/li>\n<li>New (Links table): <strong>Risk<\/strong> column and filter \u2014 Suspect \/ Worth a look \/ No issue found \/ Not assessed. Opening a link shows exactly what was observed: where it lands now, the page title that was read there, and the anchor text it was compared against. You decide what to do; the plugin never edits or removes a link on its own.<\/li>\n<li>New (Status): dead domains are no longer lumped in with timeouts and 5xx errors. A host that does not resolve gets its own <strong>\"Domain not found\"<\/strong> status and quick filter, confirmed by an actual DNS lookup so a resolver hiccup cannot mislabel your whole inventory.<\/li>\n<li>New (Trust): mark a domain as trusted and it stops being flagged everywhere at once \u2014 decisions are recorded per domain, not per link, so a partner linked forty times is judged once.<\/li>\n<li>New (Dashboard): \"Destination health\" summary, shown only once links have actually been assessed.<\/li>\n<li>New (Export): <code>final_domain<\/code>, <code>risk_level<\/code> and <code>risk_flags<\/code> columns, included in the AI Analysis preset.<\/li>\n<li>New (REST): <code>risk<\/code>, <code>risk_flag<\/code> and <code>final_domain<\/code> filters on <code>\/links<\/code> and <code>\/links\/export<\/code>; <code>byRisk<\/code> counts in <code>GET \/links\/stats<\/code>; <code>GET|PUT \/reputation\/domains<\/code> and <code>POST \/reputation\/links\/{id}\/probe<\/code>.<\/li>\n<li>New (Settings): \"Destination health\" section \u2014 switch the pass off entirely, or cap how many destinations are fetched per run on constrained hosting.<\/li>\n<li>Privacy: <strong>everything is computed on your own server.<\/strong> No third-party API, no account, no key, nothing sent anywhere. The only requests made are to the destinations your own content already links to.<\/li>\n<li>Performance: the assessment runs in its own background pass <strong>after<\/strong> a scan, never inside it, so scan duration is unchanged. Signals that need no page fetch run on data already stored; only flagged candidates trigger a fetch, and fetches are pooled per domain \u2014 forty links to the same site cost one request.<\/li>\n<li>Extensibility: <code>mltr\/reputation\/signals<\/code>, <code>signal_weights<\/code>, <code>thresholds<\/code>, <code>parking_signatures<\/code>, <code>spam_terms<\/code>, <code>stop_words<\/code>, <code>compound_suffixes<\/code> and <code>should_probe<\/code> filters, plus a <code>mltr\/reputation\/assessed<\/code> action.<\/li>\n<li>Database: new <code>mltr_domains<\/code> table and four columns on <code>mltr_links<\/code>. Upgrades apply automatically on the next admin page load; a one-off background pass backfills existing links so the feature has something to say about your back catalogue immediately.<\/li>\n<\/ul>\n\n<h4>2.1.0<\/h4>\n\n<ul>\n<li>New (Localization): the plugin now ships with complete <strong>French, Spanish, German and Italian<\/strong> translations \u2014 covering both the PHP layer and the React admin app. The interface follows your site language automatically.<\/li>\n<li>New (i18n tooling): reproducible translation pipeline \u2014 <code>composer run make-pot<\/code> regenerates the template and <code>composer run make-json<\/code> builds the React (JS) catalogs from the <code>.po<\/code> files, consolidated to the single admin bundle WordPress actually loads.<\/li>\n<li>i18n: regenerated the <code>.pot<\/code> template (259 strings) with the full v2.0 affiliate \/ rel \/ export \/ FTC UI strings.<\/li>\n<li>Note: technical SEO tokens (<code>nofollow<\/code>, <code>sponsored<\/code>, <code>ugc<\/code>, <code>dofollow<\/code>) and affiliate network names are intentionally left untranslated for cross-language consistency.<\/li>\n<li>Hardening: a third-party callback hooked on <code>mltr\/check\/link_checked<\/code> that throws can no longer abort the scan batch or overwrite the link status that was just saved \u2014 the notification is now isolated from the persistence step.<\/li>\n<li>Hardening: when WP_DEBUG is on, unexpected per-link failures during a check are logged with their full origin (file:line + stack trace) to ease diagnosis.<\/li>\n<li>No database changes. Drop-in update from 2.0.0.<\/li>\n<\/ul>\n\n<h4>2.0.0<\/h4>\n\n<ul>\n<li>New (Affiliates): dedicated <strong>Affiliates<\/strong> tab with at-a-glance stats, prioritized action list (broken \/ missing rel=\"sponsored\" \/ missing FTC disclosure), and per-network breakdown. Each card pre-filters the Links tab on click.<\/li>\n<li>New (Affiliates): <strong>bulk add <code>rel=\"sponsored\"<\/code><\/strong> with dry-run preview \u2014 see exactly which URLs will be patched before confirming. Edits the live <code>post_content<\/code> silently (no revision, no <code>post_modified<\/code> change).<\/li>\n<li>New (External links): <strong>bulk add <code>rel=\"nofollow\"<\/code><\/strong> for external links, symmetric to the sponsored flow. Dry-run preview + silent post update + sample list of patched URLs.<\/li>\n<li>New (Links table): dedicated <strong>Rel<\/strong> column with NF \/ SP \/ UGC \/ DF badges aggregated per link, plus DataViews filter with positive (<code>Has rel=\"nofollow\"<\/code>) and <strong>negative<\/strong> (<code>Missing rel=\"nofollow\"<\/code>, <code>Missing rel=\"sponsored\"<\/code>) options \u2014 easily spot links you forgot to annotate.<\/li>\n<li>New (Bulk rel): <strong>\"Add rel=sponsored\" \/ \"Add rel=nofollow\" now apply to every link you explicitly select<\/strong>, even ones auto-detection did not flag as affiliate or external \u2014 so you can override a missed classification. The automated \"Fix all missing rel\" flows still target only detected affiliate\/external links.<\/li>\n<li>Fix (Edit Link): manually editing a link's <strong>Rel attribute<\/strong> now updates the plugin's Rel column and filters immediately, instead of staying stale until the next scan (the post content was always updated correctly; only the in-plugin index lagged).<\/li>\n<li>New (REST): <code>rel_missing<\/code> query param on <code>\/links<\/code> and <code>\/links\/export<\/code> (<code>nofollow|sponsored|ugc<\/code>). New endpoint <code>GET \/links\/externals\/missing-nofollow<\/code> returns IDs of external links lacking <code>rel=\"nofollow\"<\/code>. <code>GET \/links\/stats<\/code> exposes a new <code>externalsMissingNofollow<\/code> counter.<\/li>\n<li>New (Affiliates): <strong>FTC disclosure alert<\/strong> \u2014 flags posts with N or more affiliate links missing any of the configured disclosure phrases (e.g. \"As an Amazon Associate I earn from qualifying purchases\"). Cache invalidated on scan complete.<\/li>\n<li>New (Edit Link): SelectControl to <strong>open links in a new tab<\/strong> (<code>target=\"_blank\"<\/code>). Automatically adds <code>noopener noreferrer<\/code> to the rel for security and Google compliance.<\/li>\n<li>New (Multilingual): Polylang and WPML adapter pattern. <code>\/links<\/code> and <code>\/links\/export<\/code> accept a <code>language<\/code> filter. New <code>GET \/languages<\/code> endpoint. Filter chip in the Links table appears only when a multilingual plugin is active.<\/li>\n<li>New (Filters): <strong>Errors<\/strong> (401, 403, 5xx, network\/SSL) and <strong>Timeout<\/strong> quick-filter buttons in the Links toolbar \u2014 these statuses were previously buried in \"All\".<\/li>\n<li>New (Export): rebuilt export modal \u2014 choose CSV or JSON, pick exact columns, apply date range, and select between Full \/ AI Analysis \/ Minimal presets.<\/li>\n<li>New (Export): JSON export endpoint suitable for LLM ingestion \u2014 camelCase keys, decoded <code>redirectChain<\/code>, ISO 8601 datetimes, envelope with version and exported count.<\/li>\n<li>New (Export): 17 available export columns including <code>created_at<\/code>, <code>languages<\/code> and a decoded <code>redirect_chain<\/code>.<\/li>\n<li>New (REST): <code>\/links<\/code> and <code>\/links\/export<\/code> now accept <code>created_at_from\/to<\/code>, <code>last_checked_from\/to<\/code> ISO 8601 ranges, <code>language<\/code> and <code>affiliate_network<\/code> filters. New <code>GET \/links\/stats<\/code> adds <code>affiliateMissingSponsored<\/code> counter and <code>affiliate_broken_count<\/code> in byCategory.<\/li>\n<li>New (Admin notice): <strong>SchemaNotice<\/strong> detects when a v1.x \u2192 v2.0 upgrade left the <code>mltr_instances<\/code> table without the new <code>post_language<\/code> column and offers a one-click \"Retry migration\" button. Prevents silent data loss on rescan.<\/li>\n<li>UX: in Settings, list-like fields (post types, excluded URLs, FTC disclosure phrases) now preserve spaces and trailing newlines while typing \u2014 parsing happens only on Save.<\/li>\n<li>UX: clearable banner above the Links table when navigation from the Affiliates tab applied a filter (\"Show all links\" resets to full inventory).<\/li>\n<li>Security: requested export columns are whitelisted against the schema \u2014 unknown column names are silently dropped. Bulk endpoint max-items raised to 500 to support the \"Fix all rel=sponsored\" flow.<\/li>\n<li>Security (SSRF): the link checker now re-validates every redirect hop and blocks redirects to private, loopback, link-local, or IPv6 unique-local addresses \u2014 including AAAA-only hosts and the cloud metadata endpoint \u2014 closing a gap where a public URL could redirect inward.<\/li>\n<li>Reliability: the recurring stale-link recheck and weekly orphan cleanup are now actually scheduled on activation (previously wired up but never registered, so both ran as no-ops).<\/li>\n<li>Internationalization: PHP and React admin translations now load from the bundled <code>\/languages<\/code> catalogs (the text domain and script translations were not being registered).<\/li>\n<li>Performance: link checking scales to very large inventories without exhausting memory \u2014 pending and stale work is now queued by ID instead of materializing every link object \u2014 and a new composite <code>(status_category, last_checked)<\/code> index speeds up the pending lookup.<\/li>\n<li>Robustness: a single post that fatally crashes the scan worker (e.g. an out-of-memory parse) is now skipped after a few attempts instead of making Action Scheduler retry the same batch forever.<\/li>\n<li>UX: \"Fix all\" bulk actions are chunked under the 500-id REST cap so they complete on large sites; failed link loads now surface an error notice instead of an indistinguishable empty table; dates in the Links table are localized to the site locale.<\/li>\n<li>Backwards compatible: integrations calling <code>\/links\/export<\/code> without <code>format<\/code> or <code>columns<\/code> continue to receive the same CSV as v1.1.0. <code>PUT \/links\/{id}<\/code> without <code>target<\/code> keeps the attribute as-is.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>Tested with WordPress 7.0 (new \"Modern\" admin theme palette supported).<\/li>\n<li>Security: stricter URL scheme allowlist and DNS resolution cache to reduce DNS rebinding TOCTOU window during HTTP checks.<\/li>\n<li>Security: hardened REST validation on <code>PUT \/links\/{id}<\/code> (URL validated via <code>wp_http_validate_url()<\/code>, <code>rel<\/code> filtered against the allowed token list).<\/li>\n<li>Security: rate-limit added on CSV export endpoint (HTTP 429 if triggered more than once per 30 seconds).<\/li>\n<li>Performance: link and instance bulk inserts now use multi-VALUES batched statements (~90% I\/O reduction on large scans).<\/li>\n<li>Performance: new composite index <code>(post_id, link_id)<\/code> on the instances table; existing installs get it automatically on next admin page load (no re-activation needed).<\/li>\n<li>Performance: cross-request lock on the in-poll Action Scheduler queue runner to avoid duplicate work when the dashboard is open in multiple tabs.<\/li>\n<li>UX: empty-state messaging on the Dashboard and Links table before the first scan.<\/li>\n<li>UX: bulk delete now asks for confirmation showing the count of links to remove.<\/li>\n<li>UX: scan-status polling backs off exponentially when the backend is unreachable instead of hammering the API.<\/li>\n<li>UX: completion message uses proper pluralization.<\/li>\n<li>UX: per-network affiliate labels (Amazon, ShareASale, CJ, etc.) are now internationalized.<\/li>\n<li>Accessibility: improved heading structure in the Dashboard, ARIA group on the quick filter bar, focus management in the Edit Link modal, and live-region announcements for snackbar notifications.<\/li>\n<li>Theming: admin styles now use CSS custom properties that pick up the active WordPress admin palette tokens.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release.<\/li>\n<li>Background link scanning via Action Scheduler with resume capability.<\/li>\n<li>Three scan modes: Full Scan, Delta Scan (modified posts only), and Reset Data.<\/li>\n<li>Parallel HTTP checking with HEAD\/GET fallback.<\/li>\n<li>Affiliate link detection for 14+ networks, including cloaked internal redirects.<\/li>\n<li>Redirect chain and loop detection (single, chains of 2+, loops).<\/li>\n<li>Per-affiliate-network breakdown in the Dashboard.<\/li>\n<li>Modern React admin dashboard with real-time scan progress and cancel control.<\/li>\n<li>Link table with quick filter tabs (All \/ Broken \/ Redirects \/ OK \/ Pending), search, advanced filters, pagination, and bulk actions.<\/li>\n<li>Inline link editing with URL and rel attribute updates \u2014 silent (no post_modified bump, no revision).<\/li>\n<li>CSV export with active filters.<\/li>\n<li>Scan resume after interruption.<\/li>\n<li>Configurable post types, batch sizes, timeouts, request delays, and recheck intervals.<\/li>\n<li>Full internationalization support (i18n ready).<\/li>\n<\/ul>","raw_excerpt":"A high-performance link checker for WordPress with affiliate detection, redirect chain analysis, and background processing.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/293106","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=293106"}],"author":[{"embeddable":true,"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/muri3"}],"wp:attachment":[{"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=293106"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=293106"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=293106"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=293106"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=293106"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/gd.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=293106"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}