=== SEOYEN SEO Platform ===
Contributors: tevfikgulep
Tags: seo, redirects, schema, internal linking, ai content
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 2.2.7
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

One-click WordPress integration for the SEOYEN SEO platform. AI content, internal links, redirects, schema, audit auto-fix, instant indexing.

== Description ==

SEOYEN SEO Platform is the official WordPress companion plugin for the SEOYEN SEO platform (https://seoyen.com/). It lets you apply decisions made in the SEOYEN panel to your WordPress site with a single click — publish AI-generated articles, add internal links from SEOYEN suggestions, fix audit issues, manage 301/302/307/308/410 redirects, write meta descriptions that work with every major SEO plugin, and much more.

Use the plugin alongside an active SEOYEN account to turn panel recommendations into live site changes without copying and pasting, editing templates, or juggling multiple SEO tools. A guided welcome screen greets you right after activation and walks you through connecting the plugin to your SEOYEN account.

**Highlights**

* Publish AI-generated content (articles and paragraphs) from the SEOYEN panel straight to WordPress as drafts, scheduled posts, or published articles.
* Apply internal link suggestions with a single click. The inserter first tokenizes the existing `<a>` elements in your content, then links only the first still-unlinked occurrence of the anchor text — so it never writes a link inside an existing link, and never double-links the same phrase.
* Manage 301/302/307/308/410 redirects with per-hit analytics, support for exact, prefix, and regex matching, CSV bulk import, and automatic capture of broken backlinks.
* Auto-fix common SEO audit issues on 58 supported check types (title, meta description, canonical, robots.txt, schema Article/Breadcrumb/Organization/WebSite/Product/FAQPage, Open Graph + Twitter Card expansion, hreflang, alt text, viewport, apple-touch-icon, theme-color, llms.txt, AI bot management, broken-link 301 capture, redirect chains, WooCommerce product and category copy, and more).
* Notify IndexNow (Bing/Microsoft) the moment content is published — optional and disabled by default, with a verification key generated inside the plugin.
* Submit published URLs to the Google Indexing API using your own Google Cloud service-account credentials — optional and disabled by default.
* Apply AI-generated meta descriptions across Yoast SEO, Rank Math, SEOPress, AIOSEO, or the built-in native fallback — the plugin detects your active SEO plugin automatically.
* Set image alt text in bulk, including featured images and media library entries.
* Manage JSON-LD schema markup per post (append, replace, upsert) and output it in a single clean `<script type="application/ld+json">` block.
* Convert content into social media drafts ready for your editorial workflow.
* Serve a custom `robots.txt` and `llms.txt` through virtual routes — no file permission changes required.
* Outbound webhook dispatcher with HMAC-SHA256 signing so you can forward activity to Zapier, Make, n8n, or your own endpoint.
* Complete activity log with search, filter, date range, and CSV export.
* System status page with REST loopback test, active SEO plugin detection, and a debug bundle downloader.

**Instant indexing**

When enabled, the plugin pings IndexNow (Bing/Microsoft) and the Google Indexing API the moment you publish content, so search engines learn about new URLs in minutes instead of days. Both integrations are optional and disabled by default. IndexNow uses a verification key generated inside the plugin and served through a virtual `/{key}.txt` route, and Google Instant Indexing authenticates with your own Google Cloud service-account JSON over an OAuth2 JWT (RS256) — no third-party libraries involved.

**SEO plugin integrations**

The plugin talks to existing SEO plugins so there are no conflicts:

* Yoast SEO (Free and Premium) — meta description, title, canonical, Open Graph.
* Rank Math — meta description, title, canonical, Open Graph.
* SEOPress — meta description, title, canonical, Open Graph.
* AIOSEO (All in One SEO) — meta description, title, canonical, Open Graph, Twitter Card.
* Native fallback — if no SEO plugin is active, the plugin outputs standards-compliant meta tags itself.

**Dashboard widget**

A native WordPress dashboard widget summarises your audit score, top tracked keywords, recent organic clicks, and the latest activity from both the panel and the plugin. Refreshable with a single click.

**Privacy-first**

The plugin does not make any remote call until you enter a SEOYEN API key and complete the three-step setup wizard. There is no separate telemetry switch and nothing is sent anonymously: once an API key is saved, the plugin reports to your own SEOYEN panel once a day (site address, plugin version, which optional features are enabled, and — on WooCommerce stores — product and category counts; never customer or visitor data). Removing the API key stops every outbound call. On uninstall, plugin data is kept unless you explicitly opt in to full data removal.

**SEOYEN account required for AI features**

The internal-links apply, redirect management, and local admin tools work without an account. Panel-driven features (AI content, meta description generation, audit auto-fix driven from the panel, suggestion lists) require an active SEOYEN account so the WordPress plugin can talk to the panel API.

== External Services ==

This plugin connects to external services to provide its functionality. The integrations are documented below so you can make an informed decision before activating them.

**1. SEOYEN Panel API (required for panel-driven features)**

* Endpoint: `https://seoyen.com/panel/api/api-platform.php`
* What it does: Receives WordPress site actions (liveness pings, activation handshake, link/meta/schema/alt/social/autofix apply callbacks, redirect sync, activity log sync) and returns the data the plugin needs to render panel-driven UI (audit summary, link suggestions, AI content, audit auto-fix instructions).
* When: Only after the user enters a SEOYEN API key in **SEOYEN SEO > Settings** and completes the setup wizard. The plugin makes no outbound calls on activation or before the key is set.
* Data sent: site URL, admin email, WordPress version, PHP version and plugin version (once, during the activation handshake); a once-a-day status ping carrying the site URL, plugin version, whether IndexNow and AI-bot logging are enabled, the plugin's REST rate limit, the list of audit checks this plugin version can fix, and — on WooCommerce stores only — the WooCommerce version, currency, product and category counts and the number of pending change-journal rows; an hourly aggregate of AI-crawler visits (per day, per bot name, per path — hit counts only, no IP addresses, no human visitor data) while AI bot logging is enabled; and action-specific payloads (e.g. redirect source and target URLs, meta description text, target post ID, activity log entries). No content of posts or media is sent unless you explicitly trigger a publish or apply action.
* Data received: AI-generated content, internal link suggestions, audit fixes, redirect and webhook configurations.
* Terms of service: https://seoyen.com/sartlar/
* Privacy policy: https://seoyen.com/gizlilik/

**2. User-configured outbound webhooks (optional)**

* Endpoints: User-provided URLs entered in the webhook settings.
* What it does: When enabled, the plugin POSTs HMAC-SHA256-signed JSON payloads to the URLs you configure whenever a tracked event occurs (for example, a redirect being created or an AI meta description being applied). You control the URLs and the event types.
* When: Only if you explicitly add at least one webhook endpoint and enable it.
* Data sent: The event name and a minimal payload describing the event. No credentials or user information is included.
* Terms and privacy: Determined by the third party that owns the webhook endpoint.

**3. WordPress core REST self-call (loopback)**

During the setup wizard the plugin makes a single request to its own `/wp-json/seoyen/v1/ping` endpoint to verify REST API availability. This is a loopback request to the same WordPress install and does not leave the server.

**4. IndexNow (optional)**

* Endpoint: `https://api.indexnow.org/indexnow`
* What it does: When you enable IndexNow in **SEOYEN SEO > IndexNow** and generate a verification key, the canonical URL of newly published content is submitted to the IndexNow service (operated by Microsoft Bing) so search engines can index it faster.
* When: Only if you explicitly enable the feature (disabled by default). No request is made while the feature is off.
* Data sent: The URL of the published content, your site host, and your own verification key. The key is generated inside the plugin (never fetched from a third party) and is served through a virtual `/{key}.txt` route on your own site.
* Terms of service: https://www.bing.com/indexnow
* Privacy policy: https://www.microsoft.com/privacy

**5. Google Instant Indexing (optional)**

* Endpoints: `https://oauth2.googleapis.com/token` and `https://indexing.googleapis.com/v3/urlNotifications:publish`
* What it does: When you upload your own Google Cloud service-account JSON and enable the feature, the URLs of newly published content are submitted to the Google Indexing API. Authentication uses an OAuth2 JWT (RS256) signed locally with OpenSSL — no third-party libraries are involved.
* When: Only if you explicitly enable the feature and provide your own service-account credentials (disabled by default).
* Data sent: The published URL and your own service-account credentials (used solely to obtain an access token from Google). The private key is stored in your WordPress database and is never displayed back in the browser.
* Terms of service: https://developers.google.com/search/apis
* Privacy policy: https://policies.google.com/privacy

**6. SEOYEN Panel API — WooCommerce catalogue sync (optional, WooCommerce only)**

* Endpoint: `https://seoyen.com/panel/api/api-platform.php` (the same panel API as item 1).
* What it does: When your site runs WooCommerce and you have connected a SEOYEN account, the panel reads your product and category catalogue so it can score product SEO, flag missing product descriptions and empty category pages, and generate copy on request. The plugin also keeps a local change journal so price and stock movements are reported at intra-day resolution — WooCommerce does not update a product's `post_modified` timestamp when an order reduces stock, so a date-based sync would silently miss it.
* When: Once per day via a scheduled sync, plus whenever you trigger a catalogue refresh or a product SEO action yourself. If WooCommerce is not active, no product data is collected and no product query is ever executed.
* Data sent: Product and category attributes only — name, SKU, GTIN, price, sale price, cost (when set), stock status and quantity, brand, category and tag names, permalink, image URLs, product type, publish status, and aggregate review **counts** and average rating. Change-journal rows (which product changed, which field, when). Daily order **totals** (aggregate revenue and order count per day) when GA4-style commerce reporting is enabled.
* Data NOT sent: No customer personal data of any kind — no customer names, e-mail addresses, phone numbers, billing or shipping addresses, and no IP addresses. No order numbers or individual order records. No review text and no reviewer identity; only the number of reviews and the average score.
* Terms of service: https://seoyen.com/sartlar/
* Privacy policy: https://seoyen.com/gizlilik/

== Installation ==

1. Upload the plugin ZIP via **Plugins > Add New > Upload Plugin**, or unzip into `wp-content/plugins/seoyen/`.
2. Activate **SEOYEN SEO Platform** from the Plugins screen.
3. Go to **SEOYEN SEO > Settings** and paste the API key generated in the SEOYEN panel (Settings > Integrations > WordPress Plugin).
4. Click **Connect to Panel**. The plugin will run a three-step handshake and report when the connection is active.
5. Open the **SEOYEN SEO** top-level menu to explore the dashboard, redirects, internal links, content suggestions, activity log, and system status pages.

== Frequently Asked Questions ==

= Do I need a SEOYEN account? =

Yes for AI and panel-driven features (AI content publish, meta description apply, audit auto-fix from the panel, suggestion lists, webhook dispatch back to the panel). The redirect manager, admin UI, and local debugging pages work without a panel connection.

= Which SEO plugins are supported? =

Yoast SEO (Free and Premium), Rank Math, SEOPress, and AIOSEO (All in One SEO) are detected automatically. If none of these are active, the plugin falls back to writing meta descriptions, canonical tags, and Open Graph tags itself. You only need one of these (including the native fallback) to get meta description support.

= Can I use this plugin without the SEOYEN panel? =

The redirect manager, admin UI, activity log, and system status work without a panel connection. AI features, audit auto-fix from the panel, and suggestion lists require an active SEOYEN account because the data comes from the panel API.

= Does this plugin track users on my site? =

No. The plugin adds no public-facing tracking scripts and sets no cookies. There is no separate telemetry switch: after you save a SEOYEN API key the plugin talks only to your own SEOYEN panel — a once-a-day status ping (site address, plugin version, which optional features are enabled, and product/category counts on WooCommerce stores) and, while AI bot logging is enabled, an hourly aggregate of AI-crawler visits (per day, per bot name, per path — counts only, no IP addresses, no human visitor data). Removing the API key stops every outbound call. The full list is in the External Services section above.

= How do redirects work? =

Redirects are stored in the custom table `{prefix}seoyen_redirects`. On every public request the plugin hooks `template_redirect` at priority 1, normalizes the request URI, looks up the destination, and — if a match is found — issues a `wp_safe_redirect()` with the configured status code. Hit counts are updated on the `shutdown` hook so they do not slow down the response.

= Will it conflict with Yoast, Rank Math, SEOPress, or AIOSEO? =

No. The plugin uses an adapter pattern. If Yoast is active, meta descriptions are written into Yoast's post meta keys; the same is true for Rank Math, SEOPress, and AIOSEO (written into AIOSEO's own database table). If none are active, the plugin writes its own meta tags in the head. The plugin never outputs duplicate meta tags.

= Can I remove plugin data on uninstall? =

Yes. In **SEOYEN SEO > Settings** there is a "Keep data on uninstall" toggle. When disabled, uninstalling the plugin drops all six custom tables (redirects, activity log, webhooks, AI-bot hits, WooCommerce change state and journal), deletes `_seoyen_*` post meta and term meta, the plugin's own user preferences (dashboard widget options, welcome-notice dismissal), its options and cached transients, and removes the `seoyen_manage` capability. Meta written into Yoast, Rank Math, SEOPress or AIOSEO fields is left untouched. On a multisite network every site is cleaned according to its own toggle. The default is to keep data so you can reactivate without losing redirects or activity history.

= My site already has a physical robots.txt file — do the plugin's robots.txt rules still apply? =

No. When a physical `robots.txt` exists in the WordPress root, the web server serves that file directly and WordPress never runs the virtual `robots.txt` — the AI-bot toggles and any rule pushed from the panel are silently bypassed. **SEOYEN SEO > Settings > SEO Behavior** shows a warning with a one-click "delete the physical file" button while this is the case; the plugin only writes a physical file when the panel explicitly asks for it, and the `files/status` endpoint reports `physical_exists`.

= Is it GPL-compatible? =

Yes. SEOYEN SEO Platform is released under GPLv2 or later, the same license as WordPress core.

= Where do I report bugs or request features? =

Please contact support through https://seoyen.com/ or open an issue in the SEOYEN panel's help section.

== Screenshots ==

1. Dashboard overview with audit score, organic traffic summary, recent activity feed, and quick action cards for publish, redirects, audit, and panel.
2. Redirect manager with sortable list, bulk actions, search, and CSV import for migrating from other redirect plugins or importing broken-backlink targets from the SEOYEN panel.
3. Setup wizard — three-step API key, connect, and verify flow with inline validation.
4. AI content suggestions — list of panel-generated drafts with one-click apply/reject buttons and a preview of the incoming change.
5. Activity log with date-range filter, action and result filters, search box, and CSV export.
6. System status page with REST loopback test, active SEO plugin detection, and debug bundle downloader.

== Changelog ==

= 2.2.7 =
* **Site-wide SEO: all eight tabs now actually do something.** The screen shipped with eight tabs, but only the separator, the title patterns and a few social/schema keys were ever read — ticking "disable author archives", "Google verification code", "remove emoji scripts", "RSS prefix" or "breadcrumbs" said "saved" and changed nothing on your site. Robots, crawl cleanup, structured data (WebSite, knowledge graph, per-type Article/BlogPosting, real `@id` linking) and breadcrumbs (`[seoyen_breadcrumbs]` shortcode and `seoyen_breadcrumbs()` template tag) are all applied now, and the visible breadcrumb trail and the one reported to search engines finally come from one source.
* **Readable `{token}` syntax in patterns.** `{title} {sep} {sitename}` replaces `%%title%% %%sep%% %%sitename%%`; existing records migrate on their own and both forms keep resolving. An unrecognised token is rejected when you save and reported to you instead of leaking into your page title, and a token sitting inside a resolved value is no longer resolved a second time or silently deleted.
* **The same settings live in wp-admin, and both screens show the same values.** The tree is built from your site's real post types and taxonomies instead of nine fixed ones, with a per-language override layer. Empty fields arrive with **recommendations** — ready patterns with your brand written as `{sitename}` rather than baked in, and your real site name pulled in so the length counter is honest. A recommendation is not a default: nothing changes on your site until you save, and the screen says so.
* **Every tab, section and key now explains itself,** limits included ("removing the REST link does not disable the REST API itself"). The variable table gained an "example on your site" column, and image fields get a real WordPress media picker with a preview.
* **Suggestions start with site-wide fixes, and the payoff is measured.** Above the tab bar you now see how many issues and rules each group holds and, when it can be measured, "one change closes N issues" — measured on a 1,672-page site, a single title-pattern change closes 1,246 of 1,286 "title too long" issues. The number is computed from your last audit run and nothing is printed when it cannot be measured. Each group deep-links to its tab, where **apply-and-save takes one click** and writes only the fields you applied. Per-page suggestions were not removed.
* **Security: your Google Instant Indexing service-account key is now encrypted at rest.** The JSON, private key included, was stored in plain text, so a database dump or any plugin that lists options could expose it. It is now kept in an AES-256-GCM envelope whose key is derived from your `wp-config.php` salts and never stored. Existing installs migrate on first read; if the envelope cannot be opened after a salt change or a server move you are told to re-upload the JSON instead of seeing a silent "not connected".
* **Security: the ReDoS gate catches five more bypass classes and the failed-key counter is race-free.** Grouping layers (`((a+))+`), ambiguous alternation (`(a|aa)+`) and the spaced `x` form (`/^( a + ) + $/x`) were all accepted before at the same cost, and on servers with an unlimited PCRE backtrack limit the clamp was skipped exactly where it mattered (measured: 15 seconds for one rule). The bucket counting bad API-key attempts used read-modify-write, so concurrent requests multiplied the per-minute limit, and the 250 ms delay was paid before the counter moved.
* **"Applied" is only reported when something was really applied — eleven paths closed.** The plugin used to answer "written" while nothing changed on your site: your audit issue was stamped resolved and AI credit was spent. Content writes are now read back and compared; a missing anchor no longer appends the paragraph to the end of the post; a physical `robots.txt` shadowing the virtual one returns an explicit error instead of 200; invalid `og:type`, `og:url`, `twitter:card` and review ratings never reach storage; the same paragraph is not written twice; "nothing changed" no longer fires your webhook; term fixes go through the same gate as post fixes; and a failed callback to the panel is now shown instead of "✓ Fixed".
* **Redirect loops are caught across the whole chain.** A → B → C → A made pages unreachable with `ERR_TOO_MANY_REDIRECTS`: only a single step was checked, and editing an existing rule's target skipped the gate entirely. Create and update now share one gate that walks the chain. Bulk import no longer accepts fields outside the contract, an already-inactive rule is not counted as "1 hop removed", and resolving a chain appends to your note instead of overwriting it.
* **Front end: share cards, canonical, structured data and robots.txt.** A SEO title now reaches `og:title`/`twitter:title`; a card is no longer left without a description; both cards use the same image; the site-wide pattern no longer overwrites a post's own social title or description; and a canonical you wrote is really applied when the theme printed its own first. Raw HTML entities are gone from JSON-LD across 27 fields (measured live: `Türkiye&#039;nin`) while address and identity fields stay untouched, `@graph` members no longer carry `@context`, and variable WooCommerce products are reported with `AggregateOffer` instead of a single lowest price. Unticking "Output native meta tags" no longer leaves titles, robots, schema, breadcrumbs and redirects unbound or deletes our own schema nodes, and removing one bot from `robots.txt` no longer deletes another bot's shared `Disallow:` or migrates rules into the wrong group.
* **Content writes no longer damage what they touch.** An internal link for the anchor `SEO` was written as `<a href="…">SEO</a>YEN`, splitting a brand name in your published body with no way back — and in the other direction an unlinked phrase was skipped as "already linked". Both gates now share one Unicode word-boundary grammar (required for Turkish). Separately, an update from the panel no longer silently demotes a published post to draft: unknown statuses are rejected, and statuses your own site registers (`archived`) are kept.
* **Screens, AIOSEO and the commerce ledger.** You now come back to the tab you saved; CSV import counts the rows it drops instead of reporting a clean success; the dashboard widget and System Status no longer contradict each other; bulk delete without JavaScript explains itself instead of doing nothing; and Content Bot run history speaks in your site's time, including runs created after midnight. With AIOSEO active, term fixes are now really written and verified or honestly skipped, and "improve this meta description" can see your existing text. In the ledger, a failed journal write no longer loses a price change and a 5,000-row backlog no longer locks it permanently. Also: resumable multisite activation, backoff on hosts without table-creation rights, RSS tokens resolved per post, "Page 2 of 1", static front pages, sub-directory path doubling, meta description cleanup, and bulk requests consuming their real rate-limit weight.

= 2.2.6 =
* **Anchor links (`#…`) can now be fixed.** The "links to a redirect" fix could not find a link that carried a section anchor (`/privacy-policy/#your-rights`): it answered "this may come from a theme template, fix it by hand" while the link was right there in the page body (measured on the live site). Matching now ignores the anchor and **carries it over to the new address**: `/privacy-policy/#your-rights` → `/privacy/#your-rights`. If the destination the panel measured carries an anchor of its own, that one wins; links without an anchor behave exactly as before.
* **A page no longer links to itself.** WordPress's "guessed redirect" feature 301s a made-up or stale short URL to the very same post most of the time. The fix took that for a "new destination" and rewrote the link to the page's own address: a pointless link that also hid the real problem (the made-up URL). Such a pair is now never applied — the panel does not send it and the plugin does not write it; the reason is stated in the response and you are asked to fix or remove the link by hand.

= 2.2.5 =
* **"Links to a redirect" is now actually fixed in your content.** The audit finding used to be wired to the wrong fix: the plugin wrote a 301 rule while the `<a href>` on the page stayed unchanged, so the issue came straight back on the next crawl (and most calls failed outright because the panel could not produce the data that fix needed). The link on the page is now rewritten to the final destination the panel measured live, and your markup style is preserved — a relative link stays relative. If the link lives in a **custom menu item** instead of the post body, the menu URL is updated and read back to verify the write.
* **What is left alone is stated plainly.** A link whose final destination is **another site** is never rewritten (it may be intentional), and links the panel could not measure (timeout, loop, non-2xx response) are skipped with the reason in the response. `www.` and non-`www.` addresses are treated as different targets — that difference is the defect being fixed.
* **"Fixed" is only reported when something was actually fixed.** When an audit fix could not be applied (the URL matches no post, the link comes from a theme template, the target is external) the plugin still answered `success`: the activity log recorded `success`, the `seoyen_audit_applied` event — and therefore your webhook — fired, and the row in wp-admin was marked "✓ Fixed". Such a result is now logged as **skipped**, the webhook does **not** fire, the row is not marked fixed, and the reason is shown on screen. The same contract was closed on the internal-link endpoint in 2.2.4; the audit endpoint was still open.
* **Automatic "Table of Contents" insertion has been removed.** Fixing an audit issue from the panel or the plugin no longer prepends a table of contents to your post. A table of contents is a **design component**, not SEO data: the generated list was an unstyled flat `<ul>`, ignored heading levels, and was written permanently to the top of the post with no way back. **Detection stays:** the "no table of contents" finding is still reported — solve it with your theme's or a dedicated plugin's own component. The content-editing mode list is down from six to **five** (`append`, `replace`, `add_subheadings`, `split_paragraphs`, `simplify_sentences`); the removed `add_toc` mode is now rejected explicitly.
* **The "paragraph too long" fix no longer burns AI credit for nothing.** That fix splits paragraphs entirely locally, at sentence boundaries in your own text — it never looked at the AI output. The panel still generated text on every click, charged your credit and threw the result away. AI is no longer called for this fix and no credit is spent. The split threshold now matches the audit's own threshold (300 words); previously the plugin's 120-word default was used and paragraphs that were never flagged got split too.

= 2.2.4 =
* **The Content Bot's "Run now" no longer publishes the same post twice.** The panel already published the post when the run finished; the plugin didn't know that and created the same article a second time. The panel now publishes and the plugin only shows an "Open post / Open draft" link; a "Push to draft" button appears only if the panel could not publish.
* **wp-admin no longer waits 20 seconds when the panel is unreachable.** Suggestions, Internal Link Suggestions and the SEOYEN data pages never got the 5-second cap promised in 2.2.3, and each of four independent hooks paid its own timeout. All panel calls now share one circuit breaker capped at 5 seconds, a failure is remembered for 15 minutes with a "Try again" button, and "Test Connection" always bypasses the circuit for a real request.
* **The "Connected" badge now reports the panel's own verdict** — it shows "Not verified" with a link to the wizard when the panel has rejected your API key, instead of reading "Connected" merely because a key is present.
* **Changing the API key or panel URL now clears the plugin's cache,** including on sites with a persistent object cache, and "Reset All Settings" also drops runtime records and stale keys — previously data belonging to the old key survived the change.
* **Redirects now match the requests they were meant to match:** an exact-match rule also catches requests carrying query strings (`?utm_source=…`, `?fbclid=…`), percent-encoded requests match rules written with Turkish characters, a prefix rule whose target sits under its own source no longer loops, and a bulk import above 500 rows is rejected explicitly instead of being silently truncated.
* **The write path is honest:** Twitter Card, review fields, `noindex` removal, site-wide settings and schema deletion are no longer reported "applied" without a verified write; "internal link applied" is reported only when the link was really inserted (a skipped result is logged as **skipped**, with a reason, and fires no webhook); writing `llms-full.txt` no longer returns an unverified 2xx; sending the same article twice returns the existing post instead of creating a second one; and an appended paragraph no longer lands inside a Gutenberg heading block.
= 2.2.3 =
* **Security.** A pattern (regex) redirect's target could put the part captured from the pattern into the **domain** of the destination address (`https://$1`) — an open door that could send a visitor to a completely foreign site based on what they typed. Such a target is now rejected on save and, even if one was saved earlier, is never applied at request time; using `$1` in the **path** still works. Your **IndexNow key** — IndexNow's proof of ownership for your site — is now served only from its own path instead of being readable from any address on your site.
* **Uninstalling no longer leaves data behind.** With "delete data too" selected, all **six** tables the plugin creates are now dropped (2.2.2's removal list was hand-written and only counted four), along with `_seoyen_*` fields on categories and tags, the plugin's own user preferences, three forgotten option rows, five transient families and a leftover scheduler. On **multisite networks** every site is now walked individually and each follows its own "keep my data" preference; Yoast / Rank Math / SEOPress fields are left untouched.
* **"Applied" lies are closed, and panel-driven fixes no longer damage your content.** Canonical, hreflang, `og:type`, `og:url`, `twitter:card`, image alt text, `llms-full.txt` and writes to a third-party SEO plugin now require a verified write before success is claimed. Re-saving a post through a panel-applied fix used to **permanently** remove manually-added `<iframe>` (YouTube embeds, maps), `<object>` and `<script>` elements — your existing content is now preserved as-is. Internal-link insertion is scoped to visible text only, so an anchor word that also appears inside an `alt=` attribute or a Gutenberg block comment no longer breaks the image or the block.
* **wp-admin no longer freezes for 20 seconds when the panel is unreachable** (calls capped at 5 seconds, a failure remembered for 15 minutes), and **SEOPress and All in One SEO are now detected correctly** — with either plugin active SEOYEN kept printing its own meta/OG/Twitter/canonical tags on top of theirs, and now stays quiet. Your saved data is not deleted; it all comes back if you deactivate the other plugin.
= 2.2.2 =
* **The "Duplicate H1" and "H1 too long" fixes can now actually be applied.** For these two findings, "Apply via WP" changed nothing on most themes: the plugin could only fix the `<h1>` tag in the post content, while most themes print the visible heading from the post title, not the content. Measured (28 August 2026, three rounds): the target posts had no `<h1>` in their content at all, the fix was silently skipped all three rounds, and the same finding kept coming back on every audit.
* **You're now explicitly asked what should happen.** If there's no `<h1>` in the content to change, the plugin no longer gives up silently; the panel shows a **"Also update the post title"** checkbox under the fix. Nothing changes unless the box is checked.
* **Your permalink never changes.** Once you approve, only the post title is updated — the permalink (slug) stays exactly as it was. Where the title appears: menus, post lists, and search results.
* **No "I did it" lie.** If WordPress rejects the title update, the fix is not counted as successful; the issue stays open in the audit list.

= 2.2.1 =
* **Meta descriptions reported as "fixed" now actually show up on the page.** Six fix types replied "written" and genuinely saved the value, but nothing changed on the page the visitor saw. MEASURED (live, 28 August 2026; 18 fix types × 3 rounds): `content.meta_too_long`, `content.meta_too_short`, `content.meta_description_duplicate`, and `social_meta.og_description_missing` stayed silent 3/3 rounds.
* **Cause:** the plugin saw "this tag already exists on the page" and backed off. Most themes print their own `<meta name="description">` tag before the plugin's turn comes. This wasn't specific to any one site — every site whose theme prints its own description had the same silent no-op.
* **Now:** if you have an explicitly saved value, the plugin REPLACES the existing tag instead of staying quiet — covers the page description, Open Graph description and type, and Twitter Card type/title/description.
* **Deliberately left alone:** if the value isn't text you wrote but a fallback derived from an excerpt, the theme's tag is untouched; if Yoast / Rank Math / SEOPress / AIOSEO is active, nothing changes; if multiple tags share the same name, only the first is fixed.
* **`twitter:card` is no longer duplicated** — if the theme (or a plugin like Jetpack) already prints its own card tag, a second, conflicting one is no longer added. All three output points in the class now follow the same contract.
* Whether the overwrite happens or is skipped, a descriptive line is written to the server error log at most once an hour.

= 2.2.0 =
* **Eleven labels that had stayed in English on the Turkish admin screen were translated** — tool names (Meta Description, Alt Text, Social Media Converter, Paragraph Generator, Auto-Fix), severity levels (Critical, Info), and article-card labels (AI Article, Keyword:, Draft, Published). The same screen no longer mixes two languages.
* Only the displayed text changed; settings, records, and the technical post-status values (`draft` / `publish`) stayed the same.

= 2.1.3 =
* **"Tek Tıkla Düzelt" artık gerçekten hepsini kapsıyor.** Kart "42 URL etkilendi" derken düğme yalnız ekranda gösterilen ilk 5 adresi düzeltiyordu. Düğme artık ekrandaki listeye hiç bakmaz: sorunun TÜM uygun adreslerini panelin toplu düzeltme motoruna verir.
* **Tıklamadan önce ne harcayacağınız yazılı olarak söyleniyor** — kaç sayfa işlenecek, tahmini kaç kredi gidecek, kalan bakiyeniz ne. Onaylamadan hiçbir kredi harcanmaz. Bakiye sınırsızsa rakam yerine "sınırsız" yazar; maliyet hesaplanamıyorsa uydurulmaz, "hesaplanamadı" denir ve sayfa sayısı yine söylenir.
* **Kart, panelin o soruna ait sayfasına bağlantı veriyor** ("Tek tek seçmek için panelde aç →") — hangi sayfayı düzelteceğinize tek tek karar vermek isterseniz oradan seçersiniz.
* **İlerleme dürüst üç kovada gösteriliyor**: hazır · hata · bekliyor. "Bekliyor" hata değildir — o adresin düzeltmesi henüz üretilmedi. İstediğiniz an durdurabilirsiniz; durdurmak izlemeyi keser, panelde süren üretimi öldürmez ve bu açıkça yazar.
* Etkilenen adres önizlemesi 5 yerine 20 satır gösteriyor.

= 2.1.2 =
* **Düzelttiğiniz denetim sorunları aktif listeden düşüyor.** Bir sorunu düzeltmenize rağmen kart listede duruyordu; hangi işi yapıp hangisini yapmadığınızı ayırt edemiyordunuz. Düzeltilenler artık **"Uygulanmış düzeltmeler"** başlıklı, varsayılan olarak kapalı bir bölümde toplanır — kaybolmazlar, sadece yolunuzdan çekilirler.
* **Bir sorun yeniden ortaya çıkarsa kendiliğinden aktif listeye döner** — arşiv "bir daha bakma" listesi değildir; sonraki denetimde aynı kontrol yeniden başarısız olursa sorun açık listede yeniden belirir.
* Uygulanmış düzeltme sayısı başlıkta gösterilir; sayı panelden okunamıyorsa başlık sayısız basılır, uydurma rakam gösterilmez.

= 2.1.1 =
* **"Uygulanıyor…" sonsuza kadar durmuyor** — düzeltme ekranı bir sonucu iki saate kadar bekleyebiliyordu; geçen süre yoktu, iptal yoktu. Bekleme artık ölçülmüş süreye göre: tek düzeltme için 6 dakika, bir sorunun tüm adresleri için toplam 30 dakika.
* **Beklerken geçen süre görünüyor** ("42 sn", "3 dk 07 sn") ve istediğiniz an **İptal** diyebiliyorsunuz. İptal dürüsttür: izleme durur, sunucudaki üretim arka planda sürebilir, ama sitenize hiçbir şey yazılmaz — yeniden deneyebilirsiniz.
* **Süre dolduğunda "başarısız" denmiyor** — düzeltme hâlâ üretiliyor olabilir; ekran "sonucu bekleyemedik" der ve ne yapmanız gerektiğini yazar.
* **"Ağ hatası" yerine sunucunun gerçekten ne dediği yazıyor** — zaman aşımı mı, sunucuya ulaşılamıyor mu, beklenmeyen yanıt mı; durum koduyla birlikte. Ham hata sayfası kullanıcıya gösterilmez, yalnız geliştirici konsoluna düşer.

== Upgrade Notice ==

= 2.2.7 =
Site-wide SEO is now real: all eight tabs actually apply, the settings also live in wp-admin, and one click closes issues site-wide. Eleven paths that reported success without writing are fixed; redirect loops are caught across the chain. Recommended update.

= 2.2.6 =
Links with a # anchor are now fixed too, and the anchor moves to the new address. A link that WordPress redirects back to the same page is no longer rewritten, so a page can no longer link to itself. Recommended update.

= 2.2.5 =
Links to a redirect are now rewritten in your content, and fixes that could not be applied are no longer reported as fixed. Automatic table-of-contents insertion is removed (detection stays); the paragraph-too-long fix no longer spends AI credit. Recommended update.

= 2.2.4 =
Structured data no longer duplicates your theme's; internal-link, llms-full and schema-adapter "applied" replies are now verified; panel outages no longer freeze wp-admin for 20s; non-Turkish admins see English, not half-Turkish. Recommended update.

= 2.2.3 =
Security and honesty round: pattern redirects can no longer escape to a foreign domain, the IndexNow key is only served from its own path, and uninstall now drops all six tables. Fixes that fail to apply are no longer reported as successful. Recommended update.

= 2.2.2 =
Duplicate H1 and H1-too-long fixes can now actually be applied: when there's no H1 in the content to change, the plugin asks for approval to update the post title instead of giving up silently. Nothing changes without approval; your permalink stays the same. Recommended update.

= 2.2.1 =
Meta description fixes now actually show up on the page: with an explicitly saved value, the plugin replaces the tag your theme prints. Derived fallbacks leave the theme untouched; behavior is unchanged when another SEO plugin is active. Recommended update.

= 2.2.0 =
Eleven labels that had stayed in English on the Turkish admin screen were translated: tool names, severity levels, and article-card labels are now all one language. Only displayed text changed; your settings and data were untouched. Recommended update.

= 2.1.3 =
"Tek Tıkla Düzelt" artık sorunun tüm uygun sayfalarını kapsıyor ve tıklamadan önce kaç sayfa işleneceğini, tahmini kredi maliyetini ve kalan bakiyenizi söylüyor. Onaylamadan kredi harcanmaz. Önerilen güncelleme.

= 2.1.2 =
Düzelttiğiniz denetim sorunları artık aktif listeden düşüyor ve varsayılan olarak kapalı "Uygulanmış düzeltmeler" bölümünde toplanıyor. Sorun yeniden ortaya çıkarsa kendiliğinden aktif listeye döner. Önerilen güncelleme.

= 2.1.0 =
WooCommerce için e-ticaret SEO katmanı: ürün ve kategori kataloğu, gün içi stok-fiyat takibi, üç yeni tek-tık düzeltme, sekiz denetim kontrolü. Kaldırma bildirimi, hız sınırı ve otopilotun sessiz yayını da düzeltildi. Önerilen güncelleme.

= 2.0.8 =
İçerik menüsü Öneriler oldu ve menü kalemleri işe göre kümelendi. Eski adres yenisine yönlendirilir, yer imleri kırılmaz. İçerik Botu değişmedi. SEOYEN veri sayfaları bölüm ve sekmelere ayrıldı; eklenti panelden eski kalsa bile sayfa eksiksiz görünür.

= 1.1.0 =
Sunucu-sürümlü companion çerçevesi: AI Görünürlük + Arama Performansı sekmeleri eklendi, panele yeni özellik geldiğinde artık eklenti güncellemesi gerekmiyor. Grafikler paketlenmiş Chart.js ile çizilir. Önerilen güncelleme.

= 1.0.13 =
AI bot ziyaret kaydı + IndexNow + Google Instant Indexing + karşılama ekranı + robots.txt AI bot toggle'ları. Panel bağlantısını kıran çift-header hatası düzeltildi — tüm kullanıcılar için önerilen güncelleme.

= 1.0.8 =
Autofix 57 check'e çıkarıldı, 12 yeni tür (twitter:card türü, HowTo/Video/Review schema, html lang, Claude/Perplexity allow, düşük kelime sayısı, llms-full.txt). Schema builder + frontend genişlemesi. Önerilen güncelleme.

= 1.0.7 =
Autofix 45 check'e çıkarıldı, Schema builder, redirect chain resolver, AI bot yönetimi. Önerilen güncelleme.

= 1.0.6 =
Düzeltme callback'i + URL satırı UI güncelleme + AI hata mesajı iyileştirme. Önerilen güncelleme.

= 1.0.5 =
AI tabanlı düzeltme sistemi eklendi. Tek Tıkla Düzelt için Bireysel veya üstü paket ve AI kredisi gereklidir. Fallback mantığı kaldırıldı — yalnızca yapay zeka ile çalışır. Önerilen güncelleme.

= 1.0.4 =
Tek Tıkla Düzelt akıllı fallback'lerle tam çalışır hale getirildi. Meta, alt metin, OG, hreflang otomatik üretilir. Toplu URL düzeltme eklendi. Önerilen güncelleme.

= 1.0.3 =
Kritik 403 nonce hatası ve update "Unauthorized" hatası çözüldü. Önerilen güncelleme.

= 1.0.2 =
Tek Tıkla Düzelt düzeltildi (403 fix), Tümü sekmesinde audit ve AI önerileri birlikte, otomatik versiyon sync, tamamen Türkçe UI. Önerilen güncelleme.

= 1.0.1 =
Site Audit Fixes + AI Articles tabs added, internal links auto-populate, setup wizard and redirect form fixes, foreign notice suppression, full-width admin pages. Recommended update.

= 1.0.0 =
Initial public release.
