| 开发者 |
guillaumeferrari
lozit |
|---|---|
| 更新时间 | 2026年5月28日 17:21 |
| PHP版本: | 8.1 及以上 |
| WordPress版本: | 6.9 |
| 版权: | 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.
v1 only swaps the entire page (the variant must be a separate post). Block-level and product-level testing are on the roadmap.
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.