diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index 38ef16f5..8797932b 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -40,6 +40,9 @@ jobs:
--health-retries 10
steps:
+ - name: Install SVN
+ run: sudo apt-get install -y subversion
+
- name: Checkout code
uses: actions/checkout@v4
diff --git a/assets/src/js/integrations/form-submit.js b/assets/src/js/integrations/form-submit.js
new file mode 100644
index 00000000..7b6f5a34
--- /dev/null
+++ b/assets/src/js/integrations/form-submit.js
@@ -0,0 +1,41 @@
+/**
+ * Plausible Analytics
+ *
+ * Track Form Submissions JS
+ */
+document.addEventListener('DOMContentLoaded', () => {
+ let plausible_track_form_submit = {
+ forms: document.querySelectorAll('form'),
+
+ /**
+ * Initialization.
+ */
+ init: function () {
+ this.bindEvents();
+ },
+
+ /**
+ * Bind Events.
+ */
+ bindEvents: function () {
+ let self = this;
+
+ this.forms.forEach((form) => {
+ form.addEventListener('submit', (e) => {
+ if (e.target.checkValidity()) {
+ self.trackSubmission();
+ }
+ })
+ })
+ },
+
+ /**
+ * Send a custom event to Plausible.
+ */
+ trackSubmission: function () {
+ plausible(plausible_analytics_i18n.form_completions, {'props': {'form': document.location.pathname}});
+ }
+ };
+
+ plausible_track_form_submit.init();
+});
diff --git a/src/Actions.php b/src/Actions.php
index e29b98d5..10fafb8d 100644
--- a/src/Actions.php
+++ b/src/Actions.php
@@ -1,7 +1,6 @@
tag "tells" the Plausible API which version of the plugin is used, to allow tailored error messages, specific to the plugin
- * version.
- *
+ * This tag "tells" the Plausible API which version of the plugin is used, to allow tailored error messages,
+ * specific to the plugin version.
* @return void
*/
public function insert_version_meta_tag() {
@@ -37,7 +34,6 @@ public function insert_version_meta_tag() {
/**
* Register Assets.
- *
* @since 1.0.0
* @access public
* @return void
@@ -56,9 +52,16 @@ public function maybe_register_assets() {
}
$version =
- Helpers::proxy_enabled() && file_exists( Helpers::get_js_path() ) ? filemtime( Helpers::get_js_path() ) : PLAUSIBLE_ANALYTICS_VERSION;
+ Helpers::proxy_enabled() && file_exists( Helpers::get_js_path() ) ? filemtime( Helpers::get_js_path() ) :
+ PLAUSIBLE_ANALYTICS_VERSION;
- wp_enqueue_script( 'plausible-analytics', Helpers::get_js_url( true ), '', $version, apply_filters( 'plausible_load_js_in_footer', false ) );
+ wp_enqueue_script(
+ 'plausible-analytics',
+ Helpers::get_js_url( true ),
+ '',
+ $version,
+ apply_filters( 'plausible_load_js_in_footer', false )
+ );
// Goal tracking inline script (Don't disable this as it is required by 404).
wp_add_inline_script(
@@ -77,7 +80,7 @@ public function maybe_register_assets() {
);
/**
- * Documentation.location.pathname is a variable. @see wp_json_encode() doesn't allow passing variable, only strings. This fixes that.
+ * document.location.pathname is a variable. @see wp_json_encode() doesn't allow passing variable, only strings. This fixes that.
*/
$data = str_replace( '"document.location.pathname"', 'document.location.pathname', $data );
@@ -95,14 +98,17 @@ public function maybe_register_assets() {
[
'props' => [
// convert queries to lowercase and remove trailing whitespace to ensure same terms are grouped together
- 'search_query' => strtolower(trim(get_search_query())),
+ 'search_query' => strtolower( trim( get_search_query() ) ),
'result_count' => $wp_query->found_posts,
],
]
);
$script = "plausible('WP Search Queries', $data );";
- wp_add_inline_script( 'plausible-analytics', "document.addEventListener('DOMContentLoaded', function() {\n$script\n});" );
+ wp_add_inline_script(
+ 'plausible-analytics',
+ "document.addEventListener('DOMContentLoaded', function() {\n$script\n});"
+ );
}
// This action allows you to add your own custom scripts!
@@ -111,7 +117,6 @@ public function maybe_register_assets() {
/**
* Create admin bar nodes.
- *
* @since 1.3.0
* @access public
*
diff --git a/src/Admin/Provisioning.php b/src/Admin/Provisioning.php
index 424d2ab8..ad8f1b4a 100644
--- a/src/Admin/Provisioning.php
+++ b/src/Admin/Provisioning.php
@@ -1,7 +1,6 @@
custom_event_goals = [
- '404' => __( '404', 'plausible-analytics' ),
- 'outbound-links' => __( 'Outbound Link: Click', 'plausible-analytics' ),
- 'file-downloads' => __( 'File Download', 'plausible-analytics' ),
- 'search' => __( 'WP Search Queries', 'plausible-analytics' ),
+ '404' => __( '404', 'plausible-analytics' ),
+ 'outbound-links' => __( 'Outbound Link: Click', 'plausible-analytics' ),
+ 'file-downloads' => __( 'File Download', 'plausible-analytics' ),
+ 'search' => __( 'WP Search Queries', 'plausible-analytics' ),
+ 'form-completions' => __( 'Form Completions', 'plausible-analytics' ),
];
$this->init();
@@ -79,10 +79,8 @@ public function __construct( $client = null ) {
/**
* Action & filter hooks.
- *
* @return void
* @throws ApiException
- *
* @codeCoverageIgnore
*/
private function init() {
@@ -210,11 +208,11 @@ private function create_goals( $goals ) {
* @param $settings
*
* @return void
- *
* @codeCoverageIgnore Because we don't want to test the API.
*/
public function maybe_create_woocommerce_funnel( $old_settings, $settings ) {
- if ( ! Helpers::is_enhanced_measurement_enabled( 'revenue', $settings[ 'enhanced_measurements' ] ) || ! Integrations::is_wc_active() ) {
+ if ( ! Helpers::is_enhanced_measurement_enabled( 'revenue', $settings[ 'enhanced_measurements' ] ) ||
+ ! Integrations::is_wc_active() ) {
return; // @codeCoverageIgnore
}
@@ -254,7 +252,6 @@ public function maybe_create_woocommerce_funnel( $old_settings, $settings ) {
* @param $steps
*
* @return void
- *
* @codeCoverageIgnore Because this method should be mocked in tests if needed.
*/
private function create_funnel( $name, $steps ) {
@@ -325,14 +322,13 @@ public function maybe_delete_goals( $old_settings, $settings ) {
}
/**
- * Delete all custom WooCommerce event goals if Revenue setting is disabled. The funnel is deleted when the minimum required no. of goals is no
- * longer met.
+ * Delete all custom WooCommerce event goals if Revenue setting is disabled. The funnel is deleted when the minimum
+ * required no. of goals is no longer met.
*
* @param $old_settings
* @param $settings
*
* @return void
- *
* @codeCoverageIgnore Because we don't want to test if the API is working.
*/
public function maybe_delete_woocommerce_goals( $old_settings, $settings ) {
@@ -361,14 +357,13 @@ public function maybe_delete_woocommerce_goals( $old_settings, $settings ) {
}
/**
- * Searches an array for the presence of $string within each element's value. Strips currencies using a regex, e.g. (USD), because these are
- * added to revenue goals by Plausible.
+ * Searches an array for the presence of $string within each element's value. Strips currencies using a regex, e.g.
+ * (USD), because these are added to revenue goals by Plausible.
*
* @param string $string
* @param array $haystack
*
* @return false|mixed
- *
* @codeCoverageIgnore Because it can't be unit tested.
*/
private function array_search_contains( $string, $haystack ) {
@@ -390,7 +385,6 @@ private function array_search_contains( $string, $haystack ) {
* @param array $settings
*
* @return void
- *
* @codeCoverageIgnore Because we don't want to test if the API is working.
*/
public function maybe_create_custom_properties( $old_settings, $settings ) {
@@ -398,7 +392,8 @@ public function maybe_create_custom_properties( $old_settings, $settings ) {
if ( ! Helpers::is_enhanced_measurement_enabled( 'pageview-props', $enhanced_measurements ) &&
! Helpers::is_enhanced_measurement_enabled( 'revenue', $enhanced_measurements ) &&
- ! Helpers::is_enhanced_measurement_enabled( 'search', $enhanced_measurements ) ) {
+ ! Helpers::is_enhanced_measurement_enabled( 'search', $enhanced_measurements ) &&
+ ! Helpers::is_enhanced_measurement_enabled( 'form-completions', $enhanced_measurements ) ) {
return; // @codeCoverageIgnore
}
@@ -417,7 +412,8 @@ public function maybe_create_custom_properties( $old_settings, $settings ) {
/**
* Create Custom Properties for WooCommerce integration.
*/
- if ( Helpers::is_enhanced_measurement_enabled( 'revenue', $enhanced_measurements ) && Integrations::is_wc_active() ) {
+ if ( Helpers::is_enhanced_measurement_enabled( 'revenue', $enhanced_measurements ) &&
+ Integrations::is_wc_active() ) {
foreach ( WooCommerce::CUSTOM_PROPERTIES as $property ) {
$properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => $property ] ] );
}
@@ -432,6 +428,10 @@ public function maybe_create_custom_properties( $old_settings, $settings ) {
}
}
+ if ( Helpers::is_enhanced_measurement_enabled( 'form-completions', $enhanced_measurements ) ) {
+ $properties[] = new Client\Model\CustomProp( [ 'custom_prop' => [ 'key' => 'form' ] ] );
+ }
+
if ( empty( $properties ) ) {
return; // @codeCoverageIgnore
}
diff --git a/src/Admin/Settings/Page.php b/src/Admin/Settings/Page.php
index d6b70632..742e570c 100644
--- a/src/Admin/Settings/Page.php
+++ b/src/Admin/Settings/Page.php
@@ -2,7 +2,6 @@
/**
* Plausible Analytics | Settings API.
- *
* @since 1.3.0
* @package WordPress
* @subpackage Plausible Analytics
@@ -72,7 +71,6 @@ class Page extends API {
/**
* Constructor.
- *
* @since 1.3.0
* @access public
* @return void
@@ -120,7 +118,8 @@ public function __construct() {
],
[
'label' => empty( $settings[ 'domain_name' ] ) || empty( $settings[ 'api_token' ] ) ?
- esc_html__( 'Connect', 'plausible-analytics' ) : esc_html__( 'Connected', 'plausible-analytics' ),
+ esc_html__( 'Connect', 'plausible-analytics' ) :
+ esc_html__( 'Connected', 'plausible-analytics' ),
'slug' => 'connect_plausible_analytics',
'type' => 'button',
'disabled' => empty( $settings[ 'domain_name' ] ) ||
@@ -140,42 +139,42 @@ public function __construct() {
'plausible-analytics'
),
'fields' => [
- '404' => [
+ '404' => [
'label' => esc_html__( '404 error pages', 'plausible-analytics' ),
'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-track-404-error-pages',
'slug' => 'enhanced_measurements',
'type' => 'checkbox',
'value' => '404',
],
- 'outbound-links' => [
+ 'outbound-links' => [
'label' => esc_html__( 'Outbound links', 'plausible-analytics' ),
'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-track-external-link-clicks',
'slug' => 'enhanced_measurements',
'type' => 'checkbox',
'value' => 'outbound-links',
],
- 'file-downloads' => [
+ 'file-downloads' => [
'label' => esc_html__( 'File downloads', 'plausible-analytics' ),
'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-track-file-downloads',
'slug' => 'enhanced_measurements',
'type' => 'checkbox',
'value' => 'file-downloads',
],
- 'search' => [
+ 'search' => [
'label' => esc_html__( 'Search queries', 'plausible-analytics' ),
'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-enable-site-search-tracking',
'slug' => 'enhanced_measurements',
'type' => 'checkbox',
'value' => 'search',
],
- 'tagged-events' => [
+ 'tagged-events' => [
'label' => esc_html__( 'Custom events', 'plausible-analytics' ),
'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-setup-custom-events-to-track-goal-conversions',
'slug' => 'enhanced_measurements',
'type' => 'checkbox',
'value' => 'tagged-events',
],
- 'revenue' => [
+ 'revenue' => [
'label' => esc_html__( 'Ecommerce revenue', 'plausible-analytics' ),
'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-track-ecommerce-revenue',
'slug' => 'enhanced_measurements',
@@ -183,21 +182,28 @@ public function __construct() {
'value' => 'revenue',
'disabled' => ! empty( $settings[ 'self_hosted_domain' ] ),
],
- 'pageview-props' => [
+ 'pageview-props' => [
'label' => esc_html__( 'Authors and categories', 'plausible-analytics' ),
'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-send-custom-properties',
'slug' => 'enhanced_measurements',
'type' => 'checkbox',
'value' => 'pageview-props',
],
- 'hash' => [
+ 'form-completions' => [
+ 'label' => esc_html__( 'Form completions', 'plausible-analytics' ),
+ 'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-track-form-completions',
+ 'slug' => 'enhanced_measurements',
+ 'type' => 'checkbox',
+ 'value' => 'form-completions',
+ ],
+ 'hash' => [
'label' => esc_html__( 'Hash-based routing', 'plausible-analytics' ),
'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-enable-hash-based-url-tracking',
'slug' => 'enhanced_measurements',
'type' => 'checkbox',
'value' => 'hash',
],
- 'compat' => [
+ 'compat' => [
'label' => esc_html__( 'IE compatibility', 'plausible-analytics' ),
'docs' => 'https://plausible.io/wordpress-analytics-plugin#how-to-track-visitors-who-use-internet-explorer',
'slug' => 'enhanced_measurements',
@@ -221,7 +227,8 @@ public function __construct() {
get_site_url( null, rest_get_url_prefix() ),
empty(
Helpers::get_settings()[ 'proxy_enabled' ]
- ) ? 'a random directory/file for storing the JS file' : 'a JS file, called ' . str_replace(
+ ) ? 'a random directory/file for storing the JS file' :
+ 'a JS file, called ' . str_replace(
ABSPATH,
'',
Helpers::get_proxy_resource( 'cache_dir' ) . Helpers::get_proxy_resource(
@@ -256,7 +263,8 @@ public function __construct() {
'slug' => 'enable_analytics_dashboard',
'type' => 'checkbox',
'value' => 'on',
- 'disabled' => empty( Helpers::get_settings()[ 'api_token' ] ) && empty( Helpers::get_settings()[ 'self_hosted_domain' ] ),
+ 'disabled' => empty( Helpers::get_settings()[ 'api_token' ] ) &&
+ empty( Helpers::get_settings()[ 'self_hosted_domain' ] ),
],
],
],
@@ -377,15 +385,13 @@ public function __construct() {
'slug' => 'self_hosted_shared_link',
'type' => 'group',
'desc' => sprintf(
- '- ' .
- __(
+ '
- ' . __(
'Create a secure and private shared link in your Plausible account.',
'plausible-analytics'
- ) .
- '
- ' .
- __( 'Paste the shared link in the text box to view your stats in your WordPress dashboard.', 'plausible-analytics' ) .
- '
' .
- '
',
+ ) . '' . __(
+ 'Paste the shared link in the text box to view your stats in your WordPress dashboard.',
+ 'plausible-analytics'
+ ) . '' . '',
esc_url( 'https://plausible.io/docs/embed-dashboard' )
),
'fields' => [
@@ -425,7 +431,6 @@ public function __construct() {
/**
* If proxy is enabled, or self-hosted domain has a value, display warning box.
- *
* @see self::proxy_warning()
*/
if ( Helpers::proxy_enabled() || ! empty( $settings[ 'self_hosted_domain' ] ) ) {
@@ -458,7 +463,6 @@ public function __construct() {
/**
* Init action hooks.
- *
* @return void
*/
private function init() {
@@ -509,7 +513,6 @@ private function build_user_roles_array( $slug, $disable_elements = [] ) {
/**
* Register Menu.
- *
* @since 1.0.0
* @access public
* @return void
@@ -579,7 +582,6 @@ public function register_menu() {
/**
* A little hack to add some classes to the core #wpcontent div.
- *
* @return void
*/
public function add_background_color() {
@@ -590,7 +592,6 @@ public function add_background_color() {
/**
* Statistics Page via Embed feature.
- *
* @since 1.2.0
* @access public
* @return void
@@ -643,7 +644,6 @@ public function render_analytics_dashboard() {
* When this option was saved to the database, underlying code would fail, throwing a CORS related error in browsers.
* Now, we explicitly check for the existence of this example "auth" key, and display a human-readable error message to
* those who haven't properly set it up.
- *
* @since v1.2.5
* For self-hosters the View Stats option doesn't need to be enabled, if a Shared Link is entered, we can assume they want to View Stats.
* For regular users, the shared link is provisioned by the API, so it shouldn't be empty.
@@ -707,7 +707,10 @@ public function render_analytics_dashboard() {
); ?>
click here to enable View Stats in WordPress.', 'plausible-analytics' ),
+ __(
+ 'Please click here to enable View Stats in WordPress.',
+ 'plausible-analytics'
+ ),
admin_url( 'options-general.php?page=plausible_analytics#is_shared_link' )
);
?>
diff --git a/src/Integrations.php b/src/Integrations.php
index 97d22bd3..d0458e6e 100644
--- a/src/Integrations.php
+++ b/src/Integrations.php
@@ -2,7 +2,6 @@
/**
* Plausible Analytics | Integrations
- *
* @since 2.1.0
* @package WordPress
* @subpackage Plausible Analytics
@@ -25,7 +24,6 @@ public function __construct() {
/**
* Run available integrations.
- *
* @return void
*/
private function init() {
@@ -38,11 +36,14 @@ private function init() {
if ( self::is_edd_active() ) {
// new Integrations\EDD();
}
+
+ if ( self::is_form_submit_active() ) {
+ new Integrations\FormSubmit();
+ }
}
/**
* Checks if WooCommerce is installed and activated.
- *
* @return bool
*/
public static function is_wc_active() {
@@ -51,10 +52,20 @@ public static function is_wc_active() {
/**
* Checks if Easy Digital Downloads is installed and activated.
- *
* @return bool
*/
public static function is_edd_active() {
return apply_filters( 'plausible_analytics_integrations_edd', function_exists( 'EDD' ) );
}
+
+ /**
+ * Check if Form Submissions option is enabled in Enhanced Measurements.
+ * @return mixed|null
+ */
+ public static function is_form_submit_active() {
+ return apply_filters(
+ 'plausible_analytics_integrations_form_submit',
+ Helpers::is_enhanced_measurement_enabled( 'form-submit' )
+ );
+ }
}
diff --git a/src/Integrations/FormSubmit.php b/src/Integrations/FormSubmit.php
new file mode 100644
index 00000000..af2e6796
--- /dev/null
+++ b/src/Integrations/FormSubmit.php
@@ -0,0 +1,91 @@
+init();
+ }
+
+ /**
+ * Init
+ * @return void
+ * @codeCoverageIgnore
+ */
+ private function init() {
+ /**
+ * Adds required JS and classes.
+ */
+ add_action( 'wp_enqueue_scripts', [ $this, 'add_js' ], 1 );
+ /**
+ * Contact Form 7 doesn't respect JS checkValidity() function, so this is a custom compatibility fix.
+ */
+ add_filter( 'wpcf7_validate', [ $this, 'maybe_track_submission' ], 10, 2 );
+ }
+
+ /**
+ * Enqueues the required JavaScript for form submissions integration.
+ * @return void
+ * @codeCoverageIgnore because there's nothing to test here.
+ */
+ public function add_js() {
+ if ( defined( 'WPCF7_VERSION' ) ) {
+ return;
+ }
+
+ wp_register_script(
+ 'plausible-form-submit-integration',
+ PLAUSIBLE_ANALYTICS_PLUGIN_URL . 'assets/dist/js/plausible-form-submit-integration.js',
+ [ 'plausible-analytics' ],
+ filemtime( PLAUSIBLE_ANALYTICS_PLUGIN_DIR . 'assets/dist/js/plausible-form-submit-integration.js' )
+ );
+
+ wp_localize_script(
+ 'plausible-form-submit-integration',
+ 'plausible_analytics_i18n',
+ [ 'form_completions' => __( 'Form Completions', 'plausible-analytics' ), ]
+ );
+
+ wp_enqueue_script( 'plausible-form-submit-integration' );
+ }
+
+ /**
+ * Tracks the form submission if CF7 says it's valid.
+ *
+ * @param \WPCF7_Validation $result Form submission result object containing validation results.
+ * @param array $tags Array of tags associated with the form fields.
+ *
+ * @return \WPCF7_Validation
+ * @codeCoverageIgnore because we can't test XHR requests here.
+ */
+ public function maybe_track_submission( $result, $tags ) {
+ $invalid_fields = $result->get_invalid_fields();
+
+ if ( empty( $invalid_fields ) ) {
+ $post = get_post( $_POST[ '_wpcf7_container_post' ] );
+ $uri = '/' . $post->post_name . '/';
+
+ $proxy = new Proxy( false );
+ $proxy->do_request(
+ __( 'Form Completions', 'plausible-analytics' ),
+ null,
+ null,
+ [ 'form' => $uri ]
+ );
+ }
+
+ return $result;
+ }
+}
diff --git a/webpack.config.js b/webpack.config.js
index 1949692e..585f156c 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -13,7 +13,8 @@ const config = {
mode,
entry: {
'plausible-admin': ['./assets/src/css/admin/main.css', './assets/src/js/admin/main.js'],
- 'plausible-woocommerce-integration': ['./assets/src/js/integrations/woocommerce.js']
+ 'plausible-woocommerce-integration': ['./assets/src/js/integrations/woocommerce.js'],
+ 'plausible-form-submit-integration': ['./assets/src/js/integrations/form-submit.js']
},
output: {
path: path.join(__dirname, './assets/dist/'),