PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.14.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.14.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 / TrackingCode / TrackingCodeGenerator.php
matomo / classes / WpMatomo / TrackingCode Last commit date
TrackingCodeGenerator.php 4 years ago
TrackingCodeGenerator.php
391 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\TrackingCode;
11
12 use WP_Query;
13 use WpMatomo\Admin\CookieConsent;
14 use WpMatomo\Admin\TrackingSettings;
15 use WpMatomo\Logger;
16 use WpMatomo\Paths;
17 use WpMatomo\Settings;
18 use WpMatomo\Site;
19 // phpcs:ignore PHPCompatibility.UseDeclarations.NewUseConstFunction.Found
20 use function is_user_logged_in;
21
22 if ( ! defined( 'ABSPATH' ) ) {
23 exit; // if accessed directly
24 }
25
26 class TrackingCodeGenerator {
27 const TRACKPAGEVIEW = "_paq.push(['trackPageView']);";
28 const MTM_INIT = 'var _mtm = _mtm || [];';
29
30 /**
31 * @var Settings
32 */
33 private $settings;
34
35 /**
36 * @var Logger
37 */
38 private $logger;
39
40 /**
41 * @param Settings $settings
42 */
43 public function __construct( $settings ) {
44 $this->settings = $settings;
45 $this->logger = new Logger();
46 }
47
48 public function register_hooks() {
49 add_action( 'matomo_site_synced', [ $this, 'update_tracking_code' ], $prio = 10, $args = 0 );
50 add_action( 'matomo_tracking_settings_changed', [ $this, 'update_tracking_code' ], $prio = 10, $args = 0 );
51 }
52
53 public function update_tracking_code() {
54 if ( $this->settings->is_current_tracking_code()
55 && $this->settings->get_option( 'tracking_code' ) ) {
56 return false;
57 }
58
59 $track_mode = $this->settings->get_global_option( 'track_mode' );
60
61 if ( ! $this->settings->is_tracking_enabled()
62 || TrackingSettings::TRACK_MODE_MANUALLY === $track_mode ) {
63 return false;
64 }
65
66 $blod_id = get_current_blog_id();
67 $idsite = Site::get_matomo_site_id( $blod_id );
68
69 if ( ! $idsite ) {
70 $this->logger->log( 'Not found related idSite for blog ' . get_current_blog_id() );
71
72 return false;
73 }
74
75 if ( TrackingSettings::TRACK_MODE_DEFAULT === $track_mode ) {
76 $result = $this->prepare_tracking_code( $idsite );
77
78 if ( ! $this->settings->get_global_option( 'track_noscript' ) ) {
79 $result['noscript'] = '';
80 }
81 } elseif ( TrackingSettings::TRACK_MODE_TAGMANAGER === $track_mode && matomo_has_tag_manager() ) {
82 $result = $this->prepare_tagmanger_code( $this->settings, $this->logger );
83 } else {
84 $result = [
85 'script' => '<!-- Matomo: no supported track_mode selected -->',
86 'noscript' => '',
87 ];
88 }
89
90 if ( ! empty( $result['script'] ) ) {
91 $this->settings->set_option( 'tracking_code', $result['script'] );
92 $this->settings->set_option( 'noscript_code', $result['noscript'] );
93 }
94
95 $this->settings->set_option( Settings::OPTION_LAST_TRACKING_CODE_UPDATE, time() );
96 $this->settings->save();
97
98 return $result;
99 }
100
101 public function get_noscript_code() {
102 $this->update_tracking_code();
103
104 return $this->settings->get_noscript_tracking_code();
105 }
106
107 public function get_tracking_code() {
108 $this->update_tracking_code();
109
110 $tracking_code = $this->settings->get_js_tracking_code();
111
112 if ( $this->settings->track_user_id_enabled() ) {
113 $tracking_code = $this->apply_user_tracking( $tracking_code );
114 }
115 if ( $this->settings->track_404_enabled() && is_404() ) {
116 $tracking_code = $this->apply_404_changes( $tracking_code );
117 }
118 if ( $this->settings->track_search_enabled() ) {
119 $tracking_code = $this->apply_search_changes( $tracking_code );
120 }
121
122 return $tracking_code;
123 }
124
125 /**
126 * @param Settings $settings
127 * @param Logger $logger
128 *
129 * @return array
130 */
131 private function prepare_tagmanger_code( $settings, $logger ) {
132 $logger->log( 'Apply tag manager code changes:' );
133
134 $container_ids = $settings->get_global_option( 'tagmanger_container_ids' );
135
136 $code = '<!-- Matomo Tag Manager -->';
137
138 if ( ! empty( $container_ids ) && is_array( $container_ids ) ) {
139 $paths = new Paths();
140 $upload_url = $paths->get_upload_base_url();
141
142 foreach ( $container_ids as $container_id => $enabled ) {
143 if ( $enabled
144 && ctype_alnum( $container_id )
145 && strlen( $container_id ) <= 16 ) {
146 $container_url = $upload_url . '/container_' . rawurlencode( $container_id ) . '.js';
147
148 $data_cf_async = '';
149 if ( $settings->get_global_option( 'track_datacfasync' ) ) {
150 $data_cf_async = 'data-cfasync="false"';
151 }
152
153 if ( $settings->get_global_option( 'force_protocol' ) === 'https' ) {
154 $container_url = preg_replace( '(^http://)', 'https://', $container_url );
155 }
156
157 $code .= '
158 <script ' . $data_cf_async . '>
159 ' . self::MTM_INIT . '
160 _mtm.push({\'mtm.startTime\': (new Date().getTime()), \'event\': \'mtm.Start\'});
161 var d=document, g=d.createElement(\'script\'), s=d.getElementsByTagName(\'script\')[0];
162 g.type=\'text/javascript\'; g.async=true; g.src="' . $container_url . '"; s.parentNode.insertBefore(g,s);
163 </script>';
164 }
165 }
166 }
167
168 $code .= '<!-- End Matomo Tag Manager -->';
169
170 return [
171 'script' => $code,
172 'noscript' => '',
173 ];
174 }
175
176 public function get_tracker_endpoint() {
177 $paths = new Paths();
178
179 if ( $this->settings->get_global_option( 'track_api_endpoint' ) === 'restapi' ) {
180 $tracker_endpoint = $paths->get_tracker_api_rest_api_endpoint();
181 } else {
182 $tracker_endpoint = $paths->get_tracker_api_url_in_matomo_dir();
183 }
184
185 if ( $this->settings->get_global_option( 'force_protocol' ) === 'https' ) {
186 $tracker_endpoint = preg_replace( '(^http://)', 'https://', $tracker_endpoint );
187 } else {
188 $tracker_endpoint = preg_replace( '(^https?://)', '//', $tracker_endpoint );
189 }
190
191 return $tracker_endpoint;
192 }
193
194 public function get_js_endpoint() {
195 $paths = new Paths();
196 if ( $this->settings->get_global_option( 'track_js_endpoint' ) === 'restapi' ) {
197 $js_endpoint = $paths->get_js_tracker_rest_api_endpoint();
198 } elseif ( $this->settings->get_global_option( 'track_js_endpoint' ) === 'plugin' ) {
199 $js_endpoint = plugins_url( 'app/matomo.js', MATOMO_ANALYTICS_FILE );
200 } else {
201 $js_endpoint = $paths->get_js_tracker_url_in_matomo_dir();
202 }
203
204 if ( $this->settings->get_global_option( 'force_protocol' ) === 'https' ) {
205 $js_endpoint = preg_replace( '(^http://)', 'https://', $js_endpoint );
206 } else {
207 $js_endpoint = preg_replace( '(^https?://)', '//', $js_endpoint );
208 }
209
210 return $js_endpoint;
211 }
212
213 /**
214 * @param $idsite
215 *
216 * @return array
217 */
218 public function prepare_tracking_code( $idsite ) {
219 $log_level = is_admin() ? Logger::LEVEL_DEBUG : Logger::LEVEL_INFO;
220
221 $this->logger->log( 'Apply tracking code changes:', $log_level );
222
223 $tracker_endpoint = $this->get_tracker_endpoint();
224 $js_endpoint = $this->get_js_endpoint();
225
226 $options = [];
227
228 if ( $this->settings->get_global_option( 'set_download_extensions' ) ) {
229 $options[] = "_paq.push(['setDownloadExtensions', " . wp_json_encode( $this->settings->get_global_option( 'set_download_extensions' ) ) . ']);';
230 }
231 if ( $this->settings->get_global_option( 'add_download_extensions' ) ) {
232 $options[] = "_paq.push(['addDownloadExtensions', " . wp_json_encode( $this->settings->get_global_option( 'add_download_extensions' ) ) . ']);';
233 }
234 if ( $this->settings->get_global_option( 'set_download_classes' ) ) {
235 $options[] = "_paq.push(['setDownloadClasses', " . wp_json_encode( $this->settings->get_global_option( 'set_download_classes' ) ) . ']);';
236 }
237 if ( $this->settings->get_global_option( 'set_link_classes' ) ) {
238 $options[] = "_paq.push(['setLinkClasses', " . wp_json_encode( $this->settings->get_global_option( 'set_link_classes' ) ) . ']);';
239 }
240 if ( $this->settings->get_global_option( 'disable_cookies' ) ) {
241 $options[] = "_paq.push(['disableCookies']);";
242 }
243 if ( $this->settings->get_global_option( 'track_crossdomain_linking' ) ) {
244 $options[] = "_paq.push(['enableCrossDomainLinking']);";
245 }
246 if ( $this->settings->get_global_option( 'track_jserrors' ) ) {
247 $options[] = "_paq.push(['enableJSErrorTracking']);";
248 }
249
250 $cookie_domain = $this->settings->get_tracking_cookie_domain();
251 if ( ! empty( $cookie_domain ) ) {
252 $options[] = '_paq.push(["setCookieDomain", ' . wp_json_encode( $cookie_domain ) . ']);';
253 }
254
255 $track_across_alias = $this->settings->get_global_option( 'track_across_alias' );
256
257 if ( $track_across_alias ) {
258 // todo detect more hosts such as when using WPML etc
259 $hosts = [ wp_parse_url( home_url(), PHP_URL_HOST ) ];
260 $hosts = array_filter( $hosts );
261 $hosts = array_map(
262 function ( $host ) {
263 return '*.' . $host;
264 },
265 $hosts
266 );
267 if ( ! empty( $hosts ) ) {
268 $options[] = '_paq.push(["setDomains", ' . wp_json_encode( $hosts ) . ']);';
269 }
270 }
271 if ( $this->settings->get_global_option( 'force_post' ) ) {
272 $options[] = "_paq.push(['setRequestMethod', 'POST']);";
273 }
274
275 $cookie_consent = new CookieConsent();
276 $cookie_consent_option = $cookie_consent->get_tracking_consent_option( $this->settings->get_global_option( 'cookie_consent' ) );
277 // for unit test cases
278 if ( ! empty( $cookie_consent_option ) ) {
279 $options[] = $cookie_consent_option;
280 }
281
282 if ( $this->settings->get_global_option( 'limit_cookies' ) ) {
283 $options[] = "_paq.push(['setVisitorCookieTimeout', " . wp_json_encode( $this->settings->get_global_option( 'limit_cookies_visitor' ) ) . ']);';
284 $options[] = "_paq.push(['setSessionCookieTimeout', " . wp_json_encode( $this->settings->get_global_option( 'limit_cookies_session' ) ) . ']);';
285 $options[] = "_paq.push(['setReferralCookieTimeout', " . wp_json_encode( $this->settings->get_global_option( 'limit_cookies_referral' ) ) . ']);';
286 }
287 if ( $this->settings->get_global_option( 'track_content' ) === 'all' ) {
288 $options[] = "_paq.push(['trackAllContentImpressions']);";
289 } elseif ( $this->settings->get_global_option( 'track_content' ) === 'visible' ) {
290 $options[] = "_paq.push(['trackVisibleContentImpressions']);";
291 }
292 if ( (int) $this->settings->get_global_option( 'track_heartbeat' ) > 0 ) {
293 $options[] = "_paq.push(['enableHeartBeatTimer', " . intval( $this->settings->get_global_option( 'track_heartbeat' ) ) . ']);';
294 }
295
296 $data_cf_async = '';
297 $data_of_async_option = [];
298 if ( $this->settings->get_global_option( 'track_datacfasync' ) ) {
299 $data_cf_async = 'data-cfasync="false"';
300 $data_of_async_option['data-cfasync'] = 'false';
301 }
302
303 $script = "var _paq = window._paq = window._paq || [];\n";
304 $script .= implode( "\n", $options );
305 $script .= self::TRACKPAGEVIEW;
306 $script .= "_paq.push(['enableLinkTracking']);_paq.push(['alwaysUseSendBeacon']);";
307 $script .= "_paq.push(['setTrackerUrl', " . wp_json_encode( $tracker_endpoint ) . ']);';
308 $script .= "_paq.push(['setSiteId', '" . intval( $idsite ) . "']);";
309 $script .= "var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
310 g.type='text/javascript'; g.async=true; g.src=" . wp_json_encode( $js_endpoint ) . '; s.parentNode.insertBefore(g,s);';
311
312 if ( function_exists( 'wp_get_inline_script_tag' ) ) {
313 $script = wp_get_inline_script_tag(
314 $script,
315 $data_of_async_option
316 );
317 } else {
318 /*
319 * method wp_get_inline_script_tag add a line feed.
320 * to get the unit tests pass, we add a line feed when not using the method
321 */
322 $script = '<script ' . $data_cf_async . ">\n" . $script . "\n</script>\n";
323 }
324
325 $script = '<!-- Matomo -->' . $script . '<!-- End Matomo Code -->';
326
327 $no_script = '<noscript><p><img referrerpolicy="no-referrer-when-downgrade" src="' . esc_url( $tracker_endpoint ) . '?idsite=' . intval( $idsite ) . '&amp;rec=1" style="border:0;" alt="" /></p></noscript>';
328
329 $script = apply_filters( 'matomo_tracking_code_script', $script, $idsite );
330 $script = apply_filters( 'matomo_tracking_code_noscript', $script, $idsite );
331
332 $this->logger->log( 'Finished tracking code: ' . $script, $log_level );
333 $this->logger->log( 'Finished noscript code: ' . $no_script, $log_level );
334
335 return [
336 'script' => $script,
337 'noscript' => $no_script,
338 ];
339 }
340
341 private function apply_404_changes( $tracking_code ) {
342 $this->logger->log( 'Apply 404 tracking changes. Blog ID: ' . get_current_blog_id() );
343
344 $code = "_paq.push(['setDocumentTitle', '404/URL = '+String(document.location.pathname+document.location.search).replace(/\//g,'%2f') + '/From = ' + String(document.referrer).replace(/\//g,'%2f')]);";
345 $tracking_code = str_replace( self::TRACKPAGEVIEW, $code . self::TRACKPAGEVIEW, $tracking_code );
346 $tracking_code = str_replace( self::MTM_INIT, $code . self::MTM_INIT, $tracking_code );
347
348 return $tracking_code;
349 }
350
351 private function apply_search_changes( $tracking_code ) {
352 $this->logger->log( 'Apply search tracking changes. Blog ID: ' . get_current_blog_id() );
353 $obj_search = new WP_Query( 's=' . get_search_query() . '&showposts=-1' );
354 $int_result_count = $obj_search->post_count;
355
356 $code = "window._paq = window._paq || []; window._paq.push(['trackSiteSearch','" . get_search_query() . "', false, " . $int_result_count . "]);\n";
357 $tracking_code = str_replace( self::TRACKPAGEVIEW, $code . self::TRACKPAGEVIEW, $tracking_code );
358 $tracking_code = str_replace( self::MTM_INIT, $code . self::MTM_INIT, $tracking_code );
359
360 return $tracking_code;
361 }
362
363 private function apply_user_tracking( $tracking_code ) {
364 $user_id_to_track = null;
365 if ( is_user_logged_in() ) {
366 // Get the User ID Admin option, and the current user's data
367 $uid_from = $this->settings->get_global_option( 'track_user_id' );
368 $current_user = wp_get_current_user(); // current user
369 // Get the user ID based on the admin setting
370 if ( 'uid' === $uid_from ) {
371 $user_id_to_track = $current_user->ID;
372 } elseif ( 'email' === $uid_from ) {
373 $user_id_to_track = $current_user->user_email;
374 } elseif ( 'username' === $uid_from ) {
375 $user_id_to_track = $current_user->user_login;
376 } elseif ( 'displayname' === $uid_from ) {
377 $user_id_to_track = $current_user->display_name;
378 }
379 }
380 $user_id_to_track = apply_filters( 'matomo_tracking_user_id', $user_id_to_track );
381 // Check we got a User ID to track, and track it
382 if ( isset( $user_id_to_track ) && ! empty( $user_id_to_track ) ) {
383 $code = "window._paq = window._paq || []; window._paq.push(['setUserId', '" . esc_js( $user_id_to_track ) . "']);\n";
384 $tracking_code = str_replace( self::TRACKPAGEVIEW, $code . self::TRACKPAGEVIEW, $tracking_code );
385 $tracking_code = str_replace( self::MTM_INIT, $code . self::MTM_INIT, $tracking_code );
386 }
387
388 return $tracking_code;
389 }
390 }
391