=== Anchormender – Internal Link Suggestions, Orphan Post Finder & Bulk Internal Linking ===
Contributors: emtiazzahid
Tags: internal links, internal linking, orphan pages, link suggestions, link building
Requires at least: 6.6
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.0.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Suggests internal links while you write, shows the sentence to link from, and fixes orphan pages in bulk. AI rerank is optional.

== Description ==

Internal links are the cheapest SEO win there is, and the easiest one to forget. Anchormender builds a small search index of your own content and uses it to suggest links **at the moment you are writing**, in the sentence where they belong.

https://www.youtube.com/watch?v=ej04XJITkSA

= In the editor =

* **Block editor sidebar**: lists related posts, each with the sentence in your draft that fits best and the proposed anchor phrase highlighted in it. **Insert** links the first occurrence of the anchor in the paragraph that holds that sentence, using the editor's own link format, so it looks exactly like a link you added by hand. **Change the anchor** before inserting, or **Skip** a suggestion so it stops showing for this post.
* **Link from other posts on the same topic** (block editor sidebar): find published posts on the same topic that could link *to* the one you are editing and add the link in one of them with "Add link in that post", without leaving the editor. That other post is saved right away; a revision is saved when the post type supports revisions.
* **Classic editor meta box**: lists related posts with the target title, a relevance score and an editable anchor, with **Insert** and **Skip** for each and a **Refresh** button. Insert links the anchor in the paragraph the suggestion was made for, in the text currently in the editor, following the same rules as bulk linking (see "Built to be light"). In classic HTML, when no paragraph matches (the suggestion came without a matching sentence, or you edited that paragraph since), it falls back to the first suitable occurrence of the anchor; if the anchor cannot be placed it says so and adds nothing.
* Insert in the sidebar or the meta box only changes the post in the editor. Nothing is saved until you save the post.
* Suggestions are based on the text currently in the editor, unsaved changes included. In the block editor they refresh automatically once the word count has changed by about 50 words (you can turn this off under Settings → Suggestions) and whenever you click Refresh. The classic editor box loads suggestions when the screen opens and updates them when you click Refresh.

= Site-wide =

* **Link report**: inbound and outbound internal link counts for every post, with **orphans** and "fewer than 2 inbound" one click away, search, sort, CSV export.
* **Fix an orphan** from the report: pick from suggested source posts and insert links straight into them.
* **Bulk linking**: choose up to 50 posts per batch (orphans, posts with fewer than 2 inbound links or all indexed posts, each taken fewest inbound links first, or up to 50 specific post IDs), review every proposal with its sentence and anchor, then apply. Every changed post is saved right away; a revision is saved when the post type supports revisions. **Undo** removes the links a batch added in one click and keeps their text; if you removed one of those links or changed its text after the batch ran, Undo leaves that link alone, says so, and still removes the batch's other links.
* **Link audit** (weekly and on demand): links to posts that were unpublished, trashed or deleted after the link was indexed, generic anchors like "click here", anchors that are over-used for one target, and links to noindex pages.

= How relevance works =

Titles, headings, taxonomy terms and the first part of the body are tokenised, stemmed and stop-word filtered per language, and stored as TF-IDF vectors in custom tables. Candidates are retrieved from a postings list, scored by cosine similarity with a bonus for shared title words and taxonomy terms, then matched to the best sentence by word overlap. It is fast, runs entirely on your server, and re-indexes a post in the background about a minute after you save it.

= Optional AI rerank =

Off by default. On WordPress 7.0 and later it uses the AI provider you connect under Settings → Connectors, through WordPress's built-in AI Client, so there is no separate key to manage. On any version you can instead bring your own key for OpenAI, Anthropic, Google Gemini or an OpenAI-compatible endpoint. When you ask for it in the block editor sidebar, the title, headings and up to the first 1,500 characters of text of the post you are editing, plus the titles and short excerpts of up to 20 candidate posts, go to the provider you chose, and a re-ranked list with suggested anchor phrases comes back. You see a token estimate first and none of your content is sent until you confirm. Details under External services.

= Built to be light =

* Custom tables, no post meta bloat, indexing in Action Scheduler batches that stop once they have run for half of PHP's `max_execution_time` (at least 5 and at most 20 seconds; 20 seconds when there is no limit) and then continue in smaller batches
* Never places links inside headings, existing links, code or buttons
* Bulk linking, "Add link in that post", fixing an orphan from the report and Insert in the classic editor box only link a paragraph that has fewer links than "Max links per paragraph" (1 by default, so a paragraph that already has a link is left alone). Insert in the block editor sidebar links the paragraph of the suggestion you click and does not count the links already in it.
* Polylang and WPML aware: suggestions stay within the post's language
* WP-CLI: `wp anchormender index rebuild|status|update`, `wp anchormender report [all|orphans|low]`, `wp anchormender suggest <id>`

= Hooks for developers =

`anchormender_capability`, `anchormender_excluded_post_types`, `anchormender_exclude_post`, `anchormender_index_text`, `anchormender_stopwords`, `anchormender_post_language`, `anchormender_candidates`, `anchormender_before_insert`, `anchormender_after_insert`, `anchormender_generic_anchors`, `anchormender_index_on_save`, `anchormender_batch_time_budget`, `anchormender_after_index`, `anchormender_is_builder_post`.

= More plugins by Emtiaz Zahid =

* [Launchmender](https://wordpress.org/plugins/launchmender/): 44 pre-launch checks with one-click fixes, a manual checklist and a client handoff report
* [Faultwright](https://wordpress.org/plugins/faultwright/): PHP errors, JavaScript errors, failed cron jobs, emails and updates on one timeline
* [Stalemender](https://wordpress.org/plugins/stalemender/): content audit that gives every post a Freshness Score (0–100) and tells you why
* [Waymender](https://wordpress.org/plugins/waymender/): finds broken links without hammering your server, logs 404s and fixes both in one click

== External services ==

Anchormender's link index, suggestions, link report, audit and bulk linking run entirely on your own server and never contact any outside service. The index stores stemmed keywords derived from the titles, headings, taxonomy terms and text of your published posts, plus the anchor text of the internal links between them, in custom database tables; it does not store the full post text. Nothing is indexed for drafts, private or password-protected posts. The tables are removed on uninstall unless you turn on "Keep data" in Settings.

= Optional AI rerank =

AI rerank is off by default. To use it you turn it on under Anchormender → Settings → AI assist and choose where requests go:

* **WordPress AI** (WordPress 7.0 and later, the default there): the request goes through WordPress's built-in AI Client to the AI provider you connected under Settings → Connectors. WordPress chooses the provider and model and keeps the API key; Anchormender stores no key for it. The connectors WordPress offers are for the providers listed below, and the terms and privacy policy of the one you connected apply.
* **Your own key**: you enter an API key for one of the providers below, and the request goes directly from your server to that provider.

None of your content is sent until you click "AI rerank" in the block editor sidebar and then "Run" after seeing the token estimate. With WordPress AI, clicking "AI rerank" can make WordPress ask the connected provider which models it offers; that request carries the API key but none of your content. Each run sends one request containing:

* the title, headings and up to the first 1,500 characters of text of the post you are editing, including unsaved changes in the editor;
* the titles and short excerpts, up to 160 characters each, of up to 20 candidate posts from your site;
* the API key, in the request header (your own key, or the one WordPress holds for the connected provider).

It does not send visitor data, user accounts or settings. Requests made with your own key do not include your site URL; requests made through WordPress AI use WordPress's standard HTTP User-Agent, which includes your site's address. The provider replies with a ranked list of candidate IDs and suggested anchor phrases. Anchormender does not store the reply; it only logs the token counts of each call locally so you can see usage in Settings. If you use your own key, it is stored encrypted in the WordPress options table and is never displayed again beyond its last four characters.

Providers and their terms:

* OpenAI (api.openai.com) – Terms: https://openai.com/policies/terms-of-use – Privacy: https://openai.com/policies/privacy-policy
* Anthropic (api.anthropic.com) – Terms: https://www.anthropic.com/legal/commercial-terms – Privacy: https://www.anthropic.com/legal/privacy
* Google Gemini (generativelanguage.googleapis.com) – Terms: https://ai.google.dev/gemini-api/terms – Privacy: https://policies.google.com/privacy
* OpenAI-compatible endpoint – any server you enter as the Base URL, including one on your own network. The plugin does not restrict this address; the data above is sent to that server and its terms and privacy policy apply.

Anchormender bundles Action Scheduler for background indexing; it makes requests only to your own site. The plugin registers a suggested paragraph under Settings → Privacy → Policy Guide.

== Privacy ==

Anchormender keeps a search index of your published posts and pages in its own database tables. For each post it stores stemmed keywords derived from its title, headings, taxonomy terms and text, with their weights, plus the post's language, word count and a checksum of its content; it does not store the full text. It also stores the internal links between posts with their anchor text, which links it added, the suggestions skipped for each post and who skipped them, bulk-linking jobs, and the token counts of AI rerank calls. For each job it records the user who started it and the links the job added (post, target URL and anchor text), which Undo removes again, and the ids of the revisions WordPress saved (a revision is saved when the post type supports revisions), so you can also restore one of those revisions by hand. Nothing about your visitors is stored and no cookies are set.

Nothing leaves your site unless you turn on the optional AI rerank, with a provider connected under Settings → Connectors or your own API key; what is sent is listed under External services. The bundled Action Scheduler only talks to your own site.

Uninstalling removes the tables, options, scheduled jobs and the encrypted API key unless you enable "Keep data on uninstall". The plugin suggests a paragraph for your privacy policy under Settings → Privacy → Policy Guide.

== Installation ==

1. Upload the plugin to `/wp-content/plugins/anchormender` or install it from the Plugins screen.
2. Activate it.
3. Open **Anchormender → Report** and click **Build index**.
4. Edit any post and open the **Anchormender** sidebar (the link icon in the top-right toolbar).

== Frequently Asked Questions ==

= Does it send my content anywhere? =

Not unless you turn on AI rerank and click it. Indexing, suggestions, the report, the audit and bulk linking run on your own server. With AI rerank on, each run sends the current post's title, headings and up to the first 1,500 characters of its text, plus candidate titles and excerpts, to the provider you selected, and only after you confirm the token estimate.

= Will it change my posts without asking? =

No. Insert in the block editor sidebar or in the classic editor box changes only the post you are editing, inside the editor; it is saved when you save the post. "Add link in that post" (under "Link from other posts on the same topic") is different: it adds the link to that other, published post and saves it immediately. Fixing an orphan from the report and bulk linking also save each changed post right away, and each bulk batch can be undone. In all three cases a revision is saved when the post type supports revisions.

= What about page builders? =

Suggestions work in the block editor sidebar and in the classic editor box. Posts built with Elementor, Divi, Beaver Builder, WPBakery, Bricks or Oxygen can still be suggested as link targets, but Anchormender does not insert links into them: Insert is switched off while editing such a post, and bulk linking, fixing an orphan and "Link from other posts on the same topic" never pick one as the post that gets the link. Developers can adjust the detection with the `anchormender_is_builder_post` filter.

= Which post types are supported? =

Posts and pages by default; add any public post type under Settings → Indexing.

= Does it work with Yoast SEO, Rank Math or SEOPress? =

Yes. Anchormender reads their noindex flags for the audit.

= Does the plugin send anything to its author? =

No. Anchormender has no server of its own and sends no usage statistics or telemetry. The index, suggestions, reports and bulk linking all run on your server. The only outbound requests are the optional AI rerank described under "External services", which is off until you turn it on and connect a provider under Settings → Connectors or enter your own API key.

= Does AI rerank use the WordPress AI Client? =

Yes, on WordPress 7.0 and later. Choose "WordPress AI (Settings → Connectors)" under Anchormender → Settings → AI assist (it is the default there), and requests go through WordPress's built-in AI Client to the provider you connected, with the key WordPress keeps. On older WordPress versions, or if you prefer, you can enter your own API key for OpenAI, Anthropic, Google Gemini or an OpenAI-compatible endpoint instead.

= Does the bundled Action Scheduler conflict with other plugins? =

No. Anchormender loads Action Scheduler 3.9.3 from `lib/action-scheduler/`. Action Scheduler is built for this: each copy registers its version with `ActionScheduler_Versions` behind `function_exists()` and `class_exists()` guards, and only the newest copy on the site boots. WooCommerce and other plugins that bundle it share the same mechanism.

= Where is the JavaScript source? =

The editor sidebar and the admin app ship un-minified in `assets/src/`; the files in `assets/build/` are produced from them with `npm install && npm run build` (the `package.json` is included). The bundled Action Scheduler library in `lib/` is unmodified upstream code under its own GPL licence. Nothing else is compiled or obfuscated.

= Who can use it? =

Anyone who can edit a post sees suggestions for that post. The report, bulk and audit screens need the `edit_others_posts` capability (filterable with `anchormender_capability`); settings need `manage_options`.

== Screenshots ==

1. Suggestions in the block editor sidebar with the matching sentence and anchor.
2. A link inserted in place, ready to save.
3. The link report with orphan pages and inbound/outbound counts.
4. Bulk linking: review every proposal before applying.
5. The existing-link audit.
6. AI rerank with a token estimate before anything is sent.

== Changelog ==

= 1.0.1 =
* Fix: the link audit now recognises posts set to noindex in SEOPress.

= 1.0.0 =
* Initial release.
