PluginProbe ʕ •ᴥ•ʔ
Smush – Image Optimization, Compression, Lazy Load, WebP & CDN / 3.14.1
Smush – Image Optimization, Compression, Lazy Load, WebP & CDN v3.14.1
4.1.0 4.0.3 4.0.2 2.8.1 2.9.1 3.0.0 3.0.1 3.0.2 3.1.1 3.10.1 3.10.2 3.10.3 3.11.1 3.12.3 3.12.4 3.12.5 3.12.6 3.13.0 3.13.1 3.13.2 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.15.2 3.15.3 3.15.4 3.15.5 3.16.2 3.16.4 3.16.5 3.16.6 3.17.0 3.17.1 3.18.0 3.18.1 3.2.0.1 3.2.1 3.2.2.1 3.2.4 3.20.0 3.21.1 3.22.1 3.22.3 3.23.0 3.23.1 3.23.2 3.23.3 3.23.4 3.24.0 3.24.0-beta.2 3.3.0 3.3.1 3.3.2 3.4.1 3.4.2 3.6.1 3.6.3 3.7.0 3.7.1 3.7.2 3.7.3 3.8.2 3.8.3 3.8.4 3.8.5 3.8.7 3.8.8 3.9.0 3.9.1 3.9.11 3.9.2 3.9.4 3.9.5 3.9.8 3.9.9 trunk 1.0.0 1.0.1 1.0.2 1.1 1.1.1 1.1.2 1.1.3 1.2 1.2.1 1.2.10 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.5.1 1.6.5.2 1.6.5.3 1.6.5.4 1.7 1.7.1 1.7.1.1 2.0 2.0.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.6.2 2.0.6.3 2.0.6.5 2.0.7 2.0.7.1 2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2 2.2.1 2.2.2 2.3 2.3.1 2.4 2.4.2 2.4.3 2.4.4 2.4.5 2.5.2 2.5.3 2.6.1 2.6.2 2.6.3 2.7 2.7.1 2.7.4 2.7.4.1 2.7.5 2.7.6 2.7.8 2.7.8.1 2.7.9.1 2.8.0 2.8.0.1
wp-smushit / core / modules / class-product-analytics.php
wp-smushit / core / modules Last commit date
async 2 years ago background 2 years ago bulk 2 years ago helpers 2 years ago class-abstract-module.php 2 years ago class-backup.php 2 years ago class-cdn.php 2 years ago class-dir.php 2 years ago class-lazy.php 2 years ago class-png2jpg.php 2 years ago class-product-analytics.php 2 years ago class-resize-detection.php 2 years ago class-resize.php 2 years ago class-smush.php 2 years ago class-webp.php 2 years ago
class-product-analytics.php
522 lines
1 <?php
2
3 namespace Smush\Core\Modules;
4
5 use Smush\Core\Array_Utils;
6 use Smush\Core\Integrations\Mixpanel;
7 use Smush\Core\Media_Library\Background_Media_Library_Scanner;
8 use Smush\Core\Media_Library\Media_Library_Scan_Background_Process;
9 use Smush\Core\Media_Library\Media_Library_Scanner;
10 use Smush\Core\Modules\Background\Background_Process;
11 use Smush\Core\Server_Utils;
12 use Smush\Core\Settings;
13 use Smush\Core\Stats\Global_Stats;
14 use WP_Smush;
15
16 class Product_Analytics {
17 const PROJECT_TOKEN = '5d545622e3a040aca63f2089b0e6cae7';
18 /**
19 * @var Mixpanel
20 */
21 private $mixpanel;
22 /**
23 * @var Settings
24 */
25 private $settings;
26 /**
27 * @var Server_Utils
28 */
29 private $server_utils;
30 /**
31 * @var Media_Library_Scan_Background_Process
32 */
33 private $scan_background_process;
34
35 public function __construct() {
36 $this->settings = Settings::get_instance();
37 $this->server_utils = new Server_Utils();
38 $this->scan_background_process = Background_Media_Library_Scanner::get_instance()->get_background_process();
39
40 $this->hook_actions();
41 }
42
43 private function hook_actions() {
44 // Setting events.
45 add_action( 'wp_smush_settings_updated', array( $this, 'track_opt_toggle' ), 10, 2 );
46 add_action( 'wp_smush_settings_updated', array( $this, 'intercept_settings_update' ), 10, 2 );
47 add_action( 'wp_smush_settings_deleted', array( $this, 'intercept_reset' ) );
48 add_action( 'wp_smush_settings_updated', array( $this, 'track_integrations_saved' ), 10, 2 );
49
50 if ( ! $this->settings->get( 'usage' ) ) {
51 return;
52 }
53
54 // Other events.
55 add_action( 'wp_smush_directory_smush_start', array( $this, 'track_directory_smush' ) );
56 add_action( 'wp_smush_bulk_smush_start', array( $this, 'track_bulk_smush_start' ) );
57 add_action( 'wp_smush_bulk_smush_completed', array( $this, 'track_bulk_smush_completed' ) );
58 add_action( 'wp_smush_config_applied', array( $this, 'track_config_applied' ) );
59
60 $identifier = $this->scan_background_process->get_identifier();
61
62 add_action( "{$identifier}_before_start", array( $this, 'track_background_scan_start' ) );
63 add_action( "{$identifier}_completed", array( $this, 'track_background_scan_end' ) );
64
65 add_action(
66 "{$identifier}_cancelled",
67 array( $this, 'track_background_scan_process_cancellation' ),
68 10, 2
69 );
70 add_action(
71 "{$identifier}_dead",
72 array( $this, 'track_background_scan_process_death' ),
73 10, 2
74 );
75 }
76
77 /**
78 * @return Mixpanel
79 */
80 private function get_mixpanel() {
81 if ( is_null( $this->mixpanel ) ) {
82 $this->mixpanel = $this->prepare_mixpanel_instance();
83 }
84
85 return $this->mixpanel;
86 }
87
88 public function intercept_settings_update( $old_settings, $settings ) {
89 if ( empty( $settings['usage'] ) ) {
90 // Use the most up-to-data value of 'usage'
91 return;
92 }
93
94 $settings = $this->remove_unchanged_settings( $old_settings, $settings );
95 $handled = $this->maybe_track_feature_toggle( $settings );
96
97 if ( ! $handled ) {
98 $handled = $this->maybe_track_cdn_update( $settings );
99 }
100 }
101
102 private function maybe_track_feature_toggle( array $settings ) {
103 foreach ( $settings as $setting_key => $setting_value ) {
104 $handler = "track_{$setting_key}_feature_toggle";
105 if ( method_exists( $this, $handler ) ) {
106 call_user_func( array( $this, $handler ), $setting_value );
107
108 return true;
109 }
110 }
111
112 return false;
113 }
114
115 private function remove_unchanged_settings( $old_settings, $settings ) {
116 $changed = array();
117 foreach ( $settings as $setting_key => $setting_value ) {
118 $old_setting_value = isset( $old_settings[ $setting_key ] ) ? $old_settings[ $setting_key ] : '';
119 if ( $old_setting_value !== $setting_value ) {
120 $changed[ $setting_key ] = $setting_value;
121 }
122 }
123
124 return $changed;
125 }
126
127 public function get_bulk_properties() {
128 $bulk_property_labels = array(
129 'auto' => 'Automatic Compression',
130 'strip_exif' => 'Metadata',
131 'resize' => 'Resize Original Images',
132 'original' => 'Compress original images',
133 'backup' => 'Backup original images',
134 'png_to_jpg' => 'Auto-convert PNGs to JPEGs (lossy)',
135 'no_scale' => 'Disable scaled images',
136 );
137
138 $image_sizes = Settings::get_instance()->get_setting( 'wp-smush-image_sizes' );
139 $bulk_properties = array(
140 'Image Sizes' => empty( $image_sizes ) ? 'All' : 'Custom',
141 'Mode' => $this->settings->get_current_lossy_level_label(),
142 'Parallel Processing' => defined( 'WP_SMUSH_PARALLEL' ) && WP_SMUSH_PARALLEL ? 'Enabled' : 'Disabled',
143 );
144 foreach ( $bulk_property_labels as $bulk_setting => $bulk_property_label ) {
145 $property_value = Settings::get_instance()->get( $bulk_setting )
146 ? 'Enabled'
147 : 'Disabled';
148 $bulk_properties[ $bulk_property_label ] = $property_value;
149 }
150
151 return $bulk_properties;
152 }
153
154 private function track_detection_feature_toggle( $setting_value ) {
155 return $this->track_feature_toggle( $setting_value, 'Image Resize Detection' );
156 }
157
158 private function track_webp_mod_feature_toggle( $setting_value ) {
159 return $this->track_feature_toggle( $setting_value, 'Local WebP' );
160 }
161
162 private function track_cdn_feature_toggle( $setting_value ) {
163 return $this->track_feature_toggle( $setting_value, 'CDN' );
164 }
165
166 private function track_lazy_load_feature_toggle( $setting_value ) {
167 return $this->track_feature_toggle( $setting_value, 'Lazy Load' );
168 }
169
170 private function track_feature_toggle( $active, $feature ) {
171 $event = $active
172 ? 'Feature Activated'
173 : 'Feature Deactivated';
174
175 $this->get_mixpanel()->track( $event, array(
176 'Feature' => $feature,
177 'Triggered From' => $this->identify_referrer(),
178 ) );
179
180 return true;
181 }
182
183 private function identify_referrer() {
184 $onboarding_request = ! empty( $_REQUEST['action'] ) && 'smush_setup' === $_REQUEST['action'];
185 if ( $onboarding_request ) {
186 return 'Wizard';
187 }
188
189 $path = parse_url( wp_get_referer(), PHP_URL_QUERY );
190 $query_vars = array();
191 parse_str( $path, $query_vars );
192 $page = empty( $query_vars['page'] ) ? '' : $query_vars['page'];
193 $triggered_from = array(
194 'smush' => 'Dashboard',
195 'smush-bulk' => 'Bulk Smush',
196 'smush-directory' => 'Directory Smush',
197 'smush-lazy-load' => 'Lazy Load',
198 'smush-cdn' => 'CDN',
199 'smush-webp' => 'Local WebP',
200 'smush-integrations' => 'Integrations',
201 'smush-settings' => 'Settings',
202 );
203
204 return empty( $triggered_from[ $page ] )
205 ? ''
206 : $triggered_from[ $page ];
207 }
208
209 private function prepare_mixpanel_instance() {
210 $mixpanel = new Mixpanel( $this->get_token() );
211 $mixpanel->identify( $this->get_unique_id() );
212 $mixpanel->registerAll( $this->get_super_properties() );
213
214 return $mixpanel;
215 }
216
217 public function get_super_properties() {
218 global $wp_version;
219
220 return array(
221 'active_theme' => get_stylesheet(),
222 'locale' => get_locale(),
223 'mysql_version' => $this->server_utils->get_mysql_version(),
224 'php_version' => phpversion(),
225 'plugin' => 'Smush',
226 'plugin_type' => WP_Smush::is_pro() ? 'pro' : 'free',
227 'plugin_version' => WP_SMUSH_VERSION,
228 'server_type' => $this->server_utils->get_server_type(),
229 'memory_limit' => $this->convert_to_megabytes( $this->server_utils->get_memory_limit() ),
230 'max_execution_time' => $this->server_utils->get_max_execution_time(),
231 'wp_type' => is_multisite() ? 'multisite' : 'single',
232 'wp_version' => $wp_version,
233 'device' => $this->get_device(),
234 'user_agent' => $this->server_utils->get_user_agent(),
235 );
236 }
237
238 private function get_device() {
239 if ( ! $this->is_mobile() ) {
240 return 'desktop';
241 }
242
243 if ( $this->is_tablet() ) {
244 return 'tablet';
245 }
246
247 return 'mobile';
248 }
249
250 private function is_tablet() {
251 if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
252 return false;
253 }
254 /**
255 * It doesn't work with IpadOS due to of this:
256 * https://stackoverflow.com/questions/62323230/how-can-i-detect-with-php-that-the-user-uses-an-ipad-when-my-user-agent-doesnt-c
257 */
258 $tablet_pattern = '/(tablet|ipad|playbook|kindle|silk)/i';
259 return preg_match( $tablet_pattern, $_SERVER['HTTP_USER_AGENT'] );
260 }
261
262 private function is_mobile() {
263 if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
264 return false;
265 }
266 // Do not use wp_is_mobile() since it doesn't detect ipad/tablet.
267 $mobile_patten = '/Mobile|iP(hone|od|ad)|Android|BlackBerry|tablet|IEMobile|Kindle|NetFront|Silk|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune|playbook/i';
268 return preg_match( $mobile_patten, $_SERVER['HTTP_USER_AGENT'] );
269 }
270
271 private function normalize_url( $url ) {
272 $url = str_replace( array( 'http://', 'https://', 'www.' ), '', $url );
273
274 return untrailingslashit( $url );
275 }
276
277 private function maybe_track_cdn_update( $settings ) {
278 $cdn_properties = array();
279 $cdn_property_labels = $this->cdn_property_labels();
280 foreach ( $settings as $setting_key => $setting_value ) {
281 if ( array_key_exists( $setting_key, $cdn_property_labels ) ) {
282 $property_label = $cdn_property_labels[ $setting_key ];
283 $property_value = $setting_value ? 'Enabled' : 'Disabled';
284 $cdn_properties[ $property_label ] = $property_value;
285 }
286 }
287
288 if ( $cdn_properties ) {
289 $this->get_mixpanel()->track( 'CDN Updated', $cdn_properties );
290
291 return true;
292 }
293
294 return false;
295 }
296
297 private function cdn_property_labels() {
298 return array(
299 'background_images' => 'Background Images',
300 'auto_resize' => 'Automatic Resizing',
301 'webp' => 'WebP Conversions',
302 'rest_api_support' => 'Rest API',
303 );
304 }
305
306 public function track_directory_smush() {
307 $this->get_mixpanel()->track( 'Directory Smushed' );
308 }
309
310 public function track_bulk_smush_start() {
311 $this->get_mixpanel()->track( 'Bulk Smush Started', $this->get_bulk_properties() );
312 }
313
314 public function track_bulk_smush_completed() {
315 $this->get_mixpanel()->track( 'Bulk Smush Completed', $this->get_bulk_smush_stats() );
316 }
317
318 private function get_bulk_smush_stats() {
319 $global_stats = WP_Smush::get_instance()->core()->get_global_stats();
320 $array_util = new Array_Utils();
321
322 return array(
323 'Total Savings' => $this->convert_to_megabytes( (int) $array_util->get_array_value( $global_stats, 'savings_bytes' ) ),
324 'Total Images' => (int) $array_util->get_array_value( $global_stats, 'count_images' ),
325 'Media Optimization Percentage' => (float) $array_util->get_array_value( $global_stats, 'percent_optimized' ),
326 'Percentage of Savings' => (float) $array_util->get_array_value( $global_stats, 'savings_percent' ),
327 'Images Resized' => (int) $array_util->get_array_value( $global_stats, 'count_resize' ),
328 'Resize Savings' => $this->convert_to_megabytes( (int) $array_util->get_array_value( $global_stats, 'savings_resize' ) ),
329 );
330 }
331
332 public function track_config_applied( $config_name ) {
333 $properties = $config_name
334 ? array( 'Config Name' => $config_name )
335 : array();
336
337 $properties['Triggered From'] = $this->identify_referrer();
338
339 $this->get_mixpanel()->track( 'Config Applied', $properties );
340 }
341
342 public function get_unique_id() {
343 return $this->normalize_url( home_url() );
344 }
345
346 public function get_token() {
347 return self::PROJECT_TOKEN;
348 }
349
350 public function track_opt_toggle( $old_settings, $settings ) {
351 $settings = $this->remove_unchanged_settings( $old_settings, $settings );
352
353 if ( isset( $settings['usage'] ) ) {
354 $this->get_mixpanel()->track( $settings['usage'] ? 'Opt In' : 'Opt Out' );
355 }
356 }
357
358 public function track_integrations_saved( $old_settings, $settings ) {
359 if ( empty( $settings['usage'] ) ) {
360 return;
361 }
362
363 $settings = $this->remove_unchanged_settings( $old_settings, $settings );
364 if ( empty( $settings ) ) {
365 return;
366 }
367
368 $this->maybe_track_integrations_toggle( $settings );
369 }
370
371 private function maybe_track_integrations_toggle( $settings ) {
372 $integrations = array(
373 'gutenberg' => 'Gutenberg',
374 'gform' => 'Gravity Forms',
375 'js_builder' => 'WP Bakery',
376 's3' => 'Amazon S3',
377 'nextgen' => 'NextGen Gallery',
378 );
379
380 foreach ( $settings as $integration_slug => $is_activated ) {
381 if ( ! array_key_exists( $integration_slug, $integrations ) ) {
382 continue;
383 }
384
385 if ( $is_activated ) {
386 $this->get_mixpanel()->track(
387 'Integration Activated',
388 array(
389 'Integration' => $integrations[ $integration_slug ],
390 )
391 );
392 } else {
393 $this->get_mixpanel()->track(
394 'Integration Deactivated',
395 array(
396 'Integration' => $integrations[ $integration_slug ],
397 )
398 );
399 }
400 }
401 }
402
403 public function intercept_reset() {
404 if ( $this->settings->get( 'usage' ) ) {
405 $this->get_mixpanel()->track( 'Opt Out' );
406 }
407 }
408
409 public function track_background_scan_start() {
410 $properties = array(
411 'Scan Type' => $this->scan_background_process->get_status()->is_dead() ? 'Retry' : 'New',
412 );
413
414 $this->get_mixpanel()->track( 'Scan Started', array_merge(
415 $properties,
416 $this->get_bulk_properties(),
417 $this->get_scan_properties()
418 ) );
419 }
420
421 public function track_background_scan_end() {
422 $this->get_mixpanel()->track( 'Scan Ended', array_merge(
423 $this->get_bulk_properties(),
424 $this->get_scan_properties()
425 ) );
426 }
427
428 /**
429 * @param $identifier string
430 * @param $background_process Background_Process
431 *
432 * @return void
433 */
434 public function track_background_scan_process_cancellation( $identifier, $background_process ) {
435 $this->get_mixpanel()->track(
436 'Background Scan Process Cancelled',
437 $this->get_background_process_status_properties( $background_process )
438 );
439 }
440
441 /**
442 * @param $identifier string
443 * @param $background_process Background_Process
444 *
445 * @return void
446 */
447 public function track_background_scan_process_death( $identifier, $background_process ) {
448 $scanner = new Media_Library_Scanner();
449
450 $this->get_mixpanel()->track(
451 'Background Scan Process Dead',
452 array_merge(
453 array( 'Slice Size' => $scanner->get_slice_size() ),
454 $this->get_background_process_status_properties( $background_process )
455 )
456 );
457 }
458
459 private function get_scan_properties() {
460 $global_stats = Global_Stats::get();
461 $global_stats_array = $global_stats->to_array();
462
463 $labels = array(
464 'image_attachment_count' => 'Image Attachment Count',
465 'optimized_images_count' => 'Optimized Images Count',
466 'optimize_count' => 'Optimize Count',
467 'reoptimize_count' => 'Reoptimize Count',
468 'ignore_count' => 'Ignore Count',
469 'animated_count' => 'Animated Count',
470 'error_count' => 'Error Count',
471 'percent_optimized' => 'Percent Optimized',
472 'size_before' => 'Size Before',
473 'size_after' => 'Size After',
474 'savings_percent' => 'Savings Percent',
475 );
476
477 $savings_keys = array(
478 'size_before',
479 'size_after',
480 );
481
482 foreach ( $labels as $key => $label ) {
483 if ( isset( $global_stats_array[ $key ] ) ) {
484 $properties[ $label ] = $global_stats_array[ $key ];
485
486 if ( in_array( $key, $savings_keys, true ) ) {
487 $properties[ $label ] = $this->convert_to_megabytes( $properties[ $label ] );
488 }
489 }
490 }
491
492 return $properties;
493 }
494
495 /**
496 * @param Background_Process $background_process
497 *
498 * @return array
499 */
500 private function get_background_process_status_properties( Background_Process $background_process ) {
501 $background_process_status = $background_process ? $background_process->get_status() : false;
502 $properties = array();
503 if ( $background_process_status ) {
504 $properties = array(
505 'Total Items' => $background_process_status->get_total_items(),
506 'Processed Items' => $background_process_status->get_processed_items(),
507 'Failed Items' => $background_process_status->get_failed_items(),
508 );
509 }
510
511 return $properties;
512 }
513
514 private function convert_to_megabytes( $size_in_bytes ) {
515 if ( empty( $size_in_bytes ) ) {
516 return 0;
517 }
518 $unit_mb = pow( 1024, 2 );
519 return round( $size_in_bytes / $unit_mb, 2 );
520 }
521 }
522