=== INI Scout ===
Contributors: milenfrom
Tags: seo, schema, open graph, sitemap, indexnow
Requires at least: 5.7
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.6.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Lightweight, automated SEO for WordPress.

== Description ==

INI Scout is a focused, no-bloat SEO plugin for WordPress. It adds solid on-page
SEO with automatic structured data, and stays out of the way of multilingual
plugins (e.g. TranslatePress / WPML — hreflang) and the rest of your stack.

**Dashboard &amp; settings**

* A modern admin UI: vertical-nav settings with cards, accordions and toggle
  switches, plus an at-a-glance SEO dashboard.

**On-page engine**

* Document `<title>` and meta-description templating, per post type and per
  taxonomy, with per-post and per-term overrides.
* Canonical URLs for every context, with per-post override.
* Robots directives via the core `wp_robots` filter (no duplicate tags):
  per-post noindex, plus noindex toggles for search, date and author archives.
* `max-image-preview:large`, `max-snippet:-1`, `max-video-preview:-1` by default.
* Google / Bing site-verification meta tags (output on every page).

**Social**

* Open Graph and Twitter Card tags, with featured-image fallback and a
  site-wide default share image. Per-post share-image override.

**Structured data (Schema.org JSON-LD)**

* `WebSite` (+ optional Sitelinks SearchAction) and
  `Organization` / `LocalBusiness` with address, geo, telephone, opening hours,
  price range and social profiles (`sameAs`).
* `Article` / `NewsArticle` for posts and news.
* Per-post-type schema mapping: assign any post type to a schema type
  (Article, WebPage, Store, Event, SaleEvent, …) from the settings screen, or
  leave it on `auto` for sensible defaults.
* `BreadcrumbList` on every non-home page.

**Sitemap**

* Custom XML sitemap at `/sitemap.xml` (index + per-type / per-taxonomy
  sub-sitemaps) with image entries and `lastmod`; respects noindex and leaves out
  password-protected content. Replaces the WordPress core sitemap. Excludable per
  post type / taxonomy. With plain permalinks it is served at
  `/?iniseo_sitemap=index`.

**Redirects (optional)**

* 301, 302, 307 and 308 redirects and 410 / 451 responses, matched by exact
  address, start, end, contained text or regular expression, with options for
  case and query strings. Rules run before WordPress loads the page and cost
  nothing while there are none.
* Optional hit counts, automatic 301s when the address of published content
  changes, a privacy-friendly 404 log, and CSV import / export (including CSV
  exports of the Redirection plugin). Everything is off until you turn it on.

**Import from other SEO plugins**

* Switching from Yoast SEO, All in One SEO, Rank Math, SEOPress, SmartCrawl or
  The SEO Framework? INI Scout → Import (or `wp iniseo import`) brings over
  titles, descriptions, robots settings, social fields, keyphrases, settings and
  redirects. A preview shows what will happen first; only empty INI Scout
  fields are filled, the old plugin's data is never changed, and every import
  can be undone.

**Set up with an AI agent**

* Let your own AI agent (Claude, ChatGPT, OpenCode, …) configure INI Scout
  through WP-CLI (`wp iniseo …`) or the REST API with an Application Password,
  following a guide built into the plugin. Every change is a dry run until
  confirmed, your own values are kept unless you agree, risky changes need your
  yes, and each agent session can be undone in one click. REST access is off
  until you turn it on.

**IndexNow (instant indexing)**

* Auto-submits published / updated URLs to Bing, Yandex, Seznam and Naver the
  moment content changes (a single ping is shared between them). Google does not
  consume IndexNow — the sitemap + Search Console cover Google.
* Secret key generated automatically and served as a virtual verification file
  (no stray file in the webroot); one-click regenerate and a manual test ping.
* Submits only what may be indexed (never noindexed or password-protected
  content, nor anything while search engines are discouraged). Each change is
  submitted once, at the end of the request that saved it (a bulk edit as one
  batch), after the response has been sent where the server supports it
  (PHP-FPM, LiteSpeed), so the editor save does not wait. Failed batches are
  retried (5 min, 30 min, 2 h); WP-Cron is only a safety net, and visitors
  never wait. Capped, non-autoloaded submission log.

**Dashboard**

* A modern, WordPress-native overview: setup checklist with progress, live
  feature toggles, IndexNow status &amp; recent submissions, and per-content-type
  meta-description coverage bars.

**robots.txt**

* Virtual robots.txt editor that always advertises the sitemap.

**Breadcrumbs**

* `[iniseo_breadcrumbs]` shortcode and `iniseo_breadcrumbs()` template tag,
  sharing the same trail as the BreadcrumbList schema.

== Installation ==

1. The plugin lives in `wp-content/plugins/ini-scout/`.
2. Activate **INI Scout** from Plugins. Activation registers the sitemap rewrite
   rules and flushes them once.
3. Open **INI Scout → Settings** and fill in the Local Business tab (address, geo,
   phone, hours) so the Organization / LocalBusiness schema is complete.
4. Add the verification codes (General tab) if you use Search Console / Bing.

== Notes ==

* Multilingual: hreflang and translated URLs remain TranslatePress's job. Every
  tag this plugin prints is in the rendered HTML, so it is translatable through
  TranslatePress's string editor.
* Caching: all front-end output is produced on `wp_head` and served from the
  existing page cache for anonymous visitors.

== External services ==

IndexNow is optional and disabled by default. When you enable it and publish or
update eligible content, or explicitly submit a URL, INI Scout sends the site
host, public page URLs, verification key and key-file URL to
https://api.indexnow.org/IndexNow. Participating search engines share these
submissions. No account is required. Submission does not guarantee indexing.
Service information and terms: https://www.indexnow.org/terms
Privacy information: https://www.indexnow.org/terms (Privacy section)

Crawl and content analysis fetch public pages from your own site to analyze the
rendered content. No external analytics or telemetry service is used.

== Frequently Asked Questions ==

= How do I switch from the previous SeoWP plugin? =

Back up your database and deactivate SeoWP before activating INI Scout. Existing
settings, post and term metadata, IndexNow keys, shortcodes and integration hooks
are retained. Do not use WordPress's Delete action on the old plugin: its
uninstaller removes shared SEO data. After deactivating it, remove the old plugin
folder through your hosting file manager instead.

= Does INI Scout require the INI platform? =

No. All SEO features work independently. The optional platform integration
exposes an authenticated REST endpoint for a separately installed connector.

= Can I switch from another SEO plugin? =

Yes: INI Scout → Import reads the data of Yoast SEO, All in One SEO, Rank Math,
SEOPress, SmartCrawl and The SEO Framework, even after you deactivate them.
Preview first, then import. Fields you already filled in INI Scout are never
overwritten, and settings are replaced only where INI Scout still has its
default. Anything that has no INI Scout equivalent (for example "force index"
or a custom-field variable) is listed in a downloadable report. Every import
can be undone. Keep the old plugin installed until you have checked the
result: deleting it runs its uninstaller, which removes its data.

= Can an AI agent set up INI Scout for me? =

Yes. Open INI Scout → Dashboard → "Set up with an AI agent" and copy the prompt
into your agent. With SSH it uses WP-CLI; without SSH, turn on REST access there
and give the agent an Application Password. The agent reads the guide built into
the plugin, asks you before applying anything, and every session can be undone
(INI Scout → Agent sessions).

= What does the 404 log store? =

Only when you turn it on: each address that was not found, how often it was
requested (by visitors and by bots, counted apart), when it was first and last
requested, and the host name of the referring site. No IP addresses and no
browser details are stored. Query strings are dropped unless you choose to keep
them. Entries older than 90 days, and the least-requested entries beyond 2,000,
are removed automatically.

= Do redirects keep working if I deactivate INI Scout? =

No. Redirects run inside the plugin, so they stop while it is deactivated and
start again when you reactivate it. Your rules are kept until you delete the
plugin.

== Changelog ==

= 1.6.0 =
* New: Set up with an AI agent. Your own agent (Claude, ChatGPT, OpenCode, …) can configure INI Scout through WP-CLI (`wp iniseo …`) or the REST API with an Application Password, following a guide built into the plugin.
* Every agent change is a dry run until confirmed, never overwrites your own values unless you agree, asks before risky changes, and belongs to a session you can undo in one click (INI Scout → Agent sessions).
* New: `wp iniseo audit` — a read-only SEO check with suggested fixes.
* REST access for agents is off until you turn it on (Dashboard → Set up with an AI agent).
* New: opening days for local business structured data (Settings → Local Business → Opening hours). With no day ticked the hours apply to every day, as before.
* Import: `wp iniseo import run --dry-run` now lists every setting it would change, and takes `--format=json`.
* Fix: a 410 / 451 redirect rule was answered with a 301 to the home page on sites with plain permalinks and a static front page.

= 1.5.0 =
* New: Redirects (optional, off until you turn it on). 301, 302, 307 and 308 redirects and 410 / 451 responses, matched by exact address, start, end, contained text or regular expression. Optional hit counts, automatic 301s when the address of published content changes, a 404 log that stores no IP addresses or browser details, and CSV import / export (also reads Redirection plugin exports). Rules run before WordPress loads the page and cost nothing while there are none.
* New: Import from Yoast SEO, All in One SEO, Rank Math, SEOPress, SmartCrawl and The SEO Framework — titles, descriptions, robots settings, social fields, keyphrases, settings and redirects (INI Scout → Import, or `wp iniseo import`). Preview first; only empty INI Scout fields are filled; the old plugin's data is never changed; every import can be undone.

= 1.4.0 =
* New: "Check for updates" beside the plugin version on the Plugins screen. It asks WordPress.org directly and, when a newer compatible release exists, shows WordPress's own update row with "update now", with no wait for the regular 12-hour update check. It runs only when an administrator clicks it, never contacts WordPress.org in the background, and does not change WordPress's update list or auto-updates.

= 1.3.0 =
* Fix: creating a new post or page no longer breaks the block editor ("slug.replace is not a function"). When a post has neither a slug nor a title yet — every new post, and any untitled draft — WordPress reports the numeric post ID in place of the slug, which the "Search appearance" preview did not expect. The preview now shows no URL path until the post has a real slug, and the content analysis reports "No slug yet" instead of treating the post ID as the slug.
* Fix: with the SEO title left blank, the editor's "Search appearance" and social previews — and the content analysis's SEO title length check — now show the title the site actually outputs: the post type's title template (or the Home title on a static front page), filled with the post title as you type it. They previously used a fixed "Title – Site name" taken when the editor opened, so a new post previewed as just the site name, title edits were not reflected and custom title templates were ignored. The SEO rating stored when you save a post now measures the same title.
* Fix: a static front page's own SEO title and meta description are now used on the site, including in its Open Graph and X (Twitter) tags. They were ignored in favour of the Homepage title and description settings, which remain the default when the page's own fields are blank.
* Fix: the content analysis now measures an SEO title that uses variables (for example "%title% %sep% Special") with the variables filled in, as the site outputs it, instead of the raw template text — both while you edit and in the rating stored when you save.
* Fix: with the meta description left blank, the editor's "Search appearance" and social previews now show the description the site actually outputs — the post type's description template, by default the excerpt (or the Homepage description on a static front page), following the content as you type — instead of saying there is none. The content analysis measures that same description while you edit (it previously measured none) and when you save, and measures a hand-written description that uses variables with them filled in.
* Fix: the posts page's own SEO and social settings — SEO title, meta description, canonical URL, robots options, and the social title, description and image — now apply to the blog index it shows. They were all ignored there. Blank fields keep the blog index's previous output, except that the posts page's featured image, if it has one, is now its share image, as on other pages.
* Fix: the canonical URL on page 2 and later of the blog, category, tag, author and other archives (and of a latest-posts homepage) now matches the address WordPress serves that page at. With plain permalinks it was malformed (for example "?cat=3/page/2/"), with a permalink structure that has no trailing slash it added one, and with /index.php/ permalinks a latest-posts homepage's later pages lacked /index.php.
* Fix: the robots meta tag now outputs `max-snippet:-1` and `max-video-preview:-1` as documented. Their values were missing, which left both directives invalid.
* Fix: the editor previews now fill in every variable the site supports (for example %category% or %currentyear%), not only %title%, %sitename%, %tagline%, %sep% and %excerpt%, so a hand-typed SEO title or description previews as it will be output. %excerpt_only% follows the manual excerpt as you type, and the previews strip shortcodes and HTML exactly as the site does: only registered shortcodes are removed, together with any content they enclose.
* Fix: the meta description field and the content analysis no longer say a blank description falls back to "the excerpt" — the default can also be a description template or, on a static front page, the Homepage description.
* Fix: password-protected posts no longer reveal their text. Without a manual excerpt, the first words of a protected post appeared in its meta description, Open Graph and X (Twitter) descriptions, JSON-LD and llms.txt for every visitor. Protected posts now have no automatic description (a hand-written SEO description is still used), are left out of llms.txt and the sitemap (as in WordPress's own sitemap), and titles no longer show "Protected:" / "Private:" or HTML tags.
* Fix: saving no longer damages the variables %category%, %date%, %caption% and %category_description% (they were stored as, for example, "tegory%") in SEO and social fields, category and tag fields and Settings templates. Values already damaged are repaired once, by the first admin requests after updating (one at a time; a large site takes a few): only values that show the damage are changed, and a notice for administrators lists them (the first 50 by name). A post, category or Settings screen opened before the repair finished and saved after it is repaired as it is saved, and so is a settings file exported by an earlier version when it is imported. Percent-encoded text is kept too: a robots.txt rule for a non-Latin address such as "Disallow: /%D0%BA…" was saved as "Disallow: /", which blocked the whole site, and social profile URLs with non-Latin characters are now accepted.
* Fix: IndexNow submits each change once, and only what may be indexed. Updating a post in the block editor sent its URL twice and held the save until IndexNow answered; posts published with "noindex" ticked, content types hidden from search engines and password-protected posts were submitted. Saved posts are now queued and sent as one batch at the end of the request that saved them (the block editor's second, meta-box request), after the response has gone to the browser on PHP-FPM and LiteSpeed servers; a post saved through the REST API alone goes out with the next request after 30 seconds. Overlapping saves (bulk edits, imports, several editors) are neither lost nor sent twice, a batch whose request dies while sending is sent again, and a batch that fails for a passing reason (no answer in time, HTTP 429 or 5xx) is retried up to three times, after 5 minutes, 30 minutes and 2 hours, on any server. Until something is due, requests do no IndexNow work beyond reading one option. Visitors never wait for it: on servers that cannot answer first, what is due goes out with the next request by someone who edits content (or WP-CLI); WP-Cron only serves as a last safety net. The editor button, list actions and Crawl page apply the same checks and say why an item was skipped.
* Fix: the per-taxonomy title and description templates in Settings are now used on category, tag and other term archives (they had no effect), and a term's own meta description fills in variables like its SEO title. Term SEO fields save for anyone allowed to edit the term.
* Fix: sitemap: noindexed categories and tags are left out; the index lists no empty sub-sitemaps, and empty or out-of-range pages return 404; a latest-posts home page is listed; ordering is stable; post types whose slug ends in digits work. Neither the sitemap nor llms.txt is served while search engines are discouraged (Settings → Reading).
* Fix: the sitemap, llms.txt and the IndexNow key file work with plain permalinks and with /index.php/ permalinks, and their links in Settings and robots.txt follow. Switching the sitemap off (or deactivating the plugin) no longer makes /sitemap.xml redirect to the home page, rewrite rules refresh by themselves after an update and on every site of a network, and the IndexNow key-file rule no longer takes over other root .txt addresses. With custom robots.txt content and our sitemap off, WordPress's own Sitemap line is kept.
* Fix: robots meta: 404 pages are noindexed, as Settings says; switching off "Hide from search engines" for search results now works; a site that discourages search engines keeps WordPress's plain "noindex, nofollow".
* Fix: page 2 and later of a post split with &lt;!--nextpage--&gt; keep their own canonical URL instead of page 1's, and a canonical URL override typed as a path ("/page/") or without https:// is saved as a full address.
* Fix: structured data: social profile links are output as sameAs (they never were); no country is assumed (the default was Bulgaria) and a country alone is no address; text has no HTML entities ("Don’t", not "Don&amp;#8217;t"); an Article's main page is a real node; a Store has the venue's address; Event dates in month/day/year or written-out form are read correctly and in the site's timezone.
* Fix: breadcrumbs now cover the posts page, show a post's most specific category after its parents, and include the archive of hierarchical content types.
* Fix: a deleted share image falls back to the featured, content type or default image instead of leaving the page without one.
* Fix: themes without title-tag support no longer get a second &lt;title&gt;; their own is replaced with the SEO title.
* Fix: the WooCommerce Shop page's own SEO and social fields, canonical URL and noindex box now apply to the shop.
* Fix: on a site with no saved settings yet (for example a network-activated subsite), the dashboard's feature toggles and the IndexNow page no longer switch off the sitemap, schema, content analysis and other default settings.
* Fix: content analysis: "Focus keyphrase in the URL slug" works with Cyrillic and other non-Latin slugs; empty content is not rated "good" and the "Manual" strategy is not graded; text in a script the language rules do not cover (Ukrainian, Greek, Japanese…) is no longer scored "very easy"; headings and list items count as separate sentences, and Chinese, Japanese, Korean and Arabic sentence punctuation is recognised; an invalid character no longer empties the analysis; the "✗" marks display correctly; the calibration table matches the editor's score.
* Fix: Crawl & score rates pages exactly as the editor does, skips password-protected posts, reports redirects instead of scoring the page they lead to, and shows titles without HTML entities. Rendered pages (Crawl, and the page-builder fallback in the editor) are measured on the post's own content, without the theme's comments, forms or navigation. The dashboard's coverage card counts only indexable content and describes what it counts.
* Fix: uninstalling removes all of the plugin's data — including robots and crawl meta, dismissed checklist items and scheduled tasks — on every site of a network.
* Improve: the editor's "Search appearance" and social previews now refresh outside the block editor's data-store updates, at most once per frame. An error in a preview can no longer stop the editor from loading, and the previews do less work while you type. The content analysis re-runs only when the content, title, slug or excerpt changes, and stored ratings are updated when you save, not while you type. Saving a page-builder page no longer waits for a request to its own page: it is re-rated from it after the save has been answered (at the end of the request on PHP-FPM and LiteSpeed servers, otherwise through WP-Cron).

= 1.2.0 =
* Rebrand the plugin, administration screens, exports and translation domain to INI Scout.
* Use the ini-scout directory and main plugin file for WordPress.org distribution.
* Remove the private update channel; updates are delivered by WordPress.org.
* Preserve existing settings, metadata, shortcodes and integration identifiers.


= 1.1.2 =
* Fix: the editor "Search appearance" preview now shows the auto-generated description (the %excerpt% fallback) correctly. It previously read the excerpt only from the value present at page load and never followed the content editor, so on screens without a standard excerpt box — notably WooCommerce products, where the description lives in the content editor — the preview wrongly said "No meta description" even though the live page does output one. The preview now derives %excerpt% from the live editor (Block and Classic) just like the front-end. Front-end output was already correct and is unchanged.

= 1.1.1 =
* Fix: the Setup-checklist item "Business address & location added" now ticks once the required address parts are present (street + city + country). It was incorrectly gated on map coordinates (latitude/longitude), so a complete address with no coordinates never completed. Coordinates and postal code remain optional — Google geocodes from the address. The platform `iniseo/v1/state → schema_types.has_address` now uses the same definition, so the checklist and the reported data agree.

= 1.1.0 =
* New: **Crawl & score** page (INI Scout → Crawl). Pick content types and run a batched, cancellable crawl that fetches each published URL's rendered HTML (so page-builder content is measured), scores SEO and readability, stores the scores, and can optionally submit each URL to IndexNow. Available regardless of IndexNow (the IndexNow submit is an optional per-run extra).
* New: Per-post **Submit to IndexNow** button in the content-analysis metabox (published posts, when IndexNow is enabled).
* New: Posts/Pages **list actions** — row actions "Crawl & score" and "Submit to IndexNow", plus matching **bulk actions** for selected items.
* Fix: the SEO / Readability columns on the post list now light up after a crawl (the crawl writes the same rating meta the columns read).
* New: Setup-checklist items can be **dismissed** (e.g. address / opening hours on non-local-business sites); dismissed items no longer count toward the progress total, and can be restored.

= 1.0.10 =
* Fix: IndexNow settings (enable + content-type whitelist) now save correctly. They were silently reverted because the shared settings sanitizer — which runs on every save of the option — re-read the old IndexNow values from the database instead of honouring the IndexNow page's own submission. The sanitizer now keeps IndexNow values when they are present in the submitted data and only preserves the stored values when they are absent (the main Settings form), so a Settings save still never wipes IndexNow.
* Fix: Search-engine verification meta tags (Google / Bing) are now output on every page, not just the homepage — search engines may re-verify from any URL, and this matches every other SEO plugin.
* New: The site-wide "Default share image" can now be set from the Titles tab too (the same setting as Social → Default share image — one value, editable from either place, no duplicate save).

= 1.0.9 =
* Internal: INI Scout now registers its REST namespace (iniseo/v1) with the INI WP connector's family registry, so the INI Protector plugin's "Require login for REST API" never blocks the INI Scout platform endpoint the control panel pulls. No user-facing change.

= 1.0.8 =
* Fix: the SEO title length check now measures the **SEO title** (the INI Scout title field), not the WordPress post title. Editing the SEO title now updates the "Aim for 30–60" check live; when the SEO title is left blank it measures the rendered template default ("Post title – Site name") — i.e. exactly what appears in the page <title>.

= 1.0.7 =
* New: Manual "Re-analyze" button by the SEO score ring, plus a per-suggestion re-check action, so the score can be refreshed on demand while editing.
* Improve: Content analysis now reads the rendered front-end HTML when the editor body is thin (page builders such as Elementor, and ACF-only layouts), so the word count and keyphrase checks reflect what visitors and search engines actually see — fixing misleading "very little content" warnings and stale scores.
* Internal: Head cleanup (generator / WLW / RSD / shortlink removal) moved to the INI Protector plugin so there is a single owner. INI Scout keeps its noindex directives and all other SEO features.

= 1.0.0 =
* Initial release.
