Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ Please make sure you make a backup of your database before updating any version
* Improved: the WooCommerce add-to-cart, remove-from-cart, checkout and purchase events now include a currency property, so they can be broken down by the currency they were placed in. The purchase event uses the currency the order was placed in.
* Fixed: in "domain per language" mode, the proxy endpoint and the locally cached tracker script were loaded from the default domain, which could cause CORS errors on the other language domains.
* Fixed: Plugin Tokens for language domains whose key contains an underscore (e.g., TranslatePress' nl_NL) were stored under a stripped key, which meant they were never read back.
* Fixed: goal, event and funnel names (e.g. "Woo Complete Purchase") were translatable, so a translation update could start sending events under new names, splitting each goal's data and stopping revenue from being recorded. They're now always sent in English. On French sites, where these names were translated in September 2026, events are sent to the original goals again; goals created with the French names can be removed from your Plausible dashboard.
* Tested with WP 7.1.

= 2.6.1 =
Expand Down
14 changes: 9 additions & 5 deletions src/Admin/Provisioning.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,16 @@ class Provisioning {
public function __construct( $client = null ) {
$this->client = $client;

/**
* These names identify the goals in Plausible Analytics and must match the names the events are sent under, so
* they're deliberately not translatable. @see https://github.com/plausible/wordpress/issues/326
*/
$this->custom_event_goals = [
EnhancedMeasurements::FOUR_O_FOUR => __( '404', 'plausible-analytics' ),
EnhancedMeasurements::CLOAKED_AFFILIATE_LINKS => __( 'Cloaked Link: Click', 'plausible-analytics' ),
EnhancedMeasurements::FORM_COMPLETIONS => __( 'WP Form Completions', 'plausible-analytics' ),
EnhancedMeasurements::QUERY_PARAMS => __( 'WP Query Parameters', 'plausible-analytics' ),
EnhancedMeasurements::SEARCH_QUERIES => __( 'WP Search Queries', 'plausible-analytics' ),
EnhancedMeasurements::FOUR_O_FOUR => '404',
EnhancedMeasurements::CLOAKED_AFFILIATE_LINKS => 'Cloaked Link: Click',
EnhancedMeasurements::FORM_COMPLETIONS => 'WP Form Completions',
EnhancedMeasurements::QUERY_PARAMS => 'WP Query Parameters',
EnhancedMeasurements::SEARCH_QUERIES => 'WP Search Queries',
];

$this->init();
Expand Down
2 changes: 1 addition & 1 deletion src/Admin/Provisioning/Integrations.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ private function maybe_dismantle_outdated_funnel( $funnel_name, $view_product_pa

$first_step = (string) ( $steps[0]['goal']['display_name'] ?? '' );

// Only a view-product step is checked: other steps are named after the (translatable) event goals.
// Only the view-product step is checked: it's the only step whose goal changes, with the languages served.
if ( strpos( $first_step, 'Visit ' ) !== 0 || $first_step === sprintf( 'Visit %s', $view_product_path ) ) {
return $all_ids;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Admin/Provisioning/Integrations/EDD.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ public function maybe_create_edd_funnel( $old_settings, $settings ) {

$edd = new Integrations\EDD( false );

$this->integrations->create_integration_funnel( $edd->event_goals, __( 'EDD Purchase Funnel', 'plausible-analytics' ), $edd->post_type );
// Plausible Analytics identifies funnels by name, so it's deliberately not translatable, like the goal names.
$this->integrations->create_integration_funnel( $edd->event_goals, 'EDD Purchase Funnel', $edd->post_type );
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Admin/Provisioning/Integrations/WooCommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public function maybe_create_woocommerce_funnel( $old_settings, $settings ) {

$woocommerce = new Integrations\WooCommerce( false );

$this->integrations->create_integration_funnel( $woocommerce->event_goals, __( 'Woo Purchase Funnel', 'plausible-analytics' ), $woocommerce->post_type );
// Plausible Analytics identifies funnels by name, so it's deliberately not translatable, like the goal names.
$this->integrations->create_integration_funnel( $woocommerce->event_goals, 'Woo Purchase Funnel', $woocommerce->post_type );
}

/**
Expand Down
48 changes: 26 additions & 22 deletions src/Admin/Upgrades.php
Original file line number Diff line number Diff line change
Expand Up @@ -432,13 +432,17 @@ private function upgrade_to_260() {
}

/**
* After updating to 2.6.2, (re)create the Custom Properties and — for multilingual ecommerce installs — the
* integration goals, so existing installs pick up the currency Custom Property and the per-language
* Pageview goals (e.g. /es/producto*) without having to save their settings first.
* After updating to 2.6.2, (re)create the goals, the Custom Properties and the integration funnels, so existing
* installs pick them up without having to save their settings first:
*
* The Custom Properties apply to every WooCommerce/EDD install with Ecommerce Revenue enabled. The localized
* Pageview goals only apply when a multilingual plugin (WPML, with or without WooCommerce Multilingual &
* Multicurrency, or TranslatePress) is active.
* - Goal and funnel names are no longer translated (@see https://github.com/plausible/wordpress/issues/326). Sites
* whose goals were created under translated names (e.g. French, since September 2026) now send their events under
* the English names, so those goals, including the Revenue goal, must exist.
* - The currency Custom Property applies to every WooCommerce/EDD install with Ecommerce Revenue enabled.
* - When a multilingual plugin (WPML, with or without WooCommerce Multilingual & Multicurrency, or TranslatePress)
* is active, the view-product goals are localized per language (e.g. /es/producto*).
*
* Goals and funnels are created with get-or-create requests, so existing ones are left unchanged.
*
* This runs on init (@see Upgrades::__construct()), so the multilingual plugin's languages are available: WPML loads
* them on plugins_loaded and TranslatePress stores them in an option. When there are none (e.g. WPML's setup hasn't
Expand All @@ -452,33 +456,33 @@ private function upgrade_to_260() {
* @codeCoverageIgnore because all we'd be doing is testing the Plugins API.
*/
public function upgrade_to_262() {
$provisioning = new Provisioning();
$settings = Helpers::get_settings();

/**
* (Re)create the goals of the enabled Enhanced Measurements under their (untranslated) names. Bails when no Plugin
* Token is entered yet; the goals are created as soon as one is.
*
* @see Provisioning::maybe_provision_on_connect()
*/
$provisioning->maybe_create_goals( [], $settings );

$is_ecommerce = \Plausible\Analytics\WP\Integrations::is_wc_active() || \Plausible\Analytics\WP\Integrations::is_edd_active();

if ( $is_ecommerce && EnhancedMeasurements::is_enabled( EnhancedMeasurements::ECOMMERCE_REVENUE ) ) {
$provisioning = new Provisioning();
$settings = Helpers::get_settings();

/**
* The currency Custom Property (@see Provisioning::CUSTOM_PROPERTIES) applies to every ecommerce install,
* so (re)create the Custom Properties whether or not a multilingual plugin is active. Bails when no Plugin
* Token is entered yet; the properties are created as soon as one is.
*
* @see Provisioning::maybe_provision_on_connect() Creates the goals and custom properties as soon as a
* Plugin Token is entered.
*/
// The currency Custom Property (@see Provisioning::CUSTOM_PROPERTIES) applies to every ecommerce install.
$provisioning->maybe_create_custom_properties( [], $settings );

/**
* The localized Pageview goals only matter when a multilingual plugin is active and serves languages. When
* it doesn't serve any yet (e.g. WPML's setup hasn't been completed), skip the funnels rather than
* provisioning the default language's path only; they're (re)created with the right paths on the next
* settings save.
* (Re)create the funnels and their goals. When a multilingual plugin is active but doesn't serve any languages
* yet (e.g. WPML's setup hasn't been completed), skip them rather than provisioning the default language's
* path only; they're (re)created with the right paths on the next settings save.
*
* @see Provisioning\Integrations\WooCommerce::init() Both funnels are (re)created on
* update_option_plausible_analytics_settings, i.e. when the Plugin Token is saved.
* @see Provisioning\Integrations\EDD::init()
*/
if ( Helpers::get_multilang_plugin() && ! empty( Helpers::get_active_languages() ) ) {
if ( ! Helpers::get_multilang_plugin() || ! empty( Helpers::get_active_languages() ) ) {
$integrations = new Integrations( $provisioning );

( new Provisioning\Integrations\WooCommerce( $integrations ) )->maybe_create_woocommerce_funnel( [], $settings );
Expand Down
6 changes: 5 additions & 1 deletion src/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,18 @@ public static function get_active_languages() {
* language) the store's base currency is used, and Plausible converts foreign-currency purchases into
* it automatically.
*
* The base currency is read from WooCommerce's setting instead of get_woocommerce_currency(), because multicurrency
* plugins filter the latter to the currency of the current request (e.g. WCML returns the visitor's currency in
* AJAX requests), which would make the goal's currency depend on whichever request happens to provision it.
*
* @since 2.6.2
*
* @param string $domain_key 'default' or a WPML language code (e.g. 'es').
*
* @return string ISO 4217 currency code.
*/
public static function get_currency_for_language( $domain_key = 'default' ) {
$base = Integrations::is_edd_active() ? edd_get_currency() : get_woocommerce_currency();
$base = Integrations::is_edd_active() ? edd_get_currency() : (string) get_option( 'woocommerce_currency', 'USD' );

// A default currency per language is a WCML (WooCommerce + WPML) concept only.
if ( Integrations::is_edd_active() || static::get_multilang_plugin() !== static::MULTILANG_PLUGIN_WPML ) {
Expand Down
16 changes: 10 additions & 6 deletions src/Integrations/EDD.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ public function __construct( $init = true ) {
$uri = '/' . $uri;
}

/**
* These names identify the goals in Plausible Analytics, so they must never change: they're deliberately not
* translatable, because a translation (update) would start sending events under new names, splitting each
* goal's data and leaving e.g. the Revenue goal behind. @see https://github.com/plausible/wordpress/issues/326
*/
$this->event_goals = [
// translators: %s: Product page URI pattern.
'view-product' => sprintf( __( 'Visit %s*', 'plausible-analytics' ), $uri ),
'add-to-cart' => __( 'EDD Add to Cart', 'plausible-analytics' ),
'remove-from-cart' => __( 'EDD Remove from Cart', 'plausible-analytics' ),
'checkout' => __( 'EDD Start Checkout', 'plausible-analytics' ),
'purchase' => __( 'EDD Complete Purchase', 'plausible-analytics' ),
'view-product' => sprintf( 'Visit %s*', $uri ),
'add-to-cart' => 'EDD Add to Cart',
'remove-from-cart' => 'EDD Remove from Cart',
'checkout' => 'EDD Start Checkout',
'purchase' => 'EDD Complete Purchase',
];

$this->init( $init );
Expand Down
5 changes: 3 additions & 2 deletions src/Integrations/FormSubmit.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public function add_js() {
wp_localize_script(
'plausible-form-submit-integration',
'plausible_analytics_i18n',
[ 'form_completions' => __( 'WP Form Completions', 'plausible-analytics' ), ]
// The goal's name, which is deliberately not translatable. @see Provisioning::__construct()
[ 'form_completions' => 'WP Form Completions', ]
);

wp_enqueue_script( 'plausible-form-submit-integration' );
Expand Down Expand Up @@ -109,7 +110,7 @@ private function track_submission( $uri ) {
$proxy = new Proxy( false );

$proxy->do_request(
__( 'WP Form Completions', 'plausible-analytics' ),
'WP Form Completions',
null,
null,
[ 'path' => $uri ]
Expand Down
16 changes: 10 additions & 6 deletions src/Integrations/WooCommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,17 @@ public function __construct( $init = true ) {
$uri = '/' . $uri;
}

/**
* These names identify the goals in Plausible Analytics, so they must never change: they're deliberately not
* translatable, because a translation (update) would start sending events under new names, splitting each
* goal's data and leaving e.g. the Revenue goal behind. @see https://github.com/plausible/wordpress/issues/326
*/
$this->event_goals = [
// translators: %s: Product page URI pattern.
'view-product' => sprintf( __( 'Visit %s*', 'plausible-analytics' ), $uri ),
'add-to-cart' => __( 'Woo Add to Cart', 'plausible-analytics' ),
'remove-from-cart' => __( 'Woo Remove from Cart', 'plausible-analytics' ),
'checkout' => __( 'Woo Start Checkout', 'plausible-analytics' ),
'purchase' => __( 'Woo Complete Purchase', 'plausible-analytics' ),
'view-product' => sprintf( 'Visit %s*', $uri ),
'add-to-cart' => 'Woo Add to Cart',
'remove-from-cart' => 'Woo Remove from Cart',
'checkout' => 'Woo Start Checkout',
'purchase' => 'Woo Complete Purchase',
];

$this->init( $init );
Expand Down
27 changes: 27 additions & 0 deletions tests/integration/Admin/ProvisioningTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,33 @@
use function Brain\Monkey\Functions\when;

class ProvisioningTest extends TestCase {
/**
* The goal names must match the names the events are sent under, so a translation must never change them.
*
* @see https://github.com/plausible/wordpress/issues/326
* @return void
* @throws \ReflectionException
*/
public function testCustomEventGoalsAreNotTranslated() {
$translate = function ( $translation, $text, $domain ) {
return $domain === 'plausible-analytics' ? "Traduit : $text" : $translation;
};

add_filter( 'gettext', $translate, 10, 3 );

try {
$goals = new \ReflectionProperty( Provisioning::class, 'custom_event_goals' );
$goals->setAccessible( true );

$this->assertEquals(
[ '404', 'Cloaked Link: Click', 'WP Form Completions', 'WP Query Parameters', 'WP Search Queries' ],
array_values( $goals->getValue( new Provisioning( false ) ) )
);
} finally {
remove_filter( 'gettext', $translate );
}
}

/**
* @see Provisioning::create_goal_request()
* @return void
Expand Down
7 changes: 5 additions & 2 deletions tests/integration/HelpersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -462,13 +462,15 @@ public function testGetActiveLanguagesTranslatePressSkipsUnpublishedLanguages()

/**
* A language's pinned WCML default currency should be used; anything else ("Keep", stored as false, 0 or '0')
* should fall back to the store's base currency.
* should fall back to the store's base currency setting, not the (filtered) currency of the current request.
*
* @see Helpers::get_currency_for_language()
* @return void
*/
public function testGetCurrencyForLanguage() {
when( 'get_woocommerce_currency' )->justReturn( 'USD' );
// Multicurrency plugins filter this to the currency of the current request.
when( 'get_woocommerce_currency' )->justReturn( 'JPY' );
update_option( 'woocommerce_currency', 'USD' );

$plugin = function () {
return Helpers::MULTILANG_PLUGIN_WPML;
Expand Down Expand Up @@ -511,6 +513,7 @@ public function testGetCurrencyForLanguage() {
remove_filter( 'plausible_analytics_multilang_plugin', $plugin );
remove_filter( 'wpml_default_language', $default );
delete_option( '_wcml_settings' );
delete_option( 'woocommerce_currency' );
}
}

Expand Down
31 changes: 31 additions & 0 deletions tests/integration/Integrations/WooCommerceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,37 @@

#[AllowDynamicProperties]
class WooCommerceTest extends TestCase {
/**
* The event names identify the goals in Plausible, so a translation must never change them.
*
* @see https://github.com/plausible/wordpress/issues/326
* @return void
*/
public function testEventGoalsAreNotTranslated() {
when( 'wc_get_permalink_structure' )->justReturn( [ 'product_base' => 'product' ] );

$translate = function ( $translation, $text, $domain ) {
return $domain === 'plausible-analytics' ? "Traduit : $text" : $translation;
};

add_filter( 'gettext', $translate, 10, 3 );

try {
$this->assertEquals(
[
'view-product' => 'Visit /product*',
'add-to-cart' => 'Woo Add to Cart',
'remove-from-cart' => 'Woo Remove from Cart',
'checkout' => 'Woo Start Checkout',
'purchase' => 'Woo Complete Purchase',
],
( new WooCommerce( false ) )->event_goals
);
} finally {
remove_filter( 'gettext', $translate );
}
}

/**
* @see WooCommerce::track_entered_checkout()
* @return void
Expand Down
Loading