diff --git a/app/event.php b/app/event.php index ec74f1a..774b390 100644 --- a/app/event.php +++ b/app/event.php @@ -36,6 +36,12 @@ public static function getEvent($id) $all['results'] = result::getResultsForEvent($id); $all['routes'] = route::getRoutesForEvent($id); $all['API version'] = RG2VERSION; + if (defined('STRAVA_SECRET')){ + $all['strava'] = 'true'; + } else { + $all['strava'] = 'false'; + } + $output = json_encode($all); @file_put_contents(CACHE_DIRECTORY."all_".$id.".json", $output); } diff --git a/app/strava.php b/app/strava.php new file mode 100644 index 0000000..b15a7d8 --- /dev/null +++ b/app/strava.php @@ -0,0 +1,84 @@ +access_token; + + // just echo the token, don't get activities + //echo "{\"access_token\":" .$access_token. "}"; + + //get latest strava activities + $activities = strava::getActivities($access_token); + + echo $activities; + + } + + public static function getToken($code) + { + $url = 'https://www.strava.com/api/v3/oauth/token'; + $data = array('client_id' => STRAVA_CLIENT, 'client_secret' => STRAVA_SECRET, + 'code' => $code, 'grant_type' => 'authorization_code'); + + // use key 'http' even if you send the request to https://... + $options = array( + 'http' => array( + 'header' => "Content-type: application/x-www-form-urlencoded\r\n", + 'method' => 'POST', + 'content' => http_build_query($data) + ) + ); + $context = stream_context_create($options); + $result = file_get_contents($url, false, $context); + if ($result === FALSE) { /* Handle error */ } + return $result; + } + + public static function getActivities($token) + { + $url = 'https://www.strava.com/api/v3/athlete/activities'; + + // use key 'http' even if you send the request to https://... + $options = array( + 'http' => array( + 'header' => "Authorization: Bearer " . $token, + 'method' => 'GET' + ) + ); + $context = stream_context_create($options); + $activities = file_get_contents($url, false, $context); + if ($activities === FALSE) { /* Handle error */ }; + + $result = "{\"access_token\":\"" .$token. "\", \"activities\":" .$activities."}"; + + return $result; + } + + public static function getActivityStream($data){ + + list($activity, $token) = explode('|', $data); + + $url = 'https://www.strava.com/api/v3/activities/'. $activity . '/streams?keys=latlng,time'; + + // use key 'http' even if you send the request to https://... + $options = array( + 'http' => array( + 'header' => "Authorization: Bearer " . $token, + 'method' => 'GET' + ) + ); + $context = stream_context_create($options); + $stream = file_get_contents($url, false, $context); + if ($stream === FALSE) { /* Handle error */ }; + + return $stream; + } + +} \ No newline at end of file diff --git a/html/infopanel.html b/html/infopanel.html index 66996f1..1e25d82 100644 --- a/html/infopanel.html +++ b/html/infopanel.html @@ -75,6 +75,10 @@

Load GPS file (GPX or TCX)

+
+ +
+
diff --git a/js/draw.js b/js/draw.js index ff0f24b..62da373 100644 --- a/js/draw.js +++ b/js/draw.js @@ -28,6 +28,10 @@ this.gpstrack.uploadGPS(evt); }, + uploadStrava : function (evt) { + this.gpstrack.uploadStrava(evt); + }, + getControlXY : function () { return {x: this.controlx, y: this.controly}; }, @@ -341,6 +345,7 @@ $("#btn-three-seconds").button('enable'); // setting value to null allows you to open the same file again if needed $("#rg2-load-gps-file").val(null).button('enable'); + $("#btn-get-strava").button('enable'); rg2.redraw(false); }, diff --git a/js/gpstrack.js b/js/gpstrack.js index 12db5fd..7ba54aa 100644 --- a/js/gpstrack.js +++ b/js/gpstrack.js @@ -119,6 +119,31 @@ } } }, + + uploadStrava : function(json){ + for (i=0; i < json.data.length; i++){ + + const d = json.data[i]; + + if (d.type === 'latlng'){ + + for (l=0; l < d.data.length; l++){ + const lat = d.data[l][0]; + const lon = d.data[l][1]; + this.lat.push(lat); + this.lon.push(lon); + }; + + } else if (d.type === 'time'){ + + for (t=0; t < d.data.length; t++){ + const tm = d.data[t] + this.routeData.time.push(tm) + }; + } + }; + this.processGPSTrack(); + }, getStartOffset : function (timestring) { var secs; diff --git a/js/rg2getjson.js b/js/rg2getjson.js index 9b0c70e..9c0f4ec 100644 --- a/js/rg2getjson.js +++ b/js/rg2getjson.js @@ -127,6 +127,9 @@ rg2.courses.generateControlList(rg2.controls); $("#btn-toggle-controls").show(); $("#btn-toggle-names").show(); + if (json.data.strava ==='false'){ + $("#btn-get-strava").hide(); + } processResults(json); processGPSTracks(json); rg2.eventLoaded(); diff --git a/js/rg2ui.js b/js/rg2ui.js index 45975cc..48af2e9 100644 --- a/js/rg2ui.js +++ b/js/rg2ui.js @@ -145,6 +145,7 @@ $("#btn-reset-drawing").button().button("disable").click(function () { rg2.drawing.resetDrawing(); }); + $("#btn-get-strava").button().button("disable"); $("#btn-save-gps-route").button().button("disable").click(function () { rg2.drawing.saveGPSRoute(); }); @@ -663,6 +664,34 @@ $("#rg2-load-gps-file").change(function (evt) { rg2.drawing.uploadGPS(evt); }); + + $("#btn-get-strava").click(function(){ + //open oauth popup. return list of latest activities. + rg2.utils.oauthpopup({ + path: 'https://www.strava.com/oauth/authorize?client_id=65468&response_type=code&redirect_uri='+window.location.protocol+'//'+window.location.hostname+'/rg2/rg2api.php&approval_prompt=force&scope=activity:read_all', + callback: function(activities){ + + //parse the returned json + var activ = JSON.parse(activities); + var act = activ.activities; + var token = activ.access_token; + + //reset div to empty + document.getElementById('strava-activities').innerHTML = ''; + //disable load button + $("#btn-get-strava").button("disable"); + + //add current activities to div so user can select one + //limit to 10 + for (var i=0; i<10; i++){ + var a = act[i]; + var activityId = a.id.toString() + rg2.utils.addStravaActivitySelector('strava-activities', activityId, a.name, token, a.type, a.start_date_local); + } + } + }); + }); + }, configureUI: function () { diff --git a/js/utils.js b/js/utils.js index 5e3f252..39d0fde 100644 --- a/js/utils.js +++ b/js/utils.js @@ -2,6 +2,101 @@ /*global rg2Config:false */ (function () { var utils = { + + oauthpopup : function (options){ + // Open another window for oauth authorisation. Pass token back to main window when successful. + options.windowName = options.windowName || 'ConnectWithOAuth'; // should not include space for IE + options.windowOptions = options.windowOptions || 'location=0,status=0,width=800,height=400'; + options.callback = options.callback || function(){ window.location.reload(); }; + var that = this; + console.log(options.path); + that._oauthWindow = window.open(options.path, options.windowName, options.windowOptions); + that._oauthInterval = window.setInterval(function(){ + + var activities = ''; + + if (that._oauthWindow.document.body.innerHTML.startsWith('{"access_token":')){ + activities = that._oauthWindow.document.body.innerHTML; + that._oauthWindow.close(); + } + + if (that._oauthWindow.closed) { + window.clearInterval(that._oauthInterval); + options.callback(activities); + } + }, 1000); + }, + + addStravaActivitySelector: function (elementId, activityId, name, token, type, dt){ + + //Reformat string as date + dat = dt.substring(0,10)+ " "+ dt.substring(11,16) + + //create button elements + tr = document.getElementById(elementId).insertRow(); + + var td = tr.insertCell(); + + var actTitle = document.createElement("span"); + actTitle.setAttribute("id", "act-title"); + actTitle.innerText = name; + td.appendChild(actTitle); + + var td = tr.insertCell(); + + var actType = document.createElement("span"); + actType.setAttribute("id", "act-type"); + actType.innerText = type; + td.appendChild(actType); + + var td = tr.insertCell(); + + var actDate = document.createElement("span"); + actDate.setAttribute("id", "act-date"); + actDate.innerText = dat; + td.appendChild(actDate); + + var td = tr.insertCell(); + + var btn = document.createElement("button"); + btn.setAttribute("id", "act-btn"); + btn.className = "act-btn"; + btn.innerText = 'Add'; + td.appendChild(btn); + + //request activity stream onclick and load to interface. + btn.onclick = function() { + btn.innerText = "Added"; + + //disable other buttons + var cusid_ele = document.getElementsByClassName('act-btn'); + for (var i = 0; i < cusid_ele.length; ++i) { + var item = cusid_ele[i]; + if (item.innerText === 'Add'){ + //hide other buttons. Not strictly necessary as clicking different add will override + //but probably more obvious to the user if we don't allow it? + item.style.visibility = 'hidden'; + } else { + //disable Added button + item.style.pointerEvents = 'none'; + } + } + + // get activity stream + $.getJSON(rg2Config.json_url, { + type : "stravaActivityStream", + id : activityId+'|'+token, + cache : false + }).done(function (json) { + rg2.drawing.uploadStrava(json); + }).fail(function (jqxhr, textStatus, error) { + /*jslint unparam:true*/ + reportJSONFail("Activities request failed: " + error); + }); + } + + }, + rotatePoint : function (x, y, angle) { // rotation matrix: see http://en.wikipedia.org/wiki/Rotation_matrix var pt = {}; diff --git a/rg2-config .txt b/rg2-config .txt index a3dc7e0..e1e4804 100644 --- a/rg2-config .txt +++ b/rg2-config .txt @@ -55,4 +55,8 @@ // // or //define('EPSG_CODE', "EPSG:12345|EPSG:67890"); - //define('EPSG_PARAMS', "+proj=x +ellps=WGS84 +datum=WGS84 +units=m +no_defs|+proj=y +ellps=WGS84 +datum=WGS84 +units=m +no_defs"); \ No newline at end of file + //define('EPSG_PARAMS', "+proj=x +ellps=WGS84 +datum=WGS84 +units=m +no_defs|+proj=y +ellps=WGS84 +datum=WGS84 +units=m +no_defs"); + + // If using strava, add the application oauth client and secret + //define('STRAVA_CLIENT', '') + //define('STRAVA_SECRET', '') \ No newline at end of file diff --git a/rg2api.php b/rg2api.php index f190766..3141f14 100644 --- a/rg2api.php +++ b/rg2api.php @@ -11,6 +11,7 @@ require(dirname(__FILE__) . '/app/splitsbrowser.php'); require(dirname(__FILE__) . '/app/user.php'); require(dirname(__FILE__) . '/app/utils.php'); + require(dirname(__FILE__) . '/app/strava.php'); require_once(dirname(__FILE__) . '/rg2-config.php'); @@ -55,18 +56,25 @@ } else { $id = 0; } + if (isset($_GET['code'])) { + $code = $_GET['code']; + } else { + $code = 'unknown'; + } - if ($_SERVER['REQUEST_METHOD'] == 'GET') { - handleGetRequest($type, $id); + if ($_SERVER['REQUEST_METHOD'] == 'GET' && $code == 'unknown') { + handleGetRequest($type, $id); + } elseif ($_SERVER['REQUEST_METHOD'] == 'GET' && $code !== 'unknown') { + strava::getStravaActivities($code); } elseif ($_SERVER['REQUEST_METHOD'] == 'POST') { - if ($type == 'uploadmapfile') { - map::uploadMapFile(); - } else { - handlePostRequest($type, $id); - } + if ($type == 'uploadmapfile') { + map::uploadMapFile(); + } else { + handlePostRequest($type, $id); + } } else { - header('HTTP/1.1 405 Method Not Allowed'); - header('Allow: GET, POST'); + header('HTTP/1.1 405 Method Not Allowed'); + header('Allow: GET, POST'); } function handlePostRequest($type, $eventid) @@ -186,6 +194,9 @@ function handleGetRequest($type, $id) event::fixResults($id); $output = json_encode("Results fixed for event ".$id); break; + case 'stravaActivityStream': + $output = strava::getActivityStream($id); + break; default: utils::rg2log("Get request not recognised: ".$type.", ".$id); $output = json_encode("Request not recognised.");