diff --git a/app/Http/Controllers/NotesController.php b/app/Http/Controllers/NotesController.php index 93fecc23..dbf5adc7 100644 --- a/app/Http/Controllers/NotesController.php +++ b/app/Http/Controllers/NotesController.php @@ -5,26 +5,31 @@ namespace App\Http\Controllers; use App\Note; use Illuminate\Http\Request; use Jonnybarnes\IndieWeb\Numbers; +use App\Services\ActivityStreamsService; // Need to sort out Twitter and webmentions! class NotesController extends Controller { /** - * Show all the notes. + * Show all the notes. This is also the homepage. * - * @param Illuminate\Http\Request request; * @return \Illuminte\View\Factory view */ - public function index(Request $request) + public function index() { + if (request()->wantsActivityStream()) { + return (new ActivityStreamsService)->siteOwnerResponse(); + } + $notes = Note::orderBy('id', 'desc') ->with('place', 'media', 'client') ->withCount(['webmentions As replies' => function ($query) { $query->where('type', 'in-reply-to'); }])->paginate(10); + $aslink = config('app.url'); - return view('notes.index', compact('notes')); + return view('notes.index', compact('notes', 'aslink')); } /** @@ -37,7 +42,13 @@ class NotesController extends Controller { $note = Note::nb60($urlId)->with('webmentions')->firstOrFail(); - return view('notes.show', compact('note')); + if (request()->wantsActivityStream()) { + return (new ActivityStreamsService)->singleNoteResponse($note); + } + + $aslink = $note->longurl; + + return view('notes.show', compact('note', 'aslink')); } /** diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 88d47aec..a79e464a 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -5,6 +5,7 @@ namespace App\Providers; use App\Tag; use App\Note; use Validator; +use Illuminate\Http\Request; use Laravel\Dusk\DuskServiceProvider; use Illuminate\Support\ServiceProvider; @@ -42,10 +43,15 @@ class AppServiceProvider extends ServiceProvider $tag = Tag::firstOrCreate(['tag' => $tag]); $tagsToAdd[] = $tag->id; } - if (count($tagsToAdd > 0)) { + if (count($tagsToAdd) > 0) { $note->tags()->attach($tagsToAdd); } }); + + // Request AS macro + Request::macro('wantsActivityStream', function() { + return str_contains(mb_strtolower($this->header('Accept')), 'application/activity+json'); + }); } /** diff --git a/app/Services/ActivityStreamsService.php b/app/Services/ActivityStreamsService.php new file mode 100644 index 00000000..6d948ea0 --- /dev/null +++ b/app/Services/ActivityStreamsService.php @@ -0,0 +1,50 @@ + 'https://www.w3.org/ns/activitystreams', + 'id' => config('app.url'), + 'type' => 'Person', + 'name' => config('app.display_name'), + 'preferredUsername' => 'jonnybarnes', + ]); + + return response($data)->header('Content-Type', 'application/activity+json'); + } + + public function singleNoteResponse(Note $note) + { + $data = json_encode([ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'summary' => 'Jonny added a note to his microblog', + 'type' => 'Add', + 'published' => $note->updated_at->toW3cString(), + 'actor' => [ + 'type' => 'Person', + 'id' => config('app.url'), + 'name' => config('app.display_name'), + 'url' => config('app.url'), + 'image' => [ + 'type' => 'Link', + 'href' => config('app.url') . '/assets/img/jmb-bw.jpg', + 'mediaType' => '/image/jpeg', + ], + ], + 'object' => [ + 'id' => $note->longurl, + 'type' => 'Note', + 'url' => $note->longurl, + 'name' => strip_tags($note->note) + ], + ]); + + return response($data)->header('Content-Type', 'application/activity+json'); + } +} diff --git a/app/Services/NoteService.php b/app/Services/NoteService.php index e2b91773..a858089f 100644 --- a/app/Services/NoteService.php +++ b/app/Services/NoteService.php @@ -37,7 +37,7 @@ class NoteService ); if (array_key_exists('published', $data) && empty($data['published']) === false) { - $carbon = new \Carbon\Carbon($data['published']); + $carbon = carbon($data['published']); $note->created_at = $note->updated_at = $carbon->toDateTimeString(); } diff --git a/app/WebMention.php b/app/WebMention.php index 3c1ee3a7..5e3d570f 100644 --- a/app/WebMention.php +++ b/app/WebMention.php @@ -5,7 +5,6 @@ namespace App; use Cache; use Twitter; use HTMLPurifier; -use Carbon\Carbon; use HTMLPurifier_Config; use Illuminate\Filesystem\Filesystem; use Illuminate\Database\Eloquent\Model; @@ -63,10 +62,9 @@ class WebMention extends Model public function getPublishedAttribute() { $microformats = json_decode($this->mf2, true); - $carbon = new Carbon(); if (isset($microformats['items'][0]['properties']['published'][0])) { try { - $published = $carbon->parse( + $published = carbon()->parse( $microformats['items'][0]['properties']['published'][0] )->toDayDateTimeString(); } catch (\Exception $exception) { diff --git a/changelog.md b/changelog.md index 14f965c0..d631dbdb 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,8 @@ # Changelog +## Version 0.7.1 (2017-09-13) + - Add content-negotiated AS data for homepage and single notes + ## Version 0.7 (2017-09-08) - Add Laravel Horizon diff --git a/composer.json b/composer.json index 78c96fd5..cd32e17b 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ }, "require-dev": { "barryvdh/laravel-debugbar": "~3.0", + "bmitch/churn-php": "^0.2.0", "filp/whoops": "~2.0", "fzaninotto/faker": "~1.4", "jakub-onderka/php-parallel-lint": "^0.9.2", diff --git a/composer.lock b/composer.lock index 224a0680..23c97c51 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "551d407b87a7d9ff3e65969f33605014", + "content-hash": "ee80722747b7215eddfb5da75063b542", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.36.3", + "version": "3.36.4", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "7ffaee4359c161339867e565f18f6e3b7e77e44e" + "reference": "acb08da60a042e17b0cd9e5e111ba895c8a79f2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7ffaee4359c161339867e565f18f6e3b7e77e44e", - "reference": "7ffaee4359c161339867e565f18f6e3b7e77e44e", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/acb08da60a042e17b0cd9e5e111ba895c8a79f2d", + "reference": "acb08da60a042e17b0cd9e5e111ba895c8a79f2d", "shasum": "" }, "require": { @@ -84,7 +84,7 @@ "s3", "sdk" ], - "time": "2017-09-07T22:30:02+00:00" + "time": "2017-09-08T19:50:29+00:00" }, { "name": "barnabywalters/mf-cleaner", @@ -303,16 +303,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.0.7", + "version": "1.0.8", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12" + "reference": "9dd73a03951357922d8aee6cc084500de93e2343" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b17e6153cb7f33c7e44eb59578dc12eee5dc8e12", - "reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/9dd73a03951357922d8aee6cc084500de93e2343", + "reference": "9dd73a03951357922d8aee6cc084500de93e2343", "shasum": "" }, "require": { @@ -358,7 +358,7 @@ "ssl", "tls" ], - "time": "2017-03-06T11:59:08+00:00" + "time": "2017-09-11T07:24:36+00:00" }, { "name": "cviebrock/eloquent-sluggable", @@ -1947,16 +1947,16 @@ }, { "name": "laravel/horizon", - "version": "v1.0.1", + "version": "v1.0.2", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "856fe55c6d054dc063e71d97c0873013803d96df" + "reference": "ec52cd6c304eb9dfc3320e32d9e43c5ee55e7d7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/856fe55c6d054dc063e71d97c0873013803d96df", - "reference": "856fe55c6d054dc063e71d97c0873013803d96df", + "url": "https://api.github.com/repos/laravel/horizon/zipball/ec52cd6c304eb9dfc3320e32d9e43c5ee55e7d7c", + "reference": "ec52cd6c304eb9dfc3320e32d9e43c5ee55e7d7c", "shasum": "" }, "require": { @@ -2011,7 +2011,7 @@ "laravel", "queue" ], - "time": "2017-09-06T19:57:45+00:00" + "time": "2017-09-08T16:30:09+00:00" }, { "name": "laravel/scout", @@ -4400,6 +4400,67 @@ ], "time": "2017-08-29T08:54:25+00:00" }, + { + "name": "bmitch/churn-php", + "version": "0.2.0", + "source": { + "type": "git", + "url": "https://github.com/bmitch/churn-php.git", + "reference": "0026a7db3bebb83dc9b1cd1941e1cb15629c1dc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bmitch/churn-php/zipball/0026a7db3bebb83dc9b1cd1941e1cb15629c1dc8", + "reference": "0026a7db3bebb83dc9b1cd1941e1cb15629c1dc8", + "shasum": "" + }, + "require": { + "php": "^7.0", + "symfony/console": "^3.2", + "symfony/process": "^3.2", + "symfony/yaml": "^3.3", + "tightenco/collect": "^5.4" + }, + "require-dev": { + "bmitch/codor": "^1.0", + "jakub-onderka/php-console-highlighter": "^0.3.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "larapack/dd": "^1.1", + "mockery/mockery": "dev-master", + "phploc/phploc": "^3.0", + "phpmd/phpmd": "^2.5", + "phpunit/phpunit": "^5.7", + "sebastian/phpcpd": "^2.0", + "sensiolabs/security-checker": "^4.0", + "slevomat/coding-standard": "^2.0" + }, + "bin": [ + "churn" + ], + "type": "library", + "autoload": { + "psr-4": { + "Churn\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bill Mitchell", + "email": "wkmitch@gmail.com" + } + ], + "description": "Discover files in need of refactoring.", + "homepage": "https://github.com/bmitch/churn-php", + "keywords": [ + "bmitch", + "churn-php" + ], + "time": "2017-09-02T00:42:45+00:00" + }, { "name": "doctrine/instantiator", "version": "1.1.0", @@ -6290,6 +6351,61 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "symfony/yaml", + "version": "v3.3.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "1d8c2a99c80862bdc3af94c1781bf70f86bccac0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/1d8c2a99c80862bdc3af94c1781bf70f86bccac0", + "reference": "1d8c2a99c80862bdc3af94c1781bf70f86bccac0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-07-29T21:54:42+00:00" + }, { "name": "theseer/fdomdocument", "version": "1.6.6", diff --git a/helpers.php b/helpers.php index d70efc56..7e5a0852 100644 --- a/helpers.php +++ b/helpers.php @@ -16,6 +16,7 @@ if (! function_exists('normalize_url')) { $newUrl = ''; $url = parse_url($url); $defaultSchemes = ['http' => 80, 'https' => 443]; + if (isset($url['scheme'])) { $url['scheme'] = strtolower($url['scheme']); // Strip scheme default ports @@ -27,10 +28,12 @@ if (! function_exists('normalize_url')) { } $newUrl .= "{$url['scheme']}://"; } + if (isset($url['host'])) { $url['host'] = mb_strtolower($url['host']); $newUrl .= $url['host']; } + if (isset($url['port'])) { $newUrl .= ":{$url['port']}"; } @@ -63,10 +66,14 @@ if (! function_exists('normalize_url')) { } $url['path'] = preg_replace_callback( array_map( - create_function('$str', 'return "/%" . strtoupper($str) . "/x";'), + function ($str) { + return "/%" . strtoupper($str) . "/x"; + }, $u ), - create_function('$matches', 'return chr(hexdec($matches[0]));'), + function ($matches) { + return chr(hexdec($matches[0])); + }, $url['path'] ); // Remove directory index @@ -197,3 +204,10 @@ if (! function_exists('prettyPrintJson')) { return str_replace("\t", ' ', $result); } } + +// sourced from https://twitter.com/jrubsc/status/907776591320764416/photo/1 +if (! function_exists('carbon')) { + function carbon(...$args) { + return new Carbon\Carbon(...$args); + } +} diff --git a/package-lock.json b/package-lock.json index 96c10fa7..1b72344d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,11 +29,6 @@ "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.0.0.tgz", "integrity": "sha1-wd5CkwgUJNo6wwwjr6hQrxAZu1Q=" }, - "JSV": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", - "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=" - }, "abbrev": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", @@ -3889,6 +3884,14 @@ } } }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, "string-width": { "version": "1.0.2", "bundled": true, @@ -3899,14 +3902,6 @@ "strip-ansi": "3.0.1" } }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, "stringstream": { "version": "0.0.5", "bundled": true, @@ -4817,6 +4812,11 @@ } } }, + "JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=" + }, "kdbush": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-1.0.1.tgz", @@ -7587,6 +7587,21 @@ "integrity": "sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4=", "dev": true }, + "string_decoder": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", + "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", + "requires": { + "safe-buffer": "5.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" + } + } + }, "string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", @@ -7605,21 +7620,6 @@ "strip-ansi": "3.0.1" } }, - "string_decoder": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.2.tgz", - "integrity": "sha1-sp4fThEl+pehA4K4pTNze3SR4Xk=", - "requires": { - "safe-buffer": "5.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", - "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=" - } - } - }, "stringify-object": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.0.tgz", @@ -8520,14 +8520,6 @@ } } }, - "webStorage": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/webStorage/-/webStorage-1.2.4.tgz", - "integrity": "sha1-/jNN8N5uLe58i9A2uxVaw115FTY=", - "requires": { - "gr-event-dispatcher": "1.1.1" - } - }, "webpack": { "version": "3.5.6", "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.5.6.tgz", @@ -8790,6 +8782,14 @@ } } }, + "webStorage": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/webStorage/-/webStorage-1.2.4.tgz", + "integrity": "sha1-/jNN8N5uLe58i9A2uxVaw115FTY=", + "requires": { + "gr-event-dispatcher": "1.1.1" + } + }, "webworkify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/webworkify/-/webworkify-1.4.0.tgz", diff --git a/resources/views/master.blade.php b/resources/views/master.blade.php index fbe53414..0c9855dc 100644 --- a/resources/views/master.blade.php +++ b/resources/views/master.blade.php @@ -12,6 +12,8 @@ +@isset($aslink) +@endisset diff --git a/tests/Browser/ExampleTest.php b/tests/Browser/ExampleTest.php deleted file mode 100644 index a0099346..00000000 --- a/tests/Browser/ExampleTest.php +++ /dev/null @@ -1,23 +0,0 @@ -browse(function (Browser $browser) { - $browser->visit('/') - ->assertSee('Built with love'); - }); - } -} diff --git a/tests/Feature/ActivityStreamTest.php b/tests/Feature/ActivityStreamTest.php new file mode 100644 index 00000000..5d7b2c24 --- /dev/null +++ b/tests/Feature/ActivityStreamTest.php @@ -0,0 +1,50 @@ +get('/', ['Accept' => 'application/activity+json']); + $response->assertHeader('Content-Type', 'application/activity+json'); + $response->assertJson([ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => config('app.url'), + 'type' => 'Person', + 'name' => config('app.display_name'), + ]); + } + + /** + * Test request to a single note returns AS2.0 data. + * + * @return void + */ + public function test_single_note_returns_as_data() + { + $note = \App\Note::find(11); + $response = $this->get('/notes/B', ['Accept' => 'application/activity+json']); + $response->assertHeader('Content-Type', 'application/activity+json'); + $response->assertJson([ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Add', + 'actor' => [ + 'type' => 'Person', + 'id' => config('app.url'), + ], + 'object' => [ + 'type' => 'Note', + 'name' => strip_tags($note->note) + ] + ]); + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php deleted file mode 100644 index f31e495c..00000000 --- a/tests/Feature/ExampleTest.php +++ /dev/null @@ -1,21 +0,0 @@ -get('/'); - - $response->assertStatus(200); - } -} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index e9fe19c6..00000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,19 +0,0 @@ -assertTrue(true); - } -}