From a9f089098c8e57d2685eb8af32f456d0999bafc7 Mon Sep 17 00:00:00 2001 From: Jonny Barnes Date: Wed, 3 Aug 2016 16:08:30 +0100 Subject: [PATCH] Work on webmwtion code refactoring --- app/Http/Controllers/NotesController.php | 98 +++++++---- app/Jobs/ProcessWebMention.php | 212 ++++++++--------------- app/Jobs/SaveProfileImage.php | 67 +++++++ composer.lock | 116 ++++++------- resources/views/singlenote.blade.php | 4 +- tests/NotesTest.php | 25 +-- tests/ProcessWebMentionTest.php | 29 ++++ tests/WebMentionsTest.php | 92 ++++++++++ 8 files changed, 395 insertions(+), 248 deletions(-) create mode 100644 app/Jobs/SaveProfileImage.php create mode 100644 tests/ProcessWebMentionTest.php create mode 100644 tests/WebMentionsTest.php diff --git a/app/Http/Controllers/NotesController.php b/app/Http/Controllers/NotesController.php index 1d6125fe..590ff654 100644 --- a/app/Http/Controllers/NotesController.php +++ b/app/Http/Controllers/NotesController.php @@ -7,6 +7,8 @@ use Twitter; use App\Tag; use App\Note; use Jonnybarnes\IndieWeb\Numbers; +use Illuminate\Filesystem\Filesystem; +use Jonnybarnes\WebmentionsParser\Authorship; // Need to sort out Twitter and webmentions! @@ -23,8 +25,8 @@ class NotesController extends Controller foreach ($notes as $note) { $replies = 0; foreach ($note->webmentions as $webmention) { - if ($webmention->type == 'reply') { - $replies = $replies + 1; + if ($webmention->type == 'in-reply-to') { + $replies++; } } $note->replies = $replies; @@ -67,31 +69,51 @@ class NotesController extends Controller public function singleNote($urlId) { $numbers = new Numbers(); + $authorship = new Authorship(); $realId = $numbers->b60tonum($urlId); $note = Note::find($realId); $replies = []; $reposts = []; $likes = []; foreach ($note->webmentions as $webmention) { + /* + reply->url | + reply->photo | Author + reply->name | + reply->source + reply->date + reply->reply + + repost->url | + repost->photo | Author + repost->name | + repost->date + repost->source + + like->url | + like->photo | Author + like->name | + */ + $microformats = json_decode($webmention->mf2); + $authorHCard = $authorship->findAuthor($microformats); + $content['url'] = $authorHCard['properties']['url'][0]; + $content['photo'] = $this->createPhotoLink($authorHCard['properties']['photo'][0]); + $content['name'] = $authorHCard['properties']['name'][0]; switch ($webmention->type) { - case 'reply': - $content = unserialize($webmention->content); - $content['source'] = $this->bridgyReply($webmention->source); - $content['photo'] = $this->createPhotoLink($content['photo']); + case 'in-reply-to': + $content['source'] = $webmention->source; $content['date'] = $carbon->parse($content['date'])->toDayDateTimeString(); + $content['reply'] = $microformats['items'][0]['properties']['content'][0]['html_purified']; $replies[] = $content; break; - case 'repost': - $content = unserialize($webmention->content); - $content['photo'] = $this->createPhotoLink($content['photo']); + case 'repost-of': $content['date'] = $carbon->parse($content['date'])->toDayDateTimeString(); + $content['source'] = $webmention->source; $reposts[] = $content; break; - case 'like': - $content = unserialize($webmention->content); - $content['photo'] = $this->createPhotoLink($content['photo']); + case 'like-of': $likes[] = $content; break; } @@ -164,41 +186,43 @@ class NotesController extends Controller return view('taggednotes', ['notes' => $notes, 'tag' => $tag]); } - /** - * Swap a brid.gy URL shim-ing a twitter reply to a real twitter link. - * - * @param string - * @return string - */ - public function bridgyReply($source) - { - $url = $source; - if (mb_substr($source, 0, 28, 'UTF-8') == 'https://brid-gy.appspot.com/') { - $parts = explode('/', $source); - $tweetId = array_pop($parts); - if ($tweetId) { - $url = 'https://twitter.com/_/status/' . $tweetId; - } - } - - return $url; - } - /** * Create the photo link. * + * We shall leave twitter.com and twimg.com links as they are. Then we shall + * check for local copies, if that fails leave the link as is. + * * @param string * @return string */ public function createPhotoLink($url) { - $host = parse_url($url)['host']; - if ($host != 'twitter.com' && $host != 'pbs.twimg.com') { - return '/assets/profile-images/' . $host . '/image'; - } - if (mb_substr($url, 0, 20) == 'http://pbs.twimg.com') { + $host = parse_url($url, PHP_URL_HOST); + if ($host == 'pbs.twimg.com') { + //make sure we use HTTPS, we know twitter supports it return str_replace('http://', 'https://', $url); } + if ($host == 'twitter.com') { + if (Cache::has($url)) { + return Cache::get($url); + } + $username = parse_url($url, PHP_URL_PATH); + try { + $info = Twitter::getUsers(['screen_name' => $username]); + $profile_image = $info->profile_image_url_https; + Cache::put($url, $profile_image, 10080); //1 week + } catch (Exception $e) { + return $url; //not sure here + } + + return $profile_image; + } + $filesystem = new Filesystem(); + if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) { + return '/assets/profile-images/' . $host . '/image'; + } + + return $url; } /** diff --git a/app/Jobs/ProcessWebMention.php b/app/Jobs/ProcessWebMention.php index 324fee6a..380ffb94 100644 --- a/app/Jobs/ProcessWebMention.php +++ b/app/Jobs/ProcessWebMention.php @@ -13,13 +13,16 @@ use Illuminate\Queue\InteractsWithQueue; use Jonnybarnes\WebmentionsParser\Parser; use GuzzleHttp\Exception\RequestException; use Illuminate\Contracts\Queue\ShouldQueue; +use Illuminate\Foundation\Bus\DispatchesJobs; +use App\Exceptions\RemoteContentNotFoundException; class ProcessWebMention extends Job implements ShouldQueue { - use InteractsWithQueue, SerializesModels; + use InteractsWithQueue, SerializesModels, DispatchesJobs; protected $note; protected $source; + protected $guzzle; /** * Create a new job instance. @@ -28,10 +31,11 @@ class ProcessWebMention extends Job implements ShouldQueue * @param string $source * @return void */ - public function __construct(Note $note, $source) + public function __construct(Note $note, $source, Client $guzzle = null) { $this->note = $note; $this->source = $source; + $this->guzzle = $guzzle ?? new Client(); } /** @@ -46,100 +50,60 @@ class ProcessWebMention extends Job implements ShouldQueue $baseURL = $sourceURL['scheme'] . '://' . $sourceURL['host']; $remoteContent = $this->getRemoteContent($this->source); if ($remoteContent === null) { - return false; + throw new RemoteContentNotFoundException; } - $mf2Parser = new Mf2\Parser($remoteContent, $baseURL, true); - $microformats = $mf2Parser->parse(); - $count = WebMention::where('source', '=', $this->source)->count(); - if ($count > 0) { - //we already have a webmention from this source - $webmentions = WebMention::where('source', '=', $this->source)->get(); - foreach ($webmentions as $webmention) { - //now check it still 'mentions' this target - //we switch for each type of mention (reply/like/repost) - switch ($webmention->type) { - case 'reply': - if ($parser->checkInReplyTo($microformats, $note->longurl) == false) { - //it doesn't so delete - $webmention->delete(); + $microformats = Mf2\parse($remoteContent, $baseURL); + $webmentions = WebMention::where('source', $this->source)->get(); + foreach ($webmentions as $webmention) { + //check webmention still references target + //we try each type of mention (reply/like/repost) + if ($webmention->type == 'in-reply-to') { + if ($parser->checkInReplyTo($microformats, $this->note->longurl) == false) { + //it doesn't so delete + $webmention->delete(); - return true; - } - //webmenion is still a reply, so update content - $content = $parser->replyContent($microformats); - $this->saveImage($content); - $content['reply'] = $this->filterHTML($content['reply']); - $content = serialize($content); - $webmention->content = $content; - $webmention->save(); + return; + } + //webmenion is still a reply, so update content + $microformats = $this->filterHTML($microformats); + $this->dispatch(new SaveProfileImage($microformats)); + $webmention->mf2 = json_encode($microformats); + $webmention->save(); - return true; - break; - case 'like': - if ($parser->checkLikeOf($microformats, $note->longurl) == false) { - //it doesn't so delete - $webmention->delete(); + return; + } + if ($webmention->type == 'like-of') { + if ($parser->checkLikeOf($microformats, $note->longurl) == false) { + //it doesn't so delete + $webmention->delete(); - return true; - } //note we don't need to do anything if it still is a like - break; - case 'repost': - if ($parser->checkRepostOf($microformats, $note->longurl) == false) { - //it doesn't so delete - $webmention->delete(); + return; + } //note we don't need to do anything if it still is a like + } + if ($webmention->type == 'repost-of') { + if ($parser->checkRepostOf($microformats, $note->longurl) == false) { + //it doesn't so delete + $webmention->delete(); + + return; + } //again, we don't need to do anything if it still is a repost + } + }//foreach - return true; - } //again, we don't need to do anything if it still is a repost - break; - }//switch - }//foreach - }//if //no wemention in db so create new one $webmention = new WebMention(); - //check it is in fact a reply - if ($parser->checkInReplyTo($microformats, $note->longurl)) { - $content = $parser->replyContent($microformats); - $this->saveImage($content); - $content['reply'] = $this->filterHTML($content['reply']); - $content = serialize($content); - $webmention->source = $this->source; - $webmention->target = $note->longurl; - $webmention->commentable_id = $this->note->id; - $webmention->commentable_type = 'App\Note'; - $webmention->type = 'reply'; - $webmention->content = $content; - $webmention->save(); + $type = $parser->getMentionType($microformats); //throw error here? + $this->dispatch(new SaveProfileImage($microformats)); + $microformats = $this->filterHTML($microformats); + $webmention->source = $this->source; + $webmention->target = $this->note->longurl; + $webmention->commentable_id = $this->note->id; + $webmention->commentable_type = 'App\Note'; + $webmention->type = $type; + $webmention->mf2 = json_encode($microformats); + $webmention->save(); - return true; - } elseif ($parser->checkLikeOf($microformats, $note->longurl)) { - //it is a like - $content = $parser->likeContent($microformats); - $this->saveImage($content); - $content = serialize($content); - $webmention->source = $this->source; - $webmention->target = $note->longurl; - $webmention->commentable_id = $this->note->id; - $webmention->commentable_type = 'App\Note'; - $webmention->type = 'like'; - $webmention->content = $content; - $webmention->save(); - - return true; - } elseif ($parser->checkRepostOf($microformats, $note->longurl)) { - //it is a repost - $content = $parser->repostContent($microformats); - $this->saveImage($content); - $content = serialize($content); - $webmention->source = $this->source; - $webmention->target = $note->longurl; - $webmention->commentable_id = $this->note->id; - $webmention->commentable_type = 'App\Note'; - $webmention->type = 'repost'; - $webmention->content = $content; - $webmention->save(); - - return true; - } + return; } /** @@ -150,16 +114,20 @@ class ProcessWebMention extends Job implements ShouldQueue */ private function getRemoteContent($url) { - $client = new Client(); - try { - $response = $client->request('GET', $url); + $response = $this->guzzle->request('GET', $url); } catch (RequestException $e) { return; } $html = (string) $response->getBody(); $path = storage_path() . '/HTML/' . $this->createFilenameFromURL($url); - $this->fileForceContents($path, $html); + $parts = explode('/', $path); + $name = array_pop($parts); + $dir = implode('/', $parts); + if (! is_dir($dir)) { + mkdir($dir, 0755, true); + } + file_put_contents("$dir/$name", $html); return $html; } @@ -182,65 +150,29 @@ class ProcessWebMention extends Job implements ShouldQueue } /** - * Save a file, and create any necessary folders. + * Filter the HTML in a reply webmention. * - * @param string The directory to save to - * @param binary The file to save + * @param array The unfiltered microformats + * @return array The filtered microformats */ - private function fileForceContents($dir, $contents) + private function filterHTML($microformats) { - $parts = explode('/', $dir); - $name = array_pop($parts); - $dir = implode('/', $parts); - if (! is_dir($dir)) { - mkdir($dir, 0755, true); + if (isset($microformats['items'][0]['properties']['content'][0]['html'])) { + $microformats['items'][0]['properties']['content'][0]['html_purified'] = $this->useHTMLPurifier( + $microformats['items'][0]['properties']['content'][0]['html'] + ); } - file_put_contents("$dir/$name", $contents); + + return $microformats; } /** - * Save a profile image to the local cache. - * - * @param array source content - * @return bool wether image was saved or not (we don’t save twitter profiles) - */ - public function saveImage(array $content) - { - $photo = $content['photo']; - $home = $content['url']; - //dont save pbs.twimg.com links - if (parse_url($photo)['host'] != 'pbs.twimg.com' - && parse_url($photo)['host'] != 'twitter.com') { - $client = new Client(); - try { - $response = $client->get($photo); - $image = $response->getBody(true); - $path = public_path() . '/assets/profile-images/' . parse_url($home)['host'] . '/image'; - $this->fileForceContents($path, $image); - } catch (Exception $e) { - // we are openning and reading the default image so that - // fileForceContent work - $default = public_path() . '/assets/profile-images/default-image'; - $handle = fopen($default, 'rb'); - $image = fread($handle, filesize($default)); - fclose($handle); - $path = public_path() . '/assets/profile-images/' . parse_url($home)['host'] . '/image'; - $this->fileForceContents($path, $image); - } - - return true; - } - - return false; - } - - /** - * Purify HTML received from a webmention. + * Set up and use HTMLPurifer on some HTML. * * @param string The HTML to be processed * @return string The processed HTML */ - public function filterHTML($html) + private function useHTMLPurifier($html) { $config = HTMLPurifier_Config::createDefault(); $config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier'); diff --git a/app/Jobs/SaveProfileImage.php b/app/Jobs/SaveProfileImage.php new file mode 100644 index 00000000..5600fe21 --- /dev/null +++ b/app/Jobs/SaveProfileImage.php @@ -0,0 +1,67 @@ +microformats = $microformats; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle(Authorship $authorship) + { + try { + $author = $authorship->findAuthor($microformats); + } catch (AuthorshipParserException $e) { + return; + } + $photo = $author['properties'][0]['photo'][0]; + $home = $author['properties'][0]['url'][0]; + //dont save pbs.twimg.com links + if (parse_url($photo, PHP_URL_HOST) != 'pbs.twimg.com' + && parse_url($photo, PHP_URL_HOST) != 'twitter.com') { + $client = new Client(); + try { + $response = $client->get($photo); + $image = $response->getBody(true); + } catch (RequestException $e) { + // we are openning and reading the default image so that + $default = public_path() . '/assets/profile-images/default-image'; + $handle = fopen($default, 'rb'); + $image = fread($handle, filesize($default)); + fclose($handle); + } + $path = public_path() . '/assets/profile-images/' . parse_url($home, PHP_URL_HOST) . '/image'; + $parts = explode('/', $path); + $name = array_pop($parts); + $dir = implode('/', $parts); + if (! is_dir($dir)) { + mkdir($dir, 0755, true); + } + file_put_contents("$dir/$name", $image); + } + } +} diff --git a/composer.lock b/composer.lock index 36372915..92da6d7a 100644 --- a/composer.lock +++ b/composer.lock @@ -1672,16 +1672,16 @@ }, { "name": "monolog/monolog", - "version": "1.20.0", + "version": "1.21.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "55841909e2bcde01b5318c35f2b74f8ecc86e037" + "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/55841909e2bcde01b5318c35f2b74f8ecc86e037", - "reference": "55841909e2bcde01b5318c35f2b74f8ecc86e037", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f42fbdfd53e306bda545845e4dbfd3e72edb4952", + "reference": "f42fbdfd53e306bda545845e4dbfd3e72edb4952", "shasum": "" }, "require": { @@ -1746,7 +1746,7 @@ "logging", "psr-3" ], - "time": "2016-07-02 14:02:10" + "time": "2016-07-29 03:23:52" }, { "name": "mtdowling/cron-expression", @@ -2652,16 +2652,16 @@ }, { "name": "symfony/console", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a7abb7153f6d1da47f87ec50274844e246b09d9f" + "reference": "926061e74229e935d3c5b4e9ba87237316c6693f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a7abb7153f6d1da47f87ec50274844e246b09d9f", - "reference": "a7abb7153f6d1da47f87ec50274844e246b09d9f", + "url": "https://api.github.com/repos/symfony/console/zipball/926061e74229e935d3c5b4e9ba87237316c6693f", + "reference": "926061e74229e935d3c5b4e9ba87237316c6693f", "shasum": "" }, "require": { @@ -2708,20 +2708,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2016-06-29 07:02:21" + "time": "2016-07-30 07:22:48" }, { "name": "symfony/debug", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "c54bc3539c3b87e86799533801e8ae0e971d78c2" + "reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/c54bc3539c3b87e86799533801e8ae0e971d78c2", - "reference": "c54bc3539c3b87e86799533801e8ae0e971d78c2", + "url": "https://api.github.com/repos/symfony/debug/zipball/697c527acd9ea1b2d3efac34d9806bf255278b0a", + "reference": "697c527acd9ea1b2d3efac34d9806bf255278b0a", "shasum": "" }, "require": { @@ -2765,20 +2765,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:40:00" + "time": "2016-07-30 07:22:48" }, { "name": "symfony/event-dispatcher", - "version": "v3.1.2", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "7f9839ede2070f53e7e2f0849b9bd14748c434c5" + "reference": "c0c00c80b3a69132c4e55c3e7db32b4a387615e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7f9839ede2070f53e7e2f0849b9bd14748c434c5", - "reference": "7f9839ede2070f53e7e2f0849b9bd14748c434c5", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c0c00c80b3a69132c4e55c3e7db32b4a387615e5", + "reference": "c0c00c80b3a69132c4e55c3e7db32b4a387615e5", "shasum": "" }, "require": { @@ -2825,11 +2825,11 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:41:56" + "time": "2016-07-19 10:45:57" }, { "name": "symfony/finder", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -2878,16 +2878,16 @@ }, { "name": "symfony/http-foundation", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "1341139f906d295baa4f4abd55293d07e25a065a" + "reference": "49ba00f8ede742169cb6b70abe33243f4d673f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/1341139f906d295baa4f4abd55293d07e25a065a", - "reference": "1341139f906d295baa4f4abd55293d07e25a065a", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49ba00f8ede742169cb6b70abe33243f4d673f82", + "reference": "49ba00f8ede742169cb6b70abe33243f4d673f82", "shasum": "" }, "require": { @@ -2927,20 +2927,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2016-06-29 07:02:21" + "time": "2016-07-17 13:54:30" }, { "name": "symfony/http-kernel", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "177b63b2d50b63fa6d82ea41359ed9928cc7a1fb" + "reference": "d97ba4425e36e79c794e7d14ff36f00f081b37b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/177b63b2d50b63fa6d82ea41359ed9928cc7a1fb", - "reference": "177b63b2d50b63fa6d82ea41359ed9928cc7a1fb", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d97ba4425e36e79c794e7d14ff36f00f081b37b3", + "reference": "d97ba4425e36e79c794e7d14ff36f00f081b37b3", "shasum": "" }, "require": { @@ -3009,7 +3009,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2016-06-30 16:30:17" + "time": "2016-07-30 09:10:37" }, { "name": "symfony/polyfill-mbstring", @@ -3180,16 +3180,16 @@ }, { "name": "symfony/process", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d7cde1f9d94d87060204f863779389b61c382eeb" + "reference": "768debc5996f599c4372b322d9061dba2a4bf505" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d7cde1f9d94d87060204f863779389b61c382eeb", - "reference": "d7cde1f9d94d87060204f863779389b61c382eeb", + "url": "https://api.github.com/repos/symfony/process/zipball/768debc5996f599c4372b322d9061dba2a4bf505", + "reference": "768debc5996f599c4372b322d9061dba2a4bf505", "shasum": "" }, "require": { @@ -3225,11 +3225,11 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:40:00" + "time": "2016-07-28 11:13:34" }, { "name": "symfony/routing", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", @@ -3304,16 +3304,16 @@ }, { "name": "symfony/translation", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "6bf844e1ee3c820c012386c10427a5c67bbefec8" + "reference": "eee6c664853fd0576f21ae25725cfffeafe83f26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/6bf844e1ee3c820c012386c10427a5c67bbefec8", - "reference": "6bf844e1ee3c820c012386c10427a5c67bbefec8", + "url": "https://api.github.com/repos/symfony/translation/zipball/eee6c664853fd0576f21ae25725cfffeafe83f26", + "reference": "eee6c664853fd0576f21ae25725cfffeafe83f26", "shasum": "" }, "require": { @@ -3364,20 +3364,20 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:40:00" + "time": "2016-07-30 07:22:48" }, { "name": "symfony/var-dumper", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "2f046e9a9d571f22cc8b26783564876713b06579" + "reference": "1f7e071aafc6676fcb6e3f0497f87c2397247377" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/2f046e9a9d571f22cc8b26783564876713b06579", - "reference": "2f046e9a9d571f22cc8b26783564876713b06579", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/1f7e071aafc6676fcb6e3f0497f87c2397247377", + "reference": "1f7e071aafc6676fcb6e3f0497f87c2397247377", "shasum": "" }, "require": { @@ -3427,7 +3427,7 @@ "debug", "dump" ], - "time": "2016-06-29 05:40:00" + "time": "2016-07-26 08:03:56" }, { "name": "themattharris/tmhoauth", @@ -5100,7 +5100,7 @@ }, { "name": "symfony/css-selector", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -5153,16 +5153,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "62769e3409006b937bb333b29da8df9a8b262975" + "reference": "dff8fecf1f56990d88058e3a1885c2a5f1b8e970" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/62769e3409006b937bb333b29da8df9a8b262975", - "reference": "62769e3409006b937bb333b29da8df9a8b262975", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/dff8fecf1f56990d88058e3a1885c2a5f1b8e970", + "reference": "dff8fecf1f56990d88058e3a1885c2a5f1b8e970", "shasum": "" }, "require": { @@ -5205,20 +5205,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:40:00" + "time": "2016-07-30 07:22:48" }, { "name": "symfony/yaml", - "version": "v3.1.2", + "version": "v3.1.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de" + "reference": "1819adf2066880c7967df7180f4f662b6f0567ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/2884c26ce4c1d61aebf423a8b912950fe7c764de", - "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de", + "url": "https://api.github.com/repos/symfony/yaml/zipball/1819adf2066880c7967df7180f4f662b6f0567ac", + "reference": "1819adf2066880c7967df7180f4f662b6f0567ac", "shasum": "" }, "require": { @@ -5254,7 +5254,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2016-06-29 05:41:56" + "time": "2016-07-17 14:02:08" }, { "name": "webmozart/assert", diff --git a/resources/views/singlenote.blade.php b/resources/views/singlenote.blade.php index d564616e..9c5f6525 100644 --- a/resources/views/singlenote.blade.php +++ b/resources/views/singlenote.blade.php @@ -20,13 +20,13 @@ @if(count($likes) > 0)

Likes

@endif @foreach($likes as $like) - + @endforeach @if(count($reposts) > 0)

Reposts

@endif @foreach($reposts as $repost)

{{ $repost['name'] }} - reposted this at {{ $repost['date'] }}.

+ reposted this at {{ $repost['date'] }}.

@endforeach @stop diff --git a/tests/NotesTest.php b/tests/NotesTest.php index 233704e3..c1ac954d 100644 --- a/tests/NotesTest.php +++ b/tests/NotesTest.php @@ -2,6 +2,7 @@ namespace App\Tests; +use Cache; use TestCase; use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\DatabaseMigrations; @@ -131,15 +132,15 @@ class NotesTest extends TestCase } /** - * Test the bridgy url shim method. + * Test a correct profile link is formed from a generic URL. * * @return void */ - public function testBridgy() + public function testCreatePhotoLinkWithNonCachedImage() { - $url = 'https://brid-gy.appspot.com/comment/twitter/jonnybarnes/497778866816299008/497781260937203712'; - $expected = 'https://twitter.com/_/status/497781260937203712'; - $this->assertEquals($expected, $this->notesController->bridgyReply($url)); + $homepage = 'https://example.org/profile.png'; + $expected = 'https://example.org/profile.png'; + $this->assertEquals($expected, $this->notesController->createPhotoLink($homepage)); } /** @@ -147,10 +148,10 @@ class NotesTest extends TestCase * * @return void */ - public function testCreatePhotoLinkWithGenericURL() + public function testCreatePhotoLinkWithCachedImage() { - $homepage = 'https://example.org'; - $expected = '/assets/profile-images/example.org/image'; + $homepage = 'https://aaronparecki.com/profile.png'; + $expected = '/assets/profile-images/aaronparecki.com/image'; $this->assertEquals($expected, $this->notesController->createPhotoLink($homepage)); } @@ -159,7 +160,7 @@ class NotesTest extends TestCase * * @return void */ - public function testCreatePhotoLinkWithTwitterProfileImageURL() + public function testCreatePhotoLinkWithTwimgProfileImageURL() { $twitterProfileImage = 'http://pbs.twimg.com/1234'; $expected = 'https://pbs.twimg.com/1234'; @@ -171,9 +172,11 @@ class NotesTest extends TestCase * * @return void */ - public function testCreatePhotoLinkWithTwitterURL() + public function testCreatePhotoLinkWithCachedTwitterURL() { $twitterURL = 'https://twitter.com/example'; - $this->assertNull($this->notesController->createPhotoLink($twitterURL)); + $expected = 'https://pbs.twimg.com/static_profile_link.jpg'; + Cache::put($twitterURL, $expected, 1); + $this->assertEquals($expected, $this->notesController->createPhotoLink($twitterURL)); } } diff --git a/tests/ProcessWebMentionTest.php b/tests/ProcessWebMentionTest.php new file mode 100644 index 00000000..77e04ba0 --- /dev/null +++ b/tests/ProcessWebMentionTest.php @@ -0,0 +1,29 @@ +appurl = config('app.url'); + } + + /** + * A basic test. + * + * @return void + */ + public function testExample() + { + + } +} diff --git a/tests/WebMentionsTest.php b/tests/WebMentionsTest.php new file mode 100644 index 00000000..a0bf502c --- /dev/null +++ b/tests/WebMentionsTest.php @@ -0,0 +1,92 @@ +appurl = config('app.url'); + } + + /** + * Test webmentions without source and target are rejected. + * + * @return void + */ + public function testWebmentionsWithoutSourceAndTargetAreRejected() + { + $this->call('POST', $this->appurl . '/webmention', ['source' => 'https://example.org/post/123']); + $this->assertResponseStatus(400) + ->see('You need both the target and source parameters'); + } + + /** + * Test invalid target gets a 400 response. + * + * @return void + */ + public function testInvalidTargetReturns400Response() + { + $this->call('POST', $this->appurl . '/webmention', [ + 'source' => 'https://example.org/post/123', + 'target' => $this->appurl . '/invalid/target' + ]); + $this->assertResponseStatus(400) + ->see('Invalid request'); + } + + /** + * Test blog target gets a 501 response. + * + * @return void + */ + public function testBlogpostTargetReturns501Response() + { + $this->call('POST', $this->appurl . '/webmention', [ + 'source' => 'https://example.org/post/123', + 'target' => $this->appurl . '/blog/target' + ]); + $this->assertResponseStatus(501) + ->see('I don’t accept webmentions for blog posts yet.'); + } + + /** + * Test that a non-existant note gives a 400 response. + * + * @return void + */ + public function testNonexistantNoteReturns400Response() + { + $this->call('POST', $this->appurl . '/webmention', [ + 'source' => 'https://example.org/post/123', + 'target' => $this->appurl . '/notes/ZZZZZ' + ]); + $this->assertResponseStatus(400) + ->see('This note doesn’t exist.'); + } + + /** + * Test a legit webmention triggers the ProcessWebMention job. + * + * @return void + */ + public function testLegitimateWebmnetionTriggersProcessWebMentionJob() + { + $this->expectsJobs(\App\Jobs\ProcessWebMention::class); + $this->call('POST', $this->appurl . '/webmention', [ + 'source' => 'https://example.org/post/123', + 'target' => $this->appurl . '/notes/B' + ]); + $this->assertResponseStatus(202) + ->see('Webmention received, it will be processed shortly'); + } +}