| 开发者 | 301st |
|---|---|
| 更新时间 | 2026年5月13日 06:18 |
| PHP版本: | 8.0 及以上 |
| WordPress版本: | 6.9 |
| 版权: | GPLv2 or later |
| 版权网址: | 版权信息 |
{a|b|c} — randomly pick one option, with nesting support[<config>a|b|c] — pick N elements, shuffle, join with custom separators%var% — global, local (#set), and shortcode-level variable scopes{?VAR?then|else} — render a branch based on whether a variable is set (also {?!VAR?then} inverted){plural <count>: form1|form2|form3} — pick grammatically correct noun form by count. RU/UK/BE 3-form (one|few|many), EN-style 2-form (one|many). First spintax engine with first-class plurals.#include or [spintax]wp spintax bindings list|apply|test|export|importspintax folder to /wp-content/plugins/[spintax slug="my-template"] in posts/pages or spintax_render('my-template') in theme filesGo to Spintax > Add New in the WordPress admin. Enter a title and your spintax markup in the editor.
{a|b|c} — randomly picks one option[a|b|c] — permutation: picks N elements, shuffles, joins with space[<minsize=2;maxsize=3;sep=", ";lastsep=" and "> a|b|c|d] — configured permutation%variable% — variable reference#set %var% = value — local variable definition{?VAR?then|else} — conditional: render a branch by truthiness of %VAR% (also {?!VAR?then} inverted){plural %Count%: form1|form2|form3} — plural agreement: picks the correct grammatical form by count (RU 3-form, EN 2-form)/#comment#/ — block comment (stripped from output)#include "slug" — embed another template{plural N: form1|form2|form3} in depth (EN/RU){?VAR?then|else} value-driven branching (EN/RU)The plugin uses the WordPress Object Cache API. With a persistent backend (Redis, Memcached), cached output persists across requests. Without one, templates are re-rendered on each page load.
Yes: [spintax slug="greeting" name="Alice" city="Moscow"] makes %name% and %city% available inside the template.
A binding pairs a Spintax template (or a per-post inline source) with one target field on one post type — for example "Posts → ACF: hero_subtitle". Configure it once under Spintax → Bindings and the plugin populates the field on every matching post on save, on a cron schedule, or on demand via Bulk Apply. Manual edits are preserved by default (hash-tracked); flags control whether the binding auto-seeds empty fields, regenerates on every save, or clears the field when the template renders to empty.
Yes. Bindings support both ACF (text / textarea / wysiwyg, top-level fields) and plain post-meta keys. ACF Free and Pro are both supported; nested fields (repeater / flexible_content rows) are not supported in 2.0 — that lands in a later release. The form-side field picker auto-fills the stable ACF field key so writes work on the first save without ACF's reference-meta handshake.
It's a recommended optional dependency for binding-heavy sites. The plugin works without it, but two features degrade:
wp spintax bindings apply --binding=<id> --all).Five subcommands under wp spintax bindings:
wp spintax bindings list [--format=table|json|csv] — list all bindings on the site.wp spintax bindings apply --binding=<id> [--all|--post=<id>] [--dry-run] — run a binding against all matching posts (or a single post), with optional dry-run. This is the no-Action-Scheduler fallback path for Bulk Apply.wp spintax bindings test --binding=<id> --post=<id> — dry-run a binding against one post and report what BindingApplier::plan() would do (would_write, current value, rendered preview, skip reason). Same logic as the admin Test panel.wp spintax bindings export [--format=json] [> bindings.json] — emit the full bindings store as JSON, deduped by (post_type, target.key).wp spintax bindings import --file=bindings.json [--overwrite] [--dry-run] — import bindings from JSON. --overwrite updates matches on the same target triple; without it, duplicates are skipped. Use --dry-run to preview the plan without writing.A binding template renders with four layered variable sources (later layers override earlier ones — see spec §4.3):
#set block in Settings → Spintax → Global Variables. Site-wide.#set block in the binding's Variables → "Per-binding #set overrides" textarea. Applies to that binding only.%post_id%, %post_title%, %post_url%, %post_slug%, %post_date%, %post_modified%, %author_id%, %author_name%.%acf_<field_name>%. Reads happen after ACF persists its values (save_post priority 20 hook), so siblings are always fresh on save_post triggers. Only meaningful for ACF-target bindings.{a|b|c}, [a|b], {?VAR?then|else}, {plural %N%: ...}, #include "slug", /#comment#/).
Two trigger paths, both configurable per binding under "Triggers":
save_post priority 20. Runs after ACF persists its own field values, so sibling reads see fresh data. Skipped during autosave / bulk-edit / REST batch imports / revisions / trash flips.spintax_binding_cron_<binding_id>. On the scheduled tick, the callback enqueues an Action Scheduler walk (or runs synchronously if AS isn't installed — see the Action Scheduler FAQ above). Independent of save_post; use this to refresh content periodically without an editor touch.Each binding tracks a SHA-1 signature of its last-rendered value in post-meta _spintax_last_render_sig_<binding_id>. On every subsequent run with Preserve manual edits enabled (default), it compares the current target value's hash to the stored signature:
SKIP_MANUAL_EDIT_DETECTED and log the skip.Regenerate on every save, this gives a "refresh on save unless edited" workflow. With Auto-seed empty fields instead, the binding only writes when the target is empty — manual edits are preserved by definition because they're never overwritten.
There is a "cold-start" exception: when a binding first sees a post with non-empty target content and no signature yet, it treats the existing value as an unwritten manual baseline and skips (SKIP_COLD_START_MANUAL) until the editor clicks the binding's "Initialize from current value" path (a later UI addition) or accepts the regeneration by clearing the field first.
Bindings are a pre-generation system, not a render-on-read layer. The rendered string is stored in the target field; consumers (themes, blocks, REST readers) get that stored value directly. Editing the source template doesn't propagate to existing posts until a trigger writes a fresh value to each one. When you edit a template that has bindings pointing at it, the plugin:
wp spintax bindings apply --binding=<id> --all from the CLI). The Stale badge only clears when the entire walk completes with zero failures — partial-failure walks keep the badge so you notice the divergence and retry.
200 bindings per site. The store is a single autoloaded option (~500 bytes per binding), and the cap keeps autoload memory bounded. If you genuinely need more, please open an issue with your use case.
The form rejects five tiers of reserved keys at save time:
_wp_, _edit_, _oembed_, plus _pingme, _encloseme, _thumbnail_id._spintax_* prefixes (source, signature, cache-version slots used by other bindings).post_title, post_content, post_excerpt, post_name, post_status, post_date, post_modified, post_parent, post_author, post_type, post_password, etc. These aren't post-meta and writing to them via update_post_meta() silently creates shadow rows.(post type, target key), regardless of whether the kind is ACF or post_meta (they share the same database row).field_5f8a1234abcd) is required, and verified against acf_get_field() when ACF is loaded.No — bindings are per-site. Each subsite manages its own. Use wp --url=site2 spintax bindings import --file=site1-bindings.json to copy bindings between subsites via the WP-CLI export/import round-trip.
Not in 2.0; bindings are admin-only. The wp spintax bindings WP-CLI surface covers staging→production sync scenarios. REST API exposure is tracked for a later release.
nested-spintax-for-acf. Is there a migration path?Yes. After activating Spintax 2.0, a dismissible admin banner points to Tools → Spintax Migration. The wizard scans for predecessor data, shows a per-row preview, and creates bindings deduped by (post type, target field). Per-post sources and variables are copied non-destructively — the old plugin's data stays in place until you delete it.
BindingApplier::plan() rejects bindings whose stored target.field_key no longer resolves to a field with the expected name (deleted, renamed, or re-assigned in ACF). Two new return codes: skip_acf_not_loaded (ACF deactivated since the binding was saved) and skip_invalid_acf_field (key + name disagreement). Closes a path where CLI-imported or imported-while-ACF-inactive bindings could write through update_field() to the wrong field.BindingApplier::read_target() and ::write_target() no longer fall back to plain update_post_meta() / get_post_meta() for kind = acf_field when ACF isn't loaded. The applier short-circuits at the runtime guard above, so the low-level methods are the sole writer for verified targets. Pre-2.0.3 the silent fallback could write the rendered value to a post-meta row ACF would never see again._spintax_binding_walk_failed_v_<id> flag. The final chunk gates stamp_last_applied_version() on the cumulative flag. 2.0.1 only checked the current chunk, so a multi-chunk walk that failed in chunk 1 and succeeded in the final chunk would still clear the Stale badge.WP_Error 'walk_in_progress'. Both enqueue() and run_synchronously() acquire a per-binding lock (option _spintax_binding_walk_lock_<id>) at walk start; stale locks older than one hour are auto-overwritten so a crashed walk doesn't permanently jam the binding.npm run lint:php and lint:php:fix moved to scripts/lint-php.sh / scripts/lint-php-fix.sh. The inline command tripped over bash-c quoting on Windows. .gitattributes enforces LF endings on shipped text files.wp spintax bindings import --overwrite help text updated to reflect the 2.0.1 (post_type, target.key) uniqueness contract.wp spintax bindings WP-CLI surface, variable scopes (global / per-binding / post context / ACF siblings), trigger options (save_post + per-binding cron), manual edit detection, template-edit propagation, reserved-key tiers.(post_type, field name) no longer coexist — they wrote to the same database row and silently raced. Tier 4 uniqueness now ignores target.kind. Existing pre-2.0.1 conflicts remain in the data store but the next save of either binding will reject.target.field_key and validate it against the live ACF field when ACF is loaded. Previously a missing or mistyped field key could route update_field() writes to a different field.skip_out_of_scope_type / skip_out_of_scope_status for posts that wouldn't match the binding's scope in live triggers. Two new applier return codes — total now 11 instead of 9.auto_seed_empty (default on; never clobbers existing content), regenerate_on_save, preserve_manual_edits (hash-tracks the last rendered value so external edits are detected), clear_on_empty. Cold-start behaviour documented to avoid false manual-edit positives.wp_schedule_event hooks per binding.%post_id%, %post_title%, %post_url%, %post_slug%, %post_date%, %post_modified%, %author_id%, %author_name% post-context variables — opt-in per binding.%acf_<field_name>% variables — opt-in per binding, exposes ACF sibling fields in the same group.wp spintax bindings list|apply|test|export|import — full WP-CLI surface for staging→production workflows and Action-Scheduler-less environments.nested-spintax-for-acf. Detects, previews, and imports legacy data deduped by (post_type, target.key). Original predecessor data is never deleted by the migration._spintax_* prefixes, wp_posts column names, and duplicate (post_type, target.kind, target.key) triples at form save.{plural <count>: form1|form2|form3} — pick the correct grammatical form by count. RU/UK/BE = 3 forms (one|few|many); EN/ES/PT/DE etc. = 2 forms (one|many). Count is a %var% reference or literal integer (resolved after variable expansion, so helper-var patterns via #set work). Locale comes from per-template post meta _spintax_locale or the WordPress site locale. Lenient at runtime: malformed constructs render verbatim with fullwidth braces instead of crashing the page. First spintax engine to treat plural as a first-class primitive.{}, []) always on; arity check (RU expects 3, EN expects 2) when locale is known.spintax-plurals.test.ts in casino-platform). Engine classes Plurals, PluralArityError, PluralFormError ship alongside Conditionals from 1.4.0.{?VAR?then|else} — render a branch based on whether a variable is set/non-empty (also {?!VAR?then} for inverted, optional else). Resolves both before and after %var% expansion, so conditionals inside variable values work too.соц., эл., Mr., Inc. no longer trigger sentence-end capitalisation of the next word. Covers Russian editorial/address/unit shorthands plus English titles and business suffixes.#set directive with an empty value (#set %x% =) no longer silently swallows the next directive on the following line.[<li>item</li>|<li>...]) are no longer mis-parsed as a <config> block.< sep > before |<and>, <или>)