PluginProbe
Parse.ly / 3.24.1
Parse.ly v3.24.1
3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 3.20.3 All 105 releases
wp-parsely / src / Telemetry / Tracks / class-tracks-event.php

class-tracks-event.php in Parse.ly 3.24.1, at src/Telemetry/Tracks/class-tracks-event.php

292 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Telemetry: Tracks Event class
4 *
5 * @package Parsely\Telemetry
6 * @since 3.12.0
7 */
8
9 declare(strict_types=1);
10
11 namespace Parsely\Telemetry;
12
13 use Parsely\Utils\Utils;
14 use stdClass;
15 use WP_Error;
16
17 use const Parsely\PARSELY_VERSION;
18
19 /**
20 * Class that creates and validates Tracks events.
21 *
22 * @since 3.12.0
23 */
24 class Tracks_Event {
25 /**
26 * Event name prefix.
27 */
28 protected const EVENT_NAME_PREFIX = 'wpparsely_';
29
30 /**
31 * Event name regex.
32 */
33 protected const EVENT_NAME_REGEX = '/^(([a-z0-9]+)_){1}([a-z0-9_]+)$/';
34
35 /**
36 * Property name regex.
37 */
38 protected const PROPERTY_NAME_REGEX = '/^[a-z_][a-z0-9_]*$/';
39
40 /**
41 * Variable containing the event's data or an error if one was encountered
42 * during the event's creation.
43 *
44 * @var stdClass|WP_Error
45 */
46 protected $data = null;
47
48 /**
49 * Constructor.
50 *
51 * @since 3.12.0
52 *
53 * @param string $event_name The event's name.
54 * @param array<string, mixed>|array<empty> $event_properties Any properties included in the event.
55 */
56 public function __construct( string $event_name, array $event_properties ) {
57 $event_data = self::process_properties( $event_name, $event_properties );
58 $validation_result = self::get_event_validation_result( $event_data );
59
60 $this->data = $validation_result ?? $event_data;
61 }
62
63 /**
64 * Returns the event's data.
65 *
66 * @since 3.12.0
67 *
68 * @return stdClass|WP_Error Event object if the event was created successfully, WP_Error otherwise.
69 */
70 public function get_data() {
71 return $this->data;
72 }
73
74 /**
75 * Returns whether the event can be recorded.
76 *
77 * @since 3.12.0
78 *
79 * @return bool|WP_Error True if the event is recordable.
80 * False if the event is not recordable.
81 * WP_Error if the event is generating an error.
82 */
83 public function is_recordable() {
84 if ( ! Telemetry_System::is_wpadmin_telemetry_allowed() ) {
85 return false;
86 }
87
88 // Don't record events during unit tests and CI runs.
89 if ( 'wptests_capabilities' === wp_get_current_user()->cap_key ) {
90 return false;
91 }
92
93 if ( is_wp_error( $this->data ) ) {
94 return $this->data;
95 }
96
97 return true;
98 }
99
100 /**
101 * Processes the event's properties to get them ready for validation.
102 *
103 * @since 3.12.0
104 *
105 * @param string $event_name The event's name.
106 * @param array<string, mixed>|array<empty> $event_properties Any event properties to be processed.
107 * @return stdClass The resulting event object with processed properties.
108 */
109 protected static function process_properties(
110 string $event_name,
111 array $event_properties
112 ): stdClass {
113 $event = (object) self::sanitize_properties_array( $event_properties );
114 $event = self::set_user_properties( $event );
115
116 // Set event name.
117 $event->_en = preg_replace(
118 '/^(?:' . self::EVENT_NAME_PREFIX . ')?(.*)/',
119 self::EVENT_NAME_PREFIX . '\1',
120 $event_name
121 ) ?? '';
122
123 // Set event timestamp.
124 if ( ! isset( $event->_ts ) ) {
125 $event->_ts = Utils::get_timestamp();
126 }
127
128 // Remove non-routable IPs to prevent record from being discarded.
129 if ( property_exists( $event, '_via_ip' ) &&
130 1 === preg_match( '/^192\.168|^10\./', $event->_via_ip ) ) {
131 unset( $event->_via_ip );
132 }
133
134 // Set VIP environment if it exists.
135 if ( defined( 'VIP_GO_APP_ENVIRONMENT' ) ) {
136 $app_environment = constant( 'VIP_GO_APP_ENVIRONMENT' );
137 if ( is_string( $app_environment ) && '' !== $app_environment ) {
138 $event->vipgo_env = $app_environment;
139 }
140 }
141
142 // Set the WP Parsely plugin version.
143 if ( defined( '\Parsely\PARSELY_VERSION' ) ) {
144 $event->parsely_version = PARSELY_VERSION;
145 }
146
147 return $event;
148 }
149
150 /**
151 * Sets the Tracks User ID and User ID Type depending on the current
152 * environment.
153 *
154 * @since 3.12.0
155 *
156 * @param stdClass $event The event to annotate with identity information.
157 * @return stdClass The new event object including identity information.
158 */
159 protected static function set_user_properties( stdClass $event ): stdClass {
160 $wp_user_id = get_current_user_id();
161
162 // Only track logged-in users.
163 if ( 0 === $wp_user_id ) {
164 return $event;
165 }
166
167 // Users in the VIP environment.
168 if ( defined( 'VIP_GO_APP_ID' ) ) {
169 $app_id = constant( 'VIP_GO_APP_ID' );
170 if ( is_integer( $app_id ) && 0 < $app_id ) {
171 $event->_ui = $app_id . '_' . $wp_user_id;
172 $event->_ut = 'vip_go_app_wp_user';
173
174 return $event;
175 }
176 }
177
178 // All other environments.
179 $wp_base_url = get_option( 'home' );
180 if ( ! is_string( $wp_base_url ) || '' === $wp_base_url ) {
181 $wp_base_url = get_option( 'siteurl' );
182 }
183
184 /**
185 * The base URL of the site.
186 *
187 * @var string $wp_base_url
188 */
189 $event->_ui = wp_hash( sprintf( '%s|%s', $wp_base_url, $wp_user_id ) );
190 $event->_ut = 'wpparsely:user_id';
191
192 return $event;
193 }
194
195 /**
196 * Validates the event object.
197 *
198 * @since 3.12.0
199 *
200 * @param stdClass $event Event object to validate.
201 * @return ?WP_Error null if validation passed, error otherwise.
202 */
203 protected static function get_event_validation_result( stdClass $event ): ?WP_Error {
204 // Check that required fields are defined.
205 if ( ! $event->_en ) {
206 return new WP_Error(
207 'invalid_event',
208 __( 'The _en property must be specified', 'wp-parsely' ),
209 array( 'status' => 400 )
210 );
211 }
212
213 // Validate Event Name (_en).
214 if ( ! self::event_name_is_valid( $event->_en ) ) {
215 return new WP_Error(
216 'invalid_event_name',
217 __( 'A valid event name must be specified', 'wp-parsely' ),
218 array( 'status' => 400 )
219 );
220 }
221
222 // Validate property names format.
223 foreach ( array_keys( (array) $event ) as $key ) {
224 if ( ! self::property_name_is_valid( $key ) && '_en' !== $key ) {
225 return new WP_Error(
226 'invalid_property_name',
227 __( 'A valid property name must be specified', 'wp-parsely' ),
228 array( 'status' => 400 )
229 );
230 }
231 }
232
233 // Validate User ID (_ui) and User ID Type (_ut).
234 if ( ! ( property_exists( $event, '_ui' ) && property_exists( $event, '_ut' ) ) ) {
235 return new WP_Error(
236 'empty_user_information',
237 __( 'Could not determine user identity and type', 'wp-parsely' ),
238 array( 'status' => 400 )
239 );
240 }
241
242 return null;
243 }
244
245 /**
246 * Checks if the passed event name is valid.
247 *
248 * @since 3.12.0
249 *
250 * @param string $event_name The event's name.
251 * @return bool Whether the event name is valid.
252 */
253 protected static function event_name_is_valid( string $event_name ): bool {
254 return false !== preg_match( self::EVENT_NAME_REGEX, $event_name );
255 }
256
257 /**
258 * Checks if the passed property name is valid.
259 *
260 * @since 3.12.0
261 *
262 * @param string $property_name The property's name.
263 * @return bool Whether the property name is valid.
264 */
265 protected static function property_name_is_valid( string $property_name ): bool {
266 return false !== preg_match( self::PROPERTY_NAME_REGEX, $property_name );
267 }
268
269 /**
270 * Sanitizes the passed properties array.
271 *
272 * @since 3.12.0
273 *
274 * @param array<string, mixed>|array<empty> $event_properties The array to be sanitized.
275 * @return array<string, mixed>|array<empty> The sanitized array.
276 */
277 protected static function sanitize_properties_array( array $event_properties ): array {
278 $result = array();
279
280 foreach ( $event_properties as $key => $value ) {
281 if ( is_string( $value ) ) {
282 $result[ $key ] = $value;
283 continue;
284 }
285
286 $result[ $key ] = wp_json_encode( $value );
287 }
288
289 return $result;
290 }
291 }
292