暫無描述

Analytics.php 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. <?php
  2. /*******************************************************************************
  3. * Copyright (c) 2019, Code Atlantic LLC
  4. ******************************************************************************/
  5. if ( ! defined( 'ABSPATH' ) ) {
  6. exit;
  7. }
  8. /**
  9. * Controls the basic analytics methods for Popup Maker
  10. */
  11. class PUM_Analytics {
  12. /**
  13. * Initializes analytics endpoints and data
  14. */
  15. public static function init() {
  16. if ( ! self::analytics_enabled() ) {
  17. return;
  18. }
  19. add_action( 'rest_api_init', array( __CLASS__, 'register_endpoints' ) );
  20. add_action( 'wp_ajax_pum_analytics', array( __CLASS__, 'ajax_request' ) );
  21. add_action( 'wp_ajax_nopriv_pum_analytics', array( __CLASS__, 'ajax_request' ) );
  22. add_filter( 'pum_vars', array( __CLASS__, 'pum_vars' ) );
  23. }
  24. /**
  25. * Checks whether analytics is enabled.
  26. *
  27. * @return bool
  28. */
  29. public static function analytics_enabled() {
  30. $disabled = pum_get_option( 'disable_analytics' ) || popmake_get_option( 'disable_popup_open_tracking' );
  31. return (bool) apply_filters( 'pum_analytics_enabled', ! $disabled );
  32. }
  33. /**
  34. * Get a list of key pairs for each event type.
  35. * Internally used only for meta keys.
  36. *
  37. * Example returns [[open,opened],[conversion,conversion]].
  38. *
  39. * Usage examples:
  40. * - popup_open_count, popup_last_opened
  41. * - popup_conversion_count, popup_last_conversion
  42. *
  43. * @param string $event Event key.
  44. *
  45. * @return mixed
  46. */
  47. public static function event_keys( $event ) {
  48. $keys = array( $event, rtrim( $event, 'e' ) . 'ed' );
  49. if ( 'conversion' === $event ) {
  50. $keys[1] = 'conversion';
  51. }
  52. return apply_filters( 'pum_analytics_event_keys', $keys, $event );
  53. }
  54. /**
  55. * Returns an array of valid event types.
  56. *
  57. * @return string[]
  58. */
  59. public static function valid_events() {
  60. return apply_filters( 'pum_analytics_valid_events', array( 'open', 'conversion' ) );
  61. }
  62. /**
  63. * Track an event.
  64. *
  65. * This is called by various methods including the ajax & rest api requests.
  66. *
  67. * Can be used externally such as after purchase tracking.
  68. *
  69. * @param array $args
  70. */
  71. public static function track( $args = array() ) {
  72. if ( empty ( $args['pid'] ) || $args['pid'] <= 0 ) {
  73. return;
  74. }
  75. // $uuid = isset( $_COOKIE['__pum'] ) ? sanitize_text_field( $_COOKIE['__pum'] ) : false;
  76. // $session = $uuid && isset( $_COOKIE[ $uuid ] ) ? PUM_Utils_Array::safe_json_decode( $_COOKIE[ $uuid ] ) : false;
  77. $event = sanitize_text_field( $args['event'] );
  78. $popup = pum_get_popup( $args['pid'] );
  79. if ( ! pum_is_popup( $popup ) || ! in_array( $event, self::valid_events(), true ) ) {
  80. return;
  81. }
  82. $popup->increase_event_count( $event );
  83. if ( has_action( 'pum_analytics_' . $event ) ) {
  84. do_action( 'pum_analytics_' . $event, $popup->ID, $args );
  85. }
  86. do_action( 'pum_analytics_event', $args );
  87. }
  88. /**
  89. * Process ajax requests.
  90. *
  91. * Only used when WP-JSON Restful API is not available.
  92. */
  93. public static function ajax_request() {
  94. $args = wp_parse_args( $_REQUEST, array(
  95. 'event' => null,
  96. 'pid' => null,
  97. 'method' => null,
  98. ) );
  99. self::track( $args );
  100. switch ( $args['method'] ) {
  101. case 'image':
  102. self::serve_pixel();
  103. break;
  104. case 'json':
  105. self::serve_json();
  106. break;
  107. default:
  108. self::serve_no_content();
  109. break;
  110. }
  111. }
  112. /**
  113. * @param WP_REST_Request $request
  114. *
  115. * @return WP_Error|mixed
  116. */
  117. public static function analytics_endpoint( WP_REST_Request $request ) {
  118. $args = $request->get_params();
  119. if ( ! $args || empty( $args['pid'] ) ) {
  120. return new WP_Error( 'missing_params', __( 'Missing Parameters.' ), array( 'status' => 404 ) );
  121. }
  122. self::track( $args );
  123. self::serve_no_content();
  124. return true;
  125. }
  126. /**
  127. * @param $param
  128. *
  129. * @return bool
  130. */
  131. public static function endpoint_absint( $param ) {
  132. return is_numeric( $param );
  133. }
  134. /**
  135. * Registers the analytics endpoints
  136. */
  137. public static function register_endpoints() {
  138. register_rest_route( self::get_analytics_namespace(), self::get_analytics_route(), apply_filters( 'pum_analytics_rest_route_args', array(
  139. 'methods' => 'GET',
  140. 'callback' => array( __CLASS__, 'analytics_endpoint' ),
  141. 'permission_callback' => '__return_true',
  142. 'args' => array(
  143. 'event' => array(
  144. 'required' => true,
  145. 'description' => __( 'Event Type', 'popup-maker' ),
  146. 'type' => 'string',
  147. ),
  148. 'pid' => array(
  149. 'required' => true,
  150. 'description' => __( 'Popup ID', 'popup-maker' ),
  151. 'type' => 'integer',
  152. 'validation_callback' => array( __CLASS__, 'endpoint_absint' ),
  153. 'sanitize_callback' => 'absint',
  154. ),
  155. ),
  156. ) ) );
  157. }
  158. /**
  159. * Adds our analytics endpoint to pum_vars
  160. *
  161. * @param array $vars The current pum_vars.
  162. * @return array The updates pum_vars
  163. */
  164. public static function pum_vars( $vars = array() ) {
  165. $vars['analytics_route'] = self::get_analytics_route();
  166. if ( function_exists( 'rest_url' ) ) {
  167. $vars['analytics_api'] = esc_url_raw( rest_url( self::get_analytics_namespace() ) );
  168. } else {
  169. $vars['analytics_api'] = false;
  170. }
  171. return $vars;
  172. }
  173. /**
  174. * Gets the analytics namespace
  175. *
  176. * If bypass adblockers is enabled, will return random or custom string. If not, returns 'pum/v1'.
  177. *
  178. * @return string The analytics namespce
  179. * @since 1.13.0
  180. */
  181. public static function get_analytics_namespace() {
  182. $version = 1;
  183. $namespace = self::customize_endpoint_value( 'pum' );
  184. return "$namespace/v$version";
  185. }
  186. /**
  187. * Gets the analytics route
  188. *
  189. * If bypass adblockers is enabled, will return random or custom string. If not, returns 'analytics'.
  190. *
  191. * @return string The analytics route
  192. * @since 1.13.0
  193. */
  194. public static function get_analytics_route() {
  195. $route = 'analytics';
  196. return self::customize_endpoint_value( $route );
  197. }
  198. /**
  199. * Customizes the endpoint value given to it
  200. *
  201. * If bypass adblockers is enabled, will return random or custom string. If not, returns the value given to it.
  202. *
  203. * @param string $value The value to, potentially, customize.
  204. * @return string
  205. * @since 1.13.0
  206. */
  207. public static function customize_endpoint_value( $value = '' ) {
  208. $bypass_adblockers = pum_get_option( 'bypass_adblockers', false );
  209. if ( true === $bypass_adblockers || 1 === intval( $bypass_adblockers ) ) {
  210. switch ( pum_get_option( 'adblock_bypass_url_method', 'random' ) ) {
  211. case 'custom':
  212. $value = preg_replace( '/[^a-z0-9]+/', '-', pum_get_option( 'adblock_bypass_custom_filename', $value ) );
  213. break;
  214. case 'random':
  215. default:
  216. $site_url = get_site_url();
  217. $value = md5( $site_url . $value );
  218. break;
  219. }
  220. }
  221. return $value;
  222. }
  223. /**
  224. * Creates and returns a 1x1 tracking gif to the browser.
  225. */
  226. public static function serve_pixel() {
  227. $gif = self::get_file( Popup_Maker::$DIR . 'assets/images/beacon.gif' );
  228. header( 'Content-Type: image/gif' );
  229. header( 'Content-Length: ' . strlen( $gif ) );
  230. exit( $gif );
  231. }
  232. /**
  233. * @param $path
  234. *
  235. * @return bool|string
  236. */
  237. public static function get_file( $path ) {
  238. if ( function_exists( 'realpath' ) ) {
  239. $path = realpath( $path );
  240. }
  241. if ( ! $path || ! @is_file( $path ) ) {
  242. return '';
  243. }
  244. return @file_get_contents( $path );
  245. }
  246. /**
  247. * Returns a 204 no content header.
  248. */
  249. public static function serve_no_content() {
  250. header( "HTTP/1.0 204 No Content" );
  251. header( 'Content-Type: image/gif' );
  252. header( 'Content-Length: 0' );
  253. exit;
  254. }
  255. /**
  256. * Serves a proper json response.
  257. *
  258. * @param mixed $data
  259. */
  260. public static function serve_json( $data = 0 ) {
  261. header( 'Content-Type: application/json' );
  262. echo PUM_Utils_Array::safe_json_encode( $data );
  263. exit;
  264. }
  265. }