From 75717ca08bf599684d39961c47d1397c9cbd0fff Mon Sep 17 00:00:00 2001 From: Mark Drovdahl Date: Wed, 3 Oct 2018 18:16:26 -0400 Subject: [PATCH 1/3] MVP of the Strava importer --- importers/keyring-importer-strava.php | 314 ++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 importers/keyring-importer-strava.php diff --git a/importers/keyring-importer-strava.php b/importers/keyring-importer-strava.php new file mode 100644 index 0000000..e560e1b --- /dev/null +++ b/importers/keyring-importer-strava.php @@ -0,0 +1,314 @@ +error( __( 'Make sure you select a valid category to import your activities into.', 'keyring' ) ); + } + + if ( empty( $_POST['author'] ) || ! ctype_digit( $_POST['author'] ) ) { + $this->error( __( 'You must select an author to assign to all activities.', 'keyring' ) ); + } + + if ( isset( $_POST['auto_import'] ) ) { + $_POST['auto_import'] = true; + } else { + $_POST['auto_import'] = false; + } + + // If there were errors, output them, otherwise store options and start importing. + if ( count( $this->errors ) ) { + $this->step = 'options'; + } else { + $this->set_option( array( + 'category' => (int) $_POST['category'], + 'tags' => explode( ',', $_POST['tags'] ), + 'author' => (int) $_POST['author'], + 'auto_import' => $_POST['auto_import'], + ) ); + + $this->step = 'import'; + } + } + + /** + Create the request which will be sent to the Strava API + TODO: this probably needs to be paged? + **/ + function build_request_url() { + // This Strava endpoint returns a list of activities for the authenticated user in descending order (most recent first) + // This Strava endpoint can be filtered for activities that have taken place "before" or "after" a certain time. These can be combined. + // We use the API date filter to request activities more recent than the latest activty we have stored. + // This endpoint can also be paged, but for now we're WRONGLY ASSUMING no paging required. + // Our Strava keyring token has a "first_date" which (maybe) corresponds to the earliest activity for this Strava account + // first date example: "first_date: 2014-06-07T19:13:55Z" is UTC + // First import should query using "before" now and walk backwards towards the "first_date". + // Auto import should query using "before" now and walk backwards towards the date of the most recently imported activity. + $url = 'https://www.strava.com/api/v3/athlete/activities'; + + // Get the latest imported activities. + $latest = get_posts( + array( + 'numberposts' => 1, + 'orderby' => 'date', + 'order' => 'DESC', + 'tax_query' => array( + array( + 'taxonomy' => 'keyring_services', + 'field' => 'slug', + 'terms' => array( $this->taxonomy->slug ), + 'operator' => 'IN', + ), + ), + ) + ); + + // If we already have activities imported, only query Strava for activities more recent than the most recently imported activity. + if ( $latest ) { + // Convert the post_date. + $last = date( 'Ymd H:i:s', strtotime( $latest[0]->post_date_gmt ) ); + $url = add_query_arg( 'after', strtotime( $last ), $url ); + error_log( 'we have prior imports, latest activity date is: ' . $last . "\n" . 'titled: ' . $latest[0]->post_title ); + } else { + // If we have no activities imported, we will assume this is our first import and we query for activites after the "first_date". + // Queries to Strava for ?after=[epoch_date] return activities in ascending order and can be paged. + $date = $this->service->token->get_meta( 'first_date' ); // We should have the profile creation date for the Strava Athlete. + error_log( 'first date: ' . $date ); + $url = add_query_arg( 'page', $this->get_option( 'page', 1 ), $url ); + $url = add_query_arg( 'per_page', self::NUM_PER_LOAD, $url ); + $url = add_query_arg( 'after', strtotime( $date ), $url ); + } + error_log( "querying strava: " . $url ); + return $url; + } + + /** + Helper function to convert between meters and kilometers + Anything less than 1 kilometer is formatted in meters, otherwise kilometers + todo: extend with a switch statement and a second parameter of "units" to eg: convert meters to miles + + @param number $num is a distance value in meters + **/ + function format_distance( $num ) { + if ( $num < 1000 ) { + // Translators: todo add comment. + return sprintf( __( '%s meters', 'keyring' ), $num ); + } else { + // Translators: todo add comment. + return sprintf( __( '%s kilometers', 'keyring' ), round( $num / 1000, 1 ) ); + } + } + + /** + Helper function to convert between seconds, minutes and hours + Anything less than 1 hour is shown in minutes, otherwise hours + minutes + + @param number $num is a time value in seconds + **/ + function format_duration( $num ) { + if ( $num < 3600 ) { + // Translators: there are 60 seconds in a minute. + return sprintf( __( '%s minutes', 'keyring' ), round( $num / 60 ) ); + } else { + $hours = floor( $num / 60 / 60 ); + $num = $num - ( $hours * 60 * 60 ); + // Translators: there are 60 minutes in an hour. + return sprintf( __( '%1$s hours, %2$s minutes', 'keyring' ), $hours, round( $num / 60 ) ); + } + } + + /** + This function converts Strava activities to WordPress post objects + + @param json $importdata is the json coming from the Strava API. + returns an array of posts: + $this->posts[] = compact( + 'post_author', + 'post_date', + 'post_content', + 'post_title', + 'post_status', + 'post_category', + 'tags', + 'strava_raw' + ); + **/ + function extract_posts_from_data( $importdata ) { + //error_log( $importdata ); + // TODO: need to catch cases where $importdata == [] + + // If we get back an empty array, it may be b/c we're querying for a date beyond which there are no activities to import. + // or we have no data to process. + if ( null === $importdata || empty( $importdata ) ) { + $this->finished = true; + error_log( 'nothing to import'); + return new Keyring_Error( 'keyring-strava-importer-failed-download', __( 'Failed to download activities from Strava. Please wait a few minutes and try again.', 'keyring' ) ); + } + + // If we have the wrong type of data. + if ( ! is_array( $importdata ) || ! is_object( $importdata[0] ) ) { + $this->finished = true; + error_log( 'nothing to import'); + return new Keyring_Error( 'keyring-strava-importer-failed-download', __( 'Failed to download your activities from Strava. Please wait a few minutes and try again.', 'keyring' ) ); + } + + // Iterate over the activities + foreach ( $importdata as $post ) { + // Set WP "post_date" to the Strava activity "start_date" which is UTC, eg: "2018-02-06T17:36:50Z" + // TODO: get more discrete and add time to post_date + // Set WP post category and post tags from the import options. + // Set WP post title to the Strava activity "name". + error_log( $post->start_date ); + $post_date = substr( $post->start_date, 0, 4 ) . '-' . substr( $post->start_date, 5, 2 ) . '-' . substr( $post->start_date, 8, 2 ) . ' ' . substr( $post->start_date, 11, 8 ); + error_log( $post_date ); + $post_category = array( $this->get_option( 'category' ) ); + $tags = $this->get_option( 'tags' ); + $post_title = $post->name; + + // Set WP post content to a summary of the Strava activity + // Strava activities have a "type". Initially we'll only import types: "Hike", "Run" and "Ride" and use Strava's distance and "moving time" fields + // Check if the activity has a distance value + // TODO: add heartrate, but conditionally on "has_heartrate":true in the API response. + if ( ! empty( $post->distance ) ) { + switch ( $post->type ) { + case 'Hike': + $post_content = sprintf( + // Translators: Hiked [distance] in [duration]. + __( 'Hiked %1$s in %2$s' ), + $this->format_distance( $post->distance ), + $this->format_duration( $post->moving_time ) + ); + break; + + case 'Run': + $post_content = sprintf( + // Translators: Ran [distance] in [duration]. + __( 'Ran %1$s in %2$s' ), + $this->format_distance( $post->distance ), + $this->format_duration( $post->moving_time ) + ); + break; + + case 'Ride': + $post_content = sprintf( + // Translators: Cycled [distance] in [duration]. + __( 'Cycled %1$s in %2$s' ), + $this->format_distance( $post->distance ), + $this->format_duration( $post->moving_time ) + ); + break; + } + } + + // Set post author from the import options. + $post_author = $this->get_option( 'author' ); + // Set post status from import options, default to published. + $post_status = $this->get_option( 'status', 'publish' ); + // Keep the raw JSON activity from Strava. + $strava_raw = $post; + // Build an array of post objects. + $this->posts[] = compact( + 'post_author', + 'post_date', + 'post_content', + 'post_title', + 'post_status', + 'post_category', + 'tags', + 'strava_raw' + ); + } + } + + /** + This function inserts WP post objects into the database + The first time this is run, there might be years of activities to import... + On subsequent runs, we should only import net-new activities + **/ + function insert_posts() { + global $wpdb; + $imported = 0; + $skipped = 0; + + foreach ( $this->posts as $post ) { + extract( $post ); + error_log ( $post_title . ', ' . $post_content . ', ' . $post_date ); + // Avoid inserting duplicate activities + if ( + // TODO: get more defensive here, in case one of these doesn't exist + $post_id = post_exists( $post_date ) + ) { + // Looks like a duplicate + error_log( "skipping" ); + $skipped++; + } else { + $post_id = wp_insert_post( $post, $wp_error = TRUE ); + + if ( is_wp_error( $post_id ) ) { + return $post_id; + } + + if ( ! $post_id ) { + continue; + } + + // Track which Keyring service was used. + wp_set_object_terms( $post_id, self::LABEL, 'keyring_services' ); + + set_post_format( $post_id, 'status' ); + + // Update Category. + wp_set_post_categories( $post_id, $post_category ); + + if ( count( $tags ) ) { + wp_set_post_terms( $post_id, implode( ',', $tags ) ); + } + + add_post_meta( $post_id, 'raw_import_data', wp_slash( wp_json_encode( $strava_raw ) ) ); + error_log( 'importing' ); + $imported++; + + do_action( 'keyring_post_imported', $post_id, static::SLUG, $post ); + } + } + $this->posts = array(); + + // Return, so that the handler can output info (or update DB, or whatever). + return array( 'imported' => $imported, 'skipped' => $skipped ); + } + } // end class Keyring_Strava_Importer +} // end function Keyring_Strava_Importer + +add_action( 'init', function() { + Keyring_Strava_Importer(); // Instantiate the class + keyring_register_importer( + 'strava', + 'Keyring_Strava_Importer', + plugin_basename( __FILE__ ), + __( '[Under Development!] Import your daily activities as single Posts, marked with the "status" format. You can also use the data as part of other maps or whatever else you\'d like to do with it.', 'keyring' ) + ); +} ); From 8b266e49f5af3e7ab0f7063ac1776d4a6f418456 Mon Sep 17 00:00:00 2001 From: Mark Drovdahl Date: Sun, 7 Oct 2018 20:54:25 -0700 Subject: [PATCH 2/3] Cleaning up comments, removing calls to error_log() --- importers/keyring-importer-strava.php | 115 +++++++++++--------------- 1 file changed, 47 insertions(+), 68 deletions(-) diff --git a/importers/keyring-importer-strava.php b/importers/keyring-importer-strava.php index e560e1b..f8b2513 100644 --- a/importers/keyring-importer-strava.php +++ b/importers/keyring-importer-strava.php @@ -9,7 +9,7 @@ function Keyring_Strava_Importer() { /** - This is a class to import data from the Strava.com API + This is a class to import data from https://www.strava.com/api/v3 @author Mark Drovdahl @Category class @@ -19,7 +19,7 @@ class Keyring_Strava_Importer extends Keyring_Importer_Base { const LABEL = 'Strava'; // e.g. 'Twitter'. const KEYRING_SERVICE = 'Keyring_Service_Strava'; // Full class name of the Keyring_Service this importer requires. const REQUESTS_PER_LOAD = 1; // How many remote requests should be made before reloading the page? - const NUM_PER_LOAD = 30; // How many activities per API request? We'll use Strava's default. + const NUM_PER_LOAD = 30; // How many activities per API request? We'll use Strava's default of 30. /** Borrowed from other keyring-social-importers @@ -57,20 +57,16 @@ function handle_request_options() { /** Create the request which will be sent to the Strava API - TODO: this probably needs to be paged? + - This MVP uses the "athlete/activities" endpoint which returns activity summaries https://developers.strava.com/docs/reference/#api-models-SummaryActivity + - The "athlete/activities" endpoint can be filtered for activities that have taken place "before" or "after" a given time. These can be combined to target specific date ranges. Queries to the endpoint with ?after=[epoch_date] return activities in ascending order (oldest first) and can be paged with ?page= and segmented by ?per_page= + - Our Strava keyring token has a "first_date" which indicates when the Strava Athlete profile was created. We can assume there are no activies to import which are older than that date. + first date example: "first_date: 2014-06-07T19:13:55Z" is UTC + TODO: use the Keyring reprocessor w/the strava "id" from the first API call to then call "/activities/{id}" endpoint which returns moar! activity details https://developers.strava.com/docs/reference/#api-models-DetailedActivity **/ function build_request_url() { - // This Strava endpoint returns a list of activities for the authenticated user in descending order (most recent first) - // This Strava endpoint can be filtered for activities that have taken place "before" or "after" a certain time. These can be combined. - // We use the API date filter to request activities more recent than the latest activty we have stored. - // This endpoint can also be paged, but for now we're WRONGLY ASSUMING no paging required. - // Our Strava keyring token has a "first_date" which (maybe) corresponds to the earliest activity for this Strava account - // first date example: "first_date: 2014-06-07T19:13:55Z" is UTC - // First import should query using "before" now and walk backwards towards the "first_date". - // Auto import should query using "before" now and walk backwards towards the date of the most recently imported activity. $url = 'https://www.strava.com/api/v3/athlete/activities'; - // Get the latest imported activities. + // Get the latest imported activity. $latest = get_posts( array( 'numberposts' => 1, @@ -87,29 +83,29 @@ function build_request_url() { ) ); - // If we already have activities imported, only query Strava for activities more recent than the most recently imported activity. + // If we already have activities imported, only query Strava for activities more recent than the latest imported activity. if ( $latest ) { - // Convert the post_date. + // Convert the WP post_date. Strava needs it in epoch/unix time. $last = date( 'Ymd H:i:s', strtotime( $latest[0]->post_date_gmt ) ); + + // Build our API request url with ?after=[epoch_date] param. $url = add_query_arg( 'after', strtotime( $last ), $url ); - error_log( 'we have prior imports, latest activity date is: ' . $last . "\n" . 'titled: ' . $latest[0]->post_title ); } else { - // If we have no activities imported, we will assume this is our first import and we query for activites after the "first_date". - // Queries to Strava for ?after=[epoch_date] return activities in ascending order and can be paged. - $date = $this->service->token->get_meta( 'first_date' ); // We should have the profile creation date for the Strava Athlete. - error_log( 'first date: ' . $date ); + // If we have no activities imported, we assume this is our first import and we query for activites after the "first_date". + $date = $this->service->token->get_meta( 'first_date' ); + + // Build our API request url with ?after=[epoch_date] and ?page= and ?per_page= params. + $url = add_query_arg( 'after', strtotime( $date ), $url ); $url = add_query_arg( 'page', $this->get_option( 'page', 1 ), $url ); $url = add_query_arg( 'per_page', self::NUM_PER_LOAD, $url ); - $url = add_query_arg( 'after', strtotime( $date ), $url ); } - error_log( "querying strava: " . $url ); return $url; } /** - Helper function to convert between meters and kilometers + Helper function to format meters to kilometers. Could go with format_duration() in a Convert_Units() class. Anything less than 1 kilometer is formatted in meters, otherwise kilometers - todo: extend with a switch statement and a second parameter of "units" to eg: convert meters to miles + TODO: extend with a switch statement and a second parameter of "units" to eg: convert meters to miles @param number $num is a distance value in meters **/ @@ -124,7 +120,7 @@ function format_distance( $num ) { } /** - Helper function to convert between seconds, minutes and hours + Helper function to format seconds to minutes and hours Anything less than 1 hour is shown in minutes, otherwise hours + minutes @param number $num is a time value in seconds @@ -142,56 +138,38 @@ function format_duration( $num ) { } /** - This function converts Strava activities to WordPress post objects - - @param json $importdata is the json coming from the Strava API. - returns an array of posts: - $this->posts[] = compact( - 'post_author', - 'post_date', - 'post_content', - 'post_title', - 'post_status', - 'post_category', - 'tags', - 'strava_raw' - ); + This function converts Strava activity objects to WordPress post objects + + @param json object $importdata The json returned from the Strava api. + @return Array of posts: **/ function extract_posts_from_data( $importdata ) { - //error_log( $importdata ); - // TODO: need to catch cases where $importdata == [] - - // If we get back an empty array, it may be b/c we're querying for a date beyond which there are no activities to import. - // or we have no data to process. + // Early return if we get back an empty array, it may be b/c we're querying for a date beyond which there are no activities to import. + // TODO: the "Failed to download..." message is not being output, so when there are no more activites to return, the user gets a confusing message. if ( null === $importdata || empty( $importdata ) ) { $this->finished = true; - error_log( 'nothing to import'); return new Keyring_Error( 'keyring-strava-importer-failed-download', __( 'Failed to download activities from Strava. Please wait a few minutes and try again.', 'keyring' ) ); } - // If we have the wrong type of data. + // Early return if we have the wrong type of data. if ( ! is_array( $importdata ) || ! is_object( $importdata[0] ) ) { $this->finished = true; - error_log( 'nothing to import'); return new Keyring_Error( 'keyring-strava-importer-failed-download', __( 'Failed to download your activities from Strava. Please wait a few minutes and try again.', 'keyring' ) ); } - // Iterate over the activities + // Iterate over the activities. foreach ( $importdata as $post ) { - // Set WP "post_date" to the Strava activity "start_date" which is UTC, eg: "2018-02-06T17:36:50Z" - // TODO: get more discrete and add time to post_date - // Set WP post category and post tags from the import options. - // Set WP post title to the Strava activity "name". - error_log( $post->start_date ); - $post_date = substr( $post->start_date, 0, 4 ) . '-' . substr( $post->start_date, 5, 2 ) . '-' . substr( $post->start_date, 8, 2 ) . ' ' . substr( $post->start_date, 11, 8 ); - error_log( $post_date ); + // Map Strava data model to WP post model. post->start_date is in UTC. + // Set WP post_title to the Strava activity "name". + $post_date = substr( $post->start_date, 0, 4 ) . '-' . substr( $post->start_date, 5, 2 ) . '-' . substr( $post->start_date, 8, 2 ) . ' ' . substr( $post->start_date, 11, 8 ); $post_category = array( $this->get_option( 'category' ) ); $tags = $this->get_option( 'tags' ); - $post_title = $post->name; + $post_title = $post->name; - // Set WP post content to a summary of the Strava activity - // Strava activities have a "type". Initially we'll only import types: "Hike", "Run" and "Ride" and use Strava's distance and "moving time" fields - // Check if the activity has a distance value + // Set WP post content to a summary of the Strava activity. + // Strava activities have a "type". Initially we only support importing activity types: "Hike", "Run" and "Ride" and use Strava's distance and "moving time" fields. + // Check if the activity has a distance value. + // TODO: support other activity types. // TODO: add heartrate, but conditionally on "has_heartrate":true in the API response. if ( ! empty( $post->distance ) ) { switch ( $post->type ) { @@ -256,16 +234,14 @@ function insert_posts() { foreach ( $this->posts as $post ) { extract( $post ); - error_log ( $post_title . ', ' . $post_content . ', ' . $post_date ); - // Avoid inserting duplicate activities + // Avoid inserting duplicate activities. if ( - // TODO: get more defensive here, in case one of these doesn't exist - $post_id = post_exists( $post_date ) + $post_id = post_exists( $post_title, $post_content, $post_date ) ) { - // Looks like a duplicate - error_log( "skipping" ); + // Looks like a duplicate. $skipped++; } else { + // Insert the post into the DB. $post_id = wp_insert_post( $post, $wp_error = TRUE ); if ( is_wp_error( $post_id ) ) { @@ -279,19 +255,22 @@ function insert_posts() { // Track which Keyring service was used. wp_set_object_terms( $post_id, self::LABEL, 'keyring_services' ); + // Set the post format. set_post_format( $post_id, 'status' ); // Update Category. wp_set_post_categories( $post_id, $post_category ); + // Update tags. if ( count( $tags ) ) { wp_set_post_terms( $post_id, implode( ',', $tags ) ); } + // Save the raw JSON in post-meta. add_post_meta( $post_id, 'raw_import_data', wp_slash( wp_json_encode( $strava_raw ) ) ); - error_log( 'importing' ); $imported++; + // A potentially useful action to hoook into for further processing. do_action( 'keyring_post_imported', $post_id, static::SLUG, $post ); } } @@ -299,16 +278,16 @@ function insert_posts() { // Return, so that the handler can output info (or update DB, or whatever). return array( 'imported' => $imported, 'skipped' => $skipped ); - } + } // end insert_posts function } // end class Keyring_Strava_Importer } // end function Keyring_Strava_Importer add_action( 'init', function() { - Keyring_Strava_Importer(); // Instantiate the class + Keyring_Strava_Importer(); keyring_register_importer( 'strava', 'Keyring_Strava_Importer', plugin_basename( __FILE__ ), - __( '[Under Development!] Import your daily activities as single Posts, marked with the "status" format. You can also use the data as part of other maps or whatever else you\'d like to do with it.', 'keyring' ) + __( '[Under Development!] Import your Strava activities, each as a single Post, marked with the "status" format. The Post title is set to the Activity name and a basic summary of the activity including the distance and the duration goes in the Post body.', 'keyring' ) ); } ); From cb6cfcc3bb9dc25c0c634daaf2951fd5c560da66 Mon Sep 17 00:00:00 2001 From: Beau Lebens Date: Sat, 3 Nov 2018 16:38:15 -0600 Subject: [PATCH 3/3] Strava: Get ready for merge - clean up some spacing - handle empty result-set better, to avoid throwing an error - add periods to the end of post_content - handle "Workout" activity type as well - store some additional data in separate fields --- importers/keyring-importer-strava.php | 72 +++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/importers/keyring-importer-strava.php b/importers/keyring-importer-strava.php index f8b2513..0bc7fca 100644 --- a/importers/keyring-importer-strava.php +++ b/importers/keyring-importer-strava.php @@ -89,13 +89,13 @@ function build_request_url() { $last = date( 'Ymd H:i:s', strtotime( $latest[0]->post_date_gmt ) ); // Build our API request url with ?after=[epoch_date] param. - $url = add_query_arg( 'after', strtotime( $last ), $url ); + $url = add_query_arg( 'after', strtotime( $last ), $url ); } else { // If we have no activities imported, we assume this is our first import and we query for activites after the "first_date". $date = $this->service->token->get_meta( 'first_date' ); // Build our API request url with ?after=[epoch_date] and ?page= and ?per_page= params. - $url = add_query_arg( 'after', strtotime( $date ), $url ); + $url = add_query_arg( 'after', strtotime( $date ), $url ); $url = add_query_arg( 'page', $this->get_option( 'page', 1 ), $url ); $url = add_query_arg( 'per_page', self::NUM_PER_LOAD, $url ); } @@ -144,6 +144,12 @@ function format_duration( $num ) { @return Array of posts: **/ function extract_posts_from_data( $importdata ) { + // Looks like we ran out of results. + if ( is_array( $importdata ) && empty( $importdata ) ) { + $this->finished = true; + return; + } + // Early return if we get back an empty array, it may be b/c we're querying for a date beyond which there are no activities to import. // TODO: the "Failed to download..." message is not being output, so when there are no more activites to return, the user gets a confusing message. if ( null === $importdata || empty( $importdata ) ) { @@ -152,7 +158,7 @@ function extract_posts_from_data( $importdata ) { } // Early return if we have the wrong type of data. - if ( ! is_array( $importdata ) || ! is_object( $importdata[0] ) ) { + if ( ! is_object( $importdata[0] ) ) { $this->finished = true; return new Keyring_Error( 'keyring-strava-importer-failed-download', __( 'Failed to download your activities from Strava. Please wait a few minutes and try again.', 'keyring' ) ); } @@ -161,7 +167,7 @@ function extract_posts_from_data( $importdata ) { foreach ( $importdata as $post ) { // Map Strava data model to WP post model. post->start_date is in UTC. // Set WP post_title to the Strava activity "name". - $post_date = substr( $post->start_date, 0, 4 ) . '-' . substr( $post->start_date, 5, 2 ) . '-' . substr( $post->start_date, 8, 2 ) . ' ' . substr( $post->start_date, 11, 8 ); + $post_date = substr( $post->start_date, 0, 4 ) . '-' . substr( $post->start_date, 5, 2 ) . '-' . substr( $post->start_date, 8, 2 ) . ' ' . substr( $post->start_date, 11, 8 ); $post_category = array( $this->get_option( 'category' ) ); $tags = $this->get_option( 'tags' ); $post_title = $post->name; @@ -176,7 +182,7 @@ function extract_posts_from_data( $importdata ) { case 'Hike': $post_content = sprintf( // Translators: Hiked [distance] in [duration]. - __( 'Hiked %1$s in %2$s' ), + __( 'Hiked %1$s in %2$s.' ), $this->format_distance( $post->distance ), $this->format_duration( $post->moving_time ) ); @@ -185,7 +191,7 @@ function extract_posts_from_data( $importdata ) { case 'Run': $post_content = sprintf( // Translators: Ran [distance] in [duration]. - __( 'Ran %1$s in %2$s' ), + __( 'Ran %1$s in %2$s.' ), $this->format_distance( $post->distance ), $this->format_duration( $post->moving_time ) ); @@ -194,20 +200,55 @@ function extract_posts_from_data( $importdata ) { case 'Ride': $post_content = sprintf( // Translators: Cycled [distance] in [duration]. - __( 'Cycled %1$s in %2$s' ), + __( 'Cycled %1$s in %2$s.' ), $this->format_distance( $post->distance ), $this->format_duration( $post->moving_time ) ); break; + + case 'Workout': + default: + if ( $post->has_heartrate ) { + $post_content = sprintf( + // Translators: Worked out for [duration] with a max heartrate of [heartrate] + __( 'Worked out for %1$s with a max heartrate of %2$d.' ), + $this->format_duration( $post->moving_time ), + $post->max_heartrate + ); + } else { + $post_content = sprintf( + // Translators: Worked out for [duration]. + __( 'Worked out for %1$s.' ), + $this->format_duration( $post->moving_time ) + ); + } + break; } } // Set post author from the import options. $post_author = $this->get_option( 'author' ); - // Set post status from import options, default to published. + + // Set post status from import options, default to published unless set to private on Strava. + // @todo Currently this won't work because you need a token with scope=activity:read_all to get private activities. Will need to modify or filter the Strava Service file for that. + $private = $post->private; $post_status = $this->get_option( 'status', 'publish' ); + if ( $private ) { + $post_status = 'private'; // Force private posts + } + + $strava_id = $post->id; + $strava_permalink = 'https://www.strava.com/activities/' . $post->id; + + // Grab an encoded/compressed polyline of the GPS data if available. + $geo = ''; + if ( ! empty( $post->map ) && ! empty( $post->map->summary_polyline ) ) { + $geo = $post->map->summary_polyline; + } + // Keep the raw JSON activity from Strava. $strava_raw = $post; + // Build an array of post objects. $this->posts[] = compact( 'post_author', @@ -217,7 +258,11 @@ function extract_posts_from_data( $importdata ) { 'post_status', 'post_category', 'tags', - 'strava_raw' + 'strava_raw', + 'strava_permalink', + 'strava_id', + 'geo', + 'private' ); } } @@ -266,6 +311,15 @@ function insert_posts() { wp_set_post_terms( $post_id, implode( ',', $tags ) ); } + add_post_meta( $post_id, 'strava_id', $strava_id ); + add_post_meta( $post_id, 'strava_permalink', $strava_permalink ); + + // Store the encoded polyline; will require decoding to map it + if ( $geo ) { + add_post_meta( $post_id, 'geo_polyline_encoded', $geo ); + add_post_meta( $post_id, 'geo_public', ( $private ? '0' : '1') ); // Hide geo if it's a private activity + } + // Save the raw JSON in post-meta. add_post_meta( $post_id, 'raw_import_data', wp_slash( wp_json_encode( $strava_raw ) ) ); $imported++;