diff options
-rw-r--r-- | controllers/auth.php | 4 | ||||
-rw-r--r-- | controllers/controllers.php | 18 | ||||
-rw-r--r-- | lib/helpers.php | 71 | ||||
-rw-r--r-- | views/dashboard.php | 87 | ||||
-rw-r--r-- | views/docs.php | 33 |
5 files changed, 205 insertions, 8 deletions
diff --git a/controllers/auth.php b/controllers/auth.php index abbe3b9..13081c4 100644 --- a/controllers/auth.php +++ b/controllers/auth.php @@ -235,6 +235,10 @@ $app->get('/auth/callback', function() use($app) { $user->micropub_response = $token['response']; $user->save(); $_SESSION['user_id'] = $user->id(); + + // Make a request to the micropub endpoint to discover the syndication targets if any. + // Errors are silently ignored here. The user will be able to retry from the new post interface and get feedback. + get_syndication_targets($user); } unset($_SESSION['auth_state']); diff --git a/controllers/controllers.php b/controllers/controllers.php index 5b28bb8..dc18584 100644 --- a/controllers/controllers.php +++ b/controllers/controllers.php @@ -32,6 +32,7 @@ $app->get('/new', function() use($app) { 'micropub_scope' => $user->micropub_scope, 'micropub_access_token' => $user->micropub_access_token, 'response_date' => $user->last_micropub_response_date, + 'syndication_targets' => json_decode($user->syndication_targets, true), 'test_response' => $test_response, 'location_enabled' => $user->location_enabled )); @@ -104,12 +105,28 @@ $app->get('/add-to-home', function() use($app) { } }); +$app->get('/micropub/syndications', function() use($app) { + if($user=require_login($app)) { + $data = get_syndication_targets($user); + $app->response()->body(json_encode(array( + 'targets' => $data['targets'], + 'response' => $data['response'] + ))); + } +}); + $app->post('/micropub/post', function() use($app) { if($user=require_login($app)) { $params = $app->request()->params(); + // Remove any blank params + $params = array_filter($params, function($v){ + return $v !== ''; + }); + // Now send to the micropub endpoint $r = micropub_post($user->micropub_endpoint, $params, $user->micropub_access_token); + $request = $r['request']; $response = $r['response']; $user->last_micropub_response = json_encode($r); @@ -126,6 +143,7 @@ $app->post('/micropub/post', function() use($app) { $user->save(); $app->response()->body(json_encode(array( + 'request' => htmlspecialchars($request), 'response' => htmlspecialchars($response), 'location' => $location, 'error' => $r['error'], diff --git a/lib/helpers.php b/lib/helpers.php index 7748c4e..60eda75 100644 --- a/lib/helpers.php +++ b/lib/helpers.php @@ -73,20 +73,87 @@ function micropub_post($endpoint, $params, $access_token) { 'Authorization: Bearer ' . $access_token )); curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array_merge(array( + $post = http_build_query(array_merge(array( 'h' => 'entry' - ), $params))); + ), $params)); + curl_setopt($ch, CURLOPT_POSTFIELDS, $post); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLINFO_HEADER_OUT, true); $response = curl_exec($ch); $error = curl_error($ch); + $sent_headers = curl_getinfo($ch, CURLINFO_HEADER_OUT); + $request = $sent_headers . $post; return array( + 'request' => $request, 'response' => $response, 'error' => $error, 'curlinfo' => curl_getinfo($ch) ); } +function micropub_get($endpoint, $params, $access_token) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $endpoint . '?' . http_build_query($params)); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Authorization: Bearer ' . $access_token + )); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + $data = array(); + if($response) { + parse_str($response, $data); + } + $error = curl_error($ch); + return array( + 'response' => $response, + 'data' => $data, + 'error' => $error, + 'curlinfo' => curl_getinfo($ch) + ); +} + +function get_syndication_targets(&$user) { + $targets = array(); + + $r = micropub_get($user->micropub_endpoint, array('q'=>'syndicate-to'), $user->micropub_access_token); + if($r['data'] && array_key_exists('syndicate-to', $r['data'])) { + $targetURLs = preg_split('/, ?/', $r['data']['syndicate-to']); + foreach($targetURLs as $t) { + + // If the syndication target doesn't have a scheme, add http + if(!preg_match('/^http/', $t)) + $tmp = 'http://' . $t; + + // Parse the target expecting it to be a URL + $url = parse_url($tmp); + + // If there's a host, and the host contains a . then we can assume there's a favicon + // parse_url will parse strings like http://twitter into an array with a host of twitter, which is not resolvable + if(array_key_exists('host', $url) && strpos($url['host'], '.') !== false) { + $targets[] = array( + 'target' => $t, + 'favicon' => 'http://' . $url['host'] . '/favicon.ico' + ); + } else { + $targets[] = array( + 'target' => $t, + 'favicon' => false + ); + } + } + } + if(count($targets)) { + $user->syndication_targets = json_encode($targets); + $user->save(); + } + + return array( + 'targets' => $targets, + 'response' => $r + ); +} + function static_map($latitude, $longitude, $height=180, $width=700, $zoom=14) { return 'http://static-maps.pdx.esri.com/img.php?marker[]=lat:' . $latitude . ';lng:' . $longitude . ';icon:small-blue-cutout&basemap=gray&width=' . $width . '&height=' . $height . '&zoom=' . $zoom; } diff --git a/views/dashboard.php b/views/dashboard.php index cabeedc..d88a59c 100644 --- a/views/dashboard.php +++ b/views/dashboard.php @@ -24,6 +24,24 @@ </div> <div class="form-group"> + <label for="note_syndicate-to"><code>syndicate-to</code> <a href="javascript:reload_syndications()">(refresh)</a></label> + <div id="syndication-container"> + <?php + if($this->syndication_targets) { + echo '<ul>'; + foreach($this->syndication_targets as $syn) { + echo '<li><button data-syndication="'.$syn['target'].'" class="btn btn-default btn-block"><img src="'.$syn['favicon'].'" width="16" height="16"> '.$syn['target'].'</button></li>'; + } + echo '</ul>'; + } else { + ?><div class="bs-callout bs-callout-warning">No syndication targets were found on your site. + You can provide a <a href="/docs#syndication">list of supported syndication targets</a> that will appear as checkboxes here.</div><?php + } + ?> + </div> + </div> + + <div class="form-group"> <label for="note_location"><code>location</code></label> <input type="checkbox" id="note_location_chk" value=""> <img src="/images/spinner.gif" id="note_location_loading" style="display: none;"> @@ -45,6 +63,11 @@ <div class="alert alert-danger hidden" id="test_error"><strong>Your endpoint did not return a Location header.</strong><br>See <a href="/creating-a-micropub-endpoint">Creating a Micropub Endpoint</a> for more information.</div> + <div id="last_request_container" style="display: none;"> + <h4>Request made to your Micropub endpoint</h4> + <pre id="test_request" style="width: 100%; min-height: 140px;"></pre> + </div> + <?php if($this->test_response): ?> <h4>Last response from your Micropub endpoint <span id="last_response_date">(<?= relative_time($this->response_date) ?>)</span></h4> <?php endif; ?> @@ -80,17 +103,26 @@ $(function(){ $("#btn_post").click(function(){ + + var syndications = []; + $("#syndication-container button.btn-info").each(function(i,btn){ + syndications.push($(btn).data('syndication')); + }); + $.post("/micropub/post", { content: $("#note_content").val(), - in_reply_to: $("#note_in_reply_to").val(), + 'in-reply-to': $("#note_in_reply_to").val(), location: $("#note_location").val(), category: $("#note_category").val(), - slug: $("#note_slug").val() + slug: $("#note_slug").val(), + 'syndicate-to': syndications.join(',') }, function(data){ var response = JSON.parse(data); if(response.location != false) { - $("#note_form").slideUp(); + $("#note_form").slideUp(200, function(){ + $(window).scrollTop($("#test_success").position().top); + }); $("#test_success").removeClass('hidden'); $("#test_error").addClass('hidden'); @@ -107,8 +139,11 @@ $(function(){ } $("#last_response_date").html("(just now)"); + $("#test_request").html(response.request); + $("#last_request_container").show(); $("#test_response").html(response.response); - }) + }); + return false; }); function location_error(msg) { @@ -174,10 +209,54 @@ $(function(){ fetch_location(); } + bind_syndication_buttons(); }); + +function reload_syndications() { + $.getJSON("/micropub/syndications", function(data){ + if(data.targets) { + $("#syndication-container").html('<ul></ul>'); + for(var i in data.targets) { + var target = data.targets[i].target; + var favicon = data.targets[i].favicon; + $("#syndication-container ul").append('<li><button data-syndication="'+target+'" class="btn btn-default btn-block"><img src="'+favicon+'" width="16" height="16"> '+target+'</button></li>'); + } + bind_syndication_buttons(); + } else { + + } + console.log(data); + }); +} + +function bind_syndication_buttons() { + $("#syndication-container button").unbind("click").click(function(){ + $(this).toggleClass('btn-info'); + return false; + }); +} + </script> <style type="text/css"> + #syndication-container ul { + list-style-type: none; + margin: 0; + padding: 10px; + } + #syndication-container li { + padding: 0; + margin-bottom: 6px; + } + #syndication-container button { + max-width: 240px; + text-shadow: none; + } + #syndication-container button img { + float: left; + margin-left: 10px; + } + #last_response_date { font-size: 80%; font-weight: normal; diff --git a/views/docs.php b/views/docs.php index 48fd0d8..94856a1 100644 --- a/views/docs.php +++ b/views/docs.php @@ -1,7 +1,7 @@ <div class="narrow"> <?= partial('partials/header') ?> - <h2>Introduction</h2> + <h2 id="introduction">Introduction</h2> <div class="col-xs-6 col-md-4" style="float: right;"> <span class="thumbnail"><img src="/images/quill-ui.png"></span> @@ -15,7 +15,9 @@ <p>Once you've signed in, you'll see an interface like the one shown which you can use to write a post. Clicking "post" will make a Micropub request to your endpoint.<p> - <h2>Configuring Endpoints</h2> + + + <h2 id="endpoints">Configuring Endpoints</h2> <h3>Authorization Endpoint</h3> <?= partial('partials/auth-endpoint-help') ?> @@ -28,4 +30,31 @@ <p>The <a href="/creating-a-micropub-endpoint">Creating a Micropub Endpoint</a> tutorial will walk you through how to handle incoming POST requests from apps like this.</p> + + + <h2 id="syndication">Syndication Targets</h2> + + <p>You can provide a list of supported syndication targets that will appear as checkboxes when you are creating a new post.</p> + + <p>To do this, your Micropub endpoint will need to respond to a GET request containing a query string of <code>q=syndicate-to</code>. This request will be made with the access token that was generated for this app, so you can choose which syndication targets you want to allow this app to use.</p> + + <p>Below is the request and expected response that Quill looks for.</p> + + <pre><code>GET /micropub?q=syndicate-to HTTP/1.1 +Authorization: Bearer xxxxxxxxxx + +HTTP/1.1 200 OK +Content-type: application/x-www-form-urlencoded + +syndicate-to=syndicate-to=twitter.com%2Faaronpk%2Cfacebook.com%2Faaronpk +</code></pre> + + <p>The response should be a form-encoded reply with a single field, <code>syndicate-to</code>. The value is a comma-separated list of syndication targets. The actual values are up to your Micropub endpoint, but a good convention is to use the domain name of the service (e.g. twitter.com), or domain name and username (e.g. twitter.com/aaronpk).</p> + + <p>If you do include the domain name, Quill will be able to show icons for recognized services next to the checkboxes.</p> + + <p>Quill will check for your supported syndication targets when you sign in, but there is also a link on the new post screen to manually re-check if you'd like.</p> + + + </div>
\ No newline at end of file |