diff --git a/.env.example b/.env.example
index 15ee3f46..72c19df3 100644
--- a/.env.example
+++ b/.env.example
@@ -22,7 +22,7 @@ REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
-MAIL_HOST=mailtrap.io
+MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
@@ -52,3 +52,5 @@ TWITTER_ACCESS_TOKEN_SECRET=
SCOUT_DRIVER=pgsql
PIWIK=false
+
+PSYSH_CONFIG=tinker.config.php
diff --git a/.gitattributes b/.gitattributes
index a8763f8e..2195b20d 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,4 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
+*.js linguist-vendored
diff --git a/.travis.yml b/.travis.yml
index df73f1c7..b1a6d010 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,6 +19,7 @@ addons:
paths:
- $(ls tests/Browser/screenshots/*.png | tr "\n" ":")
- $(ls tests/Browser/console/*.log | tr "\n" ":")
+ - $(ls storage/logs/*.log | tr "\n" ":")
- $(ls /tmp/*.log | tr "\n" ":")
services:
@@ -49,16 +50,18 @@ install:
- travis/install-nginx.sh
before_script:
+ - echo 'error_log = "/tmp/php.error.log"' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
- psql -U travis -c 'create database travis_ci_test'
- psql -U travis -d travis_ci_test -c 'create extension postgis'
- cp .env.travis .env
- php artisan key:generate
- php artisan migrate
- php artisan db:seed
+ - php artisan token:generate
- phantomjs --webdriver=127.0.0.1:9515 --webdriver-loglevel=DEBUG &
- sleep 5 # Give artisan some time to start serving
script:
- php vendor/bin/phpunit --coverage-text
- php artisan dusk
- - php artisan security:check
+ - php vendor/bin/security-checker security:check ./composer.lock --end-point=http://security.sensiolabs.org/check_lock
diff --git a/app/Console/Commands/GenerateToken.php b/app/Console/Commands/GenerateToken.php
index 72bea542..4398a757 100644
--- a/app/Console/Commands/GenerateToken.php
+++ b/app/Console/Commands/GenerateToken.php
@@ -2,9 +2,9 @@
namespace App\Console\Commands;
+use App\IndieWebUser;
use App\Services\TokenService;
use Illuminate\Console\Command;
-use Illuminate\Support\Facades\Storage;
class GenerateToken extends Command
{
@@ -49,10 +49,12 @@ class GenerateToken extends Command
$data = [
'me' => config('app.url'),
'client_id' => route('micropub-client'),
- 'scope' => 'post',
+ 'scope' => 'create update',
];
$token = $tokenService->getNewToken($data);
- Storage::disk('local')->put('dev-token', $token);
+ $user = IndieWebUser::where('me', config('app.url'))->first();
+ $user->token = $token;
+ $user->save();
$this->info('Set token');
}
diff --git a/app/Exceptions/InvalidTokenException.php b/app/Exceptions/InvalidTokenException.php
new file mode 100644
index 00000000..8184cfa7
--- /dev/null
+++ b/app/Exceptions/InvalidTokenException.php
@@ -0,0 +1,13 @@
+indieAuthService = $indieAuthService ?? new IndieAuthService();
- $this->tokenService = $tokenService ?? new TokenService();
+ public function __construct(Client $client = null)
+ {
+ $this->client = $client ?? new Client();
}
/**
@@ -44,25 +35,31 @@ class IndieAuthController extends Controller
*/
public function start(Request $request)
{
- $authorizationEndpoint = $this->indieAuthService->getAuthorizationEndpoint(
- $request->input('me')
- );
- if ($authorizationEndpoint !== null) {
- $authorizationURL = $this->indieAuthService->buildAuthorizationURL(
+ $url = normalize_url($request->input('me'));
+ $authorizationEndpoint = $this->client->discoverAuthorizationEndpoint($url);
+ if ($authorizationEndpoint != null) {
+ $state = bin2hex(openssl_random_pseudo_bytes(16));
+ session(['state' => $state]);
+ $authorizationURL = $this->client->buildAuthorizationURL(
$authorizationEndpoint,
- $request->input('me')
+ $url,
+ route('indieauth-callback'), //redirect_uri
+ route('micropub-client'), //client_id
+ $state
);
if ($authorizationURL) {
return redirect($authorizationURL);
}
+
+ return redirect(route('micropub-client'))->with('error', 'Error building authorization URL');
}
return redirect(route('micropub-client'))->with('error', 'Unable to determine authorisation endpoint');
}
/**
- * Once they have verified themselves through the authorisation endpint
- * the next step is retreiveing a token from the token endpoint.
+ * Once they have verified themselves through the authorisation endpoint
+ * the next step is register/login the user.
*
* @param \Illuminate\Http\Rrequest $request
* @return \Illuminate\Routing\RedirectResponse redirect
@@ -75,38 +72,16 @@ class IndieAuthController extends Controller
'Invalid state
value returned from indieauth server'
);
}
- $tokenEndpoint = $this->indieAuthService->getTokenEndpoint($request->input('me'));
- if ($tokenEndpoint === false) {
- return redirect(route('micropub-client'))->with(
- 'error',
- 'Unable to determine token endpoint'
- );
- }
- $data = [
- 'endpoint' => $tokenEndpoint,
- 'code' => $request->input('code'),
- 'me' => $request->input('me'),
- 'redirect_url' => route('indieauth-callback'),
- 'client_id' => route('micropub-client'),
- 'state' => $request->input('state'),
- ];
- $token = $this->indieAuthService->getAccessToken($data);
- if (array_key_exists('access_token', $token)) {
- $request->session()->put('me', $token['me']);
- $request->session()->put('token', $token['access_token']);
+ $url = normalize_url($request->input('me'));
+ $indiewebUser = IndieWebUser::firstOrCreate(['me' => $url]);
+ $request->session()->put(['me' => $url]);
- return redirect(route('micropub-client'));
- }
-
- return redirect(route('micropub-client'))->with(
- 'error',
- 'Unable to get a token from the endpoint'
- );
+ return redirect(route('micropub-client'));
}
/**
- * Log out the user, flush an session data, and overwrite any cookie data.
+ * Log out the user, flush the session data.
*
* @return \Illuminate\Routing\RedirectResponse redirect
*/
@@ -114,44 +89,6 @@ class IndieAuthController extends Controller
{
$request->session()->flush();
- return redirect(route('micropub-client'))->cookie('me', 'loggedout', 1);
- }
-
- /**
- * If the user has auth’d via IndieAuth, issue a valid token.
- *
- * @param \Illuminate\Http\Request $request
- * @return \Illuminate\Http\Response
- */
- public function tokenEndpoint(Request $request)
- {
- $authData = [
- 'code' => $request->input('code'),
- 'me' => $request->input('me'),
- 'redirect_url' => $request->input('redirect_uri'),
- 'client_id' => $request->input('client_id'),
- 'state' => $request->input('state'),
- ];
- $auth = $this->indieAuthService->verifyIndieAuthCode($authData);
- if (array_key_exists('me', $auth)) {
- $scope = $auth['scope'] ?? '';
- $tokenData = [
- 'me' => $request->input('me'),
- 'client_id' => $request->input('client_id'),
- 'scope' => $auth['scope'],
- ];
- $token = $this->tokenService->getNewToken($tokenData);
- $content = http_build_query([
- 'me' => $request->input('me'),
- 'scope' => $scope,
- 'access_token' => $token,
- ]);
-
- return response($content)
- ->header('Content-Type', 'application/x-www-form-urlencoded');
- }
- $content = 'There was an error verifying the authorisation code.';
-
- return response($content, 400);
+ return redirect(route('micropub-client'));
}
}
diff --git a/app/Http/Controllers/MicropubClientController.php b/app/Http/Controllers/MicropubClientController.php
index bdeede26..4c97b968 100644
--- a/app/Http/Controllers/MicropubClientController.php
+++ b/app/Http/Controllers/MicropubClientController.php
@@ -2,7 +2,7 @@
namespace App\Http\Controllers;
-use App\Services\IndieAuthService;
+use App\IndieWebUser;
use IndieAuth\Client as IndieClient;
use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Http\{Request, Response};
@@ -10,20 +10,13 @@ use GuzzleHttp\Exception\{ClientException, ServerException};
class MicropubClientController extends Controller
{
- /**
- * The IndieAuth service container.
- */
- protected $indieAuthService;
-
/**
* Inject the dependencies.
*/
public function __construct(
- IndieAuthService $indieAuthService = null,
IndieClient $indieClient = null,
GuzzleClient $guzzleClient = null
) {
- $this->indieAuthService = $indieAuthService ?? new IndieAuthService();
$this->guzzleClient = $guzzleClient ?? new GuzzleClient();
$this->indieClient = $indieClient ?? new IndieClient();
}
@@ -37,8 +30,11 @@ class MicropubClientController extends Controller
public function create(Request $request)
{
$url = $request->session()->get('me');
- $syndication = $request->session()->get('syndication');
- $mediaEndpoint = $request->session()->get('media-endpoint');
+ if ($url) {
+ $indiewebUser = IndieWebUser::where('me', $url)->first();
+ }
+ $syndication = $this->parseSyndicationTargets($indiewebUser->syndication);
+ $mediaEndpoint = $indiewebUser->mediaEndpoint ?? null;
$mediaURLs = $request->session()->get('media-links');
return view('micropub.create', compact('url', 'syndication', 'mediaEndpoint', 'mediaURLs'));
@@ -56,19 +52,17 @@ class MicropubClientController extends Controller
return back();
}
- $mediaEndpoint = $request->session()->get('media-endpoint');
- if ($mediaEndpoint == null) {
+ $user = IndieWebUser::where('me', $request->session()->get('me'))->firstOrFail();
+ if ($user->mediaEndpoint == null || $user->token == null) {
return back();
}
- $token = $request->session()->get('token');
-
$mediaURLs = [];
foreach ($request->file('file') as $file) {
try {
- $response = $this->guzzleClient->request('POST', $mediaEndpoint, [
+ $response = $this->guzzleClient->request('POST', $user->mediaEndpoint, [
'headers' => [
- 'Authorization' => 'Bearer ' . $token,
+ 'Authorization' => 'Bearer ' . $user->token,
],
'multipart' => [
[
@@ -109,27 +103,142 @@ class MicropubClientController extends Controller
*/
public function store(Request $request)
{
- $domain = $request->session()->get('me');
- $token = $request->session()->get('token');
+ $url = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
- $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint(
- $domain,
- $this->indieClient
- );
+ if ($user->token == null) {
+ return redirect(route('micropub-client'))->with('error', 'You haven’t requested a token yet');
+ }
+
+ $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if (! $micropubEndpoint) {
return redirect(route('micropub-client'))->with('error', 'Unable to determine micropub API endpoint');
}
- $response = $this->postNoteRequest($request, $micropubEndpoint, $token);
+ $headers = [
+ 'Authorization' => 'Bearer ' . $user->token,
+ ];
- if ($response->getStatusCode() == 201) {
- $request->session()->forget('media-links');
- $location = $response->getHeader('Location');
- if (is_array($location)) {
- return redirect($location[0]);
+ if ($user->syntax == 'html') {
+ $multipart = [
+ [
+ 'name' => 'h',
+ 'contents' => 'entry',
+ ],
+ [
+ 'name' => 'content',
+ 'contents' => $request->input('content'),
+ ],
+ ];
+ if ($request->hasFile('photo')) {
+ $photos = $request->file('photo');
+ foreach ($photos as $photo) {
+ $multipart[] = [
+ 'name' => 'photo[]',
+ 'contents' => fopen($photo->path(), 'r'),
+ 'filename' => $photo->getClientOriginalName(),
+ ];
+ }
+ }
+ if ($request->input('in-reply-to') != '') {
+ $multipart[] = [
+ 'name' => 'in-reply-to',
+ 'contents' => $request->input('in-reply-to'),
+ ];
+ }
+ if ($request->input('mp-syndicate-to')) {
+ foreach ($request->input('mp-syndicate-to') as $syn) {
+ $multipart[] = [
+ 'name' => 'mp-syndicate-to[]',
+ 'contents' => $syn,
+ ];
+ }
+ }
+ if ($request->input('location')) {
+ if ($request->input('location') !== 'no-location') {
+ $multipart[] = [
+ 'name' => 'location',
+ 'contents' => $request->input('location'),
+ ];
+ }
+ }
+ if ($request->input('media')) {
+ foreach ($request->input('media') as $media) {
+ $multipart[] = [
+ 'name' => 'photo[]',
+ 'contents' => $media,
+ ];
+ }
+ }
+ try {
+ $response = $this->guzzleClient->post($micropubEndpoint, [
+ 'multipart' => $multipart,
+ 'headers' => $headers,
+ ]);
+ } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+ return redirect(route('micropub-client'))->with(
+ 'error',
+ 'There was a bad response from the micropub endpoint.'
+ );
}
- return redirect($location);
+ if ($response->getStatusCode() == 201) {
+ $request->session()->forget('media-links');
+ $location = $response->getHeader('Location');
+ if (is_array($location)) {
+ return redirect($location[0]);
+ }
+
+ return redirect($location);
+ }
+ }
+
+ if ($user->syntax == 'json') {
+ $json = [];
+ $json['type'] = ['h-entry'];
+ $json['properties'] = ['content' => [$request->input('content')]];
+
+ if ($request->input('in-reply-to') != '') {
+ $json['properties']['in-reply-to'] = [$request->input('in-reply-to')];
+ }
+ if ($request->input('mp-syndicate-to')) {
+ foreach ($request->input('mp-syndicate-to') as $syn) {
+ $json['properties']['mp-syndicate-to'] = [$syn];
+ }
+ }
+ if ($request->input('location')) {
+ if ($request->input('location') !== 'no-location') {
+ $json['properties']['location'] = [$request->input('location')];
+ }
+ }
+ if ($request->input('media')) {
+ $json['properties']['photo'] = [];
+ foreach ($request->input('media') as $media) {
+ $json['properties']['photo'][] = $media;
+ }
+ }
+
+ try {
+ $response = $this->guzzleClient->post($micropubEndpoint, [
+ 'json' => $json,
+ 'headers' => $headers,
+ ]);
+ } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+ return redirect(route('micropub-client'))->with(
+ 'error',
+ 'There was a bad response from the micropub endpoint.'
+ );
+ }
+
+ if ($response->getStatusCode() == 201) {
+ $request->session()->forget('media-links');
+ $location = $response->getHeader('Location');
+ if (is_array($location)) {
+ return redirect($location[0]);
+ }
+
+ return redirect($location);
+ }
}
return redirect(route('micropub-client'))->with('error', 'Endpoint didn’t create the note.');
@@ -143,26 +252,100 @@ class MicropubClientController extends Controller
*/
public function config(Request $request)
{
- $data['me'] = $request->session()->get('me');
- $data['token'] = $request->session()->get('token');
- $data['syndication'] = $request->session()->get('syndication') ?? 'none defined';
- $data['media-endpoint'] = $request->session()->get('media-endpoint') ?? 'none defined';
+ //default values
+ $data = [
+ 'me' => '',
+ 'token' => 'none',
+ 'syndication' => 'none defined',
+ 'media-endpoint' => 'none defined',
+ 'syntax' => 'html',
+ ];
+ if ($request->session()->has('me')) {
+ $data['me'] = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $request->session()->get('me'))->first();
+ $data['token'] = $user->token ?? 'none defined';
+ $data['syndication'] = $user->syndication ?? 'none defined';
+ $data['media-endpoint'] = $user->mediaEndpoint ?? 'none defined';
+ $data['syntax'] = $user->syntax;
+ }
return view('micropub.config', compact('data'));
}
/**
- * Query the micropub endpoint and store response in the session.
+ * Get a new token.
+ *
+ * @param Illuminate\Http\Request $request
+ * @return view
+ */
+ public function getNewToken(Request $request)
+ {
+ if ($request->session()->has('me')) {
+ $url = normalize_url($request->session()->get('me'));
+ $authozationEndpoint = $this->indieClient->discoverAuthorizationEndpoint($url);
+ if ($authozationEndpoint) {
+ $state = bin2hex(random_bytes(16));
+ $request->session()->put('state', $state);
+ $authorizationURL = $this->indieClient->buildAuthorizationURL(
+ $authozationEndpoint,
+ $url,
+ route('micropub-client-get-new-token-callback'), // redirect_uri
+ route('micropub-client'), //client_id
+ $state,
+ 'create update' // scope needs to be a setting
+ );
+
+ return redirect($authorizationURL);
+ }
+
+ return back();
+ }
+
+ return back();
+ }
+
+ /**
+ * The callback for getting a token.
+ */
+ public function getNewTokenCallback(Request $request)
+ {
+ if ($request->input('state') !== $request->session()->get('state')) {
+ return route('micropub-client')->with('error', 'The state
didn’t match.');
+ }
+ $tokenEndpoint = $this->indieClient->discoverTokenEndpoint(normalize_url($request->input('me')));
+ if ($tokenEndpoint) {
+ $token = $this->indieClient->getAccessToken(
+ $tokenEndpoint,
+ $request->input('code'),
+ $request->input('me'),
+ route('micropub-client-get-new-token-callback'), // redirect_uri
+ route('micropub-client'), // client_id
+ $request->input('state')
+ );
+ if (array_key_exists('access_token', $token)) {
+ $url = normalize_url($token['me']);
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
+ $user->token = $token['access_token'];
+ $user->save();
+
+ return redirect('micropub-config');
+ }
+ }
+ }
+
+ /**
+ * Query the micropub endpoint and store response.
*
* @param Illuminate\Http\Request $request
* @return redirect
*/
public function queryEndpoint(Request $request)
{
- $domain = $request->session()->get('me');
- $token = $request->session()->get('token');
- $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain);
- if ($micropubEndpoint !== null) {
+ $url = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
+ $token = $user->token;
+ $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
+ if ($micropubEndpoint) {
try {
$response = $this->guzzleClient->get($micropubEndpoint, [
'headers' => ['Authorization' => 'Bearer ' . $token],
@@ -172,96 +355,35 @@ class MicropubClientController extends Controller
return back();
}
$body = (string) $response->getBody();
+ $data = json_decode($body, true);
- $syndication = $this->parseSyndicationTargets($body);
- $request->session()->put('syndication', $syndication);
+ if (array_key_exists('syndicate-to', $data)) {
+ $user->syndication = json_encode($data['syndicate-to']);
+ }
- $mediaEndpoint = $this->parseMediaEndpoint($body);
- $request->session()->put('media-endpoint', $mediaEndpoint);
+ if (array_key_exists('media-endpoint', $data)) {
+ $user->mediaEndpoint = $data['media-endpoint'];
+ }
+ $user->save();
return back();
}
}
/**
- * This method performs the actual POST request.
+ * Update the syntax setting.
*
- * @param \Illuminate\Http\Request $request
- * @param string The Micropub endpoint to post to
- * @param string The token to authenticate the request with
- * @return \GuzzleHttp\Response $response | \Illuminate\RedirectFactory redirect
+ * @param Illuminate\Http\Request $request
+ * @return Illuminate\Http\RedirectResponse
+ * @todo validate input
*/
- private function postNoteRequest(
- Request $request,
- $micropubEndpoint,
- $token
- ) {
- $multipart = [
- [
- 'name' => 'h',
- 'contents' => 'entry',
- ],
- [
- 'name' => 'content',
- 'contents' => $request->input('content'),
- ],
- ];
- if ($request->hasFile('photo')) {
- $photos = $request->file('photo');
- foreach ($photos as $photo) {
- $multipart[] = [
- 'name' => 'photo[]',
- 'contents' => fopen($photo->path(), 'r'),
- 'filename' => $photo->getClientOriginalName(),
- ];
- }
- }
- if ($request->input('in-reply-to') != '') {
- $multipart[] = [
- 'name' => 'in-reply-to',
- 'contents' => $request->input('in-reply-to'),
- ];
- }
- if ($request->input('mp-syndicate-to')) {
- foreach ($request->input('mp-syndicate-to') as $syn) {
- $multipart[] = [
- 'name' => 'mp-syndicate-to[]',
- 'contents' => $syn,
- ];
- }
- }
- if ($request->input('location')) {
- if ($request->input('location') !== 'no-location') {
- $multipart[] = [
- 'name' => 'location',
- 'contents' => $request->input('location'),
- ];
- }
- }
- if ($request->input('media')) {
- foreach ($request->input('media') as $media) {
- $multipart[] = [
- 'name' => 'photo[]',
- 'contents' => $media,
- ];
- }
- }
- $headers = [
- 'Authorization' => 'Bearer ' . $token,
- ];
- try {
- $response = $this->guzzleClient->post($micropubEndpoint, [
- 'multipart' => $multipart,
- 'headers' => $headers,
- ]);
- } catch (\GuzzleHttp\Exception\BadResponseException $e) {
- return redirect(route('micropub-client'))->with(
- 'error',
- 'There was a bad response from the micropub endpoint.'
- );
- }
+ public function updateSyntax(Request $request)
+ {
+ $user = IndieWebUser::where('me', $request->session()->get('me'))->firstOrFail();
+ $user->syntax = $request->syntax;
+ $user->save();
- return $response;
+ return redirect(route('micropub-config'));
}
/**
@@ -272,16 +394,17 @@ class MicropubClientController extends Controller
*/
public function newPlace(Request $request)
{
- if ($request->session()->has('token') === false) {
+ $url = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
+
+ if ($user->token === null) {
return response()->json([
'error' => true,
'error_description' => 'No known token',
], 400);
}
- $domain = $request->session()->get('me');
- $token = $request->session()->get('token');
- $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+ $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if (! $micropubEndpoint) {
return response()->json([
'error' => true,
@@ -289,13 +412,27 @@ class MicropubClientController extends Controller
], 400);
}
- $place = $this->postPlaceRequest($request, $micropubEndpoint, $token);
- if ($place === false) {
+ $formParams = [
+ 'h' => 'card',
+ 'name' => $request->input('place-name'),
+ 'description' => $request->input('place-description'),
+ 'geo' => 'geo:' . $request->input('place-latitude') . ',' . $request->input('place-longitude'),
+ ];
+ $headers = [
+ 'Authorization' => 'Bearer ' . $user->token,
+ ];
+ try {
+ $response = $this->guzzleClient->request('POST', $micropubEndpoint, [
+ 'form_params' => $formParams,
+ 'headers' => $headers,
+ ]);
+ } catch (ClientException $e) {
return response()->json([
'error' => true,
'error_description' => 'Unable to create the new place',
], 400);
}
+ $place = $response->getHeader('Location')[0];
return response()->json([
'uri' => $place,
@@ -305,44 +442,6 @@ class MicropubClientController extends Controller
]);
}
- /**
- * Actually make a micropub request to make a new place.
- *
- * @param \Illuminate\Http\Request $request
- * @param string The Micropub endpoint to post to
- * @param string The token to authenticate the request with
- * @param \GuzzleHttp\Client $client
- * @return \GuzzleHttp\Response $response | \Illuminate\RedirectFactory redirect
- */
- private function postPlaceRequest(
- Request $request,
- $micropubEndpoint,
- $token
- ) {
- $formParams = [
- 'h' => 'card',
- 'name' => $request->input('place-name'),
- 'description' => $request->input('place-description'),
- 'geo' => 'geo:' . $request->input('place-latitude') . ',' . $request->input('place-longitude'),
- ];
- $headers = [
- 'Authorization' => 'Bearer ' . $token,
- ];
- try {
- $response = $this->guzzleClient->request('POST', $micropubEndpoint, [
- 'form_params' => $formParams,
- 'headers' => $headers,
- ]);
- } catch (ClientException $e) {
- return false;
- }
- if ($response->getStatusCode() == 201) {
- return $response->getHeader('Location')[0];
- }
-
- return false;
- }
-
/**
* Make a request to the micropub endpoint requesting any nearby places.
*
@@ -351,16 +450,17 @@ class MicropubClientController extends Controller
*/
public function nearbyPlaces(Request $request)
{
- if ($request->session()->has('token') === false) {
+ $url = normalize_url($request->session()->get('me'));
+ $user = IndieWebUser::where('me', $url)->firstOrFail();
+
+ if ($user->token === null) {
return response()->json([
'error' => true,
'error_description' => 'No known token',
], 400);
}
- $domain = $request->session()->get('me');
- $token = $request->session()->get('token');
- $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+ $micropubEndpoint = $this->indieClient->discoverMicropubEndpoint($url);
if (! $micropubEndpoint) {
return response()->json([
@@ -375,7 +475,7 @@ class MicropubClientController extends Controller
$query .= ';u=' . $request->input('u');
}
$response = $this->guzzleClient->get($micropubEndpoint, [
- 'headers' => ['Authorization' => 'Bearer ' . $token],
+ 'headers' => ['Authorization' => 'Bearer ' . $user->token],
'query' => ['q' => $query],
]);
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
@@ -390,30 +490,34 @@ class MicropubClientController extends Controller
}
/**
- * Parse the syndication targets retreived from a cookie, to a form that can
- * be used in a view.
+ * Parse the syndication targets JSON into a an array.
*
- * @param string $syndicationTargets
+ * @param string|null
* @return array|null
*/
private function parseSyndicationTargets($syndicationTargets = null)
{
- if ($syndicationTargets === null) {
+ if ($syndicationTargets === null || $syndicationTargets === '') {
return;
}
$syndicateTo = [];
$data = json_decode($syndicationTargets, true);
- if (array_key_exists('syndicate-to', $data)) {
- foreach ($data['syndicate-to'] as $syn) {
+ if (array_key_exists('uid', $data)) {
+ $syndicateTo[] = [
+ 'target' => $data['uid'],
+ 'name' => $data['name'],
+ ];
+ }
+ foreach ($data as $syn) {
+ if (array_key_exists('uid', $syn)) {
$syndicateTo[] = [
'target' => $syn['uid'],
'name' => $syn['name'],
];
}
}
- if (count($syndicateTo) > 0) {
- return $syndicateTo;
- }
+
+ return $syndicateTo;
}
/**
diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php
index e414da3d..2aba67cc 100644
--- a/app/Http/Controllers/MicropubController.php
+++ b/app/Http/Controllers/MicropubController.php
@@ -3,8 +3,9 @@
namespace App\Http\Controllers;
use Ramsey\Uuid\Uuid;
-use App\{Media, Place};
+use App\{Media, Note, Place};
use Illuminate\Http\{Request, Response};
+use App\Exceptions\InvalidTokenException;
use Ramsey\Uuid\Exception\UnsatisfiedDependencyException;
use App\Services\{NoteService, PlaceService, TokenService};
@@ -47,34 +48,67 @@ class MicropubController extends Controller
*/
public function post(Request $request)
{
- $httpAuth = $request->header('Authorization');
- if (preg_match('/Bearer (.+)/', $httpAuth, $match)) {
- $token = $match[1];
- $tokenData = $this->tokenService->validateToken($token);
- if ($tokenData->hasClaim('scope')) {
- $scopes = explode(' ', $tokenData->getClaim('scope'));
- if (array_search('post', $scopes) !== false) {
- $clientId = $tokenData->getClaim('client_id');
- if (($request->input('h') == 'entry') || ($request->input('type')[0] == 'h-entry')) {
- $data = [];
- $data['client-id'] = $clientId;
- if ($request->header('Content-Type') == 'application/json') {
- $data['content'] = $request->input('properties.content')[0];
- $data['in-reply-to'] = $request->input('properties.in-reply-to')[0];
- $data['location'] = $request->input('properties.location');
- //flatten location if array
- if (is_array($data['location'])) {
- $data['location'] = $data['location'][0];
- }
- } else {
- $data['content'] = $request->input('content');
- $data['in-reply-to'] = $request->input('in-reply-to');
- $data['location'] = $request->input('location');
+ try {
+ $tokenData = $this->tokenService->validateToken($request->bearerToken());
+ } catch (InvalidTokenException $e) {
+ return response()->json([
+ 'response' => 'error',
+ 'error' => 'invalid_token',
+ 'error_description' => 'The provided token did not pass validation',
+ ], 400);
+ }
+ 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();
+ }
+ $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];
+ $data['location'] = $request->input('properties.location');
+ //flatten location if array
+ if (is_array($data['location'])) {
+ $data['location'] = $data['location'][0];
+ }
+ $data['published'] = $request->input('properties.published')[0];
+ //create checkin place
+ if (array_key_exists('checkin', $request->input('properties'))) {
+ $data['checkin'] = $request->input('properties.checkin.0.properties.url.0');
+ try {
+ $this->placeService->createPlaceFromCheckin($request->input('properties.checkin.0'));
+ } catch (\Exception $e) {
+ $data['checkin'] = null;
}
- $data['syndicate'] = [];
- $targets = array_pluck(config('syndication.targets'), 'uid', 'service.name');
- if (is_string($request->input('mp-syndicate-to'))) {
- $service = array_search($request->input('mp-syndicate-to'));
+ }
+ } 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');
+ if (is_string($request->input('mp-syndicate-to'))) {
+ $service = array_search($request->input('mp-syndicate-to'));
+ if ($service == 'Twitter') {
+ $data['syndicate'][] = 'twitter';
+ }
+ if ($service == 'Facebook') {
+ $data['syndicate'][] = 'facebook';
+ }
+ }
+ if (is_array($request->input('mp-syndicate-to'))) {
+ foreach ($targets as $service => $target) {
+ if (in_array($target, $request->input('mp-syndicate-to'))) {
if ($service == 'Twitter') {
$data['syndicate'][] = 'twitter';
}
@@ -82,83 +116,145 @@ class MicropubController extends Controller
$data['syndicate'][] = 'facebook';
}
}
- if (is_array($request->input('mp-syndicate-to'))) {
- foreach ($targets as $service => $target) {
- if (in_array($target, $request->input('mp-syndicate-to'))) {
- if ($service == 'Twitter') {
- $data['syndicate'][] = 'twitter';
- }
- if ($service == 'Facebook') {
- $data['syndicate'][] = 'facebook';
- }
- }
- }
- }
- $data['photo'] = [];
- if (is_array($request->input('photo'))) {
- foreach ($request->input('photo') as $photo) {
- if (is_string($photo)) {
- //only supporting media URLs for now
- $data['photo'][] = $photo;
- }
- }
- }
- try {
- $note = $this->noteService->createNote($data);
- } catch (Exception $exception) {
- return response()->json(['error' => true], 400);
- }
-
- return response()->json([
- 'response' => 'created',
- 'location' => $note->longurl,
- ], 201)->header('Location', $note->longurl);
}
- if ($request->input('h') == 'card' || $request->input('type')[0] == 'h-card') {
- $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');
+ }
+ $data['photo'] = [];
+ if (is_array($request->input('photo'))) {
+ foreach ($request->input('photo') as $photo) {
+ if (is_string($photo)) {
+ //only supporting media URLs for now
+ $data['photo'][] = $photo;
+ }
+ }
+ }
+ try {
+ $note = $this->noteService->createNote($data);
+ } catch (\Exception $exception) {
+ return response()->json(['error' => true], 400);
+ }
+
+ return response()->json([
+ 'response' => 'created',
+ 'location' => $note->longurl,
+ ], 201)->header('Location', $note->longurl);
+ }
+ if ($request->input('h') == 'card' || $request->input('type')[0] == 'h-card') {
+ if (stristr($tokenData->getClaim('scope'), 'create') === false) {
+ return $this->returnInsufficientScopeResponse();
+ }
+ $data = [];
+ if ($request->header('Content-Type') == 'application/json') {
+ $data['name'] = $request->input('properties.name');
+ $data['description'] = $request->input('properties.description') ?? null;
+ if ($request->has('properties.geo')) {
+ $data['geo'] = $request->input('properties.geo');
+ }
+ } else {
+ $data['name'] = $request->input('name');
+ $data['description'] = $request->input('description');
+ if ($request->has('geo')) {
+ $data['geo'] = $request->input('geo');
+ }
+ if ($request->has('latitude')) {
+ $data['latitude'] = $request->input('latitude');
+ $data['longitude'] = $request->input('longitude');
+ }
+ }
+ try {
+ $place = $this->placeService->createPlace($data);
+ } catch (\Exception $exception) {
+ return response()->json(['error' => true], 400);
+ }
+
+ return response()->json([
+ 'response' => 'created',
+ 'location' => $place->longurl,
+ ], 201)->header('Location', $place->longurl);
+ }
+ if ($request->input('action') == 'update') {
+ if (stristr($tokenData->getClaim('scope'), 'update') === false) {
+ return $this->returnInsufficientScopeResponse();
+ }
+ $urlPath = parse_url($request->input('url'), PHP_URL_PATH);
+ //is it a note we are updating?
+ if (mb_substr($urlPath, 1, 5) === 'notes') {
+ try {
+ $note = Note::nb60(basename($urlPath))->first();
+ } catch (\Exception $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];
}
- } 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');
+ 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));
+ }
+ }
}
}
- try {
- $place = $this->placeService->createPlace($data);
- } catch (Exception $exception) {
- return response()->json(['error' => true], 400);
- }
+ $note->save();
return response()->json([
- 'response' => 'created',
- 'location' => $place->longurl,
- ], 201)->header('Location', $place->longurl);
+ 'response' => 'updated',
+ ]);
+ }
+ //how about “add”
+ if ($request->has('add')) {
+ foreach ($request->input('add') as $property => $value) {
+ if ($property == 'syndication') {
+ foreach ($value as $syndicationURL) {
+ if (starts_with($syndicationURL, 'https://www.facebook.com')) {
+ $note->facebook_url = $syndicationURL;
+ }
+ if (starts_with($syndicationURL, 'https://www.swarmapp.com')) {
+ $note->swarm_url = $syndicationURL;
+ }
+ if (starts_with($syndicationURL, 'https://twitter.com')) {
+ $note->tweet_id = basename(parse_url($syndicationURL, PHP_URL_PATH));
+ }
+ }
+ }
+ if ($property == 'photo') {
+ foreach ($value as $photoURL) {
+ if (start_with($photo, 'https://')) {
+ $media = new Media();
+ $media->path = $photoURL;
+ $media->type = 'image';
+ $media->save();
+ $note->media()->save($media);
+ }
+ }
+ }
+ }
+ $note->save();
+
+ return response()->json([
+ 'response' => 'updated',
+ ]);
}
}
}
-
- return response()->json([
- 'response' => 'error',
- 'error' => 'invalid_token',
- 'error_description' => 'The token provided is not valid or does not have the necessary scope',
- ], 400);
}
return response()->json([
'response' => 'error',
- 'error' => 'no_token',
- 'error_description' => 'No OAuth token sent with request',
- ], 400);
+ 'error' => 'forbidden',
+ 'error_description' => 'The token has no scopes',
+ ], 403);
}
/**
@@ -172,68 +268,58 @@ class MicropubController extends Controller
*/
public function get(Request $request)
{
- $httpAuth = $request->header('Authorization');
- if (preg_match('/Bearer (.+)/', $httpAuth, $match)) {
- $token = $match[1];
- $valid = $this->tokenService->validateToken($token);
-
- if ($valid === null) {
- return response()->json([
- 'response' => 'error',
- 'error' => 'invalid_token',
- 'error_description' => 'The provided token did not pass validation',
- ], 400);
- }
- //we have a valid token, is `syndicate-to` set?
- if ($request->input('q') === 'syndicate-to') {
- return response()->json([
- 'syndicate-to' => config('syndication.targets'),
- ]);
- }
-
- //nope, how about a config query?
- if ($request->input('q') == 'config') {
- return response()->json([
- 'syndicate-to' => config('syndication.targets'),
- 'media-endpoint' => route('media-endpoint'),
- ]);
- }
-
- //nope, how about a geo URL?
- if (substr($request->input('q'), 0, 4) === 'geo:') {
- preg_match_all(
- '/([0-9\.\-]+)/',
- $request->input('q'),
- $matches
- );
- $distance = (count($matches[0]) == 3) ? 100 * $matches[0][2] : 1000;
- $places = Place::near($matches[0][0], $matches[0][1], $distance);
- foreach ($places as $place) {
- $place->uri = config('app.url') . '/places/' . $place->slug;
- }
-
- return response()->json([
- 'response' => 'places',
- 'places' => $places,
- ]);
- }
-
- //nope, just return the token
+ try {
+ $tokenData = $this->tokenService->validateToken($request->bearerToken());
+ } catch (InvalidTokenException $e) {
return response()->json([
- 'response' => 'token',
- 'token' => [
- 'me' => $valid->getClaim('me'),
- 'scope' => $valid->getClaim('scope'),
- 'client_id' => $valid->getClaim('client_id'),
- ],
+ 'response' => 'error',
+ 'error' => 'invalid_token',
+ 'error_description' => 'The provided token did not pass validation',
+ ], 400);
+ }
+ //we have a valid token, is `syndicate-to` set?
+ if ($request->input('q') === 'syndicate-to') {
+ return response()->json([
+ 'syndicate-to' => config('syndication.targets'),
]);
}
+ //nope, how about a config query?
+ if ($request->input('q') == 'config') {
+ return response()->json([
+ 'syndicate-to' => config('syndication.targets'),
+ 'media-endpoint' => route('media-endpoint'),
+ ]);
+ }
+
+ //nope, how about a geo URL?
+ if (substr($request->input('q'), 0, 4) === 'geo:') {
+ preg_match_all(
+ '/([0-9\.\-]+)/',
+ $request->input('q'),
+ $matches
+ );
+ $distance = (count($matches[0]) == 3) ? 100 * $matches[0][2] : 1000;
+ $places = Place::near($matches[0][0], $matches[0][1], $distance);
+ foreach ($places as $place) {
+ $place->uri = config('app.url') . '/places/' . $place->slug;
+ }
+
+ return response()->json([
+ 'response' => 'places',
+ 'places' => $places,
+ ]);
+ }
+
+ //nope, just return the token
return response()->json([
- 'response' => 'error',
- 'error' => 'no_token',
- 'error_description' => 'No token provided with request',
- ], 400);
+ 'response' => 'token',
+ 'token' => [
+ 'me' => $tokenData->getClaim('me'),
+ 'scope' => $tokenData->getClaim('scope'),
+ 'client_id' => $tokenData->getClaim('client_id'),
+ ],
+ ]);
}
/**
@@ -244,82 +330,65 @@ class MicropubController extends Controller
*/
public function media(Request $request)
{
- //can this go in middleware
- $httpAuth = $request->header('Authorization');
- if (preg_match('/Bearer (.+)/', $httpAuth, $match)) {
- $token = $match[1];
- $tokenData = $this->tokenService->validateToken($token);
+ try {
+ $tokenData = $this->tokenService->validateToken($request->bearerToken());
+ } catch (InvalidTokenException $e) {
+ return response()->json([
+ 'response' => 'error',
+ 'error' => 'invalid_token',
+ 'error_description' => 'The provided token did not pass validation',
+ ], 400);
+ }
- if ($tokenData === null) {
- return response()->json([
- 'response' => 'error',
- 'error' => 'invalid_token',
- 'error_description' => 'The provided token did not pass validation',
- ], 400);
+ //check post scope
+ if ($tokenData->hasClaim('scope')) {
+ if (stristr($token->getClaim('scope'), 'post') === false) {
+ return $this->returnInsufficientScopeResponse();
}
-
- //check post scope
- if ($tokenData->hasClaim('scope')) {
- $scopes = explode(' ', $tokenData->getClaim('scope'));
- if (array_search('post', $scopes) !== false) {
- //check media valid
- if ($request->hasFile('file') && $request->file('file')->isValid()) {
- $type = $this->getFileTypeFromMimeType($request->file('file')->getMimeType());
- 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);
- }
- try {
- $path = $request->file('file')->storeAs('media', $filename, 's3');
- } catch (Exception $e) { // which exception?
- return response()->json([
- 'response' => 'error',
- 'error' => 'service_unavailable',
- 'error_description' => 'Unable to save media to S3',
- ], 503);
- }
- $media = new Media();
- $media->token = $token;
- $media->path = $path;
- $media->type = $type;
- $media->save();
-
- return response()->json([
- 'response' => 'created',
- 'location' => $media->url,
- ], 201)->header('Location', $media->url);
- }
-
+ //check media valid
+ if ($request->hasFile('file') && $request->file('file')->isValid()) {
+ $type = $this->getFileTypeFromMimeType($request->file('file')->getMimeType());
+ try {
+ $filename = Uuid::uuid4() . '.' . $request->file('file')->extension();
+ } catch (UnsatisfiedDependencyException $e) {
return response()->json([
'response' => 'error',
- 'error' => 'invalid_request',
- 'error_description' => 'The uploaded file failed validation',
- ], 400);
+ 'error' => 'internal_server_error',
+ 'error_description' => 'A problem occured handling your request',
+ ], 500);
}
+ try {
+ $path = $request->file('file')->storeAs('media', $filename, 's3');
+ } catch (Exception $e) { // which exception?
+ return response()->json([
+ 'response' => 'error',
+ 'error' => 'service_unavailable',
+ 'error_description' => 'Unable to save media to S3',
+ ], 503);
+ }
+ $media = new Media();
+ $media->token = $request->bearerToken();
+ $media->path = $path;
+ $media->type = $type;
+ $media->save();
return response()->json([
- 'response' => 'error',
- 'error' => 'insufficient_scope',
- 'error_description' => 'The provided token has insufficient scopes',
- ], 401);
+ 'response' => 'created',
+ 'location' => $media->url,
+ ], 201)->header('Location', $media->url);
}
return response()->json([
'response' => 'error',
- 'error' => 'unauthorized',
- 'error_description' => 'No token provided with request',
- ], 401);
+ 'error' => 'invalid_request',
+ 'error_description' => 'The uploaded file failed validation',
+ ], 400);
}
return response()->json([
'response' => 'error',
- 'error' => 'no_token',
- 'error_description' => 'There was no token provided with the request',
+ 'error' => 'invalid_request',
+ 'error_description' => 'The provided token has no scopes',
], 400);
}
@@ -366,4 +435,13 @@ class MicropubController extends Controller
return 'download';
}
+
+ private function returnInsufficientScopeResponse()
+ {
+ return response()->json([
+ 'response' => 'error',
+ 'error' => 'insufficient_scope',
+ 'error_description' => 'The token’s scope does not have the necessary requirements.',
+ ], 401);
+ }
}
diff --git a/app/Http/Controllers/TokenEndpointController.php b/app/Http/Controllers/TokenEndpointController.php
new file mode 100644
index 00000000..fb0da5ac
--- /dev/null
+++ b/app/Http/Controllers/TokenEndpointController.php
@@ -0,0 +1,79 @@
+client = $client ?? new Client();
+ $this->tokenService = $tokenService ?? new TokenService();
+ }
+
+ /**
+ * If the user has auth’d via the IndieAuth protocol, issue a valid token.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\Response
+ */
+ public function create(Request $request)
+ {
+ $authorizationEndpoint = $this->client->discoverAuthorizationEndpoint(normalize_url($request->input('me')));
+ if ($authorizationEndpoint) {
+ $auth = $this->client->verifyIndieAuthCode(
+ $authorizationEndpoint,
+ $request->input('code'),
+ $request->input('me'),
+ $request->input('redirect_uri'),
+ $request->input('client_id'),
+ $request->input('state')
+ );
+ if (array_key_exists('me', $auth)) {
+ $scope = $auth['scope'] ?? '';
+ $tokenData = [
+ 'me' => $request->input('me'),
+ 'client_id' => $request->input('client_id'),
+ 'scope' => $scope,
+ ];
+ $token = $this->tokenService->getNewToken($tokenData);
+ $content = http_build_query([
+ 'me' => $request->input('me'),
+ 'scope' => $scope,
+ 'access_token' => $token,
+ ]);
+
+ return response($content)->header(
+ 'Content-Type',
+ 'application/x-www-form-urlencoded'
+ );
+ }
+
+ return response('There was an error verifying the authorisation code.', 400);
+ }
+
+ return response('Can’t determine the authorisation endpoint.', 400);
+ }
+}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index 45473220..1fad4108 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -34,7 +34,8 @@ 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\DevTokenMiddleware::class,
+ \App\Http\Middleware\LocalhostSessionMiddleware::class,
],
'api' => [
@@ -58,5 +59,6 @@ class Kernel extends HttpKernel
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'myauth' => \App\Http\Middleware\MyAuthMiddleware::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+ 'micropub.token' => \App\Http\Middleware\VerifyMicropubToken::class,
];
}
diff --git a/app/Http/Middleware/LocalhostSessionMiddleware.php b/app/Http/Middleware/LocalhostSessionMiddleware.php
new file mode 100644
index 00000000..ded4f25a
--- /dev/null
+++ b/app/Http/Middleware/LocalhostSessionMiddleware.php
@@ -0,0 +1,26 @@
+ config('app.url')]` as I can’t manually log in as
+ * a .localhost domain.
+ *
+ * @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')]);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/VerifyMicropubToken.php b/app/Http/Middleware/VerifyMicropubToken.php
new file mode 100644
index 00000000..93e2edf6
--- /dev/null
+++ b/app/Http/Middleware/VerifyMicropubToken.php
@@ -0,0 +1,28 @@
+bearerToken() === null) {
+ return response()->json([
+ 'response' => 'error',
+ 'error' => 'unauthorized',
+ 'error_description' => 'No access token was provided in the request',
+ ], 401);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/IndieWebUser.php b/app/IndieWebUser.php
new file mode 100644
index 00000000..d35fa1b5
--- /dev/null
+++ b/app/IndieWebUser.php
@@ -0,0 +1,15 @@
+path, 'https://')) {
+ return $this->path;
+ }
+
return config('filesystems.disks.s3.url') . '/' . $this->path;
}
}
diff --git a/app/Note.php b/app/Note.php
index 6a51a896..000ccd25 100644
--- a/app/Note.php
+++ b/app/Note.php
@@ -191,6 +191,20 @@ class Note extends Model
return $name;
}
+ /**
+ * Scope a query to select a note via a NewBase60 id.
+ *
+ * @param \Illuminate\Database\Eloquent\Builder $query
+ * @param string $nb60id
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeNb60($query, $nb60id)
+ {
+ $numbers = new Numbers();
+
+ return $query->where('id', $numbers->b60tonum($nb60id));
+ }
+
/**
* Take note that this method does two things, given @username (NOT [@username](URL)!)
* we try to create a fancy hcard from our contact info. If this is not possible
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index d3aa0488..88d47aec 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -33,7 +33,7 @@ class AppServiceProvider extends ServiceProvider
//Add tags for notes
Note::created(function ($note) {
$tagsToAdd = [];
- preg_match_all('/#([^\s<>]+)\b/', $note, $tags);
+ preg_match_all('/#([^\s<>]+)\b/', $note->note, $tags);
foreach ($tags[1] as $tag) {
$tag = Tag::normalizeTag($tag);
}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index ed0e3e5b..9b28f3e3 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -13,7 +13,7 @@ class EventServiceProvider extends ServiceProvider
* @var array
*/
protected $listen = [
- 'App\Events\SomeEvent' => [
+ 'App\Events\Event' => [
'App\Listeners\EventListener',
],
];
diff --git a/app/Services/IndieAuthService.php b/app/Services/IndieAuthService.php
index 29b3b65c..2ea3476f 100644
--- a/app/Services/IndieAuthService.php
+++ b/app/Services/IndieAuthService.php
@@ -45,14 +45,12 @@ class IndieAuthService
session(['state' => $state]);
$redirectURL = route('indieauth-callback');
$clientId = route('micropub-client');
- $scope = 'post';
$authorizationURL = $this->client->buildAuthorizationURL(
$authEndpoint,
$this->client->normalizeMeURL($domain),
$redirectURL,
$clientId,
- $state,
- $scope
+ $state
);
return $authorizationURL;
diff --git a/app/Services/NoteService.php b/app/Services/NoteService.php
index d395327b..09dbb1c9 100644
--- a/app/Services/NoteService.php
+++ b/app/Services/NoteService.php
@@ -17,6 +17,16 @@ class NoteService
*/
public function createNote(array $data): Note
{
+ //check the input
+ if (array_key_exists('content', $data) === false) {
+ throw new \Exception('No content defined'); //we can’t fudge the data
+ }
+ 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'],
@@ -25,6 +35,11 @@ class NoteService
]
);
+ if (array_key_exists('published', $data) && empty($data['published']) === false) {
+ $carbon = new \Carbon\Carbon($data['published']);
+ $note->created_at = $note->updated_at = $carbon->toDateTimeString();
+ }
+
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
@@ -44,6 +59,13 @@ class NoteService
}
}
+ if (array_key_exists('checkin', $data) && $data['checkin'] !== null) {
+ $place = Place::where('foursquare', $data['checkin'])->first();
+ if ($place !== null) {
+ $note->place()->associate($place);
+ }
+ }
+
/* drop image support for now
//add images to media library
if ($request->hasFile('photo')) {
@@ -55,12 +77,17 @@ class NoteService
*/
//add support for media uploaded as URLs
foreach ($data['photo'] as $photo) {
- // check the media was uploaded to my endpoint
+ // 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();
- $note->media()->save($media);
+ } else {
+ $media = Media::firstOrNew(['path' => $photo]);
+ // currently assuming this is a photo from Swarm
+ $media->type = 'image';
+ $media->save();
}
+ $note->media()->save($media);
}
$note->save();
diff --git a/app/Services/PlaceService.php b/app/Services/PlaceService.php
index 1a02dff5..f1bc249a 100644
--- a/app/Services/PlaceService.php
+++ b/app/Services/PlaceService.php
@@ -36,4 +36,39 @@ class PlaceService
return $place;
}
+
+ /**
+ * Create a place from a h-card checkin, for exameple from OwnYourSwarm.
+ *
+ * @param array
+ * @return bool
+ */
+ public function createPlaceFromCheckin(array $checkin): bool
+ {
+ //check if the place exists if from swarm
+ if (array_key_exists('url', $checkin['properties'])) {
+ $search = Place::where('foursquare', $checkin['properties']['url'][0])->count();
+ if ($search === 1) {
+ return true;
+ }
+ }
+ if (array_key_exists('name', $checkin['properties']) === false) {
+ throw new \InvalidArgumentException('Missing required name');
+ }
+ if (array_key_exists('latitude', $checkin['properties']) === false) {
+ throw new \InvalidArgumentException('Missing required longitude/latitude');
+ }
+ $place = new Place();
+ $place->name = $checkin['properties']['name'][0];
+ if (starts_with($checkin['properties']['url'][0], 'https://foursquare.com')) {
+ $place->foursquare = $checkin['properties']['url'][0];
+ }
+ $place->location = new Point(
+ (float) $checkin['properties']['latitude'][0],
+ (float) $checkin['properties']['longitude'][0]
+ );
+ $place->save();
+
+ return true;
+ }
}
diff --git a/app/Services/TokenService.php b/app/Services/TokenService.php
index fd4cd1cb..42f2f718 100644
--- a/app/Services/TokenService.php
+++ b/app/Services/TokenService.php
@@ -4,12 +4,9 @@ declare(strict_types=1);
namespace App\Services;
-use RuntimeException;
-use Lcobucci\JWT\Token;
-use Lcobucci\JWT\Parser;
-use Lcobucci\JWT\Builder;
-use InvalidArgumentException;
use Lcobucci\JWT\Signer\Hmac\Sha256;
+use App\Exceptions\InvalidTokenException;
+use Lcobucci\JWT\{Builder, Parser, Token};
class TokenService
{
@@ -39,17 +36,18 @@ class TokenService
* @param string The token
* @return mixed
*/
- public function validateToken(string $token): ?Token
+ public function validateToken(string $bearerToken): ?Token
{
$signer = new Sha256();
try {
- $token = (new Parser())->parse((string) $token);
- } catch (InvalidArgumentException | RuntimeException $e) {
- return null;
+ $token = (new Parser())->parse((string) $bearerToken);
+ } catch (\InvalidArgumentException $e) {
+ throw new InvalidTokenException('Token could not be parsed');
}
- if ($token->verify($signer, config('app.key'))) {
- //signuture valid
- return $token;
+ if (! $token->verify($signer, config('app.key'))) {
+ throw new InvalidTokenException('Token failed verification');
}
+
+ return $token;
}
}
diff --git a/changelog.md b/changelog.md
index d38e0a76..780a538d 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,10 @@
# Changelog
+## Version 0.5 (2017-06-18)
+ - Update micropub client to allow indieweb users
+ - Update micropub endpoint to allow for entry updates
+ - Add support for checkins, so we can use ownyourswarm
+
## Version 0.4.2 (2017-03-24)
- fixed issue#47, only the slug was being sent by client, which was messing up endpoint code
- minor changes to es6 code, bet lint-staged working again
diff --git a/composer.json b/composer.json
index be04049c..d1ff9e16 100644
--- a/composer.json
+++ b/composer.json
@@ -42,7 +42,10 @@
],
"psr-4": {
"App\\": "app/"
- }
+ },
+ "files": [
+ "helpers.php"
+ ]
},
"autoload-dev": {
"psr-4": {
@@ -67,6 +70,7 @@
},
"config": {
"preferred-install": "dist",
- "sort-packages": true
+ "sort-packages": true,
+ "optimize-autoloader": true
}
}
diff --git a/composer.lock b/composer.lock
index 84679550..02b615a3 100644
--- a/composer.lock
+++ b/composer.lock
@@ -8,16 +8,16 @@
"packages": [
{
"name": "aws/aws-sdk-php",
- "version": "3.24.7",
+ "version": "3.27.2",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "f062d7ea2123fe2aefef91da855c10ef8ff3af1c"
+ "reference": "eb10e43cccf8e868f9622ab8ce2beb9fb756b5a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f062d7ea2123fe2aefef91da855c10ef8ff3af1c",
- "reference": "f062d7ea2123fe2aefef91da855c10ef8ff3af1c",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/eb10e43cccf8e868f9622ab8ce2beb9fb756b5a8",
+ "reference": "eb10e43cccf8e868f9622ab8ce2beb9fb756b5a8",
"shasum": ""
},
"require": {
@@ -39,7 +39,7 @@
"ext-simplexml": "*",
"ext-spl": "*",
"nette/neon": "^2.3",
- "phpunit/phpunit": "~4.0|~5.0",
+ "phpunit/phpunit": "^4.8.35|^5.4.0",
"psr/cache": "^1.0"
},
"suggest": {
@@ -84,7 +84,7 @@
"s3",
"sdk"
],
- "time": "2017-03-23T22:17:20+00:00"
+ "time": "2017-05-11T21:23:43+00:00"
},
{
"name": "barnabywalters/mf-cleaner",
@@ -180,6 +180,65 @@
],
"time": "2016-08-19T16:43:44+00:00"
},
+ {
+ "name": "composer/ca-bundle",
+ "version": "1.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/ca-bundle.git",
+ "reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
+ "reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12",
+ "shasum": ""
+ },
+ "require": {
+ "ext-openssl": "*",
+ "ext-pcre": "*",
+ "php": "^5.3.2 || ^7.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.5",
+ "psr/log": "^1.0",
+ "symfony/process": "^2.5 || ^3.0"
+ },
+ "suggest": {
+ "symfony/process": "This is necessary to reliably check whether openssl_x509_parse is vulnerable on older php versions, but can be ignored on PHP 5.5.6+"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\CaBundle\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
+ "keywords": [
+ "cabundle",
+ "cacert",
+ "certificate",
+ "ssl",
+ "tls"
+ ],
+ "time": "2017-03-06T11:59:08+00:00"
+ },
{
"name": "dnoegel/php-xdg-base-dir",
"version": "0.1",
@@ -685,16 +744,16 @@
},
{
"name": "erusev/parsedown",
- "version": "1.6.1",
+ "version": "1.6.2",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
- "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb"
+ "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb",
- "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb",
+ "url": "https://api.github.com/repos/erusev/parsedown/zipball/1bf24f7334fe16c88bf9d467863309ceaf285b01",
+ "reference": "1bf24f7334fe16c88bf9d467863309ceaf285b01",
"shasum": ""
},
"require": {
@@ -723,7 +782,7 @@
"markdown",
"parser"
],
- "time": "2016-11-02T15:56:58+00:00"
+ "time": "2017-03-29T16:04:15+00:00"
},
{
"name": "ezyang/htmlpurifier",
@@ -1045,16 +1104,16 @@
},
{
"name": "indieauth/client",
- "version": "0.2.0",
+ "version": "0.2.1",
"source": {
"type": "git",
"url": "https://github.com/indieweb/indieauth-client-php.git",
- "reference": "4b9bd766a92b8abbe420f5889bf7ebac7678151d"
+ "reference": "f5f6efad79334d1ff9370fe4dce8ccf4814820fa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/indieweb/indieauth-client-php/zipball/4b9bd766a92b8abbe420f5889bf7ebac7678151d",
- "reference": "4b9bd766a92b8abbe420f5889bf7ebac7678151d",
+ "url": "https://api.github.com/repos/indieweb/indieauth-client-php/zipball/f5f6efad79334d1ff9370fe4dce8ccf4814820fa",
+ "reference": "f5f6efad79334d1ff9370fe4dce8ccf4814820fa",
"shasum": ""
},
"require": {
@@ -1080,7 +1139,7 @@
}
],
"description": "IndieAuth Client Library",
- "time": "2017-02-09T23:42:05+00:00"
+ "time": "2017-04-26T21:44:35+00:00"
},
{
"name": "indieweb/link-rel-parser",
@@ -1405,16 +1464,16 @@
},
{
"name": "laravel/framework",
- "version": "v5.4.16",
+ "version": "v5.4.23",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "6cf379ec34d08bcdc9c7183e369a8fdf04ade80d"
+ "reference": "ad82327705658dbf5f0ce72805caa950dfbe150d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/6cf379ec34d08bcdc9c7183e369a8fdf04ade80d",
- "reference": "6cf379ec34d08bcdc9c7183e369a8fdf04ade80d",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/ad82327705658dbf5f0ce72805caa950dfbe150d",
+ "reference": "ad82327705658dbf5f0ce72805caa950dfbe150d",
"shasum": ""
},
"require": {
@@ -1530,20 +1589,20 @@
"framework",
"laravel"
],
- "time": "2017-03-21T19:34:41+00:00"
+ "time": "2017-05-11T20:10:35+00:00"
},
{
"name": "laravel/scout",
- "version": "v3.0.2",
+ "version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/scout.git",
- "reference": "1ddb0fa6f165bf6a69864960102062e7cf3f989d"
+ "reference": "64d28db58a054174eadf1d4df38dad81ff7e68dd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/scout/zipball/1ddb0fa6f165bf6a69864960102062e7cf3f989d",
- "reference": "1ddb0fa6f165bf6a69864960102062e7cf3f989d",
+ "url": "https://api.github.com/repos/laravel/scout/zipball/64d28db58a054174eadf1d4df38dad81ff7e68dd",
+ "reference": "64d28db58a054174eadf1d4df38dad81ff7e68dd",
"shasum": ""
},
"require": {
@@ -1590,7 +1649,7 @@
"laravel",
"search"
],
- "time": "2017-03-01T14:37:40+00:00"
+ "time": "2017-04-09T00:54:26+00:00"
},
{
"name": "laravel/tinker",
@@ -1710,16 +1769,16 @@
},
{
"name": "league/commonmark",
- "version": "0.15.3",
+ "version": "0.15.4",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
- "reference": "c8b43ee5821362216f8e9ac684f0f59de164edcc"
+ "reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c8b43ee5821362216f8e9ac684f0f59de164edcc",
- "reference": "c8b43ee5821362216f8e9ac684f0f59de164edcc",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c",
+ "reference": "c4c8e6bf99e62d9568875d9fc3ef473fe3e18e0c",
"shasum": ""
},
"require": {
@@ -1775,20 +1834,20 @@
"markdown",
"parser"
],
- "time": "2016-12-19T00:11:43+00:00"
+ "time": "2017-05-09T12:47:53+00:00"
},
{
"name": "league/flysystem",
- "version": "1.0.37",
+ "version": "1.0.40",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
- "reference": "78b5cc4feb61a882302df4fbaf63b7662e5e4ccd"
+ "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/78b5cc4feb61a882302df4fbaf63b7662e5e4ccd",
- "reference": "78b5cc4feb61a882302df4fbaf63b7662e5e4ccd",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3828f0b24e2c1918bb362d57a53205d6dc8fde61",
+ "reference": "3828f0b24e2c1918bb362d57a53205d6dc8fde61",
"shasum": ""
},
"require": {
@@ -1810,12 +1869,12 @@
"league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
"league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
"league/flysystem-copy": "Allows you to use Copy.com storage",
- "league/flysystem-dropbox": "Allows you to use Dropbox storage",
"league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
"league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
"league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
"league/flysystem-webdav": "Allows you to use WebDAV storage",
- "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter"
+ "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
+ "spatie/flysystem-dropbox": "Allows you to use Dropbox storage"
},
"type": "library",
"extra": {
@@ -1858,25 +1917,25 @@
"sftp",
"storage"
],
- "time": "2017-03-22T15:43:14+00:00"
+ "time": "2017-04-28T10:15:08+00:00"
},
{
"name": "league/flysystem-aws-s3-v3",
- "version": "1.0.13",
+ "version": "1.0.15",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
- "reference": "dc56a8faf3aff0841f9eae04b6af94a50657896c"
+ "reference": "c947f36f977b495a57e857ae1630df0da35ec456"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/dc56a8faf3aff0841f9eae04b6af94a50657896c",
- "reference": "dc56a8faf3aff0841f9eae04b6af94a50657896c",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c947f36f977b495a57e857ae1630df0da35ec456",
+ "reference": "c947f36f977b495a57e857ae1630df0da35ec456",
"shasum": ""
},
"require": {
"aws/aws-sdk-php": "^3.0.0",
- "league/flysystem": "~1.0",
+ "league/flysystem": "^1.0.40",
"php": ">=5.5.0"
},
"require-dev": {
@@ -1905,7 +1964,7 @@
}
],
"description": "Flysystem adapter for the AWS S3 SDK v3.x",
- "time": "2016-06-21T21:34:35+00:00"
+ "time": "2017-04-28T10:21:54+00:00"
},
{
"name": "martinbean/laravel-sluggable-trait",
@@ -2658,16 +2717,16 @@
},
{
"name": "ramsey/uuid",
- "version": "3.6.0",
+ "version": "3.6.1",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
- "reference": "0b7bdfb180e72c8d76e75a649ced67e392201458"
+ "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ramsey/uuid/zipball/0b7bdfb180e72c8d76e75a649ced67e392201458",
- "reference": "0b7bdfb180e72c8d76e75a649ced67e392201458",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/4ae32dd9ab8860a4bbd750ad269cba7f06f7934e",
+ "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e",
"shasum": ""
},
"require": {
@@ -2679,9 +2738,9 @@
},
"require-dev": {
"apigen/apigen": "^4.1",
- "codeception/aspect-mock": "1.0.0",
+ "codeception/aspect-mock": "^1.0 | ^2.0",
"doctrine/annotations": "~1.2.0",
- "goaop/framework": "1.0.0-alpha.2",
+ "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1",
"ircmaxell/random-lib": "^1.1",
"jakub-onderka/php-parallel-lint": "^0.9.0",
"mockery/mockery": "^0.9.4",
@@ -2736,23 +2795,24 @@
"identifier",
"uuid"
],
- "time": "2017-03-18T15:38:09+00:00"
+ "time": "2017-03-26T20:37:53+00:00"
},
{
"name": "sensiolabs/security-checker",
- "version": "v4.0.2",
+ "version": "v4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sensiolabs/security-checker.git",
- "reference": "56bded66985e22f6eac2cf86735fd21c625bff2f"
+ "reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/56bded66985e22f6eac2cf86735fd21c625bff2f",
- "reference": "56bded66985e22f6eac2cf86735fd21c625bff2f",
+ "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
+ "reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc",
"shasum": ""
},
"require": {
+ "composer/ca-bundle": "^1.0",
"symfony/console": "~2.7|~3.0"
},
"bin": [
@@ -2780,20 +2840,20 @@
}
],
"description": "A security checker for your composer.lock",
- "time": "2017-03-09T17:33:20+00:00"
+ "time": "2017-03-31T14:50:32+00:00"
},
{
"name": "swiftmailer/swiftmailer",
- "version": "v5.4.6",
+ "version": "v5.4.8",
"source": {
"type": "git",
"url": "https://github.com/swiftmailer/swiftmailer.git",
- "reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e"
+ "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e",
- "reference": "81fdccfaf8bdc5d5d7a1ef6bb3a61bbb1a6c4a3e",
+ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/9a06dc570a0367850280eefd3f1dc2da45aef517",
+ "reference": "9a06dc570a0367850280eefd3f1dc2da45aef517",
"shasum": ""
},
"require": {
@@ -2834,20 +2894,20 @@
"mail",
"mailer"
],
- "time": "2017-02-13T07:52:53+00:00"
+ "time": "2017-05-01T15:54:03+00:00"
},
{
"name": "symfony/console",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "28fb243a2b5727774ca309ec2d92da240f1af0dd"
+ "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/28fb243a2b5727774ca309ec2d92da240f1af0dd",
- "reference": "28fb243a2b5727774ca309ec2d92da240f1af0dd",
+ "url": "https://api.github.com/repos/symfony/console/zipball/a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38",
+ "reference": "a7a17e0c6c3c4d70a211f80782e4b90ddadeaa38",
"shasum": ""
},
"require": {
@@ -2897,20 +2957,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2017-03-06T19:30:27+00:00"
+ "time": "2017-04-26T01:39:17+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "a48f13dc83c168f1253a5d2a5a4fb46c36244c4c"
+ "reference": "02983c144038e697c959e6b06ef6666de759ccbc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/a48f13dc83c168f1253a5d2a5a4fb46c36244c4c",
- "reference": "a48f13dc83c168f1253a5d2a5a4fb46c36244c4c",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/02983c144038e697c959e6b06ef6666de759ccbc",
+ "reference": "02983c144038e697c959e6b06ef6666de759ccbc",
"shasum": ""
},
"require": {
@@ -2950,20 +3010,20 @@
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
- "time": "2017-02-21T09:12:04+00:00"
+ "time": "2017-05-01T14:55:58+00:00"
},
{
"name": "symfony/debug",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/debug.git",
- "reference": "b90c9f91ad8ac37d9f114e369042d3226b34dc1a"
+ "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/b90c9f91ad8ac37d9f114e369042d3226b34dc1a",
- "reference": "b90c9f91ad8ac37d9f114e369042d3226b34dc1a",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/fd6eeee656a5a7b384d56f1072243fe1c0e81686",
+ "reference": "fd6eeee656a5a7b384d56f1072243fe1c0e81686",
"shasum": ""
},
"require": {
@@ -3007,20 +3067,20 @@
],
"description": "Symfony Debug Component",
"homepage": "https://symfony.com",
- "time": "2017-02-18T17:28:00+00:00"
+ "time": "2017-04-19T20:17:50+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d"
+ "reference": "b8a401f733b43251e1d088c589368b2a94155e40"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d",
- "reference": "b7a1b9e0a0f623ce43b4c8d775eb138f190c9d8d",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b8a401f733b43251e1d088c589368b2a94155e40",
+ "reference": "b8a401f733b43251e1d088c589368b2a94155e40",
"shasum": ""
},
"require": {
@@ -3067,20 +3127,20 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2017-02-21T09:12:04+00:00"
+ "time": "2017-05-01T14:58:48+00:00"
},
{
"name": "symfony/finder",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10"
+ "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/92d7476d2df60cd851a3e13e078664b1deb8ce10",
- "reference": "92d7476d2df60cd851a3e13e078664b1deb8ce10",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/9cf076f8f492f4b1ffac40aae9c2d287b4ca6930",
+ "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930",
"shasum": ""
},
"require": {
@@ -3116,20 +3176,20 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2017-02-21T09:12:04+00:00"
+ "time": "2017-04-12T14:13:17+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "c57009887010eb4e58bfca2970314a5b820b24b9"
+ "reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c57009887010eb4e58bfca2970314a5b820b24b9",
- "reference": "c57009887010eb4e58bfca2970314a5b820b24b9",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9de6add7f731e5af7f5b2e9c0da365e43383ebef",
+ "reference": "9de6add7f731e5af7f5b2e9c0da365e43383ebef",
"shasum": ""
},
"require": {
@@ -3169,20 +3229,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2017-03-04T12:23:14+00:00"
+ "time": "2017-05-01T14:55:58+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "bc909e85b8585c9edf043d0fca871308c41bb9b4"
+ "reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/bc909e85b8585c9edf043d0fca871308c41bb9b4",
- "reference": "bc909e85b8585c9edf043d0fca871308c41bb9b4",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/46e8b209abab55c072c47d72d5cd1d62c0585e05",
+ "reference": "46e8b209abab55c072c47d72d5cd1d62c0585e05",
"shasum": ""
},
"require": {
@@ -3251,7 +3311,7 @@
],
"description": "Symfony HttpKernel Component",
"homepage": "https://symfony.com",
- "time": "2017-03-10T18:35:31+00:00"
+ "time": "2017-05-01T17:46:48+00:00"
},
{
"name": "symfony/polyfill-mbstring",
@@ -3314,16 +3374,16 @@
},
{
"name": "symfony/process",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892"
+ "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/68bfa8c83f24c0ac04ea7193bcdcda4519f41892",
- "reference": "68bfa8c83f24c0ac04ea7193bcdcda4519f41892",
+ "url": "https://api.github.com/repos/symfony/process/zipball/999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
+ "reference": "999c2cf5061e627e6cd551dc9ebf90dd1d11d9f0",
"shasum": ""
},
"require": {
@@ -3359,20 +3419,20 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2017-03-04T12:23:14+00:00"
+ "time": "2017-04-12T14:13:17+00:00"
},
{
"name": "symfony/routing",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "d6605f9a5767bc5bc4895e1c762ba93964608aee"
+ "reference": "5029745d6d463585e8b487dbc83d6333f408853a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/d6605f9a5767bc5bc4895e1c762ba93964608aee",
- "reference": "d6605f9a5767bc5bc4895e1c762ba93964608aee",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/5029745d6d463585e8b487dbc83d6333f408853a",
+ "reference": "5029745d6d463585e8b487dbc83d6333f408853a",
"shasum": ""
},
"require": {
@@ -3434,20 +3494,20 @@
"uri",
"url"
],
- "time": "2017-03-02T15:58:09+00:00"
+ "time": "2017-04-12T14:13:17+00:00"
},
{
"name": "symfony/translation",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690"
+ "reference": "f4a04d2df710f81515df576b2de06bdeee518b83"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690",
- "reference": "0e1b15ce8fbf3890f4ccdac430ed5e07fdfe0690",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/f4a04d2df710f81515df576b2de06bdeee518b83",
+ "reference": "f4a04d2df710f81515df576b2de06bdeee518b83",
"shasum": ""
},
"require": {
@@ -3498,20 +3558,20 @@
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2017-03-04T12:23:14+00:00"
+ "time": "2017-04-12T14:13:17+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "4100f347aff890bc16b0b4b42843b599db257b2d"
+ "reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/4100f347aff890bc16b0b4b42843b599db257b2d",
- "reference": "4100f347aff890bc16b0b4b42843b599db257b2d",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
+ "reference": "fa47963ac7979ddbd42b2d646d1b056bddbf7bb8",
"shasum": ""
},
"require": {
@@ -3522,9 +3582,11 @@
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
},
"require-dev": {
+ "ext-iconv": "*",
"twig/twig": "~1.20|~2.0"
},
"suggest": {
+ "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
"ext-symfony_debug": ""
},
"type": "library",
@@ -3564,7 +3626,7 @@
"debug",
"dump"
],
- "time": "2017-02-20T13:45:48+00:00"
+ "time": "2017-05-01T14:55:58+00:00"
},
{
"name": "themattharris/tmhoauth",
@@ -3610,16 +3672,16 @@
},
{
"name": "thujohn/twitter",
- "version": "2.2.2",
+ "version": "2.2.5",
"source": {
"type": "git",
"url": "https://github.com/thujohn/twitter.git",
- "reference": "203d903856212835206675ae9c0504d74b681886"
+ "reference": "ff414bdadba3f1570ca211355e5359ec266552d8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/thujohn/twitter/zipball/203d903856212835206675ae9c0504d74b681886",
- "reference": "203d903856212835206675ae9c0504d74b681886",
+ "url": "https://api.github.com/repos/thujohn/twitter/zipball/ff414bdadba3f1570ca211355e5359ec266552d8",
+ "reference": "ff414bdadba3f1570ca211355e5359ec266552d8",
"shasum": ""
},
"require": {
@@ -3650,7 +3712,7 @@
"laravel5",
"twitter"
],
- "time": "2017-01-31T23:37:52+00:00"
+ "time": "2017-04-27T09:00:04+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -3861,16 +3923,16 @@
},
{
"name": "facebook/webdriver",
- "version": "1.4.0",
+ "version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/facebook/php-webdriver.git",
- "reference": "3ea034c056189e11c0ce7985332a9f4b5b2b5db2"
+ "reference": "eadb0b7a7c3e6578185197fd40158b08c3164c83"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/3ea034c056189e11c0ce7985332a9f4b5b2b5db2",
- "reference": "3ea034c056189e11c0ce7985332a9f4b5b2b5db2",
+ "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/eadb0b7a7c3e6578185197fd40158b08c3164c83",
+ "reference": "eadb0b7a7c3e6578185197fd40158b08c3164c83",
"shasum": ""
},
"require": {
@@ -3909,7 +3971,7 @@
"selenium",
"webdriver"
],
- "time": "2017-03-22T10:56:03+00:00"
+ "time": "2017-04-28T14:54:49+00:00"
},
{
"name": "fzaninotto/faker",
@@ -4053,21 +4115,22 @@
},
{
"name": "laravel/dusk",
- "version": "v1.0.10",
+ "version": "v1.1.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/dusk.git",
- "reference": "11537ac1a939a2194e9e3cdc2536e6e34eff9ea6"
+ "reference": "6b81e97ae1ce384e3d8dbd020b2b9751c1449889"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/dusk/zipball/11537ac1a939a2194e9e3cdc2536e6e34eff9ea6",
- "reference": "11537ac1a939a2194e9e3cdc2536e6e34eff9ea6",
+ "url": "https://api.github.com/repos/laravel/dusk/zipball/6b81e97ae1ce384e3d8dbd020b2b9751c1449889",
+ "reference": "6b81e97ae1ce384e3d8dbd020b2b9751c1449889",
"shasum": ""
},
"require": {
"facebook/webdriver": "~1.0",
- "illuminate/support": "~5.3",
+ "illuminate/console": "~5.4",
+ "illuminate/support": "~5.4",
"nesbot/carbon": "~1.20",
"php": ">=5.6.4",
"symfony/console": "~3.2",
@@ -4104,7 +4167,7 @@
"testing",
"webdriver"
],
- "time": "2017-03-03T14:36:19+00:00"
+ "time": "2017-04-23T17:13:04+00:00"
},
{
"name": "maximebf/debugbar",
@@ -4172,12 +4235,12 @@
"version": "0.9.9",
"source": {
"type": "git",
- "url": "https://github.com/padraic/mockery.git",
+ "url": "https://github.com/mockery/mockery.git",
"reference": "6fdb61243844dc924071d3404bb23994ea0b6856"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/padraic/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856",
+ "url": "https://api.github.com/repos/mockery/mockery/zipball/6fdb61243844dc924071d3404bb23994ea0b6856",
"reference": "6fdb61243844dc924071d3404bb23994ea0b6856",
"shasum": ""
},
@@ -4234,16 +4297,16 @@
},
{
"name": "myclabs/deep-copy",
- "version": "1.6.0",
+ "version": "1.6.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe"
+ "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe",
- "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8e6e04167378abf1ddb4d3522d8755c5fd90d102",
+ "reference": "8e6e04167378abf1ddb4d3522d8755c5fd90d102",
"shasum": ""
},
"require": {
@@ -4272,7 +4335,7 @@
"object",
"object graph"
],
- "time": "2017-01-26T22:05:40+00:00"
+ "time": "2017-04-12T18:52:22+00:00"
},
{
"name": "phpdocumentor/reflection-common",
@@ -4485,16 +4548,16 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "4.0.7",
+ "version": "4.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "09e2277d14ea467e5a984010f501343ef29ffc69"
+ "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/09e2277d14ea467e5a984010f501343ef29ffc69",
- "reference": "09e2277d14ea467e5a984010f501343ef29ffc69",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
+ "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
"shasum": ""
},
"require": {
@@ -4544,7 +4607,7 @@
"testing",
"xunit"
],
- "time": "2017-03-01T09:12:17+00:00"
+ "time": "2017-04-02T07:44:40+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -4734,16 +4797,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "5.7.17",
+ "version": "5.7.19",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "68752b665d3875f9a38a357e3ecb35c79f8673bf"
+ "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/68752b665d3875f9a38a357e3ecb35c79f8673bf",
- "reference": "68752b665d3875f9a38a357e3ecb35c79f8673bf",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/69c4f49ff376af2692bad9cebd883d17ebaa98a1",
+ "reference": "69c4f49ff376af2692bad9cebd883d17ebaa98a1",
"shasum": ""
},
"require": {
@@ -4812,7 +4875,7 @@
"testing",
"xunit"
],
- "time": "2017-03-19T16:52:12+00:00"
+ "time": "2017-04-03T02:22:27+00:00"
},
{
"name": "phpunit/phpunit-mock-objects",
@@ -5477,16 +5540,16 @@
},
{
"name": "symfony/yaml",
- "version": "v3.2.6",
+ "version": "v3.2.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a"
+ "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/093e416ad096355149e265ea2e4cc1f9ee40ab1a",
- "reference": "093e416ad096355149e265ea2e4cc1f9ee40ab1a",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/acec26fcf7f3031e094e910b94b002fa53d4e4d6",
+ "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6",
"shasum": ""
},
"require": {
@@ -5528,20 +5591,20 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2017-03-07T16:47:02+00:00"
+ "time": "2017-05-01T14:55:58+00:00"
},
{
"name": "theseer/fdomdocument",
- "version": "1.6.1",
+ "version": "1.6.5",
"source": {
"type": "git",
"url": "https://github.com/theseer/fDOMDocument.git",
- "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684"
+ "reference": "8dcfd392135a5bd938c3c83ea71419501ad9855d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
- "reference": "d9ad139d6c2e8edf5e313ffbe37ff13344cf0684",
+ "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/8dcfd392135a5bd938c3c83ea71419501ad9855d",
+ "reference": "8dcfd392135a5bd938c3c83ea71419501ad9855d",
"shasum": ""
},
"require": {
@@ -5568,7 +5631,7 @@
],
"description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.",
"homepage": "https://github.com/theseer/fDOMDocument",
- "time": "2015-05-27T22:58:02+00:00"
+ "time": "2017-04-21T14:50:31+00:00"
},
{
"name": "webmozart/assert",
diff --git a/config/database.php b/config/database.php
index d32ac66a..cd74d4d7 100644
--- a/config/database.php
+++ b/config/database.php
@@ -46,8 +46,9 @@ return [
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
- 'charset' => 'utf8',
- 'collation' => 'utf8_unicode_ci',
+ 'unix_socket' => env('DB_SOCKET', ''),
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
diff --git a/config/view.php b/config/view.php
index e193ab61..2acfd9cc 100644
--- a/config/view.php
+++ b/config/view.php
@@ -14,7 +14,7 @@ return [
*/
'paths' => [
- realpath(base_path('resources/views')),
+ resource_path('views'),
],
/*
diff --git a/database/migrations/2017_03_28_130855_create_indie_web_users_table.php b/database/migrations/2017_03_28_130855_create_indie_web_users_table.php
new file mode 100644
index 00000000..2fec7645
--- /dev/null
+++ b/database/migrations/2017_03_28_130855_create_indie_web_users_table.php
@@ -0,0 +1,36 @@
+increments('id');
+ $table->string('me')->unique();
+ $table->text('token')->nullable();
+ $table->string('syntax')->default('json');
+ $table->jsonb('syndication')->nullable();
+ $table->string('mediaEndpoint')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('indie_web_users');
+ }
+}
diff --git a/database/migrations/2017_04_25_203734_update_notes_table_add_swarm_url.php b/database/migrations/2017_04_25_203734_update_notes_table_add_swarm_url.php
new file mode 100644
index 00000000..4a161735
--- /dev/null
+++ b/database/migrations/2017_04_25_203734_update_notes_table_add_swarm_url.php
@@ -0,0 +1,32 @@
+string('swarm_url')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('notes', function (Blueprint $table) {
+ $table->dropColumn('swarm_url');
+ });
+ }
+}
diff --git a/database/migrations/2017_05_12_135451_update_places_table_add_foursquare_column.php b/database/migrations/2017_05_12_135451_update_places_table_add_foursquare_column.php
new file mode 100644
index 00000000..0e352aff
--- /dev/null
+++ b/database/migrations/2017_05_12_135451_update_places_table_add_foursquare_column.php
@@ -0,0 +1,32 @@
+string('foursquare')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('places', function (Blueprint $table) {
+ $table->dropColumn('foursquare');
+ });
+ }
+}
diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php
index 7decf629..8e906692 100644
--- a/database/seeds/DatabaseSeeder.php
+++ b/database/seeds/DatabaseSeeder.php
@@ -17,5 +17,6 @@ class DatabaseSeeder extends Seeder
$this->call(PlacesTableSeeder::class);
$this->call(NotesTableSeeder::class);
$this->call(WebMentionsTableSeeder::class);
+ $this->call(IndieWebUserTableSeeder::class);
}
}
diff --git a/database/seeds/IndieWebUserTableSeeder.php b/database/seeds/IndieWebUserTableSeeder.php
new file mode 100644
index 00000000..1f6d11c7
--- /dev/null
+++ b/database/seeds/IndieWebUserTableSeeder.php
@@ -0,0 +1,16 @@
+ config('app.url')]);
+ }
+}
diff --git a/deploy.sh b/deploy.sh
index 0d7ca306..c16a2e4e 100755
--- a/deploy.sh
+++ b/deploy.sh
@@ -3,7 +3,8 @@
echo "Putting the Laravel app in maintenance mode"
php artisan down
-echo "Updating composer dependencies"
+echo "Updating composer and dependencies"
+composer self-update
composer install
echo "Caching Laravel route and config files"
diff --git a/helpers.php b/helpers.php
new file mode 100644
index 00000000..d70efc56
--- /dev/null
+++ b/helpers.php
@@ -0,0 +1,199 @@
+ 80, 'https' => 443];
+ if (isset($url['scheme'])) {
+ $url['scheme'] = strtolower($url['scheme']);
+ // Strip scheme default ports
+ if (isset($defaultSchemes[$url['scheme']]) &&
+ isset($url['port']) &&
+ $defaultSchemes[$url['scheme']] == $url['port']
+ ) {
+ unset($url['port']);
+ }
+ $newUrl .= "{$url['scheme']}://";
+ }
+ if (isset($url['host'])) {
+ $url['host'] = mb_strtolower($url['host']);
+ $newUrl .= $url['host'];
+ }
+ if (isset($url['port'])) {
+ $newUrl .= ":{$url['port']}";
+ }
+
+ if (isset($url['path'])) {
+ // Case normalization
+ $url['path'] = normalizer_normalize($url['path'], Normalizer::FORM_C);
+ // Strip duplicate slashes
+ while (preg_match("/\/\//", $url['path'])) {
+ $url['path'] = preg_replace('/\/\//', '/', $url['path']);
+ }
+
+ /*
+ * Decode unreserved characters, http://www.apps.ietf.org/rfc/rfc3986.html#sec-2.3
+ * Heavily rewritten version of urlDecodeUnreservedChars() in Glen Scott's url-normalizer.
+ */
+ $u = [];
+ for ($o = 65; $o <= 90; $o++) {
+ $u[] = dechex($o);
+ }
+ for ($o = 97; $o <= 122; $o++) {
+ $u[] = dechex($o);
+ }
+ for ($o = 48; $o <= 57; $o++) {
+ $u[] = dechex($o);
+ }
+ $chrs = ['-', '.', '_', '~'];
+ foreach ($chrs as $chr) {
+ $u[] = dechex(ord($chr));
+ }
+ $url['path'] = preg_replace_callback(
+ array_map(
+ create_function('$str', 'return "/%" . strtoupper($str) . "/x";'),
+ $u
+ ),
+ create_function('$matches', 'return chr(hexdec($matches[0]));'),
+ $url['path']
+ );
+ // Remove directory index
+ $defaultIndexes = ["/default\.aspx/" => 'default.aspx/', "/default\.asp/" => 'default.asp/',
+ "/index\.html/" => 'index.html/', "/index\.htm/" => 'index.htm/',
+ "/default\.html/" => 'default.html/', "/default\.htm/" => 'default.htm/',
+ "/index\.php/" => 'index.php/', "/index\.jsp/" => 'index.jsp/', ];
+ foreach ($defaultIndexes as $index => $strip) {
+ if (preg_match($index, $url['path'])) {
+ $url['path'] = str_replace($strip, '', $url['path']);
+ }
+ }
+ // here we only want to drop a slash for the root domain
+ // e.g. http://example.com/ -> http://example.com
+ // but http://example.com/path/ -/-> http://example.com/path
+ if ($url['path'] == '/') {
+ unset($url['path']);
+ }
+
+ /**
+ * Path segment normalization, http://www.apps.ietf.org/rfc/rfc3986.html#sec-5.2.4
+ * Heavily rewritten version of removeDotSegments() in Glen Scott's url-normalizer.
+ */
+ $new_path = '';
+ while (! empty($url['path'])) {
+ if (preg_match('!^(\.\./|\./)!x', $url['path'])) {
+ $url['path'] = preg_replace('!^(\.\./|\./)!x', '', $url['path']);
+ } elseif (preg_match('!^(/\./)!x', $url['path'], $matches)
+ || preg_match('!^(/\.)$!x', $url['path'], $matches)) {
+ $url['path'] = preg_replace('!^' . $matches[1] . '!', '/', $url['path']);
+ } elseif (preg_match('!^(/\.\./|/\.\.)!x', $url['path'], $matches)) {
+ $url['path'] = preg_replace('!^' . preg_quote($matches[1], '!') . '!x', '/', $url['path']);
+ $new_path = preg_replace('!/([^/]+)$!x', '', $new_path);
+ } elseif (preg_match('!^(\.|\.\.)$!x', $url['path'])) {
+ $url['path'] = preg_replace('!^(\.|\.\.)$!x', '', $url['path']);
+ } else {
+ if (preg_match('!(/*[^/]*)!x', $url['path'], $matches)) {
+ $first_path_segment = $matches[1];
+ $url['path'] = preg_replace('/^' . preg_quote($first_path_segment, '/') . '/', '', $url['path'], 1);
+ $new_path .= $first_path_segment;
+ }
+ }
+ }
+ $newUrl .= $new_path;
+ }
+
+ if (isset($url['fragment'])) {
+ unset($url['fragment']);
+ }
+
+ // Sort GET params alphabetically, not because the RFC requires it but because it's cool!
+ if (isset($url['query'])) {
+ if (preg_match('/&/', $url['query'])) {
+ $s = explode('&', $url['query']);
+ $url['query'] = '';
+ sort($s);
+ foreach ($s as $z) {
+ $url['query'] .= "{$z}&";
+ }
+ $url['query'] = preg_replace('/&\Z/', '', $url['query']);
+ }
+ $newUrl .= "?{$url['query']}";
+ }
+
+ return $newUrl;
+ }
+}
+
+// sourced from https://stackoverflow.com/a/9776726
+if (! function_exists('prettyPrintJson')) {
+ function prettyPrintJson(string $json): string
+ {
+ $result = '';
+ $level = 0;
+ $in_quotes = false;
+ $in_escape = false;
+ $ends_line_level = null;
+ $json_length = strlen($json);
+
+ for ($i = 0; $i < $json_length; $i++) {
+ $char = $json[$i];
+ $new_line_level = null;
+ $post = '';
+ if ($ends_line_level !== null) {
+ $new_line_level = $ends_line_level;
+ $ends_line_level = null;
+ }
+ if ($in_escape) {
+ $in_escape = false;
+ } elseif ($char === '"') {
+ $in_quotes = ! $in_quotes;
+ } elseif (! $in_quotes) {
+ switch ($char) {
+ case '}':
+ case ']':
+ $level--;
+ $ends_line_level = null;
+ $new_line_level = $level;
+ break;
+ case '{':
+ case '[':
+ $level++;
+ //no break
+ case ',':
+ $ends_line_level = $level;
+ break;
+ case ':':
+ $post = ' ';
+ break;
+ case ' ':
+ case "\t":
+ case "\n":
+ case "\r":
+ $char = '';
+ $ends_line_level = $new_line_level;
+ $new_line_level = null;
+ break;
+ }
+ } elseif ($char === '\\') {
+ $in_escape = true;
+ }
+ if ($new_line_level !== null) {
+ $result .= "\n".str_repeat("\t", $new_line_level);
+ }
+ $result .= $char.$post;
+ }
+
+ return str_replace("\t", ' ', $result);
+ }
+}
diff --git a/package.json b/package.json
index e071c0af..9af1ac7b 100644
--- a/package.json
+++ b/package.json
@@ -6,15 +6,15 @@
"license": "CC0-1.0",
"dependencies": {
"alertify.js": "^1.0.12",
- "mapbox-gl": "^0.34.0",
+ "mapbox-gl": "0.37.0",
"marked": "^0.3.6",
- "normalize.css": "^5.0.0",
+ "normalize.css": "7.0.0",
"webStorage": "^1.2.2"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-core": "^6.21.0",
- "babel-loader": "^6.2.10",
+ "babel-loader": "7.0.0",
"babel-preset-env": "^1.2.2",
"babel-preset-es2015": "^6.18.0",
"babel-preset-latest": "^6.16.0",
diff --git a/public/assets/js/maps.js.br b/public/assets/js/maps.js.br
index e33fccb9..322ca078 100644
Binary files a/public/assets/js/maps.js.br and b/public/assets/js/maps.js.br differ
diff --git a/public/assets/js/maps.js.gz b/public/assets/js/maps.js.gz
index 9d1006d8..07aa158d 100644
Binary files a/public/assets/js/maps.js.gz and b/public/assets/js/maps.js.gz differ
diff --git a/public/assets/js/newnote.js.br b/public/assets/js/newnote.js.br
index d0640178..e50fcb08 100644
Binary files a/public/assets/js/newnote.js.br and b/public/assets/js/newnote.js.br differ
diff --git a/public/assets/js/newnote.js.gz b/public/assets/js/newnote.js.gz
index a22d5ee3..8d481a8e 100644
Binary files a/public/assets/js/newnote.js.gz and b/public/assets/js/newnote.js.gz differ
diff --git a/public/assets/prism/prism.css b/public/assets/prism/prism.css
index 86122e46..eec8b864 100644
--- a/public/assets/prism/prism.css
+++ b/public/assets/prism/prism.css
@@ -1,84 +1,79 @@
-/* http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+git+http+markdown+php+php-extras+scss+sql&plugins=line-numbers+show-invisibles */
+/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript+bash+c+csharp+cpp+ruby+css-extras+diff+git+go+http+ini+json+latex+lua+makefile+markdown+nginx+objectivec+php+php-extras+python+rust+sass+scss+sql+swift+vim+wiki+yaml&plugins=line-numbers+autolinker */
/**
- * prism.js Dark theme for JavaScript, CSS and HTML
- * Based on the slides of the talk “/Reg(exp){2}lained/”
- * @author Lea Verou
+ * okaidia theme for JavaScript, CSS and HTML
+ * Loosely based on Monokai textmate theme by http://www.monokai.nl/
+ * @author ocodia
*/
code[class*="language-"],
pre[class*="language-"] {
- color: white;
- text-shadow: 0 -.1em .2em black;
- font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
- direction: ltr;
- text-align: left;
- white-space: pre;
- word-spacing: normal;
- word-break: normal;
- line-height: 1.5;
+ color: #f8f8f2;
+ background: none;
+ text-shadow: 0 1px rgba(0, 0, 0, 0.3);
+ font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 1.5;
- -moz-tab-size: 4;
- -o-tab-size: 4;
- tab-size: 4;
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
- -webkit-hyphens: none;
- -moz-hyphens: none;
- -ms-hyphens: none;
- hyphens: none;
-}
-
-@media print {
- code[class*="language-"],
- pre[class*="language-"] {
- text-shadow: none;
- }
-}
-
-pre[class*="language-"],
-:not(pre) > code[class*="language-"] {
- background: hsl(30, 20%, 25%);
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
- padding: 1em;
- margin: .5em 0;
- overflow: auto;
- border: .3em solid hsl(30, 20%, 40%);
- border-radius: .5em;
- box-shadow: 1px 1px .5em black inset;
+ padding: 1em;
+ margin: .5em 0;
+ overflow: auto;
+ border-radius: 0.3em;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+ background: #272822;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
- padding: .15em .2em .05em;
- border-radius: .3em;
- border: .13em solid hsl(30, 20%, 40%);
- box-shadow: 1px 1px .3em -.1em black inset;
+ padding: .1em;
+ border-radius: .3em;
+ white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
- color: hsl(30, 20%, 50%);
+ color: slategray;
}
.token.punctuation {
- opacity: .7;
+ color: #f8f8f2;
}
.namespace {
- opacity: .7;
+ opacity: .7;
}
.token.property,
.token.tag,
-.token.boolean,
-.token.number,
.token.constant,
-.token.symbol {
- color: hsl(350, 40%, 70%);
+.token.symbol,
+.token.deleted {
+ color: #f92672;
+}
+
+.token.boolean,
+.token.number {
+ color: #ae81ff;
}
.token.selector,
@@ -87,7 +82,7 @@ pre[class*="language-"] {
.token.char,
.token.builtin,
.token.inserted {
- color: hsl(75, 70%, 60%);
+ color: #a6e22e;
}
.token.operator,
@@ -96,93 +91,76 @@ pre[class*="language-"] {
.language-css .token.string,
.style .token.string,
.token.variable {
- color: hsl(40, 90%, 60%);
+ color: #f8f8f2;
}
.token.atrule,
.token.attr-value,
+.token.function {
+ color: #e6db74;
+}
+
.token.keyword {
- color: hsl(350, 40%, 70%);
+ color: #66d9ef;
}
.token.regex,
.token.important {
- color: #e90;
+ color: #fd971f;
}
.token.important,
.token.bold {
- font-weight: bold;
+ font-weight: bold;
}
.token.italic {
- font-style: italic;
+ font-style: italic;
}
.token.entity {
- cursor: help;
-}
-
-.token.deleted {
- color: red;
+ cursor: help;
}
pre.line-numbers {
- position: relative;
- padding-left: 3.8em;
- counter-reset: linenumber;
+ position: relative;
+ padding-left: 3.8em;
+ counter-reset: linenumber;
}
pre.line-numbers > code {
- position: relative;
+ position: relative;
}
.line-numbers .line-numbers-rows {
- position: absolute;
- pointer-events: none;
- top: 0;
- font-size: 100%;
- left: -3.8em;
- width: 3em; /* works for line-numbers below 1000 lines */
- letter-spacing: -1px;
- border-right: 1px solid #999;
+ position: absolute;
+ pointer-events: none;
+ top: 0;
+ font-size: 100%;
+ left: -3.8em;
+ width: 3em; /* works for line-numbers below 1000 lines */
+ letter-spacing: -1px;
+ border-right: 1px solid #999;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
- .line-numbers-rows > span {
- pointer-events: none;
- display: block;
- counter-increment: linenumber;
- }
+ .line-numbers-rows > span {
+ pointer-events: none;
+ display: block;
+ counter-increment: linenumber;
+ }
- .line-numbers-rows > span:before {
- content: counter(linenumber);
- color: #999;
- display: block;
- padding-right: 0.8em;
- text-align: right;
- }
-.token.tab:not(:empty):before,
-.token.cr:before,
-.token.lf:before {
- color: hsl(24, 20%, 85%);
-}
-
-.token.tab:not(:empty):before {
- content: '\21E5';
-}
-
-.token.cr:before {
- content: '\240D';
-}
-
-.token.crlf:before {
- content: '\240D\240A';
-}
-.token.lf:before {
- content: '\240A';
+ .line-numbers-rows > span:before {
+ content: counter(linenumber);
+ color: #999;
+ display: block;
+ padding-right: 0.8em;
+ text-align: right;
+ }
+.token a {
+ color: inherit;
}
diff --git a/public/assets/prism/prism.css.br b/public/assets/prism/prism.css.br
index f381a5e3..76b56929 100644
Binary files a/public/assets/prism/prism.css.br and b/public/assets/prism/prism.css.br differ
diff --git a/public/assets/prism/prism.css.gz b/public/assets/prism/prism.css.gz
index af19bb4c..156b0d07 100644
Binary files a/public/assets/prism/prism.css.gz and b/public/assets/prism/prism.css.gz differ
diff --git a/public/assets/prism/prism.js b/public/assets/prism/prism.js
index b00d099e..8510bb39 100644
--- a/public/assets/prism/prism.js
+++ b/public/assets/prism/prism.js
@@ -1,15 +1,37 @@
-/* http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+git+http+markdown+php+php-extras+scss+sql&plugins=line-numbers+show-invisibles */
-var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+""+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code;_self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);;
-Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?[^\s>\/]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(t){"entity"===t.type&&(t.attributes.title=t.content.replace(/&/,"&"))});;
-Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/