| 开发者 |
guillaumeferrari
lozit |
|---|---|
| 更新时间 | 2026年6月15日 17:15 |
| PHP版本: | 8.1 及以上 |
| WordPress版本: | 7.0 |
| 版权: | GPLv2 or later |
| 版权网址: | 版权信息 |
docs/security/latest.md).
Features
abtest_event_logged action hook ready for v2 GA4/webhook integrations/wp-content/plugins/.On their first visit to the control page, a cookie abtest_{experiment_id} is set with value a or b. Subsequent visits read that cookie — the visitor always sees the same variant.
No. Logged-in users with edit_posts capability are bypassed and always see the control. The admin bar shows a marker indicating which experiment is running on the page.
Always use the Update action — Plugins → Installed Plugins → Update, or wp plugin update variolab-ab-testing on the command line. Do not delete the plugin to reinstall a newer copy: deleting runs the uninstaller. As of v0.18.0 the uninstaller keeps your data by default (so an accidental delete no longer wipes your experiments, stats, page-import records, or settings), but the Update action is still the correct, cleanest way to upgrade. If you ever do want a full clean removal that erases everything, tick Delete all data on uninstall in Settings → Data & uninstall before deleting the plugin.
v1 only swaps the entire page (the variant must be a separate post). Block-level and product-level testing are on the roadmap.
A/B test pages must never be served from a cache. A cached page freezes one variant for every visitor (breaking the 50/50 split) and skips impression logging — so conversions get dropped. The plugin already sends no-store headers and auto-excludes test URLs from WP Rocket and LiteSpeed, but server-level and edge caches it can't control (Cloudways/Varnish, generic nginx caches, Cloudflare APO) may still cache the page. Two options:
/lp/…) so one rule covers all of them. Step-by-step guidance for your host is shown in Settings → Caching & CDN.noindex on those pages if SEO matters).hsFormCallback window message, but the current HubSpot embed (Forms V4) doesn't broadcast it — it dispatches a DOM event (hs-form-event:on-submission:success) instead, so submissions weren't being counted. The tracker now listens for the V4 event (and still supports the legacy message for older embeds), reads the form GUID via HubSpot's getFormFromEvent API when present, and records the conversion. If you used a HubSpot goal on 0.20.0, this is the fix that makes it fire.hsFormCallback message HubSpot posts to the page — the same hook Google Analytics / GTM use), verifies it really comes from a HubSpot origin, and records the conversion. Leave the goal value empty to count any HubSpot form submission on the test page, or paste a HubSpot form GUID to count only that specific form. When you preview a running test as a logged-in admin, the browser console logs each HubSpot event so you can confirm the wiring with one test submission.wp plugin update); only delete when you really want the plugin gone. A new Settings → Data & uninstall section explains this and offers an opt-in "Delete all data on uninstall" checkbox (off by default) for when you want a clean removal that erases everything.?_abtcb=… redirect when you can't edit your cache rules. The admin cache notice now also detects Cloudflare and points to both options. (Default off; recommended fix remains a proper cache exclusion.)variolab-ab-testing/assets/js/tracker.js and AbtestTracker from it manually.wp_salt('auth'), so rotating your site's authentication keys silently reset visitor de-duplication (re-counting returning visitors). It now uses a dedicated salt seeded once from the current auth salt — existing counts are preserved (no reset on update), and future key rotations no longer disturb your stats. Last clean-up item from the 2026-06-12 internal security audit.unfiltered_html capability) and, when rendering, re-filters anything that wasn't through WordPress's standard sanitizer. Single-site admins and existing imported pages are unaffected; the feature also remains available to multisite site administrators (their content is simply sanitized as WordPress already does on save). Closes the last actionable item from the 2026-06-12 internal security audit.hash_equals()); and a misleading .gitignore entry was cleaned up. No functional change for normal use.cron_schedules filter, which fires before the init action — tripping WordPress 6.7+'s _load_textdomain_just_in_time notice on every cron run (visible with WP_DEBUG and flagged by Plugin Check). The label, only ever shown in cron-management tools, is no longer translated, so nothing loads the text domain early. No functional change.127.0.0.1, 169.254.169.254, 10.0.0.0/8, 192.168.x) and at request time, where WordPress resolves the hostname and rejects targets that point inside your network (reject_unsafe_urls, which also blocks redirect-based bypasses). Legitimate public endpoints (Zapier, Make, Slack, your own API) are unaffected. Hardening from the 2026-06-12 internal security audit.<hr class="wp-header-end"> marker injected at the top of Admin::render_brand_header(), so all four plugin pages (List / Edit / Settings / Import) get the fix.templates/blank-canvas.php called UrlScripts::render_for_position(), which was never defined on the class — every visit to an HTML-imported page running an A/B test crashed with Call to undefined method. Added the missing method as the return-string counterpart of the existing print_for_position(); themed pages are unaffected.Stats::overview_kpis() aggregator; toolbar with 5 status chips (All / Draft / Running / Paused / Ended) + date range with 7d/30d/All-time presets; URL blocks rendered as cards with per-experiment 3-column CSS grid; ended experiments collapse into a native <details> per URL block. Cream canvas (#EFECE4) replaces the wp-admin gray across every plugin admin screen (scoped via body.toplevel_page_abtest-experiments).assets/js/url-charts.js wrapper with a ~200-LOC vanilla list-interactions.js that renders an SVG polyline per (experiment, variant) using a 12-hex rotating palette so the chart line color matches the variant tag color in the row above. Variant A renders solid, B/C/D dashed. Dashed light-gray vertical markers show each experiment's start + end dates with hover tooltip.Admin::render_brand_header( $title ) helper applied to List / Edit / Settings / Import; the legacy form-table styles on Edit / Settings / Import are preserved by the dual wrap vlab-page abtest-wrap class so the form submission paths are unchanged.pyftsubset). SIL OFL 1.1 license files shipped alongside.?status_filter=all|draft|running|paused|ended query arg replaces the old ?show=. The legacy parameter is translated silently for one release so bookmarks keep working.admin-tokens.css (design tokens + @font-face, everywhere) → admin-shell.css (cream bg + brandline + buttons, everywhere) → admin-list.css (list-specific, list page only). The legacy admin.css is kept verbatim for the other pages.Abtest\ PHP namespace, abtest_* hook / cookie / option / table prefixes, REST namespace abtest/v1, custom table wp_abtest_events. No DB / cookie / option breaking change.variolab → variolab-ab-testing (matches the slug wp.org reserved on resubmission). Text domain mass-updated across every __() / _e() call, main file variolab.php → variolab-ab-testing.php, composer.json / package.json package names, phpcs.xml.dist text-domain element + file ref, tests/Integration/bootstrap.php require path, .github/workflows/{ci,release}.yml build folder + zip filename + header-version grep..html / .htm / .js files to the uploads directory (wp.org policy: no code-bearing files in uploads even though the area itself is allowed). The main index.html is read directly from the zip into memory and stored in post_content instead — never touches disk. CSS, images, fonts continue to extract normally. Local JS the template referenced via <script src="./bundle.js"> will 404 at render time; admins can re-inject inline JS via the per-URL tracking-scripts feature.UrlScripts::print_for_position() now wraps each entry via wp_print_inline_script_tag() — the WP-blessed inline-script helper — instead of raw echo. The new UrlScripts::parse_script_input() silently strips <script ...> / </script> wrappers the admin pastes, extracting src / async / defer / type / id attributes for fidelity. 11 unit tests cover plain JS / single wrapper / multi-script degraded mode / orphan tags / boolean attributes.ab_experiment → abtest_experiment (wp.org requires ≥4-char prefixes; ab_ was too short). Menu slug ab-testing → abtest-experiments. New idempotent migration Plugin::pre_install_rename_post_type() runs at upgrade (DB schema v1.3.0 → v1.4.0) renaming every existing post_type row in one statement before the new CPT registers on init. Uninstall handler accepts both new and legacy slugs so old installs still get cleaned up.Abtest\ namespace, abtest_* hook / cookie / option / table prefixes (already 6-char), and REST namespace abtest/v1 stay untouched — no breaking change for existing data.variolab). The wp.org Plugin Review Team flagged "Uplift" on two cumulative grounds: (1) it is the standard industry term for the A/B-testing lift metric (non-distinctive — every VWO/Statsig/Insider/etc. doc uses "uplift" to mean conversion-rate lift), and (2) UPLIFT® is a live USPTO trademark (Reg. 4973441, UPLIFT INC., San Francisco) in the same "Advertising, Business & Retail Services" class as the plugin. Variolab is an invented term (vario + lab) with no wp.org / USPTO / SaaS hit at name-pick time.uplift-ab-testing → variolab everywhere — every __()/_e() call across includes/), main plugin file uplift-ab-testing.php → variolab.php, composer.json/package.json package names, phpcs.xml.dist text-domain element + file ref + ruleset name, tests/Integration/bootstrap.php require path, .github/workflows/{ci,release}.yml build folder + zip filename + header-version grep.Abtest\ PHP namespace, abtest_* hook/cookie prefixes, REST namespace abtest/v1, custom table wp_abtest_events, option keys (abtest_settings, abtest_db_version).uplift-ab-testing). The WordPress trademark guideline forbids the word "WordPress" in both the plugin display name and the slug — this rename closes the last remaining wp.org submission blocker.uplift-ab-testing everywhere — every __()/_e() call across includes/), main plugin file ab-testing-wordpress.php → uplift-ab-testing.php, composer.json/package.json package names, phpcs.xml.dist text-domain element, tests/Integration/bootstrap.php require path, release.yml + ci.yml build paths and zip filename.Abtest\ PHP namespace, abtest_* hook prefixes, abtest_* cookies, REST namespace abtest/v1, custom table wp_abtest_events, and option keys (abtest_settings, abtest_db_version) all stay as-is. They're internal — never visible to wp.org reviewers and never on a user URL.wp-tests-config.php, phpunit.xml*, and phpcs.xml* are now excluded from the built plugin folder by both release.yml and ci.yml. They were leaking into the artifact and tripping missing_direct_file_access_protection (the test bootstrap doesn't and shouldn't have an ABSPATH guard).languages/.gitkeep with languages/index.php (the canonical "Silence is golden" pattern). .gitkeep was rejected as a hidden file by Plugin Check.templates/blank-canvas.php ($insert_at → $abtest_insert_at, $body_close → $abtest_body_close). Template files run in global scope, so unprefixed top-level vars trip PrefixAllGlobals.NonPrefixedVariableFound.release.yml's rsync) instead of the raw repo, so dev-only files (tests/, .claude/, .github/, CLAUDE.md, composer.json, etc.) no longer pollute the report. Cuts ~80% of the false-positive noise.ignore-codes list added with one-line rationale per entry: custom-table direct queries, file-system ops on plugin-controlled paths, mt_rand/mt_srand for variant picking, meta_query slow-query warnings, the init core-hook false positive.load_plugin_textdomain() call: WordPress.org auto-loads translations for hosted plugins since WP 4.6 — manual loading is now discouraged. Text-domain header stays declared so JIT loading still works.languages/ folder (with a .gitkeep documenting why) to satisfy the Domain Path: /languages plugin header — Plugin Check (and wp.org reviewers) flag the header when the folder doesn't exist.assets/js/vendor/chart.umd.min.js. This satisfies the wp.org plugin guideline #5 "Trying to remotely load code". MIT license attribution + update instructions are documented in assets/js/vendor/README.md.plugin-check-action runs on every push to main and PR. Same automated checks as the wp.org reviewers (plugin headers, i18n, late escaping, deprecated APIs, internationalization). Any future regression that would be flagged at submission time is caught at push time instead.<meta name="robots" content="noindex,nofollow"> tag and a matching X-Robots-Tag HTTP header — regardless of which experiment is currently running. Recommended for landing pages dedicated to paid traffic, or any URL where you don't want both A/B variants to compete in search results.abtest_url_settings option keyed by URL path) so every experiment that lands on the same URL inherits it. Future URL-scoped flags can plug into the same store.Abtest\UrlSettings helper class with 7 unit tests covering normalization, default pruning, and per-URL independence..pot / .po) can produce localized versions later. Audit reports, todo, slash commands, internal rules, lessons-learned all translated. CLAUDE.md adds an explicit "English only in the repo" rule to prevent regressions.Abtest\Admin\StatsExplain with 8 unit tests covering each branch.lint job is BLOCKING (was continue-on-error). Any new code that violates the ruleset fails the build.[], short ternary ?:, alignment, and trivial-method docblocks no longer enforced. All Security / SQL / i18n / capability / nonce sniffs remain strict.phpcs:ignore annotations on the codebase carry a one-line justification (why the rule is suppressed at this site).translators: comments on all _n() / __() calls with placeholders so the .pot file can guide translators.Autoload::load($class) to Autoload::load($class_name) since class is a PHP reserved keyword as a parameter name.wp_check_filetype_and_ext()) on top of the extension allowlist — for .zip this catches a PHP file disguised as a zip via magic-byte mismatch.http:// or https:// (anti-SSRF basic — blocks gopher://, ftp://, webcal://, etc. that esc_url_raw() would otherwise accept)./abtest/v1/convert now rate-limits each visitor IP to 60 conversions per minute (filterable via abtest_convert_rate_limit_per_min). Returns HTTP 429 when exceeded. Prevents distributed flood from biasing experiment statistics... defensively (anti-traversal hardening)..gitignore extended with .env, .env.*, wp-tests-config.php, *.local.php, *.key, *.pem, *.p12, secrets.json (preventive — none of these files exist today).file_get_contents() calls reading local files (4 spots) and on the intentional 5-minute Watcher cron interval.'sslverify' => true explicitly so a third-party http_request_args filter can't silently downgrade SSL verification. Aligns with the explicit setting already in the GA4 integration.test_url = /promo/ now matches /fr/promo/, /en/promo/, /de/promo/, etc. The bundled MultiLanguage helper auto-detects WPML/Polylang and strips the language prefix from request paths before matching. Compound slugs (pt-br, en-us) supported. Mid-path occurrences of a language slug (e.g. /blog/fr/x/) are NOT stripped — only true URL prefixes.abtest_request_path for custom multilingual setups: receives the normalized request path, returns whatever you want the matcher to see. Documented in README.remove_filter('abtest_request_path', [\Abtest\MultiLanguage::class, 'strip_language_prefix']).init priority 0 instead of plugins_loaded.GET /wp-json/abtest/v1/stats now runs a single batched SQL query for N experiments instead of N individual queries (N+1 → 1). New public Stats::raw_counts_for_experiments() powers both the REST endpoint and the admin list — same SQL path everywhere.abtest_visitor_has_consent filter returns true. Without consent, visitors silently see Variant A (same path as out-of-target). Off by default, no breaking change.wp_add_privacy_policy_content() — find it under Settings → Privacy → Policy Guide → Variolab – A/B Testing, ready to paste into your privacy policy.Consent helper class + 5 unit tests covering the 4 gate states (off, on+true, on+false, on+null/missing filter)..zip archives — extracts CSS/JS/images to wp-content/uploads/abtest-templates/{slug}/, rewrites relative asset URLs in the HTML so the page renders with full styling (security: extension allowlist + path-traversal guard).index.html files in wp-content/uploads/abtest-templates/{slug}/ from your IDE, SFTP, or cloud sync — WP-Cron syncs changed files into pages every 5 minutes (or click "Scan now" in the Import HTML page). Hash-based change detection skips unchanged files.test_url = /promo/?campaign=fb matches visitor URL /promo/?campaign=fb&utm_source=email. Param order is canonicalized.test_url = /promotion-été/ matches both the raw and percent-encoded request paths.pattern= constraint removed accordingly.CF-IPCountry header (and similar X-* headers), with a abtest_visitor_country filter for custom geo plugins._abtest_variants from legacy control_id/variant_id pair.variants, comparisons, baseline, best, alpha.control_id/variant_id accessors and meta still work; legacy A/B keys still in compute() output.test_url independent from variant pages.