PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 All 507 releases
← All changes | jetpack_vendor/automattic/jetpack-connection/src/class-tracking.php +332 -0 16.2-beta → 16.3-beta View file →
@@ -1,0 +1,332 @@
1 +<?php
2 +/**
3 + * Nosara Tracks for Jetpack
4 + *
5 + * @package automattic/jetpack-connection
6 + */
7 +
8 +namespace Automattic\Jetpack;
9 +
10 +use Automattic\Jetpack\IP\Utils as IP_Utils;
11 +
12 +/**
13 + * The Tracking class, used to record events in wpcom
14 + */
15 +class Tracking {
16 + /**
17 + * The assets version.
18 + *
19 + * @since 1.13.1
20 + * @deprecated since 1.40.1
21 + *
22 + * @var string Assets version.
23 + */
24 + const ASSETS_VERSION = '1.0.0';
25 +
26 + /**
27 + * Slug of the product that we are tracking.
28 + *
29 + * @var string
30 + */
31 + private $product_name;
32 +
33 + /**
34 + * Connection manager object.
35 + *
36 + * @var Object
37 + */
38 + private $connection;
39 +
40 + /**
41 + * Creates the Tracking object.
42 + *
43 + * @param string $product_name the slug of the product that we are tracking.
44 + * @param \Automattic\Jetpack\Connection\Manager $connection the connection manager object.
45 + */
46 + public function __construct( $product_name = 'jetpack', $connection = null ) {
47 + $this->product_name = $product_name;
48 + $this->connection = $connection;
49 + if ( $this->connection === null ) {
50 + // TODO We should always pass a Connection.
51 + $this->connection = new Connection\Manager();
52 + }
53 +
54 + if ( ! did_action( 'jetpack_set_tracks_ajax_hook' ) ) {
55 + add_action( 'wp_ajax_jetpack_tracks', array( $this, 'ajax_tracks' ) );
56 +
57 + /**
58 + * Fires when the Tracking::ajax_tracks() callback has been hooked to the
59 + * wp_ajax_jetpack_tracks action. This action is used to ensure that
60 + * the callback is hooked only once.
61 + *
62 + * @since 1.13.11
63 + */
64 + do_action( 'jetpack_set_tracks_ajax_hook' );
65 + }
66 + }
67 +
68 + /**
69 + * Universal method for for all tracking events triggered via the JavaScript client.
70 + *
71 + * @access public
72 + */
73 + public function ajax_tracks() {
74 + // Check for nonce.
75 + if (
76 + empty( $_REQUEST['tracksNonce'] )
77 + || ! wp_verify_nonce( $_REQUEST['tracksNonce'], 'jp-tracks-ajax-nonce' ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP core doesn't pre-sanitize nonces either.
78 + ) {
79 + wp_send_json_error(
80 + __( 'You aren’t authorized to do that.', 'jetpack-connection' ),
81 + 403,
82 + JSON_UNESCAPED_SLASHES
83 + );
84 + }
85 +
86 + if ( ! isset( $_REQUEST['tracksEventName'] ) || ! isset( $_REQUEST['tracksEventType'] ) ) {
87 + wp_send_json_error(
88 + __( 'No valid event name or type.', 'jetpack-connection' ),
89 + 403,
90 + JSON_UNESCAPED_SLASHES
91 + );
92 + exit; // @phan-suppress-current-line PhanPluginUnreachableCode -- @todo Remove when WP 7.1 is the minimum version.
93 + }
94 +
95 + $tracks_data = array();
96 + if ( 'click' === $_REQUEST['tracksEventType'] && isset( $_REQUEST['tracksEventProp'] ) ) {
97 + if ( is_array( $_REQUEST['tracksEventProp'] ) ) {
98 + // map_deep() rather than array_map(): the request is client-supplied and may nest,
99 + // and sanitize_text_field() returns an empty string when handed an array.
100 + $tracks_data = map_deep( wp_unslash( $_REQUEST['tracksEventProp'] ), 'sanitize_text_field' );
101 + } else {
102 + $tracks_data = array( 'clicked' => sanitize_text_field( wp_unslash( $_REQUEST['tracksEventProp'] ) ) );
103 + }
104 + }
105 +
106 + // Tracks only accepts lowercase alphanumerics and underscores in an event name
107 + // (see Jetpack_Tracks_Event::EVENT_NAME_REGEX), which is what sanitize_key() permits.
108 + $this->record_user_event( sanitize_key( wp_unslash( $_REQUEST['tracksEventName'] ) ), $tracks_data, null, false );
109 +
110 + wp_send_json_success( null, null, JSON_UNESCAPED_SLASHES );
111 + }
112 +
113 + /**
114 + * Register script necessary for tracking.
115 + *
116 + * @param boolean $enqueue Also enqueue? defaults to false.
117 + */
118 + public static function register_tracks_functions_scripts( $enqueue = false ) {
119 +
120 + // Register jp-tracks as it is a dependency.
121 + wp_register_script(
122 + 'jp-tracks',
123 + '//stats.wp.com/w.js',
124 + array(),
125 + gmdate( 'YW' ),
126 + true
127 + );
128 +
129 + Assets::register_script(
130 + 'jp-tracks-functions',
131 + '../dist/tracks-callables.js',
132 + __FILE__,
133 + array(
134 + 'dependencies' => array( 'jp-tracks' ),
135 + 'enqueue' => $enqueue,
136 + 'in_footer' => true,
137 + )
138 + );
139 + }
140 +
141 + /**
142 + * Enqueue script necessary for tracking.
143 + */
144 + public function enqueue_tracks_scripts() {
145 + Assets::register_script(
146 + 'jptracks',
147 + '../dist/tracks-ajax.js',
148 + __FILE__,
149 + array(
150 + 'dependencies' => array( 'jquery' ),
151 + 'enqueue' => true,
152 + 'in_footer' => true,
153 + )
154 + );
155 +
156 + wp_localize_script(
157 + 'jptracks',
158 + 'jpTracksAJAX',
159 + array(
160 + 'ajaxurl' => admin_url( 'admin-ajax.php' ),
161 + 'jpTracksAJAX_nonce' => wp_create_nonce( 'jp-tracks-ajax-nonce' ),
162 + )
163 + );
164 + }
165 +
166 + /**
167 + * Send an event in Tracks.
168 + *
169 + * @param string $event_type Type of the event.
170 + * @param array $data Data to send with the event.
171 + * @param mixed $user Username, user_id, or WP_User object.
172 + * @param bool $use_product_prefix Whether to use the object's product name as a prefix to the event type. If
173 + * set to false, the prefix will be 'jetpack_'.
174 + */
175 + public function record_user_event( $event_type, $data = array(), $user = null, $use_product_prefix = true ) {
176 + if ( ! $user ) {
177 + $user = wp_get_current_user();
178 + }
179 + $site_url = get_option( 'siteurl' );
180 +
181 + $data['_via_ua'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
182 + $data['_via_ip'] = isset( $_SERVER['REMOTE_ADDR'] ) ? (string) IP_Utils::clean_ip( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- clean_ip() validates the address.
183 + $data['_lg'] = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) : '';
184 + $data['blog_url'] = $site_url;
185 + $data['blog_id'] = \Jetpack_Options::get_option( 'id' );
186 +
187 + // Top level events should not be namespaced.
188 + if ( '_aliasUser' !== $event_type ) {
189 + $prefix = $use_product_prefix ? $this->product_name : 'jetpack';
190 + $event_type = $prefix . '_' . $event_type;
191 + }
192 +
193 + $data['jetpack_version'] = defined( 'JETPACK__VERSION' ) ? JETPACK__VERSION : '0';
194 +
195 + return $this->tracks_record_event( $user, $event_type, $data );
196 + }
197 +
198 + /**
199 + * Record an event in Tracks - this is the preferred way to record events from PHP.
200 + *
201 + * @param mixed $user username, user_id, or WP_User object.
202 + * @param string $event_name The name of the event.
203 + * @param array $properties Custom properties to send with the event.
204 + * @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred.
205 + *
206 + * @return bool true for success | \WP_Error if the event pixel could not be fired
207 + */
208 + public function tracks_record_event( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
209 +
210 + // We don't want to track user events during unit tests/CI runs.
211 + if ( $user instanceof \WP_User && 'wptests_capabilities' === $user->cap_key ) {
212 + return false;
213 + }
214 + $terms_of_service = new Terms_Of_Service();
215 + $status = new Status();
216 + // Don't track users who have not agreed to our TOS.
217 + if ( ! $this->should_enable_tracking( $terms_of_service, $status ) ) {
218 + return false;
219 + }
220 +
221 + $event_obj = $this->tracks_build_event_obj( $user, $event_name, $properties, $event_timestamp_millis );
222 +
223 + if ( is_wp_error( $event_obj->error ) ) {
224 + return $event_obj->error;
225 + }
226 +
227 + return $event_obj->record();
228 + }
229 +
230 + /**
231 + * Determines whether tracking should be enabled.
232 + *
233 + * @param \Automattic\Jetpack\Terms_Of_Service $terms_of_service A Terms_Of_Service object.
234 + * @param \Automattic\Jetpack\Status $status A Status object.
235 + *
236 + * @return boolean True if tracking should be enabled, else false.
237 + */
238 + public function should_enable_tracking( $terms_of_service, $status ) {
239 + if ( $status->is_offline_mode() ) {
240 + return false;
241 + }
242 +
243 + return $terms_of_service->has_agreed() || $this->connection->is_user_connected();
244 + }
245 +
246 + /**
247 + * Procedurally build a Tracks Event Object.
248 + * NOTE: Use this only when the simpler Automattic\Jetpack\Tracking->jetpack_tracks_record_event() function won't work for you.
249 + *
250 + * @param \WP_User $user WP_User object.
251 + * @param string $event_name The name of the event.
252 + * @param array $properties Custom properties to send with the event.
253 + * @param int $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred.
254 + *
255 + * @return \Jetpack_Tracks_Event|\WP_Error
256 + */
257 + private function tracks_build_event_obj( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
258 + $identity = $this->tracks_get_identity( $user->ID );
259 +
260 + $properties['user_lang'] = $user->get( 'WPLANG' );
261 +
262 + $blog_details = array(
263 + 'blog_lang' => $properties['blog_lang'] ?? get_bloginfo( 'language' ),
264 + 'blog_id' => \Jetpack_Options::get_option( 'id' ),
265 + );
266 +
267 + $timestamp = ( false !== $event_timestamp_millis ) ? $event_timestamp_millis : round( microtime( true ) * 1000 );
268 + $timestamp_string = is_string( $timestamp ) ? $timestamp : number_format( $timestamp, 0, '', '' );
269 +
270 + return new \Jetpack_Tracks_Event(
271 + array_merge(
272 + $blog_details,
273 + (array) $properties,
274 + $identity,
275 + array(
276 + '_en' => $event_name,
277 + '_ts' => $timestamp_string,
278 + )
279 + )
280 + );
281 + }
282 +
283 + /**
284 + * Get the identity to send to tracks.
285 + *
286 + * @param int $user_id The user id of the local user.
287 + *
288 + * @return array $identity
289 + */
290 + public function tracks_get_identity( $user_id ) {
291 +
292 + // Meta is set, and user is still connected. Use WPCOM ID.
293 + $wpcom_id = get_user_meta( $user_id, 'jetpack_tracks_wpcom_id', true );
294 + if ( $wpcom_id && is_string( $wpcom_id ) && $this->connection->is_user_connected( $user_id ) ) {
295 + return array(
296 + '_ut' => 'wpcom:user_id',
297 + '_ui' => $wpcom_id,
298 + );
299 + }
300 +
301 + // User is connected, but no meta is set yet. Use WPCOM ID and set meta.
302 + if ( $this->connection->is_user_connected( $user_id ) ) {
303 + $wpcom_user_data = $this->connection->get_connected_user_data( $user_id );
304 + $wpcom_id = $wpcom_user_data['ID'] ?? null;
305 +
306 + if ( is_string( $wpcom_id ) ) {
307 + update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_id );
308 +
309 + return array(
310 + '_ut' => 'wpcom:user_id',
311 + '_ui' => $wpcom_id,
312 + );
313 + }
314 + }
315 +
316 + // User isn't linked at all. Fall back to anonymous ID.
317 + $anon_id = get_user_meta( $user_id, 'jetpack_tracks_anon_id', true );
318 + if ( ! $anon_id ) {
319 + $anon_id = \Jetpack_Tracks_Client::get_anon_id();
320 + add_user_meta( $user_id, 'jetpack_tracks_anon_id', $anon_id, false );
321 + }
322 +
323 + if ( ! isset( $_COOKIE['tk_ai'] ) && ! headers_sent() ) {
324 + setcookie( 'tk_ai', $anon_id, 0, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), false ); // phpcs:ignore Jetpack.Functions.SetCookie -- This is a random string and should be fine.
325 + }
326 +
327 + return array(
328 + '_ut' => 'anon',
329 + '_ui' => $anon_id,
330 + );
331 + }
332 +}