Improved test code coverage, including necessary refactor

Squashed commit of the following:

commit 18996bc4916e945e130ad4abb17149ea2d0afa93
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Dec 7 14:46:41 2017 +0000

    Fixing styleci issue

commit e5bfedfdede637d3090603b55946cc34aec8b52d
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Dec 7 14:45:47 2017 +0000

    The Micorpub controller istelf is basically refactored now

commit 9eacc968c8bf9b0f5e746c38f04423c51f678d50
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Dec 7 14:16:23 2017 +0000

    Add more tests, easily into 90% plus test coverage now

commit 2dcbf94298841884083f752f17de46ef7809a369
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Dec 7 10:57:42 2017 +0000

    Fix some styleci issues

commit 6a989f61af412d59751281bc09ba0778597f09ec
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Dec 7 10:56:13 2017 +0000

    First real attempt at refactoring micropub controller

commit c10da3d630ca8e06b120122041bb6c64c1084325
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Dec 5 16:51:20 2017 +0000

    Fix a styleci issue

commit d7ee556fdebdd2160620e65d4bf3c67134f01187
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Dec 5 16:47:52 2017 +0000

    Improve the code for the media endpoint and add tests

commit bc81bbaff3fad8afcc2135d76f213bba8ba54534
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Dec 5 16:47:07 2017 +0000

    Modify process image to process media, this allows for improvment within the micropub controller

commit d13035cb2cbeff41d15ff017beb9a5b6bb41daee
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Dec 4 15:14:18 2017 +0000

    Better name the current tests

commit 0a2ef16f74f3e322a945a8d6b239be3f97e8c5b8
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Dec 4 14:08:13 2017 +0000

    Reach 100% code coverage for webmentions

commit 60f8b196a0fb4e6570b74d7577a9ff5f6c218613
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Dec 4 13:35:03 2017 +0000

    Fix a styleci issue

commit e96be1869da909b8b080a38e4e33f94656b5b786
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Dec 4 13:32:54 2017 +0000

    Add tests for Notes Admin code

commit 213319798bf5b3118034ab18a7c9eca5cf18296f
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Dec 4 12:44:02 2017 +0000

    Fix a styleci issue

commit a614332c7664f66edcb78ef23db392a417c0deb0
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Dec 4 12:43:02 2017 +0000

    Fix a phpcs issue

commit 6a2e445dbf87fb8b48a24b34ca0fc3518b4376b6
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Dec 4 12:20:19 2017 +0000

    Add some tests fopr editing places in the admin cp

commit 7044f2555228ffd7df2823c0c19a4380ca886b5d
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Dec 4 11:35:59 2017 +0000

    Add tests for contacts admin code

commit 280c4498dbe2cf2879a08ee45a90a0c9ff2f296a
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Sat Dec 2 13:45:27 2017 +0000

    Improve admin related tests

commit ee93a108d11053b1f2fe38f30f3219b3e96267fc
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Sat Dec 2 00:01:42 2017 +0000

    Measure coverage with coveralls

commit 47c4f5088b1b12e4d183a72b9792e723bda64f77
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 23:57:32 2017 +0000

    Remove un-used PhotosController

commit 5737621dbc0ff7009896b483e1d004cdf2eb1523
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 23:47:08 2017 +0000

    Remove the curl test that was used for debugging purposes

commit b4d312b056df884eef7e4b0392b5dd1a3ae978bd
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 23:39:47 2017 +0000

    We don’t want ports in the longurl/shorturl env vars

commit 915370845448ef4e1be1ff5e7f4ef084c4200953
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 23:30:17 2017 +0000

    Use the right port

commit f9a3eac51063899fd003a8fa572dcf972d2f3a5c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 23:18:57 2017 +0000

    I can’t get nginx listening on custom domains

commit 3742a04cda5b5ae56ad839bf0ed74ddd9874e74c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 23:10:51 2017 +0000

    Try naked domains, i.e. without .localhost

commit d46040f86f32f0a70dae3bd1cf7c27465ee83a77
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 23:02:51 2017 +0000

    Add some default values back for pid and error_log

commit f15eb73b995f215e3b56243bbba49f23393beb5e
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 22:56:38 2017 +0000

    Let pid be the default value

commit 91048b96302b5f13c1eba4ae9ab226ff00859ea6
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 22:51:22 2017 +0000

    Don’t try and run php-fpm and nginx on the same port

commit 4203f7de976df9d0e832c09b18f15fff05837af7
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 22:36:26 2017 +0000

    Can only have one default_server

commit 6d117929d09707add7d687883d4cb6eba5881e96
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 22:10:35 2017 +0000

    Try and get a long url and short url both working

commit 995c7721e3dd96b01307cf75ae0f839fad96aaf9
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 22:01:14 2017 +0000

    Test the remaining shorturl redirects

commit d4c8a1a9c41c4e807d986b54c397b88a54292713
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 22:00:54 2017 +0000

    Get rid of some of my legacy redirects

commit 96d8c5b4cf576d66864a9e241cbef89b7bbe4597
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 21:46:42 2017 +0000

    Try and fix which url is being replied to

commit 692a7d0a2dcb3050f9c6da4afce2bb6c8ee72583
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 21:22:15 2017 +0000

    Add tests and improve code for the search feature

commit 442c26ff485a4d20710e1c38ab78bb1df212edda
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 20:04:00 2017 +0000

    Add tests for saving css preference in the session

commit e2180f0c40e4e8de293d2dd174816095affb0e3f
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 19:35:32 2017 +0000

    Some styleci fixes

commit a90bbe79c092ee4548f4fbad1399b99fddcb432c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 19:33:40 2017 +0000

    Don’t consider un-used laravel middleware when calculating code coverage

commit 15edeb0d0179d5a37648c454d316c99250ba9bea
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 19:27:42 2017 +0000

    Add some tests for the process webmention job

commit a354c63945c582196990823845b57c7e51ff182d
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 17:55:47 2017 +0000

    Add tests for save profile image job

commit 0230102bafddbccd81512c37330be0e2fcb39a53
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 15:05:30 2017 +0000

    Test sending webmentions

commit e664e911c5fcca7fc884eda39253b503970a8bc2
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 15:04:44 2017 +0000

    Fix typo in test name

commit 391d1aa8f584b9e8bd8a7154b68447a2098ddc1a
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 12:04:05 2017 +0000

    Add tests for syndication jobs

commit 16d8215c09ff235c5b8253189055ad6c17f2f009
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Dec 1 11:49:05 2017 +0000

    Fix issue with scope date and month being 12 the int or 12 the string

commit 0d9aaaa178b088a6856a70f4338fffea0ad8a0f3
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Nov 30 19:00:39 2017 +0000

    Improve Bookmark processing code and test coverage

commit 93d4419b37af7d0fcf8fe48cb855c1290512b30c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Nov 30 18:59:10 2017 +0000

    Improve Like processing code and test coverage

commit 1ccb42c48a4760daf9cf0bed744740aa931a6a89
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Nov 30 14:12:07 2017 +0000

    Try and install nginx natively, given its listed in travis-ci/apt-package-whitelist

commit 126a3c4c9639eae1a986674a952923dd4e1384cc
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Nov 30 12:09:47 2017 +0000

    Test the download webmention job

commit 2889e95c1526796684613d7fbb0998f654dbef28
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Nov 30 12:08:56 2017 +0000

    Test the add client job

commit 4054a24e93ffe5dd68c2ab211bf6a88b114c3f10
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Nov 27 21:21:09 2017 +0000

    Ignore code coverage for such a trivial command

commit 5bda55d401fa677a84d334b6200d7ce045e23a90
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Nov 27 18:22:18 2017 +0000

    For now don’t analyse laravel’s exception handler

commit bb21e2ffc620cf22c14b49f2f292616f0d1bcc77
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Nov 27 18:21:55 2017 +0000

    test coverage

commit 2980127778d5be8dee7dd119cc6eb20b7a9f5e52
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Nov 27 16:30:44 2017 +0000

    phpcs fixes

commit 6e802bf78a93e5886a38007177d54d33ed85b31b
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Nov 27 16:28:47 2017 +0000

    Increase and improve tests for bookmarks

commit 1f6ffbacb86be86faa2cd37022d50e41f9582d59
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Mon Nov 27 13:04:11 2017 +0000

    Increase test coverage for token generation/validation

commit 66d866fdbfd1dcd0f161d4d30c7fb79fbb4a7250
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Sun Nov 26 16:58:30 2017 +0000

    Increase test coverage for Place relasted code

commit 6774d5c837ccc8c7d9f1cd80607dd40ffb5257f5
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Sun Nov 26 16:57:42 2017 +0000

    Increase test coverage for Like relasted code

commit 4e055527276499eca721cf9be64473d91b30e1d9
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Sun Nov 26 10:21:21 2017 +0000

    Ignore code that can’t be tested from coverage analysis

commit fbe7a88ab2319a3dcf4d441bd6db7be49e6445c5
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Sun Nov 26 10:20:48 2017 +0000

    Increase test coverages for note creation/deletion

commit cae9bce276372a4870b0cbe7ebd561ef49d1b131
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Nov 24 15:16:52 2017 +0000

    Test a single bookmark page

commit e23cf9846ae5bb28554d7665aee92826e237dc38
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Nov 24 15:16:34 2017 +0000

    Test login route for admin page

commit 9e2902f2048edac949edd0cc73e1abbf84a4f224
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Nov 24 14:11:46 2017 +0000

    Improve articles controller to redirect outdated links, add tests

commit 49bfaaf615461149a06bcfb74fd7a676eb3d49f8
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Nov 24 13:57:03 2017 +0000

    Remove Auth controllers we don’t use

commit 5cebbc45aa72d24fc729c314c4f6ef1fc99c16d3
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Nov 24 13:36:11 2017 +0000

    More testing, looking at middleware being applied

commit 2e5c9dd77143d3b23b3461decd5d1cda1375c510
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Fri Nov 24 13:12:56 2017 +0000

    Tweak reply webmentions and re-parsing them, as well as add tests

commit 766739f42fe8432daa10172ae7fcff84fe29d78d
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Nov 23 21:18:56 2017 +0000

    Test redownload webmentions command dispatches the job

commit 6a25e99af4bad4ea3070a1ac4277542a52f6b465
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Nov 23 20:24:18 2017 +0000

    Fixing and increasing test coverage for notes, still some work left to do

commit 1e10fffb344b50a4665060d42e94cb564a8404e7
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Nov 23 15:16:11 2017 +0000

    Increase test coverage for places

commit 0b40ccedd285262b57be2414dfa2fd637e80ef2b
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Thu Nov 23 11:54:08 2017 +0000

    Tweak of code and increased test coverage

commit 64dc03b381fb79046e57ba2c623cb3a667adc345
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Nov 22 15:38:17 2017 +0000

    Increase text coverage for Tags

commit 62bdb775ec5ec2c06595fc4d427703c683ae81a7
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Nov 22 15:25:33 2017 +0000

    Increase test coverage for media-client code

commit 6bcf4cb909048e94aed7b7807bbe380833c258a1
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Nov 22 15:25:05 2017 +0000

    Incurease test coverage for media

commit 89ede0fd1c9d7ee66b1af8be2b4d75b03b4bd8b9
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Wed Nov 22 15:24:36 2017 +0000

    Fix some issues with notes and increase test coverage

commit 1ad7c952703e9098bd80c2024eeec0a0937c9db3
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Nov 21 14:40:33 2017 +0000

    Improve bookmarks test coverage

commit 5a3cb440edeb9c63e9c447a36b3000dcb0b0ec7a
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Nov 21 14:40:05 2017 +0000

    Add Like tests

commit 8481e367ea74b081269a99ee97175f49d71176b8
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Nov 21 14:08:38 2017 +0000

    Add a another Like to the db without content and with an auhtor_url

commit 4053dcf8ec02e8b3618eb3ad2b875870368eab49
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Nov 21 14:07:32 2017 +0000

    Don’t show the url instead of content, just leave blank

commit 8e12a125b927efd327323044e5f86beef6ccef8c
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Nov 21 14:05:36 2017 +0000

    Add tests for Article model

commit d827d3399a1ed9513c4bac05e5078345f2768081
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Nov 21 14:05:14 2017 +0000

    Make sure year and month are cast to ints for scopeDate

commit db747d95bc80e521604b4b3487bed6c7fd46b8fe
Author: Jonny Barnes <jonny@jonnybarnes.uk>
Date:   Tue Nov 21 14:04:26 2017 +0000

    Remove un-needed webmentions method, change scopeDate to use integers for month
This commit is contained in:
Jonny Barnes 2017-12-07 15:06:56 +00:00
parent 6e02e31f52
commit a4f07e510e
117 changed files with 3336 additions and 1330 deletions

View file

@ -17,7 +17,7 @@ class Article extends Model
*
* @var array
*/
protected $dates = ['deleted_at'];
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
/**
* The database table used by the model.
@ -40,16 +40,6 @@ class Article extends Model
];
}
/**
* Define the relationship with webmentions.
*
* @var array
*/
public function webmentions()
{
return $this->morphMany('App\WebMention', 'commentable');
}
/**
* We shall set a blacklist of non-modifiable model attributes.
*
@ -66,7 +56,7 @@ class Article extends Model
{
$markdown = new CommonMarkConverter();
$html = $markdown->convertToHtml($this->main);
//change <pre><code>[lang] ~> <pre><code data-language="lang">
// changes <pre><code>[lang] ~> <pre><code data-language="lang">
$match = '/<pre><code>\[(.*)\]\n/';
$replace = '<pre><code class="language-$1">';
$text = preg_replace($match, $replace, $html);
@ -130,20 +120,20 @@ class Article extends Model
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeDate($query, $year = null, $month = null)
public function scopeDate($query, int $year = null, int $month = null)
{
if ($year == null) {
return $query;
}
$start = $year . '-01-01 00:00:00';
$end = ($year + 1) . '-01-01 00:00:00';
if (($month !== null) && ($month !== '12')) {
if (($month !== null) && ($month !== 12)) {
$start = $year . '-' . $month . '-01 00:00:00';
$end = $year . '-' . ($month + 1) . '-01 00:00:00';
}
if ($month === '12') {
if ($month === 12) {
$start = $year . '-12-01 00:00:00';
//$end as above
$end = ($year + 1) . '-01-01 00:00:00';
}
return $query->where([

View file

@ -5,6 +5,9 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use SensioLabs\Security\SecurityChecker;
/**
* @codeCoverageIgnore
*/
class SecurityCheck extends Command
{
/**

View file

@ -7,6 +7,9 @@ use Illuminate\Support\Facades\Route;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
/**
* @codeCoverageIgnore
*/
class Handler extends ExceptionHandler
{
/**

View file

@ -1,10 +0,0 @@
<?php
namespace App\Exceptions;
use Exception;
class InternetArchiveErrorSavingException extends Exception
{
//
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions;
class InternetArchiveException extends \Exception
{
}

View file

@ -4,7 +4,7 @@ namespace App\Exceptions;
use Exception;
class RemoteContentNotFound extends Exception
class RemoteContentNotFoundException extends Exception
{
//used when guzzle cant find the remote content
}

View file

@ -86,9 +86,9 @@ class ClientsController extends Controller
* @param string The client id
* @return redirect
*/
public function destroy($articleId)
public function destroy($clientId)
{
MicropubClient::where('id', $articleId)->delete();
MicropubClient::where('id', $clientId)->delete();
return redirect('/admin/clients');
}

View file

@ -83,16 +83,14 @@ class ContactsController extends Controller
$contact->facebook = $request->input('facebook');
$contact->save();
if ($request->hasFile('avatar')) {
if ($request->input('homepage') != '') {
$dir = parse_url($request->input('homepage'))['host'];
$destination = public_path() . '/assets/profile-images/' . $dir;
$filesystem = new Filesystem();
if ($filesystem->isDirectory($destination) === false) {
$filesystem->makeDirectory($destination);
}
$request->file('avatar')->move($destination, 'image');
if ($request->hasFile('avatar') && ($request->input('homepage') != '')) {
$dir = parse_url($request->input('homepage'), PHP_URL_HOST);
$destination = public_path() . '/assets/profile-images/' . $dir;
$filesystem = new Filesystem();
if ($filesystem->isDirectory($destination) === false) {
$filesystem->makeDirectory($destination);
}
$request->file('avatar')->move($destination, 'image');
}
return redirect('/admin/contacts');
@ -123,37 +121,47 @@ class ContactsController extends Controller
*/
public function getAvatar($contactId)
{
// Initialising
$avatarURL = null;
$avatar = null;
$contact = Contact::findOrFail($contactId);
$homepage = $contact->homepage;
if (($homepage !== null) && ($homepage !== '')) {
$client = new Client();
if (mb_strlen($contact->homepage !== null) !== 0) {
$client = resolve(Client::class);
try {
$response = $client->get($homepage);
$html = (string) $response->getBody();
$mf2 = \Mf2\parse($html, $homepage);
$response = $client->get($contact->homepage);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
return "Bad Response from $homepage";
return redirect('/admin/contacts/' . $contactId . '/edit')
->with('error', 'Bad resposne from contacts homepage');
}
$avatarURL = null; // Initialising
$mf2 = \Mf2\parse((string) $response->getBody(), $contact->homepage);
foreach ($mf2['items'] as $microformat) {
if ($microformat['type'][0] == 'h-card') {
$avatarURL = $microformat['properties']['photo'][0];
if (array_get($microformat, 'type.0') == 'h-card') {
$avatarURL = array_get($microformat, 'properties.photo.0');
break;
}
}
try {
$avatar = $client->get($avatarURL);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
return "Unable to get $avatarURL";
if ($avatarURL !== null) {
try {
$avatar = $client->get($avatarURL);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
return redirect('/admin/contacts/' . $contactId . '/edit')
->with('error', 'Unable to download avatar');
}
}
$directory = public_path() . '/assets/profile-images/' . parse_url($homepage)['host'];
$filesystem = new Filesystem();
if ($filesystem->isDirectory($directory) === false) {
$filesystem->makeDirectory($directory);
}
$filesystem->put($directory . '/image', $avatar->getBody());
if ($avatar !== null) {
$directory = public_path() . '/assets/profile-images/' . parse_url($contact->homepage, PHP_URL_HOST);
$filesystem = new Filesystem();
if ($filesystem->isDirectory($directory) === false) {
$filesystem->makeDirectory($directory);
}
$filesystem->put($directory . '/image', $avatar->getBody());
return view('admin.contacts.getavatarsuccess', ['homepage' => parse_url($homepage)['host']]);
return view('admin.contacts.getavatarsuccess', [
'homepage' => parse_url($contact->homepage, PHP_URL_HOST),
]);
}
}
return redirect('/admin/contacts/' . $contactId . '/edit');
}
}

View file

@ -3,21 +3,12 @@
namespace App\Http\Controllers\Admin;
use App\Note;
use Validator;
use Illuminate\Http\Request;
use App\Jobs\SendWebMentions;
use App\Services\NoteService;
use App\Http\Controllers\Controller;
class NotesController extends Controller
{
protected $noteService;
public function __construct(NoteService $noteService)
{
$this->noteService = $noteService;
}
/**
* List the notes that can be edited.
*
@ -51,30 +42,10 @@ class NotesController extends Controller
*/
public function store(Request $request)
{
$validator = Validator::make(
$request->all(),
['photo' => 'photosize'],
['photosize' => 'At least one uploaded file exceeds size limit of 5MB']
);
if ($validator->fails()) {
return redirect('/admin/notes/create')
->withErrors($validator)
->withInput();
}
$data = [];
$data['content'] = $request->input('content');
$data['in-reply-to'] = $request->input('in-reply-to');
$data['location'] = $request->input('location');
$data['syndicate'] = [];
if ($request->input('twitter')) {
$data['syndicate'][] = 'twitter';
}
if ($request->input('facebook')) {
$data['syndicate'][] = 'facebook';
}
$note = $this->noteService->createNote($data);
Note::create([
'in-reply-to' => $request->input('in-reply-to'),
'note' => $request->input('content'),
]);
return redirect('/admin/notes');
}

View file

@ -47,11 +47,7 @@ class PlacesController extends Controller
*/
public function store(Request $request)
{
$data = [];
$data['name'] = $request->name;
$data['description'] = $request->description;
$data['latitude'] = $request->latitude;
$data['longitude'] = $request->longitude;
$data = $request->only(['name', 'description', 'latitude', 'longitude']);
$place = $this->placeService->createPlace($data);
return redirect('/admin/places');
@ -67,14 +63,7 @@ class PlacesController extends Controller
{
$place = Place::findOrFail($placeId);
return view('admin.places.edit', [
'id' => $placeId,
'name' => $place->name,
'description' => $place->description,
'latitude' => $place->latitude,
'longitude' => $place->longitude,
'icon' => $place->icon ?? 'marker',
]);
return view('admin.places.edit', compact('place'));
}
/**

View file

@ -15,7 +15,7 @@ class ArticlesController extends Controller
public function index($year = null, $month = null)
{
$articles = Article::where('published', '1')
->date($year, $month)
->date((int) $year, (int) $month)
->orderBy('updated_at', 'desc')
->simplePaginate(5);
@ -31,7 +31,7 @@ class ArticlesController extends Controller
{
$article = Article::where('titleurl', $slug)->firstOrFail();
if ($article->updated_at->year != $year || $article->updated_at->month != $month) {
throw new \Exception;
return redirect('/blog/' . $article->updated_at->year . '/' . $article->updated_at->month .'/' . $slug);
}
return view('articles.show', compact('article'));

View file

@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
}

View file

@ -1,39 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}

View file

@ -1,71 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}

View file

@ -1,39 +0,0 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
}

View file

@ -5,47 +5,41 @@ namespace App\Http\Controllers;
use Storage;
use Monolog\Logger;
use Ramsey\Uuid\Uuid;
use App\Jobs\ProcessImage;
use App\Services\LikeService;
use App\Services\BookmarkService;
use App\Jobs\ProcessMedia;
use Illuminate\Http\UploadedFile;
use Monolog\Handler\StreamHandler;
use App\{Like, Media, Note, Place};
use Intervention\Image\ImageManager;
use Illuminate\Http\{Request, Response};
use App\Exceptions\InvalidTokenException;
use Phaza\LaravelPostgis\Geometries\Point;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Ramsey\Uuid\Exception\UnsatisfiedDependencyException;
use Intervention\Image\Exception\NotReadableException;
use App\Services\{NoteService, PlaceService, TokenService};
use App\Services\Micropub\{HCardService, HEntryService, UpdateService};
class MicropubController extends Controller
{
/**
* The Token service container.
*/
protected $tokenService;
/**
* The Note service container.
*/
protected $noteService;
/**
* The Place service container.
*/
protected $placeService;
protected $hentryService;
protected $hcardService;
protected $updateService;
/**
* Inject the dependencies.
*/
public function __construct(
TokenService $tokenService,
NoteService $noteService,
PlaceService $placeService
PlaceService $placeService,
HEntryService $hentryService,
HCardService $hcardService,
UpdateService $updateService
) {
$this->tokenService = $tokenService;
$this->noteService = $noteService;
$this->placeService = $placeService;
$this->hentryService = $hentryService;
$this->hcardService = $hcardService;
$this->updateService = $updateService;
}
/**
@ -60,262 +54,46 @@ class MicropubController extends Controller
try {
$tokenData = $this->tokenService->validateToken($request->bearerToken());
} catch (InvalidTokenException $e) {
return $this->invalidTokenResponse();
}
if ($tokenData->hasClaim('scope') === false) {
return $this->tokenHasNoScopeResponse();
}
$this->logMicropubRequest($request);
if (($request->input('h') == 'entry') || ($request->input('type.0') == 'h-entry')) {
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->insufficientScopeResponse();
}
$location = $this->hentryService->process($request);
return response()->json([
'response' => 'error',
'error' => 'invalid_token',
'error_description' => 'The provided token did not pass validation',
], 400);
}
// Log the request
$logger = new Logger('micropub');
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')), Logger::DEBUG);
$logger->debug('MicropubLog', $request->all());
if ($tokenData->hasClaim('scope')) {
if (($request->input('h') == 'entry') || ($request->input('type.0') == 'h-entry')) {
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->returnInsufficientScopeResponse();
}
if ($request->has('properties.like-of') || $request->has('like-of')) {
$like = (new LikeService())->createLike($request);
return response()->json([
'response' => 'created',
'location' => config('app.url') . "/likes/$like->id",
], 201)->header('Location', config('app.url') . "/likes/$like->id");
}
if ($request->has('properties.bookmark-of') || $request->has('bookmark-of')) {
$bookmark = (new BookmarkService())->createBookmark($request);
return response()->json([
'response' => 'created',
'location' => config('app.url') . "/bookmarks/$bookmark->id",
], 201)->header('Location', config('app.url') . "/bookmarks/$bookmark->id");
}
$data = [];
$data['client-id'] = $tokenData->getClaim('client_id');
if ($request->header('Content-Type') == 'application/json') {
if (is_string($request->input('properties.content.0'))) {
$data['content'] = $request->input('properties.content.0'); //plaintext content
}
if (is_array($request->input('properties.content.0'))
&& array_key_exists('html', $request->input('properties.content.0'))
) {
$data['content'] = $request->input('properties.content.0.html');
}
$data['in-reply-to'] = $request->input('properties.in-reply-to.0');
// check location is geo: string
if (is_string($request->input('properties.location.0'))) {
$data['location'] = $request->input('properties.location.0');
}
// check location is h-card
if (is_array($request->input('properties.location.0'))) {
if ($request->input('properties.location.0.type.0' === 'h-card')) {
try {
$place = $this->placeService->createPlaceFromCheckin(
$request->input('properties.location.0')
);
$data['checkin'] = $place->longurl;
} catch (\Exception $e) {
//
}
}
}
$data['published'] = $request->input('properties.published.0');
//create checkin place
if (array_key_exists('checkin', $request->input('properties'))) {
$data['swarm-url'] = $request->input('properties.syndication.0');
try {
$place = $this->placeService->createPlaceFromCheckin(
$request->input('properties.checkin.0')
);
$data['checkin'] = $place->longurl;
} catch (\Exception $e) {
$data['checkin'] = null;
$data['swarm-url'] = null;
}
}
} else {
$data['content'] = $request->input('content');
$data['in-reply-to'] = $request->input('in-reply-to');
$data['location'] = $request->input('location');
$data['published'] = $request->input('published');
}
$data['syndicate'] = [];
$targets = array_pluck(config('syndication.targets'), 'uid', 'service.name');
$mpSyndicateTo = null;
if ($request->has('mp-syndicate-to')) {
$mpSyndicateTo = $request->input('mp-syndicate-to');
}
if ($request->has('properties.mp-syndicate-to')) {
$mpSyndicateTo = $request->input('properties.mp-syndicate-to');
}
if (is_string($mpSyndicateTo)) {
$service = array_search($mpSyndicateTo, $targets);
if ($service == 'Twitter') {
$data['syndicate'][] = 'twitter';
}
if ($service == 'Facebook') {
$data['syndicate'][] = 'facebook';
}
}
if (is_array($mpSyndicateTo)) {
foreach ($mpSyndicateTo as $uid) {
$service = array_search($uid, $targets);
if ($service == 'Twitter') {
$data['syndicate'][] = 'twitter';
}
if ($service == 'Facebook') {
$data['syndicate'][] = 'facebook';
}
}
}
$data['photo'] = [];
$photos = null;
if ($request->has('photo')) {
$photos = $request->input('photo');
}
if ($request->has('properties.photo')) {
$photos = $request->input('properties.photo');
}
if ($photos !== null) {
foreach ($photos as $photo) {
if (is_string($photo)) {
//only supporting media URLs for now
$data['photo'][] = $photo;
}
}
if (starts_with($request->input('properties.syndication.0'), 'https://www.instagram.com')) {
$data['instagram-url'] = $request->input('properties.syndication.0');
}
}
try {
$note = $this->noteService->createNote($data);
} catch (\Exception $exception) {
return response()->json(['error' => true], 400);
}
return response()->json([
'response' => 'created',
'location' => $note->longurl,
], 201)->header('Location', $note->longurl);
}
if ($request->input('h') == 'card' || $request->input('type')[0] == 'h-card') {
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->returnInsufficientScopeResponse();
}
$data = [];
if ($request->header('Content-Type') == 'application/json') {
$data['name'] = $request->input('properties.name');
$data['description'] = $request->input('properties.description') ?? null;
if ($request->has('properties.geo')) {
$data['geo'] = $request->input('properties.geo');
}
} else {
$data['name'] = $request->input('name');
$data['description'] = $request->input('description');
if ($request->has('geo')) {
$data['geo'] = $request->input('geo');
}
if ($request->has('latitude')) {
$data['latitude'] = $request->input('latitude');
$data['longitude'] = $request->input('longitude');
}
}
try {
$place = $this->placeService->createPlace($data);
} catch (\Exception $exception) {
return response()->json(['error' => true], 400);
}
return response()->json([
'response' => 'created',
'location' => $place->longurl,
], 201)->header('Location', $place->longurl);
}
if ($request->input('action') == 'update') {
if (stristr($tokenData->getClaim('scope'), 'update') === false) {
return $this->returnInsufficientScopeResponse();
}
$urlPath = parse_url($request->input('url'), PHP_URL_PATH);
//is it a note we are updating?
if (mb_substr($urlPath, 1, 5) === 'notes') {
try {
$note = Note::nb60(basename($urlPath))->firstOrFail();
} catch (ModelNotFoundException $exception) {
return response()->json([
'error' => 'invalid_request',
'error_description' => 'No known note with given ID',
]);
}
//got the note, are we dealing with a “replace” request?
if ($request->has('replace')) {
foreach ($request->input('replace') as $property => $value) {
if ($property == 'content') {
$note->note = $value[0];
}
if ($property == 'syndication') {
foreach ($value as $syndicationURL) {
if (starts_with($syndicationURL, 'https://www.facebook.com')) {
$note->facebook_url = $syndicationURL;
}
if (starts_with($syndicationURL, 'https://www.swarmapp.com')) {
$note->swarm_url = $syndicationURL;
}
if (starts_with($syndicationURL, 'https://twitter.com')) {
$note->tweet_id = basename(parse_url($syndicationURL, PHP_URL_PATH));
}
}
}
}
$note->save();
return response()->json([
'response' => 'updated',
]);
}
//how about “add”
if ($request->has('add')) {
foreach ($request->input('add') as $property => $value) {
if ($property == 'syndication') {
foreach ($value as $syndicationURL) {
if (starts_with($syndicationURL, 'https://www.facebook.com')) {
$note->facebook_url = $syndicationURL;
}
if (starts_with($syndicationURL, 'https://www.swarmapp.com')) {
$note->swarm_url = $syndicationURL;
}
if (starts_with($syndicationURL, 'https://twitter.com')) {
$note->tweet_id = basename(parse_url($syndicationURL, PHP_URL_PATH));
}
}
}
if ($property == 'photo') {
foreach ($value as $photoURL) {
if (start_with($photo, 'https://')) {
$media = new Media();
$media->path = $photoURL;
$media->type = 'image';
$media->save();
$note->media()->save($media);
}
}
}
}
$note->save();
return response()->json([
'response' => 'updated',
]);
}
}
}
'response' => 'created',
'location' => $location,
], 201)->header('Location', $location);
}
return response()->json([
'response' => 'error',
'error' => 'forbidden',
'error_description' => 'The token has no scopes',
], 403);
if ($request->input('h') == 'card' || $request->input('type')[0] == 'h-card') {
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->insufficientScopeResponse();
}
$location = $this->hcardService->process($request);
return response()->json([
'response' => 'created',
'location' => $location,
], 201)->header('Location', $location);
}
if ($request->input('action') == 'update') {
if (stristr($tokenData->getClaim('scope'), 'update') === false) {
return $this->returnInsufficientScopeResponse();
}
return $this->updateService->process($request);
}
}
/**
@ -332,20 +110,15 @@ class MicropubController extends Controller
try {
$tokenData = $this->tokenService->validateToken($request->bearerToken());
} catch (InvalidTokenException $e) {
return response()->json([
'response' => 'error',
'error' => 'invalid_token',
'error_description' => 'The provided token did not pass validation',
], 400);
return $this->invalidTokenResponse();
}
//we have a valid token, is `syndicate-to` set?
if ($request->input('q') === 'syndicate-to') {
return response()->json([
'syndicate-to' => config('syndication.targets'),
]);
}
//nope, how about a config query?
if ($request->input('q') == 'config') {
return response()->json([
'syndicate-to' => config('syndication.targets'),
@ -353,7 +126,6 @@ class MicropubController extends Controller
]);
}
//nope, how about a geo URL?
if (substr($request->input('q'), 0, 4) === 'geo:') {
preg_match_all(
'/([0-9\.\-]+)/',
@ -362,9 +134,6 @@ class MicropubController extends Controller
);
$distance = (count($matches[0]) == 3) ? 100 * $matches[0][2] : 1000;
$places = Place::near(new Point($matches[0][0], $matches[0][1]))->get();
foreach ($places as $place) {
$place->uri = config('app.url') . '/places/' . $place->slug;
}
return response()->json([
'response' => 'places',
@ -372,7 +141,7 @@ class MicropubController extends Controller
]);
}
//nope, just return the token
// default response is just to return the token data
return response()->json([
'response' => 'token',
'token' => [
@ -394,69 +163,18 @@ class MicropubController extends Controller
try {
$tokenData = $this->tokenService->validateToken($request->bearerToken());
} catch (InvalidTokenException $e) {
return response()->json([
'response' => 'error',
'error' => 'invalid_token',
'error_description' => 'The provided token did not pass validation',
], 400);
return $this->invalidTokenResponse();
}
$logger = new Logger('micropub');
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')), Logger::DEBUG);
$logger->debug('MicropubMediaLog', $request->all());
//check post scope
if ($tokenData->hasClaim('scope')) {
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->returnInsufficientScopeResponse();
}
//check media valid
if ($request->hasFile('file') && $request->file('file')->isValid()) {
try {
$filename = Uuid::uuid4() . '.' . $request->file('file')->extension();
} catch (UnsatisfiedDependencyException $e) {
return response()->json([
'response' => 'error',
'error' => 'internal_server_error',
'error_description' => 'A problem occured handling your request',
], 500);
}
if ($tokenData->hasClaim('scope') === false) {
return $this->tokenHasNoScopeResponse();
}
$size = $request->file('file')->getClientSize();
Storage::disk('local')->put($filename, $request->file('file')->openFile()->fread($size));
try {
Storage::disk('s3')->put('media/' . $filename, $request->file('file')->openFile()->fread($size));
} catch (Exception $e) { // which exception?
return response()->json([
'response' => 'error',
'error' => 'service_unavailable',
'error_description' => 'Unable to save media to S3',
], 503);
}
$manager = app()->make(ImageManager::class);
try {
$image = $manager->make($request->file('file'));
$width = $image->width();
} catch (\Intervention\Image\Exception\NotReadableException $exception) {
// not an image
$width = null;
}
$media = new Media();
$media->token = $request->bearerToken();
$media->path = 'media/' . $filename;
$media->type = $this->getFileTypeFromMimeType($request->file('file')->getMimeType());
$media->image_widths = $width;
$media->save();
dispatch(new ProcessImage($filename));
return response()->json([
'response' => 'created',
'location' => $media->url,
], 201)->header('Location', $media->url);
}
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->insufficientScopeResponse();
}
if (($request->hasFile('file') && $request->file('file')->isValid()) === false) {
return response()->json([
'response' => 'error',
'error' => 'invalid_request',
@ -464,11 +182,32 @@ class MicropubController extends Controller
], 400);
}
$this->logMicropubRequest($request);
$filename = $this->saveFile($request->file('file'));
$manager = resolve(ImageManager::class);
try {
$image = $manager->make($request->file('file'));
$width = $image->width();
} catch (NotReadableException $exception) {
// not an image
$width = null;
}
$media = Media::create([
'token' => $request->bearerToken(),
'path' => 'media/' . $filename,
'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()),
'image_widths' => $width,
]);
ProcessMedia::dispatch($filename);
return response()->json([
'response' => 'error',
'error' => 'invalid_request',
'error_description' => 'The provided token has no scopes',
], 400);
'response' => 'created',
'location' => $media->url,
], 201)->header('Location', $media->url);
}
/**
@ -515,7 +254,23 @@ class MicropubController extends Controller
return 'download';
}
private function returnInsufficientScopeResponse()
private function logMicropubRequest(Request $request)
{
$logger = new Logger('micropub');
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')), Logger::DEBUG);
$logger->debug('MicropubLog', $request->all());
}
private function saveFile(UploadedFile $file)
{
$filename = Uuid::uuid4() . '.' . $file->extension();
$size = $file->getClientSize();
Storage::disk('local')->put($filename, $file->openFile()->fread($size));
return $filename;
}
private function insufficientScopeResponse()
{
return response()->json([
'response' => 'error',
@ -523,4 +278,22 @@ class MicropubController extends Controller
'error_description' => 'The tokens scope does not have the necessary requirements.',
], 401);
}
private function invalidTokenResponse()
{
return response()->json([
'response' => 'error',
'error' => 'invalid_token',
'error_description' => 'The provided token did not pass validation',
], 400);
}
private function tokenHasNoScopeResponse()
{
return response()->json([
'response' => 'error',
'error' => 'invalid_request',
'error_description' => 'The provided token has no scopes',
], 400);
}
}

View file

@ -1,94 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Note;
use Imagine\Image\Box;
use Imagine\Gd\Imagine;
use Illuminate\Http\Request;
use Illuminate\Filesystem\Filesystem;
class PhotosController extends Controller
{
/**
* Image box size limit for resizing photos.
*/
public function __construct()
{
$this->imageResizeLimit = 800;
}
/**
* Save an uploaded photo to the image folder.
*
* @param \Illuminate\Http\Request $request
* @param string The associated notes nb60 ID
* @return bool
*/
public function saveImage(Request $request, $nb60id)
{
if ($request->hasFile('photo') !== true) {
return false;
}
$photoFilename = 'note-' . $nb60id;
$path = public_path() . '/assets/img/notes/';
$ext = $request->file('photo')->getClientOriginalExtension();
$photoFilename .= '.' . $ext;
$request->file('photo')->move($path, $photoFilename);
return true;
}
/**
* Prepare a photo for posting to twitter.
*
* @param string photo fileanme
* @return string small photo filename, or null
*/
public function makeSmallPhotoForTwitter($photoFilename)
{
$imagine = new Imagine();
$orig = $imagine->open(public_path() . '/assets/img/notes/' . $photoFilename);
$size = [$orig->getSize()->getWidth(), $orig->getSize()->getHeight()];
if ($size[0] > $this->imageResizeLimit || $size[1] > $this->imageResizeLimit) {
$filenameParts = explode('.', $photoFilename);
$preExt = count($filenameParts) - 2;
$filenameParts[$preExt] .= '-small';
$photoFilenameSmall = implode('.', $filenameParts);
$aspectRatio = $size[0] / $size[1];
$box = ($aspectRatio >= 1) ?
[$this->imageResizeLimit, (int) round($this->imageResizeLimit / $aspectRatio)]
:
[(int) round($this->imageResizeLimit * $aspectRatio), $this->imageResizeLimit];
$orig->resize(new Box($box[0], $box[1]))
->save(public_path() . '/assets/img/notes/' . $photoFilenameSmall);
return $photoFilenameSmall;
}
}
/**
* Get the image path for a note.
*
* @param string $nb60id
* @return string | null
*/
public function getPhotoPath($nb60id)
{
$filesystem = new Filesystem();
$photoDir = public_path() . '/assets/img/notes';
$files = $filesystem->files($photoDir);
foreach ($files as $file) {
$parts = explode('.', $file);
$name = $parts[0];
$dirs = explode('/', $name);
$actualname = last($dirs);
if ($actualname == 'note-' . $nb60id) {
$ext = $parts[1];
}
}
if (isset($ext)) {
return '/assets/img/notes/note-' . $nb60id . '.' . $ext;
}
}
}

View file

@ -10,16 +10,6 @@ class SearchController extends Controller
public function search(Request $request)
{
$notes = Note::search($request->terms)->paginate(10);
foreach ($notes as $note) {
$note->iso8601_time = $note->updated_at->toISO8601String();
$note->human_time = $note->updated_at->diffForHumans();
$photoURLs = [];
$photos = $note->getMedia();
foreach ($photos as $photo) {
$photoURLs[] = $photo->getUrl();
}
$note->photoURLs = $photoURLs;
}
return view('search', compact('notes'));
}

View file

@ -2,9 +2,6 @@
namespace App\Http\Controllers;
use App\ShortURL;
use Jonnybanres\IndieWeb\Numbers;
class ShortURLsController extends Controller
{
/*
@ -41,21 +38,11 @@ class ShortURLsController extends Controller
*
* @return \Illuminate\Routing\RedirectResponse redirect
*/
public function googlePLus()
public function googlePlus()
{
return redirect('https://plus.google.com/u/0/117317270900655269082/about');
}
/**
* Redirect from '/α' to an App.net profile.
*
* @return \Illuminate\Routing\Redirector redirect
*/
public function appNet()
{
return redirect('https://alpha.app.net/jonnybarnes');
}
/**
* Redirect a short url of this site out to a long one based on post type.
* Further redirects may happen.
@ -75,46 +62,4 @@ class ShortURLsController extends Controller
return redirect(config('app.url') . '/' . $type . '/' . $postId);
}
/**
* Redirect a saved short URL, this is generic.
*
* @param string The short URL id
* @return \Illuminate\Routing\Redirector redirect
*/
public function redirect($shortURLId)
{
$numbers = new Numbers();
$num = $numbers->b60tonum($shortURLId);
$shorturl = ShortURL::find($num);
$redirect = $shorturl->redirect;
return redirect($redirect);
}
/**
* I had an old redirect systme breifly, but cool URLs should still work.
*
* @param string URL ID
* @return \Illuminate\Routing\Redirector redirect
*/
public function oldRedirect($shortURLId)
{
$filename = base_path() . '/public/assets/old-shorturls.json';
$handle = fopen($filename, 'r');
$contents = fread($handle, filesize($filename));
$object = json_decode($contents);
foreach ($object as $key => $val) {
if ($shortURLId == $key) {
return redirect($val);
}
}
return 'This id was never used.
Old redirects are located at
<code>
<a href="https://jonnybarnes.net/assets/old-shorturls.json">old-shorturls.json</a>
</code>.';
}
}

View file

@ -36,35 +36,32 @@ class WebMentionsController extends Controller
$path = parse_url($request->input('target'), PHP_URL_PATH);
$pathParts = explode('/', $path);
switch ($pathParts[1]) {
case 'notes':
//we have a note
$noteId = $pathParts[2];
$numbers = new Numbers();
try {
$note = Note::findOrFail($numbers->b60tonum($noteId));
dispatch(new ProcessWebMention($note, $request->input('source')));
} catch (ModelNotFoundException $e) {
return new Response('This note doesnt exist.', 400);
}
if ($pathParts[1] == 'notes') {
//we have a note
$noteId = $pathParts[2];
$numbers = new Numbers();
try {
$note = Note::findOrFail($numbers->b60tonum($noteId));
dispatch(new ProcessWebMention($note, $request->input('source')));
} catch (ModelNotFoundException $e) {
return new Response('This note doesnt exist.', 400);
}
return new Response(
'Webmention received, it will be processed shortly',
202
);
break;
case 'blog':
return new Response(
'I dont accept webmentions for blog posts yet.',
501
);
break;
default:
return new Response(
'Invalid request',
400
);
break;
return new Response(
'Webmention received, it will be processed shortly',
202
);
}
if ($pathParts[1] == 'blog') {
return new Response(
'I dont accept webmentions for blog posts yet.',
501
);
}
return new Response(
'Invalid request',
400
);
}
}

View file

@ -36,7 +36,6 @@ class Kernel extends HttpKernel
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\LinkHeadersMiddleware::class,
//\App\Http\Middleware\DevTokenMiddleware::class,
\App\Http\Middleware\LocalhostSessionMiddleware::class,
\App\Http\Middleware\ActivityStreamLinks::class,
],

View file

@ -1,36 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Storage;
class DevTokenMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (config('app.env') !== 'production') {
session(['me' => config('app.url')]);
if (Storage::exists('dev-token')) {
session(['token' => Storage::get('dev-token')]);
} else {
$data = [
'me' => config('app.url'),
'client_id' => route('micropub-client'),
'scope' => 'post',
];
$tokenService = new \App\Services\TokenService();
session(['token' => $tokenService->getNewToken($data)]);
}
}
return $next($request);
}
}

View file

@ -5,6 +5,9 @@ namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
/**
* @codeCoverageIgnore
*/
class RedirectIfAuthenticated
{
/**

View file

@ -41,7 +41,7 @@ class DownloadWebMention implements ShouldQueue
//Laravel should catch and retry these automatically.
if ($response->getStatusCode() == '200') {
$filesystem = new \Illuminate\FileSystem\FileSystem();
$filename = storage_path() . '/HTML/' . $this->createFilenameFromURL($this->source);
$filename = storage_path('HTML') . '/' . $this->createFilenameFromURL($this->source);
//backup file first
$filenameBackup = $filename . '.' . date('Y-m-d') . '.backup';
if ($filesystem->exists($filename)) {

View file

@ -9,7 +9,7 @@ use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Exceptions\InternetArchiveErrorSavingException;
use App\Exceptions\InternetArchiveException;
class ProcessBookmark implements ShouldQueue
{
@ -34,12 +34,12 @@ class ProcessBookmark implements ShouldQueue
*/
public function handle()
{
$uuid = (new BookmarkService())->saveScreenshot($this->bookmark->url);
$uuid = (resolve(BookmarkService::class))->saveScreenshot($this->bookmark->url);
$this->bookmark->screenshot = $uuid;
try {
$archiveLink = (new BookmarkService())->getArchiveLink($this->bookmark->url);
} catch (InternetArchiveErrorSavingException $e) {
$archiveLink = (resolve(BookmarkService::class))->getArchiveLink($this->bookmark->url);
} catch (InternetArchiveException $e) {
$archiveLink = null;
}
$this->bookmark->archive = $archiveLink;

View file

@ -44,8 +44,8 @@ class ProcessLike implements ShouldQueue
try {
$author = $authorship->findAuthor($mf2);
if (is_array($author)) {
$this->like->author_name = $author['name'];
$this->like->author_url = $author['url'];
$this->like->author_name = array_get($author, 'properties.name.0');
$this->like->author_url = array_get($author, 'properties.url.0');
}
if (is_string($author) && $author !== '') {
$this->like->author_name = $author;

View file

@ -2,16 +2,16 @@
namespace App\Jobs;
use Storage;
use Illuminate\Bus\Queueable;
use Intervention\Image\ImageManager;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Intervention\Image\Exception\NotReadableException;
class ProcessImage implements ShouldQueue
class ProcessMedia implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@ -34,6 +34,10 @@ class ProcessImage implements ShouldQueue
*/
public function handle(ImageManager $manager)
{
Storage::disk('s3')->put(
'media/' . $this->filename,
storage_path('app') . '/' . $this->filename
);
//open file
try {
$image = $manager->make(storage_path('app') . '/' . $this->filename);

View file

@ -41,23 +41,25 @@ class ProcessWebMention implements ShouldQueue
*/
public function handle(Parser $parser, Client $guzzle)
{
$remoteContent = $this->getRemoteContent($this->source, $guzzle);
if ($remoteContent === null) {
try {
$response = $guzzle->request('GET', $this->source);
} catch (RequestException $e) {
throw new RemoteContentNotFoundException;
}
$microformats = Mf2\parse($remoteContent, $this->source);
$this->saveRemoteContent((string) $response->getBody(), $this->source);
$microformats = Mf2\parse((string) $response->getBody(), $this->source);
$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)
// 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
// it doesnt so delete
$webmention->delete();
return;
}
//webmenion is still a reply, so update content
// webmenion is still a reply, so update content
dispatch(new SaveProfileImage($microformats));
$webmention->mf2 = json_encode($microformats);
$webmention->save();
@ -66,25 +68,25 @@ class ProcessWebMention implements ShouldQueue
}
if ($webmention->type == 'like-of') {
if ($parser->checkLikeOf($microformats, $note->longurl) == false) {
//it doesn't so delete
// it doesnt so delete
$webmention->delete();
return;
} //note we don't need to do anything if it still is a like
} // note we dont 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
// it doesnt so delete
$webmention->delete();
return;
} //again, we don't need to do anything if it still is a repost
} // again, we dont need to do anything if it still is a repost
}
}//foreach
}// foreach
//no wemention in db so create new one
// no webmention in the db so create new one
$webmention = new WebMention();
$type = $parser->getMentionType($microformats); //throw error here?
$type = $parser->getMentionType($microformats); // throw error here?
dispatch(new SaveProfileImage($microformats));
$webmention->source = $this->source;
$webmention->target = $this->note->longurl;
@ -96,21 +98,23 @@ class ProcessWebMention implements ShouldQueue
}
/**
* Retreive the remote content from a URL, and caches the result.
* Save the HTML of a webmention for future use.
*
* @param string $html
* @param string $url
* @param GuzzleHttp\client $guzzle
* @return string|null
*/
private function getRemoteContent($url, Client $guzzle)
private function saveRemoteContent($html, $url)
{
try {
$response = $guzzle->request('GET', $url);
} catch (RequestException $e) {
return;
$filenameFromURL = str_replace(
['https://', 'http://'],
['https/', 'http/'],
$url
);
if (substr($url, -1) == '/') {
$filenameFromURL .= 'index.html';
}
$html = (string) $response->getBody();
$path = storage_path() . '/HTML/' . $this->createFilenameFromURL($url);
$path = storage_path() . '/HTML/' . $filenameFromURL;
$parts = explode('/', $path);
$name = array_pop($parts);
$dir = implode('/', $parts);
@ -118,24 +122,5 @@ class ProcessWebMention implements ShouldQueue
mkdir($dir, 0755, true);
}
file_put_contents("$dir/$name", $html);
return $html;
}
/**
* Create a file path from a URL. This is used when caching the HTML
* response.
*
* @param string The URL
* @return string The path name
*/
private function createFilenameFromURL($url)
{
$url = str_replace(['https://', 'http://'], ['https/', 'http/'], $url);
if (substr($url, -1) == '/') {
$url = $url . 'index.html';
}
return $url;
}
}

View file

@ -44,7 +44,7 @@ class SaveProfileImage implements ShouldQueue
//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();
$client = resolve(Client::class);
try {
$response = $client->get($photo);
$image = $response->getBody(true);

View file

@ -29,18 +29,18 @@ class SendWebMentions implements ShouldQueue
/**
* Execute the job.
*
* @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);
if ($endpoint) {
$endpoint = $this->discoverWebmentionEndpoint($url);
if ($endpoint !== null) {
$guzzle = resolve(Client::class);
$guzzle->post($endpoint, [
'form_params' => [
'source' => $this->note->longurl,
@ -55,21 +55,21 @@ class SendWebMentions implements ShouldQueue
* Discover if a URL has a webmention endpoint.
*
* @param string The URL
* @param \GuzzleHttp\Client $guzzle
* @return string The webmention endpoint URL
*/
public function discoverWebmentionEndpoint($url, $guzzle)
public function discoverWebmentionEndpoint($url)
{
//lets not send webmentions to myself
if (parse_url($url, PHP_URL_HOST) == config('app.longurl')) {
return false;
return;
}
if (starts_with($url, '/notes/tagged/')) {
return false;
return;
}
$endpoint = null;
$guzzle = resolve(Client::class);
$response = $guzzle->get($url);
//check HTTP Headers for webmention endpoint
$links = \GuzzleHttp\Psr7\parse_header($response->getHeader('Link'));
@ -92,8 +92,6 @@ class SendWebMentions implements ShouldQueue
if ($endpoint) {
return $this->resolveUri($endpoint, $url);
}
return false;
}
/**

View file

@ -8,10 +8,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class SyndicateBookmarkToFacebook implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $bookmark;

View file

@ -8,10 +8,11 @@ use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class SyndicateBookmarkToTwitter implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $bookmark;

View file

@ -24,7 +24,7 @@ class Like extends Model
public function getContentAttribute($value)
{
if ($value === null) {
return $this->url;
return null;
}
$mf2 = Mf2\parse($value, $this->url);

View file

@ -18,7 +18,7 @@ class Media extends Model
*
* @var array
*/
protected $fillable = ['path'];
protected $fillable = ['token', 'path', 'type', 'image_widths'];
/**
* Get the note that owns this media.
@ -70,10 +70,10 @@ class Media extends Model
public function getBasename($path)
{
$filenameParts = explode('.', $path);
// the following achieves this data flow
// foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar
$filenameParts = explode('.', $path);
array_pop($filenameParts);
$basename = ltrim(array_reduce($filenameParts, function ($carry, $item) {
return $carry . '.' . $item;
}, ''), '.');

View file

@ -146,9 +146,9 @@ class Note extends Model
$emoji = new EmojiModifier();
$hcards = $this->makeHCards($value);
$html = $this->convertMarkdown($hcards);
$hashtags = $this->autoLinkHashtag($html);
$modified = $emoji->makeEmojiAccessible($hashtags);
$hashtags = $this->autoLinkHashtag($hcards);
$html = $this->convertMarkdown($hashtags);
$modified = $emoji->makeEmojiAccessible($html);
return $modified;
}
@ -223,9 +223,7 @@ class Note extends Model
public function getLatitudeAttribute()
{
if ($this->place !== null) {
$lnglat = explode(' ', $this->place->location);
return $lnglat[1];
return $this->place->location->getLat();
}
if ($this->location !== null) {
$pieces = explode(':', $this->location);
@ -243,9 +241,7 @@ class Note extends Model
public function getLongitudeAttribute()
{
if ($this->place !== null) {
$lnglat = explode(' ', $this->place->location);
return $lnglat[1];
return $this->place->location->getLng();
}
if ($this->location !== null) {
$pieces = explode(':', $this->location);
@ -281,12 +277,13 @@ class Note extends Model
if (Cache::has($tweetId)) {
return Cache::get($tweetId);
}
try {
$oEmbed = Twitter::getOembed([
'id' => $tweetId,
'url' => $this->in_reply_to,
'dnt' => true,
'align' => 'center',
'maxwidth' => 550,
'maxwidth' => 512,
]);
} catch (\Exception $e) {
return;
@ -408,10 +405,10 @@ class Note extends Model
$contact = $this->contacts[$matches[1]]; // easier to read the following code
$host = parse_url($contact->homepage, PHP_URL_HOST);
$contact->photo = (file_exists(public_path() . '/assets/profile-images/' . $host . '/image')) ?
'/assets/profile-images/' . $host . '/image'
:
'/assets/profile-images/default-image';
$contact->photo = '/assets/profile-images/default-image';
if (file_exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
$contact->photo = '/assets/profile-images/' . $host . '/image';
}
return trim(view('templates.mini-hcard', ['contact' => $contact])->render());
},
@ -450,31 +447,17 @@ class Note extends Model
* @param string The note
* @return string
*/
private function autoLinkHashtag($text)
public function autoLinkHashtag($text)
{
// $replacements = ["#tag" => "<a rel="tag" href="/tags/tag">#tag</a>]
$replacements = [];
$matches = [];
if (preg_match_all('/(?<=^|\s)\#([a-zA-Z0-9\-\_]+)/i', $text, $matches, PREG_PATTERN_ORDER)) {
// Look up #tags, get Full name and URL
foreach ($matches[0] as $name) {
$name = str_replace('#', '', $name);
$replacements[$name] =
'<a rel="tag" class="p-category" href="/notes/tagged/'
. Tag::normalize($name)
. '">#'
. $name
. '</a>';
}
// Replace #tags with valid microformat-enabled link
foreach ($replacements as $name => $replacement) {
$text = str_replace('#' . $name, $replacement, $text);
}
}
return $text;
return preg_replace_callback(
'/#([^\s]*)\b/',
function ($matches) {
return '<a rel="tag" class="p-category" href="/notes/tagged/'
. Tag::normalize($matches[1]) . '">#'
. Tag::normalize($matches[1]) . '</a>';
},
$text
);
}
private function convertMarkdown($text)
@ -536,7 +519,7 @@ class Note extends Model
return $address;
}
$adress = '<span class="p-country-name">' . $json->address->country . '</span>';
$address = '<span class="p-country-name">' . $json->address->country . '</span>';
Cache::forever($latlng, $address);
return $address;

View file

@ -21,12 +21,10 @@ class NoteObserver
}
$tags->transform(function ($tag) {
return Tag::firstOrCreate(['tag' => $tag]);
});
return Tag::firstOrCreate(['tag' => $tag])->id;
})->toArray();
$note->tags()->attach($tags->map(function ($tag) {
return $tag->id;
}));
$note->tags()->attach($tags);
}
/**
@ -65,9 +63,6 @@ class NoteObserver
public function getTagsFromNote($note)
{
preg_match_all('/#([^\s<>]+)\b/', $note, $tags);
if (array_get($tags, '1') === null) {
return [];
}
return collect($tags[1])->map(function ($tag) {
return Tag::normalize($tag);

View file

@ -2,7 +2,7 @@
namespace App;
use DB;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Cviebrock\EloquentSluggable\Sluggable;
@ -79,15 +79,8 @@ class Place extends Model
public function scopeWhereExternalURL(Builder $query, string $url)
{
$type = $this->getType($url);
if ($type === null) {
// we havent set a type, therefore result must be empty set
// id cant be null, so this will return empty set
return $query->whereNull('id');
}
return $query->where('external_urls', '@>', json_encode([
$type => $url,
$this->getType($url) => $url,
]));
}
@ -131,12 +124,19 @@ class Place extends Model
return config('app.shorturl') . '/places/' . $this->slug;
}
/**
* This method is an alternative for `longurl`.
*
* @return string
*/
public function getUriAttribute()
{
return $this->longurl;
}
public function setExternalUrlsAttribute($url)
{
$type = $this->getType($url);
if ($type === null) {
throw new \Exception('Unkown external url type ' . $url);
}
$already = [];
if (array_key_exists('external_urls', $this->attributes)) {
$already = json_decode($this->attributes['external_urls'], true);
@ -155,6 +155,6 @@ class Place extends Model
return 'osm';
}
return null;
return 'default';
}
}

View file

@ -5,6 +5,9 @@ namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Broadcast;
/**
* @codeCoverageIgnore
*/
class BroadcastServiceProvider extends ServiceProvider
{
/**

View file

@ -6,6 +6,9 @@ use Illuminate\Http\Request;
use Laravel\Horizon\Horizon;
use Illuminate\Support\ServiceProvider;
/**
* @codeCoverageIgnore
*/
class HorizonServiceProvider extends ServiceProvider
{
/**

View file

@ -13,7 +13,8 @@ use App\Jobs\ProcessBookmark;
use Spatie\Browsershot\Browsershot;
use App\Jobs\SyndicateBookmarkToTwitter;
use App\Jobs\SyndicateBookmarkToFacebook;
use App\Exceptions\InternetArchiveErrorSavingException;
use GuzzleHttp\Exception\ClientException;
use App\Exceptions\InternetArchiveException;
class BookmarkService
{
@ -103,20 +104,20 @@ class BookmarkService
public function getArchiveLink(string $url): string
{
$client = new Client();
$response = $client->request('GET', 'https://web.archive.org/save/' . $url);
$client = resolve(Client::class);
try {
$response = $client->request('GET', 'https://web.archive.org/save/' . $url);
} catch (ClientException $e) {
//throw an exception to be caught
throw new InternetArchiveException;
}
if ($response->hasHeader('Content-Location')) {
if (starts_with($response->getHeader('Content-Location')[0], '/web')) {
if (starts_with(array_get($response->getHeader('Content-Location'), 0), '/web')) {
return $response->getHeader('Content-Location')[0];
}
}
if (starts_with(array_get($response->getHeader('Content-Location'), 0), '/web')) {
return $response->getHeader('Content-Location')[0];
}
//throw an exception to be caught
throw new InternetArchiveErrorSavingException;
throw new InternetArchiveException;
}
}

View file

@ -21,7 +21,7 @@ class LikeService
//micropub request
$url = normalize_url($request->input('properties.like-of.0'));
}
if (($request->header('Content-Type') == 'x-www-url-formencoded')
if (($request->header('Content-Type') == 'application/x-www-form-urlencoded')
||
($request->header('Content-Type') == 'multipart/form-data')
) {

View file

@ -0,0 +1,34 @@
<?php
namespace App\Services\Micropub;
use Illuminate\Http\Request;
use App\Services\PlaceService;
class HCardService
{
public function process(Request $request)
{
$data = [];
if ($request->header('Content-Type') == 'application/json') {
$data['name'] = $request->input('properties.name');
$data['description'] = $request->input('properties.description') ?? null;
if ($request->has('properties.geo')) {
$data['geo'] = $request->input('properties.geo');
}
} else {
$data['name'] = $request->input('name');
$data['description'] = $request->input('description');
if ($request->has('geo')) {
$data['geo'] = $request->input('geo');
}
if ($request->has('latitude')) {
$data['latitude'] = $request->input('latitude');
$data['longitude'] = $request->input('longitude');
}
}
$place = resolve(PlaceService::class)->createPlace($data);
return $place->longurl;
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Services\Micropub;
use Illuminate\Http\Request;
use App\Services\{BookmarkService, LikeService, NoteService};
class HEntryService
{
public function process(Request $request)
{
if ($request->has('properties.like-of') || $request->has('like-of')) {
$like = resolve(LikeService::class)->createLike($request);
return $like->longurl;
}
if ($request->has('properties.bookmark-of') || $request->has('bookmark-of')) {
$bookmark = resolve(BookmarkService::class)->createBookmark($request);
return $bookmark->longurl;
}
$note = resolve(NoteService::class)->createNote($request);
return $note->longurl;
}
}

View file

@ -0,0 +1,95 @@
<?php
namespace App\Services\Micropub;
use App\Note;
use App\Media;
use Illuminate\Http\Request;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class UpdateService
{
public function process(Request $request)
{
$urlPath = parse_url($request->input('url'), PHP_URL_PATH);
//is it a note we are updating?
if (mb_substr($urlPath, 1, 5) !== 'notes') {
return response()->json([
'error' => 'invalid',
'error_description' => 'This implementation currently only support the updating of notes',
], 500);
}
try {
$note = Note::nb60(basename($urlPath))->firstOrFail();
} catch (ModelNotFoundException $exception) {
return response()->json([
'error' => 'invalid_request',
'error_description' => 'No known note with given ID',
], 404);
}
//got the note, are we dealing with a “replace” request?
if ($request->has('replace')) {
foreach ($request->input('replace') as $property => $value) {
if ($property == 'content') {
$note->note = $value[0];
}
if ($property == 'syndication') {
foreach ($value as $syndicationURL) {
if (starts_with($syndicationURL, 'https://www.facebook.com')) {
$note->facebook_url = $syndicationURL;
}
if (starts_with($syndicationURL, 'https://www.swarmapp.com')) {
$note->swarm_url = $syndicationURL;
}
if (starts_with($syndicationURL, 'https://twitter.com')) {
$note->tweet_id = basename(parse_url($syndicationURL, PHP_URL_PATH));
}
}
}
}
$note->save();
return response()->json([
'response' => 'updated',
]);
}
//how about “add”
if ($request->has('add')) {
foreach ($request->input('add') as $property => $value) {
if ($property == 'syndication') {
foreach ($value as $syndicationURL) {
if (starts_with($syndicationURL, 'https://www.facebook.com')) {
$note->facebook_url = $syndicationURL;
}
if (starts_with($syndicationURL, 'https://www.swarmapp.com')) {
$note->swarm_url = $syndicationURL;
}
if (starts_with($syndicationURL, 'https://twitter.com')) {
$note->tweet_id = basename(parse_url($syndicationURL, PHP_URL_PATH));
}
}
}
if ($property == 'photo') {
foreach ($value as $photoURL) {
if (start_with($photo, 'https://')) {
$media = new Media();
$media->path = $photoURL;
$media->type = 'image';
$media->save();
$note->media()->save($media);
}
}
}
}
$note->save();
return response()->json([
'response' => 'updated',
]);
}
}
}

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Services;
use Illuminate\Http\Request;
use App\{Media, Note, Place};
use App\Jobs\{SendWebMentions, SyndicateNoteToFacebook, SyndicateNoteToTwitter};
@ -12,12 +13,111 @@ class NoteService
/**
* Create a new note.
*
* @param array $data
* @param \Illuminate\Http\Request $request
* @return \App\Note $note
*/
public function createNote(array $data): Note
public function createNote(Request $request): Note
{
//move the request to data code here before refactor
$data = [];
$data['client-id'] = resolve(TokenService::class)
->validateToken($request->bearerToken())
->getClaim('client_id');
if ($request->header('Content-Type') == 'application/json') {
if (is_string($request->input('properties.content.0'))) {
$data['content'] = $request->input('properties.content.0'); //plaintext content
}
if (is_array($request->input('properties.content.0'))
&& array_key_exists('html', $request->input('properties.content.0'))
) {
$data['content'] = $request->input('properties.content.0.html');
}
$data['in-reply-to'] = $request->input('properties.in-reply-to.0');
// check location is geo: string
if (is_string($request->input('properties.location.0'))) {
$data['location'] = $request->input('properties.location.0');
}
// check location is h-card
if (is_array($request->input('properties.location.0'))) {
if ($request->input('properties.location.0.type.0' === 'h-card')) {
try {
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
$request->input('properties.location.0')
);
$data['checkin'] = $place->longurl;
} catch (\Exception $e) {
//
}
}
}
$data['published'] = $request->input('properties.published.0');
//create checkin place
if (array_key_exists('checkin', $request->input('properties'))) {
$data['swarm-url'] = $request->input('properties.syndication.0');
try {
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
$request->input('properties.checkin.0')
);
$data['checkin'] = $place->longurl;
} catch (\Exception $e) {
$data['checkin'] = null;
$data['swarm-url'] = null;
}
}
} else {
$data['content'] = $request->input('content');
$data['in-reply-to'] = $request->input('in-reply-to');
$data['location'] = $request->input('location');
$data['published'] = $request->input('published');
}
$data['syndicate'] = [];
$targets = array_pluck(config('syndication.targets'), 'uid', 'service.name');
$mpSyndicateTo = null;
if ($request->has('mp-syndicate-to')) {
$mpSyndicateTo = $request->input('mp-syndicate-to');
}
if ($request->has('properties.mp-syndicate-to')) {
$mpSyndicateTo = $request->input('properties.mp-syndicate-to');
}
if (is_string($mpSyndicateTo)) {
$service = array_search($mpSyndicateTo, $targets);
if ($service == 'Twitter') {
$data['syndicate'][] = 'twitter';
}
if ($service == 'Facebook') {
$data['syndicate'][] = 'facebook';
}
}
if (is_array($mpSyndicateTo)) {
foreach ($mpSyndicateTo as $uid) {
$service = array_search($uid, $targets);
if ($service == 'Twitter') {
$data['syndicate'][] = 'twitter';
}
if ($service == 'Facebook') {
$data['syndicate'][] = 'facebook';
}
}
}
$data['photo'] = [];
$photos = null;
if ($request->has('photo')) {
$photos = $request->input('photo');
}
if ($request->has('properties.photo')) {
$photos = $request->input('properties.photo');
}
if ($photos !== null) {
foreach ($photos as $photo) {
if (is_string($photo)) {
//only supporting media URLs for now
$data['photo'][] = $photo;
}
}
if (starts_with($request->input('properties.syndication.0'), 'https://www.instagram.com')) {
$data['instagram-url'] = $request->input('properties.syndication.0');
}
}
//check the input
if (array_key_exists('content', $data) === false) {
$data['content'] = null;
@ -37,8 +137,8 @@ class NoteService
);
if (array_key_exists('published', $data) && empty($data['published']) === false) {
$carbon = carbon($data['published']);
$note->created_at = $note->updated_at = $carbon->toDateTimeString();
$note->created_at = $note->updated_at = carbon($data['published'])
->toDateTimeString();
}
if (array_key_exists('location', $data) && $data['location'] !== null && $data['location'] !== 'no-location') {
@ -81,7 +181,7 @@ class NoteService
*/
//add support for media uploaded as URLs
if (array_key_exists('photo', $data)) {
foreach ($data['photo'] as $photo) {
foreach ((array) $data['photo'] as $photo) {
// check the media was uploaded to my endpoint, and use path
if (starts_with($photo, config('filesystems.disks.s3.url'))) {
$path = substr($photo, strlen(config('filesystems.disks.s3.url')));
@ -104,11 +204,13 @@ class NoteService
dispatch(new SendWebMentions($note));
//syndication targets
if (in_array('twitter', $data['syndicate'])) {
dispatch(new SyndicateNoteToTwitter($note));
}
if (in_array('facebook', $data['syndicate'])) {
dispatch(new SyndicateNoteToFacebook($note));
if (array_key_exists('syndicate', $data)) {
if (in_array('twitter', $data['syndicate'])) {
dispatch(new SyndicateNoteToTwitter($note));
}
if (in_array('facebook', $data['syndicate'])) {
dispatch(new SyndicateNoteToFacebook($note));
}
}
return $note;

View file

@ -46,16 +46,16 @@ class PlaceService
public function createPlaceFromCheckin(array $checkin): Place
{
//check if the place exists if from swarm
if (array_key_exists('url', $checkin['properties'])) {
if (array_has($checkin, 'properties.url')) {
$place = Place::whereExternalURL($checkin['properties']['url'][0])->get();
if (count($place) === 1) {
return $place->first();
}
}
if (array_key_exists('name', $checkin['properties']) === false) {
if (array_has($checkin, 'properties.name') === false) {
throw new \InvalidArgumentException('Missing required name');
}
if (array_key_exists('latitude', $checkin['properties']) === false) {
if (array_has($checkin, 'properties.latitude') === false) {
throw new \InvalidArgumentException('Missing required longitude/latitude');
}
$place = new Place();

View file

@ -38,7 +38,7 @@ class TokenService
* @param string The token
* @return mixed
*/
public function validateToken(string $bearerToken): ?Token
public function validateToken(string $bearerToken): Token
{
$signer = new Sha256();
try {
@ -47,7 +47,7 @@ class TokenService
throw new InvalidTokenException('Token could not be parsed');
}
if (! $token->verify($signer, config('app.key'))) {
throw new InvalidTokenException('Token failed verification');
throw new InvalidTokenException('Token failed validation');
}
return $token;

View file

@ -6,6 +6,13 @@ use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
/**
* We shall set a blacklist of non-modifiable model attributes.
*
* @var array
*/
protected $guarded = ['id'];
/**
* Define the relationship with tags.
*
@ -24,13 +31,6 @@ class Tag extends Model
return $this->belongsToMany('App\Bookmark');
}
/**
* We shall set a blacklist of non-modifiable model attributes.
*
* @var array
*/
protected $guarded = ['id'];
/**
* Normalize tags so theyre lowercase and fancy diatrics are removed.
*

View file

@ -19,6 +19,13 @@ class WebMention extends Model
*/
protected $table = 'webmentions';
/**
* We shall set a blacklist of non-modifiable model attributes.
*
* @var array
*/
protected $guarded = ['id'];
/**
* Define the relationship.
*
@ -29,13 +36,6 @@ class WebMention extends Model
return $this->morphTo();
}
/**
* We shall set a blacklist of non-modifiable model attributes.
*
* @var array
*/
protected $guarded = ['id'];
/**
* Get the author of the webmention.
*
@ -78,9 +78,9 @@ class WebMention extends Model
}
/**
* Get the filteres HTML of a reply.
* Get the filtered HTML of a reply.
*
* @return strin|null
* @return string|null
*/
public function getReplyAttribute()
{
@ -108,14 +108,10 @@ class WebMention extends Model
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
}
$username = ltrim(parse_url($url, PHP_URL_PATH), '/');
$info = Twitter::getUsers(['screen_name' => $username]);
$profile_image = $info->profile_image_url_https;
Cache::put($url, $profile_image, 10080); //1 week
return $profile_image;
}