Merge branch 'release/0.14'
This commit is contained in:
commit
f8cb9eb4fb
181 changed files with 5829 additions and 2253 deletions
|
@ -1,9 +1,9 @@
|
|||
APP_ENV=testing
|
||||
APP_DEBUG=true
|
||||
APP_KEY=base64:6DJhvZLVjE6dD4Cqrteh+6Z5vZlG+v/soCKcDHLOAH0=
|
||||
APP_URL=http://localhost:8000
|
||||
APP_LONGURL=localhost
|
||||
APP_SHORTURL=local
|
||||
APP_URL=http://jonnybarnes.localhost:8000
|
||||
APP_LONGURL=jonnybarnes.localhost
|
||||
APP_SHORTURL=jmb.localhost
|
||||
|
||||
DB_CONNECTION=travis
|
||||
|
||||
|
|
20
.travis.yml
20
.travis.yml
|
@ -7,12 +7,13 @@ cache:
|
|||
- apt
|
||||
|
||||
addons:
|
||||
hosts:
|
||||
- jmb.localhost
|
||||
- jonnybarnes.localhost
|
||||
postgresql: "9.6"
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: 'deb http://ppa.launchpad.net/nginx/development/ubuntu trusty main'
|
||||
packages:
|
||||
- nginx
|
||||
- nginx-full
|
||||
- realpath
|
||||
- postgresql-9.6-postgis-2.3
|
||||
- imagemagick
|
||||
|
@ -36,10 +37,6 @@ php:
|
|||
- 7.1
|
||||
- 7.2
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: 7.2
|
||||
|
||||
before_install:
|
||||
- printf "\n" | pecl install imagick
|
||||
- cp .env.travis .env
|
||||
|
@ -47,6 +44,7 @@ before_install:
|
|||
- psql -U travis -c 'create database travis_ci_test'
|
||||
- psql -U travis -d travis_ci_test -c 'create extension postgis'
|
||||
- travis_retry composer self-update --preview
|
||||
- pear install pear/PHP_CodeSniffer && phpenv rehash
|
||||
|
||||
install:
|
||||
- if [[ $setup = 'basic' ]]; then travis_retry composer install --no-interaction --prefer-dist; fi
|
||||
|
@ -66,6 +64,10 @@ before_script:
|
|||
#- sleep 5
|
||||
|
||||
script:
|
||||
- php vendor/bin/phpunit --coverage-text
|
||||
- php vendor/bin/phpunit --coverage-clover build/logs/clover.xml
|
||||
- phpcs
|
||||
#- php artisan dusk
|
||||
- php vendor/bin/security-checker security:check ./composer.lock --end-point=http://security.sensiolabs.org/check_lock
|
||||
- php vendor/bin/security-checker security:check --end-point=http://security.sensiolabs.org/check_lock
|
||||
|
||||
after_success:
|
||||
- travis_retry php vendor/bin/coveralls
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\WebMention;
|
||||
use App\Models\WebMention;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\FileSystem\FileSystem;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\WebMention;
|
||||
use App\Models\WebMention;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Jobs\DownloadWebMention;
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@ namespace App\Console\Commands;
|
|||
use Illuminate\Console\Command;
|
||||
use SensioLabs\Security\SecurityChecker;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class SecurityCheck extends Command
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InternetArchiveErrorSavingException extends Exception
|
||||
{
|
||||
//
|
||||
}
|
7
app/Exceptions/InternetArchiveException.php
Normal file
7
app/Exceptions/InternetArchiveException.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
class InternetArchiveException extends \Exception
|
||||
{
|
||||
}
|
|
@ -4,7 +4,7 @@ namespace App\Exceptions;
|
|||
|
||||
use Exception;
|
||||
|
||||
class RemoteContentNotFound extends Exception
|
||||
class RemoteContentNotFoundException extends Exception
|
||||
{
|
||||
//used when guzzle can’t find the remote content
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Article;
|
||||
use App\Models\Article;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\MicropubClient;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\MicropubClient;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ClientsController extends Controller
|
||||
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Contact;
|
||||
use GuzzleHttp\Client;
|
||||
use App\Models\Contact;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
@ -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 contact’s 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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,22 +2,13 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Note;
|
||||
use Validator;
|
||||
use App\Models\Note;
|
||||
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');
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Place;
|
||||
use App\Models\Place;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\PlaceService;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
@ -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'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Article;
|
||||
use App\Models\Article;
|
||||
use Jonnybarnes\IndieWeb\Numbers;
|
||||
|
||||
class ArticlesController extends Controller
|
||||
|
@ -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'));
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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']),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Bookmark;
|
||||
use App\Models\Bookmark;
|
||||
|
||||
class BookmarksController extends Controller
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Contact;
|
||||
use App\Models\Contact;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
class ContactsController extends Controller
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Note;
|
||||
use App\Article;
|
||||
use App\Models\{Article, Note};
|
||||
|
||||
class FeedsController extends Controller
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Like;
|
||||
use App\Models\Like;
|
||||
|
||||
class LikesController extends Controller
|
||||
{
|
||||
|
|
|
@ -2,320 +2,96 @@
|
|||
|
||||
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 App\Services\TokenService;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use App\{Like, Media, Note, Place};
|
||||
use Intervention\Image\ImageManager;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Http\{Request, Response};
|
||||
use App\Exceptions\InvalidTokenException;
|
||||
use App\Models\{Like, Media, Note, Place};
|
||||
use Phaza\LaravelPostgis\Geometries\Point;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Ramsey\Uuid\Exception\UnsatisfiedDependencyException;
|
||||
use App\Services\{NoteService, PlaceService, TokenService};
|
||||
use Intervention\Image\Exception\NotReadableException;
|
||||
use App\Services\Micropub\{HCardService, HEntryService, UpdateService};
|
||||
|
||||
class MicropubController extends Controller
|
||||
{
|
||||
/**
|
||||
* The Token service container.
|
||||
*/
|
||||
protected $tokenService;
|
||||
protected $hentryService;
|
||||
protected $hcardService;
|
||||
protected $updateService;
|
||||
|
||||
/**
|
||||
* The Note service container.
|
||||
*/
|
||||
protected $noteService;
|
||||
|
||||
/**
|
||||
* The Place service container.
|
||||
*/
|
||||
protected $placeService;
|
||||
|
||||
/**
|
||||
* Inject the dependencies.
|
||||
*/
|
||||
public function __construct(
|
||||
TokenService $tokenService,
|
||||
NoteService $noteService,
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function receives an API request, verifies the authenticity
|
||||
* then passes over the info to the relavent Service class.
|
||||
*
|
||||
* @param \Illuminate\Http\Request request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function post(Request $request)
|
||||
public function post()
|
||||
{
|
||||
try {
|
||||
$tokenData = $this->tokenService->validateToken($request->bearerToken());
|
||||
$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();
|
||||
}
|
||||
// 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);
|
||||
if ($tokenData->hasClaim('scope') === false) {
|
||||
return $this->tokenHasNoScopeResponse();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
$this->logMicropubRequest(request()->all());
|
||||
|
||||
return response()->json([
|
||||
'response' => 'created',
|
||||
'location' => $note->longurl,
|
||||
], 201)->header('Location', $note->longurl);
|
||||
if ((request()->input('h') == 'entry') || (request()->input('type.0') == 'h-entry')) {
|
||||
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
|
||||
return $this->insufficientScopeResponse();
|
||||
}
|
||||
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);
|
||||
}
|
||||
$location = $this->hentryService->process(request()->all(), $this->getCLientId());
|
||||
|
||||
return response()->json([
|
||||
'response' => 'created',
|
||||
'location' => $place->longurl,
|
||||
], 201)->header('Location', $place->longurl);
|
||||
return response()->json([
|
||||
'response' => 'created',
|
||||
'location' => $location,
|
||||
], 201)->header('Location', $location);
|
||||
}
|
||||
|
||||
if (request()->input('h') == 'card' || request()->input('type')[0] == 'h-card') {
|
||||
if (stristr($tokenData->getClaim('scope'), 'create') === false) {
|
||||
return $this->insufficientScopeResponse();
|
||||
}
|
||||
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();
|
||||
$location = $this->hcardService->process(request()->all());
|
||||
|
||||
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' => 'created',
|
||||
'location' => $location,
|
||||
], 201)->header('Location', $location);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'response' => 'updated',
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (request()->input('action') == 'update') {
|
||||
if (stristr($tokenData->getClaim('scope'), 'update') === false) {
|
||||
return $this->insufficientScopeResponse();
|
||||
}
|
||||
|
||||
return $this->updateService->process(request()->all());
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error' => 'forbidden',
|
||||
'error_description' => 'The token has no scopes',
|
||||
], 403);
|
||||
'error_description' => 'unsupported_request_type',
|
||||
], 500);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -324,47 +100,37 @@ class MicropubController extends Controller
|
|||
* appropriately. Further if the request has the query parameter
|
||||
* synidicate-to we respond with the known syndication endpoints.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function get(Request $request)
|
||||
public function get()
|
||||
{
|
||||
try {
|
||||
$tokenData = $this->tokenService->validateToken($request->bearerToken());
|
||||
$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') {
|
||||
|
||||
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') {
|
||||
if (request()->input('q') == 'config') {
|
||||
return response()->json([
|
||||
'syndicate-to' => config('syndication.targets'),
|
||||
'media-endpoint' => route('media-endpoint'),
|
||||
]);
|
||||
}
|
||||
|
||||
//nope, how about a geo URL?
|
||||
if (substr($request->input('q'), 0, 4) === 'geo:') {
|
||||
if (substr(request()->input('q'), 0, 4) === 'geo:') {
|
||||
preg_match_all(
|
||||
'/([0-9\.\-]+)/',
|
||||
$request->input('q'),
|
||||
request()->input('q'),
|
||||
$matches
|
||||
);
|
||||
$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 +138,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' => [
|
||||
|
@ -386,77 +152,25 @@ class MicropubController extends Controller
|
|||
/**
|
||||
* Process a media item posted to the media endpoint.
|
||||
*
|
||||
* @param Illuminate\Http\Request $request
|
||||
* @return Illuminate\Http\Response
|
||||
*/
|
||||
public function media(Request $request)
|
||||
public function media()
|
||||
{
|
||||
try {
|
||||
$tokenData = $this->tokenService->validateToken($request->bearerToken());
|
||||
$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 +178,32 @@ class MicropubController extends Controller
|
|||
], 400);
|
||||
}
|
||||
|
||||
$this->logMicropubRequest(request()->all());
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -495,6 +230,7 @@ class MicropubController extends Controller
|
|||
$videoMimeTypes = [
|
||||
'video/mp4',
|
||||
'video/mpeg',
|
||||
'video/ogg',
|
||||
'video/quicktime',
|
||||
'video/webm',
|
||||
];
|
||||
|
@ -515,7 +251,29 @@ class MicropubController extends Controller
|
|||
return 'download';
|
||||
}
|
||||
|
||||
private function returnInsufficientScopeResponse()
|
||||
private function getClientId(): string
|
||||
{
|
||||
return resolve(TokenService::class)
|
||||
->validateToken(request()->bearerToken())
|
||||
->getClaim('client_id');
|
||||
}
|
||||
|
||||
private function logMicropubRequest(array $request)
|
||||
{
|
||||
$logger = new Logger('micropub');
|
||||
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')), Logger::DEBUG);
|
||||
$logger->debug('MicropubLog', $request);
|
||||
}
|
||||
|
||||
private function saveFile(UploadedFile $file)
|
||||
{
|
||||
$filename = Uuid::uuid4() . '.' . $file->extension();
|
||||
Storage::disk('local')->put($filename, $file);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
private function insufficientScopeResponse()
|
||||
{
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
|
@ -523,4 +281,22 @@ class MicropubController extends Controller
|
|||
'error_description' => 'The token’s 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Note;
|
||||
use App\Models\Note;
|
||||
use Illuminate\Http\Request;
|
||||
use Jonnybarnes\IndieWeb\Numbers;
|
||||
use App\Services\ActivityStreamsService;
|
||||
|
|
|
@ -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 note’s 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Place;
|
||||
use App\Models\Place;
|
||||
|
||||
class PlacesController extends Controller
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Note;
|
||||
use App\Models\Note;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SearchController extends Controller
|
||||
|
@ -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'));
|
||||
}
|
||||
|
|
|
@ -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>.';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Note;
|
||||
use App\Models\Note;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Jobs\ProcessWebMention;
|
||||
|
@ -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 doesn’t 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 doesn’t exist.', 400);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
'Webmention received, it will be processed shortly',
|
||||
202
|
||||
);
|
||||
break;
|
||||
case 'blog':
|
||||
return new Response(
|
||||
'I don’t 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 don’t accept webmentions for blog posts yet.',
|
||||
501
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
'Invalid request',
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,9 @@ namespace App\Http\Middleware;
|
|||
use Closure;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\MicropubClient;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\MicropubClient;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Bookmark;
|
||||
use App\Models\Bookmark;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Services\BookmarkService;
|
||||
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;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Like;
|
||||
use App\Models\Like;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
|
@ -4,8 +4,8 @@ namespace App\Jobs;
|
|||
|
||||
use Mf2;
|
||||
use GuzzleHttp\Client;
|
||||
use App\{Note, WebMention};
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\{Note, WebMention};
|
||||
use Jonnybarnes\WebmentionsParser\Parser;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
@ -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 doesn’t 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 doesn’t so delete
|
||||
$webmention->delete();
|
||||
|
||||
return;
|
||||
} //note we don't need to do anything if it still is a like
|
||||
} // 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
|
||||
// it doesn’t so delete
|
||||
$webmention->delete();
|
||||
|
||||
return;
|
||||
} //again, we don't need to do anything if it still is a repost
|
||||
} // again, we don’t 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Note;
|
||||
use App\Models\Note;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
@ -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)
|
||||
{
|
||||
//let’s 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,16 +2,17 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Bookmark;
|
||||
use GuzzleHttp\Client;
|
||||
use App\Models\Bookmark;
|
||||
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;
|
||||
|
||||
|
|
|
@ -2,16 +2,17 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Bookmark;
|
||||
use GuzzleHttp\Client;
|
||||
use App\Models\Bookmark;
|
||||
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;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Note;
|
||||
use App\Models\Note;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Note;
|
||||
use App\Models\Note;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Cviebrock\EloquentSluggable\Sluggable;
|
||||
|
@ -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([
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
@ -27,7 +27,7 @@ class Bookmark extends Model
|
|||
*/
|
||||
public function tags()
|
||||
{
|
||||
return $this->belongsToMany('App\Tag');
|
||||
return $this->belongsToMany('App\Models\Tag');
|
||||
}
|
||||
|
||||
/**
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use Mf2;
|
||||
use HTMLPurifier;
|
||||
|
@ -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);
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
@ -18,14 +18,14 @@ class Media extends Model
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['path'];
|
||||
protected $fillable = ['token', 'path', 'type', 'image_widths'];
|
||||
|
||||
/**
|
||||
* Get the note that owns this media.
|
||||
*/
|
||||
public function note()
|
||||
{
|
||||
return $this->belongsTo('App\Note');
|
||||
return $this->belongsTo('App\Models\Note');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}, ''), '.');
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
|
@ -27,6 +27,6 @@ class MicropubClient extends Model
|
|||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->hasMany('App\Note', 'client_id', 'client_url');
|
||||
return $this->hasMany('App\Models\Note', 'client_id', 'client_url');
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use Cache;
|
||||
use Twitter;
|
||||
|
@ -69,7 +69,7 @@ class Note extends Model
|
|||
*/
|
||||
public function tags()
|
||||
{
|
||||
return $this->belongsToMany('App\Tag');
|
||||
return $this->belongsToMany('App\Models\Tag');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,7 +79,7 @@ class Note extends Model
|
|||
*/
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo('App\MicropubClient', 'client_id', 'client_url');
|
||||
return $this->belongsTo('App\Models\MicropubClient', 'client_id', 'client_url');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,7 +89,7 @@ class Note extends Model
|
|||
*/
|
||||
public function webmentions()
|
||||
{
|
||||
return $this->morphMany('App\WebMention', 'commentable');
|
||||
return $this->morphMany('App\Models\WebMention', 'commentable');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,7 +99,7 @@ class Note extends Model
|
|||
*/
|
||||
public function place()
|
||||
{
|
||||
return $this->belongsTo('App\Place');
|
||||
return $this->belongsTo('App\Models\Place');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,7 +109,7 @@ class Note extends Model
|
|||
*/
|
||||
public function media()
|
||||
{
|
||||
return $this->hasMany('App\Media');
|
||||
return $this->hasMany('App\Models\Media');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Cviebrock\EloquentSluggable\Sluggable;
|
||||
|
@ -53,7 +53,7 @@ class Place extends Model
|
|||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->hasMany('App\Note');
|
||||
return $this->hasMany('App\Models\Note');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,15 +79,8 @@ class Place extends Model
|
|||
|
||||
public function scopeWhereExternalURL(Builder $query, string $url)
|
||||
{
|
||||
$type = $this->getType($url);
|
||||
if ($type === null) {
|
||||
// we haven’t set a type, therefore result must be empty set
|
||||
// id can’t 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,22 @@ 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);
|
||||
if ($url === null) {
|
||||
return;
|
||||
}
|
||||
$type = $this->getType($url);
|
||||
$already = [];
|
||||
if (array_key_exists('external_urls', $this->attributes)) {
|
||||
$already = json_decode($this->attributes['external_urls'], true);
|
||||
|
@ -155,6 +158,6 @@ class Place extends Model
|
|||
return 'osm';
|
||||
}
|
||||
|
||||
return null;
|
||||
return 'default';
|
||||
}
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
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.
|
||||
*
|
||||
|
@ -13,7 +20,7 @@ class Tag extends Model
|
|||
*/
|
||||
public function notes()
|
||||
{
|
||||
return $this->belongsToMany('App\Note');
|
||||
return $this->belongsToMany('App\Models\Note');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,16 +28,9 @@ class Tag extends Model
|
|||
*/
|
||||
public function bookmarks()
|
||||
{
|
||||
return $this->belongsToMany('App\Bookmark');
|
||||
return $this->belongsToMany('App\Models\Bookmark');
|
||||
}
|
||||
|
||||
/**
|
||||
* We shall set a blacklist of non-modifiable model attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = ['id'];
|
||||
|
||||
/**
|
||||
* Normalize tags so they’re lowercase and fancy diatrics are removed.
|
||||
*
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Models;
|
||||
|
||||
use Cache;
|
||||
use Twitter;
|
||||
|
@ -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;
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\{Note, Tag};
|
||||
use App\Models\{Note, Tag};
|
||||
|
||||
class NoteObserver
|
||||
{
|
||||
|
@ -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);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Note;
|
||||
use App\Models\Note;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Observers\NoteObserver;
|
||||
use Laravel\Dusk\DuskServiceProvider;
|
||||
|
|
|
@ -5,6 +5,9 @@ namespace App\Providers;
|
|||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class BroadcastServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,9 @@ use Illuminate\Http\Request;
|
|||
use Laravel\Horizon\Horizon;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
class HorizonServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Note;
|
||||
use App\Models\Note;
|
||||
|
||||
class ActivityStreamsService
|
||||
{
|
||||
|
|
|
@ -4,42 +4,39 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Tag;
|
||||
use App\Bookmark;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Jobs\ProcessBookmark;
|
||||
use App\Models\{Bookmark, Tag};
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Create a new Bookmark.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param array $request
|
||||
* @return Bookmark $bookmark
|
||||
*/
|
||||
public function createBookmark(Request $request): Bookmark
|
||||
public function createBookmark(array $request): Bookmark
|
||||
{
|
||||
if ($request->header('Content-Type') == 'application/json') {
|
||||
if (array_get($request, 'properties.bookmark-of.0')) {
|
||||
//micropub request
|
||||
$url = normalize_url($request->input('properties.bookmark-of.0'));
|
||||
$name = $request->input('properties.name.0');
|
||||
$content = $request->input('properties.content.0');
|
||||
$categories = $request->input('properties.category');
|
||||
$url = normalize_url(array_get($request, 'properties.bookmark-of.0'));
|
||||
$name = array_get($request, 'properties.name.0');
|
||||
$content = array_get($request, 'properties.content.0');
|
||||
$categories = array_get($request, 'properties.category');
|
||||
}
|
||||
if (
|
||||
($request->header('Content-Type') == 'application/x-www-form-urlencoded')
|
||||
||
|
||||
(str_contains($request->header('Content-Type'), 'multipart/form-data'))
|
||||
) {
|
||||
$url = normalize_url($request->input('bookmark-of'));
|
||||
$name = $request->input('name');
|
||||
$content = $request->input('content');
|
||||
$categories = $request->input('category');
|
||||
if (array_get($request, 'bookmark-of')) {
|
||||
$url = normalize_url(array_get($request, 'bookmark-of'));
|
||||
$name = array_get($request, 'name');
|
||||
$content = array_get($request, 'content');
|
||||
$categories = array_get($request, 'category');
|
||||
}
|
||||
|
||||
$bookmark = Bookmark::create([
|
||||
|
@ -55,11 +52,11 @@ class BookmarkService
|
|||
|
||||
$targets = array_pluck(config('syndication.targets'), 'uid', 'service.name');
|
||||
$mpSyndicateTo = null;
|
||||
if ($request->has('mp-syndicate-to')) {
|
||||
$mpSyndicateTo = $request->input('mp-syndicate-to');
|
||||
if (array_get($request, 'mp-syndicate-to')) {
|
||||
$mpSyndicateTo = array_get($request, 'mp-syndicate-to');
|
||||
}
|
||||
if ($request->has('properties.mp-syndicate-to')) {
|
||||
$mpSyndicateTo = $request->input('properties.mp-syndicate-to');
|
||||
if (array_get($request, 'properties.mp-syndicate-to')) {
|
||||
$mpSyndicateTo = array_get($request, 'properties.mp-syndicate-to');
|
||||
}
|
||||
if (is_string($mpSyndicateTo)) {
|
||||
$service = array_search($mpSyndicateTo, $targets);
|
||||
|
@ -104,20 +101,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,29 +4,25 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Like;
|
||||
use App\Models\Like;
|
||||
use App\Jobs\ProcessLike;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LikeService
|
||||
{
|
||||
/**
|
||||
* Create a new Like.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param array $request
|
||||
* @return Like $like
|
||||
*/
|
||||
public function createLike(Request $request): Like
|
||||
public function createLike(array $request): Like
|
||||
{
|
||||
if ($request->header('Content-Type') == 'application/json') {
|
||||
if (array_get($request, 'properties.like-of.0')) {
|
||||
//micropub request
|
||||
$url = normalize_url($request->input('properties.like-of.0'));
|
||||
$url = normalize_url(array_get($request, 'properties.like-of.0'));
|
||||
}
|
||||
if (
|
||||
($request->header('Content-Type') == 'x-www-url-formencoded')
|
||||
||
|
||||
($request->header('Content-Type') == 'multipart/form-data')
|
||||
) {
|
||||
$url = normalize_url($request->input('like-of'));
|
||||
if (array_get($request, 'like-of')) {
|
||||
$url = normalize_url(array_get($request, 'like-of'));
|
||||
}
|
||||
|
||||
$like = Like::create(['url' => $url]);
|
||||
|
|
27
app/Services/Micropub/HCardService.php
Normal file
27
app/Services/Micropub/HCardService.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Micropub;
|
||||
|
||||
use App\Services\PlaceService;
|
||||
|
||||
class HCardService
|
||||
{
|
||||
public function process(array $request)
|
||||
{
|
||||
$data = [];
|
||||
if (array_get($request, 'properties.name')) {
|
||||
$data['name'] = array_get($request, 'properties.name');
|
||||
$data['description'] = array_get($request, 'properties.description');
|
||||
$data['geo'] = array_get($request, 'properties.geo');
|
||||
} else {
|
||||
$data['name'] = array_get($request, 'name');
|
||||
$data['description'] = array_get($request, 'description');
|
||||
$data['geo'] = array_get($request, 'geo');
|
||||
$data['latitude'] = array_get($request, 'latitude');
|
||||
$data['longitude'] = array_get($request, 'longitude');
|
||||
}
|
||||
$place = resolve(PlaceService::class)->createPlace($data);
|
||||
|
||||
return $place->longurl;
|
||||
}
|
||||
}
|
27
app/Services/Micropub/HEntryService.php
Normal file
27
app/Services/Micropub/HEntryService.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Micropub;
|
||||
|
||||
use App\Services\{BookmarkService, LikeService, NoteService};
|
||||
|
||||
class HEntryService
|
||||
{
|
||||
public function process(array $request, string $client = null)
|
||||
{
|
||||
if (array_get($request, 'properties.like-of') || array_get($request, 'like-of')) {
|
||||
$like = resolve(LikeService::class)->createLike($request);
|
||||
|
||||
return $like->longurl;
|
||||
}
|
||||
|
||||
if (array_get($request, 'properties.bookmark-of') || array_get($request, 'bookmark-of')) {
|
||||
$bookmark = resolve(BookmarkService::class)->createBookmark($request);
|
||||
|
||||
return $bookmark->longurl;
|
||||
}
|
||||
|
||||
$note = resolve(NoteService::class)->createNote($request, $client);
|
||||
|
||||
return $note->longurl;
|
||||
}
|
||||
}
|
98
app/Services/Micropub/UpdateService.php
Normal file
98
app/Services/Micropub/UpdateService.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Micropub;
|
||||
|
||||
use App\Models\{Media, Note};
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
|
||||
class UpdateService
|
||||
{
|
||||
public function process(array $request)
|
||||
{
|
||||
$urlPath = parse_url(array_get($request, '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 (array_get($request, 'replace')) {
|
||||
foreach (array_get($request, '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 (array_get($request, 'add')) {
|
||||
foreach (array_get($request, '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 (starts_with($photoURL, 'https://')) {
|
||||
$media = new Media();
|
||||
$media->path = $photoURL;
|
||||
$media->type = 'image';
|
||||
$media->save();
|
||||
$note->media()->save($media);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$note->save();
|
||||
|
||||
return response()->json([
|
||||
'response' => 'updated',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'response' => 'error',
|
||||
'error_description' => 'unsupported request',
|
||||
], 500);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\{Media, Note, Place};
|
||||
use App\Models\{Media, Note, Place};
|
||||
use App\Jobs\{SendWebMentions, SyndicateNoteToFacebook, SyndicateNoteToTwitter};
|
||||
|
||||
class NoteService
|
||||
|
@ -12,91 +12,38 @@ class NoteService
|
|||
/**
|
||||
* Create a new note.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $request
|
||||
* @param string $client
|
||||
* @return \App\Note $note
|
||||
*/
|
||||
public function createNote(array $data): Note
|
||||
public function createNote(array $request, string $client = null): Note
|
||||
{
|
||||
|
||||
//check the input
|
||||
if (array_key_exists('content', $data) === false) {
|
||||
$data['content'] = null;
|
||||
}
|
||||
if (array_key_exists('in-reply-to', $data) === false) {
|
||||
$data['in-reply-to'] = null;
|
||||
}
|
||||
if (array_key_exists('client-id', $data) === false) {
|
||||
$data['client-id'] = null;
|
||||
}
|
||||
$note = Note::create(
|
||||
[
|
||||
'note' => $data['content'],
|
||||
'in_reply_to' => $data['in-reply-to'],
|
||||
'client_id' => $data['client-id'],
|
||||
'note' => $this->getContent($request),
|
||||
'in_reply_to' => $this->getInReplyTo($request),
|
||||
'client_id' => $client,
|
||||
]
|
||||
);
|
||||
|
||||
if (array_key_exists('published', $data) && empty($data['published']) === false) {
|
||||
$carbon = carbon($data['published']);
|
||||
$note->created_at = $note->updated_at = $carbon->toDateTimeString();
|
||||
if ($this->getPublished($request)) {
|
||||
$note->created_at = $note->updated_at = $this->getPublished($request);
|
||||
}
|
||||
|
||||
if (array_key_exists('location', $data) && $data['location'] !== null && $data['location'] !== 'no-location') {
|
||||
if (starts_with($data['location'], config('app.url'))) {
|
||||
//uri of form http://host/places/slug, we want slug
|
||||
//get the URL path, then take last part, we can hack with basename
|
||||
//as path looks like file path.
|
||||
$place = Place::where('slug', basename(parse_url($data['location'], PHP_URL_PATH)))->first();
|
||||
$note->place()->associate($place);
|
||||
}
|
||||
if (substr($data['location'], 0, 4) == 'geo:') {
|
||||
preg_match_all(
|
||||
'/([0-9\.\-]+)/',
|
||||
$data['location'],
|
||||
$matches
|
||||
);
|
||||
$note->location = $matches[0][0] . ', ' . $matches[0][1];
|
||||
$note->location = $this->getLocation($request);
|
||||
|
||||
if ($this->getCheckin($request)) {
|
||||
$note->place()->associate($this->getCheckin($request));
|
||||
$note->swarm_url = $this->getSwarmUrl($request);
|
||||
if ($note->note === null || $note->note == '') {
|
||||
$note->note = 'I’ve just checked in with Swarm';
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('checkin', $data) && $data['checkin'] !== null) {
|
||||
$place = Place::where('slug', basename(parse_url($data['checkin'], PHP_URL_PATH)))->first();
|
||||
if ($place !== null) {
|
||||
$note->place()->associate($place);
|
||||
$note->swarm_url = $data['swarm-url'];
|
||||
if ($note->note === null || $note->note == '') {
|
||||
$note->note = 'I’ve just checked in with Swarm';
|
||||
}
|
||||
}
|
||||
}
|
||||
$note->instagram_url = $this->getInstagramUrl($request);
|
||||
|
||||
/* drop image support for now
|
||||
//add images to media library
|
||||
if ($request->hasFile('photo')) {
|
||||
$files = $request->file('photo');
|
||||
foreach ($files as $file) {
|
||||
$note->addMedia($file)->toCollectionOnDisk('images', 's3');
|
||||
}
|
||||
}
|
||||
*/
|
||||
//add support for media uploaded as URLs
|
||||
if (array_key_exists('photo', $data)) {
|
||||
foreach ($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')));
|
||||
$media = Media::where('path', ltrim($path, '/'))->firstOrFail();
|
||||
} else {
|
||||
$media = Media::firstOrNew(['path' => $photo]);
|
||||
// currently assuming this is a photo from Swarm or OwnYourGram
|
||||
$media->type = 'image';
|
||||
$media->save();
|
||||
}
|
||||
$note->media()->save($media);
|
||||
}
|
||||
if (array_key_exists('instagram-url', $data)) {
|
||||
$note->instagram_url = $data['instagram-url'];
|
||||
}
|
||||
foreach ($this->getMedia($request) as $media) {
|
||||
$note->media()->save($media);
|
||||
}
|
||||
|
||||
$note->save();
|
||||
|
@ -104,13 +51,175 @@ 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 (count($this->getSyndicationTargets($request)) > 0) {
|
||||
if (in_array('twitter', $this->getSyndicationTargets($request))) {
|
||||
dispatch(new SyndicateNoteToTwitter($note));
|
||||
}
|
||||
if (in_array('facebook', $this->getSyndicationTargets($request))) {
|
||||
dispatch(new SyndicateNoteToFacebook($note));
|
||||
}
|
||||
}
|
||||
|
||||
return $note;
|
||||
}
|
||||
|
||||
private function getContent(array $request): ?string
|
||||
{
|
||||
if (array_get($request, 'properties.content.0.html')) {
|
||||
return array_get($request, 'properties.content.0.html');
|
||||
}
|
||||
if (is_string(array_get($request, 'properties.content.0'))) {
|
||||
return array_get($request, 'properties.content.0');
|
||||
}
|
||||
|
||||
return array_get($request, 'content');
|
||||
}
|
||||
|
||||
private function getInReplyTo(array $request): ?string
|
||||
{
|
||||
if (array_get($request, 'properties.in-reply-to.0')) {
|
||||
return array_get($request, 'properties.in-reply-to.0');
|
||||
}
|
||||
|
||||
return array_get($request, 'in-reply-to');
|
||||
}
|
||||
|
||||
private function getPublished(array $request): ?string
|
||||
{
|
||||
if (array_get($request, 'properties.published.0')) {
|
||||
return carbon(array_get($request, 'properties.published.0'))
|
||||
->toDateTimeString();
|
||||
}
|
||||
if (array_get($request, 'published')) {
|
||||
return carbon(array_get($request, 'published'))->toDateTimeString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getLocation(array $request): ?string
|
||||
{
|
||||
$location = array_get($request, 'properties.location.0') ?? array_get($request, 'location');
|
||||
if (is_string($location) && substr($location, 0, 4) == 'geo:') {
|
||||
preg_match_all(
|
||||
'/([0-9\.\-]+)/',
|
||||
$location,
|
||||
$matches
|
||||
);
|
||||
|
||||
return $matches[0][0] . ', ' . $matches[0][1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getCheckin(array $request): ?Place
|
||||
{
|
||||
if (array_get($request, 'properties.location.0.type.0') === 'h-card') {
|
||||
try {
|
||||
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
||||
array_get($request, 'properties.location.0')
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $place;
|
||||
}
|
||||
if (starts_with(array_get($request, 'properties.location.0'), config('app.url'))) {
|
||||
return Place::where(
|
||||
'slug',
|
||||
basename(
|
||||
parse_url(
|
||||
array_get($request, 'properties.location.0'),
|
||||
PHP_URL_PATH
|
||||
)
|
||||
)
|
||||
)->first();
|
||||
}
|
||||
if (array_get($request, 'properties.checkin')) {
|
||||
try {
|
||||
$place = resolve(PlaceService::class)->createPlaceFromCheckin(
|
||||
array_get($request, 'properties.checkin.0')
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $place;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getSwarmUrl(array $request): ?string
|
||||
{
|
||||
if (stristr(array_get($request, 'properties.syndication.0', ''), 'swarmapp')) {
|
||||
return array_get($request, 'properties.syndication.0');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getSyndicationTargets(array $request): array
|
||||
{
|
||||
$syndication = [];
|
||||
$targets = array_pluck(config('syndication.targets'), 'uid', 'service.name');
|
||||
$mpSyndicateTo = array_get($request, 'mp-syndicate-to') ?? array_get($request, 'properties.mp-syndicate-to');
|
||||
if (is_string($mpSyndicateTo)) {
|
||||
$service = array_search($mpSyndicateTo, $targets);
|
||||
if ($service == 'Twitter') {
|
||||
$syndication[] = 'twitter';
|
||||
}
|
||||
if ($service == 'Facebook') {
|
||||
$syndication[] = 'facebook';
|
||||
}
|
||||
}
|
||||
if (is_array($mpSyndicateTo)) {
|
||||
foreach ($mpSyndicateTo as $uid) {
|
||||
$service = array_search($uid, $targets);
|
||||
if ($service == 'Twitter') {
|
||||
$syndication[] = 'twitter';
|
||||
}
|
||||
if ($service == 'Facebook') {
|
||||
$syndication[] = 'facebook';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $syndication;
|
||||
}
|
||||
|
||||
private function getMedia(array $request): array
|
||||
{
|
||||
$media = [];
|
||||
$photos = array_get($request, 'photo') ?? array_get($request, 'properties.photo');
|
||||
|
||||
if (isset($photos)) {
|
||||
foreach ((array) $photos 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')));
|
||||
$media[] = Media::where('path', ltrim($path, '/'))->firstOrFail();
|
||||
} else {
|
||||
$newMedia = Media::firstOrNew(['path' => $photo]);
|
||||
// currently assuming this is a photo from Swarm or OwnYourGram
|
||||
$newMedia->type = 'image';
|
||||
$newMedia->save();
|
||||
$media[] = $newMedia;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
private function getInstagramUrl(array $request): ?string
|
||||
{
|
||||
if (starts_with(array_get($request, 'properties.syndication.0'), 'https://www.instagram.com')) {
|
||||
return array_get($request, 'properties.syndication.0');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Place;
|
||||
use App\Models\Place;
|
||||
use Phaza\LaravelPostgis\Geometries\Point;
|
||||
|
||||
class PlaceService
|
||||
|
@ -19,7 +19,7 @@ class PlaceService
|
|||
{
|
||||
//obviously a place needs a lat/lng, but this could be sent in a geo-url
|
||||
//if no geo array key, we assume the array already has lat/lng values
|
||||
if (array_key_exists('geo', $data)) {
|
||||
if (array_key_exists('geo', $data) && $data['geo'] !== null) {
|
||||
preg_match_all(
|
||||
'/([0-9\.\-]+)/',
|
||||
$data['geo'],
|
||||
|
@ -46,24 +46,24 @@ class PlaceService
|
|||
public function createPlaceFromCheckin(array $checkin): Place
|
||||
{
|
||||
//check if the place exists if from swarm
|
||||
if (array_key_exists('url', $checkin['properties'])) {
|
||||
$place = Place::whereExternalURL($checkin['properties']['url'][0])->get();
|
||||
if (array_has($checkin, 'properties.url')) {
|
||||
$place = Place::whereExternalURL(array_get($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();
|
||||
$place->name = $checkin['properties']['name'][0];
|
||||
$place->external_urls = $checkin['properties']['url'][0];
|
||||
$place->name = array_get($checkin, 'properties.name.0');
|
||||
$place->external_urls = array_get($checkin, 'properties.url.0');
|
||||
$place->location = new Point(
|
||||
(float) $checkin['properties']['latitude'][0],
|
||||
(float) $checkin['properties']['longitude'][0]
|
||||
(float) array_get($checkin, 'properties.latitude.0'),
|
||||
(float) array_get($checkin, 'properties.longitude.0')
|
||||
);
|
||||
$place->save();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
# Changelog
|
||||
|
||||
## Version 0.14 (2017-12-22)
|
||||
- Tests
|
||||
- Refactor
|
||||
- More tests, seriously, code-coverage to now above 90%
|
||||
|
||||
## Version 0.13.1 (2017-11-20)
|
||||
- A small fix when adding a new bookmark
|
||||
|
||||
|
|
|
@ -35,12 +35,14 @@
|
|||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "~3.0",
|
||||
"bmitch/churn-php": "^0.2.0",
|
||||
"codedungeon/phpunit-result-printer": "^0.3.0",
|
||||
"filp/whoops": "~2.0",
|
||||
"fzaninotto/faker": "~1.4",
|
||||
"jakub-onderka/php-parallel-lint": "^0.9.2",
|
||||
"laravel/dusk": "^2.0",
|
||||
"mockery/mockery": "0.9.*",
|
||||
"nunomaduro/collision": "^1.1",
|
||||
"php-coveralls/php-coveralls": "^1.0",
|
||||
"phpunit/phpunit": "~6.0",
|
||||
"sebastian/phpcpd": "^3.0"
|
||||
},
|
||||
|
|
1052
composer.lock
generated
1052
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Bookmark;
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(App\Bookmark::class, function (Faker $faker) {
|
||||
$factory->define(Bookmark::class, function (Faker $faker) {
|
||||
return [
|
||||
'url' => $faker->url,
|
||||
'name' => $faker->sentence,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Like;
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(App\Like::class, function (Faker $faker) {
|
||||
$factory->define(Like::class, function (Faker $faker) {
|
||||
return [
|
||||
'url' => $faker->url,
|
||||
'author_name' => $faker->name,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Note;
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(App\Note::class, function (Faker $faker) {
|
||||
$factory->define(Note::class, function (Faker $faker) {
|
||||
return [
|
||||
'note' => $faker->paragraph,
|
||||
];
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Tag;
|
||||
use Faker\Generator as Faker;
|
||||
|
||||
$factory->define(App\Tag::class, function (Faker $faker) {
|
||||
$factory->define(Tag::class, function (Faker $faker) {
|
||||
return [
|
||||
'tag' => $faker->word,
|
||||
];
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class UpdateModelsReferenceInWebmentionsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('webmentions', function (Blueprint $table) {
|
||||
DB::statement("UPDATE webmentions SET commentable_type = 'App\\Models\\Note' WHERE commentable_type = 'App\\Note'");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('webmentions', function (Blueprint $table) {
|
||||
DB::statement("UPDATE webmentions SET commentable_type = 'App\\Note' WHERE commentable_type = 'App\\Models\\Note'");
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Article;
|
||||
use App\Models\Article;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ArticlesTableSeeder extends Seeder
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Models\{Bookmark, Tag};
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class BookmarksTableSeeder extends Seeder
|
||||
|
@ -11,8 +12,8 @@ class BookmarksTableSeeder extends Seeder
|
|||
*/
|
||||
public function run()
|
||||
{
|
||||
factory(App\Bookmark::class, 10)->create()->each(function ($bookmark) {
|
||||
$bookmark->tags()->save(factory(App\Tag::class)->make());
|
||||
factory(Bookmark::class, 10)->create()->each(function ($bookmark) {
|
||||
$bookmark->tags()->save(factory(Tag::class)->make());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Contact;
|
||||
use App\Models\Contact;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class ContactsTableSeeder extends Seeder
|
||||
|
@ -22,7 +22,6 @@ class ContactsTableSeeder extends Seeder
|
|||
'nick' => 'aaron',
|
||||
'name' => 'Aaron Parecki',
|
||||
'homepage' => 'https://aaronparecki.com',
|
||||
'twitter' => 'aaronpk',
|
||||
'facebook' => '123456',
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Like;
|
||||
use Faker\Generator;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class LikesTableSeeder extends Seeder
|
||||
|
@ -11,6 +13,16 @@ class LikesTableSeeder extends Seeder
|
|||
*/
|
||||
public function run()
|
||||
{
|
||||
factory(App\Like::class, 10)->create();
|
||||
factory(Like::class, 10)->create();
|
||||
|
||||
$faker = new Generator();
|
||||
$faker->addProvider(new \Faker\Provider\en_US\Person($faker));
|
||||
$faker->addProvider(new \Faker\Provider\Lorem($faker));
|
||||
$faker->addProvider(new \Faker\Provider\Internet($faker));
|
||||
Like::create([
|
||||
'url' => $faker->url,
|
||||
'author_url' => $faker->url,
|
||||
'author_name' => $faker->name,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\{Media, Note, Place};
|
||||
|
||||
class NotesTableSeeder extends Seeder
|
||||
{
|
||||
|
@ -11,26 +12,31 @@ class NotesTableSeeder extends Seeder
|
|||
*/
|
||||
public function run()
|
||||
{
|
||||
factory(App\Note::class, 10)->create();
|
||||
factory(Note::class, 10)->create();
|
||||
sleep(1);
|
||||
$noteWithPlace = App\Note::create([
|
||||
$noteTwitterReply = Note::create([
|
||||
'note' => 'What does this even mean?',
|
||||
'in_reply_to' => 'https://twitter.com/realDonaldTrump/status/933662564587855877',
|
||||
]);
|
||||
sleep(1);
|
||||
$noteWithPlace = Note::create([
|
||||
'note' => 'Having a #beer at the local. 🍺',
|
||||
]);
|
||||
$noteWithPlace->tweet_id = '123456789';
|
||||
$place = App\Place::find(1);
|
||||
$place = Place::find(1);
|
||||
$noteWithPlace->place()->associate($place);
|
||||
$noteWithPlace->save();
|
||||
sleep(1);
|
||||
$noteWithContact = App\Note::create([
|
||||
$noteWithContact = Note::create([
|
||||
'note' => 'Hi @tantek'
|
||||
]);
|
||||
sleep(1);
|
||||
$noteWithContactPlusPic = App\Note::create([
|
||||
$noteWithContactPlusPic = Note::create([
|
||||
'note' => 'Hi @aaron',
|
||||
'client_id' => 'https://jbl5.dev/notes/new'
|
||||
]);
|
||||
sleep(1);
|
||||
$noteWithoutContact = App\Note::create([
|
||||
$noteWithoutContact = Note::create([
|
||||
'note' => 'Hi @bob',
|
||||
'client_id' => 'https://quill.p3k.io'
|
||||
]);
|
||||
|
@ -41,13 +47,31 @@ class NotesTableSeeder extends Seeder
|
|||
mkdir(public_path() . '/assets/profile-images/aaronparecki.com', 0755);
|
||||
copy(base_path() . '/tests/aaron.png', public_path() . '/assets/profile-images/aaronparecki.com/image');
|
||||
}
|
||||
$noteWithCoords = App\Note::create([
|
||||
'note' => 'Note from somehwere',
|
||||
$noteWithCoords = Note::create([
|
||||
'note' => 'Note from a town',
|
||||
]);
|
||||
$noteWithCoords->location = '53.499,-2.379';
|
||||
$noteWithCoords->save();
|
||||
sleep(1);
|
||||
$noteSyndicated = App\Note::create([
|
||||
$noteWithCoords2 = Note::create([
|
||||
'note' => 'Note from a city',
|
||||
]);
|
||||
$noteWithCoords2->location = '53.9026894,-2.42250444118781';
|
||||
$noteWithCoords2->save();
|
||||
sleep(1);
|
||||
$noteWithCoords3 = Note::create([
|
||||
'note' => 'Note from a county',
|
||||
]);
|
||||
$noteWithCoords3->location = '57.5066357,-5.0038367';
|
||||
$noteWithCoords3->save();
|
||||
sleep(1);
|
||||
$noteWithCoords4 = Note::create([
|
||||
'note' => 'Note from a country',
|
||||
]);
|
||||
$noteWithCoords4->location = '63.000147,-136.002502';
|
||||
$noteWithCoords4->save();
|
||||
sleep(1);
|
||||
$noteSyndicated = Note::create([
|
||||
'note' => 'This note has all the syndication targets',
|
||||
]);
|
||||
$noteSyndicated->tweet_id = '123456';
|
||||
|
@ -56,8 +80,29 @@ class NotesTableSeeder extends Seeder
|
|||
$noteSyndicated->instagram_url = 'https://www.instagram.com/p/aWsEd123Jh';
|
||||
$noteSyndicated->save();
|
||||
sleep(1);
|
||||
$noteWithTextLinkandEmoji = App\Note::create([
|
||||
$noteWithTextLinkandEmoji = Note::create([
|
||||
'note' => 'I love https://duckduckgo.com 💕' // theres a two-heart emoji at the end of this
|
||||
]);
|
||||
sleep(1);
|
||||
$media = new Media();
|
||||
$media->path = 'media/f1bc8faa-1a8f-45b8-a9b1-57282fa73f87.jpg';
|
||||
$media->type = 'image';
|
||||
$media->image_widths = '3648';
|
||||
$media->save();
|
||||
$noteWithImage = Note::create([
|
||||
'note' => 'A lovely waterfall',
|
||||
]);
|
||||
$noteWithImage->media()->save($media);
|
||||
sleep(1);
|
||||
$noteFromInstagram = Note::create([
|
||||
'note' => 'Lovely #wedding #weddingfavour',
|
||||
]);
|
||||
$noteFromInstagram->instagram_url = 'https://www.instagram.com/p/Bbo22MHhE_0';
|
||||
$noteFromInstagram->save();
|
||||
$mediaInstagram = new Media();
|
||||
$mediaInstagram->path = 'https://scontent-lhr3-1.cdninstagram.com/t51.2885-15/e35/23734479_149605352435937_400133507076063232_n.jpg';
|
||||
$mediaInstagram->type = 'image';
|
||||
$mediaInstagram->save();
|
||||
$noteFromInstagram->media()->save($mediaInstagram);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\Place;
|
||||
use App\Models\Place;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Phaza\LaravelPostgis\Geometries\Point;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
use App\WebMention;
|
||||
use App\Models\WebMention;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class WebMentionsTableSeeder extends Seeder
|
||||
|
@ -12,13 +12,21 @@ class WebMentionsTableSeeder extends Seeder
|
|||
*/
|
||||
public function run()
|
||||
{
|
||||
$webmention = WebMention::create([
|
||||
'source' => 'https://aaornpk.localhost/reply/1',
|
||||
'target' => 'https://jonnybarnes.localhost/notes/D',
|
||||
'commentable_id' => '13',
|
||||
'commentable_type' => 'App\Note',
|
||||
$webmentionAaron = WebMention::create([
|
||||
'source' => 'https://aaronpk.localhost/reply/1',
|
||||
'target' => config('app.url') . '/notes/E',
|
||||
'commentable_id' => '14',
|
||||
'commentable_type' => 'App\Models\Note',
|
||||
'type' => 'in-reply-to',
|
||||
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"url": ["https://aaronpk.localhost/reply/1"], "name": ["Hi too"], "author": [{"type": ["h-card"], "value": "Aaron Parecki", "properties": {"url": ["https://aaronpk.localhost"], "name": ["Aaron Parecki"], "photo": ["https://aaronparecki.com/images/profile.jpg"]}}], "content": [{"html": "Hi too", "value": "Hi too"}], "published": ["' . date(DATE_W3C) . '"], "in-reply-to": ["https://aaronpk.loclahost/reply/1", "https://jonnybarnes.uk/notes/D"]}}]}'
|
||||
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"url": ["https://aaronpk.localhost/reply/1"], "name": ["Hi too"], "author": [{"type": ["h-card"], "value": "Aaron Parecki", "properties": {"url": ["https://aaronpk.localhost"], "name": ["Aaron Parecki"], "photo": ["https://aaronparecki.com/images/profile.jpg"]}}], "content": [{"html": "Hi too", "value": "Hi too"}], "published": ["' . date(DATE_W3C) . '"], "in-reply-to": ["https://aaronpk.loclahost/reply/1", "' . config('app.url') .'/notes/E"]}}]}'
|
||||
]);
|
||||
$webmentionTantek = WebMention::create([
|
||||
'source' => 'http://tantek.com/',
|
||||
'target' => config('app.url') . '/notes/D',
|
||||
'commentable_id' => '13',
|
||||
'commentable_type' => 'App\Models\Note',
|
||||
'type' => 'in-reply-to',
|
||||
'mf2' => '{"rels": [], "items": [{"type": ["h-entry"], "properties": {"url": ["http://tantek.com/"], "name": ["KUTGW"], "author": [{"type": ["h-card"], "value": "Tantek Celik", "properties": {"url": ["http://tantek.com/"], "name": ["Tantek Celik"]}}], "content": [{"html": "kutgw", "value": "kutgw"}], "published": ["' . date(DATE_W3C) . '"], "in-reply-to": ["' . config('app.url') . '/notes/D"]}}]}'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
1471
package-lock.json
generated
1471
package-lock.json
generated
File diff suppressed because it is too large
Load diff
35
package.json
35
package.json
|
@ -6,13 +6,13 @@
|
|||
"license": "CC0-1.0",
|
||||
"dependencies": {
|
||||
"alertify.js": "^1.0.12",
|
||||
"mapbox-gl": "^0.42.0",
|
||||
"marked": "^0.3.6",
|
||||
"mapbox-gl": "^0.42.2",
|
||||
"marked": "^0.3.7",
|
||||
"normalize.css": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ajv": "^5.3.0",
|
||||
"autoprefixer": "^7.1.6",
|
||||
"ajv": "^5.5.2",
|
||||
"autoprefixer": "^7.2.3",
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
|
@ -21,22 +21,23 @@
|
|||
"babel-preset-latest": "^6.16.0",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"dotenv-webpack": "^1.5.4",
|
||||
"eslint": "^4.11.0",
|
||||
"eslint": "^4.13.1",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-node": "^5.2.1",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"husky": "^0.14.3",
|
||||
"lint-staged": "^5.0.0",
|
||||
"husky": "^0.15.0-beta.16",
|
||||
"lint-staged": "^6.0.0",
|
||||
"postcss-cli": "^4.1.1",
|
||||
"postcss-sass": "^0.2.0",
|
||||
"pre-commit": "^1.1.3",
|
||||
"source-list-map": "^2.0.0",
|
||||
"stylelint": "^8.2.0",
|
||||
"stylelint-config-standard": "^17.0.0",
|
||||
"uglify-js": "^3.1.9",
|
||||
"webpack": "^3.8.1",
|
||||
"webpack-sources": "^1.0.2"
|
||||
"stylelint": "^8.4.0",
|
||||
"stylelint-config-standard": "^18.0.0",
|
||||
"uglify-js": "^3.2.2",
|
||||
"webpack": "^3.10.0",
|
||||
"webpack-sources": "^1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"compress": "scripts/compress",
|
||||
|
@ -47,17 +48,21 @@
|
|||
"make:css": "npm run lint:sass && npm run sass && npm run postcss",
|
||||
"make:js": "npm run lint:es6 && npm run webpack && npm run uglifyjs",
|
||||
"postcss": "postcss public/assets/css/app.css --use autoprefixer --autoprefixer.browsers \"> 5%\" --replace --map",
|
||||
"precommit": "lint-staged",
|
||||
"sass": "sassc --style compressed --sourcemap resources/assets/sass/app.scss public/assets/css/app.css",
|
||||
"uglifyjs": "scripts/uglifyjs",
|
||||
"webpack": "webpack --progress --colors"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"resources/assets/es6/*.js": [
|
||||
"./resources/assets/es6/*.js": [
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
],
|
||||
"resources/assets/sass/**/*.scss": [
|
||||
"*.scss": [
|
||||
"stylelint --syntax=scss --fix",
|
||||
"git add"
|
||||
]
|
||||
|
|
8
phpcs.xml
Normal file
8
phpcs.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0"?>
|
||||
<ruleset name="jonnybarnes.uk">
|
||||
<description>Custom configuration for code running jonnybarnes.uk</description>
|
||||
<file>./app/</file>
|
||||
<rule ref="PSR2">
|
||||
<exclude name="PSR2.Namespaces.UseDeclaration" />
|
||||
</rule>
|
||||
</ruleset>
|
|
@ -7,7 +7,8 @@
|
|||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false">
|
||||
stopOnFailure="false"
|
||||
printerClass="Codedungeon\PHPUnitPrettyResultPrinter\Printer">
|
||||
<testsuites>
|
||||
<testsuite name="Feature">
|
||||
<directory suffix="Test.php">./tests/Feature</directory>
|
||||
|
|
2
public/assets/css/app.css
vendored
2
public/assets/css/app.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
|||
{"version":3,"sources":["../../../resources/assets/sass/_border-box.scss","../../../resources/assets/sass/_base-font.scss","../../../resources/assets/sass/_header.scss","../../../resources/assets/sass/_variables.scss","../../../resources/assets/sass/_main.scss","../../../resources/assets/sass/_hovercard.scss","../../../resources/assets/sass/_notes.scss","../../../resources/assets/sass/_pagination.scss","../../../resources/assets/sass/_contacts-page.scss","../../../resources/assets/sass/_projects.scss","../../../resources/assets/sass/_footer.scss","../../../resources/assets/sass/_bridgy-links.scss","../../../resources/assets/sass/_emoji.scss","../../../resources/assets/sass/_mapbox.scss","../../../resources/assets/sass/_colors.scss","../../../resources/assets/sass/_styles.scss","../../../resources/assets/sass/_tags.scss"],"names":[],"mappings":"AAKA,KACI,8BAAsB,AAAtB,qBAAsB,CACzB,qBAKG,2BAAmB,AAAnB,kBAAmB,CACtB,KCVG,eACA,yBAA0B,CAC7B,gBAGG,oBAAqB,CACxB,WCNG,oBACA,AADA,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,kBACA,AADA,cACA,yBACA,AADA,sBACA,AADA,mBACA,WACA,eCJgB,CDKnB,cAGG,eACA,cAAe,CAClB,eAGG,cAAe,CAClB,KEdG,oBACA,AADA,oBACA,AADA,aACA,4BACA,AADA,6BACA,AADA,0BACA,AADA,sBACA,0BACA,AADA,uBACA,AADA,oBACA,gBACA,cACA,gBAAiB,CACpB,SAGG,cAAe,CAClB,WAIG,gBAAiB,CACpB,aCfG,iBAAkB,CACrB,qBAGG,iBAAkB,CACrB,2BAGG,WAAY,CACf,8BAGG,oBAAa,AAAb,oBAAa,AAAb,YAAa,CAChB,WAGG,kBACA,8BACA,AADA,6BACA,AADA,uBACA,AADA,mBACA,yBACA,AADA,sBACA,AADA,8BACA,sBACA,AADA,mBACA,AADA,qBACA,iBACA,YACA,WACA,UACA,WACA,uBACA,kBACA,2CACA,AADA,mCACA,YAAa,CAChB,0BAGG,WACA,WAAY,CACf,sBAGG,YAAa,CCnCjB,MACI,oBACA,AADA,oBACA,AADA,aACA,4BACA,AADA,6BACA,AADA,0BACA,AADA,sBACA,cAAe,CAClB,UAGG,eACA,eAAgB,CACnB,eAGG,oBACA,AADA,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,uBACA,AADA,mBACA,yBAA8B,AAA9B,sBAA8B,AAA9B,6BAA8B,CACjC,MAGG,WACA,UAAW,CACd,YCtBG,oBACA,AADA,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,uBACA,AADA,mBACA,8BACA,AADA,2BACA,AADA,6BACA,eACA,oBAAqB,CACxB,cCLG,eACA,oBACA,AADA,oBACA,AADA,aACA,8BACA,AADA,8BACA,AADA,+BACA,AADA,2BACA,yBACA,AADA,sBACA,AADA,8BACA,eAAgB,CACnB,kBAGG,WACA,WAAY,CACf,UCVG,cAAe,CAClB,gBCDG,gBACA,cACA,gBAAiB,CACpB,OAGG,gBACA,cACA,oBACA,AADA,oBACA,AADA,aACA,4BACA,AADA,6BACA,AADA,0BACA,AADA,sBACA,yBAAmB,AAAnB,sBAAmB,AAAnB,kBAAmB,CACtB,qDCVG,YAAa,CAChB,sDCCG,iBAAkB,CACrB,gFAIG,kBACA,cACA,UACA,aACA,OACA,cACA,qBACA,yBACA,oBACA,4CACA,AADA,oCACA,yBACA,kCACA,WACA,cACA,0CAAkC,AAAlC,iCAAkC,CACrC,2BAGG,KACI,aACA,6BACA,wCACA,0BACA,8BAAkC,AAAlC,qBAAkC,CAGtC,GACI,aACA,kCACA,yBACA,WACA,4CAAgD,AAAhD,mCAAgD,CAAA,CAIxD,AApBC,mBAGG,KACI,aACA,6BACA,wCACA,0BACA,8BAAkC,AAAlC,qBAAkC,CAGtC,GACI,aACA,kCACA,yBACA,WACA,4CAAgD,AAAhD,mCAAgD,CAAA,CAIxD,aACI,kCACI,kCAAmC,CACtC,CC/CL,KACI,YAAa,CAChB,oBAGG,kBAAmB,CACtB,QAGG,y4HACA,wBACA,WACA,WAAY,CACf,UAGG,kBACA,MACA,OACA,iBACA,cAAe,CAClB,gBAGG,gBACA,gBAAiB,CACpB,KCzBG,gCACA,kBAAmB,CACtB,WAGG,8BACA,kBAAmB,CACtB,YAIG,iBAAkB,CACrB,aCZG,oBAAqB,CACxB,KAGG,oBAAqB,CACxB,MCHG,SACA,gBACA,SAAU,CACb,SAGG,WACA,oBAAqB,CACxB,kBAIG,wBACA,0BACA,mBACA,qBACA,cACA,mBACA,sBACA,kBACA,qBACA,qBACA,8BAAsB,AAAtB,qBAAsB,CACzB,YAGG,0BACA,uCACA,oCACA,oCACA,WACA,kBACA,QACA,KAAM,CACT,WAGG,4BACA,kBAAmB,CACtB,kBAGG,4BAA6B,CAChC","file":"app.css"}
|
||||
{"version":3,"sources":["../../../resources/assets/sass/_border-box.scss","../../../resources/assets/sass/_base-font.scss","../../../resources/assets/sass/_header.scss","../../../resources/assets/sass/_variables.scss","../../../resources/assets/sass/_main.scss","../../../resources/assets/sass/_hovercard.scss","../../../resources/assets/sass/_notes.scss","../../../resources/assets/sass/_pagination.scss","../../../resources/assets/sass/_contacts-page.scss","../../../resources/assets/sass/_projects.scss","../../../resources/assets/sass/_footer.scss","../../../resources/assets/sass/_bridgy-links.scss","../../../resources/assets/sass/_emoji.scss","../../../resources/assets/sass/_mapbox.scss","../../../resources/assets/sass/_colors.scss","../../../resources/assets/sass/_styles.scss","../../../resources/assets/sass/_tags.scss"],"names":[],"mappings":"AAKA,KACI,8BAAsB,AAAtB,qBAAsB,CACzB,qBAKG,2BAAmB,AAAnB,kBAAmB,CACtB,KCVG,eACA,gCAAiC,CACpC,gBAGG,oBAAqB,CACxB,WCNG,oBACA,AADA,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,kBACA,AADA,cACA,yBACA,AADA,sBACA,AADA,mBACA,WACA,eCJgB,CDKnB,cAGG,eACA,cAAe,CAClB,eAGG,cAAe,CAClB,KEdG,oBACA,AADA,oBACA,AADA,aACA,4BACA,AADA,6BACA,AADA,0BACA,AADA,sBACA,0BACA,AADA,uBACA,AADA,oBACA,gBACA,cACA,gBAAiB,CACpB,SAGG,cAAe,CAClB,WAIG,gBAAiB,CACpB,aCfG,iBAAkB,CACrB,qBAGG,iBAAkB,CACrB,2BAGG,WAAY,CACf,WAGG,kBACA,8BACA,AADA,6BACA,AADA,uBACA,AADA,mBACA,yBACA,AADA,sBACA,AADA,8BACA,sBACA,AADA,mBACA,AADA,qBACA,iBACA,YACA,WACA,UACA,WACA,uBACA,kBACA,2CACA,AADA,mCACA,YAAa,CAChB,8BAGG,oBAAa,AAAb,oBAAa,AAAb,YAAa,CAChB,0BAGG,WACA,WAAY,CACf,sBAGG,YAAa,CCnCjB,MACI,oBACA,AADA,oBACA,AADA,aACA,4BACA,AADA,6BACA,AADA,0BACA,AADA,sBACA,cAAe,CAClB,UAGG,eACA,eAAgB,CACnB,eAGG,oBACA,AADA,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,uBACA,AADA,mBACA,yBAA8B,AAA9B,sBAA8B,AAA9B,6BAA8B,CACjC,MAGG,WACA,UAAW,CACd,YCtBG,oBACA,AADA,oBACA,AADA,aACA,8BACA,AADA,6BACA,AADA,uBACA,AADA,mBACA,8BACA,AADA,2BACA,AADA,6BACA,eACA,oBAAqB,CACxB,cCLG,eACA,oBACA,AADA,oBACA,AADA,aACA,8BACA,AADA,8BACA,AADA,+BACA,AADA,2BACA,yBACA,AADA,sBACA,AADA,8BACA,eAAgB,CACnB,kBAGG,WACA,WAAY,CACf,UCVG,cAAe,CAClB,gBCDG,gBACA,cACA,gBAAiB,CACpB,OAGG,gBACA,cACA,oBACA,AADA,oBACA,AADA,aACA,4BACA,AADA,6BACA,AADA,0BACA,AADA,sBACA,yBAAmB,AAAnB,sBAAmB,AAAnB,kBAAmB,CACtB,qDCVG,YAAa,CAChB,2BCAG,iBAAkB,CACrB,gFAIG,kBACA,cACA,UACA,aACA,OACA,cACA,qBACA,yBACA,oBACA,4CACA,AADA,oCACA,yBACA,kCACA,WACA,cACA,0CAAkC,AAAlC,iCAAkC,CACrC,2BAGG,KACI,aACA,6BACA,wCACA,0BACA,8BAAkC,AAAlC,qBAAkC,CAGtC,GACI,aACA,kCACA,yBACA,WACA,4CAAgD,AAAhD,mCAAgD,CAAA,CAIxD,AApBC,mBAGG,KACI,aACA,6BACA,wCACA,0BACA,8BAAkC,AAAlC,qBAAkC,CAGtC,GACI,aACA,kCACA,yBACA,WACA,4CAAgD,AAAhD,mCAAgD,CAAA,CAIxD,aACI,kCACI,kCAAmC,CACtC,CC9CL,KACI,YAAa,CAChB,oBAGG,kBAAmB,CACtB,QAGG,y4HACA,wBACA,WACA,WAAY,CACf,UAGG,kBACA,MACA,OACA,iBACA,cAAe,CAClB,gBAGG,gBACA,gBAAiB,CACpB,KCzBG,gCACA,kBAAmB,CACtB,WAGG,8BACA,kBAAmB,CACtB,YAIG,iBAAkB,CACrB,KCZG,oBAAqB,CACxB,aAGG,oBAAqB,CACxB,MCHG,SACA,gBACA,SAAU,CACb,SAGG,WACA,oBAAqB,CACxB,kBAIG,wBACA,0BACA,mBACA,qBACA,cACA,mBACA,sBACA,kBACA,qBACA,qBACA,8BAAsB,AAAtB,qBAAsB,CACzB,YAGG,0BACA,uCACA,oCACA,oCACA,WACA,kBACA,QACA,KAAM,CACT,WAGG,4BACA,kBAAmB,CACtB,kBAGG,4BAA6B,CAChC","file":"app.css"}
|
1
public/assets/frontend/mapbox-gl.css
vendored
1
public/assets/frontend/mapbox-gl.css
vendored
|
@ -223,6 +223,7 @@ a.mapboxgl-ctrl-logo {
|
|||
border-color: #333;
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mapboxgl-popup {
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
|||
{"version":3,"sources":["webpack:///webpack/bootstrap b6efe62e7997f66fb20e","webpack:///colours.js"],"names":["__webpack_require__","moduleId","installedModules","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","configurable","enumerable","get","n","__esModule","object","property","prototype","hasOwnProperty","p","s","css","document","querySelector","getAttribute","split","pop","getElementById","value","form","childNodes","addEventListener","e","preventDefault","newCss","link","parts","push","setAttribute","join","formData","FormData","fetch","method","credentials","body","catch","error","console","warn"],"mappings":"mBAIA,SAAAA,oBAAAC,UAGA,GAAAC,iBAAAD,UACA,OAAAC,iBAAAD,UAAAE,QAGA,IAAAC,OAAAF,iBAAAD,WACAI,EAAAJ,SACAK,GAAA,EACAH,YAUA,OANAI,QAAAN,UAAAO,KAAAJ,OAAAD,QAAAC,OAAAA,OAAAD,QAAAH,qBAGAI,OAAAE,GAAA,EAGAF,OAAAD,QAvBA,IAAAD,oBA4BAF,oBAAAS,EAAAF,QAGAP,oBAAAU,EAAAR,iBAGAF,oBAAAW,EAAA,SAAAR,QAAAS,KAAAC,QACAb,oBAAAc,EAAAX,QAAAS,OACAG,OAAAC,eAAAb,QAAAS,MACAK,cAAA,EACAC,YAAA,EACAC,IAAAN,UAMAb,oBAAAoB,EAAA,SAAAhB,QACA,IAAAS,OAAAT,QAAAA,OAAAiB,WACA,WAA2B,OAAAjB,OAAA,SAC3B,WAAiC,OAAAA,QAEjC,OADAJ,oBAAAW,EAAAE,OAAA,IAAAA,QACAA,QAIAb,oBAAAc,EAAA,SAAAQ,OAAAC,UAAsD,OAAAR,OAAAS,UAAAC,eAAAjB,KAAAc,OAAAC,WAGtDvB,oBAAA0B,EAAA,GAGA1B,oBAAAA,oBAAA2B,EAAA,iEC3DA,IAEIC,IAFOC,SAASC,cAAc,iBAEnBC,aAAa,QAAQC,MAAM,KAAKC,MAG/CJ,SAASK,eAAe,sBAAsBC,MAAQP,IAGtD,IAAIQ,KAAOP,SAASK,eAAe,oBACzBE,KAAKC,WAAW,GACtBC,iBAAiB,QAAS,SAAUC,GACpCA,EAAEC,iBACF,IAAIC,OAASZ,SAASK,eAAe,sBAAsBC,MACvDO,KAAOb,SAASC,cAAc,iBAE9Ba,MADMD,KAAKX,aAAa,QACZC,MAAM,KACtBW,MAAMV,MACNU,MAAMC,KAAKH,QACXC,KAAKG,aAAa,OAAQF,MAAMG,KAAK,MACrC,IAAIC,SAAW,IAAIC,SAASZ,MAC5Ba,MAAM,yBACFC,OAAQ,OACRC,YAAa,cACbC,KAAML,WACPM,MAAM,SAAUC,OACfC,QAAQC,KAAKF","file":"public/assets/js/colours.js.map","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 4);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap b6efe62e7997f66fb20e","//colours.js\n\nlet link = document.querySelector('#colourScheme');\n\nlet css = link.getAttribute('href').split('/').pop();\n\n// update selected item in colour scheme list\ndocument.getElementById('colourSchemeSelect').value = css;\n\n// fix form\nlet form = document.getElementById('colourSchemeForm');\nlet btn = form.childNodes[5];\nbtn.addEventListener('click', function (e) {\n e.preventDefault();\n let newCss = document.getElementById('colourSchemeSelect').value;\n let link = document.querySelector('#colourScheme');\n let css = link.getAttribute('href');\n let parts = css.split('/');\n parts.pop();\n parts.push(newCss);\n link.setAttribute('href', parts.join('/'));\n let formData = new FormData(form);\n fetch('/update-colour-scheme', {\n method: 'POST',\n credentials: 'same-origin',\n body: formData\n }).catch(function (error) {\n console.warn(error);\n });\n});\n\n\n\n// WEBPACK FOOTER //\n// ./colours.js"]}
|
||||
{"version":3,"sources":["webpack:///webpack/bootstrap b56e9accee14dcede691","webpack:///colours.js"],"names":["__webpack_require__","moduleId","installedModules","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","configurable","enumerable","get","n","__esModule","object","property","prototype","hasOwnProperty","p","s","css","document","querySelector","getAttribute","split","pop","getElementById","value","form","childNodes","addEventListener","e","preventDefault","newCss","link","parts","push","setAttribute","join","formData","FormData","fetch","method","credentials","body","catch","error","console","warn"],"mappings":"mBAIA,SAAAA,oBAAAC,UAGA,GAAAC,iBAAAD,UACA,OAAAC,iBAAAD,UAAAE,QAGA,IAAAC,OAAAF,iBAAAD,WACAI,EAAAJ,SACAK,GAAA,EACAH,YAUA,OANAI,QAAAN,UAAAO,KAAAJ,OAAAD,QAAAC,OAAAA,OAAAD,QAAAH,qBAGAI,OAAAE,GAAA,EAGAF,OAAAD,QAvBA,IAAAD,oBA4BAF,oBAAAS,EAAAF,QAGAP,oBAAAU,EAAAR,iBAGAF,oBAAAW,EAAA,SAAAR,QAAAS,KAAAC,QACAb,oBAAAc,EAAAX,QAAAS,OACAG,OAAAC,eAAAb,QAAAS,MACAK,cAAA,EACAC,YAAA,EACAC,IAAAN,UAMAb,oBAAAoB,EAAA,SAAAhB,QACA,IAAAS,OAAAT,QAAAA,OAAAiB,WACA,WAA2B,OAAAjB,OAAA,SAC3B,WAAiC,OAAAA,QAEjC,OADAJ,oBAAAW,EAAAE,OAAA,IAAAA,QACAA,QAIAb,oBAAAc,EAAA,SAAAQ,OAAAC,UAAsD,OAAAR,OAAAS,UAAAC,eAAAjB,KAAAc,OAAAC,WAGtDvB,oBAAA0B,EAAA,GAGA1B,oBAAAA,oBAAA2B,EAAA,iEC3DA,IAEIC,IAFOC,SAASC,cAAc,iBAEnBC,aAAa,QAAQC,MAAM,KAAKC,MAG/CJ,SAASK,eAAe,sBAAsBC,MAAQP,IAGtD,IAAIQ,KAAOP,SAASK,eAAe,oBACzBE,KAAKC,WAAW,GACtBC,iBAAiB,QAAS,SAAUC,GACpCA,EAAEC,iBACF,IAAIC,OAASZ,SAASK,eAAe,sBAAsBC,MACvDO,KAAOb,SAASC,cAAc,iBAE9Ba,MADMD,KAAKX,aAAa,QACZC,MAAM,KACtBW,MAAMV,MACNU,MAAMC,KAAKH,QACXC,KAAKG,aAAa,OAAQF,MAAMG,KAAK,MACrC,IAAIC,SAAW,IAAIC,SAASZ,MAC5Ba,MAAM,yBACFC,OAAQ,OACRC,YAAa,cACbC,KAAML,WACPM,MAAM,SAAUC,OACfC,QAAQC,KAAKF","file":"public/assets/js/colours.js.map","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 4);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap b56e9accee14dcede691","//colours.js\n\nlet link = document.querySelector('#colourScheme');\n\nlet css = link.getAttribute('href').split('/').pop();\n\n// update selected item in colour scheme list\ndocument.getElementById('colourSchemeSelect').value = css;\n\n// fix form\nlet form = document.getElementById('colourSchemeForm');\nlet btn = form.childNodes[5];\nbtn.addEventListener('click', function (e) {\n e.preventDefault();\n let newCss = document.getElementById('colourSchemeSelect').value;\n let link = document.querySelector('#colourScheme');\n let css = link.getAttribute('href');\n let parts = css.split('/');\n parts.pop();\n parts.push(newCss);\n link.setAttribute('href', parts.join('/'));\n let formData = new FormData(form);\n fetch('/update-colour-scheme', {\n method: 'POST',\n credentials: 'same-origin',\n body: formData\n }).catch(function (error) {\n console.warn(error);\n });\n});\n\n\n\n// WEBPACK FOOTER //\n// ./colours.js"]}
|
File diff suppressed because one or more lines are too long
2
public/assets/js/maps.js
vendored
2
public/assets/js/maps.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue