PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
merchant / inc / usage-tracking / class-merchant-usage-tracking.php

class-merchant-usage-tracking.php in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.3.2, at inc/usage-tracking/class-merchant-usage-tracking.php

495 lines 11.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Usage Tracker functionality to understand what's going on client's sites.
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit; // Exit if accessed directly.
8 }
9
10 /**
11 * Merchant_Usage_Tracking class.
12 */
13 class Merchant_Usage_Tracking {
14
15 /**
16 * The slug that will be used to save the option of Usage Tracker.
17 *
18 * @since 2.2.0
19 */
20 const SETTINGS_SLUG = 'usage-tracking-enabled';
21
22 /**
23 * Initialize the usage tracking system.
24 *
25 * @since 2.2.0
26 */
27 public static function init_system() {
28
29 /**
30 * Filter whether the Usage Tracking code is allowed to be loaded.
31 *
32 * @param bool $allowed Whether usage tracking is allowed.
33 *
34 * @since 2.2.0
35 */
36 if ( ! apply_filters( 'merchant_usage_tracking_is_allowed', true ) ) {
37 return;
38 }
39
40 $usage_tracking = new self();
41 $usage_tracking->init();
42
43 $send_task = new Merchant_Send_Usage_Task();
44 $send_task->init();
45 }
46
47 /**
48 * Attach hooks to the WordPress API
49 *
50 * @since 2.2.0
51 */
52 public function init() {
53
54 // Add settings option.
55 add_action( 'merchant_module_settings', array( $this, 'add_settings_option' ) );
56
57 // Deregister the action if option is disabled.
58 add_action( 'merchant_options_saved_global-settings', array( $this, 'maybe_cancel_task' ) );
59
60 // Schedule the task if enabled.
61 if ( self::is_enabled() ) {
62 $this->schedule_task();
63 }
64 }
65
66 /**
67 * Whether Usage Tracking is enabled.
68 *
69 * @since 2.2.0
70 *
71 * @return bool
72 */
73 public static function is_enabled() {
74
75 /**
76 * Filter whether the Usage Tracking is enabled.
77 *
78 * @param bool $enabled Whether usage tracking is enabled.
79 *
80 * @since 2.2.0
81 */
82 return (bool) apply_filters(
83 'merchant_usage_tracking_is_enabled',
84 Merchant_Admin_Options::get( 'global-settings', self::SETTINGS_SLUG, false )
85 );
86 }
87
88 /**
89 * Add "Allow Usage Tracking" to Merchant settings.
90 *
91 * @since 2.2.0
92 */
93 public function add_settings_option( $settings ) {
94
95 if (
96 ! isset( $settings['module'] ) ||
97 $settings['module'] !== 'global-settings' ||
98 ! isset( $settings['title'] ) ||
99 $settings['title'] !== esc_html__( 'Merchant Analytics', 'merchant' )
100 ) {
101 return $settings;
102 }
103
104 /**
105 * Filter whether to show the usage tracking setting in the admin.
106 *
107 * @param bool $show_setting Whether to show the usage tracking setting.
108 *
109 * @since 2.2.0
110 */
111 if ( ! apply_filters( 'merchant_usage_tracking_show_setting', true ) ) {
112 return $settings;
113 }
114
115 // Ensure fields array exists.
116 if ( ! isset( $settings['fields'] ) || ! is_array( $settings['fields'] ) ) {
117 $settings['fields'] = array();
118 }
119
120 $settings['fields'] = array_merge(
121 array(
122 array(
123 'id' => self::SETTINGS_SLUG,
124 'type' => 'switcher',
125 'title' => esc_html__( 'Improve Merchant', 'merchant' ),
126 'desc' => esc_html__( 'By allowing us to track usage data, we can better help you, as we will know which WordPress configurations, themes, and plugins we should test. No sensitive data is collected.', 'merchant' ),
127 'default' => false,
128 ),
129 ),
130 $settings['fields']
131 );
132
133 return $settings;
134 }
135
136 /**
137 * Schedule the usage tracking task.
138 *
139 * @since 2.2.0
140 */
141 public function schedule_task() {
142
143 if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
144 return;
145 }
146
147 // Check if already scheduled.
148 if ( as_next_scheduled_action( 'merchant_send_usage_data', array(), 'merchant' ) ) {
149 return;
150 }
151
152 // Schedule to run weekly.
153 as_schedule_recurring_action( time(), WEEK_IN_SECONDS, 'merchant_send_usage_data', array(), 'merchant', true );
154 }
155
156 /**
157 * Cancel the usage tracking task if disabled.
158 *
159 * @since 2.2.0
160 */
161 public function maybe_cancel_task() {
162
163 if ( self::is_enabled() || ! function_exists( 'as_unschedule_action' ) ) {
164 return;
165 }
166
167 as_unschedule_action( 'merchant_send_usage_data', array(), 'merchant' );
168 }
169
170 /**
171 * Get the User Agent string that will be sent to the API.
172 *
173 * @since 2.2.0
174 *
175 * @return string
176 */
177 public function get_user_agent() {
178
179 $version = MERCHANT_VERSION;
180
181 if ( defined( 'MERCHANT_PRO_VERSION' ) ) {
182 $version .= ' Pro/' . MERCHANT_PRO_VERSION;
183 }
184
185 return 'aThemes Merchant/' . $version . '; ' . get_bloginfo( 'url' );
186 }
187
188 /**
189 * Get data for sending to the server.
190 *
191 * @since 2.2.0
192 *
193 * @return array
194 */
195 public function get_data() {
196
197 global $wpdb;
198
199 $theme_data = wp_get_theme();
200 $is_pro = defined( 'MERCHANT_PRO_VERSION' );
201
202 $data = array(
203 // Generic data (environment) - keys without prefix.
204 'url' => home_url(),
205 'php_version' => PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION,
206 'wp_version' => get_bloginfo( 'version' ),
207 'mysql_version' => $wpdb->db_version(),
208 'server_version' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '',
209 'is_ssl' => (int) is_ssl(),
210 'is_multisite' => (int) is_multisite(),
211 'is_network_activated' => (int) $this->is_active_for_network(),
212 'is_wpcom' => (int) ( defined( 'IS_WPCOM' ) && IS_WPCOM ),
213 'is_wpcom_vip' => (int) ( ( defined( 'WPCOM_IS_VIP_ENV' ) && WPCOM_IS_VIP_ENV ) || ( function_exists( 'wpcom_is_vip' ) && wpcom_is_vip() ) ),
214 'is_wp_cache' => (int) ( defined( 'WP_CACHE' ) && WP_CACHE ),
215 'is_wp_rest_api_enabled' => (int) $this->is_rest_api_enabled(),
216 'is_user_logged_in' => (int) is_user_logged_in(),
217 'sites_count' => $this->get_sites_total(),
218 'active_plugins' => $this->get_active_plugins(),
219 'theme_name' => $theme_data->get( 'Name' ),
220 'theme_version' => $theme_data->get( 'Version' ),
221 'locale' => get_locale(),
222 'timezone_offset' => wp_timezone_string(),
223 // Merchant-specific data - keys with athemes_merchant_ prefix.
224 'athemes_merchant_version' => MERCHANT_VERSION,
225 'athemes_merchant_license_key' => $this->get_license_key(),
226 'athemes_merchant_license_type' => $this->get_license_type(),
227 'athemes_merchant_is_pro' => (int) $is_pro,
228 'athemes_merchant_lite_installed_date' => $this->get_installed_date( 'lite' ),
229 'athemes_merchant_pro_installed_date' => $is_pro ? $this->get_installed_date( 'pro' ) : '',
230 'athemes_merchant_active_modules' => $this->get_active_modules(),
231 'athemes_merchant_settings' => $this->get_settings(),
232 );
233
234 if ( $is_pro ) {
235 $data['athemes_merchant_pro_version'] = MERCHANT_PRO_VERSION;
236 }
237
238 if ( $data['is_multisite'] ) {
239 $data['url_primary'] = network_site_url();
240 }
241
242 /**
243 * Filter the usage tracking data.
244 *
245 * @param array $data Usage tracking data.
246 *
247 * @since 2.2.0
248 */
249 return apply_filters( 'merchant_usage_tracking_data', $data );
250 }
251
252 /**
253 * Get the installed date for Merchant or Merchant Pro.
254 *
255 * @since 2.2.0
256 *
257 * @param string $type Either 'lite' or 'pro'.
258 *
259 * @return int Unix timestamp of installation date.
260 */
261 private function get_installed_date( $type = 'lite' ) {
262
263 $option_name = 'merchant_installed_date';
264
265 if ( $type === 'pro' ) {
266 $option_name = 'merchant_pro_installed_date';
267 }
268
269 $installed_date = get_option( $option_name, 0 );
270
271 // If not set, set it now.
272 if ( empty( $installed_date ) ) {
273 $installed_date = time();
274 // Update the option.
275 update_option( $option_name, $installed_date );
276 }
277
278 return (int) $installed_date;
279 }
280
281 /**
282 * Get the license key (masked for security)
283 *
284 * @since 2.2.0
285
286 * @return string
287 */
288 private function get_license_key() {
289
290 $license_key = trim( get_option( 'merchant_pro_license_key', '' ) );
291
292 if ( empty( $license_key ) ) {
293 return '';
294 }
295
296 // Mask the license key for security (show only last 4 characters).
297 return sanitize_text_field( $license_key );
298 }
299
300 /**
301 * Get the license type.
302 *
303 * @since 2.2.0
304 *
305 * @return string
306 */
307 private function get_license_type() {
308
309 if ( ! defined( 'MERCHANT_PRO_VERSION' ) ) {
310 return 'lite';
311 }
312
313 // Get the license item name from option (for future plan names like "agency", "lifetime", etc.)
314 $license_item_name = get_option( 'merchant_pro_license_item_name', '' );
315
316 // If option is empty or default, return 'pro', otherwise return the plan name
317 if ( empty( $license_item_name ) || $license_item_name === 'Merchant Pro' ) {
318 return 'pro';
319 }
320
321 return sanitize_text_field( strtolower( $license_item_name ) );
322 }
323
324 /**
325 * Get all active modules.
326 *
327 * @since 2.2.0
328 *
329 * @return array
330 */
331 private function get_active_modules() {
332
333 if ( ! function_exists( 'merchant_get_active_modules' ) ) {
334 return array();
335 }
336
337 return merchant_get_active_modules();
338 }
339
340 /**
341 * Get all settings, except those with sensitive data.
342 *
343 * @since 2.2.0
344 *
345 * @return array
346 */
347 private function get_settings() {
348
349 // Get global settings (excluding sensitive data).
350 $global_settings = Merchant_Admin_Options::get_all( 'global-settings' );
351
352 if ( empty( $global_settings ) || ! is_array( $global_settings ) ) {
353 return array();
354 }
355
356 return $global_settings;
357 }
358
359 /**
360 * Get the list of active plugins.
361 *
362 * @since 2.2.0
363 *
364 * @return array
365 */
366 private function get_active_plugins() {
367
368 if ( ! function_exists( 'get_plugins' ) ) {
369 include ABSPATH . '/wp-admin/includes/plugin.php';
370 }
371
372 $active = is_multisite() ?
373 array_merge( get_option( 'active_plugins', array() ), array_flip( get_site_option( 'active_sitewide_plugins', array() ) ) ) :
374 get_option( 'active_plugins', array() );
375
376 $plugins = array_intersect_key( get_plugins(), array_flip( $active ) );
377
378 return array_map(
379 static function ( $plugin ) {
380 if ( isset( $plugin['Version'] ) ) {
381 return $plugin['Version'];
382 }
383
384 return 'Not Set';
385 },
386 $plugins
387 );
388 }
389
390 /**
391 * Test if the REST API is accessible.
392 *
393 * The REST API might be inaccessible due to various security measures,
394 * or it might be completely disabled by a plugin.
395 *
396 * @since 2.2.0
397 *
398 * @return bool
399 */
400 private function is_rest_api_enabled() {
401
402 $url = rest_url( 'wp/v2/types/post' );
403 $response = wp_remote_get(
404 $url,
405 array(
406 'timeout' => 10,
407 'cookies' => is_user_logged_in() ? wp_unslash( $_COOKIE ) : array(),
408 'headers' => array(
409 'Cache-Control' => 'no-cache',
410 'X-WP-Nonce' => wp_create_nonce( 'wp_rest' ),
411 ),
412 )
413 );
414
415 // When testing the REST API, an error was encountered, leave early.
416 if ( is_wp_error( $response ) ) {
417 return false;
418 }
419
420 // When testing the REST API, an unexpected result was returned, leave early.
421 if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
422 return false;
423 }
424
425 // The REST API did not behave correctly, leave early.
426 $body = wp_remote_retrieve_body( $response );
427
428 if ( ! $this->is_json( $body ) ) {
429 return false;
430 }
431
432 // We are all set. Confirm the connection.
433 return true;
434 }
435
436 /**
437 * Check if a string is valid JSON.
438 *
439 * @since 2.2.0
440 *
441 * @param string $value The string to check.
442 *
443 * @return bool
444 */
445 private function is_json( $value ) {
446
447 if ( ! is_string( $value ) ) {
448 return false;
449 }
450
451 json_decode( $value, true );
452
453 return json_last_error() === JSON_ERROR_NONE;
454 }
455
456 /**
457 * Determines whether the plugin is active for the entire network.
458 *
459 * @since 2.2.0
460 *
461 * @return bool
462 */
463 private function is_active_for_network() {
464
465 // Bail early, in case we are not in multisite.
466 if ( ! is_multisite() ) {
467 return false;
468 }
469
470 // Get all active plugins.
471 $plugins = get_site_option( 'active_sitewide_plugins' );
472
473 // Bail early, in case the plugin is active for the entire network.
474 if ( isset( $plugins[ plugin_basename( MERCHANT_FILE ) ] ) ) {
475 return true;
476 }
477
478 return false;
479 }
480
481 /**
482 * Total number of sites.
483 *
484 * @since 2.2.0
485 *
486 * @return int
487 */
488 private function get_sites_total() {
489 return function_exists( 'get_blog_count' ) ? (int) get_blog_count() : 1;
490 }
491 }
492
493 // Initialize usage tracking.
494 add_action( 'init', array( 'Merchant_Usage_Tracking', 'init_system' ) );
495