=== SyncDock ===
Contributors: rakibantor
Donate link: https://degird.com
Tags: api, rest-api, headless, content-management, publishing
Requires at least: 6.4
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.6.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Generate, rewrite and publish WordPress posts with AI, across every site you run. The WordPress half of SyncDock, paired with its free client.

== Description ==

**Write, rewrite and publish WordPress posts at AI speed — across every site you run.**

SyncDock is built for people who publish a lot: ten to fifty posts a day, often spread over several sites. Turn a topic into a finished, SEO-scored Gutenberg post in about a minute. Refresh an old post in one click. Plan clusters, queue a batch, and let a schedule publish it for you — without opening wp-admin for any of it.

* **Ten to fifty posts a day** — generate in bulk from a topic list, queue the batch, and drip-publish it, instead of writing one post at a time from a blank page
* **Every site from one place** — install this plugin on each site you run and publish, rewrite and schedule across all of them from one browser toolbar
* **About a minute per post** — draft, SEO score, internal links, featured image and native Gutenberg blocks in a single pipeline run, using your own AI provider key

**SyncDock is two halves, and you need both to get the point of it.**

* **SyncDock (this plugin)** runs on your WordPress site. It opens a hardened publishing API and guards it: API keys with scopes, optional HMAC request signing, rate limiting, an audit log, a Gutenberg block converter, an SEO bridge, and a durable job queue. It deliberately adds no writing interface of its own.
* **SyncDock Client (free browser extension)** is the console you actually work in. Writing, one-click AI drafting, Markdown-to-Gutenberg conversion, SEO scoring, media, scheduling, automations, and publishing to every site you manage all happen there — and reach your site through this plugin.

**Install the extension: [SyncDock Client on the Chrome Web Store](https://chromewebstore.google.com/detail/syncdock-client/ncejpifaefhebcpiicdbmdbflckkbiee)** — free, no account, one click. Install the plugin, generate an API key, paste it into the extension. That is the whole setup.

The plugin on its own is an API with nothing driving it. The extension on its own has nowhere to publish. Together they let you run WordPress content operations — one site or twenty — without opening wp-admin to write another post.

= What SyncDock Client adds on top of this plugin =

* **Publish without wp-admin** — write, edit, delete, schedule, and upload from the browser toolbar
* **One-click AI drafting and rewrites** — topic in, drafted and SEO-scored post out, using your own AI provider key
* **Markdown and HTML to Gutenberg** — paste Markdown, get native blocks: headings, lists, tables, quotes, images
* **Unlimited connected sites** — install this plugin on each site and switch between them in one click
* **Automations, workflows and a content calendar** — set a cadence per site and let it publish on schedule, with topic de-duplication
* **Content clusters** — plan topical coverage as a system instead of a pile of one-off posts
* **Media library access** — browse, search, upload, and set featured images per site
* **SEO scoring before publish** — readability, heading structure, and keyword coverage, written back through this plugin's SEO bridge
* **Keys encrypted on your own machine** — credentials sit in a PIN-locked vault in the browser, and requests go straight from your browser to your site. No SyncDock server sits in between, because there isn't one.

= Why the plugin exists =

WordPress's native REST API provides basic CRUD operations, but modern AI workflows, automation pipelines, and external integrations demand much more. SyncDock bridges the gap with:

* **Secure API Key Authentication** — SHA-256 hashed keys with per-key scopes, rate limits, and expiration
* **HMAC-SHA256 Signature Verification** — Tamper-proof request signing with replay protection
* **Gutenberg Block Converter** — Bidirectional JSON ↔ block markup for 18+ core block types
* **Content Context Layer** — AI-optimized intelligence endpoints for content discovery and gap analysis
* **Advanced Query Engine** — Multi-filter content search with taxonomy AND/OR logic
* **SEO Plugin Bridge** — Auto-detects Yoast, Rank Math, and AIOSEO for transparent SEO metadata
* **Response Caching** — Configurable TTL with auto-invalidation on content changes
* **Activity Logging** — Full request audit trail with retention policies

= Key Features =

**🔐 Multi-Layer Security**

* API Key authentication with SHA-256 hashing (keys never stored in plaintext)
* Optional HMAC-SHA256 request signing with 5-minute replay window
* 10 granular permission scopes (`read_posts`, `write_posts`, `delete_posts`, etc.)
* Per-key and per-IP rate limiting with sliding window
* Failed authentication brute-force protection (10 failures / 15 min / IP)

**📝 Full Post Lifecycle**

* Create, read, update (PUT/PATCH), and delete posts
* Structured Gutenberg block input/output (JSON ↔ block markup)
* Partial block-level updates (insert/replace/delete by index)
* Post revision listing and one-click restore
* Scheduled publishing via `status: future`
* SEO metadata sync with popular SEO plugins

**🧠 Content Intelligence**

* `/context/posts` — Recent, popular, and similar posts with content gap analysis
* `/context/taxonomies` — Hierarchical category trees and tag clouds
* `/context/media` — Library summary, MIME distribution, unattached files
* `/context/site-info` — Site configuration, image sizes, post types

**🔍 Query Engine**

* Multi-taxonomy filters with AND/OR logic
* Date range queries on publish or modified dates
* Full-text search with highlighted excerpts
* Site-wide content discovery endpoint

**📁 Media Management**

* Upload via multipart/form-data or base64 JSON
* Server-side MIME type validation
* Attach/detach media from posts
* Full image metadata and size information

**⚙️ Admin Dashboard**

* Real-time API metrics (requests, errors, response times)
* API key management with one-click creation and revocation
* Filterable activity log with status/method/search
* System health monitoring
* Configurable rate limits, caching, and data retention

= Who Is SyncDock For? =

* **Anyone running the SyncDock Client extension** — the fastest path: install, generate a key, publish. [Get the extension](https://chromewebstore.google.com/detail/syncdock-client/ncejpifaefhebcpiicdbmdbflckkbiee)
* **AI Agents** — Connect Claude, GPT, or custom models to publish and manage content
* **SaaS Applications** — Build external content management dashboards
* **Browser Extensions** — Create write-from-anywhere tools
* **Desktop Apps** — Build native publishing applications
* **Automation Pipelines** — Integrate WordPress into CI/CD or content workflows
* **Mobile Apps** — Power native mobile content creation tools

== Installation ==

= Step 1 — Install this plugin (the gateway) =

1. Install SyncDock from **Plugins > Add New**, or upload the `syncdock` folder to `/wp-content/plugins/`
2. Activate it through the **Plugins** menu in WordPress
3. Navigate to **SyncDock > Dashboard** in the admin menu

= Step 2 — Generate an API key =

4. Go to **SyncDock > API Keys** and generate a key
5. Copy the key and the HMAC secret — they are shown only once

= Step 3 — Install SyncDock Client (the app you'll use) =

6. Install the free extension: [SyncDock Client on the Chrome Web Store](https://chromewebstore.google.com/detail/syncdock-client/ncejpifaefhebcpiicdbmdbflckkbiee)
7. In the extension, open **Sites > Add site**, paste your site address and the API key
8. That's it — write, generate, and publish to this site from the browser

Prefer to drive the API yourself? Skip step 3 and call `https://yoursite.com/wp-json/syncdock/v1/` directly. **SyncDock > SyncDock Client** in the admin menu walks through both paths.

= Quick Start =

Generate an API key from the admin panel, then:

`
curl -H "X-SyncDock-Key: YOUR_API_KEY" \
     https://yoursite.com/wp-json/syncdock/v1/posts
`

Create a post with Gutenberg blocks:

`
curl -X POST \
  -H "X-SyncDock-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My First SyncDock Post",
    "status": "draft",
    "content": {
      "blocks": [
        { "type": "heading", "content": "Hello World", "attrs": { "level": 2 } },
        { "type": "paragraph", "content": "Created via SyncDock API." }
      ]
    }
  }' \
  https://yoursite.com/wp-json/syncdock/v1/posts
`

== Frequently Asked Questions ==

= Do I need the SyncDock Client extension? =

Not strictly — this plugin is a documented REST API and any client can call it. But SyncDock Client is what makes it a product rather than an endpoint: the editor, AI drafting, SEO scoring, media, scheduling, automations, and multi-site switching all live there. Without a client of some kind, the gateway is running but nothing is using it.

= Where do I get the extension? =

From the Chrome Web Store: https://chromewebstore.google.com/detail/syncdock-client/ncejpifaefhebcpiicdbmdbflckkbiee — it's free, needs no account, and works in Chrome and Chromium-based browsers. The **SyncDock > SyncDock Client** admin page links straight to it and walks through the connection.

= Does my content pass through a third-party server? =

No. The extension calls your site directly from your browser, and calls your chosen AI provider directly from your browser. This plugin makes no outbound calls of its own. There is no SyncDock cloud in the request path.

= Can one extension manage several sites? =

Yes, as many as you like. Install this plugin on each site, generate a key per site, and they all appear in the same switcher — no seat limit and no per-site fee.

= Does SyncDock replace the WordPress REST API? =

No. SyncDock runs alongside the native REST API under its own namespace (`/syncdock/v1/`). Your existing integrations are unaffected.

= How are API keys stored? =

API keys are hashed using SHA-256 before storage. The raw key is shown only once during creation and is never retrievable from the database.

= Does SyncDock support HMAC request signing? =

Yes. Each API key can be configured with HMAC mode set to `disabled`, `optional`, or `required`. When enabled, requests must include `X-SyncDock-Signature` and `X-SyncDock-Timestamp` headers.

= Which SEO plugins are supported? =

SyncDock auto-detects Yoast SEO, Rank Math, and All in One SEO Pack. If none are installed, SyncDock stores SEO data in its own meta fields.

= Can I use SyncDock with custom post types? =

The current version supports the default `post` type. Custom post type support is planned for v1.1.0.

= Is there rate limiting? =

Yes. Rate limits are enforced per API key and per IP address using a sliding window algorithm. Limits are configurable per-key and globally via the settings page.

== Changelog ==

= 1.6.1 =
* Security: a request could skip authentication entirely by varying the capital letters in the route (`/SyncDock/v1/...`), and handlers trusted an API key id supplied in the request. Every route now fails closed, and identity comes only from the authenticated key.
* Security: a status such as `pub%41lish` passed the publish capability check and was then saved as `publish`, letting a contributor publish.
* Security: scripts and iframes in post content are kept only for full-access (`*`) keys whose owner has `unfiltered_html`; a narrow key owned by an administrator can no longer store script.
* Security: users with the key-management capability can manage only their own keys - rotating, re-scoping or revoking another user's key is refused - and a rotation grace period is capped at one day.
* Security: webhooks receive only their own key's events, are deleted with the key, and carry a `delivery_id` (repeat events for one post were silently dropped).
* Security: HMAC nonces are claimed atomically, so a signed request cannot be replayed concurrently; the failed-authentication lockout can no longer block valid keys behind a proxy or CDN.
* Security: per-object checks for meta, media and drafts - `/query/discover` no longer shows other authors' drafts, full post reads return `meta`, `acf` and `seo` only to users who may edit the post, categories and tags check `assign_terms`, and media sideload refuses link-local and carrier-grade NAT addresses.
* Security: queued jobs re-check their key's scopes when they run, and each key is limited to 5000 unfinished jobs and 10 retries per job.
* Performance: the job cron idles hourly when the queue is empty, Autopilot runs within a time budget, and long-running jobs are no longer re-run while still working.
* Performance: fewer queries on content lists - one grouped gap-analysis query, capped tag context, no per-row All in One SEO lookups, and cache keys built only from known parameters.
* Fix: `PUT /posts/{id}` no longer blanks the title, content or excerpt when they are not sent; array values in query parameters no longer cause a PHP error; a truncated Autopilot article is saved as a draft instead of being published.

= 1.6.0 =
* New: Autopilot — optional server-side generation. An administrator stores one provider key (Claude, OpenAI or Gemini) under Settings; it is encrypted with libsodium, never shown again and never logged. Automations synced from SyncDock Client then write and publish on WP-Cron with no browser open, under a hard monthly budget, with no duplicate posts on retries and every run in the activity log and `job.*` webhooks. New scope `run_automation` and routes under `/automations`.
* New: Jobs & Automations screen in wp-admin — filter jobs by state, retry or cancel them, see each automation's runs and spend, and check webhook health.
* New: custom taxonomies — `GET`/`POST /terms/{taxonomy}` for any public REST-enabled taxonomy, and `terms: { taxonomy: [...] }` on post writes and reads, each checked against the key owner's term capabilities.
* New: Advanced Custom Fields — `acf` on post writes and reads for simple field types, sanitized by type, plus `/posts/{id}/fields` and `/posts/fields` to describe the registered fields.
* New: native WordPress footnotes — `footnotes: [{ id, content }]` on post writes and reads, stored where the core Footnotes block reads them.
* New: multisite — network activation, tables for sites created later, per-site API keys and data, and a network uninstall that cleans every site.
* New: site-info capabilities `autopilot`, `native_footnotes`, `taxonomies`, `acf` and `multisite`.
* Fix: site-info capabilities stayed cached after a plugin registered a taxonomy or ACF was switched on.
* Fix: `GET /terms/{taxonomy}?hierarchical=1` with `search` or `parent` returned an empty list.
* Security: an array in a Jobs screen query argument no longer causes a PHP 8 error; Autopilot key forms are handled before any output, so a forged request is refused with a 403.
* Translations: a complete `languages/syncdock.pot`.

= 1.5.0 =
* New: content audit on `GET /query/posts` — `modified_before`, `min_words`, `max_words` and `missing=seo_description,featured_image,excerpt`. Rows carry `word_count`, `featured_image` and `seo_description`.
* New: a stored word count per post (any script), kept current on save and back-filled for existing posts in small background batches.
* New: pages and public REST-enabled custom post types through `post_type` on `/posts` and `/query/posts`, with each type's own capabilities; pages accept `parent` and `menu_order`.
* New: `taxonomy_mode: remove` on `PATCH /posts/{id}` detaches the listed categories or tags.
* New: `PATCH /posts/{id}` accepts `date`, so an existing draft can be scheduled in place (`status: future` + `date`).
* New: blocks SyncDock cannot rebuild (third-party blocks, dynamic core blocks) carry their saved markup as `raw`, and `{ "type": "raw", "content": … }` writes them back unchanged — so editing or AI-rewriting a post no longer flattens or loses plugin blocks.
* New: `GET /posts/{id}/revisions?include_content=1` returns each revision's content and blocks.
* New: `GET /media?missing_alt=1` lists images without alt text; `/query/media` rows include `alt_text`.
* New: a one-paste connection code is shown when an API key is created, so SyncDock Client can connect many sites at once.
* Fix: `content.block_updates` rebuilt the whole post, turning every third-party block into a paragraph. Only the addressed blocks are rebuilt now; everything else keeps its saved markup.
* Fix: with All in One SEO 4 active, SEO fields are written to AIOSEO's own table, where it reads them.

= 1.4.0 =
* Security: every post, media, category and tag request now checks the WordPress capabilities of the user that owns the API key, as wp-admin does. A key can no longer publish, delete, change authors or read drafts beyond its owner's rights.
* Security: job ids and idempotency keys are scoped to the API key that created them; one key can no longer read another key's job or be handed another key's post.
* Security: request signature v2 (`X-SyncDock-Signature-V2`) also signs the query string. v1 keeps working for older clients.
* Security: webhooks refuse private and loopback targets, are delivered without following redirects, and are visible only to the key that registered them.
* Fix: `refresh` jobs update the target post in place (`payload.post_id`) instead of publishing a duplicate.
* New: `PUT`/`PATCH /posts/{id}` accept `expected_modified`; a post changed on the site since it was loaded returns 409 instead of being overwritten.
* Fix: the upload size setting now reaches uploads and URL imports, and a URL import stops downloading at the limit.
* Fix: an expiry sent with a timezone offset was shifted twice; key lists compared GMT expiries against the database's local clock.
* Performance: content changes no longer run a `LIKE` delete over wp_options on every save, autosave and revision.
* Uninstall removes every SyncDock table, cron event and meta key. Removed the unused HMAC toggle and CORS field from Settings.

= 1.3.1 =
* Fix: `categories` and `tags` accept term NAMES as well as ids. Names were run through `absint()` and became `0`, so a post sent by a client that knew its terms only by name (a local draft, a second site in a fan-out, a bulk import, a cron job) published with no terms and no error.
* Fix: `GET /categories` returns a FLAT list unless `hierarchical=1` is passed. It previously returned a nested tree whenever the parameter was absent, which hid every child category from clients that read the response as a list — so a sub-category could not be matched and was re-created by name on the next publish.
* New: `create_categories: true` on a post payload allows a category the site does not have to be created. Without it an unknown category name is dropped: tags are an open vocabulary, sections of a site are not.
* `GET /categories` now accepts `orderby` / `order`, like `/tags`.

= 1.3.0 =
* New: **SyncDock Client** admin page — what each half of SyncDock does, a three-step connection walkthrough, and a one-click install link for the free browser extension.
* New: Dashboard now opens with an ecosystem panel that tracks setup state (gateway installed → key generated → client connected) and reports when a client last called this site.
* New: After generating an API key, the page shows exactly what to paste into SyncDock Client, with the site address ready to copy.
* New: Plugin row links and a dismissible admin notice point to the extension, so the client half is never a surprise.
* New: Every SyncDock surface now leads with what the product is *for* — publishing at volume with AI across many sites — instead of describing the API and leaving the reader to infer the point.
* Docs: readme and plugin description rewritten around the purpose and the plugin + extension ecosystem.

= 1.2.1 =
* Fix: Post lists (`/posts`, `/query/posts`) now include `author_name`, so clients can show an Author column instead of a bare user id.
* New: `/context/site-info` reports an `authors` list (id, name, slug) of users who can edit posts, for author pickers.
* Fix: Site-info responses are cached per plugin version, so an upgrade never serves a payload from the previous shape.

= 1.2.0 =
* New: Durable server-side job queue and idempotency ledger (`/jobs`) so publishing survives client disconnects and never duplicates a post on retry.
* New: Outbound webhooks (`/webhooks`) with asynchronous cron delivery.
* New: Capability discovery — `/context/site-info` and `/query/discover` now report block support, SEO/multilingual integrations, and upload limits so clients can feature-detect before writing.
* New: SEO bridge writes canonical URLs and stores JSON-LD structured data, printed on singular views.
* New: Multilingual passthrough — posts accept `language` and `translation_of`, honoured by Polylang and WPML.
* Fix: Plugin header version aligned with the internal version constant.

= 1.0.1 =
* Fix: Added explicit `wp_kses()` escaping at all SVG icon output points to satisfy WordPress Plugin Check (late-escaping standard).
* Fix: Renamed class prefix from `SyncDock_` to `Syncdock_` across all files to eliminate dual-prefix detection (`syncdock` + `sync_dock`) by WP.org static analysis tools.

= 1.0.0 =
* Initial release
* Multi-layer authentication (API Key, HMAC-SHA256, Scopes, Rate Limiting)
* Full post CRUD with Gutenberg block converter
* Content Context Layer with gap analysis
* Advanced Query Engine with taxonomy filters
* Media upload (multipart + base64) with MIME validation
* Taxonomy management (categories + tags)
* Activity logging with configurable retention
* Response caching with auto-invalidation
* Admin dashboard with real-time metrics
* SEO plugin bridge (Yoast, Rank Math, AIOSEO)

== Screenshots ==

1. SyncDock Dashboard — real-time API metrics, system health, and recent activity.
2. API Keys — generate, manage, and revoke API keys with per-key scopes and rate limits.
3. Activity Log — filterable request log with status codes, response times, and client details.
4. Settings — configure rate limiting, HMAC verification, caching, and data retention.

== Upgrade Notice ==

= 1.6.1 =
Security release: closes an authentication bypass on mixed-case routes, a publish-capability bypass, and stored script from narrowly scoped keys. Webhooks are scoped to their own key. Recommended for all users; no API changes.

= 1.6.0 =
Adds optional Autopilot (server-side generation with an encrypted key and a monthly budget), a Jobs & Automations screen, custom taxonomies, ACF fields, native footnotes and multisite support. Existing keys, jobs and posts are kept; new tables are added automatically.

= 1.3.1 =
Fixes taxonomy on publish: category and tag NAMES are now accepted alongside ids, and `GET /categories` returns a flat list unless a tree is asked for. Posts that were arriving with no category — or with a duplicate of a sub-category — are fixed by this. Recommended for all users.

= 1.3.0 =
Adds the SyncDock Client page and setup guidance so it's clear this plugin is the WordPress half of SyncDock, paired with the free browser extension. No API changes.

= 1.0.1 =
Security hardening update: explicit output escaping for all SVG icons and unified class prefix. Recommended for all users.

= 1.0.0 =
Initial release of SyncDock.

== External services ==

Out of the box, SyncDock does not connect to any external service. API key management, authentication, rate limiting, caching and content operations all run on your WordPress server. The two optional features below send data off your site only after an administrator turns them on.

= Autopilot AI providers (optional) =

Autopilot writes posts on your server with an AI provider. It is off until an administrator stores a provider API key under SyncDock → Settings, and it only contacts the one provider that administrator picked. Nothing is sent to any provider while no key is stored.

**When data is sent:** when an administrator clicks "Test Key" (a one-word test prompt), and each time an enabled automation runs on WP-Cron (one call to write an article, plus one short call for topic ideas if that automation asks the AI to suggest topics).

**What is sent:** the stored API key (in a request header), the chosen model name, the writing instructions, and the automation's topic, site niche and standing brief. A topic-suggestion call also sends up to 30 recent post titles from your site, so the provider can avoid repeating them. No visitor data, user accounts or passwords are sent.

The services, and their terms and privacy policies:

* **Anthropic Claude API** (`api.anthropic.com`) — used when the provider is Anthropic Claude. [Terms](https://www.anthropic.com/legal/commercial-terms), [Privacy policy](https://www.anthropic.com/legal/privacy).
* **OpenAI API** (`api.openai.com`) — used when the provider is OpenAI. [Terms](https://openai.com/policies/business-terms/), [Privacy policy](https://openai.com/policies/privacy-policy/).
* **Google Gemini API** (`generativelanguage.googleapis.com`) — used when the provider is Google Gemini. [Terms](https://ai.google.dev/gemini-api/terms), [Privacy policy](https://policies.google.com/privacy).

= Outbound webhooks (optional) =

A client with the right scope can register a webhook URL. When a subscribed event happens (for example a post or job event), SyncDock sends that event's JSON payload, your site URL and a timestamp, signed with the webhook's secret, to that URL only. The receiver is chosen by you or the client you authorised, not by SyncDock, so its terms and privacy policy are the receiver's own. Private and loopback addresses are refused, and redirects are not followed.

= Other =

Gravatar avatar URLs may appear in API responses for post authors. This is standard WordPress core behavior (`get_avatar_url()`) and is not initiated by SyncDock.

The admin pages link to the SyncDock Client listing on the Chrome Web Store. These are ordinary links you choose to click — nothing is requested from, or sent to, that site by the plugin. The extension itself, if you install it, talks to your WordPress site directly from your browser; it does not route anything through a SyncDock server.
