Completed
Push — add/debug-mocker ( 986c7c...aaec4a )
by
unknown
08:37
created

Tracking::record_user_event()   B

Complexity

Conditions 7
Paths 64

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 64
nop 3
dl 0
loc 21
rs 8.6506
c 0
b 0
f 0
1
<?php
2
/**
3
 * Nosara Tracks for Jetpack
4
 *
5
 * @package automattic/jetpack-tracking
6
 */
7
8
namespace Automattic\Jetpack;
9
10
use Automattic\Jetpack\Terms_Of_Service;
11
12
/**
13
 * The Tracking class, used to record events in wpcom
14
 */
15
class Tracking {
16
	/**
17
	 * Slug of the product that we are tracking.
18
	 *
19
	 * @var string
20
	 */
21
	private $product_name;
22
23
	/**
24
	 * Connection manager object.
25
	 *
26
	 * @var Object
27
	 */
28
	private $connection;
29
30
	/**
31
	 * Creates the Tracking object.
32
	 *
33
	 * @param String                                $product_name the slug of the product that we are tracking.
34
	 * @param Automattic\Jetpack\Connection\Manager $connection   the connection manager object.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $connection not be Automattic\Jetpack\Connection\Manager|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
35
	 */
36
	public function __construct( $product_name = 'jetpack', $connection = null ) {
37
		$this->product_name = $product_name;
38
		$this->connection   = $connection;
39
		if ( is_null( $this->connection ) ) {
40
			// TODO We should always pass a Connection.
41
			$this->connection = new Connection\Manager();
42
		}
43
	}
44
45
	/**
46
	 * Enqueue script necessary for tracking.
47
	 */
48 View Code Duplication
	public function enqueue_tracks_scripts() {
49
		wp_enqueue_script( 'jptracks', plugins_url( '_inc/lib/tracks/tracks-ajax.js', JETPACK__PLUGIN_FILE ), array(), JETPACK__VERSION, true );
50
		wp_localize_script(
51
			'jptracks',
52
			'jpTracksAJAX',
53
			array(
54
				'ajaxurl'            => admin_url( 'admin-ajax.php' ),
55
				'jpTracksAJAX_nonce' => wp_create_nonce( 'jp-tracks-ajax-nonce' ),
56
			)
57
		);
58
	}
59
60
	/**
61
	 * Send an event in Tracks.
62
	 *
63
	 * @param string $event_type Type of the event.
64
	 * @param array  $data       Data to send with the event.
65
	 * @param mixed  $user       username, user_id, or WP_user object.
66
	 */
67
	public function record_user_event( $event_type, $data = array(), $user = null ) {
68
		if ( ! $user ) {
69
			$user = wp_get_current_user();
70
		}
71
		$site_url = get_option( 'siteurl' );
72
73
		$data['_via_ua']  = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '';
74
		$data['_via_ip']  = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
75
		$data['_lg']      = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
76
		$data['blog_url'] = $site_url;
77
		$data['blog_id']  = \Jetpack_Options::get_option( 'id' );
78
79
		// Top level events should not be namespaced.
80
		if ( '_aliasUser' !== $event_type ) {
81
			$event_type = $this->product_name . '_' . $event_type;
82
		}
83
84
		$data['jetpack_version'] = defined( 'JETPACK__VERSION' ) ? JETPACK__VERSION : '0';
85
86
		return $this->tracks_record_event( $user, $event_type, $data );
87
	}
88
89
	/**
90
	 * Record an event in Tracks - this is the preferred way to record events from PHP.
91
	 *
92
	 * @param mixed  $user                   username, user_id, or WP_user object.
93
	 * @param string $event_name             The name of the event.
94
	 * @param array  $properties             Custom properties to send with the event.
95
	 * @param int    $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $event_timestamp_millis not be false|integer?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
96
	 *
97
	 * @return bool true for success | \WP_Error if the event pixel could not be fired
98
	 */
99
	public function tracks_record_event( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
100
101
		// We don't want to track user events during unit tests/CI runs.
102
		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...
103
			return false;
104
		}
105
		$terms_of_service = new Terms_Of_Service();
106
		// Don't track users who have opted out or not agreed to our TOS, or are not running an active Jetpack.
107
		if ( ! $terms_of_service->has_agreed() ) {
108
			return false;
109
		}
110
111
		$event_obj = $this->tracks_build_event_obj( $user, $event_name, $properties, $event_timestamp_millis );
112
113
		if ( is_wp_error( $event_obj->error ) ) {
114
			return $event_obj->error;
115
		}
116
117
		return $event_obj->record();
0 ignored issues
show
Bug introduced by
The method record does only exist in Jetpack_Tracks_Event, but not in WP_Error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
118
	}
119
120
	/**
121
	 * Procedurally build a Tracks Event Object.
122
	 * NOTE: Use this only when the simpler Automattic\Jetpack\Tracking->jetpack_tracks_record_event() function won't work for you.
123
	 *
124
	 * @param WP_user $user                   WP_user object.
125
	 * @param string  $event_name             The name of the event.
126
	 * @param array   $properties             Custom properties to send with the event.
127
	 * @param int     $event_timestamp_millis The time in millis since 1970-01-01 00:00:00 when the event occurred.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $event_timestamp_millis not be false|integer?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
128
	 *
129
	 * @return \Jetpack_Tracks_Event|\WP_Error
130
	 */
131
	private function tracks_build_event_obj( $user, $event_name, $properties = array(), $event_timestamp_millis = false ) {
132
		$identity = $this->tracks_get_identity( $user->ID );
133
134
		$properties['user_lang'] = $user->get( 'WPLANG' );
135
136
		$blog_details = array(
137
			'blog_lang' => isset( $properties['blog_lang'] ) ? $properties['blog_lang'] : get_bloginfo( 'language' ),
138
		);
139
140
		$timestamp        = ( false !== $event_timestamp_millis ) ? $event_timestamp_millis : round( microtime( true ) * 1000 );
141
		$timestamp_string = is_string( $timestamp ) ? $timestamp : number_format( $timestamp, 0, '', '' );
142
143
		return new \Jetpack_Tracks_Event(
144
			array_merge(
0 ignored issues
show
Documentation introduced by
array_merge($blog_detail... => $timestamp_string)) is of type array, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
145
				$blog_details,
146
				(array) $properties,
147
				$identity,
148
				array(
149
					'_en' => $event_name,
150
					'_ts' => $timestamp_string,
151
				)
152
			)
153
		);
154
	}
155
156
	/**
157
	 * Get the identity to send to tracks.
158
	 *
159
	 * @param int $user_id The user id of the local user.
160
	 *
161
	 * @return array $identity
162
	 */
163
	public function tracks_get_identity( $user_id ) {
164
165
		// Meta is set, and user is still connected.  Use WPCOM ID.
166
		$wpcom_id = get_user_meta( $user_id, 'jetpack_tracks_wpcom_id', true );
167
		if ( $wpcom_id && $this->connection->is_user_connected( $user_id ) ) {
168
			return array(
169
				'_ut' => 'wpcom:user_id',
170
				'_ui' => $wpcom_id,
171
			);
172
		}
173
174
		// User is connected, but no meta is set yet.  Use WPCOM ID and set meta.
175
		if ( $this->connection->is_user_connected( $user_id ) ) {
176
			$wpcom_user_data = $this->connection->get_connected_user_data( $user_id );
177
			update_user_meta( $user_id, 'jetpack_tracks_wpcom_id', $wpcom_user_data['ID'] );
178
179
			return array(
180
				'_ut' => 'wpcom:user_id',
181
				'_ui' => $wpcom_user_data['ID'],
182
			);
183
		}
184
185
		// User isn't linked at all.  Fall back to anonymous ID.
186
		$anon_id = get_user_meta( $user_id, 'jetpack_tracks_anon_id', true );
187
		if ( ! $anon_id ) {
188
			$anon_id = \Jetpack_Tracks_Client::get_anon_id();
189
			add_user_meta( $user_id, 'jetpack_tracks_anon_id', $anon_id, false );
190
		}
191
192
		if ( ! isset( $_COOKIE['tk_ai'] ) && ! headers_sent() ) {
193
			setcookie( 'tk_ai', $anon_id );
194
		}
195
196
		return array(
197
			'_ut' => 'anon',
198
			'_ui' => $anon_id,
199
		);
200
201
	}
202
}
203