Completed
Push — master ( ff068d...c552d4 )
by
unknown
21:19 queued 10s
created

WC_Tracks_Client::maybe_set_identity_cookie()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
cc 9
nc 6
nop 0
dl 0
loc 29
ccs 0
cts 21
cp 0
crap 90
rs 8.0555
c 0
b 0
f 0
1
<?php
2
/**
3
 * Send Tracks events on behalf of a user.
4
 *
5
 * @package WooCommerce\Tracks
6
 */
7
8
defined( 'ABSPATH' ) || exit;
9
10
/**
11
 * WC_Tracks_Client class.
12
 */
13
class WC_Tracks_Client {
14
15
	/**
16
	 * Pixel URL.
17
	 */
18
	const PIXEL = 'https://pixel.wp.com/t.gif';
19
20
	/**
21
	 * Browser type.
22
	 */
23
	const BROWSER_TYPE = 'php-agent';
24
25
	/**
26
	 * User agent.
27
	 */
28
	const USER_AGENT_SLUG = 'tracks-client';
29
30
	/**
31
	 * Initialize tracks client class
32
	 *
33
	 * @return void
34
	 */
35
	public static function init() {
36
		// Use wp hook for setting the identity cookie to avoid headers already sent warnings.
37
		add_action( 'wp_loaded', array( __CLASS__, 'maybe_set_identity_cookie' ) );
38
	}
39
40
	/**
41
	 * Check if identiy cookie is set, if not set it.
42
	 *
43
	 * @return void
44
	 */
45
	public static function maybe_set_identity_cookie() {
46
		// Bail if cookie already set.
47
		if ( isset( $_COOKIE['tk_ai'] ) ) {
0 ignored issues
show
introduced by
Due to using Batcache, server side based client related logic will not work, use JS instead.
Loading history...
48
			return;
49
		}
50
51
		$user = wp_get_current_user();
52
53
		// We don't want to track user events during unit tests/CI runs.
54
		if ( $user instanceof WP_User && 'wptests_capabilities' === $user->cap_key ) {
0 ignored issues
show
Bug introduced by
The class WP_User does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
55
			return false;
56
		}
57
		$user_id = $user->ID;
58
		$anon_id = get_user_meta( $user_id, '_woocommerce_tracks_anon_id', true );
0 ignored issues
show
introduced by
get_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
59
60
		// If an id is still not found, create one and save it.
61
		if ( ! $anon_id ) {
62
			$anon_id = self::get_anon_id();
63
			update_user_meta( $user_id, '_woocommerce_tracks_anon_id', $anon_id );
0 ignored issues
show
introduced by
update_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
64
		}
65
66
		// Don't set cookie on API requests.
67
		if (
68
			! ( defined( 'REST_REQUEST' ) && REST_REQUEST ) &&
69
			! ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
70
		) {
71
			wc_setcookie( 'tk_ai', $anon_id );
72
		}
73
	}
74
75
	/**
76
	 * Record a Tracks event
77
	 *
78
	 * @param  array $event Array of event properties.
79
	 * @return bool|WP_Error         True on success, WP_Error on failure.
80
	 */
81
	public static function record_event( $event ) {
82
		if ( ! $event instanceof WC_Tracks_Event ) {
83
			$event = new WC_Tracks_Event( $event );
84
		}
85
86
		if ( is_wp_error( $event ) ) {
87
			return $event;
88
		}
89
90
		$pixel = $event->build_pixel_url( $event );
0 ignored issues
show
Unused Code introduced by
The call to WC_Tracks_Event::build_pixel_url() has too many arguments starting with $event.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
91
92
		if ( ! $pixel ) {
93
			return new WP_Error( 'invalid_pixel', 'cannot generate tracks pixel for given input', 400 );
94
		}
95
96
		return self::record_pixel( $pixel );
97
	}
98
99
	/**
100
	 * Synchronously request the pixel.
101
	 *
102
	 * @param string $pixel pixel url and query string.
103
	 * @return bool Always returns true.
104
	 */
105
	public static function record_pixel( $pixel ) {
106
		// Add the Request Timestamp and URL terminator just before the HTTP request.
107
		$pixel .= '&_rt=' . self::build_timestamp() . '&_=_';
108
109
		wp_safe_remote_get(
110
			$pixel,
111
			array(
112
				'blocking'    => false,
113
				'redirection' => 2,
114
				'httpversion' => '1.1',
115
				'timeout'     => 1,
116
			)
117
		);
118
119
		return true;
120
	}
121
122
	/**
123
	 * Create a timestap representing milliseconds since 1970-01-01
124
	 *
125
	 * @return string A string representing a timestamp.
126
	 */
127
	public static function build_timestamp() {
128
		$ts = round( microtime( true ) * 1000 );
129
130
		return number_format( $ts, 0, '', '' );
131
	}
132
133
	/**
134
	 * Get a user's identity to send to Tracks. If Jetpack exists, default to its implementation.
135
	 *
136
	 * @param int $user_id User id.
137
	 * @return array Identity properties.
138
	 */
139
	public static function get_identity( $user_id ) {
140
		$jetpack_lib = trailingslashit( WP_PLUGIN_DIR ) . 'jetpack/_inc/lib/tracks/client.php';
141
142
		if ( class_exists( 'Jetpack' ) && file_exists( $jetpack_lib ) ) {
143
			include_once $jetpack_lib;
144
145
			if ( function_exists( 'jetpack_tracks_get_identity' ) ) {
146
				return jetpack_tracks_get_identity( $user_id );
147
			}
148
		}
149
150
		// Start with a previously set cookie.
151
		$anon_id = isset( $_COOKIE['tk_ai'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['tk_ai'] ) ) : false;
0 ignored issues
show
introduced by
Due to using Batcache, server side based client related logic will not work, use JS instead.
Loading history...
152
153
		// If there is no cookie, apply a saved id.
154
		if ( ! $anon_id ) {
155
			$anon_id = get_user_meta( $user_id, '_woocommerce_tracks_anon_id', true );
0 ignored issues
show
introduced by
get_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
156
		}
157
158
		// If an id is still not found, create one and save it.
159
		if ( ! $anon_id ) {
160
			$anon_id = self::get_anon_id();
161
162
			update_user_meta( $user_id, '_woocommerce_tracks_anon_id', $anon_id );
0 ignored issues
show
introduced by
update_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
163
		}
164
165
		return array(
166
			'_ut' => 'anon',
167
			'_ui' => $anon_id,
168
		);
169
	}
170
171
	/**
172
	 * Grabs the user's anon id from cookies, or generates and sets a new one
173
	 *
174
	 * @return string An anon id for the user
175
	 */
176
	public static function get_anon_id() {
177
		static $anon_id = null;
178
179
		if ( ! isset( $anon_id ) ) {
180
181
			// Did the browser send us a cookie?
182
			if ( isset( $_COOKIE['tk_ai'] ) ) {
0 ignored issues
show
introduced by
Due to using Batcache, server side based client related logic will not work, use JS instead.
Loading history...
183
				$anon_id = sanitize_text_field( wp_unslash( $_COOKIE['tk_ai'] ) );
0 ignored issues
show
introduced by
Due to using Batcache, server side based client related logic will not work, use JS instead.
Loading history...
184
			} else {
185
186
				$binary = '';
187
188
				// Generate a new anonId and try to save it in the browser's cookies.
189
				// Note that base64-encoding an 18 character string generates a 24-character anon id.
190
				for ( $i = 0; $i < 18; ++$i ) {
191
					$binary .= chr( wp_rand( 0, 255 ) );
192
				}
193
194
				$anon_id = 'woo:' . base64_encode( $binary );
195
			}
196
		}
197
198
		return $anon_id;
199
	}
200
}
201
202
WC_Tracks_Client::init();
203