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/Http/Controllers/WebMentionsController.php b/app/Http/Controllers/WebMentionsController.php index fc254058..ad0a59fe 100644 --- a/app/Http/Controllers/WebMentionsController.php +++ b/app/Http/Controllers/WebMentionsController.php @@ -28,7 +28,7 @@ class WebMentionsController extends Controller } //next check the $target is valid - $path = parse_url($request->input('target'))['path']; + $path = parse_url($request->input('target'), PHP_URL_PATH); $pathParts = explode('/', $path); switch ($pathParts[1]) { @@ -36,9 +36,8 @@ class WebMentionsController extends Controller //we have a note $noteId = $pathParts[2]; $numbers = new Numbers(); - $realId = $numbers->b60tonum($noteId); try { - $note = Note::findOrFail($realId); + $note = Note::findOrFail($numbers->b60tonum($noteId)); $this->dispatch(new ProcessWebMention($note, $request->input('source'))); } catch (ModelNotFoundException $e) { return new Response('This note doesn’t exist.', 400); diff --git a/app/Jobs/ProcessWebMention.php b/app/Jobs/ProcessWebMention.php index b2593427..380ffb94 100644 --- a/app/Jobs/ProcessWebMention.php +++ b/app/Jobs/ProcessWebMention.php @@ -2,8 +2,8 @@ namespace App\Jobs; +use Mf2; use App\Note; -use Mf2\parse; use HTMLPurifier; use App\WebMention; use GuzzleHttp\Client; @@ -11,14 +11,18 @@ use HTMLPurifier_Config; use Illuminate\Queue\SerializesModels; 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. @@ -27,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(); } /** @@ -44,113 +49,85 @@ class ProcessWebMention extends Job implements ShouldQueue $sourceURL = parse_url($this->source); $baseURL = $sourceURL['scheme'] . '://' . $sourceURL['host']; $remoteContent = $this->getRemoteContent($this->source); - $microformats = $this->parseHTML($remoteContent, $baseURL); - $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(); + if ($remoteContent === null) { + throw new RemoteContentNotFoundException; + } + $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; } /** * Retreive the remote content from a URL, and caches the result. * - * @param string The URL to retreive content from - * @return string The HTML from the URL + * @param string The URL to retreive content from + * @return string|null The HTML from the URL (or null if error) */ private function getRemoteContent($url) { - $client = new Client(); - - $response = $client->get($url); + try { + $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; } @@ -173,79 +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); - } - - /** - * A wrapper function for php-mf2’s parse method. - * - * @param string The HTML to parse - * @param string The base URL to resolve relative URLs in the HTML against - * @return array The porcessed microformats - */ - private function parseHTML($html, $baseurl) - { - $microformats = \Mf2\parse((string) $html, $baseurl); 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/app/Jobs/SendWebMentions.php b/app/Jobs/SendWebMentions.php index 4cfbaafc..4caf6c7d 100644 --- a/app/Jobs/SendWebMentions.php +++ b/app/Jobs/SendWebMentions.php @@ -20,9 +20,10 @@ class SendWebMentions extends Job implements ShouldQueue * @param Note $note * @return void */ - public function __construct(Note $note) + public function __construct(Note $note, Client $guzzle = null) { $this->note = $note; + $this->guzzle = $guzzle ?? new Client(); } /** @@ -31,16 +32,16 @@ class SendWebMentions extends Job implements ShouldQueue * @param \GuzzleHttp\Client $guzzle * @return void */ - public function handle(Client $guzzle) + public function handle() { //grab the URLs $urlsInReplyTo = explode(' ', $this->note->in_reply_to); $urlsNote = $this->getLinks($this->note->note); $urls = array_filter(array_merge($urlsInReplyTo, $urlsNote)); //filter out none URLs foreach ($urls as $url) { - $endpoint = $this->discoverWebmentionEndpoint($url, $guzzle); + $endpoint = $this->discoverWebmentionEndpoint($url, $this->guzzle); if ($endpoint) { - $guzzle->post($endpoint, [ + $this->guzzle->post($endpoint, [ 'form_params' => [ 'source' => $this->note->longurl, 'target' => $url, @@ -73,8 +74,8 @@ class SendWebMentions extends Job implements ShouldQueue //check HTTP Headers for webmention endpoint $links = \GuzzleHttp\Psr7\parse_header($response->getHeader('Link')); foreach ($links as $link) { - if ($link['rel'] == 'webmention') { - return trim($link[0], '<>'); + if (mb_stristr($link['rel'], 'webmention')) { + return $this->resolveUri($link[0], $url); } } @@ -89,11 +90,7 @@ class SendWebMentions extends Job implements ShouldQueue $endpoint = $rels[0]['http://webmention.org/'][0]; } if ($endpoint) { - if (filter_var($endpoint, FILTER_VALIDATE_URL)) { - return $endpoint; - } - //it must be a relative url, so resolve with php-mf2 - return $mf2->resolveUrl($endpoint); + return $this->resolveUri($endpoint, $url); } return false; @@ -105,7 +102,7 @@ class SendWebMentions extends Job implements ShouldQueue * @param string $html * @return array $urls */ - private function getLinks($html) + public function getLinks($html) { $urls = []; $dom = new \DOMDocument(); @@ -117,4 +114,23 @@ class SendWebMentions extends Job implements ShouldQueue return $urls; } + + /** + * Resolve a URI if necessary. + * + * @param string $url + * @param string $base + * @return string + */ + public function resolveUri(string $url, string $base): string + { + $endpoint = \GuzzleHttp\Psr7\uri_for($url); + if ($endpoint->getScheme() !== null) { + return (string) $endpoint; + } + return (string) \GuzzleHttp\Psr7\Uri::resolve( + \GuzzleHttp\Psr7\uri_for($base), + $endpoint + ); + } } diff --git a/changelog.md b/changelog.md index f6b62544..788e181e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## Version {next} + - Adding jsonb column to store webmentions’ mf2. + * As of L5.2 this needs a custom command to drop NOT NULL from content, L5.3 should allow a fix for this + - Refactor receiving webmention code + - Refactor sending webmention code to pass webmention.rocks + ## Version 0.0.8.5 (2016-07-18) - Set the size of the textarea in a form better - Update to latest Guzzle to fix CVE-2016-5385 diff --git a/composer.lock b/composer.lock index da7115b0..f7578a1f 100644 --- a/composer.lock +++ b/composer.lock @@ -59,20 +59,20 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.18.28", + "version": "3.18.35", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "c75d3ba185d5db6998124fa1a99a63e5d529b247" + "reference": "8133c4ce336c8cac97218546f896f491bb6ffd7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c75d3ba185d5db6998124fa1a99a63e5d529b247", - "reference": "c75d3ba185d5db6998124fa1a99a63e5d529b247", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/8133c4ce336c8cac97218546f896f491bb6ffd7e", + "reference": "8133c4ce336c8cac97218546f896f491bb6ffd7e", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "~5.3|~6.0.1|~6.1", + "guzzlehttp/guzzle": "^5.3.1|^6.2.1", "guzzlehttp/promises": "~1.0", "guzzlehttp/psr7": "~1.3.1", "mtdowling/jmespath.php": "~2.2", @@ -135,7 +135,7 @@ "s3", "sdk" ], - "time": "2016-07-13 20:34:06" + "time": "2016-07-29 01:07:59" }, { "name": "barnabywalters/mf-cleaner", @@ -182,12 +182,12 @@ "version": "0.16", "source": { "type": "git", - "url": "https://github.com/Bosnadev/Database.git", + "url": "https://github.com/bosnadev/database.git", "reference": "c2748d118415d30ce69b792448689285d01ffdb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Bosnadev/Database/zipball/c2748d118415d30ce69b792448689285d01ffdb9", + "url": "https://api.github.com/repos/bosnadev/database/zipball/c2748d118415d30ce69b792448689285d01ffdb9", "reference": "c2748d118415d30ce69b792448689285d01ffdb9", "shasum": "" }, @@ -385,16 +385,16 @@ }, { "name": "ezyang/htmlpurifier", - "version": "v4.7.0", + "version": "v4.8.0", "source": { "type": "git", "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "ae1828d955112356f7677c465f94f7deb7d27a40" + "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/ae1828d955112356f7677c465f94f7deb7d27a40", - "reference": "ae1828d955112356f7677c465f94f7deb7d27a40", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", + "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", "shasum": "" }, "require": { @@ -425,31 +425,26 @@ "keywords": [ "html" ], - "time": "2015-08-05 01:03:42" + "time": "2016-07-16 12:58:58" }, { "name": "geo-io/interface", - "version": "v1.0.0", + "version": "v1.0.1", "source": { "type": "git", "url": "https://github.com/geo-io/interface.git", - "reference": "cdbb55801e3f8d5485227c2031cc7a3c16ccd06a" + "reference": "cf46fe7b013de20ab8b601238c7d91b480810644" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/geo-io/interface/zipball/cdbb55801e3f8d5485227c2031cc7a3c16ccd06a", - "reference": "cdbb55801e3f8d5485227c2031cc7a3c16ccd06a", + "url": "https://api.github.com/repos/geo-io/interface/zipball/cf46fe7b013de20ab8b601238c7d91b480810644", + "reference": "cf46fe7b013de20ab8b601238c7d91b480810644", "shasum": "" }, "require": { "php": ">=5.3.3" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, "autoload": { "psr-4": { "GeoIO\\": "src/" @@ -471,7 +466,7 @@ "geometry", "io" ], - "time": "2015-04-17 18:52:52" + "time": "2016-07-28 07:17:02" }, { "name": "geo-io/wkb-parser", @@ -1134,16 +1129,16 @@ }, { "name": "laravel/framework", - "version": "v5.2.39", + "version": "5.2.41", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "c2a77050269b4e03bd9a735a9f24e573a7598b8a" + "reference": "29ba2e310cfeb42ab6545bcd81ff4c2ec1f6b5c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/c2a77050269b4e03bd9a735a9f24e573a7598b8a", - "reference": "c2a77050269b4e03bd9a735a9f24e573a7598b8a", + "url": "https://api.github.com/repos/laravel/framework/zipball/29ba2e310cfeb42ab6545bcd81ff4c2ec1f6b5c2", + "reference": "29ba2e310cfeb42ab6545bcd81ff4c2ec1f6b5c2", "shasum": "" }, "require": { @@ -1200,7 +1195,8 @@ "illuminate/support": "self.version", "illuminate/translation": "self.version", "illuminate/validation": "self.version", - "illuminate/view": "self.version" + "illuminate/view": "self.version", + "tightenco/collect": "self.version" }, "require-dev": { "aws/aws-sdk-php": "~3.0", @@ -1259,20 +1255,20 @@ "framework", "laravel" ], - "time": "2016-06-17 19:25:12" + "time": "2016-07-20 13:13:06" }, { "name": "lcobucci/jwt", - "version": "3.1.1", + "version": "3.1.2", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "afea8e682e911a21574fd8519321b32522fa25b5" + "reference": "9a8db2cd42346f96993928ff6a6c22563f555ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/afea8e682e911a21574fd8519321b32522fa25b5", - "reference": "afea8e682e911a21574fd8519321b32522fa25b5", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/9a8db2cd42346f96993928ff6a6c22563f555ab1", + "reference": "9a8db2cd42346f96993928ff6a6c22563f555ab1", "shasum": "" }, "require": { @@ -1280,7 +1276,7 @@ "php": ">=5.5" }, "require-dev": { - "mdanter/ecc": "~0.3", + "mdanter/ecc": "~0.3.1", "mikey179/vfsstream": "~1.5", "phpmd/phpmd": "~2.2", "phpunit/php-invoker": "~1.1", @@ -1317,7 +1313,7 @@ "JWS", "jwt" ], - "time": "2016-03-24 22:46:13" + "time": "2016-07-27 11:09:37" }, { "name": "league/commonmark", @@ -1676,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": { @@ -1750,7 +1746,7 @@ "logging", "psr-3" ], - "time": "2016-07-02 14:02:10" + "time": "2016-07-29 03:23:52" }, { "name": "mtdowling/cron-expression", @@ -2158,16 +2154,16 @@ }, { "name": "psr/http-message", - "version": "1.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298" + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", - "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "shasum": "" }, "require": { @@ -2195,6 +2191,7 @@ } ], "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ "http", "http-message", @@ -2203,7 +2200,7 @@ "request", "response" ], - "time": "2015-05-04 20:22:00" + "time": "2016-08-06 14:39:51" }, { "name": "psr/log", @@ -2656,16 +2653,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": { @@ -2712,20 +2709,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": { @@ -2769,20 +2766,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": { @@ -2829,11 +2826,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", @@ -2882,16 +2879,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": { @@ -2931,20 +2928,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": { @@ -3013,7 +3010,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", @@ -3184,16 +3181,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": { @@ -3229,11 +3226,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", @@ -3308,16 +3305,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": { @@ -3368,20 +3365,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": { @@ -3431,7 +3428,7 @@ "debug", "dump" ], - "time": "2016-06-29 05:40:00" + "time": "2016-07-26 08:03:56" }, { "name": "themattharris/tmhoauth", @@ -3681,16 +3678,16 @@ }, { "name": "filp/whoops", - "version": "2.1.2", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "d13505b240a6f580bc75ba591da30299d6cb0eec" + "reference": "8828aaa2178e0a19325522e2a45282ff0a14649b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/d13505b240a6f580bc75ba591da30299d6cb0eec", - "reference": "d13505b240a6f580bc75ba591da30299d6cb0eec", + "url": "https://api.github.com/repos/filp/whoops/zipball/8828aaa2178e0a19325522e2a45282ff0a14649b", + "reference": "8828aaa2178e0a19325522e2a45282ff0a14649b", "shasum": "" }, "require": { @@ -3737,7 +3734,7 @@ "whoops", "zf2" ], - "time": "2016-04-07 06:16:25" + "time": "2016-05-06 18:25:35" }, { "name": "fzaninotto/faker", @@ -4210,16 +4207,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "900370c81280cc0d942ffbc5912d80464eaee7e9" + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/900370c81280cc0d942ffbc5912d80464eaee7e9", - "reference": "900370c81280cc0d942ffbc5912d80464eaee7e9", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3", + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3", "shasum": "" }, "require": { @@ -4228,7 +4225,7 @@ "phpunit/php-text-template": "~1.2", "phpunit/php-token-stream": "^1.4.2", "sebastian/code-unit-reverse-lookup": "~1.0", - "sebastian/environment": "^1.3.2", + "sebastian/environment": "^1.3.2 || ^2.0", "sebastian/version": "~1.0|~2.0" }, "require-dev": { @@ -4269,7 +4266,7 @@ "testing", "xunit" ], - "time": "2016-06-03 05:03:56" + "time": "2016-07-26 14:39:29" }, { "name": "phpunit/php-file-iterator", @@ -4454,16 +4451,16 @@ }, { "name": "phpunit/phpunit", - "version": "5.4.6", + "version": "5.4.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2f1fc94b77ea6418bd6a06c64a1dac0645fbce59" + "reference": "3132365e1430c091f208e120b8845d39c25f20e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2f1fc94b77ea6418bd6a06c64a1dac0645fbce59", - "reference": "2f1fc94b77ea6418bd6a06c64a1dac0645fbce59", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3132365e1430c091f208e120b8845d39c25f20e6", + "reference": "3132365e1430c091f208e120b8845d39c25f20e6", "shasum": "" }, "require": { @@ -4475,7 +4472,7 @@ "myclabs/deep-copy": "~1.3", "php": "^5.6 || ^7.0", "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "^4.0", + "phpunit/php-code-coverage": "^4.0.1", "phpunit/php-file-iterator": "~1.4", "phpunit/php-text-template": "~1.2", "phpunit/php-timer": "^1.0.6", @@ -4528,7 +4525,7 @@ "testing", "xunit" ], - "time": "2016-06-16 06:01:15" + "time": "2016-07-26 14:48:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -5104,7 +5101,7 @@ }, { "name": "symfony/css-selector", - "version": "v3.0.8", + "version": "v3.0.9", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -5157,16 +5154,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": { @@ -5209,20 +5206,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": { @@ -5258,7 +5255,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/database/migrations/2016_07_29_113150_add_jsonb_mf2_column_to_webmentions_table.php b/database/migrations/2016_07_29_113150_add_jsonb_mf2_column_to_webmentions_table.php new file mode 100644 index 00000000..04ad6f0a --- /dev/null +++ b/database/migrations/2016_07_29_113150_add_jsonb_mf2_column_to_webmentions_table.php @@ -0,0 +1,33 @@ +jsonb('mf2')->nullable(); + $table->index(['mf2']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('webmentions', function (Blueprint $table) { + $table->dropIndex(['mf2']); + $table->dropColumn('mf2'); + }); + } +} 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)
{{ $repost['name'] }}
- reposted this at {{ $repost['date'] }}.