Update repo
This commit is contained in:
@@ -1,95 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* CRUD functions for Activities.
|
||||
*
|
||||
* @package WordPress Activity Stats
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves or updates an activity in the database.
|
||||
*
|
||||
* @param array $data Associative array of data to save.
|
||||
* @param int $activity_id The ID of the activity to update. 0 for new activity.
|
||||
* @return int|false The ID of the saved/updated activity, or false on failure.
|
||||
*/
|
||||
function statpress_save_activity_data( array $data, int $activity_id = 0 ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_activities = $wpdb->prefix . 'statpress_activities';
|
||||
|
||||
// Helper to convert empty strings to NULL for the database.
|
||||
$null_if_empty = function( $value ) {
|
||||
// Also check for 0 as we don't want to nullify it for numeric fields.
|
||||
return ( '' !== $value && ! is_null( $value ) ) ? $value : null;
|
||||
};
|
||||
|
||||
// Sanitize and prepare data.
|
||||
$prepared_data = array(
|
||||
'category_id' => isset( $data['category_id'] ) ? intval( $data['category_id'] ) : null,
|
||||
'date' => isset( $data['date'] ) ? sanitize_text_field( $data['date'] ) : current_time( 'Y-m-d' ),
|
||||
'title' => isset( $data['title'] ) ? sanitize_text_field( $data['title'] ) : '',
|
||||
'distance' => isset( $data['distance'] ) ? floatval( str_replace( ',', '.', $data['distance'] ) ) : 0,
|
||||
'duration' => isset( $data['duration'] ) ? sanitize_text_field( $data['duration'] ) : '00:00:00',
|
||||
'calories' => isset( $data['calories'] ) ? intval( $data['calories'] ) : null,
|
||||
'comment' => isset( $data['comment'] ) ? sanitize_textarea_field( $data['comment'] ) : null,
|
||||
'strava_url' => isset( $data['strava_url'] ) ? $null_if_empty( esc_url_raw( $data['strava_url'] ) ) : null,
|
||||
'avg_heart_rate' => isset( $data['avg_heart_rate'] ) ? $null_if_empty( intval( $data['avg_heart_rate'] ) ) : null,
|
||||
'max_heart_rate' => isset( $data['max_heart_rate'] ) ? $null_if_empty( intval( $data['max_heart_rate'] ) ) : null,
|
||||
'avg_speed' => isset( $data['avg_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $data['avg_speed'] ) ) ) : null,
|
||||
'max_speed' => isset( $data['max_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $data['max_speed'] ) ) ) : null,
|
||||
'avg_cadence' => isset( $data['avg_cadence'] ) ? $null_if_empty( intval( $data['avg_cadence'] ) ) : null,
|
||||
'max_cadence' => isset( $data['max_cadence'] ) ? $null_if_empty( intval( $data['max_cadence'] ) ) : null,
|
||||
'total_elevation_gain' => isset( $data['total_elevation_gain'] ) ? $null_if_empty( intval( $data['total_elevation_gain'] ) ) : null,
|
||||
'total_elevation_loss' => isset( $data['total_elevation_loss'] ) ? $null_if_empty( intval( $data['total_elevation_loss'] ) ) : null,
|
||||
'min_altitude' => isset( $data['min_altitude'] ) ? $null_if_empty( intval( $data['min_altitude'] ) ) : null,
|
||||
'max_altitude' => isset( $data['max_altitude'] ) ? $null_if_empty( intval( $data['max_altitude'] ) ) : null,
|
||||
'equipment_id' => isset( $data['equipment_id'] ) ? $null_if_empty( intval( $data['equipment_id'] ) ) : null,
|
||||
'gpx_url' => isset( $data['gpx_url'] ) ? $null_if_empty( esc_url_raw( $data['gpx_url'] ) ) : null,
|
||||
'event_type_id' => isset( $data['event_type_id'] ) ? $null_if_empty( intval( $data['event_type_id'] ) ) : null,
|
||||
);
|
||||
|
||||
// Data formats for $wpdb.
|
||||
$format = array(
|
||||
'%d', // category_id
|
||||
'%s', // date
|
||||
'%s', // title
|
||||
'%f', // distance
|
||||
'%s', // duration
|
||||
'%d', // calories
|
||||
'%s', // comment
|
||||
'%s', // strava_url
|
||||
'%d', // avg_heart_rate
|
||||
'%d', // max_heart_rate
|
||||
'%f', // avg_speed
|
||||
'%f', // max_speed
|
||||
'%d', // avg_cadence
|
||||
'%d', // max_cadence
|
||||
'%d', // total_elevation_gain
|
||||
'%d', // total_elevation_loss
|
||||
'%d', // min_altitude
|
||||
'%d', // max_altitude
|
||||
'%d', // equipment_id
|
||||
'%s', // gpx_url
|
||||
'%d', // event_type_id
|
||||
);
|
||||
|
||||
if ( $activity_id > 0 ) {
|
||||
// UPDATE
|
||||
$result = $wpdb->update( $table_activities, $prepared_data, array( 'id' => $activity_id ), $format, array( '%d' ) );
|
||||
if ( false !== $result ) {
|
||||
return $activity_id;
|
||||
}
|
||||
} else {
|
||||
// INSERT
|
||||
$result = $wpdb->insert( $table_activities, $prepared_data, $format );
|
||||
if ( $result ) {
|
||||
return $wpdb->insert_id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
<?php
|
||||
/**
|
||||
* CRUD functions for Activities.
|
||||
*
|
||||
* @package WordPress Activity Stats
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly.
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves or updates an activity in the database.
|
||||
*
|
||||
* @param array $data Associative array of data to save.
|
||||
* @param int $activity_id The ID of the activity to update. 0 for new activity.
|
||||
* @return int|false The ID of the saved/updated activity, or false on failure.
|
||||
*/
|
||||
function statpress_save_activity_data( array $data, int $activity_id = 0 ) {
|
||||
global $wpdb;
|
||||
|
||||
$table_activities = $wpdb->prefix . 'statpress_activities';
|
||||
|
||||
// Helper to convert empty strings to NULL for the database.
|
||||
$null_if_empty = function( $value ) {
|
||||
// Also check for 0 as we don't want to nullify it for numeric fields.
|
||||
return ( '' !== $value && ! is_null( $value ) ) ? $value : null;
|
||||
};
|
||||
|
||||
// Sanitize and prepare data.
|
||||
$prepared_data = array(
|
||||
'category_id' => isset( $data['category_id'] ) ? intval( $data['category_id'] ) : null,
|
||||
'date' => isset( $data['date'] ) ? sanitize_text_field( $data['date'] ) : current_time( 'Y-m-d' ),
|
||||
'title' => isset( $data['title'] ) ? sanitize_text_field( $data['title'] ) : '',
|
||||
'distance' => isset( $data['distance'] ) ? floatval( str_replace( ',', '.', $data['distance'] ) ) : 0,
|
||||
'duration' => isset( $data['duration'] ) ? sanitize_text_field( $data['duration'] ) : '00:00:00',
|
||||
'calories' => isset( $data['calories'] ) ? intval( $data['calories'] ) : null,
|
||||
'comment' => isset( $data['comment'] ) ? sanitize_textarea_field( $data['comment'] ) : null,
|
||||
'strava_url' => isset( $data['strava_url'] ) ? $null_if_empty( esc_url_raw( $data['strava_url'] ) ) : null,
|
||||
'avg_heart_rate' => isset( $data['avg_heart_rate'] ) ? $null_if_empty( intval( $data['avg_heart_rate'] ) ) : null,
|
||||
'max_heart_rate' => isset( $data['max_heart_rate'] ) ? $null_if_empty( intval( $data['max_heart_rate'] ) ) : null,
|
||||
'avg_speed' => isset( $data['avg_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $data['avg_speed'] ) ) ) : null,
|
||||
'max_speed' => isset( $data['max_speed'] ) ? $null_if_empty( floatval( str_replace( ',', '.', $data['max_speed'] ) ) ) : null,
|
||||
'avg_cadence' => isset( $data['avg_cadence'] ) ? $null_if_empty( intval( $data['avg_cadence'] ) ) : null,
|
||||
'max_cadence' => isset( $data['max_cadence'] ) ? $null_if_empty( intval( $data['max_cadence'] ) ) : null,
|
||||
'total_elevation_gain' => isset( $data['total_elevation_gain'] ) ? $null_if_empty( intval( $data['total_elevation_gain'] ) ) : null,
|
||||
'total_elevation_loss' => isset( $data['total_elevation_loss'] ) ? $null_if_empty( intval( $data['total_elevation_loss'] ) ) : null,
|
||||
'min_altitude' => isset( $data['min_altitude'] ) ? $null_if_empty( intval( $data['min_altitude'] ) ) : null,
|
||||
'max_altitude' => isset( $data['max_altitude'] ) ? $null_if_empty( intval( $data['max_altitude'] ) ) : null,
|
||||
'equipment_id' => isset( $data['equipment_id'] ) ? $null_if_empty( intval( $data['equipment_id'] ) ) : null,
|
||||
'gpx_url' => isset( $data['gpx_url'] ) ? $null_if_empty( esc_url_raw( $data['gpx_url'] ) ) : null,
|
||||
'event_type_id' => isset( $data['event_type_id'] ) ? $null_if_empty( intval( $data['event_type_id'] ) ) : null,
|
||||
);
|
||||
|
||||
// Data formats for $wpdb.
|
||||
$format = array(
|
||||
'%d', // category_id
|
||||
'%s', // date
|
||||
'%s', // title
|
||||
'%f', // distance
|
||||
'%s', // duration
|
||||
'%d', // calories
|
||||
'%s', // comment
|
||||
'%s', // strava_url
|
||||
'%d', // avg_heart_rate
|
||||
'%d', // max_heart_rate
|
||||
'%f', // avg_speed
|
||||
'%f', // max_speed
|
||||
'%d', // avg_cadence
|
||||
'%d', // max_cadence
|
||||
'%d', // total_elevation_gain
|
||||
'%d', // total_elevation_loss
|
||||
'%d', // min_altitude
|
||||
'%d', // max_altitude
|
||||
'%d', // equipment_id
|
||||
'%s', // gpx_url
|
||||
'%d', // event_type_id
|
||||
);
|
||||
|
||||
if ( $activity_id > 0 ) {
|
||||
// UPDATE
|
||||
$result = $wpdb->update( $table_activities, $prepared_data, array( 'id' => $activity_id ), $format, array( '%d' ) );
|
||||
if ( false !== $result ) {
|
||||
return $activity_id;
|
||||
}
|
||||
} else {
|
||||
// INSERT
|
||||
$result = $wpdb->insert( $table_activities, $prepared_data, $format );
|
||||
if ( $result ) {
|
||||
return $wpdb->insert_id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
+296
-138
@@ -1,139 +1,297 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and parses a GPX file from a URL to extract track points and elevation profile.
|
||||
*
|
||||
* @param string $gpx_url The URL of the GPX file.
|
||||
* @return array An array containing 'points' for the map and 'elevation_profile'.
|
||||
*/
|
||||
function statpress_parse_gpx_data( $gpx_url ) {
|
||||
if ( empty( $gpx_url ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$response = wp_remote_get( $gpx_url, array( 'timeout' => 20 ) );
|
||||
|
||||
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$gpx_content = wp_remote_retrieve_body( $response );
|
||||
if ( empty( $gpx_content ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// --- Privacy Zone ---
|
||||
$privacy_options = get_option( 'statpress_privacy_options' );
|
||||
$privacy_enabled = ! empty( $privacy_options['latitude'] ) && ! empty( $privacy_options['longitude'] ) && ! empty( $privacy_options['radius'] );
|
||||
$privacy_center_lat = $privacy_enabled ? (float) $privacy_options['latitude'] : 0;
|
||||
$privacy_center_lon = $privacy_enabled ? (float) $privacy_options['longitude'] : 0;
|
||||
$privacy_radius_km = $privacy_enabled ? ( (int) $privacy_options['radius'] ) / 1000 : 0; // Convert meters to km
|
||||
|
||||
libxml_use_internal_errors( true );
|
||||
$gpx = simplexml_load_string( $gpx_content );
|
||||
libxml_clear_errors();
|
||||
|
||||
if ( false === $gpx ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Use XPath to be more robust against different GPX structures/namespaces
|
||||
$gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' );
|
||||
$trackpoints = $gpx->xpath( '//gpx:trkpt' );
|
||||
if ( empty( $trackpoints ) ) {
|
||||
$trackpoints = $gpx->xpath( '//trkpt' ); // Fallback for files without namespace
|
||||
}
|
||||
|
||||
$raw_points = array();
|
||||
$start_time = null;
|
||||
|
||||
foreach ( $trackpoints as $trkpt ) {
|
||||
if ( isset( $trkpt['lat'], $trkpt['lon'] ) ) {
|
||||
$extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null;
|
||||
$time = isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null;
|
||||
if ( $time && is_null( $start_time ) ) {
|
||||
$start_time = $time;
|
||||
}
|
||||
|
||||
$hr_val = ( $extensions && isset( $extensions->TrackPointExtension->hr ) ) ? (int) $extensions->TrackPointExtension->hr : null;
|
||||
$cad_val = ( $extensions && isset( $extensions->TrackPointExtension->cad ) ) ? (int) $extensions->TrackPointExtension->cad : null;
|
||||
|
||||
$raw_points[] = array(
|
||||
'lat' => (float) $trkpt['lat'],
|
||||
'lon' => (float) $trkpt['lon'],
|
||||
'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null,
|
||||
'time_offset' => $start_time && $time ? $time - $start_time : null,
|
||||
'hr' => $hr_val,
|
||||
'cad' => $cad_val,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $raw_points ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Process raw points to calculate profiles
|
||||
$map_points = array();
|
||||
$profiles = array(
|
||||
'distance' => array(),
|
||||
'time' => array(),
|
||||
'elevation' => array(),
|
||||
'speed' => array(),
|
||||
'hr' => array(),
|
||||
'cadence' => array(),
|
||||
);
|
||||
$cumulative_distance = 0;
|
||||
|
||||
$haversine = function( $lat1, $lon1, $lat2, $lon2 ) {
|
||||
$earth_radius = 6371; // in km
|
||||
$dLat = deg2rad( $lat2 - $lat1 );
|
||||
$dLon = deg2rad( $lon2 - $lon1 );
|
||||
$a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $dLon / 2 ) * sin( $dLon / 2 );
|
||||
$c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) );
|
||||
return $earth_radius * $c;
|
||||
};
|
||||
|
||||
foreach ( $raw_points as $i => $point ) {
|
||||
$is_in_privacy_zone = false;
|
||||
if ( $privacy_enabled ) {
|
||||
$distance_from_center = $haversine( $privacy_center_lat, $privacy_center_lon, $point['lat'], $point['lon'] );
|
||||
if ( $distance_from_center <= $privacy_radius_km ) {
|
||||
$is_in_privacy_zone = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $is_in_privacy_zone ) {
|
||||
$map_points[] = array( $point['lat'], $point['lon'] );
|
||||
$speed = null;
|
||||
|
||||
if ( $i > 0 ) {
|
||||
$prev_point = $raw_points[ $i - 1 ];
|
||||
$distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); // km
|
||||
$cumulative_distance += $distance_delta;
|
||||
|
||||
if ( ! is_null( $point['time_offset'] ) && ! is_null( $prev_point['time_offset'] ) ) {
|
||||
$time_delta = $point['time_offset'] - $prev_point['time_offset']; // seconds
|
||||
if ( $time_delta > 0 ) {
|
||||
$speed = ( $distance_delta * 3600 ) / $time_delta; // km/h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$profiles['distance'][] = round( $cumulative_distance, 3 );
|
||||
$profiles['time'][] = $point['time_offset'];
|
||||
$profiles['elevation'][] = ! is_null( $point['ele'] ) ? round( $point['ele'], 2 ) : null;
|
||||
$profiles['speed'][] = ! is_null( $speed ) ? round( $speed, 1 ) : null;
|
||||
$profiles['hr'][] = $point['hr'];
|
||||
$profiles['cadence'][] = $point['cad'];
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'points' => $map_points,
|
||||
'profiles' => $profiles,
|
||||
);
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and parses a GPX file from a URL to extract track points and elevation profile.
|
||||
*
|
||||
* @param string $gpx_url The URL of the GPX file.
|
||||
* @return array An array containing 'points' for the map and 'elevation_profile'.
|
||||
*/
|
||||
function statpress_parse_gpx_data( $gpx_url ) {
|
||||
if ( empty( $gpx_url ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$response = wp_remote_get( $gpx_url, array( 'timeout' => 20 ) );
|
||||
|
||||
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$gpx_content = wp_remote_retrieve_body( $response );
|
||||
if ( empty( $gpx_content ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// --- Privacy Zone ---
|
||||
$privacy_options = get_option( 'statpress_privacy_options' );
|
||||
$privacy_enabled = ! empty( $privacy_options['latitude'] ) && ! empty( $privacy_options['longitude'] ) && ! empty( $privacy_options['radius'] );
|
||||
$privacy_center_lat = $privacy_enabled ? (float) $privacy_options['latitude'] : 0;
|
||||
$privacy_center_lon = $privacy_enabled ? (float) $privacy_options['longitude'] : 0;
|
||||
$privacy_radius_km = $privacy_enabled ? ( (int) $privacy_options['radius'] ) / 1000 : 0; // Convert meters to km
|
||||
|
||||
libxml_use_internal_errors( true );
|
||||
$gpx = simplexml_load_string( $gpx_content );
|
||||
libxml_clear_errors();
|
||||
|
||||
if ( false === $gpx ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Use XPath to be more robust against different GPX structures/namespaces
|
||||
$gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' );
|
||||
$trackpoints = $gpx->xpath( '//gpx:trkpt' );
|
||||
if ( empty( $trackpoints ) ) {
|
||||
$trackpoints = $gpx->xpath( '//trkpt' ); // Fallback for files without namespace
|
||||
}
|
||||
|
||||
$raw_points = array();
|
||||
$start_time = null;
|
||||
|
||||
foreach ( $trackpoints as $trkpt ) {
|
||||
if ( isset( $trkpt['lat'], $trkpt['lon'] ) ) {
|
||||
$extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null;
|
||||
$time = isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null;
|
||||
if ( $time && is_null( $start_time ) ) {
|
||||
$start_time = $time;
|
||||
}
|
||||
|
||||
$hr_val = ( $extensions && isset( $extensions->TrackPointExtension->hr ) ) ? (int) $extensions->TrackPointExtension->hr : null;
|
||||
$cad_val = ( $extensions && isset( $extensions->TrackPointExtension->cad ) ) ? (int) $extensions->TrackPointExtension->cad : null;
|
||||
|
||||
$raw_points[] = array(
|
||||
'lat' => (float) $trkpt['lat'],
|
||||
'lon' => (float) $trkpt['lon'],
|
||||
'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null,
|
||||
'time_offset' => $start_time && $time ? $time - $start_time : null,
|
||||
'hr' => $hr_val,
|
||||
'cad' => $cad_val,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $raw_points ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Process raw points to calculate profiles
|
||||
$map_points = array();
|
||||
$profiles = array(
|
||||
'distance' => array(),
|
||||
'time' => array(),
|
||||
'elevation' => array(),
|
||||
'speed' => array(),
|
||||
'hr' => array(),
|
||||
'cadence' => array(),
|
||||
);
|
||||
$cumulative_distance = 0;
|
||||
|
||||
$haversine = function( $lat1, $lon1, $lat2, $lon2 ) {
|
||||
$earth_radius = 6371; // in km
|
||||
$dLat = deg2rad( $lat2 - $lat1 );
|
||||
$dLon = deg2rad( $lon2 - $lon1 );
|
||||
$a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $dLon / 2 ) * sin( $dLon / 2 );
|
||||
$c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) );
|
||||
return $earth_radius * $c;
|
||||
};
|
||||
|
||||
foreach ( $raw_points as $i => $point ) {
|
||||
$is_in_privacy_zone = false;
|
||||
if ( $privacy_enabled ) {
|
||||
$distance_from_center = $haversine( $privacy_center_lat, $privacy_center_lon, $point['lat'], $point['lon'] );
|
||||
if ( $distance_from_center <= $privacy_radius_km ) {
|
||||
$is_in_privacy_zone = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $is_in_privacy_zone ) {
|
||||
$map_points[] = array( $point['lat'], $point['lon'] );
|
||||
$speed = null;
|
||||
|
||||
if ( $i > 0 ) {
|
||||
$prev_point = $raw_points[ $i - 1 ];
|
||||
$distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); // km
|
||||
$cumulative_distance += $distance_delta;
|
||||
|
||||
if ( ! is_null( $point['time_offset'] ) && ! is_null( $prev_point['time_offset'] ) ) {
|
||||
$time_delta = $point['time_offset'] - $prev_point['time_offset']; // seconds
|
||||
if ( $time_delta > 0 ) {
|
||||
$speed = ( $distance_delta * 3600 ) / $time_delta; // km/h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$profiles['distance'][] = round( $cumulative_distance, 3 );
|
||||
$profiles['time'][] = $point['time_offset'];
|
||||
$profiles['elevation'][] = ! is_null( $point['ele'] ) ? round( $point['ele'], 2 ) : null;
|
||||
$profiles['speed'][] = ! is_null( $speed ) ? round( $speed, 1 ) : null;
|
||||
$profiles['hr'][] = $point['hr'];
|
||||
$profiles['cadence'][] = $point['cad'];
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'points' => $map_points,
|
||||
'profiles' => $profiles,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and calculates a full summary from a GPX file.
|
||||
* This function parses the entire file to get accurate summary stats, ignoring privacy zones.
|
||||
*
|
||||
* @param string $gpx_url The URL of the GPX file.
|
||||
* @return array An associative array with summary statistics.
|
||||
*/
|
||||
function statpress_calculate_gpx_summary( $gpx_url ) {
|
||||
if ( empty( $gpx_url ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$response = wp_remote_get( $gpx_url, array( 'timeout' => 20 ) );
|
||||
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$gpx_content = wp_remote_retrieve_body( $response );
|
||||
if ( empty( $gpx_content ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
libxml_use_internal_errors( true );
|
||||
$gpx = simplexml_load_string( $gpx_content );
|
||||
libxml_clear_errors();
|
||||
|
||||
if ( false === $gpx ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' );
|
||||
$trackpoints = $gpx->xpath( '//gpx:trkpt' );
|
||||
if ( empty( $trackpoints ) ) {
|
||||
$trackpoints = $gpx->xpath( '//trkpt' );
|
||||
}
|
||||
|
||||
if ( empty( $trackpoints ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$haversine = function( $lat1, $lon1, $lat2, $lon2 ) {
|
||||
$earth_radius = 6371; // km
|
||||
$dLat = deg2rad( $lat2 - $lat1 );
|
||||
$dLon = deg2rad( $lon2 - $lon1 );
|
||||
$a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $dLon / 2 ) * sin( $dLon / 2 );
|
||||
$c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) );
|
||||
return $earth_radius * $c;
|
||||
};
|
||||
|
||||
$stats = array(
|
||||
'distance' => 0,
|
||||
'total_elevation_gain' => 0,
|
||||
'total_elevation_loss' => 0,
|
||||
'min_altitude' => null,
|
||||
'max_altitude' => null,
|
||||
'max_speed' => 0,
|
||||
'max_heart_rate' => 0,
|
||||
'max_cadence' => 0,
|
||||
'hr_sum' => 0,
|
||||
'hr_count' => 0,
|
||||
'cad_sum' => 0,
|
||||
'cad_count' => 0,
|
||||
'speed_sum' => 0,
|
||||
'speed_count' => 0,
|
||||
'start_time' => null,
|
||||
'end_time' => null,
|
||||
);
|
||||
|
||||
$prev_point = null;
|
||||
|
||||
foreach ( $trackpoints as $trkpt ) {
|
||||
$point = array(
|
||||
'lat' => (float) $trkpt['lat'],
|
||||
'lon' => (float) $trkpt['lon'],
|
||||
'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null,
|
||||
'time' => isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null,
|
||||
);
|
||||
|
||||
if ( is_null( $stats['start_time'] ) && ! is_null( $point['time'] ) ) {
|
||||
$stats['start_time'] = $point['time'];
|
||||
}
|
||||
$stats['end_time'] = $point['time'];
|
||||
|
||||
if ( ! is_null( $point['ele'] ) ) {
|
||||
if ( is_null( $stats['min_altitude'] ) || $point['ele'] < $stats['min_altitude'] ) {
|
||||
$stats['min_altitude'] = $point['ele'];
|
||||
}
|
||||
if ( is_null( $stats['max_altitude'] ) || $point['ele'] > $stats['max_altitude'] ) {
|
||||
$stats['max_altitude'] = $point['ele'];
|
||||
}
|
||||
}
|
||||
|
||||
$extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null;
|
||||
if ( $extensions ) {
|
||||
if ( isset( $extensions->TrackPointExtension->hr ) ) {
|
||||
$hr = (int) $extensions->TrackPointExtension->hr;
|
||||
$stats['hr_sum'] += $hr;
|
||||
$stats['hr_count']++;
|
||||
if ( $hr > $stats['max_heart_rate'] ) {
|
||||
$stats['max_heart_rate'] = $hr;
|
||||
}
|
||||
}
|
||||
if ( isset( $extensions->TrackPointExtension->cad ) ) {
|
||||
$cad = (int) $extensions->TrackPointExtension->cad;
|
||||
$stats['cad_sum'] += $cad;
|
||||
$stats['cad_count']++;
|
||||
if ( $cad > $stats['max_cadence'] ) {
|
||||
$stats['max_cadence'] = $cad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $prev_point ) {
|
||||
$distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] );
|
||||
$stats['distance'] += $distance_delta;
|
||||
|
||||
if ( ! is_null( $point['ele'] ) && ! is_null( $prev_point['ele'] ) ) {
|
||||
$ele_delta = $point['ele'] - $prev_point['ele'];
|
||||
if ( $ele_delta > 0 ) {
|
||||
$stats['total_elevation_gain'] += $ele_delta;
|
||||
} else {
|
||||
$stats['total_elevation_loss'] += abs( $ele_delta );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_null( $point['time'] ) && ! is_null( $prev_point['time'] ) ) {
|
||||
$time_delta = $point['time'] - $prev_point['time'];
|
||||
if ( $time_delta > 0 ) {
|
||||
$speed = ( $distance_delta * 3600 ) / $time_delta; // km/h
|
||||
$stats['speed_sum'] += $speed;
|
||||
$stats['speed_count']++;
|
||||
if ( $speed > $stats['max_speed'] ) {
|
||||
$stats['max_speed'] = $speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$prev_point = $point;
|
||||
}
|
||||
|
||||
$duration_sec = ( ! is_null( $stats['end_time'] ) && ! is_null( $stats['start_time'] ) ) ? ( $stats['end_time'] - $stats['start_time'] ) : 0;
|
||||
|
||||
return array(
|
||||
'distance' => round( $stats['distance'], 2 ),
|
||||
'duration' => gmdate( 'H:i:s', $duration_sec ),
|
||||
'avg_speed' => ( $stats['distance'] > 0 && $duration_sec > 0 ) ? round( ( $stats['distance'] / ( $duration_sec / 3600 ) ), 1 ) : 0,
|
||||
'max_speed' => round( $stats['max_speed'], 1 ),
|
||||
'total_elevation_gain' => round( $stats['total_elevation_gain'] ),
|
||||
'total_elevation_loss' => round( $stats['total_elevation_loss'] ),
|
||||
'min_altitude' => ! is_null( $stats['min_altitude'] ) ? round( $stats['min_altitude'] ) : null,
|
||||
'max_altitude' => ! is_null( $stats['max_altitude'] ) ? round( $stats['max_altitude'] ) : null,
|
||||
'avg_heart_rate' => $stats['hr_count'] > 0 ? round( $stats['hr_sum'] / $stats['hr_count'] ) : 0,
|
||||
'max_heart_rate' => $stats['max_heart_rate'],
|
||||
'avg_cadence' => $stats['cad_count'] > 0 ? round( $stats['cad_sum'] / $stats['cad_count'] ) : 0,
|
||||
'max_cadence' => $stats['max_cadence'],
|
||||
);
|
||||
}
|
||||
@@ -1,33 +1,33 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add GPX support to WordPress Media Library.
|
||||
*
|
||||
* @param array $mimes Allowed mime types.
|
||||
* @return array Modified mime types.
|
||||
*/
|
||||
function statpress_add_gpx_mime_type( $mimes ) {
|
||||
$mimes['gpx'] = 'application/gpx+xml';
|
||||
return $mimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bypasses WordPress's strict file type check for GPX files.
|
||||
* This is needed because WordPress can be overly cautious with XML-based files.
|
||||
*
|
||||
* @param array $data File data.
|
||||
* @param string $file Full path to the file.
|
||||
* @param string $filename The filename.
|
||||
* @param array $mimes Mime types.
|
||||
* @return array Modified file data.
|
||||
*/
|
||||
function statpress_fix_gpx_upload_permission( $data, $file, $filename, $mimes ) {
|
||||
if ( strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ) === 'gpx' ) {
|
||||
$data['ext'] = 'gpx';
|
||||
$data['type'] = 'application/gpx+xml';
|
||||
}
|
||||
return $data;
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add GPX support to WordPress Media Library.
|
||||
*
|
||||
* @param array $mimes Allowed mime types.
|
||||
* @return array Modified mime types.
|
||||
*/
|
||||
function statpress_add_gpx_mime_type( $mimes ) {
|
||||
$mimes['gpx'] = 'application/gpx+xml';
|
||||
return $mimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bypasses WordPress's strict file type check for GPX files.
|
||||
* This is needed because WordPress can be overly cautious with XML-based files.
|
||||
*
|
||||
* @param array $data File data.
|
||||
* @param string $file Full path to the file.
|
||||
* @param string $filename The filename.
|
||||
* @param array $mimes Mime types.
|
||||
* @return array Modified file data.
|
||||
*/
|
||||
function statpress_fix_gpx_upload_permission( $data, $file, $filename, $mimes ) {
|
||||
if ( strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ) === 'gpx' ) {
|
||||
$data['ext'] = 'gpx';
|
||||
$data['type'] = 'application/gpx+xml';
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
Reference in New Issue
Block a user