PluginProbe
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) / trunk
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) vtrunk
9.1.2 9.1.1 9.1.0 9.0.2 9.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 All 153 releases
wp-analytify / classes / analytify-utils / analytify-utils-core.php

analytify-utils-core.php in Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) trunk, at classes/analytify-utils/analytify-utils-core.php

491 lines 17.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore Squiz.Commenting.FileComment.Missing -- File doc comment exists
2
3 /**
4 * Analytify Utils Core Trait
5 *
6 * This trait contains core utility functions for the Analytify plugin.
7 * It was created to separate core utility logic from the main utils class,
8 * providing essential helper functions for data processing, formatting,
9 * and common operations used throughout the plugin.
10 *
11 * PURPOSE:
12 * - Provides core utility functions
13 * - Handles data formatting and processing
14 * - Manages common operations and checks
15 * - Offers essential helper methods
16 *
17 * @package WP_Analytify
18 * @subpackage Utils
19 * @since 8.0.0
20 */
21
22 trait Analytify_Utils_Core {
23
24 /**
25 * Safely unslash data with fallback
26 *
27 * Removes slashes from data using WordPress wp_unslash function
28 * with a fallback to stripslashes_deep for compatibility.
29 *
30 * @param mixed $arg Data to unslash.
31 * @return mixed Unslashed data
32 */
33 public static function safe_wp_unslash( $arg ) {
34 return function_exists( 'wp_unslash' ) ? wp_unslash( $arg ) : stripslashes_deep( $arg );
35 }
36
37 /**
38 * Format time duration in human-readable format
39 *
40 * Converts numeric time values (in seconds) to a human-readable
41 * format with years, days, hours, minutes, and seconds.
42 *
43 * @param int|float $time Time value in seconds.
44 * @return string|false Formatted time string or false if invalid input
45 */
46 public static function pretty_time( $time ) {
47 if ( is_numeric( $time ) ) {
48 $value = array(
49 'years' => '00',
50 'days' => '00',
51 'hours' => '',
52 'minutes' => '',
53 'seconds' => '',
54 );
55 $attach_hours = '';
56 $attach_min = '';
57 $attach_sec = '';
58 $time = floor( $time );
59
60 if ( $time >= 31556926 ) {
61 $value['years'] = floor( $time / 31556926 );
62 $time = ( $time % 31556926 );
63 }
64 if ( $time >= 86400 ) {
65 $value['days'] = floor( $time / 86400 );
66 $time = ( $time % 86400 );
67 }
68 if ( $time >= 3600 ) {
69 $value['hours'] = str_pad( (string) floor( $time / 3600 ), 1, '0', STR_PAD_LEFT );
70 $time = ( $time % 3600 );
71 }
72 if ( $time >= 60 ) {
73 $value['minutes'] = str_pad( (string) floor( $time / 60 ), 1, '0', STR_PAD_LEFT );
74 $time = ( $time % 60 );
75 }
76 $value['seconds'] = str_pad( (string) floor( $time ), 1, '0', STR_PAD_LEFT );
77
78 if ( '' !== $value['hours'] ) {
79 $attach_hours = '<span class="analytify_xl_f">' . _x( 'h', 'Hour Time', 'wp-analytify' ) . ' </span> ';
80 }
81 if ( '' !== $value['minutes'] ) {
82 $attach_min = '<span class="analytify_xl_f">' . _x( 'm', 'Minute Time', 'wp-analytify' ) . ' </span>';
83 }
84 if ( '' !== $value['seconds'] ) {
85 $attach_sec = '<span class="analytify_xl_f">' . _x( 's', 'Second Time', 'wp-analytify' ) . '</span>';
86 }
87
88 return $value['hours'] . $attach_hours . $value['minutes'] . $attach_min . $value['seconds'] . $attach_sec;
89 }
90 return false;
91 }
92
93 /**
94 * Format numbers with K suffix for large values
95 *
96 * Converts large numbers to a more readable format by adding
97 * 'k' suffix for values over 10,000 (e.g., 15,000 becomes 15k).
98 *
99 * @param int|float $num Number to format.
100 * @return string Formatted number
101 */
102 public static function pretty_numbers( $num ) {
103 if ( ! is_numeric( $num ) ) {
104 return $num;
105 }
106 return ( $num > 10000 ) ? round( ( $num / 1000 ), 2 ) . 'k' : number_format( $num );
107 }
108
109 /**
110 * Convert fraction to percentage
111 *
112 * Converts a decimal fraction to a percentage and formats it
113 * using the pretty_numbers method for consistency.
114 *
115 * @param float $number Fraction to convert (0.0 to 1.0).
116 * @return string Formatted percentage
117 */
118 public static function fraction_to_percentage( $number ) {
119 return self::pretty_numbers( $number * 100 );
120 }
121
122 /**
123 * Get appropriate delimiter for REST API URLs
124 *
125 * Determines whether to use '?' or '&' as a delimiter based on
126 * whether the REST API base URL already contains query parameters.
127 *
128 * @return string Appropriate delimiter character
129 */
130 public static function get_delimiter() {
131 $rest_url = esc_url_raw( get_rest_url() );
132 return strpos( $rest_url, '/wp-json/' ) !== false ? '?' : '&';
133 }
134
135 /**
136 * Check if analytics tracking is available
137 *
138 * Determines whether analytics tracking should be enabled based on
139 * user roles, GDPR compliance, authentication status, and settings.
140 *
141 * @param bool $only_auth Whether to only check authentication.
142 * @return bool True if tracking is available, false otherwise
143 */
144 public static function is_tracking_available( $only_auth = false ) {
145 global $current_user;
146 $roles = $current_user->roles;
147
148 // Check if user role is excluded from tracking.
149 if ( isset( $roles[0] ) && in_array( $roles[0], $GLOBALS['WP_ANALYTIFY']->settings->get_option( 'exclude_users_tracking', 'wp-analytify-profile', array() ), true ) ) {
150 return false;
151 }
152
153 // Check GDPR compliance blocking.
154 if ( Class_Analytify_GDPR_Compliance::is_gdpr_compliance_blocking() ) {
155 return false;
156 }
157
158 // Check authentication and settings.
159 if ( get_option( 'pa_google_token' ) ) {
160 if ( 'on' === $GLOBALS['WP_ANALYTIFY']->settings->get_option( 'install_ga_code', 'wp-analytify-profile', 'off' ) && WP_ANALYTIFY_FUNCTIONS::get_UA_code() ) {
161 return true;
162 }
163 } elseif ( ! $only_auth && $GLOBALS['WP_ANALYTIFY']->settings->get_option( 'manual_ua_code', 'wp-analytify-authentication', false ) ) {
164 return true;
165 }
166
167 return false;
168 }
169
170 /**
171 * Check if current page uses Gutenberg editor
172 *
173 * Determines whether the current page is using the Gutenberg block
174 * editor by checking multiple methods for compatibility.
175 *
176 * @return bool True if using Gutenberg, false otherwise
177 */
178 public static function is_gutenberg_editor() {
179 if ( function_exists( 'is_gutenberg_page' ) && is_gutenberg_page() ) {
180 return true;
181 }
182 $current_screen = get_current_screen();
183 if ( $current_screen && method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
184 return true;
185 }
186 return false;
187 }
188
189 /**
190 * Get current admin post type
191 *
192 * Retrieves the post type of the current admin page by checking
193 * multiple sources in order of preference.
194 *
195 * @return string|null Post type or null if not found
196 */
197 public static function get_current_admin_post_type() {
198 global $post, $typenow, $current_screen;
199
200 if ( $post && $post->post_type ) {
201 return $post->post_type;
202 } elseif ( $typenow ) {
203 return $typenow;
204 } elseif ( $current_screen && is_object( $current_screen ) && isset( $current_screen->post_type ) ) {
205 return $current_screen->post_type;
206 } elseif ( isset( $_REQUEST['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading URL parameter for display purposes
207 return sanitize_key( $_REQUEST['post_type'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading URL parameter for display purposes
208 }
209 return null;
210 }
211
212 /**
213 * Get option value with fallback
214 *
215 * Retrieves a specific option value from a section with a default
216 * fallback value if the option is not set.
217 *
218 * @param string $option Option name to retrieve.
219 * @param string $section Section name containing the option.
220 * @param mixed $default Default value if option not found.
221 * @return mixed Option value or default
222 */
223 public static function get_option( $option, $section, $default = '' ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.defaultFound -- Default parameter name is acceptable
224 $options = get_option( $section );
225 if ( isset( $options[ $option ] ) ) {
226 return $options[ $option ];
227 }
228 return $default;
229 }
230
231 /**
232 * Update option value.
233 *
234 * Updates a specific option value in a section.
235 *
236 * @param mixed $option Option name to update.
237 * @param mixed $section Section name containing the option.
238 * @param mixed $value New value for the option.
239 * @return bool True if option was updated successfully.
240 */
241 public static function update_option( $option, $section, $value ) {
242 $options = (array) get_option( $section );
243 $options[ $option ] = $value;
244 return update_option( $section, $options );
245 }
246
247 /**
248 * Add GA4 exception
249 *
250 * Stores an exception for a specific GA4 type, including reason and message.
251 *
252 * @param mixed $type Exception type (e.g., 'mp_secret_exception', 'create_stream_exception').
253 * @param mixed $reason Reason for the exception.
254 * @param mixed $message Detailed message for the exception.
255 * @return void
256 */
257 public static function add_ga4_exception( $type, $reason, $message ) {
258 $analytify_ga4_exceptions = (array) get_option( 'analytify_ga4_exceptions' );
259 $analytify_ga4_exceptions[ $type ]['reason'] = $reason;
260 $analytify_ga4_exceptions[ $type ]['message'] = $message;
261 update_option( 'analytify_ga4_exceptions', $analytify_ga4_exceptions );
262 }
263
264 /**
265 * Remove GA4 exception
266 *
267 * Removes an exception for a specific GA4 type.
268 *
269 * @param mixed $type Exception type to remove.
270 * @return void
271 */
272 public static function remove_ga4_exception( $type ) {
273 $analytify_ga4_exceptions = (array) get_option( 'analytify_ga4_exceptions' );
274 unset( $analytify_ga4_exceptions[ $type ] );
275 update_option( 'analytify_ga4_exceptions', $analytify_ga4_exceptions );
276 }
277
278 // Additional core helpers moved from WPANALYTIFY_Utils.
279 /**
280 * Remove WordPress plugin directory path
281 *
282 * Removes the WordPress plugin directory path from a plugin file path
283 * to get a relative path for easier handling.
284 *
285 * @param string $name Full path to the plugin file.
286 * @return string Relative path
287 */
288 public static function remove_wp_plugin_dir( $name ) {
289 $plugin = str_replace( WP_PLUGIN_DIR, '', $name );
290 return substr( $plugin, 1 );
291 }
292
293 /**
294 * Safely get and sanitize GET parameter
295 *
296 * Eliminates DRY violations for the common pattern:
297 * sanitize_text_field( wp_unslash( $_GET['param'] ) )
298 *
299 * @param string $param Parameter name.
300 * @param string $default Default value if parameter not set.
301 * @return string Sanitized parameter value or default
302 */
303 public static function safe_get_param( $param, $default = '' ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.defaultFound -- Default parameter name is acceptable
304 if ( ! isset( $_GET[ $param ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is handled by caller
305 return $default;
306 }
307 return sanitize_text_field( wp_unslash( $_GET[ $param ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is handled by caller
308 }
309
310 /**
311 * Safely get and sanitize POST parameter
312 *
313 * Eliminates DRY violations for the common pattern:
314 * sanitize_text_field( wp_unslash( $_POST['param'] ) )
315 *
316 * @param string $param Parameter name.
317 * @param string $default Default value if parameter not set.
318 * @return string Sanitized parameter value or default
319 */
320 public static function safe_post_param( $param, $default = '' ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.defaultFound -- Default parameter name is acceptable
321 if ( ! isset( $_POST[ $param ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification is handled by caller
322 return $default;
323 }
324 return sanitize_text_field( wp_unslash( $_POST[ $param ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification is handled by caller
325 }
326
327 /**
328 * Check if current page matches a specific pattern
329 *
330 * Eliminates DRY violations for the common pattern:
331 * isset( $_GET['page'] ) && strpos( $_GET['page'], 'analytify-*' ) === 0
332 *
333 * @param string $pattern Page pattern to check (e.g., 'analytify-settings', 'analytify-dashboard').
334 * @return bool True if current page matches pattern
335 */
336 public static function is_current_page( $pattern ) {
337 $current_page = self::safe_get_param( 'page' );
338 return $current_page && strpos( $current_page, $pattern ) === 0;
339 }
340
341 /**
342 * Safely verify nonce with proper sanitization
343 *
344 * Eliminates DRY violations for the common pattern:
345 * wp_verify_nonce( sanitize_key( wp_unslash( $_GET['_wpnonce'] ) ), 'nonce-name' )
346 *
347 * @param string $nonce_key Nonce key to verify.
348 * @param string $action Action name for nonce verification.
349 * @param string $method Request method ('GET' or 'POST').
350 * @return bool True if nonce is valid
351 */
352 public static function safe_verify_nonce( $nonce_key, $action, $method = 'GET' ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification is handled by this method
353 $super_global = ( 'POST' === $method ) ? $_POST : $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Nonce verification is handled by this method
354
355 if ( ! isset( $super_global[ $nonce_key ] ) ) {
356 return false;
357 }
358
359 $nonce_value = sanitize_key( wp_unslash( $super_global[ $nonce_key ] ) );
360 return (bool) wp_verify_nonce( $nonce_value, $action );
361 }
362
363 /**
364 * Calculate date difference
365 *
366 * Calculates the difference between two dates and returns an array
367 * containing start date, end date, and the number of days difference.
368 *
369 * @param string $start_date Start date.
370 * @param string $end_date End date.
371 * @return array<string, mixed> Array of dates and difference
372 */
373 public static function calculate_date_diff( $start_date, $end_date ) {
374 $start_date_obj = date_create( $start_date );
375 $end_date_obj = date_create( $end_date );
376
377 if ( ! $start_date_obj || ! $end_date_obj ) {
378 return array(
379 'start_date' => $start_date,
380 'end_date' => $end_date,
381 'diff_days' => 0,
382 );
383 }
384
385 $diff = date_diff( $end_date_obj, $start_date_obj );
386 $compare_start_date = gmdate( 'Y-m-d', strtotime( $start_date . $diff->format( ' %R%a days' ) ) ? strtotime( $start_date . $diff->format( ' %R%a days' ) ) : time() );
387 $compare_end_date = $start_date;
388 $diff_days = $diff->format( '%a' );
389 return array(
390 'start_date' => $compare_start_date,
391 'end_date' => $compare_end_date,
392 'diff_days' => (string) $diff_days,
393 );
394 }
395
396 /**
397 * Print settings array.
398 *
399 * Prints an array of settings in a formatted JSON structure.
400 *
401 * @param array<string, mixed> $settings_array Array of settings to print.
402 * @return void
403 */
404 public static function print_settings_array( $settings_array ) {
405 if ( is_array( $settings_array ) ) {
406 foreach ( $settings_array as $key => $value ) {
407 if ( is_array( $value ) ) {
408 printf( "\n-- %s --\n", esc_html( $key ) );
409 // wp_json_encode outputs safe JSON text - don't escape it for textarea display.
410 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON output is safe
411 echo wp_json_encode( $value, JSON_PRETTY_PRINT ) . "\n";
412 } else {
413 printf( "%s: %s\n", esc_html( $key ), esc_html( $value ) );
414 }
415 }
416 }
417 }
418
419 /**
420 * Get addons to update.
421 *
422 * Determines which addons need to be updated based on their versions.
423 *
424 * @return array<string, mixed> Array of addon names that need updating.
425 */
426 public static function get_addons_to_upgmdate() {
427 $addons_to_update = array();
428 if ( defined( 'ANALYTIFY_PRO_VERSION' ) && -1 === version_compare( ANALYTIFY_PRO_VERSION, '5.0.0' ) ) {
429 $addons_to_update[] = 'Analytify Pro';
430 }
431 if ( defined( 'ANALTYIFY_WOOCOMMERCE_VERSION' ) && -1 === version_compare( ANALTYIFY_WOOCOMMERCE_VERSION, '5.0.0' ) ) {
432 $addons_to_update[] = 'Analytify - WooCommerce Tracking';
433 }
434 if ( defined( 'ANALTYIFY_AUTHORS_DASHBORD_VERSION' ) && -1 === version_compare( ANALTYIFY_AUTHORS_DASHBORD_VERSION, '3.0.0' ) ) {
435 $addons_to_update[] = 'Analytify - Authors Tracking';
436 }
437 if ( defined( 'ANALYTIFY_FORMS_VERSION' ) && -1 === version_compare( ANALYTIFY_FORMS_VERSION, '3.0.0' ) ) {
438 $addons_to_update[] = 'Analytify - Forms Tracking';
439 }
440 if ( defined( 'ANALTYIFY_CAMPAIGNS_VERSION' ) && -1 === version_compare( ANALTYIFY_CAMPAIGNS_VERSION, '3.0.0' ) ) {
441 $addons_to_update[] = 'Analytify - UTM Campaigns Tracking';
442 }
443 if ( defined( 'ANALTYIFY_EMAIL_VERSION' ) && -1 === version_compare( ANALTYIFY_EMAIL_VERSION, '3.0.0' ) ) {
444 $addons_to_update[] = 'Analytify - Email Notifications';
445 }
446 if ( defined( 'ANALYTIFY_DASHBOARD_VERSION' ) && -1 === version_compare( ANALYTIFY_DASHBOARD_VERSION, '3.0.0' ) ) {
447 $addons_to_update[] = 'Analytify - Google Analytics Dashboard Widget';
448 }
449 if ( class_exists( 'WP_Analytify_Edd' ) ) {
450 $all_plugins = get_plugins();
451 if ( isset( $all_plugins['wp-analytify-edd/wp-analytify-edd.php']['Version'] ) && -1 === version_compare( $all_plugins['wp-analytify-edd/wp-analytify-edd.php']['Version'], '3.0.0' ) ) {
452 $addons_to_update[] = 'Analytify - Easy Digital Downloads Tracking';
453 }
454 }
455 // Convert to associative array with addon names as keys.
456 $result = array();
457 foreach ( $addons_to_update as $addon ) {
458 $result[ $addon ] = $addon;
459 }
460 return $result;
461 }
462
463 /**
464 * Returns row limit shared by dashboard tables and CSV exports.
465 *
466 * Uses the same filter hook and context as the frontend so custom
467 * limit filters apply consistently to both views.
468 *
469 * @since 9.1.1
470 *
471 * @param string $filter_name Filter hook name.
472 * @param int $default_limit Default row limit.
473 * @param mixed ...$args Additional filter arguments after context.
474 * @return int
475 */
476 public static function wp_analytify_get_api_limit( $filter_name, $default_limit, ...$args ) {
477 $context = 'dashboard';
478
479 // Optional explicit context: 'dashboard' | 'csv_export'. Other first extras
480 // (e.g. 'WC', post ID) stay as additional filter arguments.
481 if ( isset( $args[0] ) && is_string( $args[0] )
482 && in_array( $args[0], array( 'dashboard', 'csv_export' ), true ) ) {
483 $context = array_shift( $args );
484 }
485
486 $limit = apply_filters( $filter_name, $default_limit, $context, ...$args );
487
488 return max( 0, (int) $limit );
489 }
490 }
491