Title: Megop SEO – Bulk Editor, Internal Links, Broken Links &amp; Search Console
Author: megop
Published: <strong>September 4, 2026</strong>
Last modified: September 4, 2026

---

Search plugins

![](https://ps.w.org/megop-seo/assets/banner-772x250.png?rev=3681203)

![](https://ps.w.org/megop-seo/assets/icon-256x256.png?rev=3681203)

# Megop SEO – Bulk Editor, Internal Links, Broken Links & Search Console

 By [megop](https://profiles.wordpress.org/hamuo1372/)

[Download](https://downloads.wordpress.org/plugin/megop-seo.8.3.0.zip)

 * [Details](https://wordpress.org/plugins/megop-seo/#description)
 * [Reviews](https://wordpress.org/plugins/megop-seo/#reviews)
 *  [Installation](https://wordpress.org/plugins/megop-seo/#installation)
 * [Development](https://wordpress.org/plugins/megop-seo/#developers)

 [Support](https://wordpress.org/support/plugin/megop-seo/)

## Description

Internal linking on WordPress is a paid category. The tools people recommend are
commercial and closed source, their licences are proprietary, and the ones that 
publish a price ask anywhere from a monthly subscription to several hundred dollars.
All of them were built for English first. The most translated of them ships a dozen
interface languages, and Persian is not one of them.

So for a Persian site the shelf has been empty. Not a free option, not an open one,
and not one that knows a Persian phrase can be spelled four ways that look identical
to a reader and are four different strings to a computer.

This is the option that was missing. GPLv2, every line. Five tools that would normally
be five purchases, sharing one index so they agree with each other instead of fighting.

If you have been looking for a bulk editor, a bulk price editor for WooCommerce,
a bulk duplicator, a search and replace tool, a redirect chain checker or a self
hosted analytics plugin, five of those are here, in one plugin, free.

Written and maintained by [محمد قاسمی (Mohammad Ghasemi)](https://megop.org/), Tehran,
Iran.

#### Seo Bulk Manager: your whole site in one grid

Every public post type and every public taxonomy, whatever registered them. Posts,
pages, WooCommerce products, product categories, a custom post type a plugin created
this morning. Nothing is hard coded and nothing is special cased.

**Edit the row, not the page.** H1, SEO title, meta description, featured image,
status, category, term parent, and the regular and sale price of a product. Save
one row and that row saves, in place, no reload.

**The counter counts what Google sees.** A title template such as `%title% %sep%%
sitename%` is 24 characters in a plain counter and 61 on the search result. This
one resolves the variables first, so the number under the field is the number that
decides whether you get truncated.

**Search and replace answers before it acts.** Tick the fields, type the phrase,
and the row tells you how many matches it found and where, Elementor data included.
Then you commit.

**Prices, variable products included.** An empty field is left alone, never read
as clear. One price is written to every variation.

**Turn one page into fifty.** List a word and its replacements, one page per line,
and get fifty drafts with the terms, the featured image and the Elementor data intact.
A product line with twelve near identical descriptions stops being twelve editing
sessions.

**Delete with a redirect, in one step.** The row goes to the trash and its old address
is redirected where you choose.

**Headings, without touching layout.** H1 to H6 of a page, read from the body and
from Elementor widgets, editable as text. Adding and removing is deliberately impossible:
where a heading belongs is a decision for the editor, not a list.

**Sorted by clicks.** With Search Console connected, the grid orders itself by real
impressions and clicks over a range you pick.

#### Seo Auto Link: one keyword, every page

Register a phrase on a page and the engine links it everywhere that phrase appears.
From an index, not a search of your content on every request: the heaviest page 
on the test site resolves in 7.3 milliseconds.

**It starts in shadow mode.** The index builds while nothing is injected. You read
the reports, you fix the conflicts, and only then do you switch it live.

**Persian folding before matching.** Arabic and Persian characters folded together,
the zero width non joiner treated as a space, digits normalised. Your content is
read through an offset map, never rewritten.

**Rules that stop it embarrassing you.** Longest phrase first, ranges locked so 
two keywords cannot overlap, word boundaries on both sides, no link from a page 
to itself, and never inside a heading, a button, an existing link, a shortcode or
an attribute. Caps per page, per target and per paragraph, all off by default because
the right number is your content’s, not a figure from a blog post.

**It finds the links you wrote by hand years ago.** A hand written link blocks the
engine, so those phrases sit outside every report and never update when a destination
moves. The pattern finder lists them and converts them in bulk, with the original
of every page backed up first.

**Nothing is lost when you leave.** Deactivate the plugin and every link is written
permanently into your content before it goes. Reactivate and the content returns
to its clean state, byte for byte. Measured on a real site: 830 links across 227
pages, and the restored content identical to the original.

#### Search Console: what Google knows, on your screen

Upload your own Google service account key. The plugin reads your property and nothing
leaves your site except the query to Google.

**The gap, page by page.** Google is already showing this page for these searches.
Green means the phrase is on the page, word for word. Yellow means some of its words
are. Red means the page does not say it at all, and red is where the opportunity
is.

**Index status, in Google’s own words.** One URL at a time, grouped the way Search
Console groups them: indexed, crawled but not indexed, discovered, excluded by a
tag, blocked by robots.

**Writing, with the site holding the decisions.** The site decides which page, which
phrase and where the sentence goes; the model only writes prose. A rewrite, not 
a paragraph bolted onto the end. Off by default, and it stays off until you configure
an endpoint.

**Two ways of not fooling yourself.** A control group is drawn from every queue 
as a holdout, and every edit is dated so the 28 days before it can be compared with
the 28 days after. If the whole site drops past a threshold you set, everything 
stops.

#### Link Nurse: broken links found and repaired

Content, excerpts, term descriptions, Elementor data and your XML sitemap. Relative
links resolved against the page they sit on, which is where most scanners quietly
lose a quarter of a site’s links.

**Conservative on purpose.** A 401, 403, 404 or 429 is checked again with a browser
user agent before it is reported, because large publishers answer robots differently
from browsers. Your own uploads are checked on disk, not over HTTP, which removes
hundreds of self requests per scan.

**A redirect chain is followed to its end.** Up to ten hops, and a 301 that lands
on a 404 is reported as broken with both codes, not as healthy.

**Repairs keep your house style.** A broken link is unwrapped and its text kept.
Before the first repair to any field, the original is copied aside, so undo is per
field and always available.

#### Internal Analytics: your visitors, your server

No third party anywhere in the path. One cookie holding a random session id, set
in the browser, never sent to anyone.

**Engagement, not page loads.** Seconds accrue only while the tab is visible and
the visitor actually moved, scrolled, typed or clicked.

**Events out of the box.** Outbound clicks, file downloads, tel and mailto links,
form start and submit, CTA clicks, copy events.

**Screens that answer questions.** Visits, unique visitors, page views, engagement
and bounce, average time, live visitors, and the pages and sources behind each number.
Raw rows kept for a year by default, daily totals for twice that.

#### Persian and Arabic, from the first line

The same phrase can carry an Arabic character where a Persian one belongs, a zero
width non joiner instead of a space, or a Persian digit where Google reports a Latin
one. Every tool that matches text literally treats those as different phrases. This
one folds them together first, everywhere: the linking engine, search and replace,
keyword coverage and the reports.

The screens themselves speak English, Persian or Arabic, and each person on the 
site picks their own. Choosing one does not change the language of the rest of the
dashboard.

#### It works with your SEO plugin, not against it

This does not replace Rank Math and does not try to. Rank Math owns the SEO title,
the meta description and the redirect table; this plugin reads and writes those 
fields rather than inventing its own, and resolves redirects from that table instead
of making an HTTP request. Yoast, SEOPress and All in One SEO are detected for the
sitemap tools.

#### Tested on a live shop, not a clean install

Every release runs against a live Persian WooCommerce site with several thousand
posts before it is packaged. That is where the findings come from: a Jalali date
reaching a SQL comparison and returning zero rows on a site with 66,000 recorded
visits; a scanner that had never reported a quarter of a site’s links because they
did not begin with http; a bulk editor whose first version read an empty price field
as clear. Each one is in the changelog with what caused it.

#### What it writes to your database

One table for the link index and eight for analytics, all prefixed. Keywords, exclusions
and caps live in post and term meta. Settings live in options. Nothing is written
outside your own database, and an uninstall switch decides whether all of it is 
removed or kept for a reinstall.

#### External services

**Google Search Console API**, used only when you upload your own service account
key. Your site sends the property URL, date ranges and page addresses to Google 
in order to read back the searches and index states Google holds for you. [Terms](https://developers.google.com/terms/),
[privacy](https://policies.google.com/privacy).

**An AI endpoint of your choosing**, optional and inert until you fill in a base
URL, key and model. When configured, the text of the page being improved and the
target phrases are sent to that endpoint so it can return prose. The provider is
yours, so its terms are the ones that apply.

**The sites you link to**, when you scan for broken links. Each external address
in your content is requested once, with a user agent naming this plugin and your
site so the receiving site can see who is asking.

#### Author

Built in Tehran, and run every day on the two production sites it was written for.
Bug reports and feature requests are welcome on the support forum; the [author’s page](https://megop.org/founder-bio/)
has the longer story.

## Installation

 1. Upload the plugin folder to `/wp-content/plugins/`, or install it through the Plugins
    screen.
 2. Activate it. The linking engine starts in shadow mode, so nothing on your site 
    changes yet.
 3. Open Megop SEO. The bulk grid is the first screen; the settings screen shows the
    five module switches.
 4. For automatic linking, add a keyword to a page from its editor or from the grid,
    open Seo Auto Link, press Full index rebuild, review what the engine would do, 
    and switch the engine to live when you are satisfied.
 5. For Search Console, open the Search Console screen, upload a Google service account
    key file, press Test connection, then Sync now.

For the SEO title and meta description columns in the bulk manager to appear in 
your page source, install Rank Math, because those are its fields.

The Search Console features are optional and need a Google service account of your
own. The screen walks you through creating one and accepts the key file as an upload.
Link Nurse’s XLSX export needs PHP’s ZipArchive extension, which nearly every host
provides.

## FAQ

### Do I have to use all five modules?

No. A module switched off registers no hooks at all: no menu, no route, no cost 
on any request. Its data stays untouched.

### Do I need Rank Math?

Only for the SEO title and description to reach your page source, because those 
fields are its own. Everything else works without it.

### Will it conflict with my SEO plugin?

It is built not to. No meta tags of its own, no sitemap of its own, no second set
of fields to keep in sync.

### Who can use these screens?

Administrators, by default. Two settings can open keyword editing and the analytics
to other roles. Both only widen access, so you cannot lock yourself out.

### Which language will I see?

Whichever you pick. English, Persian and Arabic, stored per person, applied to these
screens only. The rest of your dashboard never changes.

### Does it work with Persian and Arabic text?

That is what it was built for. The zero width non joiner and a space are one character,
Arabic yeh and kaf fold to Persian, and coverage folds Persian digits too.

### What does the bulk manager actually do that other bulk editors do not?

Counts the finished title rather than the template, gives every row its own search
and replace with a live count, edits prices beside titles, and writes a redirect
as it deletes.

### Is bulk editing safe on a live shop?

It is built for one. Nothing is written until you press Save on that row, and an
empty price field never clears anything.

### Which post types can I edit in the grid?

Every public one. The list is read from WordPress at load, so a custom post type
registered this morning appears on its own.

### Can I edit taxonomies too, including WooCommerce categories and brands?

Yes, every public taxonomy. A term row edits the name, the SEO title and the description,
with a parent dropdown where the taxonomy is hierarchical.

### Can I edit WooCommerce prices in bulk?

Yes, regular and sale, for every product kind. On a variable product one value reaches
every variation and the row tells you how many.

### Can I type into the body of a post from the grid?

No, deliberately. A paragraph edited blind in a one line box is how content gets
destroyed. For prose, open the post.

### Can I move posts or products into a different category from the grid?

Yes, from a dropdown in the row. Forty products into the right category becomes 
forty clicks instead of forty page loads.

### Why is the category or parent dropdown locked on my site?

Because on your permalinks that change would change a URL, and that throws away 
whatever the old address earned. The screen says so where the control would be.

### Does the counter measure the template or the finished title?

The finished title. A template is 24 characters as text and perhaps 61 once it renders,
so the variables are resolved first.

### Can I use Rank Math variables like %date% and %category% in the grid?

Yes. WordPress’s standard sanitiser eats a percent sign followed by two hex characters.
These fields are cleaned without touching percent signs.

### What does the live count in find and replace tell me?

How many times your phrase occurs in the fields you ticked, before you type the 
replacement. Then both strings and both counts, twice, before anything moves.

### Can I replace text in only some fields of a row?

Yes: H1, title, description, content, in any combination, per row.

### Can I replace a phrase across many rows at once?

Yes. Tick the rows, press Bulk replace, and see how many matches in how many rows
before anything saves.

### Do I have to save the whole page?

No. A row saves alone and reports in place, with no reload. Unsaved rows carry an
orange bar, and leaving the page asks first.

### Does a save rewrite the whole post?

It writes the H1, the body as the row holds it, and both SEO fields. An administrator’s
content goes in untouched.

### Can I change the featured image without opening the post?

Yes. The media library opens over the row and the thumbnail updates as you choose.

### Can I change the slug, the author or the date from the grid?

No. Those stay in the editor. The slug in particular, for the same reason the category
lock exists.

### Can I sort the grid by what Google actually shows?

Yes, by clicks and impressions over a range you pick, read from the Search Console
tables this same plugin fills.

### Why is the Search Console column missing from my grid?

Either the module is off, or the column is. It is a per person switch at the top
of the grid.

### Are WooCommerce variable products safe to edit here?

Yes. Only a price you changed is sent, so a title edit can never rewrite a catalogue,
and an empty field means leave alone.

### Does the grid work on Elementor pages?

Yes. Replace reaches the Elementor data, the headings tool reads its heading widgets,
and the generated stylesheet is rebuilt afterwards.

### What happens when I delete rows in bulk?

Two steps and reversibly. Delete moves rows to the trash. Delete with a redirect
trashes them and writes one rule per row.

### Where do the redirects go?

Into Rank Math’s redirect table when its module is on, and the dialog offers to 
switch it on. Otherwise into `.htaccess`, and the dialog says so.

### What is the duplicator for?

Turning one page into fifty. Every copy arrives as a draft with the same terms, 
image, meta and Elementor data, with your replacements applied.

### What does the headings tool do?

Lists a page’s H1 to H6, from the body and from Elementor, and edits the text inline.
It will not add or remove a heading.

### What does the sitemap refresh button do?

Clears the cached sitemap and reads it back the way a crawler would, so the files
are rebuilt before a crawler asks.

### Are my column and tool choices saved?

Yes, per browser. Language and the dark theme are stored per person instead.

### What is internal linking, and why does it matter?

A link from one page to another tells a search engine the second exists and what
it is about. The usual failure is not too few links, it is links placed once and
never updated.

### Where do I type keywords?

In the post editor’s side box, on a category screen, or in the grid row. One phrase
per Enter, up to a hundred per page.

### Which pages give links, and which receive them?

Giving: the types you tick, posts and pages by default. Receiving: any published
page carrying a keyword, plus custom targets.

### Will the linking engine slow my site down?

No. All the matching happens once, in the admin. The heaviest page on the test site
costs about seven milliseconds.

### Do links appear in feeds, widgets or content loaded by AJAX?

No. Main content and category descriptions, in the main loop. Feeds, REST, previews
and the Elementor editor are left alone.

### Do I have to rebuild the index after editing keywords?

Saving a post re-indexes it at once. Bigger changes queue a rebuild you start, and
the old index keeps serving until it finishes.

### Will it improve my rankings?

Nobody can promise that and this plugin does not. It makes your linking consistent
and shows you the searches Google already ties to each page.

### Does the plugin change my content?

Not while the engine is live. Links are injected as the page renders, which is why
switching the engine off makes them vanish cleanly.

### What happens if I delete the plugin entirely?

Nothing disappears from your pages. Deactivating writes every engine link permanently
into your content first, and the uninstaller repeats it as a last defence.

### Does it work with content built by shortcodes or a page builder?

Yes. A shortcode is forbidden ground. On an Elementor page the engine links the 
rendered content, exactly as visitors see it.

### Can I stop the engine linking on particular pages?

A tick in the editor for one, the blacklist screen for a list. Either way the page
keeps receiving links and stops giving them out.

### Can I control how many links appear?

Yes, per page, per target, per paragraph and incoming. All off on a fresh install,
because the right number is your content’s.

### What stops it linking a phrase inside a heading or another link?

The tokeniser, not a setting. Twenty tag types are excluded before matching starts,
so no combination of settings can switch the protection off.

### Will a keyword match inside a longer word?

No. A word boundary is required on both sides, and the zero width non joiner counts
as one.

### Can one phrase point at two pages?

Not at once. The Anchor conflict screen lists every phrase on two targets and lets
you pick the right one in place.

### In what order are keywords applied?

Longest first by default, so a long phrase is never split by a short one inside 
it. Overlapping ranges are always refused.

### I have hand written internal links from years ago. What happens to them?

They keep working and they quietly block the engine, so those phrases sit outside
every report. The Manual patterns screen finds them and converts them in bulk.

### Why is a “read more” link never offered as a pattern?

Because a call to action is navigation, not content. A stoplist of about 800 Persian,
English and Arabic words filters them, and you can edit it.

### Can I stop a bulk conversion, and is it undoable?

You can stop it and everything converted stays. Each page keeps its original in 
a backup, but no single click reverses the run, and the confirmation says so.

### Can a link end up nested inside another link?

No, and not by a setting. The engine cannot see text inside a link, and a phrase
you linked by hand is withdrawn from the whole page.

### Can I link a phrase to an address outside WordPress?

Yes. Give the Custom links screen a title, an address, nofollow and new tab switches
and some keywords.

### Can I remove one keyword everywhere without opening pages?

Yes. The keyword count on the statistics card opens a drawer with live incoming 
counts and a cross on each phrase.

### I have hundreds of posts. Do I have to type keywords one at a time?

Arriving from another linking plugin, the migration wizard copies keywords, blacklists,
caps, custom links and settings in one step. Otherwise the grid is the fastest place.

### Does it work with translation plugins?

The keyword map is global. In practice a Persian keyword only matches Persian text,
but a phrase shared between languages would link across them.

### Will my links disappear if I deactivate the plugin?

No. Deactivation writes every link permanently into your content and reactivation
takes them back out. Measured on a real site: 830 links across 227 pages, restored
byte for byte.

### What happens to pages I blacklist?

They keep receiving links and stop giving them out. The pattern converter and the
writing engine both leave their content alone.

### Can I see what it would do before it does anything?

That is the default. The engine starts in shadow mode: the index is built, every
link it would place is counted and listed, and nobody is served one.

### Do I need a Google account for the Search Console features?

A Google service account of your own, which is free, and the screen walks you through
it. No OAuth, no token to refresh, no account of mine in the path.

### Why a service account instead of signing in with Google?

Because a sign in flow needs a redirect through somebody’s server, and that somebody
would be me. This keeps the credential on your own site.

### How long does the first sync take, and do I have to keep the page open?

The first sync walks back through your history while the tab is open, so leave it
open until the tiles stop saying not complete. After that a daily run tops up by
itself.

### What is an orphan URL on the sync tiles?

An address Google reported that matches no page of yours. Its data is kept, but 
there is nothing to write for.

### What is the yellow state in keyword coverage?

Green means the phrase is on the page word for word. Red means a word is missing.
Yellow means every word is there but never together, usually the cheapest win on
the page.

### What is opportunity?

The number the queue is ranked by. A red query is worth its impressions, a yellow
one half, a green one nothing.

### How does it decide which page a search belongs to?

Three signals, each printed beside the score: rarity weighted overlap with the title
and headings, your page’s share of the site’s impressions for that query, and whether
the rarest word is in the title.

### What is the share signal, and why does it matter so much?

Share is what Google already decided, so it is worth almost half the score, more
than any wording heuristic of mine deserves. Every part is shown, so you set your
threshold by looking.

### What is a mismatch, and what is a gap?

A mismatch is Google showing page A for a search that belongs to page B, so you 
route it rather than write it. A gap is a search nobody ranks for: a page that does
not exist yet.

### What does routing a phrase actually change?

It adds the phrase to that page’s linking keywords, and the writing engine then 
treats that page as its owner. One decision, two modules.

### What happens when I press New draft on a gap?

A draft is created, titled with the search, and the phrase is routed to it. Nothing
is ever published by the plugin.

### Can it ask Google to index a page?

No, and any plugin that says it can is describing something else. This asks the 
opposite question, what Google decided about each URL, which is the more useful 
half.

### Why does an index check take so long, and why must the page stay open?

Each call to Google takes about twelve seconds. The screen runs three lanes while
it is open and estimates from your own host. Closed, the daily run continues at 
twenty pages a day.

### Which pages are checked automatically, and why are my categories never checked?

Twenty posts a day: those due a first check, then the ones Google declined, then
the never checked. Term archives change too rarely to spend quota on.

### Does a failed index check count against the quota?

Yes, because Google counted it. The plugin caps itself at 1,000 a day against Google’s
2,000.

### What is Mismatched canonical on the index status screen?

A page where Google picked a different canonical from the one you declare. Both 
are shown, because it means Google is indexing a URL you did not intend.

### My index status said every page was unknown to Google. Why?

A real bug, now fixed, that bites any non Latin site: WordPress builds percent escapes
in lower case and Google matches them in upper case.

### Can the writing engine damage my content?

The guardrails sit on your site. Only types you ticked, never a blacklisted or frozen
page, caps per week and per ninety days, no growth past 20 percent, no links or 
images, and a full snapshot behind every edit.

### Where does the new text go?

After the heading whose words best match the query; at the end when none matches;
never inside a list or a shortcode. A marker records what the engine wrote.

### If I undo an edit, do I lose changes I made to the page afterwards?

Yes. Undo restores the whole snapshot, which is what makes it exact. Undo the newest
edit first.

### How can it rewrite a paragraph without wrecking the page?

By address, not instruction. The writer gets specific paragraphs with a hash of 
each, and may only replace a hash it was given. If the page changed meanwhile the
edit is refused, not misapplied.

### Does the title and description button need Rank Math?

It writes Rank Math’s two fields, capped at 55 and 155 characters, keeping the previous
pair for undo. Without it they are written and nothing prints them.

### Why does it refuse to write a phrase that would help the page?

Because it belongs to another of your pages, and writing it here would set two of
your own pages against each other in one result. The skip is counted by name.

### My page was skipped. What do the reasons mean?

Every skip is counted with its cause: blacklisted, frozen, unpublished, an unticked
type, a cap reached, no long enough paragraph, or a query owned elsewhere.

### What is the control group for?

Telling a real improvement from a good season. A share of every queue is held out,
so what happens to those pages is what would have happened anyway. Subtract it and
what remains is what you caused.

### What is the freezer?

A page that falls by a quarter after an edit, over at least seven days, leaves the
queue. The answer to a bad outcome is to stop touching that page, not to write more.

### Why does the effect report say waiting instead of giving me a number?

Because the window is under seven days, and a number from four days is a coin toss
dressed as a result.

### What does the site wide warning threshold do?

It warns when the whole property has dropped past that percentage over four weeks.
It does not stop the engine, it tells you to look first.

### When does the daily run happen?

Once a day, at the hour the key was first saved. It rides on WP-Cron, so a real 
cron job calling `wp-cron.php` makes it exact.

### Do I need an AI provider?

No. It ships with no endpoint, no key and no default provider, and with the fields
blank no request is ever made.

### Which AI providers are supported?

Anything speaking the OpenAI chat completions shape: base URL, key, model name, 
and a Test button. Where the first party APIs are unreachable, you point it at a
gateway you can actually use.

### Where is my AI key stored?

In the plugin’s settings option, as typed. Treat it as you treat any key in a WordPress
option: a database backup contains it.

### Which PHP extensions does Search Console need?

OpenSSL and multibyte string support. The XLSX export needs ZipArchive.

### Do I have to keep the browser tab open while Link Nurse scans?

Yes, on purpose. A scan makes requests to other people’s sites, and that should 
happen while someone is watching.

### I pressed Stop, then Scan. Why did it start from zero?

Because Scan always begins a fresh scan and clears the previous results. Stop leaves
what it found on screen.

### Which links does Link Nurse deliberately never list?

Links that do something rather than go somewhere: cart, login, logout, anything 
carrying a nonce. Share buttons. Feeds, REST, admin URLs, assets, previews, and 
your excluded domains.

### Why does Link Nurse report a link my browser opens fine?

Usually because the far end blocks robots. A 401, 403, 404 or 429 is checked again
with a browser user agent first, and only 404 and 410 count as broken by default.

### Why is a link with an expired certificate not reported as broken?

Because the check asks for a status code, and a certificate error would hide that
answer behind a different problem.

### What do the sites I link to see?

One HEAD request, then a GET if HEAD is refused, from a user agent naming this plugin
and your site. Nothing about your visitors.

### My internal links are written as /page-name/ rather than full URLs. Are they checked?

Yes, resolved against the page they sit on. This is where most scanners lose links:
on the test site, 332 of 1,373, invisible on every scan until it was fixed.

### Why is a redirect shown as Manual instead of being fixed?

Because reporting a redirect and rewriting it are two different permissions, and
only the second is optional. It is also Manual when the destination could not be
established.

### What does Ignored mean?

A bare domain redirecting to its own home page. Rewriting it would change nothing
a reader notices, so it is listed and left alone.

### What happens to an Elementor button whose domain I blacklisted?

The URL goes and the button and its text stay, because restructuring Elementor’s
data to remove a widget is how pages break.

### Can I undo a repair?

Yes. The original of each field is copied aside before the first repair to it, and
Undo restores that whole field. The backups survive a new scan.

### Why does Link Nurse only report a link if it appears on the rendered page?

Because a link no visitor ever receives is not worth your attention, and Elementor
sites are full of them. If the page cannot be fetched, the finding is kept anyway.

### Where is the XLSX report saved, and who can download it?

In your uploads folder, under a name with a random suffix, keeping the five newest.
Whoever has the exact link can open it, which is why it is not guessable.

### Does the analytics module set a cookie?

One, a random session id for 30 days, set in the browser. PHP never sets it, because
a Set-Cookie header on every page would make your whole site uncacheable.

### Does analytics store IP addresses?

Truncated by default, the last octet of IPv4 and the last 80 bits of IPv6 dropped.
Behind a proxy, switch on Trust proxy headers or every visitor shares one address.

### Does analytics record who a logged in visitor is?

The user id and display name. Administrators are not tracked at all by default, 
and the untracked roles are a setting.

### Does it honour Do Not Track?

No. Nothing is sent anywhere and no advertiser is involved, but the browser signal
is not read, and I would rather say so.

### Does the tracking still work behind a full page cache?

Yes, and that is why it is a REST endpoint. A nonce baked into cached HTML dies 
within a day and takes the tracking with it, silently.

### What counts as an engaged visit, and how is the bounce rate calculated?

Seconds count only while the tab is visible and the visitor is active. A visit is
engaged with two page views or ten engaged seconds. Bounce is a hundred minus that.

### How do I track a click on one specific button?

Three ways: a data attribute on the element, a CSS selector on the Events screen,
or a call to `window.megopTrack()`.

### Why do today’s numbers differ from what the chart showed yesterday?

The charts read daily totals, rebuilt for the last three days each night, while 
today is computed live. A day settles once the rollup passes over it.

### Can I export the analytics?

Three CSV files for the range you are viewing, with a byte order mark so Excel opens
Persian text. IP addresses and user agents are left out.

### Does it send my content or my visitors anywhere?

No. Analytics never leaves your database. Search Console talks to Google only after
you upload a key. There is no telemetry of any kind.

### Why is date handling mentioned so often?

Because it broke this plugin once, on a real site: with a Jalali calendar installed,
a Persian date reaching SQL silently matches nothing, and every card read zero on
66,000 visits.

### What does the plugin leave behind when I remove it?

Everything, by default, so a reinstall finds your work where it was. One switch 
removes it all instead. The backups that make a permanent link write reversible 
are never removed.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“Megop SEO – Bulk Editor, Internal Links, Broken Links & Search Console” is open
source software. The following people have contributed to this plugin.

Contributors

 *   [ megop ](https://profiles.wordpress.org/hamuo1372/)

[Translate “Megop SEO – Bulk Editor, Internal Links, Broken Links & Search Console” into your language.](https://translate.wordpress.org/projects/wp-plugins/megop-seo)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/megop-seo/), check 
out the [SVN repository](https://plugins.svn.wordpress.org/megop-seo/), or subscribe
to the [development log](https://plugins.trac.wordpress.org/log/megop-seo/) by [RSS](https://plugins.trac.wordpress.org/log/megop-seo/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 8.3.0

 * The whole codebase was read once more, file by file, against this readme, and
   four claims the page made were not true of the code. All four are fixed in this
   build.
 * Fixed: prices typed in the bulk grid were never saved. The fields were drawn,
   the server was ready for them and the empty field rule was written and tested,
   but the browser sent only the title, the SEO fields and the body. A price now
   goes to the server only when you changed it, so a title edit never touches a 
   catalogue, an empty field is left alone for every product kind, a variable product’s
   value is written to all of its variations, and the row confirms what was written.
 * Fixed: the Undo button on a Search Console title or description edit answered“
   No backup was found” for every one of them, because a meta edit keeps its backup
   under a different key from a content edit and the button only knew the content
   key. Undo now reaches both.
 * Fixed: the control group was never actually drawn. Setting a percentage stored
   the number and the holdout was only ever formed by an operation no button called,
   so the effect report read “Control group (0)” whatever you set. The holdout is
   drawn every time the queue is built.
 * Fixed: switching the linking module off made the settings screen fail to render,
   and because the endpoint that switches modules on lived inside that module, the
   module could not be switched back on from the screen. The switches and their 
   endpoint belong to the plugin shell now, and the linking settings simply hide
   while the module is off.
 * Fixed: every save from the bulk grid stripped backslashes from the title, the
   SEO fields and the body, because the values were unslashed once at the request
   boundary and once more by WordPress on the way in.
 * Fixed: a Rank Math variable such as `%date%` or `%category%` typed into the grid
   was corrupted on save. WordPress’s standard sanitiser removes any `%` followed
   by two hexadecimal characters, so `%date%` came back as `te%`. The two Rank Math
   fields are cleaned without touching percent signs now.
 * Fixed: the whole body of a post was passed through the content filter on every
   grid save, even for an administrator, so an iframe or a form embedded in a page
   vanished when its H1 was edited. The body is filtered exactly as the block editor
   would filter it for the user saving it.
 * The plugin wide delete on uninstall switch exists now, on the settings screen
   under the module switches, off by default. Before this build the option was read
   by the uninstaller and set by nothing. When on, every table this plugin created
   is dropped, including the eleven Search Console and analytics tables the old 
   uninstaller left behind, and the analytics form’s own switch removes only that
   module’s tables.
 * Fixed: the uninstaller’s last chance write of the links died on a missing class
   when it was needed, because two of the files it depends on were never loaded 
   there.
 * The migration wizard now copies category keywords and custom links as well as
   post keywords; both routines existed and neither was called.
 * Fixed: keeping a category archive as the right target on the anchor conflict 
   screen stripped the phrase from that archive instead, because a term reference
   is negative and the handler dropped the sign.
 * Fixed: a purely numeric keyword such as 1405 lost its phrase when the post and
   term keyword maps were merged, because PHP renumbers integer keys on merge.
 * Fixed: the keyword chips on the Custom links screen had no handler, so a keyword
   could not be typed for a custom link.
 * Fixed: on the Add category screen, the keyword chips of the term just added survived
   the form reset, so the next term silently inherited them.
 * Fixed: removing several anchors at once from the anchor statistics table threw
   before the confirmation appeared.
 * Fixed: a per page outgoing cap set in the editor’s side box was ignored unless
   the site wide cap was also on.
 * Fixed: while another index build held the lock, the screen re-requested the batch
   in a tight loop; it waits five seconds now.
 * Fixed: the two step confirmation on Delete permanently for a term was skipped,
   because the post handler armed the button first.
 * Fixed: the success message after a delete with redirect printed the status code
   where the count belongs, so it read 5 rows moved to trash and 301 redirects recorded.
 * Fixed: the character counter added two spaces around `%sep%`, so the usual template
   counted two characters long.
 * The Minimum impressions setting in Search Console now does what its label says:
   a query below it is never written for. Before this build only the gaps screen
   read it.
 * The “Correct Arabic yeh and kaf” setting in Search Console is applied to the 
   text the engine writes, in sections, rewrites, titles and drafts. Before this
   build nothing read it.
 * Two switches that no code read are gone from the linking settings, Respect existing
   links and Link each phrase once per page, and the screen says instead that both
   rules are always on, which they always were.
 * A dead Links button and its toggle are gone from the grid; the linking panel 
   that does that job is the Show automatic linking toggle. The open page button
   is labelled View rather than Impressions. The headings tool’s description says
   H1 to H6, which is what it edits. The Link Nurse self test tooltip says 24 tests,
   which is how many there are. Several settings hints and tour steps in Search 
   Console now describe what the code does rather than what an earlier design intended.
 * The delete with redirect success message and the price note carry new text, and
   every new or changed string is translated into Persian and Arabic.
 * Every table and column name that reaches a query comes from one small class, `
   Megop_SEO_Db`, which builds the name itself from a literal list and hands back
   an empty string for anything it does not recognise. The three table helpers in
   the linking, Search Console and analytics modules all delegate to it, and the
   CSV exporter’s column list and date column pass through the same check before
   they are used.
 * Fixed a query shape that was safe and indefensible at the same time. The index
   status screen built a WHERE clause with `$wpdb->prepare()` and then concatenated
   that prepared fragment into a string handed straight to `get_results()`. Each
   of the five cases is a whole statement now, prepared once, with its own bound
   values and a bound limit.
 * The `phpcs:ignore` reasons are rewritten. There were 136 copies of one 300 character
   paragraph explaining why an interpolated table name was safe. That explanation
   lives in `Megop_SEO_Db` now, and each suppression carries one short reason that
   is true of the line it sits on. Plugin Check reports no errors and no warnings.
 * The donate link is removed. It pointed at the site’s front page rather than at
   a donation page.
 * This readme was rewritten from that reading of the code. Every capability described
   on this page was checked against the file that implements it, and several claims
   that were more than the code did have been cut or corrected.

#### 8.0.2

 * Fixed, and this is the one that made the scanner look like it was working: Link
   Nurse could only see links written as full URLs. The extractor collected every
   href and src on the page and then discarded anything that did not begin with 
   http, one line later. A link written as `/product/name/`, the form the editor
   produces when you pick a page from the link dialog, was gathered, thrown away,
   and never checked. On the site this plugin is developed against, 332 of the 1,373
   links in published content were stored that way, across 188 of its 364 items:
   a quarter of every link and half of every page, invisible on every scan since
   the module was written. Ten of them were internal 301s that a crawler had listed
   and Link Nurse never had. Relative links are now resolved against the page they
   sit on, exactly as a browser resolves them, and checked like every other link.
 * A repair now rewrites the link in the form the author wrote it. Both forms are
   kept for each finding: the absolute one, because that is what a status code belongs
   to, and the stored one, because that is what has to be found and replaced in 
   the content. A redirect fixed on a site-relative link stays site-relative afterwards,
   because absolute against relative is a house style and a repair has no business
   changing it.
 * Fragments (`#section`) and non-http targets (`mailto:`, `tel:`, `javascript:`)
   are recognised and skipped instead of being swept up. Protocol-relative links(`//
   example.com/x`) are resolved and checked.
 * Fixed: the XLSX export always ended on a 404. The report was written into `uploads/
   megop-seo/` while the browser was sent to `uploads/megop-link-nurse/`, one folder
   apart, with the file correct on disk the whole time. Both sides read one constant
   now, and if the file is genuinely missing the screen says so instead of navigating
   away from the results.
 * Report filenames carry a random suffix. A report holds the site’s own link data,
   and a timestamp-only name in a public uploads folder is guessable.

#### 8.0.1

 * Fixed, and this is the one worth reading: Link Nurse could not see a single redirected
   link. Redirect handling sat behind a setting whose own label reads “Rewrite 301/
   302 to final 200 destination”, a repair switch that had been wired as a detection
   gate. With it off, the scanner never resolved where a 301 went, the classifier
   had no destination to judge, and every redirected link on the site was recorded
   as healthy. A crawl of one real site listed twenty internal 301s inside published
   posts that Link Nurse had passed over on every previous scan. Detection now always
   runs, and the setting decides only whether the link is rewritten for you. A redirect
   found while rewriting is off is listed as Manual, with its destination and a 
   note naming the checkbox that unlocks it.
 * Fixed: a redirect that lands on an error page was reported as healthy. The old
   branch would only speak if the chain ended in 200, so a 301 into a deleted product,
   the exact shape WooCommerce leaves behind, fell through as though nothing were
   wrong. It is reported as broken now, with both codes, for example HTTP 301 to
   404.
 * Fixed: a redirect whose destination could not be established, whether a chain
   still redirecting after ten hops, a loop, or a follow request that failed, was
   treated as fine, and one fallback path assigned a 200 to a destination it had
   never reached. Those are reported as Manual with the reason stated, and no link
   is ever rewritten to a target the plugin did not verify.
 * The results table shows the destination on every row that has one, not only on
   rows the plugin intends to rewrite.
 * A finding is no longer dropped because the theme prints the link in a different
   form. Front-page verification now also matches on path and query, so a link rendered
   with a different scheme, without www., protocol-relative, or as a host-relative
   href is still recognised.
 * Link Nurse’s heading no longer carries a version number of its own. The unified
   header was still appending it; only the standalone fallback header had been corrected
   in 8.0.0.

#### 8.0.0

 * Fixed, and this is the one that mattered: the Link Nurse settings form never 
   saved anything. Two separate faults, one on each side. In the browser, the whole
   settings object was handed to URLSearchParams, which stringified it, so PHP received
   the literal text “[object Object]” where it expected an array, fell back to defaults
   for every key and answered “Settings saved”, which was true of nothing. And the
   module enqueued its own JavaScript with a frozen version constant instead of 
   the file’s modification time, so the corrected file sat on the server while every
   browser kept running the copy it had cached months earlier. The first fault made
   saving impossible; the second made fixing it invisible. Both are gone.
 * Saving settings now says so. A green tick on success, a red line with the reason
   on failure, and the form is repainted from what the server actually stored rather
   than from what was typed, so if a value was clamped the field shows the stored
   one and the confirmation is telling the truth.
 * Link Nurse can refresh the sitemap, and then follow it. Clearing a sitemap cache
   tells you nothing on its own, so the button does the second half too: every address
   the rebuilt sitemap lists is fetched with Link Nurse’s own checker, and anything
   that does not answer 200 is reported. This reaches a class of problem no content
   scan can find: a URL listed for search engines but linked from nowhere on the
   site, such as a category whose last post was deleted. On one real site it found
   nine dead category URLs and twenty-one redirects the first time it ran. On by
   default, switchable in the scan settings.
 * Fixed: opening the Link Nurse settings panel pushed the progress bar and every
   counter a screenful down the page, exactly while a scan was running and they 
   were the thing worth watching. The progress bar sits above the panel now.
 * Link Nurse no longer prints a version number of its own in its heading. It was
   a plugin once and it is a module now; the only version that means anything is
   the plugin’s.
 * Persian and Arabic catalogues cover the new strings, so the sitemap screens read
   in the site’s language from the first run rather than after the next translation
   round.
 * Deactivating the previous internal-linking plugin, offered at the end of the 
   migration, now requires the capability to manage plugins. It was reachable by
   anyone the keyword-editor setting allowed, which an administrator can widen to
   author level, correct for editing keywords and far too wide for switching a plugin
   off.
 * The bulk duplicator’s find-and-replace pairs are sanitised before they reach 
   a post title or body.
 * Every remaining inline script tag is printed through WordPress rather than echoed
   by hand, including the tour’s JSON data block, so nothing that filters, defers
   or adds a nonce to script output is bypassed.
 * The uninstaller now also removes Link Nurse’s own options. Its prefix differs
   from the plugin’s by one letter, so the existing sweep never reached them.
 * The plugin page has been rewritten with a full FAQ covering internal linking,
   bulk editing, broken links and the sitemap check.

#### 7.5.0

 * New: Link Nurse can refresh the sitemap, and then follow it. Clearing a sitemap
   cache tells you nothing on its own, so the button does the second half too: every
   address the rebuilt sitemap lists is fetched with Link Nurse’s own checker, and
   anything that does not answer 200 is reported. This reaches a class of problem
   no content scan can find: a URL that is listed for search engines but linked 
   from nowhere on the site, such as a category whose last post was deleted. On 
   one real site this found nine dead category URLs and twenty-one redirects that
   every previous scan had passed over. It is on by default and can be switched 
   off in the scan settings.
 * Fixed: Link Nurse settings were never saved. The browser sent the whole settings
   object as one form value, so PHP received the text “[object Object]” where it
   expected an array, fell back to the defaults for every key, and answered “Settings
   saved”, which was true of nothing. Values are now sent in PHP’s own bracket notation
   and arrive as the array the sanitiser expects.
 * Fixed: opening the Link Nurse settings panel pushed the progress bar and every
   counter a screenful down the page, exactly while a scan was running and they 
   were the thing worth watching. The progress bar sits above the panel now.
 * Deactivating the previous internal-linking plugin, offered at the end of the 
   migration, now requires the capability to manage plugins. It was reachable by
   anyone the keyword-editor setting allowed, which can be widened to author level,
   correct for editing keywords and far too wide for switching a plugin off.
 * The bulk duplicator’s find-and-replace pairs are sanitised before they reach 
   a post title or body.
 * Two remaining inline script tags are printed through WordPress instead of being
   echoed by hand, so anything that filters or defers script output can see them.
 * The uninstaller now also removes Link Nurse’s own options. Its prefix differs
   from the plugin’s by one letter, so the existing sweep never reached them.

#### 7.4.3

 * Fixed: a fatal “cannot redeclare” error on the automatic-linking screen. The 
   link statistics template declared a helper function at file level, and PHP registers
   a function declared in an included file globally even when the include runs inside
   a method, so any request that rendered that template twice stopped the whole 
   screen. The helper is a static method on the report class now, templates declare
   no functions at all, and the sort links lost a double escaping along the way:
   the URL is built once and escaped at the point of output.

#### 7.4.2

 * New: a Refresh sitemap button on the bulk manager header. Publishing a post clears
   the sitemap cache on its own; bulk work does not, and the bulk editor is exactly
   where a hundred rows change without anyone pressing Publish. The button clears
   whatever the active generator caches, whether Rank Math, Yoast SEO or All in 
   One SEO, and then reads the sitemap back over HTTP the way a crawler would, the
   index and every child file, so they are already rebuilt before a crawler asks
   for them. It reports how many sitemaps were rebuilt and how many URLs they list.
   It can be switched off under Settings, and it is hidden on a site with no sitemap
   at all.
 * Dark mode is a saved preference now, and it is offered only where it is real.
   It used to live in the address bar, so it lasted exactly one click: opening the
   next module dropped the parameter and the screen snapped back to light. It is
   stored per person now and it stays. The toggle is printed on the bulk manager
   alone, because the analytics and broken-link modules declare their palettes on:
   root, where no wrapper class can reach them, and a switch that darkens the frame
   while every card inside stays white is worse than no switch at all. Those two
   modules need a stylesheet rewrite of their own, and that is its own release.
 * The header no longer turns round when you change language. Direction is taken
   from the dashboard, so reading these screens in English on a Persian site translates
   the words and leaves the logo and the controls exactly where they were.
 * The settings screen carried a second row of tabs above the form. The Internal
   Analytics settings are shown underneath the linking settings now, on the one 
   page, with one navigation instead of two.
 * The plugin page now describes all five modules in full, states plainly which 
   parts use Rank Math’s fields and which do not, and names the author. Documentation
   only, and no code changed.

#### 7.4.1

 * Fixed: Link Nurse and Internal Analytics both registered their admin page under
   the same slug. WordPress runs every callback registered on a slug, so opening
   Internal Analytics also printed the whole Link Nurse screen above it. Link Nurse
   has its own slug now, and each screen prints only its own content.
 * Plugin Check reports nothing at all, no errors and no warnings. Input from the
   bulk editor, the keyword fields and the Search Console screens is unslashed and
   sanitized at the point it is read, and every remaining notice is a direct query
   against one of this plugin’s own tables that now carries a written reason a reviewer
   can check line by line.
 * load_plugin_textdomain() is gone. WordPress has loaded the catalogue by itself
   since 4.6, and the per-user language switch is unaffected.

#### 7.4.0

 * Every string with a placeholder now carries a translators comment, so a translator
   can see what each one refers to before guessing.
 * One message had its placeholders in a fixed order, which meant a translation 
   that reordered them would swap the two numbers. Both are now positional.
 * Output escaping reviewed at every point of output; inline scripts now go through
   wp_print_inline_script_tag().
 * unlink(), is_writable() and parse_url() replaced with their WordPress equivalents.

#### 7.3.0

 * Every screen can now be read in English, Persian or Arabic, and each person chooses
   for themselves, because the choice is stored per user and applies only to this
   plugin.
 * The JavaScript is translated too. Confirmations, live counters and inline messages
   follow the same choice as the rest of the screen.
 * The Link Nurse module folded into the plugin’s own text domain.
 * The Analytics module is translatable for the first time; its screens were hard-
   coded in Persian.
 * Catalogues rebuilt with the standard WordPress tooling, complete in Persian and
   Arabic.

#### 7.2.0

 * Index status: ask Google what it knows about each page, grouped by reason.
 * A guided tour on every Search Console screen, switchable off per user.
 * Every stylesheet rule now lives in one enqueued file.

#### 7.1.0

 * Search Console module: per-page keyword coverage, query-level effect measurement,
   control group and automatic freezing of pages that fall after an edit.
 * Persian and Arabic-Indic digits are now folded during matching. On a mixed-script
   site this alone corrected about a third of the coverage verdicts.
 * WooCommerce price is editable in the bulk manager, including variable products.
 * Blacklists and whitelists are no longer cleared when settings are saved from 
   an unrelated screen.

## Meta

 *  Version **8.3.0**
 *  Last updated **5 hours ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.0 or higher **
 *  Tested up to **7.1**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [broken links](https://wordpress.org/plugins/tags/broken-links/)[bulk edit](https://wordpress.org/plugins/tags/bulk-edit/)
   [internal links](https://wordpress.org/plugins/tags/internal-links/)[persian](https://wordpress.org/plugins/tags/persian/)
   [Search Console](https://wordpress.org/plugins/tags/search-console/)
 *  [Advanced View](https://wordpress.org/plugins/megop-seo/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/megop-seo/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/megop-seo/reviews/)

## Contributors

 *   [ megop ](https://profiles.wordpress.org/hamuo1372/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/megop-seo/)