class.tracks-event.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. <?php
  2. /**
  3. * @autounit nosara tracks-client
  4. *
  5. * Example Usage:
  6. ```php
  7. require_once( dirname(__FILE__) . 'path/to/tracks/class.tracks-event' );
  8. $event = new Jetpack_Tracks_Event( array(
  9. '_en' => $event_name, // required
  10. '_ui' => $user_id, // required unless _ul is provided
  11. '_ul' => $user_login, // required unless _ui is provided
  12. // Optional, but recommended
  13. '_via_ip' => $client_ip, // for geo, etc.
  14. // Possibly useful to set some context for the event
  15. '_via_ua' => $client_user_agent,
  16. '_via_url' => $client_url,
  17. '_via_ref' => $client_referrer,
  18. // For user-targeted tests
  19. 'abtest_name' => $abtest_name,
  20. 'abtest_variation' => $abtest_variation,
  21. // Your application-specific properties
  22. 'custom_property' => $some_value,
  23. ) );
  24. if ( is_wp_error( $event->error ) ) {
  25. // Handle the error in your app
  26. }
  27. $bump_and_redirect_pixel = $event->build_signed_pixel_url();
  28. ```
  29. */
  30. require_once( dirname(__FILE__) . '/class.tracks-client.php' );
  31. class Jetpack_Tracks_Event {
  32. const EVENT_NAME_REGEX = '/^(([a-z0-9]+)_){2}([a-z0-9_]+)$/';
  33. const PROP_NAME_REGEX = '/^[a-z_][a-z0-9_]*$/';
  34. public $error;
  35. function __construct( $event ) {
  36. $_event = self::validate_and_sanitize( $event );
  37. if ( is_wp_error( $_event ) ) {
  38. $this->error = $_event;
  39. return;
  40. }
  41. foreach( $_event as $key => $value ) {
  42. $this->{$key} = $value;
  43. }
  44. }
  45. function record() {
  46. return Jetpack_Tracks_Client::record_event( $this );
  47. }
  48. /**
  49. * Annotate the event with all relevant info.
  50. * @param mixed $event Object or (flat) array
  51. * @return mixed The transformed event array or WP_Error on failure.
  52. */
  53. static function validate_and_sanitize( $event ) {
  54. $event = (object) $event;
  55. // Required
  56. if ( ! $event->_en ) {
  57. return new WP_Error( 'invalid_event', 'A valid event must be specified via `_en`', 400 );
  58. }
  59. // delete non-routable addresses otherwise geoip will discard the record entirely
  60. if ( property_exists( $event, '_via_ip' ) && preg_match( '/^192\.168|^10\./', $event->_via_ip ) ) {
  61. unset($event->_via_ip);
  62. }
  63. $validated = array(
  64. 'browser_type' => Jetpack_Tracks_Client::BROWSER_TYPE,
  65. '_aua' => Jetpack_Tracks_Client::get_user_agent(),
  66. );
  67. $_event = (object) array_merge( (array) $event, $validated );
  68. // If you want to blacklist property names, do it here.
  69. // Make sure we have an event timestamp.
  70. if ( ! isset( $_event->_ts ) ) {
  71. $_event->_ts = Jetpack_Tracks_Client::build_timestamp();
  72. }
  73. return $_event;
  74. }
  75. /**
  76. * Build a pixel URL that will send a Tracks event when fired.
  77. * On error, returns an empty string ('').
  78. *
  79. * @return string A pixel URL or empty string ('') if there were invalid args.
  80. */
  81. function build_pixel_url() {
  82. if ( $this->error ) {
  83. return '';
  84. }
  85. $args = get_object_vars( $this );
  86. // Request Timestamp and URL Terminator must be added just before the HTTP request or not at all.
  87. unset( $args['_rt'] );
  88. unset( $args['_'] );
  89. $validated = self::validate_and_sanitize( $args );
  90. if ( is_wp_error( $validated ) )
  91. return '';
  92. return Jetpack_Tracks_Client::PIXEL . '?' . http_build_query( $validated );
  93. }
  94. static function event_name_is_valid( $name ) {
  95. return preg_match( Jetpack_Tracks_Event::EVENT_NAME_REGEX, $name );
  96. }
  97. static function prop_name_is_valid( $name ) {
  98. return preg_match( Jetpack_Tracks_Event::PROP_NAME_REGEX, $name );
  99. }
  100. static function scrutinize_event_names( $event ) {
  101. if ( ! Jetpack_Tracks_Event::event_name_is_valid( $event->_en ) ) {
  102. return;
  103. }
  104. $whitelisted_key_names = array(
  105. 'anonId',
  106. 'Browser_Type',
  107. );
  108. foreach ( array_keys( (array) $event ) as $key ) {
  109. if ( in_array( $key, $whitelisted_key_names ) ) {
  110. continue;
  111. }
  112. if ( ! Jetpack_Tracks_Event::prop_name_is_valid( $key ) ) {
  113. return;
  114. }
  115. }
  116. }
  117. }