Skip to content

v0.35.0

Choose a tag to compare

@github-actions github-actions released this 14 Aug 19:42
· 46 commits to main since this release

Added

  • Weight and dimensions now emit on OfferShippingDetails and on each variant (#614, #615).

    • Schema.org defines weight, height, width and depth on OfferShippingDetails as well as on Product, and Google draws the same distinction: product_* describes the item, shipping_* describes the parcel. Both now carry the same values.
    • WooCommerce has exactly one set of dimension fields — filed under the product editor's Shipping tab, and consumed by WooCommerce's own shipping methods to compute rates — so that one set legitimately answers both questions. For a single-item order the numbers are identical; any divergence is packaging overhead, the same approximation Google Merchant Center already accepts.
    • Each variant now carries dimensions too. A consumer reading a single hasVariant entry — the node holding the purchasable offer — previously saw none. Values resolve per variant: its own if set, the parent's otherwise, which is WooCommerce's own getter inheritance rather than logic this plugin wrote. Every variant emits, including inherited values, so a variant read in isolation never looks like it has no shipping data.
    • Virtual products, whether simple or a variation, continue to emit no shipping/dimension data — the existing needs_shipping() / has_weight() / has_dimensions() gates are unchanged. Downloadable is a separate WooCommerce checkbox and does not suppress either field: a physical product sold with a bundled download still ships and still carries its dimensions.
  • The six recommended product attributes are now created automatically (#623).

    • A fresh WooCommerce store ships with no product attributes at all, so merchants build them ad hoc, name them freely, and type values freely. The plugin now creates pa_gender, pa_age_group, pa_color, pa_size, pa_material and pa_pattern if they are missing. Seeding is attempted on activation, on every plugin upgrade, and whenever a merchant toggles the syndication enabled setting (the same internal flag that triggers a rewrite-rule flush) — not just on a version change. This is a no-op almost every time, since each attribute is skipped once it exists; the one visible case is a merchant who deliberately deleted one of these attributes seeing it recreated on the next such trigger. Use the wc_ai_storefront_seed_attributes filter (below) to opt out first if that's not wanted.
    • Gender and Age group carry Google's complete accepted values (male/female/unisex, and newborn/infant/toddler/kids/adult), because Google defines those lists exhaustively. Merchants should not need to add to them.
    • Color, Size, Material and Pattern carry a small starting set that merchants extend. Google treats these as free text and asks that submitted values match the merchant's own product page, so a canonical list would be wrong. Size deliberately uses abbreviations (e.g. S, M, L — the full set is XS through 3XL plus One size) per Google's consistency guidance, rather than the Small/Medium/Large form WooCommerce's own sample data creates.
    • An attribute that already exists is left completely alone, terms included, decided per attribute rather than all-or-nothing. Existing terms may be variation axes, so adding to or renaming them would break variations and orphan product data.
    • New wc_ai_storefront_seed_attributes filter returns false to skip seeding entirely.
  • Gender and Age group now emit as a typed Product.audience block (#618).

    • Google requires gender and age_group on all Apparel & Accessories products, and reads them from a typed PeopleAudience block, not from additionalProperty. A merchant's Gender attribute previously reached the markup only as a generic additionalProperty entry — published, but invisible to Google for that purpose. For an apparel product that means disapproval rather than a thinner listing.
    • Sourced from the pa_gender / pa_age_group attributes the plugin now seeds (#623), with a bare gender / age_group custom attribute accepted as a compatibility fallback. When both forms are present with different values, pa_gender wins over a bare gender (same for age group) — order-independent, via a priority on each map entry. The losing attribute is never discarded; it still emits as its own additionalProperty entry.
    • Gender is not validated. Any non-empty value emits as suggestedGender — a value matching male/female/unisex is normalised to lowercase, anything else (even a value Google itself rejects, like "Womens") emits trimmed and verbatim. schema:suggestedGender is Text-ranged, so this is valid markup either way; Google's Merchant Center and Search Console are the intended place to flag a bad value to the merchant, not silent validation here.
    • Age group IS mapped, and can't follow gender's rule. suggestedAge is a QuantitativeValue needing minValue/maxValue/unitCode, so an unrecognised bucket (e.g. "Grown-up") has no honest numbers to emit and falls back to additionalProperty instead. This is a data-model constraint, not an inconsistency with gender's pass-through behaviour.
    • Recognised buckets: newborn (0–3 MON), infant (3–12 MON), toddler (1–5 ANN), kids (5–13 ANN), adult (13+ ANN, no upper bound — matching Google's own worked example).
    • Variable products advertise suggestedGender / suggestedAge in variesBy when that's the varying axis, and each variant carries its own resolved audience, inherited per-field from the parent when the variation doesn't define its own.

Fixed

  • Only the dimension axes WooCommerce actually holds are emitted.

    • has_dimensions() in WooCommerce core is true when any one of length, width or height is set. The emitter published all three off that single gate, and WooCommerce stores an unset axis as an empty string — which casts to 0. A merchant who recorded only a height was therefore publishing depth: 0 and width: 0: fabricated measurements, presented as real ones.
    • Each axis is now emitted independently, gated on its own value. A 0 the merchant actually typed still emits — the check is for an empty value, not a falsy one, so a deliberate (if unphysical) zero is preserved rather than silently dropped.
    • Present since dimensions were first emitted, and made more convincing by the numeric cast below: "depth": "" reads as obviously absent, "depth": 0 reads as a measurement. Caught in review before it reached three placements instead of one.
  • Product dimension values now emit as JSON numbers instead of quoted strings (#613).

    • depth, width, and height published their QuantitativeValue.value as a string literal ("value": "10") while weight on the same product published a number ("value": 1.5). Two adjacent properties in one block disagreed on type.
    • QuantitativeValue.value is Number-ranged. The markup was not invalid, since the property also accepts Text, but a consumer that doesn't coerce read a string where every sibling gave a number.
    • The weight cast was added as audit bug #4 for exactly this reason — WooCommerce persists both weight and dimensions as free-form decimal strings (.5, 10). get_dimensions( false ) returns those props untouched, and the fix was never applied to the dimension branch. All three now cast through (float).
    • No change to which products emit dimensions, to unit codes, or to any other field.

Changed

  • Dev tooling: bump squizlabs/php_codesniffer to 3.13.6 for CVE-2026-67434 (#611). An OS-command-injection advisory (high severity, affecting <3.13.6 and >=4.0.0,<4.0.2) was published 2026-08-05 and failed the composer audit CI gate on every open PR. The package is require-dev only and is not shipped in the plugin zip, so no released version exposed merchants to it; the risk was confined to running lint locally or in CI.

  • Dev tooling: clear ten npm advisories from the dev dependency tree (#616). Transitive devDependencies only — nothing in the tree reaches the built bundle. Six alerts remain and are tracked in #596: extract-zip has no upstream fix at all, and adm-zip, @opentelemetry/core and webpack-dev-server each need a @wordpress/scripts semver-major that would be a larger change than the risk warrants. All are dev-scope, and the CI security gate scopes npm auditing to production dependencies.

Tests

  • JsonLdTest.php — new test_dimension_and_weight_values_encode_as_json_numbers asserts on the wp_json_encode output rather than the array. The pre-existing dimension test used assertEquals( '10', … ), which passes against both a string and a float under loose comparison and so could not detect this defect in either direction; the quotes are only visible after encoding. Also asserts assertIsFloat per value and that no QuantitativeValue anywhere in the markup encodes its value quoted.