=== AutoSEO — AI Content Publisher ===
Contributors: boumzoud
Donate link: https://autoseo.it.com
Tags: seo, ai, content, woocommerce, publishing
Requires at least: 6.4
Tested up to: 7.0
Requires PHP: 8.0
Stable tag: 3.2.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Publish AI-written articles from AutoSEO, fix SEO gaps on the content you already have, and keep your SEO plugin's own fields filled.

== Description ==

AutoSEO connects your WordPress site to the AutoSEO content platform at autoseo.it.com. Articles you generate there are delivered to this site over an authenticated REST endpoint and created as posts, pages or a custom post type — as drafts, published immediately, or scheduled for a future date.

The plugin's job is to make a delivered article land the way a careful editor would have published it by hand.

= Fills your SEO plugin, not a second set of tags =

AutoSEO detects which SEO plugin your site actually runs and writes to that one only:

* **Yoast SEO** — SEO title, meta description, focus keyphrase, canonical, Open Graph and Twitter fields, primary category
* **Rank Math** — title, description, focus keyword, canonical, Facebook and Twitter fields, robots directives, primary category
* **SEOPress** — title, description, target keyword, canonical, social fields
* **The SEO Framework** — title, description, canonical, Open Graph and Twitter fields

Only one plugin is ever written to. Filling two sets of fields is worse than filling none, because whichever plugin renders the `<head>` wins unpredictably. You can override the detection and pick a specific plugin, or turn the behaviour off entirely.

If **no** SEO plugin is installed, AutoSEO writes its own meta description, Open Graph and Twitter tags and BlogPosting schema — and stops the moment you install one, so your tags are never duplicated.

**All in One SEO is deliberately not written to.** AIOSEO stores post SEO in its own database table rather than in post meta, and this plugin does not write into another plugin's table — a schema change on their side could corrupt it. AIOSEO is detected and the settings screen tells you plainly that titles and descriptions were not synced. Everything else still applies.

= On-publish hygiene =

* Featured image downloaded from the article and set, with ALT text
* Excerpt filled from the meta description or the article body when one is missing
* Author assigned, so posts do not render an empty byline
* Categories and tags mapped, reusing existing terms rather than creating near-duplicates
* Re-delivering the same slug updates the existing post instead of creating a duplicate

= Managed redirects =

Redirect rules you configure in the AutoSEO dashboard are pulled hourly in the background and enforced on the front end. Rules are validated before they are stored: protocol-relative and plain-`http://` destinations are rejected, so a bad rule cannot become an open redirect.

= WooCommerce =

When WooCommerce is active, products are treated as first-class content. AutoSEO can read and report product name, SKU, price, stock status, short and long description length, categories and tags, gallery images and how many of them have no ALT text, and it can fill product SEO fields through whichever SEO plugin is active — exactly as it does for posts.

**AutoSEO never emits Product structured data.** WooCommerce core already emits it, and Yoast WooCommerce SEO, Rank Math and SEOPress Pro each replace Woo's with their own. A second Product entity on one URL is not a cosmetic duplicate; it is conflicting markup Google is willing to drop the rich result for. AutoSEO detects who owns it and stays out of the way.

Products that are out of stock and still indexable are **reported, never changed**. Whether the right answer is `noindex`, a redirect to the replacement, or leaving the page up because stock returns next week is a commercial decision, so AutoSEO surfaces it and stops.

No product fact is ever invented. AutoSEO does not generate a price, a SKU, an availability, a specification or a review.

= Optional: fix the content you already have =

Everything in this section is **off until you turn it on**, in **AutoSEO → Settings → Autopilot**, with a separate checkbox per action:

* SEO title, meta description, focus keyword and canonical
* Post excerpts
* Image ALT text
* Category and author assignment
* Repointing internal links that lead to a 404
* Serving an `/llms.txt` written by AutoSEO, so AI assistants can read a map of your site
* FAQPage schema built **only** from question-and-answer pairs already present in the post
* A visible byline and `<time datetime>` using the post's real author and real dates

Three rules apply to all of them:

1. **Empty fields only.** Unless you separately tick "allow AutoSEO to replace values that are already filled in", AutoSEO fills blanks and never touches a description, title or ALT text you wrote.
2. **Everything is reversible.** The previous value is stored before every write, and **AutoSEO → Log → Changes** lists every modification with a Revert button.
3. **Rate limited.** A per-request cap and a daily cap, both clamped to hard ceilings no setting can raise, so nothing can rewrite a whole site in one go.

= External service disclosure =

This plugin is a client for AutoSEO, a paid third-party service. It will not do anything useful without an AutoSEO account, and it communicates with **autoseo.it.com** and nowhere else.

**What the plugin sends out, and when:**

1. **When you click "Test connection", and shortly after you save a new API key** — a request to `https://autoseo.it.com/api/wordpress/verify` carrying your AutoSEO API key, your site URL, the plugin version and whether WooCommerce is active. This both checks the key and registers this site with your AutoSEO account as a publishing destination. No post content is sent.
2. **Hourly on WP-Cron, and shortly after you save a new API key** — a request to `https://autoseo.it.com/api/redirects` carrying your API key, to fetch your redirect rules. Nothing about your posts is uploaded.
3. **Only if you enable Autopilot** — an hourly request to `https://autoseo.it.com/api/wordpress/tasks` carrying your API key, your site URL and the list of actions you have permitted, to fetch pending fixes; followed by a request reporting what was applied or refused. With Autopilot off, neither request is ever made.
4. **Only if you leave IndexNow enabled** — a request to `https://autoseo.it.com/api/wordpress/indexnow` listing URLs on your site that have just changed, so AutoSEO can submit them to IndexNow (Bing, Yandex, Seznam, Naver). **URLs only — no content.** The IndexNow key lives on the AutoSEO server and is never shipped inside this plugin.

As with any request WordPress makes, the default WordPress user-agent header includes your WordPress version and your site URL.

**What AutoSEO can read back from your site:** AutoSEO calls in to this site holding your API key. With that key it can create and update posts; read a status summary (site URL, configured post type and status, how many AutoSEO articles exist, which SEO plugin was detected, whether WooCommerce is active, and which actions you have permitted); read a queue of status changes and deletions for articles AutoSEO itself delivered; read a site inventory (per post: title, current SEO fields, excerpt, word count, images and missing ALT text, internal and external link counts, dates, author, categories and tags — and for products, SKU, price, stock status and description lengths); scan for internal links that 404; and apply the fixes you have permitted. Requests without a valid key are rejected with HTTP 401.

**Cache safety.** All AutoSEO REST responses are sent with `Cache-Control: no-store`, `Vary: X-AutoSEO-Key, Authorization` and the opt-out headers LiteSpeed, WP Rocket, W3TC and Varnish honour, so a page cache cannot store an authenticated response and replay it to the public.

The plugin loads no remote code, no remote scripts and no remote stylesheets. The AutoSEO API returns data only.

* Privacy Policy: https://autoseo.it.com/privacy-policy
* Terms of Service: https://autoseo.it.com/terms-of-service

== Installation ==

1. Upload the plugin through **Plugins → Add New → Upload Plugin**, or install it from the WordPress plugin directory.
2. Activate it. **AutoSEO** appears in your admin sidebar.
3. In the AutoSEO dashboard, open **WordPress Plugin** and generate a connection key.
4. Paste the key into **AutoSEO → Settings** and click **Save settings**.
5. Click **Test connection**. The result line tells you exactly what passed or failed.

Saving only stores the key locally; it never contacts AutoSEO. The connection test is the step that proves the key works.

== Frequently Asked Questions ==

= Do I need a paid AutoSEO account? =

Yes. The plugin is a client for the AutoSEO service at autoseo.it.com and does nothing on its own. See the external service disclosure in the Description.

= Does it work with Yoast SEO? =

Yes. AutoSEO detects Yoast and fills its SEO title, meta description, focus keyphrase, canonical and social fields.

= What about Rank Math, SEOPress or The SEO Framework? =

All three are detected and filled the same way. AutoSEO writes to exactly one SEO plugin — whichever is active — so you never end up with two competing sets of tags. You can override the choice under **AutoSEO → Settings → SEO Plugin**.

= What about All in One SEO? =

AIOSEO is detected, but AutoSEO does not sync titles and descriptions into it. AIOSEO stores post SEO in its own database table rather than in post meta, and writing directly into another plugin's table risks corrupting it on their next schema change. Everything else — excerpt, image ALT text, categories, tags, author, scheduling — still applies, and the settings screen says so when AIOSEO is the active plugin.

= What happens if I have no SEO plugin at all? =

AutoSEO writes its own meta description, Open Graph and Twitter tags and BlogPosting schema. It stops doing this as soon as an SEO plugin is active.

= Will it overwrite meta I wrote by hand? =

No. Empty values are skipped, so AutoSEO fills blanks rather than replacing your work.

= Will re-sending an article create a duplicate post? =

No. A delivery whose slug already exists updates that post in place.

= Where do articles go? =

Whatever post type and status you choose under **AutoSEO → Settings**. The default is a draft, so nothing is published without your say-so until you change it.

= Does saving settings contact AutoSEO? =

No. Saving is entirely local and performs no network requests at all. Only the connection test and the hourly redirect sync talk to autoseo.it.com.

= Something failed but I see no error. Where do I look? =

**AutoSEO → Log → Activity.** Every delivery, connection test, autopilot run and applied fix is recorded there — including the ones that were rejected or failed, with the reason. Filter by type and date. The **Support diagnostics** tab gives you one copy-paste block containing your plugin, WordPress and PHP versions, connection state, permitted actions and the last 40 log lines; your API key is never included in it.

The plugin also writes failures to your PHP error log, prefixed with `[AutoSEO]`, because by design it never takes your site down when it fails.

= I pressed publish in AutoSEO and nothing arrived. =

Check **AutoSEO → Log → Activity** and filter to "Article delivery". A delivery that was rejected (missing title, empty content) or that WordPress refused to save is logged there with the reason.

= Will AutoSEO change posts I wrote myself? =

Not unless you ask it to. Every action that modifies existing content is off by default. When you do enable one, AutoSEO only fills fields that are **empty** — replacing a value you typed additionally requires ticking "allow AutoSEO to replace values that are already filled in". Every change is listed in **AutoSEO → Log → Changes** with a Revert button.

= Does the plugin create database tables? =

No. It stores its configuration, its activity log and its change log in options, and marks delivered posts with post meta. The activity log is capped at 300 entries and 30 days and is never autoloaded.

== Screenshots ==

1. AutoSEO → Settings: connection, publishing defaults and SEO plugin selection, with the detected SEO plugin named.
2. The connection test, reporting exactly why a key passed or failed.
3. AutoSEO → Settings → Autopilot: one checkbox per action type, all off until you switch them on.
4. AutoSEO → Log → Activity: every delivery, connection test and applied fix, including the ones that failed.
5. AutoSEO → Log → Changes: before and after values, with a Revert button on every change.
6. AutoSEO → Help: what the plugin sends and receives, troubleshooting, and the diagnostics table.

== Changelog ==

= 3.2.2 =

**Plugin Check**

* Resolved the two `WordPress.DB.SlowDBQuery` warnings Plugin Check raised on
  the pull-publishing duplicate guard. The lookup itself is unchanged — it is
  a single-row existence check on the plugin-owned `_autoseo_article_id` meta
  key, run once per pulled article inside the hourly cron and never on page
  views — and is now documented and annotated as such at the query site.

= 3.2.1 =

**Namespace (WordPress.org plugin review)**

* Every function, constant, option, nonce, cron hook and AJAX action is now
  prefixed `autoseopub_` / `AUTOSEOPUB_` instead of the bare `autoseo_`.
  Another plugin in the directory uses the `autoseo_` prefix — including the
  identical `autoseo_api_key` option name — so both plugins on one site would
  silently overwrite each other's settings. Existing installations are
  migrated automatically: every legacy option value is copied to its new name
  once on upgrade (settings, permissions, activity log, change/undo history)
  and legacy cron events are cleared. Post meta keys (`_autoseo_*`), the REST
  namespace (`autoseo/v1`) and the admin page slugs are unchanged, so the
  AutoSEO service, existing bookmarks and delivered-post tracking keep working
  without any action.
* Yoast term SEO fields are now written through Yoast's own
  `WPSEO_Taxonomy_Meta::set_values()` API when it is available; the direct
  read-modify-write of `wpseo_taxonomy_meta` (Yoast's storage for term meta —
  not an AutoSEO option) remains only as a fallback and still preserves every
  other value in that option untouched.

**Fixed**

* The 3.1.11 "ALT text for unattached media" permission could never actually
  be switched on: the capability was missing from the option map every other
  surface derives from, so its checkbox saved nowhere and the permission
  always read as off (it also raised an "Undefined array key" warning on the
  settings screen under WP_DEBUG). The toggle now works.
* The Activity log screen no longer raises "Undefined array key" warnings on
  a WP_DEBUG site when a log row in the database is missing fields — rows are
  normalised on read and render as blanks instead.

= 3.2.1 =

**Added**

* **Pull publishing.** The site now also collects its own queued articles from
  AutoSEO once an hour, instead of only waiting to be pushed to. Publishing used
  to fail outright whenever something sat in front of WordPress and refused us —
  a firewall, a WAF, a Cloudflare challenge, a login wall on a staging host, or
  a server not reachable from outside. An outbound request has none of those
  failure modes. Push remains the default because it is immediate; this is the
  safety net that catches whatever push could not deliver, and for a firewalled
  site it is the only path that works. Articles collected this way appear in the
  Activity log under "Collected from AutoSEO", and an article can never be
  published twice.

**Hardening (WordPress.org plugin review)**

* Every text field arriving from the AutoSEO API is now passed through
  `sanitize_text_field()` before it is written to post meta. The review flagged
  `focus_keyword`, and the fix was applied at the shared accessor those values
  all pass through, so `title`, `description`, `og_title`, `og_description`,
  `twitter_title` and `twitter_description` are covered by the same change
  rather than left for a later report. URL fields keep `esc_url_raw()` and the
  primary-category id keeps its integer sanitiser — each field now uses the
  sanitiser appropriate to its own type.

= 3.1.12 =

**Hardening (WordPress.org plugin review)**

* The FAQPage and BlogPosting JSON-LD blocks are now encoded with
  `JSON_HEX_TAG` and `JSON_HEX_AMP` and without `JSON_UNESCAPED_SLASHES`, so
  no post title, description, question or answer can ever contain a character
  sequence the HTML parser would read as markup inside the script element.
* The optional heading-order fix no longer leaves its output buffer to be
  flushed implicitly at the end of the request. The buffer is opened with a
  recorded stack depth and explicitly closed by the plugin's own shutdown
  handler (priority 0, ahead of WordPress core's buffer flush), which also
  folds in — in the correct order — any inner buffer another component opened
  on top and abandoned.
* The WordPress.org directory icon is no longer bundled inside the plugin zip;
  directory assets ship separately via SVN.
* Text domain changed to `autoseo-publisher` to match the plugin's directory
  slug. The plugin folder name is unchanged, so existing installations are
  unaffected.

= 3.1.11 =

**Images**

* New capability: **ALT text for unattached media**. Bulk importers (car
  inventory, product feeds) create thousands of media items that belong to no
  post — on one live site 262 of the first 300 images had no ALT text and no
  parent. The existing Image ALT action could never reach them: it verifies the
  image is actually used by the post it is applied from, which an orphaned
  attachment can never satisfy. This is the same write with the one rule that
  cannot apply replaced by a narrower one — it refuses any attachment that DOES
  have a parent, so it never becomes a way around the stricter check. It is a
  separate toggle, off unless you enable it, and it never replaces ALT text you
  already wrote.

= 3.1.10 =

**Plugin review**

* Cleared the last four Plugin Check warnings. Three were already meant to be
  suppressed, but the `phpcs:ignore` comments sat above the `if ( ! defined( ... ) )`
  guard rather than the `define()` on the next line, and the annotation only ever
  applies to the line directly after it — so all three were still reported. The
  constants themselves are unchanged: `DONOTCACHEPAGE`, `DONOTCACHEOBJECT` and
  `DONOTCACHEDB` are fixed names read by W3 Total Cache, WP Super Cache and
  LiteSpeed, and prefixing them would stop page caches honouring the no-cache
  request on an authenticated REST route.
* The pagination parameter on the Activity Log now goes through `absint()` instead
  of a cast inside a helper. The value was never unsafe — an integer cast cannot
  carry an injection — but the sniff recognises sanitizing functions rather than
  casts, so it read as unsanitized input in review.

= 3.1.0 =

**Security**

* Fixed a real cache-poisoning leak. On a site behind LiteSpeed Cache, `GET /wp-json/autoseo/v1/status` with **no API key** returned HTTP 200 and the full response body, with `x-litespeed-cache: hit`; the same URL with a cache-buster correctly returned 401. The key check was never wrong — the page cache had stored a response generated for an authenticated request and was replaying it publicly, exposing the site's WordPress and PHP versions and its AutoSEO configuration. Every AutoSEO REST response now carries `Cache-Control: no-store`, `Pragma: no-cache`, `Vary: X-AutoSEO-Key, Authorization`, `X-LiteSpeed-Cache-Control: no-cache` and `X-Accel-Expires: 0`, and sets `DONOTCACHEPAGE` plus the LiteSpeed and WP Rocket opt-out hooks. Any page cache does this by default; the authenticating header is not part of its cache key.
* Removed `wp_version` and `php_version` from the `/status` response. AutoSEO never used either one, and the least data is the best mitigation.

**Connection**

* The plugin now registers itself. Previously "Test connection" proved your key was valid and nothing more — so it was possible to see "Connected" here while AutoSEO had no record of your site at all, and every article in the app still said "Connect a CMS to publish". The connection test now also registers this site as a publishing destination and reports whether that succeeded. It works from behind firewalls, staging authentication, Cloudflare challenges and WooCommerce coming-soon mode, none of which allow AutoSEO to call in to your site.

**WooCommerce**

* WooCommerce is detected and products are treated as first-class content: reported in the site inventory with SKU, price, stock status, description lengths, categories, tags and gallery ALT coverage, and writable through the active SEO plugin.
* AutoSEO never emits Product structured data, and reports which plugin owns it on your site, so you can never end up with two conflicting Product entities on one URL.
* Products that are out of stock and still indexable are reported, never changed.

**New: fix existing content (all opt-in, all reversible)**

* Site inventory endpoint — reports the real SEO state from the database, not from scraped HTML.
* Apply-fixes endpoint — SEO title, meta description, focus keyword, canonical, excerpt, image ALT, category, author, term/archive meta. Supports `dry_run` and reports exactly what would change.
* Autopilot — an hourly WP-Cron job that applies approved fixes with no dashboard needed. Off by default.
* Broken internal link detection, with in-place repointing when a redirect target is known.
* IndexNow — the plugin tells AutoSEO which URLs changed; the key stays on the server.
* GEO / AI-Overview support: reports whether GPTBot, ClaudeBot, PerplexityBot, Google-Extended and bingbot (which Copilot rides on) are allowed by robots.txt; can serve an AutoSEO-authored `/llms.txt`; can mark up FAQPage schema **only** from Q&A already in the post; can add a visible byline and `<time datetime>` from the post's real author and dates.
* Safety throughout: one checkbox per action type, all defaulting to off; empty fields only unless overwriting is separately enabled; the previous value stored before every write with a per-change Revert button; per-request and per-day change caps clamped to hard ceilings; and a dry-run mode.

**Logging**

* **AutoSEO → Log** is now a real diagnostic surface: an Activity tab recording every delivery, connection test, autopilot run and applied fix — including rejections and failures, with the reason — filterable by type and date; a Changes tab with before/after values and per-change Revert; the delivered-articles list; and a Support diagnostics tab with a one-click copy block that never includes your API key. Previously a rejected delivery left no trace anywhere you could see it.
* The activity log is capped at 300 entries and 30 days, stored non-autoloaded, and can be cleared from the screen.

**Compatibility**

* Tested up to WordPress 7.0.
* Text domain corrected to `autoseo-publisher` to match the plugin name. The plugin folder name is unchanged, so existing installations are unaffected.

= 3.0.1 =

This release changes no features. It exists so that a broken AutoSEO can never take a site offline.

* Hardened: the plugin's bootstrap now checks that every one of its own module files is present and readable *before* requiring it. A `require` on a missing file is an uncatchable PHP fatal that fires on every request — front end included — so a file removed by a host's malware scanner, or never written by an interrupted upload, could previously take a whole site to HTTP 500 with no way to reach wp-admin and deactivate. AutoSEO now switches itself off, logs the missing filename, and shows an admin notice. The site keeps serving.
* Hardened: the module loading and all hook registration run inside `try { } catch ( Throwable )`. A failure disables AutoSEO's features instead of ending the request.
* Hardened: a PHP version floor is checked before any of the plugin's code loads. WordPress enforces "Requires PHP" at activation but does not re-check afterwards, so a host that moves a site to an older PHP could previously fatal every page. AutoSEO now switches itself off with a notice instead.
* Hardened: activation now aborts with a readable message and leaves the site untouched if anything throws, rather than reporting success and leaving a half-migrated install.
* Hardened: admin-only hooks are no longer registered on front-end requests. On a page view AutoSEO now touches `template_redirect`, `wp_head` and the canonical filter, and nothing else.
* Hardened: the redirect handler skips AJAX, cron, REST, WP-CLI and XML-RPC requests, only acts on GET/HEAD so a POST body can never be dropped, and refuses to act before `init` has fired.
* Fixed: a redirect rule pointing a path at itself is now rejected when stored and skipped when applied. An infinite redirect on `/` takes a site off the internet as effectively as a fatal does.
* Fixed: a missing admin page template renders a notice on that one screen instead of fataling wp-admin.

= 3.0.0 =

* Fixed: "There has been a critical error on this website" when saving settings. 2.5.0 and 2.6.0 used the WordPress Settings API, so the save was executed by `wp-admin/options.php` inside core's own loop — where any error in a sanitize callback becomes a white screen the plugin cannot intercept or explain. 3.0.0 removes `register_setting()` entirely; the form now posts to a handler this plugin owns end to end (capability check, nonce check, its own sanitisers, then a redirect with a notice). A failure is now a red admin notice, never a fatal.
* Fixed: PHP 8 `TypeError` in the REST permission check — `hash_equals()` could receive NULL when the API key header was absent.
* Added: AutoSEO is now a top-level item in the admin sidebar with Settings, Publish Log and Help screens, instead of being buried under Settings. Old `options-general.php` bookmarks are forwarded to the new location.
* Added: SEO plugin auto-detection. Rank Math, SEOPress and The SEO Framework are supported alongside Yoast, and only the active one is written to. Rank Math's robots field is correctly stored as an array.
* Added: All in One SEO is detected and reported but deliberately not written to, because it stores post SEO in its own database table.
* Added: a "Settings" link on the plugin's row on the Plugins screen.
* Added: post status and deletion tracking, so AutoSEO's view of what is published stays accurate.
* Added: on-publish hygiene — automatic excerpt, featured-image ALT text, author assignment, canonical support, and BlogPosting schema when no SEO plugin owns the head.
* Changed: the single-file plugin is now a bootstrap plus six focused modules.
* Changed: re-publishing the same slug updates the existing post instead of creating a duplicate.
* Hardened: redirect rules from the dashboard are validated before being stored; protocol-relative and plain-http destinations are rejected so a bad rule cannot become an open redirect.
* Hardened: every hooked callback is wrapped so no exception can reach WordPress. Failures go to the PHP error log instead of fataling the page.
* Unchanged: option names, so existing installs keep their configuration. The old `autoseo_yoast_enabled` boolean is migrated to the new SEO plugin setting.

= 2.6.0 =

* Fixed: saving the API key triggered a blocking 15-second outbound request inside the save request itself; any failure surfaced as a fatal admin error. The redirect sync is now deferred to WP-Cron and the save path does no network I/O.
* Reduced outbound HTTP timeout from 15s to 8s.
* Fixed: the "— None —" category no longer saves as category #1.
* Fixed: front-end redirect matching no longer passes `strtok()`'s FALSE into `untrailingslashit()`.
* Added: the REST routes also accept `Authorization: Bearer <key>`.
* Added: the connection test reports a specific reason instead of a bare failure.

= 2.4.1 =

* Security: constant-time API key comparison, nonce verification on AJAX handlers, capability checks on admin actions, input sanitization and output escaping throughout.
* Added: SSL verification enabled by default on outbound requests.
* Added: post status validation against an allowlist.

== Upgrade Notice ==

= 3.1.0 =
Adds the SEO autopilot: AutoSEO can fill missing meta, alt text and excerpts on a schedule, with per-action permissions you control. Also fixes featured images on some hosts and stops a re-publish changing a live post's status.

= 3.0.1 =

Safety release. If any part of AutoSEO is missing or fails to load, the plugin now switches itself off and logs why, instead of being able to return HTTP 500 on every page of the site. No feature changes. Recommended for all users.

= 3.0.0 =

Fixes the critical error some sites hit when saving settings, and the PHP 8 fatal in the REST key check. AutoSEO now has its own sidebar menu and supports Rank Math, SEOPress and The SEO Framework alongside Yoast. Your existing settings are carried over automatically.

= 2.6.0 =

Fixes an HTTP 500 when saving settings and a PHP 8 fatal in the REST permission check. Recommended for all users.
