PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / classes / WpMatomo / AjaxTracker.php
matomo / classes / WpMatomo Last commit date
Admin 2 months ago Commands 2 years ago Db 1 year ago Ecommerce 3 months ago Report 2 months ago Site 2 months ago TrackingCode 4 months ago Updater 4 years ago User 2 months ago Workarounds 2 years ago WpStatistics 5 months ago views 4 years ago AIBotTracking.php 4 months ago API.php 2 months ago Access.php 4 years ago AjaxTracker.php 4 months ago Annotations.php 2 months ago Bootstrap.php 10 months ago Capabilities.php 2 months ago Compatibility.php 2 months ago Email.php 2 years ago ErrorNotice.php 2 months ago Feature.php 2 months ago Installer.php 5 months ago Logger.php 1 year ago OptOut.php 2 months ago Paths.php 5 months ago PluginActionLinks.php 2 months ago PluginAdminOverrides.php 2 months ago PluginInit.php 2 months ago PrivacyBadge.php 4 years ago RedirectOnActivation.php 2 months ago Referral.php 2 months ago Roles.php 2 months ago ScheduledTasks.php 2 months ago Settings.php 4 months ago Site.php 3 years ago TrackingCode.php 2 months ago Uninstaller.php 5 months ago Updater.php 10 months ago User.php 4 years ago
AjaxTracker.php
233 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 * @package matomo
8 */
9
10 namespace WpMatomo;
11
12 use WpMatomo\Ecommerce\ServerSideVisitorId;
13 use WpMatomo\TrackingCode\GeneratorOptions;
14 use WpMatomo\TrackingCode\TrackingCodeGenerator;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit; // if accessed directly
18 }
19
20 if ( ! class_exists( '\PiwikTracker' ) ) {
21 include_once plugin_dir_path( MATOMO_ANALYTICS_FILE ) . 'app/vendor/matomo/matomo-php-tracker/MatomoTracker.php';
22 }
23
24 class AjaxTracker extends \MatomoTracker {
25
26 const IP_ADDRESS_FORWARDING_HEADER = 'X-Matomo-Forwarded-Ip';
27 const IP_ADDRESS_FORWARDING_HEADER_SERVER_NAME = 'HTTP_X_MATOMO_FORWARDED_IP';
28 const IP_ADDRESS_FORWARDING_NONCE_NAME = 'matomo-track-forward-ip';
29
30 private $has_cookie = false;
31 private $logger;
32
33 public function __construct( Settings $settings ) {
34 $this->logger = new Logger();
35
36 $site = new Site();
37 $idsite = $site->get_current_matomo_site_id();
38
39 if ( ! $idsite ) {
40 return;
41 }
42
43 $paths = new Paths();
44
45 if ( $settings->get_global_option( 'track_api_endpoint' ) === 'restapi' ) {
46 $api_endpoint = $paths->get_tracker_api_rest_api_endpoint();
47 } else {
48 $api_endpoint = $paths->get_tracker_api_url_in_matomo_dir();
49 }
50
51 parent::__construct( $idsite, $api_endpoint );
52
53 $this->ip = false;
54
55 // we are using the tracker only in ajax so the referer contains the actual url
56 $this->urlReferrer = false;
57 $this->pageUrl = ! empty( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : false;
58
59 if ( ! $settings->get_global_option( 'disable_cookies' ) ) {
60 $tracking_code_generator = new TrackingCodeGenerator( $settings, new GeneratorOptions( $settings ) );
61 $cookie_domain = $tracking_code_generator->get_tracking_cookie_domain();
62 $this->enableCookies( $cookie_domain );
63 } else {
64 $this->disableCookieSupport();
65 }
66
67 if ( $this->loadVisitorIdCookie() ) {
68 if ( ! empty( $this->cookieVisitorId ) ) {
69 $this->has_cookie = true;
70 $this->set_visitor_id_safe( $this->cookieVisitorId );
71 }
72 } else if ( function_exists( 'WC' ) && isset( WC()->session ) ) {
73 $visitor_id = WC()->session->get( ServerSideVisitorId::VISITOR_ID_SESSION_VAR_NAME );
74 if ( ! empty( $visitor_id ) ) {
75 $this->hasCookie = true; // do not set cookies for this visitor, since it would have no effect anyway
76 $this->set_visitor_id_safe( $visitor_id );
77 }
78 }
79 }
80
81 public function set_visitor_id_safe( $visitor_id ) {
82 try {
83 $this->setVisitorId( $visitor_id );
84 } catch ( \Exception $ex ) {
85 // do not fatal if the visitor ID is invalid for some reason
86 if ( ! $this->is_invalid_visitor_id_error( $ex ) ) {
87 throw $ex;
88 }
89 }
90 }
91
92 public function is_success_response( $response ) {
93 $gif_response = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
94 return $response === base64_decode( $gif_response );
95 }
96
97 protected function setCookie( $cookieName, $cookieValue, $cookieTTL ) {
98 if ( ! $this->has_cookie ) {
99 // we only set / overwrite cookies if it is a visitor that has eg no JS enabled or ad blocker enabled etc.
100 // this way we will track all cart updates and orders into the same visitor on following requests.
101 // If we recognized the visitor before via cookie we want in our case to make sure to not overwrite
102 // any cookie
103 parent::setCookie( $cookieName, $cookieValue, $cookieTTL );
104 }
105 }
106
107 protected function sendRequest( $url, $method = 'GET', $data = null, $force = false ) {
108 if ( ! $this->idSite ) {
109 $this->logger->log('ecommerce tracking could not find idSite, cannot send request');
110 return ''; // not installed or synced yet
111 }
112
113 if ( $this->is_prerender() ) {
114 // do not track if for some reason we are prerendering
115 return '';
116 }
117
118 $args = [
119 'method' => $method,
120 'headers' => [
121 'User-Agent' => $this->userAgent,
122 ],
123 'blocking' => false,
124 ];
125 if ( ! empty( $data ) ) {
126 $args['body'] = $data;
127 }
128
129 if ( ! empty( $this->ip ) ) {
130 $args['headers'][self::IP_ADDRESS_FORWARDING_HEADER] = $this->ip;
131
132 $ip_nonce = wp_create_nonce( self::IP_ADDRESS_FORWARDING_NONCE_NAME );
133 $url = $url . '&ip_nonce=' . rawurlencode( $ip_nonce );
134 }
135
136 // todo at some point we could think about including `matomo.php` here instead of doing an http request
137 // however we would need to make sure to set a custom tracker response handler to
138 // 1) Not send any response no matter what happens
139 // 2) Never exit at any point
140
141 $url = $url . '&bots=1';
142
143 try {
144 $response = $this->wp_remote_request( $url, $args );
145 } catch ( \Exception $ex ) {
146 $this->logger->log_exception( 'ajax_tracker', $ex );
147 return '';
148 }
149
150 if ( is_wp_error( $response ) ) {
151 $this->logger->log_exception( 'ajax_tracker', new \Exception( $response->get_error_message() ) );
152 return '';
153 }
154
155 return $response['body'];
156 }
157
158 private function is_invalid_visitor_id_error( \Exception $ex ) {
159 return strpos( $ex->getMessage(), 'setVisitorId() expects' ) === 0;
160 }
161
162 /**
163 * See https://developer.chrome.com/docs/web-platform/prerender-pages
164 * @return bool
165 */
166 private function is_prerender() {
167 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
168 $purpose = strtolower( isset( $_SERVER['HTTP_SEC_PURPOSE'] ) ? wp_unslash( $_SERVER['HTTP_SEC_PURPOSE'] ) : '' );
169 return strpos( $purpose, 'prefetch' ) !== false
170 || strpos( $purpose, 'prerender' ) !== false;
171 }
172
173 /**
174 * for tests to override
175 * @param string $url
176 * @param array $args
177 * @return array|\WP_Error
178 */
179 protected function wp_remote_request( $url, $args ) {
180 return wp_remote_request( $url, $args );
181 }
182
183 /**
184 * In Matomo for WordPress we want to rely entirely on JavaScript tracker
185 * for creating cookies.
186 *
187 * @return void
188 */
189 protected function setFirstPartyCookies() {
190 // disabled
191 }
192
193 /**
194 * Enables the handling of the X-Matomo-Forwarded-Ip, if it should be for
195 * the current request.
196 *
197 * Matomo for WordPress uses a custom header to correctly track client IP addresses
198 * when doing server side tracking. We choose to use this approach instead
199 * of the `cip` tracking parameter, since that parameter requires the use of
200 * a token_auth, and creating and storing a token_auth is more complexity than
201 * we want.
202 *
203 * Instead, we use a WP nonce to check whether the current request is authorized
204 * to handle the X-Matomo-Forwarded-Ip header.
205 *
206 * If it should be handled, the X-Matomo-Forwarded-Ip header is added to Matomo's
207 * list of proxy HTTP headers to look at for IP addresses.
208 */
209 public static function add_ip_forward_proxy_header_to_config(\Piwik\Config $config ) {
210 if ( empty( $_REQUEST['ip_nonce'] ) ) {
211 return;
212 }
213
214 $ip_nonce = $_REQUEST['ip_nonce'];
215 if ( ! wp_verify_nonce( $ip_nonce, self::IP_ADDRESS_FORWARDING_NONCE_NAME ) ) {
216 return;
217 }
218
219 $general = $config->General;
220 $proxy_client_headers = isset( $general['proxy_client_headers'] ) ? $general['proxy_client_headers'] : [];
221 if ( ! is_array( $proxy_client_headers ) ) {
222 $proxy_client_headers = [];
223 }
224 $proxy_client_headers[] = self::IP_ADDRESS_FORWARDING_HEADER_SERVER_NAME;
225
226 $config->General['proxy_client_headers'] = $proxy_client_headers;
227 }
228
229 public static function getCurrentUrl(): string {
230 return parent::getCurrentUrl();
231 }
232 }
233