File: //proc/self/cwd/wp-content/plugins/wp-recipe-maker/includes/public/class-wprm-analytics.php
<?php
/**
* Responsible for the recipe analytics.
*
* @link https://bootstrapped.ventures
* @since 6.5.0
*
* @package WP_Recipe_Maker
* @subpackage WP_Recipe_Maker/includes/public
*/
/**
* Responsible for the recipe analytics.
*
* @since 6.5.0
* @package WP_Recipe_Maker
* @subpackage WP_Recipe_Maker/includes/public
* @author Brecht Vandersmissen <brecht@bootstrapped.ventures>
*/
class WPRM_Analytics {
/**
* Register actions and filters.
*
* @since 6.7.0
*/
public static function init() {
add_filter( 'wprm_settings_update', array( __CLASS__, 'check_hh_token_on_settings_update' ), 10, 2 );
add_action( 'wprm_daily_cron', array( __CLASS__, 'remove_old_actions' ) );
}
/**
* Returns an array of available action types for recipe analytics.
*
* @since 9.8.0
* @return array Array of action types.
*/
public static function get_types() {
return array(
'print' => __( 'Print', 'wp-recipe-maker' ),
'equipment-link' => __( 'Equipment Link', 'wp-recipe-maker' ),
'ingredient-link' => __( 'Ingredient Link', 'wp-recipe-maker' ),
'instruction-link' => __( 'Instruction Link', 'wp-recipe-maker' ),
'adjust-servings' => __( 'Adjust Servings', 'wp-recipe-maker' ),
'unit-conversion' => __( 'Unit Conversion', 'wp-recipe-maker' ),
'comment' => __( 'Comment', 'wp-recipe-maker' ),
'user-rating' => __( 'User Rating', 'wp-recipe-maker' ),
'jump-to-recipe' => __( 'Jump to Recipe', 'wp-recipe-maker' ),
'jump-to-video' => __( 'Jump to Video', 'wp-recipe-maker' ),
'pin-button' => __( 'Pin Button', 'wp-recipe-maker' ),
'facebook-share-button' => __( 'Facebook Share', 'wp-recipe-maker' ),
'messenger-share-button' => __( 'Messenger Share', 'wp-recipe-maker' ),
'twitter-share-button' => __( 'Twitter Share', 'wp-recipe-maker' ),
'bluesky-share-button' => __( 'Bluesky Share', 'wp-recipe-maker' ),
'mastodon-share-button' => __( 'Mastodon Share', 'wp-recipe-maker' ),
'text-share-button' => __( 'Text Share', 'wp-recipe-maker' ),
'whatsapp-share-button' => __( 'WhatsApp Share', 'wp-recipe-maker' ),
'email-share-button' => __( 'Email Share', 'wp-recipe-maker' ),
'add-to-collections-button' => __( 'Add to Recipe Collections', 'wp-recipe-maker' ),
'add-to-shopping-list-button' => __( 'Add to Quick Access Shopping List', 'wp-recipe-maker' ),
);
}
/**
* Check if the Honey & Home integration is enabled and token has changed.
*
* @since 6.7.0
* @param array $new_settings Settings after update.
* @param array $old_settings Settings before update.
*/
public static function check_hh_token_on_settings_update( $new_settings, $old_settings ) {
if ( isset( $new_settings['honey_home_integration'] ) && $new_settings['honey_home_integration'] ) {
$old_token = isset( $old_settings['honey_home_token'] ) ? $old_settings['honey_home_token'] : '';
$new_token = isset( $new_settings['honey_home_token'] ) ? $new_settings['honey_home_token'] : '';
if ( $new_token !== $old_token ) {
// Activate new token.
if ( $new_token ) {
$domain = isset( $_SERVER['SERVER_NAME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) ) : '';
$response = wp_remote_get( 'https://dailygrub.com/api/verifyDomain?id=' . urlencode( $new_token ) . '&domain=' . urlencode( $domain ) );
$status = false;
if ( ! is_wp_error( $response ) ) {
$status = (array) json_decode( wp_remote_retrieve_body( $response ) );
}
update_option( 'hh_integration_status', $status, false );
if ( ! $status || ! isset( $status['success'] ) ) {
$status = array(
'success' => false,
'message' => 'Unknown error',
);
}
if ( ! $status['success'] ) {
$new_settings['honey_home_token'] = '';
}
}
}
}
return $new_settings;
}
/**
* Register a specific action.
*
* @since 6.5.0
* @param int $recipe_id Recipe to register the action for.
* @param int $post_id Post to register the action for.
* @param string $type Type of action to register.
* @param mixed $meta Meta for the action.
* @param string $uid Visitor UID.
*/
public static function register_action( $recipe_id, $post_id, $type, $meta = array(), $uid = '' ) {
if ( WPRM_Settings::get( 'analytics_enabled' ) ) {
// Respect "Do Not Track" request.
if ( isset( $_SERVER['HTTP_DNT'] ) && 1 == $_SERVER['HTTP_DNT'] ) {
return;
}
// Check if this action is excluded from being tracked.
$exclude_actions = WPRM_Settings::get( 'analytics_exclude_actions' );
$exclude_actions = preg_split( "/\r\n|\n|\r/", $exclude_actions );
if ( in_array( $type, $exclude_actions ) ) {
return;
}
// Add/sanitize meta.
$sanitized_meta = array();
switch ( $type ) {
case 'print':
$sanitized_meta['location'] = isset( $meta['location'] ) ? sanitize_key( $meta['location'] ) : 'unknown';
break;
case 'equipment-link':
$sanitized_meta['type'] = isset( $meta['type'] ) ? sanitize_key( $meta['type'] ) : 'unknown';
case 'ingredient-link':
$sanitized_meta['name'] = isset( $meta['name'] ) ? sanitize_text_field( $meta['name'] ) : 'unknown';
case 'instruction-link':
$sanitized_meta['url'] = isset( $meta['url'] ) && ! is_array( $meta['url'] ) ? $meta['url'] : 'unknown';
break;
case 'adjust-servings':
case 'unit-conversion':
$sanitized_meta['type'] = isset( $meta['type'] ) ? sanitize_key( $meta['type'] ) : 'unknown';
break;
case 'comment':
$sanitized_meta['comment_id'] = isset( $meta['comment_id'] ) ? intval( $meta['comment_id'] ) : 'unknown';
case 'user-rating':
$sanitized_meta['rating'] = isset( $meta['rating'] ) ? intval( $meta['rating'] ) : 'unknown';
break;
case 'generate-shopping-list':
$sanitized_meta['collection'] = isset( $meta['collection'] ) ? sanitize_text_field( $meta['collection'] ) : 'unknown';
break;
}
// Visitor UID.
$visitor_id = $uid ? sanitize_key( $uid ) : self::get_visitor_id();
$visitor = self::get_visitor();
// Should exclude this IP?
if ( isset( $visitor['ip'] ) && self::should_exclude_ip( $visitor['ip'] ) ) {
return;
}
// Construct and register action.
$action = array(
'recipe_id' => $recipe_id,
'post_id' => $post_id,
'type' => $type,
'meta' => $sanitized_meta,
'visitor_id' => $visitor_id,
'visitor' => $visitor,
);
WPRM_Analytics_Database::add( $action );
}
}
/**
* Get visitor ID.
*
* @since 6.5.0
*/
public static function get_visitor_id() {
$visitor_id = isset( $_COOKIE[ 'wprm_analytics_visitor' ] ) ? sanitize_key( $_COOKIE[ 'wprm_analytics_visitor' ] ) : false;
if ( ! $visitor_id ) {
$visitor_id = self::set_visitor_id();
}
return $visitor_id;
}
/**
* Set the visitor ID.
*
* @since 6.5.0
*/
public static function set_visitor_id() {
$visitor_id = uniqid( '', true );
setcookie( 'wprm_analytics_visitor', $visitor_id, 2147483647, '/' );
return $visitor_id;
}
/**
* Get visitor information.
*
* @since 6.5.0
*/
public static function get_visitor() {
$visitor = array(
'ip' => self::get_user_ip(),
);
if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
$visitor['user_agent'] = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) );
}
return $visitor;
}
/**
* Get the IP address of the current user.
* Source: http://stackoverflow.com/questions/6717926/function-to-get-user-ip-address
*
* @since 6.5.0
*/
public static function get_user_ip() {
foreach ( array( 'REMOTE_ADDR', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED' ) as $key ) {
if ( array_key_exists( $key, $_SERVER ) === true ) {
$server_value = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) );
foreach ( array_map( 'trim', explode( ',', $server_value ) ) as $ip ) {
if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
return $ip;
}
}
}
}
return 'unknown';
}
/**
* Check if a specific IP address should be excluded.
*
* @since 7.1.0
* @param string $ip IP address to check.
*/
public static function should_exclude_ip( $ip ) {
// IP addresses to exclude.
$exclude_ips_raw = WPRM_Settings::get( 'analytics_exclude_ips' );
$exclude_ips_raw = preg_split( "/\r\n|\n|\r/", $exclude_ips_raw );
$exclude_ips = array();
$exclude_ip_ranges = array();
foreach ( $exclude_ips_raw as $exclude_ip ) {
if ( strpos( $exclude_ip, '-' ) ) {
$range_ips = explode( '-', $exclude_ip );
if ( 2 === count( $range_ips ) ) {
$from = ip2long( trim( $range_ips[0] ) );
$to = ip2long( trim( $range_ips[1] ) );
if ( $from && $to && $from <= $to ) {
$exclude_ip_ranges[] = array(
'from' => $from,
'to' => $to,
);
}
}
} else {
$exclude_ips[] = trim( $exclude_ip );
}
}
// Check if should be excluded.
if ( in_array( $ip, $exclude_ips ) ) {
return true;
} else {
$ip_long = ip2long( $ip );
if ( $ip_long ) {
foreach ( $exclude_ip_ranges as $range ) {
if ( $ip_long >= $range['from'] && $ip_long <= $range['to'] ) {
return true;
}
}
}
}
return false;
}
/**
* Get optional meta that was set in the frontend.
*
* @since 6.5.0
*/
public static function get_frontend_meta() {
$cookie = isset( $_COOKIE[ 'wprm_analytics_meta' ] ) ? sanitize_text_field( wp_unslash( $_COOKIE[ 'wprm_analytics_meta' ] ) ) : false;
if ( $cookie ) {
$decoded = json_decode( stripslashes( $cookie ), true );
if ( $decoded ) {
return $decoded;
}
}
return array();
}
/**
* Remove old actions.
*
* @since 7.4.0
*/
public static function remove_old_actions() {
if ( WPRM_Settings::get( 'analytics_enabled' ) ) {
$days_to_keep = WPRM_Settings::get( 'analytics_days_to_keep' );
if ( 'unlimited' !== $days_to_keep ) {
$days_to_keep = intval( $days_to_keep );
$days_to_keep = 0 < $days_to_keep ? $days_to_keep : 90;
WPRM_Analytics_Database::purge( $days_to_keep );
}
}
}
/**
* Get analytics data for the dashboard charts.
*
* @since 7.4.0
*/
public static function get_dashboard_chart_data() {
$total_actions = 0;
$per_day = array();
$per_type = array();
$per_recipe = array();
$date_format = 'M j';
for ( $i = 7; $i >= 0; $i-- ) {
$datetime = new DateTime( $i . ' days ago' );
$date_display = $datetime->format( $date_format );
$date_database = $datetime->format( 'Y-m-d' );
$per_day_date = array(
'date' => $date_display,
'total' => 0,
);
$actions = WPRM_Analytics_Database::get_aggregated_actions_for( $date_database );
foreach ( $actions as $action ) {
$total = intval( $action->total );
$unique = intval( $action->total_unique );
// Per day.
$per_day_date[ 'total' ] += $total;
$total_actions += $total;
// Per action type.
if ( $action->type ) {
if ( ! isset( $per_type[ $action->type ] ) ) {
$per_type[ $action->type ] = array(
'total' => 0,
'unique' => 0,
);
}
$per_type[ $action->type ]['total'] += $total;
$per_type[ $action->type ]['unique'] += $unique;
}
// Per recipe.
$recipe_id = intval( $action->recipe_id );
if ( $recipe_id ) {
if ( ! isset( $per_recipe[ '' . $recipe_id ] ) ) {
$per_recipe[ '' . $recipe_id ] = array(
'total' => 0,
'unique' => 0,
);
}
$per_recipe[ '' . $recipe_id ]['total'] += $total;
$per_recipe[ '' . $recipe_id ]['unique'] += $unique;
}
}
$per_day[] = $per_day_date;
}
// Sort by total interactions.
uasort( $per_type, function($a, $b) {
if ( $a['total'] == $b['total'] ) {
return 0;
}
return $a['total'] > $b['total'] ? -1 : 1;
} );
uasort( $per_recipe, function($a, $b) {
if ( $a['total'] == $b['total'] ) {
return 0;
}
return $a['total'] > $b['total'] ? -1 : 1;
} );
// Get names for top types.
$top_types = array();
$types = self::get_types();
foreach ( $per_type as $type => $interactions ) {
if ( array_key_exists( $type, $types ) ) {
$top_types[] = array(
'id' => $type,
'name' => $types[ $type ],
'total' => $interactions['total'],
'unique' => $interactions['unique'],
);
}
}
// Get names for top recipes.
$top_recipes = array();
foreach ( $per_recipe as $recipe_id => $interactions ) {
$recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id );
if ( $recipe ) {
$top_recipes[] = array(
'id' => $recipe->id(),
'recipeId' => $recipe->id(),
'permalink' => $recipe->permalink(),
'name' => $recipe->name(),
'total' => $interactions['total'],
'unique' => $interactions['unique'],
);
}
// Stop after 5 recipes.
if ( 5 === count( $top_recipes ) ) {
break;
}
}
return array(
'total' => $total_actions,
'per_day' => $per_day,
'per_type' => $top_types,
'per_recipe' => $top_recipes,
);
}
}
WPRM_Analytics::init();