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
9 changes: 4 additions & 5 deletions .distignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
.git
.github
.agent
.claude
.editorconfig
.env
.env.example
.gitignore
.distignore
.dockerignore
Dockerfile.test
docker-compose.yml
phpcs.xml.dist
phpunit.xml.dist
composer.json
composer.lock
vendor/
tests/
bin/
build/
demo/
node_modules/
package.sh
package.json
pnpm-lock.yaml
CONTRIBUTING.md
CONFIGURATION.md
.wordpress-org/
2 changes: 1 addition & 1 deletion betterlytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* Plugin URI: https://github.com/betterlytics/betterlytics-wordpress
* Description: Privacy-first analytics for WordPress. Automatically adds the Betterlytics tracking script and provides easy WordPress action hooks integration.
* Version: 1.0.0
* Requires at least: 5.0
* Requires at least: 6.3
* Requires PHP: 7.4
* Author: Betterlytics
* Author URI: https://betterlytics.io
Expand Down
4 changes: 2 additions & 2 deletions includes/class-betterlytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ private function define_admin_hooks() {
private function define_public_hooks() {
$plugin_public = new Betterlytics_Public( $this->get_plugin_name(), $this->get_version() );

$this->loader->add_action( 'wp_head', $plugin_public, 'inject_tracking_script', 1 );
$this->loader->add_action( 'wp_footer', $plugin_public, 'output_queued_events', 99 );
$this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'inject_tracking_script', 1 );
$this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'output_queued_events', 99 );
$this->loader->add_action( 'wp_enqueue_scripts', $plugin_public, 'enqueue_event_scripts' );

// Initialize custom hooks integration.
Expand Down
146 changes: 93 additions & 53 deletions public/class-betterlytics-public.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
* @since 1.0.0
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* Public-facing functionality.
*/
Expand Down Expand Up @@ -39,14 +43,15 @@ public function __construct( $plugin_name, $version ) {
}

/**
* Inject the Betterlytics tracking script.
* Enqueue the Betterlytics tracking script.
*
* @since 1.0.0
*/
public function inject_tracking_script() {
$options = Betterlytics_Options::get_options();

// Debug: Log tracking check.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$options = Betterlytics_Options::get_options();
$tracking_status = Betterlytics_Options::is_tracking_enabled() ? 'ENABLED' : 'DISABLED';
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug only.
error_log( '[Betterlytics] Tracking status: ' . $tracking_status );
Expand All @@ -58,41 +63,74 @@ public function inject_tracking_script() {
return;
}

$options = Betterlytics_Options::get_options();
$site_id = esc_attr( $options['site_id'] );
$server_url = esc_url( $options['server_url'] );
$script_url = esc_url( $options['script_url'] );
$track_outbound = esc_attr( $options['track_outbound']['mode'] );
$web_vitals = ! empty( $options['track_web_vitals'] ) ? 'true' : 'false';
$script_url = $options['script_url'];

// Debug: Output configuration as HTML comment.
// Validate script URL before enqueueing.
if ( empty( $script_url ) || ! filter_var( $script_url, FILTER_VALIDATE_URL ) ) {
return;
}

// Register and enqueue the external tracking script with async strategy (WP 6.3+).
wp_enqueue_script(
'betterlytics-tracker',
$script_url,
[],
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- External script URL; version managed by remote server.
null,
[
'strategy' => 'async',
'in_footer' => false,
]
);

// Add filter for data-* attributes.
add_filter( 'script_loader_tag', [ $this, 'add_tracker_script_attributes' ], 10, 2 );

// Build inline script for event queue buffer.
$inline_script = 'window.betterlytics = window.betterlytics || { event: function() { (window.betterlytics.q = window.betterlytics.q || []).push(arguments); } };';

// Add debug logging if WP_DEBUG is enabled.
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
echo "<!-- Betterlytics: Tracking ENABLED -->\n";
echo '<!-- Site ID: ' . esc_html( $site_id ) . " -->\n";
echo '<!-- Server URL: ' . esc_html( $server_url ) . " -->\n";
echo '<!-- Script URL: ' . esc_html( $script_url ) . " -->\n";
$site_id = esc_js( $options['site_id'] );
$server_url = esc_js( $options['server_url'] );
$script_url_js = esc_js( $script_url );
$track_outbound = esc_js( $options['track_outbound']['mode'] );

$inline_script .= "\nconsole.log('[Betterlytics] Script injected', { siteId: '{$site_id}', serverUrl: '{$server_url}', scriptUrl: '{$script_url_js}', trackOutbound: '{$track_outbound}' });";
}

// Output async event queue for reliable tracking before script loads.
?>
<script>
window.betterlytics = window.betterlytics || {
event: function() {
(window.betterlytics.q = window.betterlytics.q || []).push(arguments);
// Add event queue buffer as inline script BEFORE the main script.
wp_add_inline_script( 'betterlytics-tracker', $inline_script, 'before' );
}
};
<?php if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) : ?>
console.log('[Betterlytics] Script injected', {
siteId: '<?php echo esc_js( $site_id ); ?>',
serverUrl: '<?php echo esc_js( $server_url ); ?>',
scriptUrl: '<?php echo esc_js( $script_url ); ?>',
trackOutbound: '<?php echo esc_js( $track_outbound ); ?>'
});
<?php endif; ?>
</script>
<?php // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- External analytics script with data attributes cannot use wp_enqueue_script(). ?>
<script async src="<?php echo esc_url( $script_url ); ?>" data-site-id="<?php echo esc_attr( $site_id ); ?>" data-server-url="<?php echo esc_url( $server_url ); ?>" data-outbound-links="<?php echo esc_attr( $track_outbound ); ?>" data-web-vitals="<?php echo esc_attr( $web_vitals ); ?>"></script>
<?php

/**
* Add data attributes to the tracking script tag.
*
* @since 1.0.0
* @param string $tag The script tag HTML.
* @param string $handle The script handle.
* @return string Modified script tag.
*/
public function add_tracker_script_attributes( $tag, $handle ) {
if ( 'betterlytics-tracker' !== $handle ) {
return $tag;
}

$options = Betterlytics_Options::get_options();

// Build data attributes string.
$data_attrs = sprintf(
' data-site-id="%s" data-server-url="%s" data-outbound-links="%s" data-web-vitals="%s"',
esc_attr( $options['site_id'] ),
esc_url( $options['server_url'] ),
esc_attr( $options['track_outbound']['mode'] ),
! empty( $options['track_web_vitals'] ) ? 'true' : 'false'
);

// Insert data attributes before the closing >.
$tag = str_replace( '></script>', $data_attrs . '></script>', $tag );

return $tag;
}

/**
Expand All @@ -112,42 +150,44 @@ public function enqueue_event_scripts() {
return;
}

wp_enqueue_script(
'betterlytics-events',
BETTERLYTICS_PLUGIN_URL . 'public/js/betterlytics-events.js',
[],
$this->version,
true
);

wp_localize_script(
wp_enqueue_script(
'betterlytics-events',
'betterlyticsEvents',
[
'trackDownloads' => ! empty( $options['track_downloads']['enabled'] ),
'trackCustomHtmlAttr' => ! empty( $options['track_custom_html_attribute']['enabled'] ),
]
BETTERLYTICS_PLUGIN_URL . 'public/js/betterlytics-events.js',
[],
$this->version,
true
);

wp_localize_script(
'betterlytics-events',
'betterlyticsEvents',
[
'trackDownloads' => ! empty( $options['track_downloads']['enabled'] ),
'trackCustomHtmlAttr' => ! empty( $options['track_custom_html_attribute']['enabled'] ),
]
);
}

/**
* Output queued events as JavaScript.
* Output queued events as inline JavaScript.
*
* @since 1.0.0
*/
public function output_queued_events() {
global $betterlytics_queued_events;

if ( empty( $betterlytics_queued_events ) ) {
if ( empty( $betterlytics_queued_events ) || ! wp_script_is( 'betterlytics-tracker', 'enqueued' ) ) {
return;
}

echo "<script>\n";
$script_lines = [];
foreach ( $betterlytics_queued_events as $event ) {
$name = esc_js( $event['name'] );
$props = wp_json_encode( $event['properties'], JSON_HEX_TAG | JSON_HEX_AMP | JSON_FORCE_OBJECT );
echo "betterlytics.event('" . esc_js( $name ) . "', " . $props . ");\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$name = esc_js( $event['name'] );
$props = wp_json_encode( $event['properties'], JSON_HEX_TAG | JSON_HEX_AMP | JSON_FORCE_OBJECT );
$script_lines[] = "betterlytics.event('" . $name . "', " . $props . ');';
}
echo "</script>\n";

// Add queued events as inline script AFTER the main tracking script.
wp_add_inline_script( 'betterlytics-tracker', implode( "\n", $script_lines ), 'after' );
}
}
2 changes: 1 addition & 1 deletion readme.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
=== Betterlytics ===
Contributors: betterlytics
Tags: analytics, privacy, gdpr, cookieless, statistics
Requires at least: 5.0
Requires at least: 6.3
Tested up to: 6.9
Stable tag: 1.0.0
Requires PHP: 7.4
Expand Down
Loading