diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..606b9714 --- /dev/null +++ b/.env.example @@ -0,0 +1,52 @@ +APP_ENV=local +APP_DEBUG=true +APP_KEY=SomeRandomString +APP_TIMEZONE=UTC +APP_LANG=en +APP_LOG=daily + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=homestead +DB_USERNAME=homestead +DB_PASSWORD=secret +DB_CONNECTION=pgsql + +CACHE_DRIVER=file +SESSION_DRIVER=file +QUEUE_DRIVER=sync + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_DRIVER=smtp +MAIL_HOST=mailtrap.io +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAILGUN_DOMAIN=null +MAILGUN_SECRET=null + +AWS_S3_KEY=your-key +AWS_S3_SECRET=your-secret +AWS_S3_REGION=region +AWS_S3_BUCKET=your-bucket +AWS_S3_URL=https://xxxxxxx.s3-region.amazonaws.com + +APP_URL=https://example.com +APP_LONGURL=example.com +APP_SHORTURL=examp.le + +ADMIN_USER=admin +ADMIN_PASS=password + +PIWIK_URL= +PIWIK_SITE_ID= + +TWITTER_CONSUMER_KEY= +TWITTER_CONSUMER_SECRET= +TWITTER_ACCESS_TOKEN= +TWITTER_ACCESS_TOKEN_SECRET= diff --git a/.env.travis b/.env.travis new file mode 100644 index 00000000..b15c8b3a --- /dev/null +++ b/.env.travis @@ -0,0 +1,11 @@ +APP_ENV=testing +APP_KEY= +APP_URL=http://localhost:8000 +APP_LONGURL=localhost +APP_SHORTURL=local + +DB_CONNECTION=travis + +CACHE_DRIVER=array +SESSION_DRIVER=array +QUEUE_DRIVER=sync diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a8763f8e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto +*.css linguist-vendored +*.scss linguist-vendored diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f791bec4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/vendor +/node_modules +/bower_components +/public/storage +Homestead.yaml +Homestead.json +.env +/.sass-cache +/public/files +/public/keybase.txt +/coverage diff --git a/.styleci.yml b/.styleci.yml new file mode 100644 index 00000000..645435d1 --- /dev/null +++ b/.styleci.yml @@ -0,0 +1,7 @@ +preset: laravel + +disabled: + - concat_without_spaces + +finder: + path: app/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..cd687b56 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +language: php + +sudo: false + +addons: + postgresql: "9.4" + +services: + - postgresql + +env: + global: + - setup=basic + +php: + - 7.0 + - nightly +matrix: + allow_failures: + - php: nightly + +before_install: + - phpenv config-rm xdebug.ini + - travis_retry composer self-update --preview + +install: + - if [[ $setup = 'basic' ]]; then travis_retry composer install --no-interaction --prefer-dist; fi + - if [[ $setup = 'stable' ]]; then travis_retry composer update --no-interaction --prefer-dist --prefer-stable; fi + - if [[ $setup = 'lowest' ]]; then travis_retry composer update --no-interaction --prefer-dist --prefer-lowest --prefer-stable; fi + +before_script: + - 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 serve & + - sleep 5 # Give artisan some time to start serving + +script: + - phpdbg -qrr vendor/bin/phpunit --coverage-text diff --git a/app/Article.php b/app/Article.php new file mode 100644 index 00000000..5ca3f582 --- /dev/null +++ b/app/Article.php @@ -0,0 +1,148 @@ +morphMany('App\WebMention', 'commentable'); + } + + /** + * We shall set a blacklist of non-modifiable model attributes. + * + * @var array + */ + protected $guarded = ['id']; + + /** + * Process the article for display. + * + * @return string + */ + public function getMainAttribute($value) + { + $unicode = new UnicodeTools(); + $markdown = new CommonMarkConverter(); + $html = $markdown->convertToHtml($unicode->convertUnicodeCodepoints($value)); + //change
[lang] ~>
+ $match = '/\[(.*)\]\n/';
+ $replace = '';
+ $text = preg_replace($match, $replace, $html);
+ $default = preg_replace('//', '', $text);
+
+ return $default;
+ }
+
+ /**
+ * Convert updated_at to W3C time format.
+ *
+ * @return string
+ */
+ public function getW3cTimeAttribute()
+ {
+ return $this->updated_at->toW3CString();
+ }
+
+ /**
+ * Convert updated_at to a tooltip appropriate format.
+ *
+ * @return string
+ */
+ public function getTooltipTimeAttribute()
+ {
+ return $this->updated_at->toRFC850String();
+ }
+
+ /**
+ * Convert updated_at to a human readable format.
+ *
+ * @return string
+ */
+ public function getHumanTimeAttribute()
+ {
+ return $this->updated_at->diffForHumans();
+ }
+
+ /**
+ * Get the pubdate value for RSS feeds.
+ *
+ * @return string
+ */
+ public function getPubdateAttribute()
+ {
+ return $this->updated_at->toRSSString();
+ }
+
+ /**
+ * A link to the article, i.e. `/blog/1999/12/25/merry-christmas`.
+ *
+ * @return string
+ */
+ public function getLinkAttribute()
+ {
+ return '/blog/' . $this->updated_at->year . '/' . $this->updated_at->format('m') . '/' . $this->titleurl;
+ }
+
+ /**
+ * Scope a query to only include articles from a particular year/month.
+ *
+ * @return \Illuminate\Database\Eloquent\Builder
+ */
+ public function scopeDate($query, $year = null, $month = null)
+ {
+ if ($year == null) {
+ return $query;
+ }
+ $start = $year . '-01-01 00:00:00';
+ $end = ($year + 1) . '-01-01 00:00:00';
+ if (($month !== null) && ($month !== '12')) {
+ $start = $year . '-' . $month . '-01 00:00:00';
+ $end = $year . '-' . ($month + 1) . '-01 00:00:00';
+ }
+ if ($month === '12') {
+ $start = $year . '-12-01 00:00:00';
+ //$end as above
+ }
+
+ return $query->where([
+ ['updated_at', '>=', $start],
+ ['updated_at', '<', $end],
+ ]);
+ }
+}
diff --git a/app/Client.php b/app/Client.php
new file mode 100644
index 00000000..6461399c
--- /dev/null
+++ b/app/Client.php
@@ -0,0 +1,22 @@
+comment(PHP_EOL.Inspiring::quote().PHP_EOL);
+ }
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
new file mode 100644
index 00000000..71c519d3
--- /dev/null
+++ b/app/Console/Kernel.php
@@ -0,0 +1,30 @@
+command('inspire')
+ // ->hourly();
+ }
+}
diff --git a/app/Contact.php b/app/Contact.php
new file mode 100644
index 00000000..18cc6fa2
--- /dev/null
+++ b/app/Contact.php
@@ -0,0 +1,22 @@
+renderExceptionWithWhoops($exc);
+ }
+
+ if ($exc instanceof ModelNotFoundException) {
+ $exc = new NotFoundHttpException($exc->getMessage(), $exc);
+ }
+
+ if ($exc instanceof TokenMismatchException) {
+ return redirect()->back()
+ ->withInput($request->except('password', '_token'))
+ ->withErrors('Validation Token has expired. Please try again', 'csrf');
+ }
+
+ return parent::render($request, $exc);
+ }
+
+ /**
+ * Render an exception using Whoops.
+ *
+ * @param \Exception $exc
+ * @return \Illuminate\Http\Response
+ */
+ protected function renderExceptionWithWhoops(Exception $exc)
+ {
+ $whoops = new \Whoops\Run;
+ $handler = new \Whoops\Handler\PrettyPageHandler();
+ $handler->setEditor(function ($file, $line) {
+ return "atom://open?file=$file&line=$line";
+ });
+ $whoops->pushHandler($handler);
+
+ return new \Illuminate\Http\Response(
+ $whoops->handleException($exc),
+ $exc->getStatusCode(),
+ $exc->getHeaders()
+ );
+ }
+}
diff --git a/app/Exceptions/RemoteContentNotFound.php b/app/Exceptions/RemoteContentNotFound.php
new file mode 100644
index 00000000..04049903
--- /dev/null
+++ b/app/Exceptions/RemoteContentNotFound.php
@@ -0,0 +1,10 @@
+username = env('ADMIN_USER');
+ }
+
+ /**
+ * Show the main admin CP page.
+ *
+ * @return \Illuminate\View\Factory view
+ */
+ public function showWelcome()
+ {
+ return view('admin.welcome', ['name' => $this->username]);
+ }
+}
diff --git a/app/Http/Controllers/ArticlesAdminController.php b/app/Http/Controllers/ArticlesAdminController.php
new file mode 100644
index 00000000..21f4124d
--- /dev/null
+++ b/app/Http/Controllers/ArticlesAdminController.php
@@ -0,0 +1,140 @@
+ $message]);
+ }
+
+ /**
+ * List the articles that can be edited.
+ *
+ * @return \Illuminate\View\Factory view
+ */
+ public function listArticles()
+ {
+ $posts = Article::select('id', 'title', 'published')->orderBy('id', 'desc')->get();
+
+ return view('admin.listarticles', ['posts' => $posts]);
+ }
+
+ /**
+ * Show the edit form for an existing article.
+ *
+ * @param string The article id
+ * @return \Illuminate\View\Factory view
+ */
+ public function editArticle($articleId)
+ {
+ $post = Article::select(
+ 'title',
+ 'main',
+ 'url',
+ 'published'
+ )->where('id', $articleId)->get();
+
+ return view('admin.editarticle', ['id' => $articleId, 'post' => $post]);
+ }
+
+ /**
+ * Show the delete confirmation form for an article.
+ *
+ * @param string The article id
+ * @return \Illuminate\View\Factory view
+ */
+ public function deleteArticle($articleId)
+ {
+ return view('admin.deletearticle', ['id' => $articleId]);
+ }
+
+ /**
+ * Process an incoming request for a new article and save it.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\View\Factory view
+ */
+ public function postNewArticle(Request $request)
+ {
+ $published = $request->input('published');
+ if ($published == null) {
+ $published = '0';
+ }
+ //if a `.md` is attached use that for the main content.
+ $content = null; //set default value
+ if ($request->hasFile('article')) {
+ $file = $request->file('article')->openFile();
+ $content = $file->fread($file->getSize());
+ }
+ $main = $content ?? $request->input('main');
+ try {
+ $article = Article::create(
+ [
+ 'url' => $request->input('url'),
+ 'title' => $request->input('title'),
+ 'main' => $main,
+ 'published' => $published,
+ ]
+ );
+ } catch (Exception $e) {
+ $msg = $e->getMessage();
+ $unique = strpos($msg, '1062');
+ if ($unique !== false) {
+ //We've checked for error 1062, i.e. duplicate titleurl
+ return redirect('admin/blog/new')->withInput()->with('message', 'Duplicate title, please change');
+ }
+ //this isn't the error you're looking for
+ throw $e;
+ }
+
+ return view('admin.newarticlesuccess', ['id' => $article->id, 'title' => $article->title]);
+ }
+
+ /**
+ * Process an incoming request to edit an article.
+ *
+ * @param string
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate|View\Factory view
+ */
+ public function postEditArticle($articleId, Request $request)
+ {
+ $published = $request->input('published');
+ if ($published == null) {
+ $published = '0';
+ }
+ $article = Article::find($articleId);
+ $article->title = $request->input('title');
+ $article->url = $request->input('url');
+ $article->main = $request->input('main');
+ $article->published = $published;
+ $article->save();
+
+ return view('admin.editarticlesuccess', ['id' => $articleId]);
+ }
+
+ /**
+ * Process a request to delete an aricle.
+ *
+ * @param string The article id
+ * @return \Illuminate\View\Factory view
+ */
+ public function postDeleteArticle($articleId)
+ {
+ Article::where('id', $articleId)->delete();
+
+ return view('admin.deletearticlesuccess', ['id' => $articleId]);
+ }
+}
diff --git a/app/Http/Controllers/ArticlesController.php b/app/Http/Controllers/ArticlesController.php
new file mode 100644
index 00000000..95ee9453
--- /dev/null
+++ b/app/Http/Controllers/ArticlesController.php
@@ -0,0 +1,69 @@
+date($year, $month)
+ ->orderBy('updated_at', 'desc')
+ ->simplePaginate(5);
+
+ return view('multipost', ['data' => $articles]);
+ }
+
+ /**
+ * Show a single article.
+ *
+ * @return \Illuminate\View\Factory view
+ */
+ public function singleArticle($year, $month, $slug)
+ {
+ $article = Article::where('titleurl', $slug)->first();
+ if ($article->updated_at->year != $year || $article->updated_at->month != $month) {
+ throw new \Exception;
+ }
+
+ return view('singlepost', ['article' => $article]);
+ }
+
+ /**
+ * We only have the ID, work out post title, year and month
+ * and redirect to it.
+ *
+ * @return \Illuminte\Routing\RedirectResponse redirect
+ */
+ public function onlyIdInUrl($inURLId)
+ {
+ $numbers = new Numbers();
+ $realId = $numbers->b60tonum($inURLId);
+ $article = Article::findOrFail($realId);
+
+ return redirect($article->link);
+ }
+
+ /**
+ * Returns the RSS feed.
+ *
+ * @return \Illuminate\Http\Response
+ */
+ public function makeRSS()
+ {
+ $articles = Article::where('published', '1')->orderBy('updated_at', 'desc')->get();
+ $buildDate = $articles->first()->updated_at->toRssString();
+ $contents = (string) view('rss', ['articles' => $articles, 'buildDate' => $buildDate]);
+
+ return (new Response($contents, '200'))->header('Content-Type', 'application/rss+xml');
+ }
+}
diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php
new file mode 100644
index 00000000..a100dd6e
--- /dev/null
+++ b/app/Http/Controllers/Auth/AuthController.php
@@ -0,0 +1,72 @@
+middleware($this->guestMiddleware(), ['except' => 'logout']);
+ }
+
+ /**
+ * Get a validator for an incoming registration request.
+ *
+ * @param array $data
+ * @return \Illuminate\Contracts\Validation\Validator
+ */
+ protected function validator(array $data)
+ {
+ return Validator::make($data, [
+ 'name' => 'required|max:255',
+ 'email' => 'required|email|max:255|unique:users',
+ 'password' => 'required|min:6|confirmed',
+ ]);
+ }
+
+ /**
+ * Create a new user instance after a valid registration.
+ *
+ * @param array $data
+ * @return User
+ */
+ protected function create(array $data)
+ {
+ return User::create([
+ 'name' => $data['name'],
+ 'email' => $data['email'],
+ 'password' => bcrypt($data['password']),
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php
new file mode 100644
index 00000000..1ceed97b
--- /dev/null
+++ b/app/Http/Controllers/Auth/PasswordController.php
@@ -0,0 +1,32 @@
+middleware('guest');
+ }
+}
diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php
new file mode 100644
index 00000000..f752fa21
--- /dev/null
+++ b/app/Http/Controllers/AuthController.php
@@ -0,0 +1,29 @@
+input('username') === env('ADMIN_USER')
+ &&
+ $request->input('password') === env('ADMIN_PASS')
+ ) {
+ session(['loggedin' => true]);
+
+ return redirect()->intended('admin');
+ }
+
+ return redirect()->route('login');
+ }
+}
diff --git a/app/Http/Controllers/ClientsAdminController.php b/app/Http/Controllers/ClientsAdminController.php
new file mode 100644
index 00000000..c374aa24
--- /dev/null
+++ b/app/Http/Controllers/ClientsAdminController.php
@@ -0,0 +1,87 @@
+ $clients]);
+ }
+
+ /**
+ * Show form to add a client name.
+ *
+ * @return \Illuminate\View\Factory view
+ */
+ public function newClient()
+ {
+ return view('admin.newclient');
+ }
+
+ /**
+ * Process the request to adda new client name.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\View\Factory view
+ */
+ public function postNewClient(Request $request)
+ {
+ Client::create([
+ 'client_url' => $request->input('client_url'),
+ 'client_name' => $request->input('client_name'),
+ ]);
+
+ return view('admin.newclientsuccess');
+ }
+
+ /**
+ * Show a form to edit a client name.
+ *
+ * @param string The client id
+ * @return \Illuminate\View\Factory view
+ */
+ public function editClient($clientId)
+ {
+ $client = Client::findOrFail($clientId);
+
+ return view('admin.editclient', [
+ 'id' => $clientId,
+ 'client_url' => $client->client_url,
+ 'client_name' => $client->client_name,
+ ]);
+ }
+
+ /**
+ * Process the request to edit a client name.
+ *
+ * @param string The client id
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\View\Factory view
+ */
+ public function postEditClient($clientId, Request $request)
+ {
+ $client = Client::findOrFail($clientId);
+ if ($request->input('edit')) {
+ $client->client_url = $request->input('client_url');
+ $client->client_name = $request->input('client_name');
+ $client->save();
+
+ return view('admin.editclientsuccess');
+ }
+ if ($request->input('delete')) {
+ $client->delete();
+
+ return view('admin.deleteclientsuccess');
+ }
+ }
+}
diff --git a/app/Http/Controllers/ContactsAdminController.php b/app/Http/Controllers/ContactsAdminController.php
new file mode 100644
index 00000000..890f1241
--- /dev/null
+++ b/app/Http/Controllers/ContactsAdminController.php
@@ -0,0 +1,166 @@
+ $contacts]);
+ }
+
+ /**
+ * Show the form to edit an existing contact.
+ *
+ * @param string The contact id
+ * @return \Illuminate\View\Factory view
+ */
+ public function editContact($contactId)
+ {
+ $contact = Contact::findOrFail($contactId);
+
+ return view('admin.editcontact', ['contact' => $contact]);
+ }
+
+ /**
+ * Show the form to confirm deleting a contact.
+ *
+ * @return \Illuminate\View\Factory view
+ */
+ public function deleteContact($contactId)
+ {
+ return view('admin.deletecontact', ['id' => $contactId]);
+ }
+
+ /**
+ * Process the request to add a new contact.
+ *
+ * @param \Illuminate\Http|request $request
+ * @return \Illuminate\View\Factory view
+ */
+ public function postNewContact(Request $request)
+ {
+ $contact = new Contact();
+ $contact->name = $request->input('name');
+ $contact->nick = $request->input('nick');
+ $contact->homepage = $request->input('homepage');
+ $contact->twitter = $request->input('twitter');
+ $contact->save();
+ $contactId = $contact->id;
+
+ return view('admin.newcontactsuccess', ['id' => $contactId]);
+ }
+
+ /**
+ * Process the request to edit a contact.
+ *
+ * @todo Allow saving profile pictures for people without homepages
+ *
+ * @param string The contact id
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\View\Factory view
+ */
+ public function postEditContact($contactId, Request $request)
+ {
+ $contact = Contact::findOrFail($contactId);
+ $contact->name = $request->input('name');
+ $contact->nick = $request->input('nick');
+ $contact->homepage = $request->input('homepage');
+ $contact->twitter = $request->input('twitter');
+ $contact->save();
+
+ if ($request->hasFile('avatar')) {
+ if ($request->input('homepage') != '') {
+ $dir = parse_url($request->input('homepage'))['host'];
+ $destination = public_path() . '/assets/profile-images/' . $dir;
+ $filesystem = new Filesystem();
+ if ($filesystem->isDirectory($destination) === false) {
+ $filesystem->makeDirectory($destination);
+ }
+ $request->file('avatar')->move($destination, 'image');
+ }
+ }
+
+ return view('admin.editcontactsuccess');
+ }
+
+ /**
+ * Process the request to delete a contact.
+ *
+ * @param string The contact id
+ * @return \Illuminate\View\Factory view
+ */
+ public function postDeleteContact($contactId)
+ {
+ $contact = Contact::findOrFail($contactId);
+ $contact->delete();
+
+ return view('admin.deletecontactsuccess');
+ }
+
+ /**
+ * Download the avatar for a contact.
+ *
+ * This method attempts to find the microformat marked-up profile image
+ * from a given homepage and save it accordingly
+ *
+ * @param string The contact id
+ * @return \Illuminate\View\Factory view
+ */
+ public function getAvatar($contactId)
+ {
+ $contact = Contact::findOrFail($contactId);
+ $homepage = $contact->homepage;
+ if (($homepage !== null) && ($homepage !== '')) {
+ $client = new Client();
+ try {
+ $response = $client->get($homepage);
+ $html = (string) $response->getBody();
+ $mf2 = \Mf2\parse($html, $homepage);
+ } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+ return "Bad Response from $homepage";
+ }
+ $avatarURL = null; // Initialising
+ foreach ($mf2['items'] as $microformat) {
+ if ($microformat['type'][0] == 'h-card') {
+ $avatarURL = $microformat['properties']['photo'][0];
+ break;
+ }
+ }
+ try {
+ $avatar = $client->get($avatarURL);
+ } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+ return "Unable to get $avatarURL";
+ }
+ $directory = public_path() . '/assets/profile-images/' . parse_url($homepage)['host'];
+ $filesystem = new Filesystem();
+ if ($filesystem->isDirectory($directory) === false) {
+ $filesystem->makeDirectory($directory);
+ }
+ $filesystem->put($directory . '/image', $avatar->getBody());
+
+ return view('admin.getavatarsuccess', ['homepage' => parse_url($homepage)['host']]);
+ }
+ }
+}
diff --git a/app/Http/Controllers/ContactsController.php b/app/Http/Controllers/ContactsController.php
new file mode 100644
index 00000000..01528411
--- /dev/null
+++ b/app/Http/Controllers/ContactsController.php
@@ -0,0 +1,49 @@
+homepagePretty = parse_url($contact->homepage)['host'];
+ $file = public_path() . '/assets/profile-images/' . $contact->homepagePretty . '/image';
+ $contact->image = ($filesystem->exists($file)) ?
+ '/assets/profile-images/' . $contact->homepagePretty . '/image'
+ :
+ '/assets/profile-images/default-image';
+ }
+
+ return view('contacts', ['contacts' => $contacts]);
+ }
+
+ /**
+ * Show a single contact.
+ *
+ * @return \Illuminate\View\Factory view
+ */
+ public function showSingle($nick)
+ {
+ $filesystem = new Filesystem();
+ $contact = Contact::where('nick', '=', $nick)->firstOrFail();
+ $contact->homepagePretty = parse_url($contact->homepage)['host'];
+ $file = public_path() . '/assets/profile-images/' . $contact->homepagePretty . '/image';
+ $contact->image = ($filesystem->exists($file)) ?
+ '/assets/profile-images/' . $contact->homepagePretty . '/image'
+ :
+ '/assets/profile-images/default-image';
+
+ return view('contact', ['contact' => $contact]);
+ }
+}
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
new file mode 100644
index 00000000..d492e0b3
--- /dev/null
+++ b/app/Http/Controllers/Controller.php
@@ -0,0 +1,14 @@
+indieAuthService = $indieAuthService ?? new IndieAuthService();
+ $this->client = $client ?? new Client();
+ $this->tokenService = $tokenService ?? new TokenService();
+ }
+
+ /**
+ * Begin the indie auth process. This method ties in to the login page
+ * from our micropub client. Here we then query the user’s homepage
+ * for their authorisation endpoint, and redirect them there with a
+ * unique secure state value.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Routing\RedirectResponse redirect
+ */
+ public function beginauth(Request $request)
+ {
+ $authorizationEndpoint = $this->indieAuthService->getAuthorizationEndpoint(
+ $request->input('me'),
+ $this->client
+ );
+ if ($authorizationEndpoint) {
+ $authorizationURL = $this->indieAuthService->buildAuthorizationURL(
+ $authorizationEndpoint,
+ $request->input('me'),
+ $this->client
+ );
+ if ($authorizationURL) {
+ return redirect($authorizationURL);
+ }
+ }
+
+ return redirect('/notes/new')->withErrors('Unable to determine authorisation endpoint', 'indieauth');
+ }
+
+ /**
+ * Once they have verified themselves through the authorisation endpint
+ * the next step is retreiveing a token from the token endpoint.
+ *
+ * @param \Illuminate\Http\Rrequest $request
+ * @return \Illuminate\Routing\RedirectResponse redirect
+ */
+ public function indieauth(Request $request)
+ {
+ if ($request->session()->get('state') != $request->input('state')) {
+ return redirect('/notes/new')->withErrors(
+ 'Invalid state
value returned from indieauth server',
+ 'indieauth'
+ );
+ }
+ $tokenEndpoint = $this->indieAuthService->getTokenEndpoint($request->input('me'), $this->client);
+ $redirectURL = config('app.url') . '/indieauth';
+ $clientId = config('app.url') . '/notes/new';
+ $data = [
+ 'endpoint' => $tokenEndpoint,
+ 'code' => $request->input('code'),
+ 'me' => $request->input('me'),
+ 'redirect_url' => $redirectURL,
+ 'client_id' => $clientId,
+ 'state' => $request->input('state'),
+ ];
+ $token = $this->indieAuthService->getAccessToken($data, $this->client);
+
+ if (array_key_exists('access_token', $token)) {
+ $request->session()->put('me', $token['me']);
+ $request->session()->put('token', $token['access_token']);
+
+ return redirect('/notes/new');
+ }
+
+ return redirect('/notes/new')->withErrors('Unable to get a token from the endpoint', 'indieauth');
+ }
+
+ /**
+ * 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, $this->client);
+ 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 (new Response($content, 200))
+ ->header('Content-Type', 'application/x-www-form-urlencoded');
+ }
+ $content = 'There was an error verifying the authorisation code.';
+
+ return new Response($content, 400);
+ }
+
+ /**
+ * Log out the user, flush an session data, and overwrite any cookie data.
+ *
+ * @param \Illuminate\Cookie\CookieJar $cookie
+ * @return \Illuminate\Routing\RedirectResponse redirect
+ */
+ public function indieauthLogout(Request $request, CookieJar $cookie)
+ {
+ $request->session()->flush();
+ $cookie->queue('me', 'loggedout', 5);
+
+ return redirect('/notes/new');
+ }
+}
diff --git a/app/Http/Controllers/MicropubClientController.php b/app/Http/Controllers/MicropubClientController.php
new file mode 100644
index 00000000..9978d985
--- /dev/null
+++ b/app/Http/Controllers/MicropubClientController.php
@@ -0,0 +1,333 @@
+indieAuthService = $indieAuthService ?? new IndieAuthService();
+ $this->guzzleClient = $guzzleClient ?? new GuzzleClient();
+ $this->indieClient = $indieClient ?? new IndieClient();
+ }
+
+ /**
+ * Display the new notes form.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\View\Factory view
+ */
+ public function newNotePage(Request $request)
+ {
+ $url = $request->session()->get('me');
+ $syndication = $this->parseSyndicationTargets(
+ $request->session()->get('syndication')
+ );
+
+ return view('micropubnewnotepage', [
+ 'url' => $url,
+ 'syndication' => $syndication,
+ ]);
+ }
+
+ /**
+ * Post the notes content to the relavent micropub API endpoint.
+ *
+ * @todo make sure this works with multiple syndication targets
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return mixed
+ */
+ public function postNewNote(Request $request)
+ {
+ $domain = $request->session()->get('me');
+ $token = $request->session()->get('token');
+
+ $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint(
+ $domain,
+ $this->indieClient
+ );
+ if (! $micropubEndpoint) {
+ return redirect('notes/new')->withErrors('Unable to determine micropub API endpoint', 'endpoint');
+ }
+
+ $response = $this->postNoteRequest($request, $micropubEndpoint, $token);
+
+ if ($response->getStatusCode() == 201) {
+ $location = $response->getHeader('Location');
+ if (is_array($location)) {
+ return redirect($location[0]);
+ }
+
+ return redirect($location);
+ }
+
+ return redirect('notes/new')->withErrors('Endpoint didn’t create the note.', 'endpoint');
+ }
+
+ /**
+ * We make a request to the micropub endpoint requesting syndication targets
+ * and store them in the session.
+ *
+ * @todo better handling of response regarding mp-syndicate-to
+ * and syndicate-to
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param \IndieAuth\Client $indieClient
+ * @param \GuzzleHttp\Client $guzzleClient
+ * @return \Illuminate\Routing\Redirector redirect
+ */
+ public function refreshSyndicationTargets(Request $request)
+ {
+ $domain = $request->session()->get('me');
+ $token = $request->session()->get('token');
+ $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+
+ if (! $micropubEndpoint) {
+ return redirect('notes/new')->withErrors('Unable to determine micropub API endpoint', 'endpoint');
+ }
+
+ try {
+ $response = $this->guzzleClient->get($micropubEndpoint, [
+ 'headers' => ['Authorization' => 'Bearer ' . $token],
+ 'query' => ['q' => 'syndicate-to'],
+ ]);
+ } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+ return redirect('notes/new')->withErrors('Bad response when refreshing syndication targets', 'endpoint');
+ }
+ $body = (string) $response->getBody();
+ $syndication = str_replace(['&', '[]'], [';', ''], $body);
+
+ $request->session()->put('syndication', $syndication);
+
+ return redirect('notes/new');
+ }
+
+ /**
+ * This method performs the actual POST request.
+ *
+ * @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
+ */
+ 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) {
+ $filename = $photo->getClientOriginalName();
+ $photo->move(storage_path() . '/media-tmp', $filename);
+ $multipart[] = [
+ 'name' => 'photo[]',
+ 'contents' => fopen(storage_path() . '/media-tmp/' . $filename, 'r'),
+ ];
+ }
+ }
+ if ($request->input('in-reply-to') != '') {
+ $multipart[] = [
+ 'name' => 'in-reply-to',
+ 'contents' => $request->input('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('confirmlocation')) {
+ $latLng = $request->input('location');
+ $geoURL = 'geo:' . str_replace(' ', '', $latLng);
+ $multipart[] = [
+ 'name' => 'location',
+ 'contents' => $geoURL,
+ ];
+ if ($request->input('address') != '') {
+ $multipart[] = [
+ 'name' => 'place_name',
+ 'contents' => $request->input('address'),
+ ];
+ }
+ }
+ $headers = [
+ 'Authorization' => 'Bearer ' . $token,
+ ];
+ try {
+ $response = $this->guzzleClient->post($micropubEndpoint, [
+ 'multipart' => $multipart,
+ 'headers' => $headers,
+ ]);
+ } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+ return redirect('notes/new')
+ ->withErrors('There was a bad response from the micropub endpoint.', 'endpoint');
+ }
+
+ return $response;
+ }
+
+ /**
+ * Create a new place.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return mixed
+ */
+ public function postNewPlace(Request $request)
+ {
+ $domain = $request->session()->get('me');
+ $token = $request->session()->get('token');
+
+ $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+ if (! $micropubEndpoint) {
+ return (new Response(json_encode([
+ 'error' => true,
+ 'message' => 'Could not determine the micropub endpoint.',
+ ]), 400))
+ ->header('Content-Type', 'application/json');
+ }
+
+ $place = $this->postPlaceRequest($request, $micropubEndpoint, $token);
+ if ($place === false) {
+ return (new Response(json_encode([
+ 'error' => true,
+ 'message' => 'Unable to create the new place',
+ ]), 400))
+ ->header('Content-Type', 'application/json');
+ }
+
+ return (new Response(json_encode([
+ 'url' => $place,
+ 'name' => $request->input('place-name'),
+ 'latitude' => $request->input('place-latitude'),
+ 'longitude' => $request->input('place-longitude'),
+ ]), 200))
+ ->header('Content-Type', 'application/json');
+ }
+
+ /**
+ * 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) {
+ //not sure yet...
+ }
+ if ($response->getStatusCode() == 201) {
+ return $response->getHeader('Location')[0];
+ }
+
+ return false;
+ }
+
+ /**
+ * Make a request to the micropub endpoint requesting any nearby places.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param string $latitude
+ * @param string $longitude
+ * @return \Illuminate\Http\Response
+ */
+ public function nearbyPlaces(
+ Request $request,
+ $latitude,
+ $longitude
+ ) {
+ $domain = $request->session()->get('me');
+ $token = $request->session()->get('token');
+ $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+
+ if (! $micropubEndpoint) {
+ return;
+ }
+
+ try {
+ $response = $this->guzzleClient->get($micropubEndpoint, [
+ 'headers' => ['Authorization' => 'Bearer ' . $token],
+ 'query' => ['q' => 'geo:' . $latitude . ',' . $longitude],
+ ]);
+ } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+ return;
+ }
+
+ return (new Response($response->getBody(), 200))
+ ->header('Content-Type', 'application/json');
+ }
+
+ /**
+ * Parse the syndication targets retreived from a cookie, to a form that can
+ * be used in a view.
+ *
+ * @param string $syndicationTargets
+ * @return array|null
+ */
+ private function parseSyndicationTargets($syndicationTargets = null)
+ {
+ if ($syndicationTargets === null) {
+ return;
+ }
+ $mpSyndicateTo = [];
+ $parts = explode(';', $syndicationTargets);
+ foreach ($parts as $part) {
+ $target = explode('=', $part);
+ $mpSyndicateTo[] = urldecode($target[1]);
+ }
+ if (count($mpSyndicateTo) > 0) {
+ return $mpSyndicateTo;
+ }
+ }
+}
diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php
new file mode 100644
index 00000000..1a941125
--- /dev/null
+++ b/app/Http/Controllers/MicropubController.php
@@ -0,0 +1,143 @@
+tokenService = $tokenService ?? new TokenService();
+ $this->noteService = $noteService ?? new NoteService();
+ $this->placeService = $placeService ?? new PlaceService();
+ }
+
+ /**
+ * This function receives an API request, verifies the authenticity
+ * then passes over the info to the relavent Service class.
+ *
+ * @param \Illuminate\Http\Request request
+ * @return \Illuminate\Http\Response
+ */
+ public function post(Request $request)
+ {
+ $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');
+ $type = $request->input('h');
+ if ($type == 'entry') {
+ $note = $this->noteService->createNote($request, $clientId);
+ $content = 'Note created at ' . $note->longurl;
+
+ return (new Response($content, 201))
+ ->header('Location', $note->longurl);
+ }
+ if ($type == 'card') {
+ $place = $this->placeService->createPlace($request);
+ $content = 'Place created at ' . $place->longurl;
+
+ return (new Response($content, 201))
+ ->header('Location', $place->longurl);
+ }
+ }
+ }
+ $content = http_build_query([
+ 'error' => 'invalid_token',
+ 'error_description' => 'The token provided is not valid or does not have the necessary scope',
+ ]);
+
+ return (new Response($content, 400))
+ ->header('Content-Type', 'application/x-www-form-urlencoded');
+ }
+ $content = 'No OAuth token sent with request.';
+
+ return new Response($content, 400);
+ }
+
+ /**
+ * A GET request has been made to `api/post` with an accompanying
+ * token, here we check wether the token is valid and respond
+ * appropriately. Further if the request has the query parameter
+ * synidicate-to we respond with the known syndication endpoints.
+ *
+ * @todo Move the syndication endpoints into a .env variable
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\Response
+ */
+ public function getEndpoint(Request $request)
+ {
+ $httpAuth = $request->header('Authorization');
+ if (preg_match('/Bearer (.+)/', $httpAuth, $match)) {
+ $token = $match[1];
+ $valid = $this->tokenService->validateToken($token);
+
+ if ($valid === null) {
+ return new Response('Invalid token', 400);
+ }
+ //we have a valid token, is `syndicate-to` set?
+ if ($request->input('q') === 'syndicate-to') {
+ $content = http_build_query([
+ 'mp-syndicate-to' => 'twitter.com/jonnybarnes',
+ ]);
+
+ return (new Response($content, 200))
+ ->header('Content-Type', 'application/x-www-form-urlencoded');
+ }
+ //nope, how about a geo URL?
+ if (substr($request->input('q'), 0, 4) === 'geo:') {
+ $geo = explode(':', $request->input('q'));
+ $latlng = explode(',', $geo[1]);
+ $latitude = $latlng[0];
+ $longitude = $latlng[1];
+ $places = Place::near($latitude, $longitude, 1000);
+
+ return (new Response(json_encode($places), 200))
+ ->header('Content-Type', 'application/json');
+ }
+ //nope, just return the token
+ $content = http_build_query([
+ 'me' => $valid->getClaim('me'),
+ 'scope' => $valid->getClaim('scope'),
+ 'client_id' => $valid->getClaim('client_id'),
+ ]);
+
+ return (new Response($content, 200))
+ ->header('Content-Type', 'application/x-www-form-urlencoded');
+ }
+ $content = 'No OAuth token sent with request.';
+
+ return new Response($content, 400);
+ }
+}
diff --git a/app/Http/Controllers/NotesAdminController.php b/app/Http/Controllers/NotesAdminController.php
new file mode 100644
index 00000000..6fd48096
--- /dev/null
+++ b/app/Http/Controllers/NotesAdminController.php
@@ -0,0 +1,100 @@
+orderBy('id', 'desc')->get();
+ foreach ($notes as $note) {
+ $note->originalNote = $note->getOriginal('note');
+ }
+
+ return view('admin.listnotes', ['notes' => $notes]);
+ }
+
+ /**
+ * Display the form to edit a specific note.
+ *
+ * @param string The note id
+ * @return \Illuminate\View\Factory view
+ */
+ public function editNotePage($noteId)
+ {
+ $note = Note::find($noteId);
+ $note->originalNote = $note->getOriginal('note');
+
+ return view('admin.editnote', ['id' => $noteId, 'note' => $note]);
+ }
+
+ /**
+ * Process a request to make a new note.
+ *
+ * @param Illuminate\Http\Request $request
+ * @todo Sort this mess out
+ */
+ public function createNote(Request $request)
+ {
+ $validator = Validator::make(
+ $request->all(),
+ ['photo' => 'photosize'],
+ ['photosize' => 'At least one uploaded file exceeds size limit of 5MB']
+ );
+ if ($validator->fails()) {
+ return redirect('/admin/note/new')
+ ->withErrors($validator)
+ ->withInput();
+ }
+
+ $note = $this->noteService->createNote($request);
+
+ return view('admin.newnotesuccess', [
+ 'id' => $note->id,
+ 'shorturl' => $note->shorturl,
+ ]);
+ }
+
+ /**
+ * Process a request to edit a note. Easy since this can only be done
+ * from the admin CP.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\View\Factory view
+ */
+ public function editNote($noteId, Request $request)
+ {
+ //update note data
+ $note = Note::find($noteId);
+ $note->note = $request->input('content');
+ $note->in_reply_to = $request->input('in-reply-to');
+ $note->save();
+
+ if ($request->input('webmentions')) {
+ $wmc = new WebMentionsController();
+ $wmc->send($note);
+ }
+
+ return view('admin.editnotesuccess', ['id' => $noteId]);
+ }
+}
diff --git a/app/Http/Controllers/NotesController.php b/app/Http/Controllers/NotesController.php
new file mode 100644
index 00000000..2a5dcb3f
--- /dev/null
+++ b/app/Http/Controllers/NotesController.php
@@ -0,0 +1,240 @@
+with('webmentions', 'place')->simplePaginate(10);
+ foreach ($notes as $note) {
+ $replies = 0;
+ foreach ($note->webmentions as $webmention) {
+ if ($webmention->type == 'reply') {
+ $replies = $replies + 1;
+ }
+ }
+ $note->replies = $replies;
+ $note->twitter = $this->checkTwitterReply($note->in_reply_to);
+ $note->iso8601_time = $note->updated_at->toISO8601String();
+ $note->human_time = $note->updated_at->diffForHumans();
+ if ($note->location && ($note->place === null)) {
+ $pieces = explode(':', $note->location);
+ $latlng = explode(',', $pieces[0]);
+ $note->latitude = trim($latlng[0]);
+ $note->longitude = trim($latlng[1]);
+ if (count($pieces) == 2) {
+ $note->address = $pieces[1];
+ }
+ }
+ if ($note->place !== null) {
+ preg_match('/\((.*)\)/', $note->place->location, $matches);
+ $lnglat = explode(' ', $matches[1]);
+ $note->latitude = $lnglat[1];
+ $note->longitude = $lnglat[0];
+ $note->address = $note->place->name;
+ $note->placeLink = '/places/' . $note->place->slug;
+ }
+ $photoURLs = [];
+ $photos = $note->getMedia();
+ foreach ($photos as $photo) {
+ $photoURLs[] = $photo->getUrl();
+ }
+ $note->photoURLs = $photoURLs;
+ }
+
+ return view('allnotes', ['notes' => $notes]);
+ }
+
+ /**
+ * Show a single note.
+ *
+ * @param string The id of the note
+ * @return \Illuminate\View\Factory view
+ */
+ public function singleNote($urlId)
+ {
+ $numbers = new Numbers();
+ $realId = $numbers->b60tonum($urlId);
+ $note = Note::find($realId);
+ $replies = [];
+ $reposts = [];
+ $likes = [];
+ foreach ($note->webmentions as $webmention) {
+ switch ($webmention->type) {
+ case 'reply':
+ $content = unserialize($webmention->content);
+ $content['source'] = $this->bridgyReply($webmention->source);
+ $content['photo'] = $this->createPhotoLink($content['photo']);
+ $content['date'] = $carbon->parse($content['date'])->toDayDateTimeString();
+ $replies[] = $content;
+ break;
+
+ case 'repost':
+ $content = unserialize($webmention->content);
+ $content['photo'] = $this->createPhotoLink($content['photo']);
+ $content['date'] = $carbon->parse($content['date'])->toDayDateTimeString();
+ $reposts[] = $content;
+ break;
+
+ case 'like':
+ $content = unserialize($webmention->content);
+ $content['photo'] = $this->createPhotoLink($content['photo']);
+ $likes[] = $content;
+ break;
+ }
+ }
+ $note->twitter = $this->checkTwitterReply($note->in_reply_to);
+ $note->iso8601_time = $note->updated_at->toISO8601String();
+ $note->human_time = $note->updated_at->diffForHumans();
+ if ($note->location && ($note->place === null)) {
+ $pieces = explode(':', $note->location);
+ $latlng = explode(',', $pieces[0]);
+ $note->latitude = trim($latlng[0]);
+ $note->longitude = trim($latlng[1]);
+ if (count($pieces) == 2) {
+ $note->address = $pieces[1];
+ }
+ }
+ if ($note->place !== null) {
+ preg_match('/\((.*)\)/', $note->place->location, $matches);
+ $lnglat = explode(' ', $matches[1]);
+ $note->latitude = $lnglat[1];
+ $note->longitude = $lnglat[0];
+ $note->address = $note->place->name;
+ $note->placeLink = '/places/' . $note->place->slug;
+ }
+
+ $note->photoURLs = [];
+ foreach ($note->getMedia() as $photo) {
+ $note->photoURLs[] = $photo->getUrl();
+ }
+
+ return view('singlenote', [
+ 'note' => $note,
+ 'replies' => $replies,
+ 'reposts' => $reposts,
+ 'likes' => $likes,
+ ]);
+ }
+
+ /**
+ * Redirect /note/{decID} to /notes/{nb60id}.
+ *
+ * @param string The decimal id of he note
+ * @return \Illuminate\Routing\RedirectResponse redirect
+ */
+ public function singleNoteRedirect($decId)
+ {
+ $numbers = new Numbers();
+ $realId = $numbers->numto60($decId);
+
+ $url = config('app.url') . '/notes/' . $realId;
+
+ return redirect($url);
+ }
+
+ /**
+ * Show all notes tagged with {tag}.
+ *
+ * @param string The tag
+ * @return \Illuminate\View\Factory view
+ */
+ public function taggedNotes($tag)
+ {
+ $tagId = Tag::where('tag', $tag)->pluck('id');
+ $notes = Tag::find($tagId)->notes()->orderBy('updated_at', 'desc')->get();
+ foreach ($notes as $note) {
+ $note->iso8601_time = $note->updated_at->toISO8601String();
+ $note->human_time = $note->updated_at->diffForHumans();
+ }
+
+ return view('taggednotes', ['notes' => $notes, 'tag' => $tag]);
+ }
+
+ /**
+ * Swap a brid.gy URL shim-ing a twitter reply to a real twitter link.
+ *
+ * @param string
+ * @return string
+ */
+ public function bridgyReply($source)
+ {
+ $url = $source;
+ if (mb_substr($source, 0, 28, 'UTF-8') == 'https://brid-gy.appspot.com/') {
+ $parts = explode('/', $source);
+ $tweetId = array_pop($parts);
+ if ($tweetId) {
+ $url = 'https://twitter.com/_/status/' . $tweetId;
+ }
+ }
+
+ return $url;
+ }
+
+ /**
+ * Create the photo link.
+ *
+ * @param string
+ * @return string
+ */
+ public function createPhotoLink($url)
+ {
+ $host = parse_url($url)['host'];
+ if ($host != 'twitter.com' && $host != 'pbs.twimg.com') {
+ return '/assets/profile-images/' . $host . '/image';
+ }
+ if (mb_substr($url, 0, 20) == 'http://pbs.twimg.com') {
+ return str_replace('http://', 'https://', $url);
+ }
+ }
+
+ /**
+ * Twitter!!!
+ *
+ * @param string The reply to URL
+ * @return string | null
+ */
+ private function checkTwitterReply($url)
+ {
+ if ($url == null) {
+ return;
+ }
+
+ if (mb_substr($url, 0, 20, 'UTF-8') !== 'https://twitter.com/') {
+ return;
+ }
+
+ $arr = explode('/', $url);
+ $tweetId = end($arr);
+ if (Cache::has($tweetId)) {
+ return Cache::get($tweetId);
+ }
+ try {
+ $oEmbed = Twitter::getOembed([
+ 'id' => $tweetId,
+ 'align' => 'center',
+ 'omit_script' => true,
+ 'maxwidth' => 550,
+ ]);
+ } catch (\Exception $e) {
+ return;
+ }
+ Cache::put($tweetId, $oEmbed, ($oEmbed->cache_age / 60));
+
+ return $oEmbed;
+ }
+}
diff --git a/app/Http/Controllers/PhotosController.php b/app/Http/Controllers/PhotosController.php
new file mode 100644
index 00000000..9eaeb769
--- /dev/null
+++ b/app/Http/Controllers/PhotosController.php
@@ -0,0 +1,94 @@
+imageResizeLimit = 800;
+ }
+
+ /**
+ * Save an uploaded photo to the image folder.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param string The associated note’s nb60 ID
+ * @return bool
+ */
+ public function saveImage(Request $request, $nb60id)
+ {
+ if ($request->hasFile('photo') !== true) {
+ return false;
+ }
+ $photoFilename = 'note-' . $nb60id;
+ $path = public_path() . '/assets/img/notes/';
+ $ext = $request->file('photo')->getClientOriginalExtension();
+ $photoFilename .= '.' . $ext;
+ $request->file('photo')->move($path, $photoFilename);
+
+ return true;
+ }
+
+ /**
+ * Prepare a photo for posting to twitter.
+ *
+ * @param string photo fileanme
+ * @return string small photo filename, or null
+ */
+ public function makeSmallPhotoForTwitter($photoFilename)
+ {
+ $imagine = new Imagine();
+ $orig = $imagine->open(public_path() . '/assets/img/notes/' . $photoFilename);
+ $size = [$orig->getSize()->getWidth(), $orig->getSize()->getHeight()];
+ if ($size[0] > $this->imageResizeLimit || $size[1] > $this->imageResizeLimit) {
+ $filenameParts = explode('.', $photoFilename);
+ $preExt = count($filenameParts) - 2;
+ $filenameParts[$preExt] .= '-small';
+ $photoFilenameSmall = implode('.', $filenameParts);
+ $aspectRatio = $size[0] / $size[1];
+ $box = ($aspectRatio >= 1) ?
+ [$this->imageResizeLimit, (int) round($this->imageResizeLimit / $aspectRatio)]
+ :
+ [(int) round($this->imageResizeLimit * $aspectRatio), $this->imageResizeLimit];
+ $orig->resize(new Box($box[0], $box[1]))
+ ->save(public_path() . '/assets/img/notes/' . $photoFilenameSmall);
+
+ return $photoFilenameSmall;
+ }
+ }
+
+ /**
+ * Get the image path for a note.
+ *
+ * @param string $nb60id
+ * @return string | null
+ */
+ public function getPhotoPath($nb60id)
+ {
+ $filesystem = new Filesystem();
+ $photoDir = public_path() . '/assets/img/notes';
+ $files = $filesystem->files($photoDir);
+ foreach ($files as $file) {
+ $parts = explode('.', $file);
+ $name = $parts[0];
+ $dirs = explode('/', $name);
+ $actualname = last($dirs);
+ if ($actualname == 'note-' . $nb60id) {
+ $ext = $parts[1];
+ }
+ }
+ if (isset($ext)) {
+ return '/assets/img/notes/note-' . $nb60id . '.' . $ext;
+ }
+ }
+}
diff --git a/app/Http/Controllers/PlacesAdminController.php b/app/Http/Controllers/PlacesAdminController.php
new file mode 100644
index 00000000..24f492f9
--- /dev/null
+++ b/app/Http/Controllers/PlacesAdminController.php
@@ -0,0 +1,85 @@
+ $places]);
+ }
+
+ /**
+ * Show the form to make a new place.
+ *
+ * @return \Illuminate\View\Factory view
+ */
+ public function newPlacePage()
+ {
+ return view('admin.newplace');
+ }
+
+ /**
+ * Display the form to edit a specific place.
+ *
+ * @param string The place id
+ * @return \Illuminate\View\Factory view
+ */
+ public function editPlacePage($placeId)
+ {
+ $place = Place::findOrFail($placeId);
+
+ $latitude = $place->getLatitude();
+ $longitude = $place->getLongitude();
+
+ return view('admin.editplace', [
+ 'id' => $placeId,
+ 'name' => $place->name,
+ 'description' => $place->description,
+ 'latitude' => $latitude,
+ 'longitude' => $longitude,
+ ]);
+ }
+
+ /**
+ * Process a request to make a new place.
+ *
+ * @param Illuminate\Http\Request $request
+ * @return Illuminate\View\Factory view
+ */
+ public function createPlace(Request $request)
+ {
+ $this->placeService->createPlace($request);
+
+ return view('admin.newplacesuccess');
+ }
+
+ /**
+ * Process a request to edit a place.
+ *
+ * @param string The place id
+ * @param Illuminate\Http\Request $request
+ * @return Illuminate\View\Factory view
+ */
+ public function editPlace($placeId, Request $request)
+ {
+ $place = Place::findOrFail($placeId);
+ $place->name = $request->name;
+ $place->description = $request->description;
+ $place->location = new Point((float) $request->latitude, (float) $request->longitude);
+ $place->save();
+
+ return view('admin.editplacesuccess');
+ }
+}
diff --git a/app/Http/Controllers/PlacesController.php b/app/Http/Controllers/PlacesController.php
new file mode 100644
index 00000000..d28a6852
--- /dev/null
+++ b/app/Http/Controllers/PlacesController.php
@@ -0,0 +1,89 @@
+ $places]);
+ }
+
+ /**
+ * Show the form for creating a new resource.
+ *
+ * @return \Illuminate\Http\Response
+ */
+ public function create()
+ {
+ //
+ }
+
+ /**
+ * Store a newly created resource in storage.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\Response
+ */
+ public function store(Request $request)
+ {
+ //
+ }
+
+ /**
+ * Display the specified resource.
+ *
+ * @param string $slug
+ * @return \Illuminate\Http\Response
+ */
+ public function show($slug)
+ {
+ $place = Place::where('slug', '=', $slug)->first();
+
+ return view('singleplace', ['place' => $place]);
+ }
+
+ /**
+ * Show the form for editing the specified resource.
+ *
+ * @param int $id
+ * @return \Illuminate\Http\Response
+ */
+ public function edit($id)
+ {
+ //
+ }
+
+ /**
+ * Update the specified resource in storage.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param int $id
+ * @return \Illuminate\Http\Response
+ */
+ public function update(Request $request, $id)
+ {
+ //
+ }
+
+ /**
+ * Remove the specified resource from storage.
+ *
+ * @param int $id
+ * @return \Illuminate\Http\Response
+ */
+ public function destroy($id)
+ {
+ //
+ }
+}
diff --git a/app/Http/Controllers/ShortURLsController.php b/app/Http/Controllers/ShortURLsController.php
new file mode 100644
index 00000000..9c34ca0f
--- /dev/null
+++ b/app/Http/Controllers/ShortURLsController.php
@@ -0,0 +1,120 @@
+b60tonum($shortURLId);
+ $shorturl = ShortURL::find($num);
+ $redirect = $shorturl->redirect;
+
+ return redirect($redirect);
+ }
+
+ /**
+ * I had an old redirect systme breifly, but cool URLs should still work.
+ *
+ * @param string URL ID
+ * @return \Illuminate\Routing\Redirector redirect
+ */
+ public function oldRedirect($shortURLId)
+ {
+ $filename = base_path() . '/public/assets/old-shorturls.json';
+ $handle = fopen($filename, 'r');
+ $contents = fread($handle, filesize($filename));
+ $object = json_decode($contents);
+
+ foreach ($object as $key => $val) {
+ if ($shortURLId == $key) {
+ return redirect($val);
+ }
+ }
+
+ return 'This id was never used.
+ Old redirects are located at
+
+ old-shorturls.json
+
.';
+ }
+}
diff --git a/app/Http/Controllers/TokensController.php b/app/Http/Controllers/TokensController.php
new file mode 100644
index 00000000..5f896e80
--- /dev/null
+++ b/app/Http/Controllers/TokensController.php
@@ -0,0 +1,61 @@
+tokenService = $tokenService ?? new TokenService();
+ }
+
+ /**
+ * Show all the saved tokens.
+ *
+ * @return \Illuminate\View\Factory view
+ */
+ public function showTokens()
+ {
+ $tokens = $$his->tokenService->getAll();
+
+ return view('admin.listtokens', ['tokens' => $tokens]);
+ }
+
+ /**
+ * Show the form to delete a certain token.
+ *
+ * @param string The token id
+ * @return \Illuminate\View\Factory view
+ */
+ public function deleteToken($tokenId)
+ {
+ return view('admin.deletetoken', ['id' => $tokenId]);
+ }
+
+ /**
+ * Process the request to delete a token.
+ *
+ * @param string The token id
+ * @return \Illuminate\View\Factory view
+ */
+ public function postDeleteToken($tokenId)
+ {
+ $this->tokenService->deleteToken($tokenId);
+
+ return view('admin.deletetokensuccess', ['id' => $tokenId]);
+ }
+}
diff --git a/app/Http/Controllers/WebMentionsController.php b/app/Http/Controllers/WebMentionsController.php
new file mode 100644
index 00000000..ccffd451
--- /dev/null
+++ b/app/Http/Controllers/WebMentionsController.php
@@ -0,0 +1,100 @@
+has('target') !== true) || ($request->has('source') !== true)) {
+ return new Response(
+ 'You need both the target and source parameters',
+ 400
+ );
+ }
+
+ //next check the $target is valid
+ $path = parse_url($request->input('target'))['path'];
+ $pathParts = explode('/', $path);
+
+ switch ($pathParts[1]) {
+ case 'notes':
+ //we have a note
+ $noteId = $pathParts[2];
+ $numbers = new Numbers();
+ $realId = $numbers->b60tonum($noteId);
+ try {
+ $note = Note::findOrFail($realId);
+ $this->dispatch(new ProcessWebMention($note, $request->input('source')));
+ } catch (ModelNotFoundException $e) {
+ return new Response('This note doesn’t exist.', 400);
+ }
+
+ return new Response(
+ 'Webmention received, it will be processed shortly',
+ 202
+ );
+ break;
+ case 'blog':
+ return new Response(
+ 'I don’t accept webmentions for blog posts yet.',
+ 501
+ );
+ break;
+ default:
+ return new Response(
+ 'Invalid request',
+ 400
+ );
+ break;
+ }
+ }
+
+ /**
+ * Send a webmention.
+ *
+ * @param \App\Note $note
+ * @return array An array of successful then failed URLs
+ */
+ public function send(Note $note)
+ {
+ //grab the URLs
+ $urlsInReplyTo = explode(' ', $note->in_reply_to);
+ $urlsNote = $this->getLinks($note->note);
+ $urls = array_filter(array_merge($urlsInReplyTo, $urlsNote)); //filter out none URLs
+ foreach ($urls as $url) {
+ $this->dispatch(new SendWebMentions($url, $note->longurl));
+ }
+ }
+
+ /**
+ * Get the URLs from a note.
+ */
+ private function getLinks($html)
+ {
+ $urls = [];
+ $dom = new \DOMDocument();
+ $dom->loadHTML($html);
+ $anchors = $dom->getElementsByTagName('a');
+ foreach ($anchors as $anchor) {
+ $urls[] = ($anchor->hasAttribute('href')) ? $anchor->getAttribute('href') : false;
+ }
+
+ return $urls;
+ }
+}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
new file mode 100644
index 00000000..ebad4d16
--- /dev/null
+++ b/app/Http/Kernel.php
@@ -0,0 +1,55 @@
+ [
+ \App\Http\Middleware\EncryptCookies::class,
+ \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+ \Illuminate\Session\Middleware\StartSession::class,
+ \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ \App\Http\Middleware\VerifyCsrfToken::class,
+ \App\Http\Middleware\LinkHeadersMiddleware::class,
+ ],
+
+ 'api' => [
+ 'throttle:60,1',
+ ],
+ ];
+
+ /**
+ * The application's route middleware.
+ *
+ * These middleware may be assigned to groups or used individually.
+ *
+ * @var array
+ */
+ protected $routeMiddleware = [
+ 'auth' => \App\Http\Middleware\Authenticate::class,
+ 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+ 'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
+ 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
+ 'myauth' => \App\Http\Middleware\MyAuthMiddleware::class,
+ 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+ ];
+}
diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
new file mode 100644
index 00000000..67abcaea
--- /dev/null
+++ b/app/Http/Middleware/Authenticate.php
@@ -0,0 +1,30 @@
+guest()) {
+ if ($request->ajax() || $request->wantsJson()) {
+ return response('Unauthorized.', 401);
+ } else {
+ return redirect()->guest('login');
+ }
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php
new file mode 100644
index 00000000..3aa15f8d
--- /dev/null
+++ b/app/Http/Middleware/EncryptCookies.php
@@ -0,0 +1,17 @@
+header('Link', '; rel="authorization_endpoint"', false);
+ $response->header('Link', config('app.url') . '/api/token>; rel="token_endpoint"', false);
+ $response->header('Link', config('app.url') . '/api/post>; rel="micropub"', false);
+ $response->header('Link', config('app.url') . '/webmention>; rel="webmention"', false);
+
+ return $response;
+ }
+}
diff --git a/app/Http/Middleware/MyAuthMiddleware.php b/app/Http/Middleware/MyAuthMiddleware.php
new file mode 100644
index 00000000..5354e55b
--- /dev/null
+++ b/app/Http/Middleware/MyAuthMiddleware.php
@@ -0,0 +1,25 @@
+session()->has('loggedin') !== true) {
+ //they’re not logged in, so send them to login form
+ return redirect()->route('login');
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php
new file mode 100644
index 00000000..e27860e2
--- /dev/null
+++ b/app/Http/Middleware/RedirectIfAuthenticated.php
@@ -0,0 +1,26 @@
+check()) {
+ return redirect('/');
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php
new file mode 100644
index 00000000..c8545185
--- /dev/null
+++ b/app/Http/Middleware/VerifyCsrfToken.php
@@ -0,0 +1,20 @@
+ config('url.longurl')], function () {
+ //Static homepage
+ Route::get('/', function () {
+ return view('homepage');
+ });
+
+ //Static project page
+ Route::get('projects', function () {
+ return view('projects');
+ });
+
+ //The login routes to get authe'd for admin
+ Route::get('login', ['as' => 'login', function () {
+ return view('login');
+ }]);
+ Route::post('login', 'AuthController@login');
+
+ //Admin pages grouped for filter
+ Route::group(['middleware' => 'myauth'], function () {
+ Route::get('admin', 'AdminController@showWelcome');
+
+ //Articles
+ Route::get('admin/blog/new', 'ArticlesAdminController@newArticle');
+ Route::get('admin/blog/edit', 'ArticlesAdminController@listArticles');
+ Route::get('admin/blog/edit/{id}', 'ArticlesAdminController@editArticle');
+ Route::get('admin/blog/delete/{id}', 'ArticlesAdminController@deleteArticle');
+ Route::post('admin/blog/new', 'ArticlesAdminController@postNewArticle');
+ Route::post('admin/blog/edit/{id}', 'ArticlesAdminController@postEditArticle');
+ Route::post('admin/blog/delete/{id}', 'ArticlesAdminController@postDeleteArticle');
+
+ //Notes
+ Route::get('admin/note/new', 'NotesAdminController@newNotePage');
+ Route::get('admin/note/edit', 'NotesAdminController@listNotesPage');
+ Route::get('admin/note/edit/{id}', 'NotesAdminController@editNotePage');
+ Route::post('admin/note/new', 'NotesAdminController@createNote');
+ Route::post('admin/note/edit/{id}', 'NotesAdminController@editNote');
+
+ //Tokens
+ Route::get('admin/tokens', 'TokensController@showTokens');
+ Route::get('admin/tokens/delete/{id}', 'TokensController@deleteToken');
+ Route::post('admin/tokens/delete/{id}', 'TokensController@postDeleteToken');
+
+ //Micropub Clients
+ Route::get('admin/clients', 'ClientsAdminController@listClients');
+ Route::get('admin/clients/new', 'ClientsAdminController@newClient');
+ Route::get('admin/clients/edit/{id}', 'ClientsAdminController@editClient');
+ Route::post('admin/clients/new', 'ClientsAdminController@postNewClient');
+ Route::post('admin/clients/edit/{id}', 'ClientsAdminController@postEditClient');
+
+ //Contacts
+ Route::get('admin/contacts/new', 'ContactsAdminController@newContact');
+ Route::get('admin/contacts/edit', 'ContactsAdminController@listContacts');
+ Route::get('admin/contacts/edit/{id}', 'ContactsAdminController@editContact');
+ Route::get('admin/contacts/edit/{id}/getavatar', 'ContactsAdminController@getAvatar');
+ Route::get('admin/contacts/delete/{id}', 'ContactsAdminController@deleteContact');
+ Route::post('admin/contacts/new', 'ContactsAdminController@postNewContact');
+ Route::post('admin/contacts/edit/{id}', 'ContactsAdminController@postEditContact');
+ Route::post('admin/contacts/delete/{id}', 'ContactsAdminController@postDeleteContact');
+
+ //Places
+ Route::get('admin/places/new', 'PlacesAdminController@newPlacePage');
+ Route::get('admin/places/edit', 'PlacesAdminController@listPlacesPage');
+ Route::get('admin/places/edit/{id}', 'PlacesAdminController@editPlacePage');
+ Route::post('admin/places/new', 'PlacesAdminController@createPlace');
+ Route::post('admin/places/edit/{id}', 'PlacesAdminController@editPlace');
+ });
+
+ //Blog pages using ArticlesController
+ Route::get('blog/s/{id}', 'ArticlesController@onlyIdInURL');
+ Route::get('blog/{year?}/{month?}', 'ArticlesController@showAllArticles');
+ Route::get('blog/{year}/{month}/{slug}', 'ArticlesController@singleArticle');
+
+ //micropub new notes page
+ //this needs to be first so `notes/new` doesn't match `notes/{id}`
+ Route::get('notes/new', 'MicropubClientController@newNotePage');
+ Route::post('notes/new', 'MicropubClientController@postNewNote');
+
+ //Notes pages using NotesController
+ Route::get('notes', 'NotesController@showNotes');
+ Route::get('note/{id}', 'NotesController@singleNoteRedirect');
+ Route::get('notes/{id}', 'NotesController@singleNote');
+ Route::get('notes/tagged/{tag}', 'NotesController@taggedNotes');
+
+ //indieauth
+ Route::any('beginauth', 'IndieAuthController@beginauth');
+ Route::get('indieauth', 'IndieAuthController@indieauth');
+ Route::post('api/token', 'IndieAuthController@tokenEndpoint');
+ Route::get('logout', 'IndieAuthController@indieauthLogout');
+
+ //micropub endoints
+ Route::post('api/post', 'MicropubController@post');
+ Route::get('api/post', 'MicropubController@getEndpoint');
+
+ //micropub refresh syndication targets
+ Route::get('refresh-syndication-targets', 'MicropubClientController@refreshSyndicationTargets');
+
+ //webmention
+ Route::get('webmention', function () {
+ return view('webmention-endpoint');
+ });
+ Route::post('webmention', 'WebMentionsController@receive');
+
+ //Contacts
+ Route::get('contacts', 'ContactsController@showAll');
+ Route::get('contacts/{nick}', 'ContactsController@showSingle');
+
+ //Places
+ Route::get('places', 'PlacesController@index');
+ Route::get('places/{slug}', 'PlacesController@show');
+ //Places micropub
+ Route::get('places/near/{lat}/{lng}', 'MicropubClientController@nearbyPlaces');
+ Route::post('places/new', 'MicropubClientController@postNewPlace');
+
+ Route::get('feed', 'ArticlesController@makeRSS');
+});
+
+//Short URL
+Route::group(['domain' => config('url.shorturl')], function () {
+ Route::get('/', 'ShortURLsController@baseURL');
+ Route::get('@', 'ShortURLsController@twitter');
+ Route::get('+', 'ShortURLsController@googlePlus');
+ Route::get('α', 'ShortURLsController@appNet');
+
+ Route::get('{type}/{id}', 'ShortURLsController@expandType')->where(
+ [
+ 'type' => '[bt]',
+ 'id' => '[0-9A-HJ-NP-Z_a-km-z]+',
+ ]
+ );
+
+ Route::get('h/{id}', 'ShortURLsController@redirect');
+ Route::get('{id}', 'ShortURLsController@oldRedirect')->where(
+ [
+ 'id' => '[0-9A-HJ-NP-Z_a-km-z]{4}',
+ ]
+ );
+});
diff --git a/app/Jobs/Job.php b/app/Jobs/Job.php
new file mode 100644
index 00000000..55ece29a
--- /dev/null
+++ b/app/Jobs/Job.php
@@ -0,0 +1,21 @@
+note = $note;
+ $this->source = $source;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @param \Jonnybarnes\WebmentionsParser\Parser $parser
+ * @return void
+ */
+ public function handle(Parser $parser)
+ {
+ $sourceURL = parse_url($this->source);
+ $baseURL = $sourceURL['scheme'] . '://' . $sourceURL['host'];
+ $remoteContent = $this->getRemoteContent($this->source);
+ $microformats = $this->parseHTML($remoteContent, $baseURL);
+ $count = WebMention::where('source', '=', $this->source)->count();
+ if ($count > 0) {
+ //we already have a webmention from this source
+ $webmentions = WebMention::where('source', '=', $this->source)->get();
+ foreach ($webmentions as $webmention) {
+ //now check it still 'mentions' this target
+ //we switch for each type of mention (reply/like/repost)
+ switch ($webmention->type) {
+ case 'reply':
+ if ($parser->checkInReplyTo($microformats, $note->longurl) == false) {
+ //it doesn't so delete
+ $webmention->delete();
+
+ return true;
+ }
+ //webmenion is still a reply, so update content
+ $content = $parser->replyContent($microformats);
+ $this->saveImage($content);
+ $content['reply'] = $this->filterHTML($content['reply']);
+ $content = serialize($content);
+ $webmention->content = $content;
+ $webmention->save();
+
+ return true;
+ break;
+ case 'like':
+ if ($parser->checkLikeOf($microformats, $note->longurl) == false) {
+ //it doesn't so delete
+ $webmention->delete();
+
+ return true;
+ } //note we don't need to do anything if it still is a like
+ break;
+ case 'repost':
+ if ($parser->checkRepostOf($microformats, $note->longurl) == false) {
+ //it doesn't so delete
+ $webmention->delete();
+
+ return true;
+ } //again, we don't need to do anything if it still is a repost
+ break;
+ }//switch
+ }//foreach
+ }//if
+ //no wemention in db so create new one
+ $webmention = new WebMention();
+ //check it is in fact a reply
+ if ($parser->checkInReplyTo($microformats, $note->longurl)) {
+ $content = $parser->replyContent($microformats);
+ $this->saveImage($content);
+ $content['reply'] = $this->filterHTML($content['reply']);
+ $content = serialize($content);
+ $webmention->source = $this->source;
+ $webmention->target = $note->longurl;
+ $webmention->commentable_id = $this->note->id;
+ $webmention->commentable_type = 'App\Note';
+ $webmention->type = 'reply';
+ $webmention->content = $content;
+ $webmention->save();
+
+ return true;
+ } elseif ($parser->checkLikeOf($microformats, $note->longurl)) {
+ //it is a like
+ $content = $parser->likeContent($microformats);
+ $this->saveImage($content);
+ $content = serialize($content);
+ $webmention->source = $this->source;
+ $webmention->target = $note->longurl;
+ $webmention->commentable_id = $this->note->id;
+ $webmention->commentable_type = 'App\Note';
+ $webmention->type = 'like';
+ $webmention->content = $content;
+ $webmention->save();
+
+ return true;
+ } elseif ($parser->checkRepostOf($microformats, $note->longurl)) {
+ //it is a repost
+ $content = $parser->repostContent($microformats);
+ $this->saveImage($content);
+ $content = serialize($content);
+ $webmention->source = $this->source;
+ $webmention->target = $note->longurl;
+ $webmention->commentable_id = $this->note->id;
+ $webmention->commentable_type = 'App\Note';
+ $webmention->type = 'repost';
+ $webmention->content = $content;
+ $webmention->save();
+
+ return true;
+ }
+ }
+
+ /**
+ * Retreive the remote content from a URL, and caches the result.
+ *
+ * @param string The URL to retreive content from
+ * @return string The HTML from the URL
+ */
+ private function getRemoteContent($url)
+ {
+ $client = new Client();
+
+ $response = $client->get($url);
+ $html = (string) $response->getBody();
+ $path = storage_path() . '/HTML/' . $this->createFilenameFromURL($url);
+ $this->fileForceContents($path, $html);
+
+ return $html;
+ }
+
+ /**
+ * Create a file path from a URL. This is used when caching the HTML
+ * response.
+ *
+ * @param string The URL
+ * @return string The path name
+ */
+ private function createFilenameFromURL($url)
+ {
+ $url = str_replace(['https://', 'http://'], ['', ''], $url);
+ if (substr($url, -1) == '/') {
+ $url = $url . 'index.html';
+ }
+
+ return $url;
+ }
+
+ /**
+ * Save a file, and create any necessary folders.
+ *
+ * @param string The directory to save to
+ * @param binary The file to save
+ */
+ private function fileForceContents($dir, $contents)
+ {
+ $parts = explode('/', $dir);
+ $name = array_pop($parts);
+ $dir = implode('/', $parts);
+ if (! is_dir($dir)) {
+ mkdir($dir, 0755, true);
+ }
+ file_put_contents("$dir/$name", $contents);
+ }
+
+ /**
+ * A wrapper function for php-mf2’s parse method.
+ *
+ * @param string The HTML to parse
+ * @param string The base URL to resolve relative URLs in the HTML against
+ * @return array The porcessed microformats
+ */
+ private function parseHTML($html, $baseurl)
+ {
+ $microformats = \Mf2\parse((string) $html, $baseurl);
+
+ return $microformats;
+ }
+
+ /**
+ * Save a profile image to the local cache.
+ *
+ * @param array source content
+ * @return bool wether image was saved or not (we don’t save twitter profiles)
+ */
+ public function saveImage(array $content)
+ {
+ $photo = $content['photo'];
+ $home = $content['url'];
+ //dont save pbs.twimg.com links
+ if (parse_url($photo)['host'] != 'pbs.twimg.com'
+ && parse_url($photo)['host'] != 'twitter.com') {
+ $client = new Client();
+ try {
+ $response = $client->get($photo);
+ $image = $response->getBody(true);
+ $path = public_path() . '/assets/profile-images/' . parse_url($home)['host'] . '/image';
+ $this->fileForceContents($path, $image);
+ } catch (Exception $e) {
+ // we are openning and reading the default image so that
+ // fileForceContent work
+ $default = public_path() . '/assets/profile-images/default-image';
+ $handle = fopen($default, 'rb');
+ $image = fread($handle, filesize($default));
+ fclose($handle);
+ $path = public_path() . '/assets/profile-images/' . parse_url($home)['host'] . '/image';
+ $this->fileForceContents($path, $image);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Purify HTML received from a webmention.
+ *
+ * @param string The HTML to be processed
+ * @return string The processed HTML
+ */
+ public function filterHTML($html)
+ {
+ $config = HTMLPurifier_Config::createDefault();
+ $config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier');
+ $purifier = new HTMLPurifier($config);
+
+ return $purifier->purify($html);
+ }
+}
diff --git a/app/Jobs/SendWebMentions.php b/app/Jobs/SendWebMentions.php
new file mode 100644
index 00000000..796fc97c
--- /dev/null
+++ b/app/Jobs/SendWebMentions.php
@@ -0,0 +1,86 @@
+url = $url;
+ $this->source = $source;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle(Client $client)
+ {
+ $endpoint = $this->discoverWebmentionEndpoint($this->url, $client);
+ if ($endpoint) {
+ $client->post($endpoint, [
+ 'form_params' => [
+ 'source' => $this->source,
+ 'target' => $this->url,
+ ],
+ ]);
+ }
+ }
+
+ /**
+ * Discover if a URL has a webmention endpoint.
+ *
+ * @param string The URL
+ * @param \GuzzleHttp\Client $client
+ * @return string The webmention endpoint URL
+ */
+ private function discoverWebmentionEndpoint($url, $client)
+ {
+ $endpoint = null;
+
+ $response = $client->get($url);
+ //check HTTP Headers for webmention endpoint
+ $links = \GuzzleHttp\Psr7\parse_header($response->getHeader('Link'));
+ foreach ($links as $link) {
+ if ($link['rel'] == 'webmention') {
+ return trim($link[0], '<>');
+ }
+ }
+
+ //failed to find a header so parse HTML
+ $html = (string) $response->getBody();
+
+ $mf2 = new \Mf2\Parser($html, $url);
+ $rels = $mf2->parseRelsAndAlternates();
+ if (array_key_exists('webmention', $rels[0])) {
+ $endpoint = $rels[0]['webmention'][0];
+ } elseif (array_key_exists('http://webmention.org/', $rels[0])) {
+ $endpoint = $rels[0]['http://webmention.org/'][0];
+ }
+ if ($endpoint) {
+ if (filter_var($endpoint, FILTER_VALIDATE_URL)) {
+ return $endpoint;
+ }
+ //it must be a relative url, so resolve with php-mf2
+ return $mf2->resolveUrl($endpoint);
+ }
+
+ return false;
+ }
+}
diff --git a/app/Jobs/SyndicateToTwitter.php b/app/Jobs/SyndicateToTwitter.php
new file mode 100644
index 00000000..377932f7
--- /dev/null
+++ b/app/Jobs/SyndicateToTwitter.php
@@ -0,0 +1,108 @@
+note = $note;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @param \Jonnybarnes\IndieWeb\Numbers $numbers
+ * @param \Jonnybarnes\IndieWeb\NotePrep $noteprep
+ * @return void
+ */
+ public function handle(Numbers $numbers, NotePrep $noteprep)
+ {
+ $noteSwappedNames = $this->swapNames($this->note->getOriginal('note'));
+ $shorturl = 'https://' . config('url.shorturl') . '/t/' . $numbers->numto60($this->note->id);
+ $tweet = $noteprep->createNote($noteSwappedNames, $shorturl, 140, true);
+ $tweetOpts = ['status' => $tweet, 'format' => 'json'];
+ if ($this->note->in_reply_to) {
+ $tweetOpts['in_reply_to_status_id'] = $noteprep->replyTweetId($this->note->in_reply_to);
+ }
+
+ /*if ($this->note->location) {
+ $explode = explode(':', $this->note->location);
+ $location = (count($explode) == 2) ? explode(',', $explode[0]) : explode(',', $explode);
+ $lat = trim($location[0]);
+ $long = trim($location[1]);
+ $jsonPlaceId = Twitter::getGeoReverse(array('lat' => $lat, 'long' => $long, 'format' => 'json'));
+ $parsePlaceId = json_decode($jsonPlaceId);
+ $placeId = $parsePlaceId->result->places[0]->id ?: null;
+ $tweetOpts['lat'] = $lat;
+ $tweetOpts['long'] = $long;
+ if ($placeId) {
+ $tweetOpts['place_id'] = $placeId;
+ }
+ }*/
+
+ $mediaItems = $this->note->getMedia();
+ if (count($mediaItems) > 0) {
+ foreach ($mediaItems as $item) {
+ $uploadedMedia = Twitter::uploadMedia(['media' => file_get_contents($item->getUrl())]);
+ $mediaIds[] = $uploadedMedia->media_id_string;
+ }
+ $tweetOpts['media_ids'] = implode(',', $mediaIds);
+ }
+
+ $responseJson = Twitter::postTweet($tweetOpts);
+ $response = json_decode($responseJson);
+ $tweetId = $response->id;
+ $this->note->tweet_id = $tweetId;
+ $this->note->save();
+ }
+
+ /**
+ * Swap @names in a note.
+ *
+ * When a note is being saved and we are posting it to twitter, we want
+ * to swap our @local_name to Twitter’s @twitter_name so the user get’s
+ * mentioned on Twitter.
+ *
+ * @param string $note
+ * @return string $noteSwappedNames
+ */
+ private function swapNames($note)
+ {
+ $regex = '/\[.*?\](*SKIP)(*F)|@(\w+)/'; //match @alice but not [@bob](...)
+ $noteSwappedNames = preg_replace_callback(
+ $regex,
+ function ($matches) {
+ try {
+ $contact = Contact::where('nick', '=', mb_strtolower($matches[1]))->firstOrFail();
+ } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
+ return '@' . $matches[1];
+ }
+ $twitterHandle = $contact->twitter;
+
+ return '@' . $twitterHandle;
+ },
+ $note
+ );
+
+ return $noteSwappedNames;
+ }
+}
diff --git a/app/Listeners/.gitkeep b/app/Listeners/.gitkeep
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/app/Listeners/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/app/Note.php b/app/Note.php
new file mode 100644
index 00000000..29213e14
--- /dev/null
+++ b/app/Note.php
@@ -0,0 +1,230 @@
+belongsToMany('App\Tag');
+ }
+
+ /**
+ * Define the relationship with webmentions.
+ *
+ * @var array
+ */
+ public function webmentions()
+ {
+ return $this->morphMany('App\WebMention', 'commentable');
+ }
+
+ /**
+ * Definte the relationship with places.
+ *
+ * @var array
+ */
+ public function place()
+ {
+ return $this->belongsTo('App\Place');
+ }
+
+ /**
+ * We shall set a blacklist of non-modifiable model attributes.
+ *
+ * @var array
+ */
+ protected $guarded = ['id'];
+
+ /**
+ * The attributes that should be mutated to dates.
+ *
+ * @var array
+ */
+ protected $dates = ['deleted_at'];
+
+ /**
+ * A mutator to ensure that in-reply-to is always non-empty or null.
+ *
+ * @param string value
+ * @return string
+ */
+ public function setInReplyToAttribute($value)
+ {
+ $this->attributes['in_reply_to'] = empty($value) ? null : $value;
+ }
+
+ /**
+ * Normalize the note to Unicode FORM C.
+ *
+ * @param string $value
+ * @return string
+ */
+ public function setNoteAttribute($value)
+ {
+ $this->attributes['note'] = normalizer_normalize($value, Normalizer::FORM_C);
+ }
+
+ /**
+ * Pre-process notes for web-view.
+ *
+ * @param string
+ * @return string
+ */
+ public function getNoteAttribute($value)
+ {
+ $unicode = new UnicodeTools();
+ $codepoints = $unicode->convertUnicodeCodepoints($value);
+ $markdown = new CommonMarkConverter();
+ $html = $markdown->convertToHtml($codepoints);
+ $hcards = $this->makeHCards($html);
+ $hashtags = $this->autoLinkHashtag($hcards);
+
+ return $hashtags;
+ }
+
+ /**
+ * Generate the NewBase60 ID from primary ID.
+ *
+ * @return string
+ */
+ public function getNb60idAttribute()
+ {
+ $numbers = new Numbers();
+
+ return $numbers->numto60($this->id);
+ }
+
+ /**
+ * The Long URL for a note.
+ *
+ * @return string
+ */
+ public function getLongurlAttribute()
+ {
+ return config('app.url') . '/notes/' . $this->nb60id;
+ }
+
+ /**
+ * The Short URL for a note.
+ *
+ * @return string
+ */
+ public function getShorturlAttribute()
+ {
+ return config('app.shorturl') . '/notes/' . $this->nb60id;
+ }
+
+ /**
+ * Get the relavent client name assocaited with the client id.
+ *
+ * @return string|null
+ */
+ public function getClientNameAttribute()
+ {
+ if ($this->client_id == null) {
+ return;
+ }
+ $name = Client::where('client_url', $this->client_id)->value('client_name');
+ if ($name == null) {
+ $url = parse_url($this->client_id);
+ if (isset($url['path'])) {
+ return $url['host'] . $url['path'];
+ }
+
+ return $url['host'];
+ }
+
+ return $name;
+ }
+
+ /**
+ * 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
+ * due to lack of contact info, we assume @username is a twitter handle and link it
+ * as such.
+ *
+ * @param string The note’s text
+ * @return string
+ */
+ private function makeHCards($text)
+ {
+ $regex = '/\[.*?\](*SKIP)(*F)|@(\w+)/'; //match @alice but not [@bob](...)
+ $hcards = preg_replace_callback(
+ $regex,
+ function ($matches) {
+ try {
+ $contact = Contact::where('nick', '=', mb_strtolower($matches[1]))->firstOrFail();
+ } catch (ModelNotFoundException $e) {
+ return '' . $matches[0] . '';
+ }
+ $path = parse_url($contact->homepage)['host'];
+ $contact->photo = (file_exists(public_path() . '/assets/profile-images/' . $path . '/image')) ?
+ '/assets/profile-images/' . $path . '/image'
+ :
+ '/assets/profile-images/default-image';
+
+ return trim(view('mini-hcard-template', ['contact' => $contact])->render());
+ },
+ $text
+ );
+
+ return $hcards;
+ }
+
+ /**
+ * Given a string and section, finds all hashtags matching
+ * `#[\-_a-zA-Z0-9]+` and wraps them in an `a` element with
+ * `rel=tag` set and a `href` of 'section/tagged/' + tagname without the #.
+ *
+ * @param string The note
+ * @return string
+ */
+ private function autoLinkHashtag($text)
+ {
+ // $replacements = ["#tag" => "#tag]
+ $replacements = [];
+ $matches = [];
+
+ if (preg_match_all('/(?<=^|\s)\#([a-zA-Z0-9\-\_]+)/i', $text, $matches, PREG_PATTERN_ORDER)) {
+ // Look up #tags, get Full name and URL
+ foreach ($matches[0] as $name) {
+ $name = str_replace('#', '', $name);
+ $replacements[$name] =
+ '#' . $name . '';
+ }
+
+ // Replace #tags with valid microformat-enabled link
+ foreach ($replacements as $name => $replacement) {
+ $text = str_replace('#' . $name, $replacement, $text);
+ }
+ }
+
+ return $text;
+ }
+}
diff --git a/app/Place.php b/app/Place.php
new file mode 100644
index 00000000..8cadf966
--- /dev/null
+++ b/app/Place.php
@@ -0,0 +1,133 @@
+hasMany('App\Note');
+ }
+
+ /**
+ * Get all places within a specified distance.
+ *
+ * @param float latitude
+ * @param float longitude
+ * @param int maximum distance
+ * @todo Check this shit.
+ */
+ public static function near(float $lat, float $lng, int $distance)
+ {
+ $point = $lng . ' ' . $lat;
+ $distace = $distance ?? 1000;
+ $places = DB::select(DB::raw("select
+ name,
+ slug,
+ ST_AsText(location) AS location,
+ ST_Distance(
+ ST_GeogFromText('SRID=4326;POINT($point)'),
+ location
+ ) AS distance
+ from places
+ where ST_DWithin(
+ ST_GeogFromText('SRID=4326;POINT($point)'),
+ location,
+ $distance
+ ) ORDER BY distance"));
+
+ return $places;
+ }
+
+ /**
+ * Convert location to text.
+ *
+ * @param text $value
+ * @return text
+ */
+ public function getLocationAttribute($value)
+ {
+ $result = DB::select(DB::raw("SELECT ST_AsText('$value')"));
+
+ return $result[0]->st_astext;
+ }
+
+ /**
+ * Get the latitude from the `location` property.
+ *
+ * @return string latitude
+ */
+ public function getLatitudeAttribute()
+ {
+ preg_match('/\((.*)\)/', $this->location, $latlng);
+
+ return explode(' ', $latlng[1])[1];
+ }
+
+ /**
+ * Get the longitude from the `location` property.
+ *
+ * @return string longitude
+ */
+ public function getLongitudeAttribute()
+ {
+ preg_match('/\((.*)\)/', $this->location, $latlng);
+
+ return explode(' ', $latlng[1])[0];
+ }
+
+ /**
+ * The Long URL for a place.
+ *
+ * @return string
+ */
+ public function getLongurlAttribute()
+ {
+ return config('app.url') . '/places/' . $this->slug;
+ }
+
+ /**
+ * The Short URL for a place.
+ *
+ * @return string
+ */
+ public function getShorturlAttribute()
+ {
+ return config('app.shorturl') . '/places/' . $this->slug;
+ }
+}
diff --git a/app/Policies/.gitkeep b/app/Policies/.gitkeep
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/app/Policies/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
new file mode 100644
index 00000000..76b7af57
--- /dev/null
+++ b/app/Providers/AppServiceProvider.php
@@ -0,0 +1,57 @@
+getSize() > 5000000) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ });
+
+ //Add tags for notes
+ Note::created(function ($note) {
+ $noteprep = new NotePrep();
+ $tagsToAdd = [];
+ $tags = $noteprep->getTags($note->note);
+ foreach ($tags as $text) {
+ $tag = Tag::firstOrCreate(['tag' => $text]);
+ $tagsToAdd[] = $tag->id;
+ }
+ if (count($tagsToAdd > 0)) {
+ $note->tags()->attach($tagsToAdd);
+ }
+ });
+ }
+
+ /**
+ * Register any application services.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ //
+ }
+}
diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php
new file mode 100644
index 00000000..57d88ea3
--- /dev/null
+++ b/app/Providers/AuthServiceProvider.php
@@ -0,0 +1,31 @@
+ 'App\Policies\ModelPolicy',
+ ];
+
+ /**
+ * Register any application authentication / authorization services.
+ *
+ * @param \Illuminate\Contracts\Auth\Access\Gate $gate
+ * @return void
+ */
+ public function boot(GateContract $gate)
+ {
+ $this->registerPolicies($gate);
+
+ //
+ }
+}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
new file mode 100644
index 00000000..58ce9624
--- /dev/null
+++ b/app/Providers/EventServiceProvider.php
@@ -0,0 +1,33 @@
+ [
+ 'App\Listeners\EventListener',
+ ],
+ ];
+
+ /**
+ * Register any other events for your application.
+ *
+ * @param \Illuminate\Contracts\Events\Dispatcher $events
+ * @return void
+ */
+ public function boot(DispatcherContract $events)
+ {
+ parent::boot($events);
+
+ //
+ }
+}
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
new file mode 100644
index 00000000..bde08819
--- /dev/null
+++ b/app/Providers/RouteServiceProvider.php
@@ -0,0 +1,61 @@
+mapWebRoutes($router);
+
+ //
+ }
+
+ /**
+ * Define the "web" routes for the application.
+ *
+ * These routes all receive session state, CSRF protection, etc.
+ *
+ * @param \Illuminate\Routing\Router $router
+ * @return void
+ */
+ protected function mapWebRoutes(Router $router)
+ {
+ $router->group([
+ 'namespace' => $this->namespace, 'middleware' => 'web',
+ ], function ($router) {
+ require app_path('Http/routes.php');
+ });
+ }
+}
diff --git a/app/Services/IndieAuthService.php b/app/Services/IndieAuthService.php
new file mode 100644
index 00000000..cc7c775c
--- /dev/null
+++ b/app/Services/IndieAuthService.php
@@ -0,0 +1,113 @@
+discoverAuthorizationEndpoint($client->normalizeMeURL($domain));
+ }
+
+ /**
+ * Given an authorization endpoint, build the appropriate authorization URL.
+ *
+ * @param string $authEndpoint
+ * @param string $domain
+ * @param \IndieAuth\Client $client
+ * @return string
+ */
+ public function buildAuthorizationURL($authEndpoint, $domain, $client)
+ {
+ $domain = $client->normalizeMeURL($domain);
+ $state = bin2hex(openssl_random_pseudo_bytes(16));
+ session(['state' => $state]);
+ $redirectURL = config('app.url') . '/indieauth';
+ $clientId = config('app.url') . '/notes/new';
+ $scope = 'post';
+ $authorizationURL = $client->buildAuthorizationURL(
+ $authEndpoint,
+ $domain,
+ $redirectURL,
+ $clientId,
+ $state,
+ $scope
+ );
+
+ return $authorizationURL;
+ }
+
+ /**
+ * Discover the token endpoint for a given domain.
+ *
+ * @param string The domain
+ * @param \IndieAuth\Client $client
+ * @return string|null
+ */
+ public function getTokenEndpoint($domain, $client)
+ {
+ return $client->discoverTokenEndpoint($domain);
+ }
+
+ /**
+ * Retrieve a token from the token endpoint.
+ *
+ * @param array The relavent data
+ * @param \IndieAuth\Client $client
+ * @return array
+ */
+ public function getAccessToken(array $data, $client)
+ {
+ return $client->getAccessToken(
+ $data['endpoint'],
+ $data['code'],
+ $data['me'],
+ $data['redirect_url'],
+ $data['client_id'],
+ $data['state']
+ );
+ }
+
+ /**
+ * Determine the Authorization endpoint, then verify the suplied code is
+ * valid.
+ *
+ * @param array The data.
+ * @param \IndieAuth\Client $client
+ * @return array|null
+ */
+ public function verifyIndieAuthCode(array $data, $client)
+ {
+ $authEndpoint = $client->discoverAuthorizationEndpoint($data['me']);
+ if ($authEndpoint) {
+ return $client->verifyIndieAuthCode(
+ $authEndpoint,
+ $data['code'],
+ $data['me'],
+ $data['redirect_url'],
+ $data['client_id'],
+ $data['state']
+ );
+ }
+ }
+
+ /**
+ * Determine the micropub endpoint.
+ *
+ * @param string $domain
+ * @param \IndieAuth\Client $client
+ * @return string The endpoint
+ */
+ public function discoverMicropubEndpoint($domain, $client)
+ {
+ return $client->discoverMicropubEndpoint($client->normalizeMeURL($domain));
+ }
+}
diff --git a/app/Services/NoteService.php b/app/Services/NoteService.php
new file mode 100644
index 00000000..61b970e0
--- /dev/null
+++ b/app/Services/NoteService.php
@@ -0,0 +1,67 @@
+ $request->input('content'),
+ 'in_reply_to' => $request->input('in-reply-to'),
+ 'client_id' => $clientId,
+ ]
+ );
+
+ $placeSlug = $request->input('location');
+ if ($placeSlug !== null && $placeSlug !== 'no-location') {
+ $place = Place::where('slug', '=', $placeSlug)->first();
+ $note->place()->associate($place);
+ $note->save();
+ }
+
+ //add images to media library
+ if ($request->hasFile('photo')) {
+ $files = $request->file('photo');
+ foreach ($files as $file) {
+ $note->addMedia($file)->toMediaLibraryOnDisk('images', 's3');
+ }
+ }
+
+ if ($request->input('webmentions')) {
+ $wmc = new WebMentionsController();
+ $wmc->send($note);
+ }
+
+ if (//micropub request, syndication sent as array
+ (is_array($request->input('mp-syndicate-to'))
+ &&
+ (in_array('twitter.com/jonnybarnes', $request->input('mp-syndicate-to')))
+ || //micropub request, syndication sent as string
+ ($request->input('mp-syndicate-to') == 'twitter.com/jonnybarnes')
+ || //local admin cp request
+ ($request->input('twitter') == true))
+ ) {
+ $this->dispatch(new SyndicateToTwitter($note));
+ }
+
+ return $note;
+ }
+}
diff --git a/app/Services/PlaceService.php b/app/Services/PlaceService.php
new file mode 100644
index 00000000..b4f737af
--- /dev/null
+++ b/app/Services/PlaceService.php
@@ -0,0 +1,39 @@
+input('geo') !== null) {
+ $parts = explode(':', $request->input('geo'));
+ $latlng = explode(',', $parts[1]);
+ $latitude = $latlng[0];
+ $longitude = $latlng[1];
+ }
+ if ($request->input('latitude') !== null) {
+ $latitude = $request->input('latitude');
+ $longitude = $request->input('longitude');
+ }
+ $place = new Place();
+ $place->name = $request->input('name');
+ $place->description = $request->input('description');
+ $place->location = new Point((float) $latitude, (float) $longitude);
+ $place->save();
+
+ return $place;
+ }
+}
diff --git a/app/Services/TokenService.php b/app/Services/TokenService.php
new file mode 100644
index 00000000..4652d30b
--- /dev/null
+++ b/app/Services/TokenService.php
@@ -0,0 +1,54 @@
+set('me', $data['me'])
+ ->set('client_id', $data['client_id'])
+ ->set('scope', $data['scope'])
+ ->set('date_issued', time())
+ ->set('nonce', bin2hex(random_bytes(8)))
+ ->sign($signer, env('APP_KEY'))
+ ->getToken();
+
+ return $token;
+ }
+
+ /**
+ * Check the token signature is valid.
+ *
+ * @param string The token
+ * @return mixed
+ */
+ public function validateToken($token)
+ {
+ $signer = new Sha256();
+ try {
+ $token = (new Parser())->parse((string) $token);
+ } catch (InvalidArgumentException $e) {
+ return;
+ } catch (RuntimeException $e) {
+ return;
+ }
+ if ($token->verify($signer, env('APP_KEY'))) {
+ //signuture valid
+ return $token;
+ }
+ }
+}
diff --git a/app/Tag.php b/app/Tag.php
new file mode 100644
index 00000000..fb55663a
--- /dev/null
+++ b/app/Tag.php
@@ -0,0 +1,39 @@
+belongsToMany('App\Note');
+ }
+
+ /**
+ * The attributes excluded from the model's JSON form.
+ *
+ * @var array
+ */
+ protected $hidden = ['deleted'];
+
+ /**
+ * We shall set a blacklist of non-modifiable model attributes.
+ *
+ * @var array
+ */
+ protected $guarded = ['id'];
+}
diff --git a/app/User.php b/app/User.php
new file mode 100644
index 00000000..75741ae4
--- /dev/null
+++ b/app/User.php
@@ -0,0 +1,26 @@
+morphTo();
+ }
+
+ /**
+ * We shall set a blacklist of non-modifiable model attributes.
+ *
+ * @var array
+ */
+ protected $guarded = ['id'];
+}
diff --git a/artisan b/artisan
new file mode 100755
index 00000000..df630d0d
--- /dev/null
+++ b/artisan
@@ -0,0 +1,51 @@
+#!/usr/bin/env php
+make(Illuminate\Contracts\Console\Kernel::class);
+
+$status = $kernel->handle(
+ $input = new Symfony\Component\Console\Input\ArgvInput,
+ new Symfony\Component\Console\Output\ConsoleOutput
+);
+
+/*
+|--------------------------------------------------------------------------
+| Shutdown The Application
+|--------------------------------------------------------------------------
+|
+| Once Artisan has finished running. We will fire off the shutdown events
+| so that any final work may be done by the application before we shut
+| down the process. This is the last thing to happen to the request.
+|
+*/
+
+$kernel->terminate($input, $status);
+
+exit($status);
diff --git a/bootstrap/app.php b/bootstrap/app.php
new file mode 100644
index 00000000..f2801adf
--- /dev/null
+++ b/bootstrap/app.php
@@ -0,0 +1,55 @@
+singleton(
+ Illuminate\Contracts\Http\Kernel::class,
+ App\Http\Kernel::class
+);
+
+$app->singleton(
+ Illuminate\Contracts\Console\Kernel::class,
+ App\Console\Kernel::class
+);
+
+$app->singleton(
+ Illuminate\Contracts\Debug\ExceptionHandler::class,
+ App\Exceptions\Handler::class
+);
+
+/*
+|--------------------------------------------------------------------------
+| Return The Application
+|--------------------------------------------------------------------------
+|
+| This script returns the application instance. The instance is given to
+| the calling script so we can separate the building of the instances
+| from the actual running of the application and sending responses.
+|
+*/
+
+return $app;
diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php
new file mode 100644
index 00000000..38301379
--- /dev/null
+++ b/bootstrap/autoload.php
@@ -0,0 +1,34 @@
+"
+ ],
+ "license": "CC0-1.0",
+ "homepage": "https://github.com/jonnybarnes/jbl5",
+ "moduleType": [],
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "test",
+ "tests"
+ ],
+ "dependencies": {
+ "fetch": "~0.11.0",
+ "alertify.js": "alertifyjs#~1.0.5",
+ "store2": "~2.3.2",
+ "Autolinker.js": "~0.24.0",
+ "marked": "~0.3.5",
+ "sanitize-css": "^3.2.0"
+ }
+}
diff --git a/changelog.md b/changelog.md
new file mode 100644
index 00000000..9ff53f79
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,4 @@
+# Changelog
+
+## Version 0.0.1 (2016-05-25)
+Initial release
diff --git a/composer.json b/composer.json
new file mode 100644
index 00000000..f9969344
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,74 @@
+{
+ "name": "jonnybarnes/jonnybarnes.uk",
+ "description": "The code for jonnybanres.uk, based on Laravel 5.2",
+ "keywords": ["framework", "laravel", "indieweb"],
+ "license": "CC0-1.0",
+ "type": "project",
+ "require": {
+ "ext-intl": "*",
+ "php": ">=7.0.0",
+ "laravel/framework": "5.2.*",
+ "jonnybarnes/unicode-tools": "dev-master",
+ "jonnybarnes/indieweb": "dev-master",
+ "jonnybarnes/webmentions-parser": "dev-master",
+ "guzzlehttp/guzzle": "~6.0",
+ "predis/predis": "~1.0",
+ "thujohn/twitter": "~2.0",
+ "mf2/mf2": "~0.3",
+ "martinbean/laravel-sluggable-trait": "0.2.*",
+ "indieauth/client": "~0.1",
+ "ezyang/htmlpurifier": "~4.6",
+ "league/commonmark": "^0.13.0",
+ "spatie/laravel-medialibrary": "^3.5",
+ "league/flysystem-aws-s3-v3": "^1.0",
+ "phaza/laravel-postgis": "dev-master",
+ "lcobucci/jwt": "^3.1"
+ },
+ "require-dev": {
+ "fzaninotto/faker": "~1.4",
+ "mockery/mockery": "0.9.*",
+ "phpunit/phpunit": "~4.0",
+ "symfony/css-selector": "2.8.*|3.0.*",
+ "symfony/dom-crawler": "2.8.*|3.0.*",
+ "barryvdh/laravel-debugbar": "~2.0",
+ "filp/whoops": "~2.0"
+ },
+ "repositories": [
+ {
+ "type": "vcs",
+ "url": "https://github.com/njbarrett/laravel-postgis"
+ }
+ ],
+ "autoload": {
+ "classmap": [
+ "database"
+ ],
+ "psr-4": {
+ "App\\": "app/"
+ }
+ },
+ "autoload-dev": {
+ "classmap": [
+ "tests/TestCase.php"
+ ]
+ },
+ "scripts": {
+ "post-root-package-install": [
+ "php -r \"copy('.env.example', '.env');\""
+ ],
+ "post-create-project-cmd": [
+ "php artisan key:generate"
+ ],
+ "post-install-cmd": [
+ "Illuminate\\Foundation\\ComposerScripts::postInstall",
+ "php artisan optimize"
+ ],
+ "post-update-cmd": [
+ "Illuminate\\Foundation\\ComposerScripts::postUpdate",
+ "php artisan optimize"
+ ]
+ },
+ "config": {
+ "preferred-install": "dist"
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 00000000..906cbbba
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,5039 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "daf7804a781d9e4f7de63ae2da8dee9e",
+ "content-hash": "59a5ff5c293a0de8dbc63e7b8f0cf055",
+ "packages": [
+ {
+ "name": "anahkiasen/underscore-php",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Anahkiasen/underscore-php.git",
+ "reference": "48f97b295c82d99c1fe10d8b0684c43f051b5580"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Anahkiasen/underscore-php/zipball/48f97b295c82d99c1fe10d8b0684c43f051b5580",
+ "reference": "48f97b295c82d99c1fe10d8b0684c43f051b5580",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/inflector": "^1.0",
+ "patchwork/utf8": "^1.2",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "fabpot/php-cs-fixer": "2.0.*@dev",
+ "phpunit/phpunit": "^4.6"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Underscore\\": [
+ "src",
+ "tests"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Maxime Fabre",
+ "email": "ehtnam6@gmail.com"
+ }
+ ],
+ "description": "A redacted port of Underscore.js for PHP",
+ "keywords": [
+ "internals",
+ "laravel",
+ "toolkit"
+ ],
+ "time": "2015-05-16 19:24:58"
+ },
+ {
+ "name": "aws/aws-sdk-php",
+ "version": "3.18.12",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/aws/aws-sdk-php.git",
+ "reference": "12386ad98e3a76df29cee9475264b7364a50542f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/12386ad98e3a76df29cee9475264b7364a50542f",
+ "reference": "12386ad98e3a76df29cee9475264b7364a50542f",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "~5.3|~6.0.1|~6.1",
+ "guzzlehttp/promises": "~1.0",
+ "guzzlehttp/psr7": "~1.0",
+ "mtdowling/jmespath.php": "~2.2",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "andrewsville/php-token-reflection": "^1.4",
+ "aws/aws-php-sns-message-validator": "~1.0",
+ "behat/behat": "~3.0",
+ "doctrine/cache": "~1.4",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-openssl": "*",
+ "ext-pcre": "*",
+ "ext-simplexml": "*",
+ "ext-spl": "*",
+ "nette/neon": "^2.3",
+ "phpunit/phpunit": "~4.0|~5.0",
+ "psr/cache": "^1.0"
+ },
+ "suggest": {
+ "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
+ "doctrine/cache": "To use the DoctrineCacheAdapter",
+ "ext-curl": "To send requests using cURL",
+ "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Aws\\": "src/"
+ },
+ "files": [
+ "src/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Amazon Web Services",
+ "homepage": "http://aws.amazon.com"
+ }
+ ],
+ "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
+ "homepage": "http://aws.amazon.com/sdkforphp",
+ "keywords": [
+ "amazon",
+ "aws",
+ "cloud",
+ "dynamodb",
+ "ec2",
+ "glacier",
+ "s3",
+ "sdk"
+ ],
+ "time": "2016-05-20 05:19:30"
+ },
+ {
+ "name": "barnabywalters/mf-cleaner",
+ "version": "v0.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/barnabywalters/php-mf-cleaner.git",
+ "reference": "ef6a16628db6e8aee2b4f8bb8093d18c24b74cd4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/barnabywalters/php-mf-cleaner/zipball/ef6a16628db6e8aee2b4f8bb8093d18c24b74cd4",
+ "reference": "ef6a16628db6e8aee2b4f8bb8093d18c24b74cd4",
+ "shasum": ""
+ },
+ "require-dev": {
+ "php": ">=5.3",
+ "phpunit/phpunit": "*"
+ },
+ "suggest": {
+ "mf2/mf2": "To parse microformats2 structures from (X)HTML"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/BarnabyWalters/Mf2/Functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Barnaby Walters",
+ "email": "barnaby@waterpigs.co.uk"
+ }
+ ],
+ "description": "Cleans up microformats2 array structures",
+ "time": "2014-10-06 23:11:15"
+ },
+ {
+ "name": "bosnadev/database",
+ "version": "0.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Bosnadev/Database.git",
+ "reference": "c2748d118415d30ce69b792448689285d01ffdb9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Bosnadev/Database/zipball/c2748d118415d30ce69b792448689285d01ffdb9",
+ "reference": "c2748d118415d30ce69b792448689285d01ffdb9",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/database": "~5.0",
+ "php": ">=5.4",
+ "ramsey/uuid": "~3.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "~2.5",
+ "mockery/mockery": "0.9.*",
+ "php": ">=5.4",
+ "phpunit/phpunit": "~4.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Bosnadev\\Database\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "authors": [
+ {
+ "name": "Mirza Pasic",
+ "email": "mirza.pasic@edu.fit.ba"
+ },
+ {
+ "name": "Peter Haza",
+ "email": "peter.haza@gmail.com"
+ }
+ ],
+ "description": "Eloquent Extended, added some PostgreSql features",
+ "keywords": [
+ "database",
+ "eloquent",
+ "laravel",
+ "mysql",
+ "postgresql"
+ ],
+ "time": "2016-04-27 15:18:36"
+ },
+ {
+ "name": "classpreloader/classpreloader",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ClassPreloader/ClassPreloader.git",
+ "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/9b10b913c2bdf90c3d2e0d726b454fb7f77c552a",
+ "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^1.0|^2.0",
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "ClassPreloader\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com"
+ },
+ {
+ "name": "Graham Campbell",
+ "email": "graham@alt-three.com"
+ }
+ ],
+ "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case",
+ "keywords": [
+ "autoload",
+ "class",
+ "preload"
+ ],
+ "time": "2015-11-09 22:51:51"
+ },
+ {
+ "name": "dnoegel/php-xdg-base-dir",
+ "version": "0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dnoegel/php-xdg-base-dir.git",
+ "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a",
+ "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "@stable"
+ },
+ "type": "project",
+ "autoload": {
+ "psr-4": {
+ "XdgBaseDir\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "implementation of xdg base directory specification for php",
+ "time": "2014-10-24 07:27:01"
+ },
+ {
+ "name": "doctrine/inflector",
+ "version": "v1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/inflector.git",
+ "reference": "90b2128806bfde671b6952ab8bea493942c1fdae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae",
+ "reference": "90b2128806bfde671b6952ab8bea493942c1fdae",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Doctrine\\Common\\Inflector\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Borschel",
+ "email": "roman@code-factory.org"
+ },
+ {
+ "name": "Benjamin Eberlei",
+ "email": "kontakt@beberlei.de"
+ },
+ {
+ "name": "Guilherme Blanco",
+ "email": "guilhermeblanco@gmail.com"
+ },
+ {
+ "name": "Jonathan Wage",
+ "email": "jonwage@gmail.com"
+ },
+ {
+ "name": "Johannes Schmitt",
+ "email": "schmittjoh@gmail.com"
+ }
+ ],
+ "description": "Common String Manipulations with regard to casing and singular/plural rules.",
+ "homepage": "http://www.doctrine-project.org",
+ "keywords": [
+ "inflection",
+ "pluralize",
+ "singularize",
+ "string"
+ ],
+ "time": "2015-11-06 14:35:42"
+ },
+ {
+ "name": "ezyang/htmlpurifier",
+ "version": "v4.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ezyang/htmlpurifier.git",
+ "reference": "ae1828d955112356f7677c465f94f7deb7d27a40"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/ae1828d955112356f7677c465f94f7deb7d27a40",
+ "reference": "ae1828d955112356f7677c465f94f7deb7d27a40",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "HTMLPurifier": "library/"
+ },
+ "files": [
+ "library/HTMLPurifier.composer.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL"
+ ],
+ "authors": [
+ {
+ "name": "Edward Z. Yang",
+ "email": "admin@htmlpurifier.org",
+ "homepage": "http://ezyang.com"
+ }
+ ],
+ "description": "Standards compliant HTML filter written in PHP",
+ "homepage": "http://htmlpurifier.org/",
+ "keywords": [
+ "html"
+ ],
+ "time": "2015-08-05 01:03:42"
+ },
+ {
+ "name": "geo-io/interface",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/geo-io/interface.git",
+ "reference": "cdbb55801e3f8d5485227c2031cc7a3c16ccd06a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/geo-io/interface/zipball/cdbb55801e3f8d5485227c2031cc7a3c16ccd06a",
+ "reference": "cdbb55801e3f8d5485227c2031cc7a3c16ccd06a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GeoIO\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com"
+ }
+ ],
+ "description": "Geo I/O base interfaces.",
+ "keywords": [
+ "geo",
+ "geometry",
+ "io"
+ ],
+ "time": "2015-04-17 18:52:52"
+ },
+ {
+ "name": "geo-io/wkb-parser",
+ "version": "v1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/geo-io/wkb-parser.git",
+ "reference": "cceee8f4e8b2058f3f1a0372c930140f23fe1ee1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/geo-io/wkb-parser/zipball/cceee8f4e8b2058f3f1a0372c930140f23fe1ee1",
+ "reference": "cceee8f4e8b2058f3f1a0372c930140f23fe1ee1",
+ "shasum": ""
+ },
+ "require": {
+ "geo-io/interface": "~1.0",
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "mockery/mockery": "~0.9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GeoIO\\WKB\\Parser\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jan Sorgalla",
+ "email": "jsorgalla@gmail.com"
+ }
+ ],
+ "description": "Well-known binary (WKB) Parser.",
+ "keywords": [
+ "geo",
+ "geometry",
+ "io",
+ "parser",
+ "wkb"
+ ],
+ "time": "2015-06-30 04:19:13"
+ },
+ {
+ "name": "guzzlehttp/guzzle",
+ "version": "6.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "d094e337976dff9d8e2424e8485872194e768662"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662",
+ "reference": "d094e337976dff9d8e2424e8485872194e768662",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/promises": "~1.0",
+ "guzzlehttp/psr7": "~1.1",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "ext-curl": "*",
+ "phpunit/phpunit": "~4.0",
+ "psr/log": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "6.2-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions_include.php"
+ ],
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "rest",
+ "web service"
+ ],
+ "time": "2016-03-21 20:02:09"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579",
+ "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "time": "2016-05-18 16:56:05"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "31382fef2889136415751badebbd1cb022a4ed72"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/31382fef2889136415751badebbd1cb022a4ed72",
+ "reference": "31382fef2889136415751badebbd1cb022a4ed72",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "PSR-7 message implementation",
+ "keywords": [
+ "http",
+ "message",
+ "stream",
+ "uri"
+ ],
+ "time": "2016-04-13 19:56:01"
+ },
+ {
+ "name": "indieauth/client",
+ "version": "0.1.14",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/indieweb/indieauth-client-php.git",
+ "reference": "504ba095ee10ffaabc570682f3a93b462ba21c77"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/indieweb/indieauth-client-php/zipball/504ba095ee10ffaabc570682f3a93b462ba21c77",
+ "reference": "504ba095ee10ffaabc570682f3a93b462ba21c77",
+ "shasum": ""
+ },
+ "require": {
+ "barnabywalters/mf-cleaner": "0.*",
+ "indieweb/link-rel-parser": "0.1.1",
+ "mf2/mf2": "~0.3",
+ "php": ">5.3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "IndieAuth": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache 2.0"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Parecki",
+ "homepage": "http://aaronparecki.com"
+ }
+ ],
+ "description": "IndieAuth Client Library",
+ "time": "2016-04-04 14:57:04"
+ },
+ {
+ "name": "indieweb/link-rel-parser",
+ "version": "0.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/indieweb/link-rel-parser-php.git",
+ "reference": "9e0e635fd301a8b1da7bc181f651f029c531dbb6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/indieweb/link-rel-parser-php/zipball/9e0e635fd301a8b1da7bc181f651f029c531dbb6",
+ "reference": "9e0e635fd301a8b1da7bc181f651f029c531dbb6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/IndieWeb/link_rel_parser.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Parecki",
+ "homepage": "http://aaronparecki.com"
+ },
+ {
+ "name": "Tantek Çelik",
+ "homepage": "http://tantek.com"
+ }
+ ],
+ "description": "Parse rel values from HTTP headers",
+ "homepage": "https://github.com/indieweb/link-rel-parser-php",
+ "keywords": [
+ "http",
+ "indieweb",
+ "microformats2"
+ ],
+ "time": "2013-12-23 00:14:58"
+ },
+ {
+ "name": "intervention/image",
+ "version": "2.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Intervention/image.git",
+ "reference": "22088b04728a039bd1fc32f7e79a89a118b78698"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Intervention/image/zipball/22088b04728a039bd1fc32f7e79a89a118b78698",
+ "reference": "22088b04728a039bd1fc32f7e79a89a118b78698",
+ "shasum": ""
+ },
+ "require": {
+ "ext-fileinfo": "*",
+ "guzzlehttp/psr7": "~1.1",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "~0.9.2",
+ "phpunit/phpunit": "3.*"
+ },
+ "suggest": {
+ "ext-gd": "to use GD library based image processing.",
+ "ext-imagick": "to use Imagick based image processing.",
+ "intervention/imagecache": "Caching extension for the Intervention Image library"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Intervention\\Image\\": "src/Intervention/Image"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Oliver Vogel",
+ "email": "oliver@olivervogel.net",
+ "homepage": "http://olivervogel.net/"
+ }
+ ],
+ "description": "Image handling and manipulation library with support for Laravel integration",
+ "homepage": "http://image.intervention.io/",
+ "keywords": [
+ "gd",
+ "image",
+ "imagick",
+ "laravel",
+ "thumbnail",
+ "watermark"
+ ],
+ "time": "2016-04-26 14:08:40"
+ },
+ {
+ "name": "jakub-onderka/php-console-color",
+ "version": "0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/JakubOnderka/PHP-Console-Color.git",
+ "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/e0b393dacf7703fc36a4efc3df1435485197e6c1",
+ "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "jakub-onderka/php-code-style": "1.0",
+ "jakub-onderka/php-parallel-lint": "0.*",
+ "jakub-onderka/php-var-dump-check": "0.*",
+ "phpunit/phpunit": "3.7.*",
+ "squizlabs/php_codesniffer": "1.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "JakubOnderka\\PhpConsoleColor": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-2-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jakub Onderka",
+ "email": "jakub.onderka@gmail.com",
+ "homepage": "http://www.acci.cz"
+ }
+ ],
+ "time": "2014-04-08 15:00:19"
+ },
+ {
+ "name": "jakub-onderka/php-console-highlighter",
+ "version": "v0.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git",
+ "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/7daa75df45242c8d5b75a22c00a201e7954e4fb5",
+ "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5",
+ "shasum": ""
+ },
+ "require": {
+ "jakub-onderka/php-console-color": "~0.1",
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "jakub-onderka/php-code-style": "~1.0",
+ "jakub-onderka/php-parallel-lint": "~0.5",
+ "jakub-onderka/php-var-dump-check": "~0.1",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "~1.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "JakubOnderka\\PhpConsoleHighlighter": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jakub Onderka",
+ "email": "acci@acci.cz",
+ "homepage": "http://www.acci.cz/"
+ }
+ ],
+ "time": "2015-04-20 18:58:01"
+ },
+ {
+ "name": "jeremeamia/SuperClosure",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jeremeamia/super_closure.git",
+ "reference": "29a88be2a4846d27c1613aed0c9071dfad7b5938"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/29a88be2a4846d27c1613aed0c9071dfad7b5938",
+ "reference": "29a88be2a4846d27c1613aed0c9071dfad7b5938",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^1.2|^2.0",
+ "php": ">=5.4",
+ "symfony/polyfill-php56": "^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0|^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "SuperClosure\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jeremy Lindblom",
+ "email": "jeremeamia@gmail.com",
+ "homepage": "https://github.com/jeremeamia",
+ "role": "Developer"
+ }
+ ],
+ "description": "Serialize Closure objects, including their context and binding",
+ "homepage": "https://github.com/jeremeamia/super_closure",
+ "keywords": [
+ "closure",
+ "function",
+ "lambda",
+ "parser",
+ "serializable",
+ "serialize",
+ "tokenizer"
+ ],
+ "time": "2015-12-05 17:17:57"
+ },
+ {
+ "name": "jmikola/geojson",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jmikola/geojson.git",
+ "reference": "6ec3016cc0215667b7775f6ead7bd0337ad66eee"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jmikola/geojson/zipball/6ec3016cc0215667b7775f6ead7bd0337ad66eee",
+ "reference": "6ec3016cc0215667b7775f6ead7bd0337ad66eee",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~3.7"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "GeoJson\\": "src/"
+ },
+ "classmap": [
+ "stubs/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jeremy Mikola",
+ "email": "jmikola@gmail.com"
+ }
+ ],
+ "description": "GeoJSON implementation for PHP",
+ "homepage": "https://github.com/jmikola/geojson",
+ "keywords": [
+ "geo",
+ "geojson",
+ "geospatial"
+ ],
+ "time": "2015-09-27 15:35:21"
+ },
+ {
+ "name": "jonnybarnes/indieweb",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jonnybarnes/indieweb.git",
+ "reference": "a1de61e99dddd4a5800565bff3802a892a693b35"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jonnybarnes/indieweb/zipball/a1de61e99dddd4a5800565bff3802a892a693b35",
+ "reference": "a1de61e99dddd4a5800565bff3802a892a693b35",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jonnybarnes\\IndieWeb\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "CC0-1.0"
+ ],
+ "authors": [
+ {
+ "name": "Jonny Barnes",
+ "email": "jonny@jonnybarnes.uk"
+ }
+ ],
+ "description": "IndieWeb helper functions",
+ "homepage": "https://github.com/jonnybarnes/indieweb",
+ "keywords": [
+ "indieweb",
+ "posse"
+ ],
+ "time": "2016-01-14 15:10:20"
+ },
+ {
+ "name": "jonnybarnes/unicode-tools",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jonnybarnes/unicode-tools.git",
+ "reference": "0f469c30cb9a40a1cb578f893b3af1abc1a6ff53"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jonnybarnes/unicode-tools/zipball/0f469c30cb9a40a1cb578f893b3af1abc1a6ff53",
+ "reference": "0f469c30cb9a40a1cb578f893b3af1abc1a6ff53",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "3.7.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Jonnybarnes\\UnicodeTools": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jonny Barnes",
+ "email": "jonny@jonnybarnes.net"
+ }
+ ],
+ "description": "Turns Unicode codepoints into raw utf-8 multibyte characters",
+ "homepage": "https://github.com/jonnybarnes/unicode-tools",
+ "keywords": [
+ "unicode",
+ "utf-8"
+ ],
+ "time": "2013-07-18 15:32:42"
+ },
+ {
+ "name": "jonnybarnes/webmentions-parser",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jonnybarnes/webmentions-parser.git",
+ "reference": "00ccf313a8c19bf795fc16ec71f2408ea23dd8d6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jonnybarnes/webmentions-parser/zipball/00ccf313a8c19bf795fc16ec71f2408ea23dd8d6",
+ "reference": "00ccf313a8c19bf795fc16ec71f2408ea23dd8d6",
+ "shasum": ""
+ },
+ "require": {
+ "guzzlehttp/guzzle": "~6.0",
+ "mf2/mf2": "~0.3",
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jonnybarnes\\WebmentionsParser\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "authors": [
+ {
+ "name": "Jonny Barnes",
+ "homepage": "https://jonnybarnes.uk"
+ }
+ ],
+ "description": "A PHP library to parse webmentions from HTML",
+ "keywords": [
+ "html",
+ "indieweb",
+ "microformats",
+ "webmentions"
+ ],
+ "time": "2016-04-03 19:57:20"
+ },
+ {
+ "name": "laravel/framework",
+ "version": "v5.2.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/framework.git",
+ "reference": "f688217113f70b01d0e127da9035195415812bef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/f688217113f70b01d0e127da9035195415812bef",
+ "reference": "f688217113f70b01d0e127da9035195415812bef",
+ "shasum": ""
+ },
+ "require": {
+ "classpreloader/classpreloader": "~3.0",
+ "doctrine/inflector": "~1.0",
+ "ext-mbstring": "*",
+ "ext-openssl": "*",
+ "jeremeamia/superclosure": "~2.2",
+ "league/flysystem": "~1.0",
+ "monolog/monolog": "~1.11",
+ "mtdowling/cron-expression": "~1.0",
+ "nesbot/carbon": "~1.20",
+ "paragonie/random_compat": "~1.4",
+ "php": ">=5.5.9",
+ "psy/psysh": "0.7.*",
+ "swiftmailer/swiftmailer": "~5.1",
+ "symfony/console": "2.8.*|3.0.*",
+ "symfony/debug": "2.8.*|3.0.*",
+ "symfony/finder": "2.8.*|3.0.*",
+ "symfony/http-foundation": "2.8.*|3.0.*",
+ "symfony/http-kernel": "2.8.*|3.0.*",
+ "symfony/polyfill-php56": "~1.0",
+ "symfony/process": "2.8.*|3.0.*",
+ "symfony/routing": "2.8.*|3.0.*",
+ "symfony/translation": "2.8.*|3.0.*",
+ "symfony/var-dumper": "2.8.*|3.0.*",
+ "vlucas/phpdotenv": "~2.2"
+ },
+ "replace": {
+ "illuminate/auth": "self.version",
+ "illuminate/broadcasting": "self.version",
+ "illuminate/bus": "self.version",
+ "illuminate/cache": "self.version",
+ "illuminate/config": "self.version",
+ "illuminate/console": "self.version",
+ "illuminate/container": "self.version",
+ "illuminate/contracts": "self.version",
+ "illuminate/cookie": "self.version",
+ "illuminate/database": "self.version",
+ "illuminate/encryption": "self.version",
+ "illuminate/events": "self.version",
+ "illuminate/exception": "self.version",
+ "illuminate/filesystem": "self.version",
+ "illuminate/hashing": "self.version",
+ "illuminate/http": "self.version",
+ "illuminate/log": "self.version",
+ "illuminate/mail": "self.version",
+ "illuminate/pagination": "self.version",
+ "illuminate/pipeline": "self.version",
+ "illuminate/queue": "self.version",
+ "illuminate/redis": "self.version",
+ "illuminate/routing": "self.version",
+ "illuminate/session": "self.version",
+ "illuminate/support": "self.version",
+ "illuminate/translation": "self.version",
+ "illuminate/validation": "self.version",
+ "illuminate/view": "self.version"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "~3.0",
+ "mockery/mockery": "~0.9.4",
+ "pda/pheanstalk": "~3.0",
+ "phpunit/phpunit": "~4.1",
+ "predis/predis": "~1.0",
+ "symfony/css-selector": "2.8.*|3.0.*",
+ "symfony/dom-crawler": "2.8.*|3.0.*"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).",
+ "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).",
+ "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).",
+ "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~5.3|~6.0).",
+ "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).",
+ "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).",
+ "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).",
+ "predis/predis": "Required to use the redis cache and queue drivers (~1.0).",
+ "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).",
+ "symfony/css-selector": "Required to use some of the crawler integration testing tools (2.8.*|3.0.*).",
+ "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (2.8.*|3.0.*).",
+ "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/Illuminate/Queue/IlluminateQueueClosure.php"
+ ],
+ "files": [
+ "src/Illuminate/Foundation/helpers.php",
+ "src/Illuminate/Support/helpers.php"
+ ],
+ "psr-4": {
+ "Illuminate\\": "src/Illuminate/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylorotwell@gmail.com"
+ }
+ ],
+ "description": "The Laravel Framework.",
+ "homepage": "http://laravel.com",
+ "keywords": [
+ "framework",
+ "laravel"
+ ],
+ "time": "2016-05-17 13:24:40"
+ },
+ {
+ "name": "lcobucci/jwt",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/lcobucci/jwt.git",
+ "reference": "afea8e682e911a21574fd8519321b32522fa25b5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/lcobucci/jwt/zipball/afea8e682e911a21574fd8519321b32522fa25b5",
+ "reference": "afea8e682e911a21574fd8519321b32522fa25b5",
+ "shasum": ""
+ },
+ "require": {
+ "ext-openssl": "*",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "mdanter/ecc": "~0.3",
+ "mikey179/vfsstream": "~1.5",
+ "phpmd/phpmd": "~2.2",
+ "phpunit/php-invoker": "~1.1",
+ "phpunit/phpunit": "~4.5",
+ "squizlabs/php_codesniffer": "~2.3"
+ },
+ "suggest": {
+ "mdanter/ecc": "Required to use Elliptic Curves based algorithms."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Lcobucci\\JWT\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Luís Otávio Cobucci Oblonczyk",
+ "email": "lcobucci@gmail.com",
+ "role": "Developer"
+ }
+ ],
+ "description": "A simple library to work with JSON Web Token and JSON Web Signature",
+ "keywords": [
+ "JWS",
+ "jwt"
+ ],
+ "time": "2016-03-24 22:46:13"
+ },
+ {
+ "name": "league/commonmark",
+ "version": "0.13.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/commonmark.git",
+ "reference": "35816f39eb2498484fbb7b1495633a976ee1a8de"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/35816f39eb2498484fbb7b1495633a976ee1a8de",
+ "reference": "35816f39eb2498484fbb7b1495633a976ee1a8de",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=5.4.8"
+ },
+ "replace": {
+ "colinodell/commonmark-php": "*"
+ },
+ "require-dev": {
+ "cebe/markdown": "~1.0",
+ "erusev/parsedown": "~1.0",
+ "jgm/commonmark": "0.25",
+ "michelf/php-markdown": "~1.4",
+ "mikehaertl/php-shellcommand": "~1.2.0",
+ "phpunit/phpunit": "~4.3|~5.0",
+ "scrutinizer/ocular": "~1.1",
+ "symfony/finder": "~2.3|~3.0"
+ },
+ "suggest": {
+ "league/commonmark-extras": "Library of useful extensions including smart punctuation"
+ },
+ "bin": [
+ "bin/commonmark"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.14-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\CommonMark\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Colin O'Dell",
+ "email": "colinodell@gmail.com",
+ "homepage": "https://www.colinodell.com",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "Markdown parser for PHP based on the CommonMark spec",
+ "homepage": "https://github.com/thephpleague/commonmark",
+ "keywords": [
+ "commonmark",
+ "markdown",
+ "parser"
+ ],
+ "time": "2016-05-21 18:41:30"
+ },
+ {
+ "name": "league/flysystem",
+ "version": "1.0.22",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/flysystem.git",
+ "reference": "bd73a91703969a2d20ab4bfbf971d6c2cbe36612"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/bd73a91703969a2d20ab4bfbf971d6c2cbe36612",
+ "reference": "bd73a91703969a2d20ab4bfbf971d6c2cbe36612",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "conflict": {
+ "league/flysystem-sftp": "<1.0.6"
+ },
+ "require-dev": {
+ "ext-fileinfo": "*",
+ "mockery/mockery": "~0.9",
+ "phpspec/phpspec": "^2.2",
+ "phpunit/phpunit": "~4.8 || ~5.0"
+ },
+ "suggest": {
+ "ext-fileinfo": "Required for MimeType",
+ "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+ "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+ "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"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "description": "Filesystem abstraction: Many filesystems, one API.",
+ "keywords": [
+ "Cloud Files",
+ "WebDAV",
+ "abstraction",
+ "aws",
+ "cloud",
+ "copy.com",
+ "dropbox",
+ "file systems",
+ "files",
+ "filesystem",
+ "filesystems",
+ "ftp",
+ "rackspace",
+ "remote",
+ "s3",
+ "sftp",
+ "storage"
+ ],
+ "time": "2016-04-28 06:53:12"
+ },
+ {
+ "name": "league/flysystem-aws-s3-v3",
+ "version": "1.0.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
+ "reference": "1f7ae4e3cc178686c49a9d23cab43ed1e955368c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/1f7ae4e3cc178686c49a9d23cab43ed1e955368c",
+ "reference": "1f7ae4e3cc178686c49a9d23cab43ed1e955368c",
+ "shasum": ""
+ },
+ "require": {
+ "aws/aws-sdk-php": "^3.0.0",
+ "league/flysystem": "~1.0",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "henrikbjorn/phpspec-code-coverage": "~1.0.1",
+ "phpspec/phpspec": "^2.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\AwsS3v3\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "description": "Flysystem adapter for the AWS S3 SDK v3.x",
+ "time": "2016-05-03 19:35:35"
+ },
+ {
+ "name": "league/glide",
+ "version": "0.3.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/glide.git",
+ "reference": "e45a4b536924956e1b20f5d023800557d466eda7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/glide/zipball/e45a4b536924956e1b20f5d023800557d466eda7",
+ "reference": "e45a4b536924956e1b20f5d023800557d466eda7",
+ "shasum": ""
+ },
+ "require": {
+ "intervention/image": "~2.1",
+ "league/flysystem": "~1.0",
+ "php": ">=5.4",
+ "symfony/http-foundation": "~2.3|~3.0",
+ "symfony/http-kernel": "~2.3|~3.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "~0.9",
+ "phpunit/php-token-stream": ">=1.3.0",
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Glide\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jonathan Reinink",
+ "email": "jonathan@reinink.ca",
+ "homepage": "http://reinink.ca"
+ }
+ ],
+ "description": "Wonderfully easy on-demand image manipulation library with an HTTP based API.",
+ "homepage": "https://github.com/thephpleague/glide",
+ "keywords": [
+ "ImageMagick",
+ "editing",
+ "gd",
+ "image",
+ "imagick",
+ "league",
+ "manipulation",
+ "processing"
+ ],
+ "time": "2016-01-25 13:35:12"
+ },
+ {
+ "name": "martinbean/laravel-sluggable-trait",
+ "version": "0.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/martinbean/laravel-sluggable-trait.git",
+ "reference": "8984dc9bc2596814f79baf44aeb9a39c9c07b149"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/martinbean/laravel-sluggable-trait/zipball/8984dc9bc2596814f79baf44aeb9a39c9c07b149",
+ "reference": "8984dc9bc2596814f79baf44aeb9a39c9c07b149",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/database": ">=4.0",
+ "illuminate/support": ">=4.0",
+ "php": ">=5.4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "MartinBean\\Database\\Eloquent\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Martin Bean",
+ "email": "martin@martinbean.co.uk"
+ }
+ ],
+ "description": "A trait you can apply to Eloquent models to have slugs automatically generated on save.",
+ "keywords": [
+ "eloquent",
+ "laravel"
+ ],
+ "time": "2015-02-17 22:47:44"
+ },
+ {
+ "name": "mf2/mf2",
+ "version": "v0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/indieweb/php-mf2.git",
+ "reference": "4fb2eb5365cbc0fd2e0c26ca748777d6c2539763"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/indieweb/php-mf2/zipball/4fb2eb5365cbc0fd2e0c26ca748777d6c2539763",
+ "reference": "4fb2eb5365cbc0fd2e0c26ca748777d6c2539763",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "3.7.*"
+ },
+ "suggest": {
+ "barnabywalters/mf-cleaner": "To more easily handle the canonical data php-mf2 gives you"
+ },
+ "bin": [
+ "bin/fetch-mf2",
+ "bin/parse-mf2"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Mf2/Parser.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "CC0"
+ ],
+ "authors": [
+ {
+ "name": "Barnaby Walters",
+ "homepage": "http://waterpigs.co.uk"
+ }
+ ],
+ "description": "A pure, generic microformats2 parser — makes HTML as easy to consume as a JSON API",
+ "keywords": [
+ "html",
+ "microformats",
+ "microformats 2",
+ "parser",
+ "semantic"
+ ],
+ "time": "2016-03-14 12:13:34"
+ },
+ {
+ "name": "monolog/monolog",
+ "version": "1.19.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Seldaek/monolog.git",
+ "reference": "5f56ed5212dc509c8dc8caeba2715732abb32dbf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5f56ed5212dc509c8dc8caeba2715732abb32dbf",
+ "reference": "5f56ed5212dc509c8dc8caeba2715732abb32dbf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "psr/log": "~1.0"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0.0"
+ },
+ "require-dev": {
+ "aws/aws-sdk-php": "^2.4.9",
+ "doctrine/couchdb": "~1.0@dev",
+ "graylog2/gelf-php": "~1.0",
+ "jakub-onderka/php-parallel-lint": "0.9",
+ "php-amqplib/php-amqplib": "~2.4",
+ "php-console/php-console": "^3.1.3",
+ "phpunit/phpunit": "~4.5",
+ "phpunit/phpunit-mock-objects": "2.3.0",
+ "raven/raven": "^0.13",
+ "ruflin/elastica": ">=0.90 <3.0",
+ "swiftmailer/swiftmailer": "~5.3"
+ },
+ "suggest": {
+ "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+ "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+ "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+ "ext-mongo": "Allow sending log messages to a MongoDB server",
+ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+ "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+ "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+ "php-console/php-console": "Allow sending log messages to Google Chrome",
+ "raven/raven": "Allow sending log messages to a Sentry server",
+ "rollbar/rollbar": "Allow sending log messages to Rollbar",
+ "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Monolog\\": "src/Monolog"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+ "homepage": "http://github.com/Seldaek/monolog",
+ "keywords": [
+ "log",
+ "logging",
+ "psr-3"
+ ],
+ "time": "2016-04-12 18:29:35"
+ },
+ {
+ "name": "mtdowling/cron-expression",
+ "version": "v1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mtdowling/cron-expression.git",
+ "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/c9ee7886f5a12902b225a1a12f36bb45f9ab89e5",
+ "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0|~5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Cron": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
+ "keywords": [
+ "cron",
+ "schedule"
+ ],
+ "time": "2016-01-26 21:23:30"
+ },
+ {
+ "name": "mtdowling/jmespath.php",
+ "version": "2.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jmespath/jmespath.php.git",
+ "reference": "192f93e43c2c97acde7694993ab171b3de284093"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/192f93e43c2c97acde7694993ab171b3de284093",
+ "reference": "192f93e43c2c97acde7694993ab171b3de284093",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "bin": [
+ "bin/jp.php"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "JmesPath\\": "src/"
+ },
+ "files": [
+ "src/JmesPath.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Declaratively specify how to extract elements from a JSON document",
+ "keywords": [
+ "json",
+ "jsonpath"
+ ],
+ "time": "2016-01-05 18:25:05"
+ },
+ {
+ "name": "nesbot/carbon",
+ "version": "1.21.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/briannesbitt/Carbon.git",
+ "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7b08ec6f75791e130012f206e3f7b0e76e18e3d7",
+ "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "symfony/translation": "~2.6|~3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0|~5.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Carbon\\": "src/Carbon/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Brian Nesbitt",
+ "email": "brian@nesbot.com",
+ "homepage": "http://nesbot.com"
+ }
+ ],
+ "description": "A simple API extension for DateTime.",
+ "homepage": "http://carbon.nesbot.com",
+ "keywords": [
+ "date",
+ "datetime",
+ "time"
+ ],
+ "time": "2015-11-04 20:07:17"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "47b254ea51f1d6d5dc04b9b299e88346bf2369e3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/47b254ea51f1d6d5dc04b9b299e88346bf2369e3",
+ "reference": "47b254ea51f1d6d5dc04b9b299e88346bf2369e3",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "time": "2016-04-19 13:41:41"
+ },
+ {
+ "name": "paragonie/random_compat",
+ "version": "v1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/random_compat.git",
+ "reference": "c7e26a21ba357863de030f0b9e701c7d04593774"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774",
+ "reference": "c7e26a21ba357863de030f0b9e701c7d04593774",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*|5.*"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "lib/random.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com"
+ }
+ ],
+ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "keywords": [
+ "csprng",
+ "pseudorandom",
+ "random"
+ ],
+ "time": "2016-03-18 20:34:03"
+ },
+ {
+ "name": "patchwork/utf8",
+ "version": "v1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/tchwork/utf8.git",
+ "reference": "30ec6451aec7d2536f0af8fe535f70c764f2c47a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/tchwork/utf8/zipball/30ec6451aec7d2536f0af8fe535f70c764f2c47a",
+ "reference": "30ec6451aec7d2536f0af8fe535f70c764f2c47a",
+ "shasum": ""
+ },
+ "require": {
+ "lib-pcre": ">=7.3",
+ "php": ">=5.3.0"
+ },
+ "suggest": {
+ "ext-iconv": "Use iconv for best performance",
+ "ext-intl": "Use Intl for best performance",
+ "ext-mbstring": "Use Mbstring for best performance",
+ "ext-wfio": "Use WFIO for UTF-8 filesystem access on Windows"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Patchwork\\": "src/Patchwork/"
+ },
+ "classmap": [
+ "src/Normalizer.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "(Apache-2.0 or GPL-2.0)"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ }
+ ],
+ "description": "Portable and performant UTF-8, Unicode and Grapheme Clusters for PHP",
+ "homepage": "https://github.com/tchwork/utf8",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "time": "2016-05-18 13:57:10"
+ },
+ {
+ "name": "phaza/laravel-postgis",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/njbarrett/laravel-postgis.git",
+ "reference": "accec379af8ceba903ceb10df37beeb9bfb411cc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/njbarrett/laravel-postgis/zipball/accec379af8ceba903ceb10df37beeb9bfb411cc",
+ "reference": "accec379af8ceba903ceb10df37beeb9bfb411cc",
+ "shasum": ""
+ },
+ "require": {
+ "bosnadev/database": "~0.16",
+ "geo-io/wkb-parser": "^1.0",
+ "illuminate/database": "^5.2",
+ "jmikola/geojson": "^1.0",
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "codeclimate/php-test-reporter": "~0.3",
+ "illuminate/pagination": "~5.0",
+ "mockery/mockery": "0.9.*",
+ "phpunit/phpunit": "~4.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Phaza\\LaravelPostgis\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "classmap": [
+ "tests/BaseTestCase.php",
+ "tests/Stubs/"
+ ]
+ },
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Peter Haza",
+ "email": "peter.haza@gmail.com"
+ },
+ {
+ "name": "Nicholas Barrett",
+ "email": "njbarrett7@gmail.com"
+ }
+ ],
+ "description": "Postgis extensions for laravel. Aims to make it easy to work with geometries from laravel models",
+ "support": {
+ "source": "https://github.com/njbarrett/laravel-postgis/tree/master",
+ "issues": "https://github.com/njbarrett/laravel-postgis/issues"
+ },
+ "time": "2016-05-21 08:00:18"
+ },
+ {
+ "name": "predis/predis",
+ "version": "v1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nrk/predis.git",
+ "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nrk/predis/zipball/84060b9034d756b4d79641667d7f9efe1aeb8e04",
+ "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "ext-curl": "Allows access to Webdis when paired with phpiredis",
+ "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Predis\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniele Alessandri",
+ "email": "suppakilla@gmail.com",
+ "homepage": "http://clorophilla.net"
+ }
+ ],
+ "description": "Flexible and feature-complete PHP client library for Redis",
+ "homepage": "http://github.com/nrk/predis",
+ "keywords": [
+ "nosql",
+ "predis",
+ "redis"
+ ],
+ "time": "2015-07-30 18:34:15"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
+ "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "time": "2015-05-04 20:22:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
+ "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Psr\\Log\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "time": "2012-12-21 11:40:51"
+ },
+ {
+ "name": "psy/psysh",
+ "version": "v0.7.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bobthecow/psysh.git",
+ "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e64e10b20f8d229cac76399e1f3edddb57a0f280",
+ "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280",
+ "shasum": ""
+ },
+ "require": {
+ "dnoegel/php-xdg-base-dir": "0.1",
+ "jakub-onderka/php-console-highlighter": "0.3.*",
+ "nikic/php-parser": "^1.2.1|~2.0",
+ "php": ">=5.3.9",
+ "symfony/console": "~2.3.10|^2.4.2|~3.0",
+ "symfony/var-dumper": "~2.7|~3.0"
+ },
+ "require-dev": {
+ "fabpot/php-cs-fixer": "~1.5",
+ "phpunit/phpunit": "~3.7|~4.0|~5.0",
+ "squizlabs/php_codesniffer": "~2.0",
+ "symfony/finder": "~2.1|~3.0"
+ },
+ "suggest": {
+ "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
+ "ext-pdo-sqlite": "The doc command requires SQLite to work.",
+ "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.",
+ "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history."
+ },
+ "bin": [
+ "bin/psysh"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-develop": "0.8.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Psy/functions.php"
+ ],
+ "psr-4": {
+ "Psy\\": "src/Psy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Justin Hileman",
+ "email": "justin@justinhileman.info",
+ "homepage": "http://justinhileman.com"
+ }
+ ],
+ "description": "An interactive shell for modern PHP.",
+ "homepage": "http://psysh.org",
+ "keywords": [
+ "REPL",
+ "console",
+ "interactive",
+ "shell"
+ ],
+ "time": "2016-03-09 05:03:14"
+ },
+ {
+ "name": "ramsey/uuid",
+ "version": "3.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ramsey/uuid.git",
+ "reference": "b4fe3b7387cb323fd15ad5837cae992422c9fa5c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ramsey/uuid/zipball/b4fe3b7387cb323fd15ad5837cae992422c9fa5c",
+ "reference": "b4fe3b7387cb323fd15ad5837cae992422c9fa5c",
+ "shasum": ""
+ },
+ "require": {
+ "paragonie/random_compat": "^1.0|^2.0",
+ "php": ">=5.4"
+ },
+ "replace": {
+ "rhumsaa/uuid": "self.version"
+ },
+ "require-dev": {
+ "apigen/apigen": "^4.1",
+ "codeception/aspect-mock": "1.0.0",
+ "goaop/framework": "1.0.0-alpha.2",
+ "ircmaxell/random-lib": "^1.1",
+ "jakub-onderka/php-parallel-lint": "^0.9.0",
+ "mockery/mockery": "^0.9.4",
+ "moontoast/math": "^1.1",
+ "phpunit/phpunit": "^4.7|^5.0",
+ "satooshi/php-coveralls": "^0.6.1",
+ "squizlabs/php_codesniffer": "^2.3"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator",
+ "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator",
+ "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+ "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).",
+ "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid",
+ "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Ramsey\\Uuid\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marijn Huizendveld",
+ "email": "marijn.huizendveld@gmail.com"
+ },
+ {
+ "name": "Thibaud Fabre",
+ "email": "thibaud@aztech.io"
+ },
+ {
+ "name": "Ben Ramsey",
+ "email": "ben@benramsey.com",
+ "homepage": "https://benramsey.com"
+ }
+ ],
+ "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).",
+ "homepage": "https://github.com/ramsey/uuid",
+ "keywords": [
+ "guid",
+ "identifier",
+ "uuid"
+ ],
+ "time": "2016-04-24 00:30:41"
+ },
+ {
+ "name": "spatie/laravel-glide",
+ "version": "2.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-glide.git",
+ "reference": "06bcfb85464a1202dfaa7494d1b1600c88c418e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-glide/zipball/06bcfb85464a1202dfaa7494d1b1600c88c418e5",
+ "reference": "06bcfb85464a1202dfaa7494d1b1600c88c418e5",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/support": "5.*",
+ "league/glide": "0.3.*",
+ "php": ">=5.4.0"
+ },
+ "require-dev": {
+ "codeception/codeception": "2.*",
+ "mockery/mockery": "~0.9.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Spatie\\Glide": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be"
+ }
+ ],
+ "description": "A Glide Service Provider for Laravel",
+ "homepage": "https://github.com/spatie/laravel-glide",
+ "time": "2016-05-13 14:56:07"
+ },
+ {
+ "name": "spatie/laravel-medialibrary",
+ "version": "3.17.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-medialibrary.git",
+ "reference": "7f96cd3c709643ab0dde977936b98a7f75396d31"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/7f96cd3c709643ab0dde977936b98a7f75396d31",
+ "reference": "7f96cd3c709643ab0dde977936b98a7f75396d31",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/bus": "~5.1.16|~5.2.0",
+ "illuminate/console": "~5.1.16|~5.2.0",
+ "illuminate/database": "~5.1.16|~5.2.0",
+ "illuminate/support": "~5.1.16|~5.2.0",
+ "php": "^5.5|^7.0",
+ "spatie/laravel-glide": "^2.2.4",
+ "spatie/pdf-to-image": "^1.0.1",
+ "spatie/string": "^2.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^2.5.2",
+ "mockery/mockery": "^0.9.4",
+ "orchestra/testbench": "^3.0",
+ "phpunit/phpunit": "^4.0",
+ "scrutinizer/ocular": "^1.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\MediaLibrary\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://murze.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Associate files with Eloquent models",
+ "homepage": "https://github.com/spatie/laravel-medialibrary",
+ "keywords": [
+ "cms",
+ "laravel",
+ "laravel-medialibrary",
+ "media",
+ "spatie"
+ ],
+ "time": "2016-04-12 09:57:52"
+ },
+ {
+ "name": "spatie/pdf-to-image",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/pdf-to-image.git",
+ "reference": "c08dac65f0f857dd4d467d40794772be5a75d6de"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/pdf-to-image/zipball/c08dac65f0f857dd4d467d40794772be5a75d6de",
+ "reference": "c08dac65f0f857dd4d467d40794772be5a75d6de",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*",
+ "scrutinizer/ocular": "~1.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\PdfToImage\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Convert a pdf to an image",
+ "homepage": "https://github.com/spatie/pdf-to-image",
+ "keywords": [
+ "convert",
+ "image",
+ "pdf",
+ "pdf-to-image",
+ "spatie"
+ ],
+ "time": "2016-04-29 08:02:56"
+ },
+ {
+ "name": "spatie/string",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/string.git",
+ "reference": "1843189c711be4dcf62b655824f2f17120c2dc7d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/string/zipball/1843189c711be4dcf62b655824f2f17120c2dc7d",
+ "reference": "1843189c711be4dcf62b655824f2f17120c2dc7d",
+ "shasum": ""
+ },
+ "require": {
+ "anahkiasen/underscore-php": "^2.0",
+ "php": ">=5.5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*",
+ "scrutinizer/ocular": "~1.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/string_functions.php"
+ ],
+ "psr-4": {
+ "Spatie\\String\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://murze.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "String handling evolved",
+ "homepage": "https://github.com/spatie/string",
+ "keywords": [
+ "handling",
+ "handy",
+ "spatie",
+ "string"
+ ],
+ "time": "2015-11-02 13:00:37"
+ },
+ {
+ "name": "swiftmailer/swiftmailer",
+ "version": "v5.4.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/swiftmailer/swiftmailer.git",
+ "reference": "d8db871a54619458a805229a057ea2af33c753e8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/d8db871a54619458a805229a057ea2af33c753e8",
+ "reference": "d8db871a54619458a805229a057ea2af33c753e8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "mockery/mockery": "~0.9.1,<0.9.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.4-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "lib/swift_required.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Corbyn"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ }
+ ],
+ "description": "Swiftmailer, free feature-rich PHP mailer",
+ "homepage": "http://swiftmailer.org",
+ "keywords": [
+ "email",
+ "mail",
+ "mailer"
+ ],
+ "time": "2016-05-01 08:45:47"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "34a214710e0714b6efcf40ba3cd1e31373a97820"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/34a214710e0714b6efcf40ba3cd1e31373a97820",
+ "reference": "34a214710e0714b6efcf40ba3cd1e31373a97820",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/event-dispatcher": "~2.8|~3.0",
+ "symfony/process": "~2.8|~3.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/process": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Console\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Console Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-04-28 09:48:42"
+ },
+ {
+ "name": "symfony/debug",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/debug.git",
+ "reference": "a06d10888a45afd97534506afb058ec38d9ba35b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/debug/zipball/a06d10888a45afd97534506afb058ec38d9ba35b",
+ "reference": "a06d10888a45afd97534506afb058ec38d9ba35b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "psr/log": "~1.0"
+ },
+ "conflict": {
+ "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
+ },
+ "require-dev": {
+ "symfony/class-loader": "~2.8|~3.0",
+ "symfony/http-kernel": "~2.8|~3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Debug\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Debug Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-03-30 10:41:14"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "807dde98589f9b2b00624dca326740380d78dbbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/807dde98589f9b2b00624dca326740380d78dbbc",
+ "reference": "807dde98589f9b2b00624dca326740380d78dbbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~2.8|~3.0",
+ "symfony/dependency-injection": "~2.8|~3.0",
+ "symfony/expression-language": "~2.8|~3.0",
+ "symfony/stopwatch": "~2.8|~3.0"
+ },
+ "suggest": {
+ "symfony/dependency-injection": "",
+ "symfony/http-kernel": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\EventDispatcher\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony EventDispatcher Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-05-05 06:56:13"
+ },
+ {
+ "name": "symfony/finder",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "c54e407b35bc098916704e9fd090da21da4c4f52"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/c54e407b35bc098916704e9fd090da21da4c4f52",
+ "reference": "c54e407b35bc098916704e9fd090da21da4c4f52",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Finder Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-03-10 11:13:05"
+ },
+ {
+ "name": "symfony/http-foundation",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-foundation.git",
+ "reference": "18b24bc32d2495ae79d76e777368786a6536fe31"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/18b24bc32d2495ae79d76e777368786a6536fe31",
+ "reference": "18b24bc32d2495ae79d76e777368786a6536fe31",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "symfony/polyfill-mbstring": "~1.1"
+ },
+ "require-dev": {
+ "symfony/expression-language": "~2.8|~3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpFoundation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony HttpFoundation Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-04-12 18:09:53"
+ },
+ {
+ "name": "symfony/http-kernel",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-kernel.git",
+ "reference": "6a5010978edf0a9646342232531e53bfc7abbcd3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6a5010978edf0a9646342232531e53bfc7abbcd3",
+ "reference": "6a5010978edf0a9646342232531e53bfc7abbcd3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "psr/log": "~1.0",
+ "symfony/debug": "~2.8|~3.0",
+ "symfony/event-dispatcher": "~2.8|~3.0",
+ "symfony/http-foundation": "~2.8|~3.0"
+ },
+ "conflict": {
+ "symfony/config": "<2.8"
+ },
+ "require-dev": {
+ "symfony/browser-kit": "~2.8|~3.0",
+ "symfony/class-loader": "~2.8|~3.0",
+ "symfony/config": "~2.8|~3.0",
+ "symfony/console": "~2.8|~3.0",
+ "symfony/css-selector": "~2.8|~3.0",
+ "symfony/dependency-injection": "~2.8|~3.0",
+ "symfony/dom-crawler": "~2.8|~3.0",
+ "symfony/expression-language": "~2.8|~3.0",
+ "symfony/finder": "~2.8|~3.0",
+ "symfony/process": "~2.8|~3.0",
+ "symfony/routing": "~2.8|~3.0",
+ "symfony/stopwatch": "~2.8|~3.0",
+ "symfony/templating": "~2.8|~3.0",
+ "symfony/translation": "~2.8|~3.0",
+ "symfony/var-dumper": "~2.8|~3.0"
+ },
+ "suggest": {
+ "symfony/browser-kit": "",
+ "symfony/class-loader": "",
+ "symfony/config": "",
+ "symfony/console": "",
+ "symfony/dependency-injection": "",
+ "symfony/finder": "",
+ "symfony/var-dumper": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpKernel\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony HttpKernel Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-05-09 22:13:13"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "dff51f72b0706335131b00a7f49606168c582594"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594",
+ "reference": "dff51f72b0706335131b00a7f49606168c582594",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2016-05-18 14:26:46"
+ },
+ {
+ "name": "symfony/polyfill-php56",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php56.git",
+ "reference": "3edf57a8fbf9a927533344cef65ad7e1cf31030a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/3edf57a8fbf9a927533344cef65ad7e1cf31030a",
+ "reference": "3edf57a8fbf9a927533344cef65ad7e1cf31030a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/polyfill-util": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php56\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "time": "2016-05-18 14:26:46"
+ },
+ {
+ "name": "symfony/polyfill-util",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-util.git",
+ "reference": "ef830ce3d218e622b221d6bfad42c751d974bf99"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ef830ce3d218e622b221d6bfad42c751d974bf99",
+ "reference": "ef830ce3d218e622b221d6bfad42c751d974bf99",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Util\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony utilities for portability of PHP codes",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compat",
+ "compatibility",
+ "polyfill",
+ "shim"
+ ],
+ "time": "2016-05-18 14:26:46"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb",
+ "reference": "53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-04-14 15:30:28"
+ },
+ {
+ "name": "symfony/routing",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/routing.git",
+ "reference": "a6cd168310066176599442aa21f5da86c3f8e0b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/a6cd168310066176599442aa21f5da86c3f8e0b3",
+ "reference": "a6cd168310066176599442aa21f5da86c3f8e0b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "conflict": {
+ "symfony/config": "<2.8"
+ },
+ "require-dev": {
+ "doctrine/annotations": "~1.0",
+ "doctrine/common": "~2.2",
+ "psr/log": "~1.0",
+ "symfony/config": "~2.8|~3.0",
+ "symfony/expression-language": "~2.8|~3.0",
+ "symfony/http-foundation": "~2.8|~3.0",
+ "symfony/yaml": "~2.8|~3.0"
+ },
+ "suggest": {
+ "doctrine/annotations": "For using the annotation loader",
+ "symfony/config": "For using the all-in-one router or any loader",
+ "symfony/dependency-injection": "For loading routes from a service",
+ "symfony/expression-language": "For using expression matching",
+ "symfony/http-foundation": "For using a Symfony Request object",
+ "symfony/yaml": "For using the YAML loader"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Routing\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Routing Component",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "router",
+ "routing",
+ "uri",
+ "url"
+ ],
+ "time": "2016-05-03 12:23:49"
+ },
+ {
+ "name": "symfony/translation",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation.git",
+ "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/f7a07af51ea067745a521dab1e3152044a2fb1f2",
+ "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/config": "<2.8"
+ },
+ "require-dev": {
+ "psr/log": "~1.0",
+ "symfony/config": "~2.8|~3.0",
+ "symfony/intl": "~2.8|~3.0",
+ "symfony/yaml": "~2.8|~3.0"
+ },
+ "suggest": {
+ "psr/log": "To use logging capability in translator",
+ "symfony/config": "",
+ "symfony/yaml": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Translation\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Translation Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-03-25 01:41:20"
+ },
+ {
+ "name": "symfony/var-dumper",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-dumper.git",
+ "reference": "0e918c269093ba4c77fca14e9424fa74ed16f1a6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e918c269093ba4c77fca14e9424fa74ed16f1a6",
+ "reference": "0e918c269093ba4c77fca14e9424fa74ed16f1a6",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "twig/twig": "~1.20|~2.0"
+ },
+ "suggest": {
+ "ext-symfony_debug": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "Resources/functions/dump.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\VarDumper\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony mechanism for exploring and dumping PHP variables",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "debug",
+ "dump"
+ ],
+ "time": "2016-04-25 11:17:47"
+ },
+ {
+ "name": "themattharris/tmhoauth",
+ "version": "0.8.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/themattharris/tmhOAuth.git",
+ "reference": "455552d6c57549632644b6c9ac9204766be2b5ee"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/themattharris/tmhOAuth/zipball/455552d6c57549632644b6c9ac9204766be2b5ee",
+ "reference": "455552d6c57549632644b6c9ac9204766be2b5ee",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "tmhOAuth": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "themattharris",
+ "email": "matt@themattharris.com",
+ "role": "Developer"
+ }
+ ],
+ "description": "An OAuth library written in PHP by @themattharris",
+ "keywords": [
+ "oauth",
+ "twitter"
+ ],
+ "time": "2014-08-06 22:29:35"
+ },
+ {
+ "name": "thujohn/twitter",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thujohn/twitter.git",
+ "reference": "137dfa006ad06b956d579ff3ce0ffa5d03506188"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thujohn/twitter/zipball/137dfa006ad06b956d579ff3ce0ffa5d03506188",
+ "reference": "137dfa006ad06b956d579ff3ce0ffa5d03506188",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/support": "4.*|5.*",
+ "php": ">=5.4.0",
+ "themattharris/tmhoauth": "0.8.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Thujohn\\Twitter": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "thujohn",
+ "email": "jonathan.thuau@gmail.com"
+ }
+ ],
+ "description": "Twitter API for Laravel",
+ "keywords": [
+ "laravel",
+ "laravel4",
+ "laravel5",
+ "twitter"
+ ],
+ "time": "2016-04-08 11:43:15"
+ },
+ {
+ "name": "vlucas/phpdotenv",
+ "version": "v2.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/vlucas/phpdotenv.git",
+ "reference": "63f37b9395e8041cd4313129c08ece896d06ca8e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/63f37b9395e8041cd4313129c08ece896d06ca8e",
+ "reference": "63f37b9395e8041cd4313129c08ece896d06ca8e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.9"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8 || ^5.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Dotenv\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause-Attribution"
+ ],
+ "authors": [
+ {
+ "name": "Vance Lucas",
+ "email": "vance@vancelucas.com",
+ "homepage": "http://www.vancelucas.com"
+ }
+ ],
+ "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+ "keywords": [
+ "dotenv",
+ "env",
+ "environment"
+ ],
+ "time": "2016-04-15 10:48:49"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "barryvdh/laravel-debugbar",
+ "version": "v2.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/barryvdh/laravel-debugbar.git",
+ "reference": "c291e58d0a13953e0f68d99182ee77ebc693edc0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c291e58d0a13953e0f68d99182ee77ebc693edc0",
+ "reference": "c291e58d0a13953e0f68d99182ee77ebc693edc0",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/support": "5.1.*|5.2.*",
+ "maximebf/debugbar": "~1.11.0",
+ "php": ">=5.5.9",
+ "symfony/finder": "~2.7|~3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Barryvdh\\Debugbar\\": "src/"
+ },
+ "files": [
+ "src/helpers.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "PHP Debugbar integration for Laravel",
+ "keywords": [
+ "debug",
+ "debugbar",
+ "laravel",
+ "profiler",
+ "webprofiler"
+ ],
+ "time": "2016-05-11 13:54:43"
+ },
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3,<8.0-DEV"
+ },
+ "require-dev": {
+ "athletic/athletic": "~0.1.8",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "http://ocramius.github.com/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://github.com/doctrine/instantiator",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "time": "2015-06-14 21:17:01"
+ },
+ {
+ "name": "filp/whoops",
+ "version": "2.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/filp/whoops.git",
+ "reference": "d13505b240a6f580bc75ba591da30299d6cb0eec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/d13505b240a6f580bc75ba591da30299d6cb0eec",
+ "reference": "d13505b240a6f580bc75ba591da30299d6cb0eec",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "require-dev": {
+ "mockery/mockery": "0.9.*",
+ "phpunit/phpunit": "^4.8 || ^5.0",
+ "symfony/var-dumper": "~3.0"
+ },
+ "suggest": {
+ "symfony/var-dumper": "Pretty print complex values better with var-dumper available",
+ "whoops/soap": "Formats errors as SOAP responses"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Whoops\\": "src/Whoops/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Filipe Dobreira",
+ "homepage": "https://github.com/filp",
+ "role": "Developer"
+ }
+ ],
+ "description": "php error handling for cool kids",
+ "homepage": "https://github.com/filp/whoops",
+ "keywords": [
+ "error",
+ "exception",
+ "handling",
+ "library",
+ "whoops",
+ "zf2"
+ ],
+ "time": "2016-04-07 06:16:25"
+ },
+ {
+ "name": "fzaninotto/faker",
+ "version": "v1.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/fzaninotto/Faker.git",
+ "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123",
+ "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.3.3|^7.0"
+ },
+ "require-dev": {
+ "ext-intl": "*",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "~1.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": []
+ },
+ "autoload": {
+ "psr-4": {
+ "Faker\\": "src/Faker/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "François Zaninotto"
+ }
+ ],
+ "description": "Faker is a PHP library that generates fake data for you.",
+ "keywords": [
+ "data",
+ "faker",
+ "fixtures"
+ ],
+ "time": "2016-04-29 12:21:54"
+ },
+ {
+ "name": "hamcrest/hamcrest-php",
+ "version": "v1.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hamcrest/hamcrest-php.git",
+ "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c",
+ "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "replace": {
+ "cordoval/hamcrest-php": "*",
+ "davedevelopment/hamcrest-php": "*",
+ "kodova/hamcrest-php": "*"
+ },
+ "require-dev": {
+ "phpunit/php-file-iterator": "1.3.3",
+ "satooshi/php-coveralls": "dev-master"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "hamcrest"
+ ],
+ "files": [
+ "hamcrest/Hamcrest.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD"
+ ],
+ "description": "This is the PHP port of Hamcrest Matchers",
+ "keywords": [
+ "test"
+ ],
+ "time": "2015-05-11 14:41:42"
+ },
+ {
+ "name": "maximebf/debugbar",
+ "version": "v1.11.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/maximebf/php-debugbar.git",
+ "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/d9302891c1f0a0ac5a4f66725163a00537c6359f",
+ "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0",
+ "psr/log": "^1.0",
+ "symfony/var-dumper": "^2.6|^3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.0|^5.0"
+ },
+ "suggest": {
+ "kriswallsmith/assetic": "The best way to manage assets",
+ "monolog/monolog": "Log using Monolog",
+ "predis/predis": "Redis storage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.11-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "DebugBar\\": "src/DebugBar/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Maxime Bouroumeau-Fuseau",
+ "email": "maxime.bouroumeau@gmail.com",
+ "homepage": "http://maximebf.com"
+ },
+ {
+ "name": "Barry vd. Heuvel",
+ "email": "barryvdh@gmail.com"
+ }
+ ],
+ "description": "Debug bar in the browser for php application",
+ "homepage": "https://github.com/maximebf/php-debugbar",
+ "keywords": [
+ "debug",
+ "debugbar"
+ ],
+ "time": "2016-01-22 12:22:23"
+ },
+ {
+ "name": "mockery/mockery",
+ "version": "0.9.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/padraic/mockery.git",
+ "reference": "4db079511a283e5aba1b3c2fb19037c645e70fc2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/padraic/mockery/zipball/4db079511a283e5aba1b3c2fb19037c645e70fc2",
+ "reference": "4db079511a283e5aba1b3c2fb19037c645e70fc2",
+ "shasum": ""
+ },
+ "require": {
+ "hamcrest/hamcrest-php": "~1.1",
+ "lib-pcre": ">=7.0",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.9.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Mockery": "library/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Pádraic Brady",
+ "email": "padraic.brady@gmail.com",
+ "homepage": "http://blog.astrumfutura.com"
+ },
+ {
+ "name": "Dave Marshall",
+ "email": "dave.marshall@atstsolutions.co.uk",
+ "homepage": "http://davedevelopment.co.uk"
+ }
+ ],
+ "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.",
+ "homepage": "http://github.com/padraic/mockery",
+ "keywords": [
+ "BDD",
+ "TDD",
+ "library",
+ "mock",
+ "mock objects",
+ "mockery",
+ "stub",
+ "test",
+ "test double",
+ "testing"
+ ],
+ "time": "2016-05-22 21:52:33"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
+ "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.0"
+ },
+ "suggest": {
+ "dflydev/markdown": "~1.0",
+ "erusev/parsedown": "~1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "phpDocumentor": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "mike.vanriel@naenius.com"
+ }
+ ],
+ "time": "2015-02-03 12:10:50"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "v1.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
+ "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": "^5.3|^7.0",
+ "phpdocumentor/reflection-docblock": "~2.0",
+ "sebastian/comparator": "~1.1",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Prophecy\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "time": "2016-02-15 07:46:21"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "2.2.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+ "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "phpunit/php-file-iterator": "~1.3",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-token-stream": "~1.3",
+ "sebastian/environment": "^1.3.2",
+ "sebastian/version": "~1.0"
+ },
+ "require-dev": {
+ "ext-xdebug": ">=2.1.4",
+ "phpunit/phpunit": "~4"
+ },
+ "suggest": {
+ "ext-dom": "*",
+ "ext-xdebug": ">=2.2.1",
+ "ext-xmlwriter": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "time": "2015-10-06 15:47:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+ "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "time": "2015-06-21 13:08:43"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "time": "2015-06-21 13:50:34"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4|~5"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "time": "2016-05-12 18:03:57"
+ },
+ {
+ "name": "phpunit/php-token-stream",
+ "version": "1.4.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+ "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+ "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Wrapper around PHP's tokenizer extension.",
+ "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+ "keywords": [
+ "tokenizer"
+ ],
+ "time": "2015-09-15 10:49:45"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "4.8.26",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74",
+ "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-pcre": "*",
+ "ext-reflection": "*",
+ "ext-spl": "*",
+ "php": ">=5.3.3",
+ "phpspec/prophecy": "^1.3.1",
+ "phpunit/php-code-coverage": "~2.1",
+ "phpunit/php-file-iterator": "~1.4",
+ "phpunit/php-text-template": "~1.2",
+ "phpunit/php-timer": "^1.0.6",
+ "phpunit/phpunit-mock-objects": "~2.3",
+ "sebastian/comparator": "~1.1",
+ "sebastian/diff": "~1.2",
+ "sebastian/environment": "~1.3",
+ "sebastian/exporter": "~1.2",
+ "sebastian/global-state": "~1.0",
+ "sebastian/version": "~1.0",
+ "symfony/yaml": "~2.1|~3.0"
+ },
+ "suggest": {
+ "phpunit/php-invoker": "~1.1"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.8.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "time": "2016-05-17 03:09:28"
+ },
+ {
+ "name": "phpunit/phpunit-mock-objects",
+ "version": "2.3.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+ "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+ "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.0.2",
+ "php": ">=5.3.3",
+ "phpunit/php-text-template": "~1.2",
+ "sebastian/exporter": "~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "suggest": {
+ "ext-soap": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sb@sebastian-bergmann.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Mock Object library for PHPUnit",
+ "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+ "keywords": [
+ "mock",
+ "xunit"
+ ],
+ "time": "2015-10-02 06:51:40"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
+ "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/diff": "~1.2",
+ "sebastian/exporter": "~1.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "http://www.github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "time": "2015-07-26 15:48:44"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
+ "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff"
+ ],
+ "time": "2015-12-08 07:14:41"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "1.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716",
+ "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "time": "2016-05-17 03:18:57"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
+ "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "sebastian/recursion-context": "~1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.2.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "http://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "time": "2015-06-21 07:55:53"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.2"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "time": "2015-10-12 03:26:01"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "1.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
+ "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "time": "2015-11-11 19:50:13"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "1.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+ "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "time": "2015-06-21 13:59:46"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/css-selector.git",
+ "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/65e764f404685f2dc20c057e889b3ad04b2e2db0",
+ "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\CssSelector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony CssSelector Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-03-04 07:55:57"
+ },
+ {
+ "name": "symfony/dom-crawler",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dom-crawler.git",
+ "reference": "49b588841225b205700e5122fa01911cabada857"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/49b588841225b205700e5122fa01911cabada857",
+ "reference": "49b588841225b205700e5122fa01911cabada857",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "require-dev": {
+ "symfony/css-selector": "~2.8|~3.0"
+ },
+ "suggest": {
+ "symfony/css-selector": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DomCrawler\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony DomCrawler Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-04-12 18:09:53"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "0047c8366744a16de7516622c5b7355336afae96"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96",
+ "reference": "0047c8366744a16de7516622c5b7355336afae96",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Yaml\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Yaml Component",
+ "homepage": "https://symfony.com",
+ "time": "2016-03-04 07:55:57"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": {
+ "jonnybarnes/unicode-tools": 20,
+ "jonnybarnes/indieweb": 20,
+ "jonnybarnes/webmentions-parser": 20,
+ "phaza/laravel-postgis": 20
+ },
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "ext-intl": "*",
+ "php": ">=7.0.0"
+ },
+ "platform-dev": []
+}
diff --git a/config/app.php b/config/app.php
new file mode 100644
index 00000000..1bcc5c20
--- /dev/null
+++ b/config/app.php
@@ -0,0 +1,241 @@
+ env('APP_ENV', 'production'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Debug Mode
+ |--------------------------------------------------------------------------
+ |
+ | When your application is in debug mode, detailed error messages with
+ | stack traces will be shown on every error that occurs within your
+ | application. If disabled, a simple generic error page is shown.
+ |
+ */
+
+ 'debug' => env('APP_DEBUG', false),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application URL
+ |--------------------------------------------------------------------------
+ |
+ | This URL is used by the console to properly generate URLs when using
+ | the Artisan command line tool. You should set this to the root of
+ | your application so that it is used when running Artisan tasks.
+ |
+ */
+
+ 'url' => env('APP_URL', 'http://localhost'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Short URL
+ |--------------------------------------------------------------------------
+ |
+ | The short URL for the application
+ |
+ */
+
+ 'shorturl' => env('APP_SHORTURL', 'http://shorturl.local'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Timezone
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the default timezone for your application, which
+ | will be used by the PHP date and date-time functions. We have gone
+ | ahead and set this to a sensible default for you out of the box.
+ |
+ */
+
+ 'timezone' => env('APP_TIMEZONE', 'UTC'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Locale Configuration
+ |--------------------------------------------------------------------------
+ |
+ | The application locale determines the default locale that will be used
+ | by the translation service provider. You are free to set this value
+ | to any of the locales which will be supported by the application.
+ |
+ */
+
+ 'locale' => env('APP_LANG', 'en'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Fallback Locale
+ |--------------------------------------------------------------------------
+ |
+ | The fallback locale determines the locale to use when the current one
+ | is not available. You may change the value to correspond to any of
+ | the language folders that are provided through your application.
+ |
+ */
+
+ 'fallback_locale' => 'en',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Encryption Key
+ |--------------------------------------------------------------------------
+ |
+ | This key is used by the Illuminate encrypter service and should be set
+ | to a random, 32 character string, otherwise these encrypted strings
+ | will not be safe. Please do this before deploying an application!
+ |
+ */
+
+ 'key' => env('APP_KEY'),
+
+ 'cipher' => 'AES-256-CBC',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Logging Configuration
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the log settings for your application. Out of
+ | the box, Laravel uses the Monolog PHP logging library. This gives
+ | you a variety of powerful log handlers / formatters to utilize.
+ |
+ | Available Settings: "single", "daily", "syslog", "errorlog"
+ |
+ */
+
+ 'log' => env('APP_LOG', 'single'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Autoloaded Service Providers
+ |--------------------------------------------------------------------------
+ |
+ | The service providers listed here will be automatically loaded on the
+ | request to your application. Feel free to add your own services to
+ | this array to grant expanded functionality to your applications.
+ |
+ */
+
+ 'providers' => [
+
+ /*
+ * Laravel Framework Service Providers...
+ */
+ Illuminate\Auth\AuthServiceProvider::class,
+ Illuminate\Broadcasting\BroadcastServiceProvider::class,
+ Illuminate\Bus\BusServiceProvider::class,
+ Illuminate\Cache\CacheServiceProvider::class,
+ Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
+ Illuminate\Cookie\CookieServiceProvider::class,
+ Illuminate\Database\DatabaseServiceProvider::class,
+ Illuminate\Encryption\EncryptionServiceProvider::class,
+ Illuminate\Filesystem\FilesystemServiceProvider::class,
+ Illuminate\Foundation\Providers\FoundationServiceProvider::class,
+ Illuminate\Hashing\HashServiceProvider::class,
+ Illuminate\Mail\MailServiceProvider::class,
+ Illuminate\Pagination\PaginationServiceProvider::class,
+ Illuminate\Pipeline\PipelineServiceProvider::class,
+ Illuminate\Queue\QueueServiceProvider::class,
+ Illuminate\Redis\RedisServiceProvider::class,
+ Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
+ Illuminate\Session\SessionServiceProvider::class,
+ Illuminate\Translation\TranslationServiceProvider::class,
+ Illuminate\Validation\ValidationServiceProvider::class,
+ Illuminate\View\ViewServiceProvider::class,
+
+ /*
+ * Application Service Providers...
+ */
+ App\Providers\AppServiceProvider::class,
+ App\Providers\AuthServiceProvider::class,
+ App\Providers\EventServiceProvider::class,
+ App\Providers\RouteServiceProvider::class,
+
+ /*
+ * Laravel Debugbar
+ */
+ Barryvdh\Debugbar\ServiceProvider::class,
+
+ /*
+ * Thujohn’s Twitter API client
+ */
+ Thujohn\Twitter\TwitterServiceProvider::class,
+
+ /*
+ * Laravel Medialibrary
+ */
+ Spatie\MediaLibrary\MediaLibraryServiceProvider::class,
+
+ /*
+ * Phaza’s Postgis library
+ */
+ Phaza\LaravelPostgis\DatabaseServiceProvider::class,
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Class Aliases
+ |--------------------------------------------------------------------------
+ |
+ | This array of class aliases will be registered when this application
+ | is started. However, feel free to register as many as you wish as
+ | the aliases are "lazy" loaded so they don't hinder performance.
+ |
+ */
+
+ 'aliases' => [
+
+ 'App' => Illuminate\Support\Facades\App::class,
+ 'Artisan' => Illuminate\Support\Facades\Artisan::class,
+ 'Auth' => Illuminate\Support\Facades\Auth::class,
+ 'Blade' => Illuminate\Support\Facades\Blade::class,
+ 'Cache' => Illuminate\Support\Facades\Cache::class,
+ 'Config' => Illuminate\Support\Facades\Config::class,
+ 'Cookie' => Illuminate\Support\Facades\Cookie::class,
+ 'Crypt' => Illuminate\Support\Facades\Crypt::class,
+ 'DB' => Illuminate\Support\Facades\DB::class,
+ 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
+ 'Event' => Illuminate\Support\Facades\Event::class,
+ 'File' => Illuminate\Support\Facades\File::class,
+ 'Gate' => Illuminate\Support\Facades\Gate::class,
+ 'Hash' => Illuminate\Support\Facades\Hash::class,
+ 'Lang' => Illuminate\Support\Facades\Lang::class,
+ 'Log' => Illuminate\Support\Facades\Log::class,
+ 'Mail' => Illuminate\Support\Facades\Mail::class,
+ 'Password' => Illuminate\Support\Facades\Password::class,
+ 'Queue' => Illuminate\Support\Facades\Queue::class,
+ 'Redirect' => Illuminate\Support\Facades\Redirect::class,
+ 'Redis' => Illuminate\Support\Facades\Redis::class,
+ 'Request' => Illuminate\Support\Facades\Request::class,
+ 'Response' => Illuminate\Support\Facades\Response::class,
+ 'Route' => Illuminate\Support\Facades\Route::class,
+ 'Schema' => Illuminate\Support\Facades\Schema::class,
+ 'Session' => Illuminate\Support\Facades\Session::class,
+ 'Storage' => Illuminate\Support\Facades\Storage::class,
+ 'URL' => Illuminate\Support\Facades\URL::class,
+ 'Validator' => Illuminate\Support\Facades\Validator::class,
+ 'View' => Illuminate\Support\Facades\View::class,
+
+ 'Debugbar' => Barryvdh\Debugbar\Facade::class,
+ 'Twitter' => Thujohn\Twitter\Facades\Twitter::class,
+
+ ],
+
+];
diff --git a/config/auth.php b/config/auth.php
new file mode 100644
index 00000000..3fa7f491
--- /dev/null
+++ b/config/auth.php
@@ -0,0 +1,107 @@
+ [
+ 'guard' => 'web',
+ 'passwords' => 'users',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Authentication Guards
+ |--------------------------------------------------------------------------
+ |
+ | Next, you may define every authentication guard for your application.
+ | Of course, a great default configuration has been defined for you
+ | here which uses session storage and the Eloquent user provider.
+ |
+ | All authentication drivers have a user provider. This defines how the
+ | users are actually retrieved out of your database or other storage
+ | mechanisms used by this application to persist your user's data.
+ |
+ | Supported: "session", "token"
+ |
+ */
+
+ 'guards' => [
+ 'web' => [
+ 'driver' => 'session',
+ 'provider' => 'users',
+ ],
+
+ 'api' => [
+ 'driver' => 'token',
+ 'provider' => 'users',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | User Providers
+ |--------------------------------------------------------------------------
+ |
+ | All authentication drivers have a user provider. This defines how the
+ | users are actually retrieved out of your database or other storage
+ | mechanisms used by this application to persist your user's data.
+ |
+ | If you have multiple user tables or models you may configure multiple
+ | sources which represent each model / table. These sources may then
+ | be assigned to any extra authentication guards you have defined.
+ |
+ | Supported: "database", "eloquent"
+ |
+ */
+
+ 'providers' => [
+ 'users' => [
+ 'driver' => 'eloquent',
+ 'model' => App\User::class,
+ ],
+
+ // 'users' => [
+ // 'driver' => 'database',
+ // 'table' => 'users',
+ // ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Resetting Passwords
+ |--------------------------------------------------------------------------
+ |
+ | Here you may set the options for resetting passwords including the view
+ | that is your password reset e-mail. You may also set the name of the
+ | table that maintains all of the reset tokens for your application.
+ |
+ | You may specify multiple password reset configurations if you have more
+ | than one user table or model in the application and you want to have
+ | separate password reset settings based on the specific user types.
+ |
+ | The expire time is the number of minutes that the reset token should be
+ | considered valid. This security feature keeps tokens short-lived so
+ | they have less time to be guessed. You may change this as needed.
+ |
+ */
+
+ 'passwords' => [
+ 'users' => [
+ 'provider' => 'users',
+ 'email' => 'auth.emails.password',
+ 'table' => 'password_resets',
+ 'expire' => 60,
+ ],
+ ],
+
+];
diff --git a/config/broadcasting.php b/config/broadcasting.php
new file mode 100644
index 00000000..abaaac32
--- /dev/null
+++ b/config/broadcasting.php
@@ -0,0 +1,52 @@
+ env('BROADCAST_DRIVER', 'pusher'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Broadcast Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define all of the broadcast connections that will be used
+ | to broadcast events to other systems or over websockets. Samples of
+ | each available type of connection are provided inside this array.
+ |
+ */
+
+ 'connections' => [
+
+ 'pusher' => [
+ 'driver' => 'pusher',
+ 'key' => env('PUSHER_KEY'),
+ 'secret' => env('PUSHER_SECRET'),
+ 'app_id' => env('PUSHER_APP_ID'),
+ 'options' => [
+ //
+ ],
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'default',
+ ],
+
+ 'log' => [
+ 'driver' => 'log',
+ ],
+
+ ],
+
+];
diff --git a/config/cache.php b/config/cache.php
new file mode 100644
index 00000000..3ffa840b
--- /dev/null
+++ b/config/cache.php
@@ -0,0 +1,81 @@
+ env('CACHE_DRIVER', 'file'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Stores
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define all of the cache "stores" for your application as
+ | well as their drivers. You may even define multiple stores for the
+ | same cache driver to group types of items stored in your caches.
+ |
+ */
+
+ 'stores' => [
+
+ 'apc' => [
+ 'driver' => 'apc',
+ ],
+
+ 'array' => [
+ 'driver' => 'array',
+ ],
+
+ 'database' => [
+ 'driver' => 'database',
+ 'table' => 'cache',
+ 'connection' => null,
+ ],
+
+ 'file' => [
+ 'driver' => 'file',
+ 'path' => storage_path('framework/cache'),
+ ],
+
+ 'memcached' => [
+ 'driver' => 'memcached',
+ 'servers' => [
+ [
+ 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
+ 'port' => env('MEMCACHED_PORT', 11211),
+ 'weight' => 100,
+ ],
+ ],
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'default',
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Key Prefix
+ |--------------------------------------------------------------------------
+ |
+ | When utilizing a RAM based store such as APC or Memcached, there might
+ | be other applications utilizing the same cache. So, we'll specify a
+ | value to get prefixed to all our keys so we can avoid collisions.
+ |
+ */
+
+ 'prefix' => 'laravel',
+
+];
diff --git a/config/compile.php b/config/compile.php
new file mode 100644
index 00000000..04807eac
--- /dev/null
+++ b/config/compile.php
@@ -0,0 +1,35 @@
+ [
+ //
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Compiled File Providers
+ |--------------------------------------------------------------------------
+ |
+ | Here you may list service providers which define a "compiles" function
+ | that returns additional files that should be compiled, providing an
+ | easy way to get common files from any packages you are utilizing.
+ |
+ */
+
+ 'providers' => [
+ //
+ ],
+
+];
diff --git a/config/database.php b/config/database.php
new file mode 100644
index 00000000..598a7ff3
--- /dev/null
+++ b/config/database.php
@@ -0,0 +1,131 @@
+ PDO::FETCH_CLASS,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Default Database Connection Name
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify which of the database connections below you wish
+ | to use as your default connection for all database work. Of course
+ | you may use many connections at once using the Database library.
+ |
+ */
+
+ 'default' => env('DB_CONNECTION', 'mysql'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Database Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here are each of the database connections setup for your application.
+ | Of course, examples of configuring each database platform that is
+ | supported by Laravel is shown below to make development simple.
+ |
+ |
+ | All database work in Laravel is done through the PHP PDO facilities
+ | so make sure you have the driver for your particular database of
+ | choice installed on your machine before you begin development.
+ |
+ */
+
+ 'connections' => [
+
+ 'sqlite' => [
+ 'driver' => 'sqlite',
+ 'database' => env('DB_DATABASE', database_path('database.sqlite')),
+ 'prefix' => '',
+ ],
+
+ 'mysql' => [
+ 'driver' => 'mysql',
+ 'host' => env('DB_HOST', 'localhost'),
+ 'port' => env('DB_PORT', '3306'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
+ 'prefix' => '',
+ 'strict' => false,
+ 'engine' => null,
+ ],
+
+ 'pgsql' => [
+ 'driver' => 'pgsql',
+ 'host' => env('DB_HOST', 'localhost'),
+ 'port' => env('DB_PORT', '5432'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'schema' => 'public',
+ ],
+
+ 'travis' => [
+ 'driver' => 'pgsql',
+ 'host' => 'localhost',
+ 'database' => 'travis_ci_test',
+ 'username' => 'travis',
+ 'password' => '',
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'schema' => 'public',
+ ]
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Migration Repository Table
+ |--------------------------------------------------------------------------
+ |
+ | This table keeps track of all the migrations that have already run for
+ | your application. Using this information, we can determine which of
+ | the migrations on disk haven't actually been run in the database.
+ |
+ */
+
+ 'migrations' => 'migrations',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Redis Databases
+ |--------------------------------------------------------------------------
+ |
+ | Redis is an open source, fast, and advanced key-value store that also
+ | provides a richer set of commands than a typical key-value systems
+ | such as APC or Memcached. Laravel makes it easy to dig right in.
+ |
+ */
+
+ 'redis' => [
+
+ 'cluster' => false,
+
+ 'default' => [
+ 'host' => env('REDIS_HOST', 'localhost'),
+ 'password' => env('REDIS_PASSWORD', null),
+ 'port' => env('REDIS_PORT', 6379),
+ 'database' => 0,
+ ],
+
+ ],
+
+];
diff --git a/config/debugbar.php b/config/debugbar.php
new file mode 100644
index 00000000..7d46a634
--- /dev/null
+++ b/config/debugbar.php
@@ -0,0 +1,145 @@
+ null,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Storage settings
+ |--------------------------------------------------------------------------
+ |
+ | DebugBar stores data for session/ajax requests.
+ | You can disable this, so the debugbar stores data in headers/session,
+ | but this can cause problems with large data collectors.
+ | By default, file storage (in the storage folder) is used. Redis and PDO
+ | can also be used. For PDO, run the package migrations first.
+ |
+ */
+ 'storage' => array(
+ 'enabled' => true,
+ 'driver' => 'file', // redis, file, pdo
+ 'path' => storage_path() . '/debugbar', // For file driver
+ 'connection' => null, // Leave null for default connection (Redis/PDO)
+ ),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Vendors
+ |--------------------------------------------------------------------------
+ |
+ | Vendor files are included by default, but can be set to false.
+ | This can also be set to 'js' or 'css', to only include javascript or css vendor files.
+ | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
+ | and for js: jquery and and highlight.js
+ | So if you want syntax highlighting, set it to true.
+ | jQuery is set to not conflict with existing jQuery scripts.
+ |
+ */
+
+ 'include_vendors' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Capture Ajax Requests
+ |--------------------------------------------------------------------------
+ |
+ | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors),
+ | you can use this option to disable sending the data through the headers.
+ |
+ */
+
+ 'capture_ajax' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | DataCollectors
+ |--------------------------------------------------------------------------
+ |
+ | Enable/disable DataCollectors
+ |
+ */
+
+ 'collectors' => array(
+ 'phpinfo' => true, // Php version
+ 'messages' => true, // Messages
+ 'time' => true, // Time Datalogger
+ 'memory' => true, // Memory usage
+ 'exceptions' => true, // Exception displayer
+ 'log' => true, // Logs from Monolog (merged in messages if enabled)
+ 'db' => true, // Show database (PDO) queries and bindings
+ 'views' => true, // Views with their data
+ 'route' => true, // Current route information
+ 'laravel' => false, // Laravel version and environment
+ 'events' => false, // All events fired
+ 'default_request' => false, // Regular or special Symfony request logger
+ 'symfony_request' => true, // Only one can be enabled..
+ 'mail' => true, // Catch mail messages
+ 'logs' => false, // Add the latest log messages
+ 'files' => false, // Show the included files
+ 'config' => false, // Display config settings
+ 'auth' => false, // Display Laravel authentication status
+ 'session' => false, // Display session data in a separate tab
+ ),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Extra options
+ |--------------------------------------------------------------------------
+ |
+ | Configure some DataCollectors
+ |
+ */
+
+ 'options' => array(
+ 'auth' => array(
+ 'show_name' => false, // Also show the users name/email in the debugbar
+ ),
+ 'db' => array(
+ 'with_params' => true, // Render SQL with the parameters substituted
+ 'timeline' => false, // Add the queries to the timeline
+ 'backtrace' => false, // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files.
+ 'explain' => array( // EXPERIMENTAL: Show EXPLAIN output on queries
+ 'enabled' => false,
+ 'types' => array('SELECT'), // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+
+ ),
+ 'hints' => true, // Show hints for common mistakes
+ ),
+ 'mail' => array(
+ 'full_log' => false
+ ),
+ 'views' => array(
+ 'data' => false, //Note: Can slow down the application, because the data can be quite large..
+ ),
+ 'route' => array(
+ 'label' => true // show complete route on bar
+ ),
+ 'logs' => array(
+ 'file' => null
+ ),
+ ),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Inject Debugbar in Response
+ |--------------------------------------------------------------------------
+ |
+ | Usually, the debugbar is added just before , by listening to the
+ | Response after the App is done. If you disable this, you have to add them
+ | in your template yourself. See http://phpdebugbar.com/docs/rendering.html
+ |
+ */
+
+ 'inject' => true,
+
+);
diff --git a/config/filesystems.php b/config/filesystems.php
new file mode 100644
index 00000000..1c46f7c4
--- /dev/null
+++ b/config/filesystems.php
@@ -0,0 +1,72 @@
+ 'local',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Default Cloud Filesystem Disk
+ |--------------------------------------------------------------------------
+ |
+ | Many applications store files both locally and in the cloud. For this
+ | reason, you may specify a default "cloud" driver here. This driver
+ | will be bound as the Cloud disk implementation in the container.
+ |
+ */
+
+ 'cloud' => 's3',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Filesystem Disks
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure as many filesystem "disks" as you wish, and you
+ | may even configure multiple disks of the same driver. Defaults have
+ | been setup for each driver as an example of the required options.
+ |
+ */
+
+ 'disks' => [
+
+ 'local' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app'),
+ ],
+
+ 'public' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/public'),
+ 'visibility' => 'public',
+ ],
+
+ 's3' => [
+ 'driver' => 's3',
+ 'key' => env('AWS_S3_KEY'),
+ 'secret' => env('AWS_S3_SECRET'),
+ 'region' => env('AWS_S3_REGION'),
+ 'bucket' => env('AWS_S3_BUCKET'),
+ ],
+
+ 'media' => [
+ 'driver' => 'local',
+ 'root' => public_path() . '/media',
+ ],
+
+ ],
+
+];
diff --git a/config/laravel-medialibrary.php b/config/laravel-medialibrary.php
new file mode 100644
index 00000000..c6abd4c5
--- /dev/null
+++ b/config/laravel-medialibrary.php
@@ -0,0 +1,40 @@
+ 'media',
+
+ /*
+ * The maximum file size of an item in bytes. Adding a file
+ * that is larger will result in an exception.
+ */
+ 'max_file_size' => 1024 * 1024 * 10,
+
+ /*
+ * This queue will used to generate derived images.
+ * Leave empty to use the default queue.
+ */
+ 'queue_name' => '',
+
+ /*
+ * The class name of the media model to be used.
+ */
+ 'media_model' => Spatie\MediaLibrary\Media::class,
+
+ /*
+ * When urls to files get generated this class will be called. Leave empty
+ * if your files are stored locally above the site root or on s3.
+ */
+ 'custom_url_generator_class' => '',
+
+ 's3' => [
+ /*
+ * The domain that should be prepended when generating urls.
+ */
+ 'domain' => env('AWS_S3_URL'),
+ ],
+];
diff --git a/config/mail.php b/config/mail.php
new file mode 100644
index 00000000..a0765885
--- /dev/null
+++ b/config/mail.php
@@ -0,0 +1,112 @@
+ env('MAIL_DRIVER', 'smtp'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | SMTP Host Address
+ |--------------------------------------------------------------------------
+ |
+ | Here you may provide the host address of the SMTP server used by your
+ | applications. A default option is provided that is compatible with
+ | the Mailgun mail service which will provide reliable deliveries.
+ |
+ */
+
+ 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | SMTP Host Port
+ |--------------------------------------------------------------------------
+ |
+ | This is the SMTP port used by your application to deliver e-mails to
+ | users of the application. Like the host we have set this value to
+ | stay compatible with the Mailgun e-mail application by default.
+ |
+ */
+
+ 'port' => env('MAIL_PORT', 587),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Global "From" Address
+ |--------------------------------------------------------------------------
+ |
+ | You may wish for all e-mails sent by your application to be sent from
+ | the same address. Here, you may specify a name and address that is
+ | used globally for all e-mails that are sent by your application.
+ |
+ */
+
+ 'from' => ['address' => null, 'name' => null],
+
+ /*
+ |--------------------------------------------------------------------------
+ | E-Mail Encryption Protocol
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the encryption protocol that should be used when
+ | the application send e-mail messages. A sensible default using the
+ | transport layer security protocol should provide great security.
+ |
+ */
+
+ 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | SMTP Server Username
+ |--------------------------------------------------------------------------
+ |
+ | If your SMTP server requires a username for authentication, you should
+ | set it here. This will get used to authenticate with your server on
+ | connection. You may also set the "password" value below this one.
+ |
+ */
+
+ 'username' => env('MAIL_USERNAME'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | SMTP Server Password
+ |--------------------------------------------------------------------------
+ |
+ | Here you may set the password required by your SMTP server to send out
+ | messages from your application. This will be given to the server on
+ | connection so that the application will be able to send messages.
+ |
+ */
+
+ 'password' => env('MAIL_PASSWORD'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Sendmail System Path
+ |--------------------------------------------------------------------------
+ |
+ | When using the "sendmail" driver to send e-mails, we will need to know
+ | the path to where Sendmail lives on this server. A default path has
+ | been provided here, which will work well on most of your systems.
+ |
+ */
+
+ 'sendmail' => '/usr/sbin/sendmail -bs',
+
+];
diff --git a/config/queue.php b/config/queue.php
new file mode 100644
index 00000000..d0f732a6
--- /dev/null
+++ b/config/queue.php
@@ -0,0 +1,85 @@
+ env('QUEUE_DRIVER', 'sync'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Queue Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the connection information for each server that
+ | is used by your application. A default configuration has been added
+ | for each back-end shipped with Laravel. You are free to add more.
+ |
+ */
+
+ 'connections' => [
+
+ 'sync' => [
+ 'driver' => 'sync',
+ ],
+
+ 'database' => [
+ 'driver' => 'database',
+ 'table' => 'jobs',
+ 'queue' => 'default',
+ 'expire' => 60,
+ ],
+
+ 'beanstalkd' => [
+ 'driver' => 'beanstalkd',
+ 'host' => 'localhost',
+ 'queue' => 'default',
+ 'ttr' => 60,
+ ],
+
+ 'sqs' => [
+ 'driver' => 'sqs',
+ 'key' => 'your-public-key',
+ 'secret' => 'your-secret-key',
+ 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id',
+ 'queue' => 'your-queue-name',
+ 'region' => 'us-east-1',
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'default',
+ 'queue' => 'default',
+ 'expire' => 60,
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Failed Queue Jobs
+ |--------------------------------------------------------------------------
+ |
+ | These options configure the behavior of failed queue job logging so you
+ | can control which database and table are used to store the jobs that
+ | have failed. You may change them to any database / table you wish.
+ |
+ */
+
+ 'failed' => [
+ 'database' => env('DB_CONNECTION', 'mysql'),
+ 'table' => 'failed_jobs',
+ ],
+
+];
diff --git a/config/services.php b/config/services.php
new file mode 100644
index 00000000..287b1186
--- /dev/null
+++ b/config/services.php
@@ -0,0 +1,38 @@
+ [
+ 'domain' => env('MAILGUN_DOMAIN'),
+ 'secret' => env('MAILGUN_SECRET'),
+ ],
+
+ 'ses' => [
+ 'key' => env('SES_KEY'),
+ 'secret' => env('SES_SECRET'),
+ 'region' => 'us-east-1',
+ ],
+
+ 'sparkpost' => [
+ 'secret' => env('SPARKPOST_SECRET'),
+ ],
+
+ 'stripe' => [
+ 'model' => App\User::class,
+ 'key' => env('STRIPE_KEY'),
+ 'secret' => env('STRIPE_SECRET'),
+ ],
+
+];
diff --git a/config/session.php b/config/session.php
new file mode 100644
index 00000000..33f62e49
--- /dev/null
+++ b/config/session.php
@@ -0,0 +1,166 @@
+ env('SESSION_DRIVER', 'file'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Lifetime
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the number of minutes that you wish the session
+ | to be allowed to remain idle before it expires. If you want them
+ | to immediately expire on the browser closing, set that option.
+ |
+ */
+
+ 'lifetime' => 60 * 24 * 7,
+
+ 'expire_on_close' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Encryption
+ |--------------------------------------------------------------------------
+ |
+ | This option allows you to easily specify that all of your session data
+ | should be encrypted before it is stored. All encryption will be run
+ | automatically by Laravel and you can use the Session like normal.
+ |
+ */
+
+ 'encrypt' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session File Location
+ |--------------------------------------------------------------------------
+ |
+ | When using the native session driver, we need a location where session
+ | files may be stored. A default has been set for you but a different
+ | location may be specified. This is only needed for file sessions.
+ |
+ */
+
+ 'files' => storage_path('framework/sessions'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Database Connection
+ |--------------------------------------------------------------------------
+ |
+ | When using the "database" or "redis" session drivers, you may specify a
+ | connection that should be used to manage these sessions. This should
+ | correspond to a connection in your database configuration options.
+ |
+ */
+
+ 'connection' => null,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Database Table
+ |--------------------------------------------------------------------------
+ |
+ | When using the "database" session driver, you may specify the table we
+ | should use to manage the sessions. Of course, a sensible default is
+ | provided for you; however, you are free to change this as needed.
+ |
+ */
+
+ 'table' => 'sessions',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Sweeping Lottery
+ |--------------------------------------------------------------------------
+ |
+ | Some session drivers must manually sweep their storage location to get
+ | rid of old sessions from storage. Here are the chances that it will
+ | happen on a given request. By default, the odds are 2 out of 100.
+ |
+ */
+
+ 'lottery' => [2, 100],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Name
+ |--------------------------------------------------------------------------
+ |
+ | Here you may change the name of the cookie used to identify a session
+ | instance by ID. The name specified here will get used every time a
+ | new session cookie is created by the framework for every driver.
+ |
+ */
+
+ 'cookie' => 'laravel_session',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Path
+ |--------------------------------------------------------------------------
+ |
+ | The session cookie path determines the path for which the cookie will
+ | be regarded as available. Typically, this will be the root path of
+ | your application but you are free to change this when necessary.
+ |
+ */
+
+ 'path' => '/',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Domain
+ |--------------------------------------------------------------------------
+ |
+ | Here you may change the domain of the cookie used to identify a session
+ | in your application. This will determine which domains the cookie is
+ | available to in your application. A sensible default has been set.
+ |
+ */
+
+ 'domain' => null,
+
+ /*
+ |--------------------------------------------------------------------------
+ | HTTPS Only Cookies
+ |--------------------------------------------------------------------------
+ |
+ | By setting this option to true, session cookies will only be sent back
+ | to the server if the browser has a HTTPS connection. This will keep
+ | the cookie from being sent to you if it can not be done securely.
+ |
+ */
+
+ 'secure' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | HTTP Access Only
+ |--------------------------------------------------------------------------
+ |
+ | Setting this value to true will prevent JavaScript from accessing the
+ | value of the cookie and the cookie will only be accessible through
+ | the HTTP protocol. You are free to modify this option if needed.
+ |
+ */
+
+ 'http_only' => true,
+
+];
diff --git a/config/ttwitter.php b/config/ttwitter.php
new file mode 100644
index 00000000..8c6e4ec2
--- /dev/null
+++ b/config/ttwitter.php
@@ -0,0 +1,18 @@
+ 'api.twitter.com',
+ 'API_VERSION' => '1.1',
+ 'AUTHENTICATE_URL' => 'https://api.twitter.com/oauth/authenticate',
+ 'AUTHORIZE_URL' => 'https://api.twitter.com/oauth/authorize',
+ 'ACCESS_TOKEN_URL' => 'oauth/access_token',
+ 'REQUEST_TOKEN_URL' => 'oauth/request_token',
+ 'USE_SSL' => true,
+
+ 'CONSUMER_KEY' => env('TWITTER_CONSUMER_KEY'),
+ 'CONSUMER_SECRET' => env('TWITTER_CONSUMER_SECRET'),
+ 'ACCESS_TOKEN' => env('TWITTER_ACCESS_TOKEN'),
+ 'ACCESS_TOKEN_SECRET' => env('TWITTER_ACCESS_TOKEN_SECRET'),
+ ];
diff --git a/config/url.php b/config/url.php
new file mode 100644
index 00000000..96767e3f
--- /dev/null
+++ b/config/url.php
@@ -0,0 +1,11 @@
+ env('APP_LONGURL', 'jonnybarnes.uk'),
+ 'shorturl' => env('APP_SHORTURL', 'jmb.so')
+];
\ No newline at end of file
diff --git a/config/view.php b/config/view.php
new file mode 100644
index 00000000..e193ab61
--- /dev/null
+++ b/config/view.php
@@ -0,0 +1,33 @@
+ [
+ realpath(base_path('resources/views')),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Compiled View Path
+ |--------------------------------------------------------------------------
+ |
+ | This option determines where all the compiled Blade templates will be
+ | stored for your application. Typically, this is within the storage
+ | directory. However, as usual, you are free to change this value.
+ |
+ */
+
+ 'compiled' => realpath(storage_path('framework/views')),
+
+];
diff --git a/database/.gitignore b/database/.gitignore
new file mode 100644
index 00000000..9b1dffd9
--- /dev/null
+++ b/database/.gitignore
@@ -0,0 +1 @@
+*.sqlite
diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php
new file mode 100644
index 00000000..df9993c9
--- /dev/null
+++ b/database/factories/ModelFactory.php
@@ -0,0 +1,28 @@
+define(App\User::class, function (Faker\Generator $faker) {
+ return [
+ 'name' => $faker->name,
+ 'email' => $faker->safeEmail,
+ 'password' => bcrypt(str_random(10)),
+ 'remember_token' => str_random(10),
+ ];
+});
+
+$factory->define(App\Note::class, function (Faker\Generator $faker) {
+ return [
+ 'note' => $faker->paragraph,
+ 'tweet_id' => $faker->randomNumber(9),
+ ];
+});
diff --git a/database/migrations/.gitkeep b/database/migrations/.gitkeep
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/database/migrations/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/database/migrations/2015_02_28_132629_create_articles_table.php b/database/migrations/2015_02_28_132629_create_articles_table.php
new file mode 100644
index 00000000..43277762
--- /dev/null
+++ b/database/migrations/2015_02_28_132629_create_articles_table.php
@@ -0,0 +1,38 @@
+increments('id');
+ $table->string('titleurl', 50)->unique();
+ $table->string('url', 120)->nullable();
+ $table->string('title');
+ $table->longText('main');
+ $table->tinyInteger('published')->default(0);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('articles');
+ }
+}
diff --git a/database/migrations/2015_02_28_144939_create_notes_table.php b/database/migrations/2015_02_28_144939_create_notes_table.php
new file mode 100644
index 00000000..33e22669
--- /dev/null
+++ b/database/migrations/2015_02_28_144939_create_notes_table.php
@@ -0,0 +1,40 @@
+increments('id');
+ $table->text('note');
+ $table->string('in_reply_to')->nullable();
+ $table->string('shorturl', 20)->nullable();
+ $table->string('location')->nullable();
+ $table->tinyInteger('photo')->nullable();
+ $table->string('tweet_id')->nullable();
+ $table->string('client_id')->nullable();
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('notes');
+ }
+}
diff --git a/database/migrations/2015_03_02_084342_create_tags_table.php b/database/migrations/2015_03_02_084342_create_tags_table.php
new file mode 100644
index 00000000..0e1175ea
--- /dev/null
+++ b/database/migrations/2015_03_02_084342_create_tags_table.php
@@ -0,0 +1,31 @@
+increments('id');
+ $table->string('tag');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('tags');
+ }
+}
diff --git a/database/migrations/2015_03_02_084956_create_note_tag_table.php b/database/migrations/2015_03_02_084956_create_note_tag_table.php
new file mode 100644
index 00000000..831cff30
--- /dev/null
+++ b/database/migrations/2015_03_02_084956_create_note_tag_table.php
@@ -0,0 +1,33 @@
+increments('id');
+ $table->integer('note_id')->unsigned();
+ $table->integer('tag_id')->unsigned();
+ $table->foreign('note_id')->references('id')->on('notes');
+ $table->foreign('tag_id')->references('id')->on('tags');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('note_tag');
+ }
+}
diff --git a/database/migrations/2015_03_02_105623_create_contacts_table.php b/database/migrations/2015_03_02_105623_create_contacts_table.php
new file mode 100644
index 00000000..b0b1e3ec
--- /dev/null
+++ b/database/migrations/2015_03_02_105623_create_contacts_table.php
@@ -0,0 +1,34 @@
+increments('id');
+ $table->string('nick');
+ $table->string('name');
+ $table->string('homepage')->nullable();
+ $table->string('twitter')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('contacts');
+ }
+}
diff --git a/database/migrations/2015_03_02_114340_create_web_mentions_table.php b/database/migrations/2015_03_02_114340_create_web_mentions_table.php
new file mode 100644
index 00000000..edbbfba3
--- /dev/null
+++ b/database/migrations/2015_03_02_114340_create_web_mentions_table.php
@@ -0,0 +1,39 @@
+increments('id');
+ $table->string('source');
+ $table->string('target');
+ $table->integer('commentable_id')->nullable();
+ $table->string('commentable_type')->nullable();
+ $table->string('type')->nullable();
+ $table->text('content');
+ $table->tinyInteger('verified')->default(1);
+ $table->timestamps();
+ $table->softDeletes();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('webmentions');
+ }
+}
diff --git a/database/migrations/2015_07_17_111512_create_clients_table.php b/database/migrations/2015_07_17_111512_create_clients_table.php
new file mode 100644
index 00000000..4821d16b
--- /dev/null
+++ b/database/migrations/2015_07_17_111512_create_clients_table.php
@@ -0,0 +1,32 @@
+increments('id');
+ $table->string('client_url');
+ $table->string('client_name');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('clients');
+ }
+}
diff --git a/database/migrations/2015_07_22_084423_create_failed_jobs_table.php b/database/migrations/2015_07_22_084423_create_failed_jobs_table.php
new file mode 100644
index 00000000..c1ba41b4
--- /dev/null
+++ b/database/migrations/2015_07_22_084423_create_failed_jobs_table.php
@@ -0,0 +1,33 @@
+increments('id');
+ $table->text('connection');
+ $table->text('queue');
+ $table->longText('payload');
+ $table->timestamp('failed_at');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('failed_jobs');
+ }
+}
diff --git a/database/migrations/2015_10_08_155111_create_media_table.php b/database/migrations/2015_10_08_155111_create_media_table.php
new file mode 100644
index 00000000..15a3ce03
--- /dev/null
+++ b/database/migrations/2015_10_08_155111_create_media_table.php
@@ -0,0 +1,35 @@
+increments('id');
+ $table->morphs('model');
+ $table->string('collection_name');
+ $table->string('name');
+ $table->string('file_name');
+ $table->string('disk');
+ $table->unsignedInteger('size');
+ $table->text('manipulations');
+ $table->text('custom_properties');
+ $table->unsignedInteger('order_column')->nullable();
+ $table->timestamps();
+ });
+ }
+ /**
+ * Reverse the migrations.
+ */
+ public function down()
+ {
+ Schema::drop('media');
+ }
+}
diff --git a/database/migrations/2015_11_07_130637_create_places_table.php b/database/migrations/2015_11_07_130637_create_places_table.php
new file mode 100644
index 00000000..3d6927c0
--- /dev/null
+++ b/database/migrations/2015_11_07_130637_create_places_table.php
@@ -0,0 +1,35 @@
+increments('id');
+ $table->string('name');
+ $table->string('slug')->unique();
+ $table->text('description')->nullable();
+ $table->point('location');
+ $table->polygon('polygon')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::drop('places');
+ }
+}
diff --git a/database/migrations/2015_11_19_221933_add_place_relation_to_notes.php b/database/migrations/2015_11_19_221933_add_place_relation_to_notes.php
new file mode 100644
index 00000000..a999d7bd
--- /dev/null
+++ b/database/migrations/2015_11_19_221933_add_place_relation_to_notes.php
@@ -0,0 +1,33 @@
+integer('place_id')->unsigned()->nullable();
+ $table->foreign('place_id')->references('id')->on('places');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('notes', function (Blueprint $table) {
+ $table->dropForeign('notes_place_id_foreign');
+ $table->dropColumn('place_id');
+ });
+ }
+}
diff --git a/database/seeds/.gitkeep b/database/seeds/.gitkeep
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/database/seeds/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/database/seeds/ArticlesTableSeeder.php b/database/seeds/ArticlesTableSeeder.php
new file mode 100644
index 00000000..af99e3c2
--- /dev/null
+++ b/database/seeds/ArticlesTableSeeder.php
@@ -0,0 +1,23 @@
+insert([
+ 'titleurl' => 'my-new-blog',
+ 'title' => 'My New Blog',
+ 'main' => 'This is my new blog. It uses `Markdown`.',
+ 'published' => 1,
+ 'created_at' => '2016-01-12 15:51:01',
+ 'updated_at' => '2016-01-12 15:51:01',
+ ]);
+ }
+}
diff --git a/database/seeds/ClientsTableSeeder.php b/database/seeds/ClientsTableSeeder.php
new file mode 100644
index 00000000..5d833f82
--- /dev/null
+++ b/database/seeds/ClientsTableSeeder.php
@@ -0,0 +1,21 @@
+insert([
+ 'client_url' => 'https://jbl5.dev/notes/new',
+ 'client_name' => 'JBL5',
+ 'created_at' => '2016-01-12 16:03:00',
+ 'updated_at' => '2016-01-12 16:03:00',
+ ]);
+ }
+}
diff --git a/database/seeds/ContactsTableSeeder.php b/database/seeds/ContactsTableSeeder.php
new file mode 100644
index 00000000..8ce80a0b
--- /dev/null
+++ b/database/seeds/ContactsTableSeeder.php
@@ -0,0 +1,32 @@
+insert([
+ 'nick' => 'tantek',
+ 'name' => 'Tantek Çelik',
+ 'homepage' => 'http://tantek.com',
+ 'twitter' => 't',
+ 'created_at' => '2016-01-12 16:11:00',
+ 'updated_at' => '2016-01-12 16:11:00',
+ ]);
+
+ DB::table('contacts')->insert([
+ 'nick' => 'aaron',
+ 'name' => 'Aaron Parecki',
+ 'homepage' => 'https://aaronparecki.com',
+ 'twitter' => 'aaronpk',
+ 'created_at' => '2016-01-12 16:12:00',
+ 'updated_at' => '2016-01-12 16:12:00',
+ ]);
+ }
+}
diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php
new file mode 100644
index 00000000..802cddc6
--- /dev/null
+++ b/database/seeds/DatabaseSeeder.php
@@ -0,0 +1,20 @@
+call(ArticlesTableSeeder::class);
+ $this->call(ClientsTableSeeder::class);
+ $this->call(ContactsTableSeeder::class);
+ $this->call(PlacesTableSeeder::class);
+ $this->call(NotesTableSeeder::class);
+ }
+}
diff --git a/database/seeds/NotesTableSeeder.php b/database/seeds/NotesTableSeeder.php
new file mode 100644
index 00000000..9fa8ccd1
--- /dev/null
+++ b/database/seeds/NotesTableSeeder.php
@@ -0,0 +1,39 @@
+create();
+ $noteWithPlace = App\Note::create([
+ 'note' => 'Having a #beer at the local.'
+ ]);
+ $place = App\Place::find(1);
+ $noteWithPlace->place()->associate($place);
+ $noteWithPlace->save();
+ $noteWithContact = App\Note::create([
+ 'note' => 'Hi @tantek'
+ ]);
+ $noteWithContactPlusPic = App\Note::create([
+ 'note' => 'Hi @aaron',
+ 'client_id' => 'https://jbl5.dev/notes/new'
+ ]);
+ $noteWithoutContact = App\Note::create([
+ 'note' => 'Hi @bob',
+ 'client_id' => 'https://quill.p3k.io'
+ ]);
+ //copy aaron’s profile pic in place
+ $spl = new SplFileInfo(public_path() . '/assets/profile-images/aaronparecki.com');
+ if ($spl->isDir() === false) {
+ mkdir(public_path() . '/assets/profile-images/aaronparecki.com', 0755);
+ copy(base_path() . '/tests/aaron.png', public_path() . '/assets/profile-images/aaronparecki.com/image');
+ }
+ }
+}
diff --git a/database/seeds/PlacesTableSeeder.php b/database/seeds/PlacesTableSeeder.php
new file mode 100644
index 00000000..34302d50
--- /dev/null
+++ b/database/seeds/PlacesTableSeeder.php
@@ -0,0 +1,23 @@
+insert([
+ 'name' => 'The Bridgewater Pub',
+ 'slug' => 'the-bridgewater-pub',
+ 'description' => 'A lovely local pub with a decent selection pf cask ales',
+ 'location' => 'POINT(-2.3805 53.4983)',
+ 'created_at' => '2016-01-12 16:19:00',
+ 'updated_at' => '2016-01-12 16:19:00',
+ ]);
+ }
+}
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 00000000..6395c379
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,81 @@
+var gulp = require('gulp');
+var zopfli = require('gulp-zopfli');
+var brotli = require('gulp-brotli');
+var elixir = require('laravel-elixir');
+
+/*
+ |--------------------------------------------------------------------------
+ | Elixir Asset Management
+ |--------------------------------------------------------------------------
+ |
+ | Elixir provides a clean, fluent API for defining some basic Gulp tasks
+ | for your Laravel application. By default, we are compiling the Sass
+ | file for our application, as well as publishing vendor resources.
+ |
+ */
+
+elixir(function(mix) {
+ mix.sass('global.scss', 'public/assets/css');
+ mix.version([
+ 'assets/css/global.css',
+ 'assets/css/projects.css',
+ 'assets/css/alertify.css',
+ 'assets/css/sanitize.min.css',
+ 'assets/css/prism.css',
+ 'assets/js/form-save.js',
+ 'assets/js/links.js',
+ 'assets/js/maps.js',
+ 'assets/js/newplace.js',
+ 'assets/js/newnote.js',
+ 'assets/js/fetch.js',
+ 'assets/js/alertify.js',
+ 'assets/js/store2.min.js',
+ 'assets/js/Autolinker.min.js',
+ 'assets/js/marked.min.js',
+ 'assets/js/prism.js',
+ ]);
+});
+
+gulp.task('gzip-built-css', function() {
+ return gulp.src('public/build/assets/css/*.css')
+ .pipe(zopfli({ format: 'gzip', append: true }))
+ .pipe(gulp.dest('public/build/assets/css/'));
+});
+
+gulp.task('br-built-css', function() {
+ return gulp.src('public/build/assets/css/*.css')
+ .pipe(brotli.compress({mode: 1, quality: 11}))
+ .pipe(gulp.dest('public/build/assets/css/'));
+});
+
+gulp.task('gzip-built-js', function() {
+ return gulp.src('public/build/assets/js/*.js')
+ .pipe(zopfli({ format: 'gzip', append: true }))
+ .pipe(gulp.dest('public/build/assets/js/'));
+});
+
+gulp.task('br-built-js', function() {
+ return gulp.src('public/build/assets/js/*.js')
+ .pipe(brotli.compress({mode: 1, quality: 11}))
+ .pipe(gulp.dest('public/build/assets/js/'));
+});
+
+gulp.task('bower', function() {
+ //copy JS files
+ gulp.src([
+ 'bower_components/fetch/fetch.js',
+ 'bower_components/alertify.js/dist/js/alertify.js',
+ 'bower_components/store2/dist/store2.min.js',
+ 'bower_components/Autolinker.js/dist/Autolinker.min.js',
+ 'bower_components/marked/marked.min.js',
+ ])
+ .pipe(gulp.dest('public/assets/js/'));
+ //copy CSS files
+ gulp.src([
+ 'bower_components/alertify.js/dist/css/alertify.css',
+ 'bower_components/sanitize-css/dist/sanitize.min.css',
+ ])
+ .pipe(gulp.dest('public/assets/css/'));
+});
+
+gulp.task('compress', ['gzip-built-css', 'br-built-css', 'gzip-built-js', 'br-built-js']);
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..d8015224
--- /dev/null
+++ b/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "jbuk-frontend",
+ "version": "0.0.1",
+ "repository": "https://github.com/jonnybarnes/jbl5",
+ "license": "CC0-1.0",
+ "devDependencies": {
+ "gulp": "~3.9",
+ "gulp-brotli": "^1.0.1",
+ "gulp-zopfli": "^1.0.0",
+ "laravel-elixir": "^5.0.0"
+ },
+ "private": true,
+ "scripts": {
+ "prod": "gulp --production",
+ "dev": "gulp watch"
+ }
+}
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 00000000..3e884d17
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ ./tests
+
+
+
+
+ ./app
+
+ ./app/Http/routes.php
+
+
+
+
+
+
+
+
+
+
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 00000000..903f6392
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,20 @@
+
+
+ Options -MultiViews
+
+
+ RewriteEngine On
+
+ # Redirect Trailing Slashes If Not A Folder...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^(.*)/$ /$1 [L,R=301]
+
+ # Handle Front Controller...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [L]
+
+ # Handle Authorization Header
+ RewriteCond %{HTTP:Authorization} .
+ RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+
diff --git a/public/assets/css/alertify.css b/public/assets/css/alertify.css
new file mode 100644
index 00000000..ced38bdd
--- /dev/null
+++ b/public/assets/css/alertify.css
@@ -0,0 +1 @@
+.alertify-logs>*{padding:12px 24px;color:#fff;box-shadow:0 2px 5px 0 rgba(0,0,0,.2);border-radius:1px}.alertify-logs>*,.alertify-logs>.default{background:rgba(0,0,0,.8)}.alertify-logs>.error{background:rgba(244,67,54,.8)}.alertify-logs>.success{background:rgba(76,175,80,.9)}.alertify{position:fixed;background-color:rgba(0,0,0,.3);left:0;right:0;top:0;bottom:0;width:100%;height:100%;z-index:2}.alertify.hide{opacity:0;pointer-events:none}.alertify,.alertify.show{box-sizing:border-box;transition:all .33s cubic-bezier(.25,.8,.25,1)}.alertify,.alertify *{box-sizing:border-box}.alertify .dialog{padding:12px}.alertify .alert,.alertify .dialog{width:100%;margin:0 auto;position:relative;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.alertify .alert>*,.alertify .dialog>*{width:400px;max-width:95%;margin:0 auto;text-align:center;padding:12px;background:#fff;box-shadow:0 2px 4px -1px rgba(0,0,0,.14),0 4px 5px 0 rgba(0,0,0,.098),0 1px 10px 0 rgba(0,0,0,.084)}.alertify .alert .msg,.alertify .dialog .msg{padding:12px;margin-bottom:12px;margin:0;text-align:left}.alertify .alert input:not(.form-control),.alertify .dialog input:not(.form-control){margin-bottom:15px;width:100%;font-size:100%;padding:12px}.alertify .alert input:not(.form-control):focus,.alertify .dialog input:not(.form-control):focus{outline-offset:-2px}.alertify .alert nav,.alertify .dialog nav{text-align:right}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button),.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button){background:transparent;box-sizing:border-box;color:rgba(0,0,0,.87);position:relative;outline:0;border:0;display:inline-block;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;padding:0 6px;margin:6px 8px;line-height:36px;min-height:36px;white-space:nowrap;min-width:88px;text-align:center;text-transform:uppercase;font-size:14px;text-decoration:none;cursor:pointer;border:1px solid transparent;border-radius:2px}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover{background-color:rgba(0,0,0,.05)}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus{border:1px solid rgba(0,0,0,.1)}.alertify .alert nav button.btn,.alertify .dialog nav button.btn{margin:6px 4px}.alertify-logs{position:fixed;z-index:1}.alertify-logs.bottom,.alertify-logs:not(.top){bottom:16px}.alertify-logs.left,.alertify-logs:not(.right){left:16px}.alertify-logs.left>*,.alertify-logs:not(.right)>*{float:left;-webkit-transform:translateZ(0);transform:translateZ(0);height:auto}.alertify-logs.left>.show,.alertify-logs:not(.right)>.show{left:0}.alertify-logs.left>*,.alertify-logs.left>.hide,.alertify-logs:not(.right)>*,.alertify-logs:not(.right)>.hide{left:-110%}.alertify-logs.right{right:16px}.alertify-logs.right>*{float:right;-webkit-transform:translateZ(0);transform:translateZ(0)}.alertify-logs.right>.show{right:0;opacity:1}.alertify-logs.right>*,.alertify-logs.right>.hide{right:-110%;opacity:0}.alertify-logs.top{top:0}.alertify-logs>*{box-sizing:border-box;transition:all .4s cubic-bezier(.25,.8,.25,1);position:relative;clear:both;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000;max-height:0;margin:0;padding:0;overflow:hidden;opacity:0;pointer-events:none}.alertify-logs>.show{margin-top:12px;opacity:1;max-height:1000px;padding:12px;pointer-events:auto}
\ No newline at end of file
diff --git a/public/assets/css/global.css b/public/assets/css/global.css
new file mode 100644
index 00000000..1750d873
--- /dev/null
+++ b/public/assets/css/global.css
@@ -0,0 +1,243 @@
+html {
+ background: url("/assets/img/escheresque.png"); }
+
+.map {
+ height: 150px; }
+
+html {
+ box-sizing: border-box; }
+
+*, *:before, *:after {
+ box-sizing: inherit; }
+
+#topheader {
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-flow: row;
+ -ms-flex-flow: row;
+ flex-flow: row; }
+
+#topheader a {
+ padding: 0.5em 1em; }
+
+nav {
+ padding-top: 0.5em; }
+
+.social-list {
+ padding-left: 2em; }
+
+.note {
+ background-color: #eee8d5;
+ box-shadow: 0 0 10px 2px #93a1a1;
+ padding: 0.5em 0.5em;
+ margin-top: 1em; }
+
+.note:after {
+ content: " ";
+ display: block;
+ height: 0;
+ clear: both; }
+
+.note a {
+ word-wrap: break-word; }
+
+.note .e-content p:first-child {
+ margin-top: 0; }
+
+.note-metadata {
+ width: 100%; }
+
+.social-links {
+ float: right; }
+
+.social-links a {
+ text-decoration: none; }
+
+.icon {
+ width: auto;
+ height: 1em;
+ fill: #268bd2; }
+
+.reply {
+ margin-left: 2em;
+ margin-right: 2em;
+ font-size: 0.8em;
+ padding: 0.5em 0.5em; }
+
+.reply-to {
+ margin-left: 2em;
+ margin-right: 2em;
+ font-size: 0.8em;
+ padding-top: 2em; }
+
+.reply-to + .note {
+ margin-top: 0.3em; }
+
+.mini-h-card {
+ border-radius: 2px;
+ border: 1px solid #586e75;
+ padding: 0 0.2em;
+ text-decoration: none;
+ margin-right: 5px;
+ white-space: nowrap; }
+
+.mini-h-card img {
+ height: 1.26em;
+ display: inline;
+ border-radius: 2px;
+ vertical-align: text-bottom; }
+
+.like-photo {
+ height: 1.26em; }
+
+.reply .e-content {
+ margin-top: 0.5em;
+ padding-left: 0.5em; }
+
+.notes-subtitle {
+ font-size: 1em; }
+
+.note-photo {
+ width: 100%;
+ height: auto;
+ image-orientation: from-image; }
+
+article header {
+ margin-top: 0.5em;
+ margin-bottom: 0.8em; }
+
+.post-info {
+ font-size: 0.8em;
+ font-style: italic;
+ margin-top: -0.8em; }
+
+.contact {
+ position: relative; }
+
+.contact-links {
+ list-style-type: none; }
+
+.contact img {
+ height: auto;
+ width: 2em;
+ position: absolute;
+ top: 0;
+ left: 0; }
+
+.contact-info {
+ margin-left: 2em; }
+
+#map {
+ height: 300px; }
+
+/* media queries */
+@media (min-width: 700px) {
+ main {
+ margin-left: 10em;
+ margin-right: 10em; }
+ footer {
+ margin-left: 13em;
+ margin-right: 13em; }
+ .youtube {
+ width: 640px;
+ height: 360px; } }
+
+@media (max-width: 699px) {
+ main {
+ margin-left: 10px;
+ margin-right: 10px; }
+ article {
+ word-wrap: break-word; }
+ footer {
+ margin-left: 15px;
+ margin-right: 15px; }
+ .youtube {
+ width: 100%;
+ height: auto; } }
+
+body {
+ text-rendering: optimizeLegibility;
+ -webkit-font-feature-settings: "liga";
+ font-feature-settings: "liga";
+ font-family: "leitura-news", serif;
+ font-size: 1.2em; }
+
+#topheader h1 {
+ font-family: "leitura-news", serif; }
+
+h1 {
+ font-family: "prenton", sans-serif; }
+
+#topheader a {
+ text-decoration: none; }
+
+nav {
+ -webkit-font-feature-settings: "dlig";
+ font-feature-settings: "dlig"; }
+
+article header h1 a {
+ text-decoration: none; }
+
+article div a {
+ text-decoration: none; }
+
+footer {
+ font-size: 0.8em; }
+
+.emoji {
+ width: auto;
+ height: 1em; }
+
+body {
+ color: #002b36; }
+
+header a {
+ color: #002b36; }
+
+a {
+ color: #268bd2; }
+
+form {
+ width: 100%; }
+
+fieldset {
+ min-width: 0;
+ width: 100%; }
+
+input[type="text"], input[type="file"], textarea {
+ width: 100%; }
+
+input, button, textarea {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ background-color: #002b36;
+ color: #fdf6e3;
+ border: 1px solid #fdf6e3;
+ border-radius: 4px; }
+
+button:hover {
+ transition: 0.5s ease-in-out;
+ background-color: #fdf6e3;
+ color: #002b36; }
+
+button:disabled {
+ background-color: #93a1a1;
+ color: #002b36; }
+
+input[type="checkbox"] {
+ -webkit-appearance: checkbox;
+ -moz-appearance: checkbox; }
+
+#photo {
+ background: inherit;
+ color: inherit;
+ border: none; }
+
+.twitter-tweet-rendered {
+ margin-bottom: 0 !important; }
+
+.twitter-tweet-rendered + .note {
+ margin-top: 0; }
+
+/*# sourceMappingURL=global.css.map */
diff --git a/public/assets/css/global.css.map b/public/assets/css/global.css.map
new file mode 100644
index 00000000..fa39372b
--- /dev/null
+++ b/public/assets/css/global.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["global.scss","layout.scss","components/fonts.scss","components/colours.scss","components/forms.scss","components/twitter.scss"],"names":[],"mappings":"AAyBA;EACC,+CAAe,EACf;;AAED;EACC,cAAc,EACd;;AC5BD;EACC,uBAAuB,EACvB;;AAED;EACC,oBAAoB,EACpB;;AAED;EACC,sBAAc;EAAd,qBAAc;EAAd,cAAc;EACd,uBAAe;EAAf,mBAAe;EAAf,eAAe,EACf;;AAED;EACC,mBAAmB,EACnB;;AAED;EACC,mBAAmB,EACnB;;AAED;EACC,kBAAkB,EAClB;;AAED;EACC,0BDhBkB;ECiBlB,iCDlBkB;ECmBlB,qBAAqB;EACrB,gBAAgB,EAChB;;AAED;EACC,aAAa;EACb,eAAe;EACf,UAAU;EACV,YAAY,EACZ;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,cAAc,EACd;;AAED;EACC,YAAY,EACZ;;AAED;EACC,aAAa,EACb;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,YAAY;EACZ,YAAY;EACZ,cD7CkB,EC8ClB;;AAED;EACC,iBAAiB;EACjB,kBAAkB;EAClB,iBAAiB;EACjB,qBAAqB,EACrB;;AAED;EACC,iBAAiB;EACjB,kBAAkB;EAClB,iBAAiB;EACjB,iBAAiB,EACjB;;AAED;EACC,kBAAkB,EAClB;;AAED;EACC,mBAAmB;EACnB,0BD/EkB;ECgFlB,iBAAiB;EACjB,sBAAsB;EACtB,kBAAkB;EAClB,oBAAoB,EACpB;;AAED;EACC,eAAe;EACf,gBAAgB;EAChB,mBAAmB;EACnB,4BAA4B,EAC5B;;AAED;EACC,eAAe,EACf;;AAED;EACC,kBAAkB;EAClB,oBAAoB,EACpB;;AAED;EACC,eAAe,EACf;;AAED;EACC,YAAY;EACZ,aAAa;EACb,8BAA8B,EAC9B;;AAID;EACC,kBAAkB;EAClB,qBAAqB,EACrB;;AAED;EACC,iBAAiB;EACjB,mBAAmB;EACnB,mBAAmB,EACnB;;AAGD;EACC,mBAAmB,EACnB;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,aAAa;EACb,WAAW;EACX,mBAAmB;EACnB,OAAO;EACP,QAAQ,EACR;;AAED;EACC,iBAAiB,EACjB;;AAED;EACC,cAAc,EACd;;AAED,mBAAmB;AACnB;EACC;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,aAAa;IACb,cAAc,EACd,EAAA;;AAGF;EACC;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,sBAAsB,EACtB;EAED;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,YAAY;IACZ,aAAa,EACb,EAAA;;ACjMF;EACC,mCAAmC;EACnC,sCAA8B;EAA9B,8BAA8B;EAC9B,mCFFsC;EEGtC,iBAAiB,EACjB;;AAED;EACC,mCFPsC,EEQtC;;AAED;EACC,mCFVyC,EEWzC;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,sCAA8B;EAA9B,8BAA8B,EAC9B;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,iBAAiB,EACjB;;AAED;EACC,YAAY;EACZ,YAAY,EACZ;;ACvCD;EACC,eHKkB,EGJlB;;AAED;EACC,eHCkB,EGAlB;;AAED;EACC,eHUkB,EGTlB;;ACTD;EACC,YAAY,EACZ;;AAED;EACC,aAAa;EACb,YAAY,EACZ;;AAED;EACC,YAAY,EACZ;;AAED;EACC,yBAAyB;EACzB,sBAAsB;EACtB,0BJXkB;EIYlB,eJLkB;EIMlB,0BJNkB;EIOlB,mBAAmB,EACnB;;AAED;EACC,6BAA6B;EAC7B,0BJZkB;EIalB,eJpBkB,EIqBlB;;AAED;EACC,0BJnBkB;EIoBlB,eJzBkB,EI0BlB;;AAED;EACC,6BAA6B;EAC7B,0BAA0B,EAC1B;;AAED;EACC,oBAAoB;EACpB,eAAe;EACf,aAAa,EACb;;AC1CD;EACE,4BAA4B,EAC7B;;AAED;EACE,cAAc,EACf","file":"global.css","sourcesContent":["//global.scss\n\n//variables\n$font-stack-body: \"leitura-news\", serif;\n$font-stack-headers: \"prenton\", sans-serif;\n\n//solarized variables TERMCOL\n$base03: #002b36;//brblack\n$base02: #073642;//black\n$base01: #586e75;//brgreen\n$base00: #657b83;//bryellow\n$base0: #839496;//brblue\n$base1: #93a1a1;//brcyan\n$base2: #eee8d5;//white\n$base3: #fdf6e3;//brwhite\n$yellow: #b58900;\n$orange: #cb4b16;\n$red: #dc322f;\n$magenta: #d33682;\n$violet: #6c71c4;\n$blue: #268bd2;\n$cyan: #2aa198;\n$green: #859900;\n\n//global styles\nhtml {\n\tbackground: url('/assets/img/escheresque.png');\n}\n\n.map {\n\theight: 150px;\n}\n\n//layout\n@import \"layout\";\n\n//components\n@import \"components/fonts\";\n@import \"components/colours\";\n@import \"components/forms\";\n@import \"components/twitter\";\n","//layout.scss\n\n//boxes\nhtml {\n\tbox-sizing: border-box;\n}\n\n*, *:before, *:after {\n\tbox-sizing: inherit;\n}\n\n#topheader {\n\tdisplay: flex;\n\tflex-flow: row;\n}\n\n#topheader a {\n\tpadding: 0.5em 1em;\n}\n\nnav {\n\tpadding-top: 0.5em;\n}\n\n.social-list {\n\tpadding-left: 2em;\n}\n\n.note {\n\tbackground-color: $base2;\n\tbox-shadow: 0 0 10px 2px $base1;\n\tpadding: 0.5em 0.5em;\n\tmargin-top: 1em;\n}\n\n.note:after {\n\tcontent: \" \";\n\tdisplay: block;\n\theight: 0;\n\tclear: both;\n}\n\n.note a {\n\tword-wrap: break-word;\n}\n\n.note .e-content p:first-child {\n\tmargin-top: 0;\n}\n\n.note-metadata {\n\twidth: 100%;\n}\n\n.social-links {\n\tfloat: right;\n}\n\n.social-links a {\n\ttext-decoration: none;\n}\n\n.icon {\n\twidth: auto;\n\theight: 1em;\n\tfill: $blue;\n}\n\n.reply {\n\tmargin-left: 2em;\n\tmargin-right: 2em;\n\tfont-size: 0.8em;\n\tpadding: 0.5em 0.5em;\n}\n\n.reply-to {\n\tmargin-left: 2em;\n\tmargin-right: 2em;\n\tfont-size: 0.8em;\n\tpadding-top: 2em;\n}\n\n.reply-to + .note {\n\tmargin-top: 0.3em;\n}\n\n.mini-h-card {\n\tborder-radius: 2px;\n\tborder: 1px solid $base01;\n\tpadding: 0 0.2em;\n\ttext-decoration: none;\n\tmargin-right: 5px;\n\twhite-space: nowrap;\n}\n\n.mini-h-card img {\n\theight: 1.26em;\n\tdisplay: inline;\n\tborder-radius: 2px;\n\tvertical-align: text-bottom;\n}\n\n.like-photo {\n\theight: 1.26em;\n}\n\n.reply .e-content {\n\tmargin-top: 0.5em;\n\tpadding-left: 0.5em;\n}\n\n.notes-subtitle {\n\tfont-size: 1em;\n}\n\n.note-photo {\n\twidth: 100%;\n\theight: auto;\n\timage-orientation: from-image;\n}\n\n//articles\n\narticle header {\n\tmargin-top: 0.5em;\n\tmargin-bottom: 0.8em;\n}\n\n.post-info {\n\tfont-size: 0.8em;\n\tfont-style: italic;\n\tmargin-top: -0.8em;\n}\n\n//contacts\n.contact {\n\tposition: relative;\n}\n\n.contact-links {\n\tlist-style-type: none;\n}\n\n.contact img {\n\theight: auto;\n\twidth: 2em;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n}\n\n.contact-info {\n\tmargin-left: 2em;\n}\n\n#map {\n\theight: 300px;\n}\n\n/* media queries */\n@media (min-width: 700px) {\n\tmain {\n\t\tmargin-left: 10em;\n\t\tmargin-right: 10em;\n\t}\n\n\tfooter {\n\t\tmargin-left: 13em;\n\t\tmargin-right: 13em;\n\t}\n\n\t.youtube {\n\t\twidth: 640px;\n\t\theight: 360px;\n\t}\n}\n\n@media (max-width: 699px) {\n\tmain {\n\t\tmargin-left: 10px;\n\t\tmargin-right: 10px;\n\t}\n\n\tarticle {\n\t\tword-wrap: break-word;\n\t}\n\n\tfooter {\n\t\tmargin-left: 15px;\n\t\tmargin-right: 15px;\n\t}\n\n\t.youtube {\n\t\twidth: 100%;\n\t\theight: auto;\n\t}\n}\n","//fonts.scss\n\nbody {\n\ttext-rendering: optimizeLegibility;\n\tfont-feature-settings: \"liga\";\n\tfont-family: $font-stack-body;\n\tfont-size: 1.2em;\n}\n\n#topheader h1 {\n\tfont-family: $font-stack-body;\n}\n\nh1 {\n\tfont-family: $font-stack-headers;\n}\n\n#topheader a {\n\ttext-decoration: none;\n}\n\nnav {\n\tfont-feature-settings: \"dlig\";\n}\n\narticle header h1 a {\n\ttext-decoration: none;\n}\n\narticle div a {\n\ttext-decoration: none;\n}\n\nfooter {\n\tfont-size: 0.8em;\n}\n\n.emoji {\n\twidth: auto;\n\theight: 1em;\n}\n","//colours.scss\nbody {\n\tcolor: $base03;\n}\n\nheader a {\n\tcolor: $base03;\n}\n\na {\n\tcolor: $blue;\n}","//forms.scss\n\nform {\n\twidth: 100%;\n}\n\nfieldset {\n\tmin-width: 0;\n\twidth: 100%;\n}\n\ninput[type=\"text\"], input[type=\"file\"], textarea {\n\twidth: 100%;\n}\n\ninput, button, textarea {\n\t-webkit-appearance: none;\n\t-moz-appearance: none;\n\tbackground-color: $base03;\n\tcolor: $base3;\n\tborder: 1px solid $base3;\n\tborder-radius: 4px;\n}\n\nbutton:hover {\n\ttransition: 0.5s ease-in-out;\n\tbackground-color: $base3;\n\tcolor: $base03;\n}\n\nbutton:disabled {\n\tbackground-color: $base1;\n\tcolor: $base03;\n}\n\ninput[type=\"checkbox\"] {\n\t-webkit-appearance: checkbox;\n\t-moz-appearance: checkbox;\n}\n\n#photo {\n\tbackground: inherit;\n\tcolor: inherit;\n\tborder: none;\n}\n","//twitter.scss\n\n.twitter-tweet-rendered {\n margin-bottom: 0 !important;\n}\n\n.twitter-tweet-rendered + .note {\n margin-top: 0;\n}\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/public/assets/css/images/icons-000000@2x.png b/public/assets/css/images/icons-000000@2x.png
new file mode 100644
index 00000000..d65438c1
Binary files /dev/null and b/public/assets/css/images/icons-000000@2x.png differ
diff --git a/public/assets/css/normalize.css b/public/assets/css/normalize.css
new file mode 100644
index 00000000..5e5e3c89
--- /dev/null
+++ b/public/assets/css/normalize.css
@@ -0,0 +1,424 @@
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS and IE text size adjust after device orientation change,
+ * without disabling user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+
+audio,
+canvas,
+progress,
+video {
+ display: inline-block; /* 1 */
+ vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * Improve readability of focused elements when they are also in an
+ * active/hover state.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+
+figure {
+ margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+
+pre {
+ overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+
+/**
+ * 1. Correct color not being inherited.
+ * Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit; /* 1 */
+ font: inherit; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+
+button {
+ overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+input {
+ line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ box-sizing: content-box; /* 2 */
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+
+optgroup {
+ font-weight: bold;
+}
+
+/* Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+td,
+th {
+ padding: 0;
+}
diff --git a/public/assets/css/prism.css b/public/assets/css/prism.css
new file mode 100644
index 00000000..86122e46
--- /dev/null
+++ b/public/assets/css/prism.css
@@ -0,0 +1,188 @@
+/* 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 */
+/**
+ * prism.js Dark theme for JavaScript, CSS and HTML
+ * Based on the slides of the talk “/Reg(exp){2}lained/”
+ * @author Lea Verou
+ */
+
+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;
+
+ -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%);
+}
+
+/* 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;
+}
+
+/* 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;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: hsl(30, 20%, 50%);
+}
+
+.token.punctuation {
+ opacity: .7;
+}
+
+.namespace {
+ opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol {
+ color: hsl(350, 40%, 70%);
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+ color: hsl(75, 70%, 60%);
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string,
+.token.variable {
+ color: hsl(40, 90%, 60%);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+ color: hsl(350, 40%, 70%);
+}
+
+.token.regex,
+.token.important {
+ color: #e90;
+}
+
+.token.important,
+.token.bold {
+ font-weight: bold;
+}
+.token.italic {
+ font-style: italic;
+}
+
+.token.entity {
+ cursor: help;
+}
+
+.token.deleted {
+ color: red;
+}
+
+pre.line-numbers {
+ position: relative;
+ padding-left: 3.8em;
+ counter-reset: linenumber;
+}
+
+pre.line-numbers > code {
+ 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;
+
+ -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: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';
+}
diff --git a/public/assets/css/projects.css b/public/assets/css/projects.css
new file mode 100644
index 00000000..d108175a
--- /dev/null
+++ b/public/assets/css/projects.css
@@ -0,0 +1,10 @@
+#projects {
+ padding-left: 33.33%;
+}
+
+h3 {
+ float: left;
+ width: 45%;
+ margin: 0 5% 0 -50%;
+ text-align: right;
+}
diff --git a/public/assets/css/sanitize.min.css b/public/assets/css/sanitize.min.css
new file mode 100644
index 00000000..9d0d9800
--- /dev/null
+++ b/public/assets/css/sanitize.min.css
@@ -0,0 +1,2 @@
+/*! sanitize.css v3.2.0 | CC0 1.0 Public Domain | github.com/10up/sanitize.css */audio:not([controls]){display:none}button{-webkit-appearance:button;overflow:visible}details{display:block}html{-ms-overflow-style:-ms-autohiding-scrollbar;overflow-y:scroll;-webkit-text-size-adjust:100%}input{-webkit-border-radius:0}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button}input[type=number]{width:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}main{display:block}pre{overflow:auto}progress{display:inline-block}small{font-size:75%}summary{display:block}svg:not(:root){overflow:hidden}template{display:none}textarea{overflow:auto}[hidden]{display:none}*,:after,:before{box-sizing:inherit}*{font-size:inherit;line-height:inherit}:after,:before{text-decoration:inherit;vertical-align:inherit}*,:after,:before{border-style:solid;border-width:0}*{background-repeat:no-repeat;margin:0;padding:0}:root{background-color:#fff;box-sizing:border-box;color:#000;cursor:default;font:100%/1.5 sans-serif}a{text-decoration:none}audio,canvas,iframe,img,svg,video{vertical-align:middle}button,input,select,textarea{background-color:transparent;color:inherit;font-family:inherit;font-style:inherit;font-weight:inherit}[type=button],[type=date],[type=datetime-local],[type=datetime],[type=email],[type=month],[type=number],[type=password],[type=reset],[type=search],[type=submit],[type=tel],[type=text],[type=time],[type=url],[type=week],button,select,textarea{min-height:1.5em}code,kbd,pre,samp{font-family:monospace}nav ol,nav ul{list-style:none}select{-moz-appearance:none;-webkit-appearance:none}select::-ms-expand{display:none}select::-ms-value{color:currentColor}table{border-collapse:collapse;border-spacing:0}textarea{resize:vertical}::-moz-selection{background-color:#b3d4fc;color:#fff;text-shadow:none}::selection{background-color:#b3d4fc;color:#fff;text-shadow:none}[aria-busy=true]{cursor:progress}[aria-controls]{cursor:pointer}[aria-disabled]{cursor:default}[hidden][aria-hidden=false]{clip:rect(0 0 0 0);display:inherit;position:absolute}[hidden][aria-hidden=false]:focus{clip:auto}[tabindex],a,area,button,input,label,select,textarea{-ms-touch-action:manipulation;touch-action:manipulation}
+/*# sourceMappingURL=sanitize.min.css.map */
\ No newline at end of file
diff --git a/public/assets/img/escheresque.png b/public/assets/img/escheresque.png
new file mode 100644
index 00000000..a1a4638a
Binary files /dev/null and b/public/assets/img/escheresque.png differ
diff --git a/public/assets/img/escheresque@2x.png b/public/assets/img/escheresque@2x.png
new file mode 100644
index 00000000..f70cc4c0
Binary files /dev/null and b/public/assets/img/escheresque@2x.png differ
diff --git a/public/assets/img/jmb-bw.png b/public/assets/img/jmb-bw.png
new file mode 100644
index 00000000..9db94a2f
Binary files /dev/null and b/public/assets/img/jmb-bw.png differ
diff --git a/public/assets/img/notes/.gitignore b/public/assets/img/notes/.gitignore
new file mode 100644
index 00000000..2c433a89
--- /dev/null
+++ b/public/assets/img/notes/.gitignore
@@ -0,0 +1,3 @@
+# ignore everthing in here except the .gitignore itself
+*
+!.gitignore
diff --git a/public/assets/jonnybarnes-public-key-ecc.asc b/public/assets/jonnybarnes-public-key-ecc.asc
new file mode 100644
index 00000000..29458716
--- /dev/null
+++ b/public/assets/jonnybarnes-public-key-ecc.asc
@@ -0,0 +1,22 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mHMEVLAKNRMJKyQDAwIIAQELAwMEXwZt3GCBMfYdy4E6SYBlhI5OWB5KLWDJIszb
+rHVk1lFzuCh8v9vO3TMJipved89lKdsqc8xa1rtUhDUSyk3DN6G9EA8CaZzPAI0W
+3rIIjKr/6XdsrCbr4uBm8pfxojlrtCNKb25ueSBCYXJuZXMgPGpvbm55QGpvbm55
+YmFybmVzLnVrPoifBBMTCgAnAhsDBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheABQJW
+jWw8BQkDvpWHAAoJEE6N3A7eXSOmEV0Bf1mwzjzKSZvn5l/VhQTrMbM47K82w/wj
+JYF9ZTy1jmXTWTMB3grnjB+VhkF0fJEO7QF+N6EULNZu9xpUX00LfasRvjhDQEoZ
+GWLJWuES3pIheS2wUKScnVxv/3Ntxl9DZyBRuHcEVLAKNRIJKyQDAwIIAQELAwME
+MyNUNRiVoPuUZHkZmoR2fP0j2d6ukPxSyIwE1i0SOokPgh2ho9az2i1JuEJnAJdC
+Jk7anTWqVIVDqY24BPdCAq3Tevvw6ZxYTX5syhQ06rP5fmA9qRvaJMj96bUk2I7U
+AwEJCYiHBBgTCgAPAhsMBQJWjWxuBQkDvpW5AAoJEE6N3A7eXSOmEE4Bfj2IlY+w
+66UuDaZubytEJ7RqZjxroSTygbFzmrrPcyeJo34spa97on2RJa3I1SUEqAF/ZXkY
+dwnMIGjsQFhmp0v0SeQqRKzQ0UsP4X/fCMhXcq1qvSZvp0xGd88OIawGL5YauDME
+VLBHDxYJKwYBBAHaRw8BAQdAN3zPDRahp3XZ2Q6SJcE4s36GziDIEOLq+jYZVTjG
+mQ6I5wQYEwoADwIbAgUCVo1sjAUJA75Y/QBqXyAEGRYKAAYFAlSwRw8ACgkQzehE
+GPNsf3t+2wEA4ZD2Fpw9oZuDVLKUfY2/7WvG9MFTJmhO7j6N2C8rdYgBAIm15ezk
+A2DUCxDKmufsYrkp73nyB/5jq0++qdFXI2oMCRBOjdwO3l0jpm9FAX49J1fL41ZU
+7QpJFEd8gJbYwjCu93YuIP6xpgSfNzE7y4Yl3fYEihmo21aEpeuxIY4BfiNY6r/z
+e5MEKgkw5EYZZK295zhyzZUh0k+0Aezlj1G9faAWgasI+Hp4ATIicNunng==
+=uwJN
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/public/assets/jonnybarnes-public-key.asc b/public/assets/jonnybarnes-public-key.asc
new file mode 100644
index 00000000..25e990db
--- /dev/null
+++ b/public/assets/jonnybarnes-public-key.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2
+
+mQINBFIpsg4BEAD/N8gCtJLVmLFNZeS9x7NcDJKAhYYLeFcNzucVKG0Q+XsLl9Ly
+u5YM4fNqKcHNSLz31lFFgM0kN6WLteCD2oRZJpKkgviitNXwX19om8rYMV7FqYBu
+GDVN6yfXLEUwa/NzsiHSr0YckFphpmdQPYkWJappcaGUzgl9sgy1uc0876NNllSU
+TA4XAgDNHydMTlXZT7b/+ttTUXkZBcl5LRBxeDx77sngkDUlZVIlD8b7fSvSNxec
+1U+aWAbnnzntDNXJ8Sve0NE3ti8GeOqeg0wWbzh9HMjadb5lX7nmATKGehefIaUv
+9D2GYk0LB/6wJVg0NK/SQmyBEB5jW3y0cI2il3NLC72qWU3J65Bl5AF0McKCBcDn
+kA5zISL8lSWXRcCnM5rf13Lz8pAOIEnbURSRILXWdXuzd2+55HaJaZFj9fQWuFZf
+3B/Rpr5rZskjdRgNToUEFYycEg70sCUnyl57oIWjQ5SWI0vqm8IaJO/wv0pSfdOC
+NpOl7ra3BZmULuXzcfR1C4akQAxkwRB2E1cG/eHoDdPSb6s/CBqM14EscHbUel5j
+xqRXc1uxRn7MOL6a8d/nE3iF8tUUPnXAwDgOZXKN2ZDfVjA9nKVuxOZka+lm2Phu
+2M3VGSzHWkJViDKZJ5XH3bYZv1ZhsjAHHM31+LwmTiy2NYxJVG+JOYsM6QARAQAB
+tDNKb25hdGhhbiBNYXggQmFybmVzIChKb25ueSkgPGpvbm55QGpvbm55YmFybmVz
+Lm5ldD6JAj0EEwEIACcFAlIpsg4CGwMFCQHhM4AFCwkIBwMFFQoJCAsFFgIDAQAC
+HgECF4AACgkQbBPmXsq8FiXn7hAAu1ssIlEtmaoVGMpmTMezqR36+/+2URfj9jah
+5TqTVD0GPnyLxD3dEQ24zmgAHHgWOYSlc/gO5AA9Ck2FNhtlqtgADBLr0dIYEVxy
+f0WckljxQ1UFGO+fDyJby2Q0lMQ+4qInY8X6G0UZ+/FhCO/7FtEdm4DKa0Kq5bof
+Iea1bL1oYb/59ZFc4mk7afQWBsXIh9HCqBik0qQ9j8LJDlEOchx8s2EaqRHO3i6P
+hRP9ceIiQrCmang7cmgfsXNe5KU9n2QFmLkiZxlH7YLSvEk2i6XerHrAduUyg+9j
+Yzkgl1oRju1UPglTN7KfCIiI/2wzu8LRZvZFoB8uXkTFtf0Dex5VIkdKyHBQ/09l
+o35+RTjviOybXv8jHYViXXpRrTx2rDLvilyueezHDDyrhEoU+19+JEzlc4bnSMes
+k2QIuXtqiKThhCxUS3tnkTRmVy9NMsWU3TcBC8sXwCJYqWFexOl04K/jfLNEBz7M
+Rj4fdo/GF8RmB/+9Kze5Rafha4pRQ5u/eevmGCxslaJe43JwV+ARNOzH7oDIPDiF
+0xvxvP+8/zMF2qsR66o12veOfIcqwtXebr/K4yngfexKzKY7LJ+i3Gz+q+KFInid
+HHwbX3rDamFdH4rSNavywg3AG6fTSDHgf8YLOPjkwHk8McWEXwcewSBE5tSNq+8O
+ZA2xoKO5Ag0EUimyDgEQAOu/anBLz4xaEw+T9jBMnqdDptJCRkT14LJFTudJK6rE
+D0qqnxNHWs+fDFs32RJSfpy8zMa+xCV0h9L2xbgI5PuWNFifQBcf+w9DWqor41Ta
+MyRuixSRWpM5UNDVjFoVa/hPiWCxZMC7K73pQ6lFyxzlAz9iJXOG+dT7GNeLjiUX
+AvBWRsfVIaV9bSNXK+o5Yb6UFwtCzeGcT0mCDs4Pc5cv3M+Kd5CDyfPPWJtGH9Fz
+2I1mAEemzP4Rgab7q9Ms1J1Tv/H5P1kaVrmdTFxbX6vi8hmnymDY5IPCWmRjBTaH
+xI9oby6Y+ExUwr1znD0pZBndJzq00hmxQIFFZDdlgqvTgJFtgvHEtjSMhMh5Cnkm
+JkSarxDpnTzKvBCUvPQeBnl8vq78jYIB9rLvmZPiwqoqnURBgKXJl+MGjhixHrmU
+hCrt0kOFX/w0APgueFXVcuBoxAQZx+uaFDrID9oJvXB7LGNnGhLd2aglz5a+kpB3
+pbmij0ercumIz3+NcGiili5zk2VHMfOI9rfqMqYmq9R9qoRancqd9CgMd5z9x999
+Ztj9GVs3rUhi4kVCzZbQLD/w7u22m+UsFEp7dbvHaaM2446k9pIcZZoUJLAmkgHQ
+dz9i9cOB3DEqo3zM2bZaHXaPIhunbzT80kiuggFxrDL00Hsm5WKZXnEX3wLoG+QD
+ABEBAAGJAiUEGAEIAA8FAlIpsg4CGwwFCQHhM4AACgkQbBPmXsq8FiX/KQ/+IzJB
+FJ15zw4gE4YOsNF7NQ5Bp8m45082ZhNVjexbPI1J6b+kFCW7lMqY2mBFlMidiheb
+RxMV5iNqrR1oXoSREo94z58X5+j1eIjjXk3TGUSuQ/4hG1yYuqrqsNpdLCmcnt5j
+7CbJ/KxnyPDltMwsMv5iS3ZDPqYHVWUieuiVss5HbFbEA9x3Hrzy7kxQNI33ZfwF
+dKNQv+Qz9dGLhygHuyPKc6e5jd6uw89pITkrnVdbc08miwGV/HlwQKNGyj2Ygwza
+a7QfhFV18qI+6ylZjdu/Kp6M8TxCaswhDhrwGtZh7v3o8HHJrKYD2Hb7IP9+K84t
+mkIII69Sbc9ZItIFzZMcyfk0RfeDy96oZUNvLc77OKbPCtD9/ZRObwxWc19s/LIJ
+sD76WbwlrR2yUnX2HYf6DxRNvxeSwjGzcqRJCZgg8LsylH2oDE+AdLKxbelDqqyT
+NtHhfF5W0URVnHxmjUBVjFxYtH8hSxSL+zOLUY6QXgtriRQa9PuQfbojlX9k3gtM
+9+53u5xtQoSwPeHczwxenYBddBGo9TsXWSvMVUQ8hlepTK3O57C1Sf+wAxW/EHeO
+30lNXDREr/J/IJ7AanL8fkUi7iaoBkx0Xdi3qe1YOu6MR1TuPT75cDoAjQEG2AUr
+igdpfiESmrELanSGdSsSBtieo+imJ3G3xmj2vrs=
+=dnNH
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/public/assets/js/Autolinker.min.js b/public/assets/js/Autolinker.min.js
new file mode 100644
index 00000000..e4f6366a
--- /dev/null
+++ b/public/assets/js/Autolinker.min.js
@@ -0,0 +1,10 @@
+/*!
+ * Autolinker.js
+ * 0.24.0
+ *
+ * Copyright(c) 2016 Gregory Jacobs
+ * MIT
+ *
+ * https://github.com/gregjacobs/Autolinker.js
+ */
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return a.Autolinker=b()}):"object"==typeof exports?module.exports=b():a.Autolinker=b()}(this,function(){var a=function(a){a=a||{},this.urls=this.normalizeUrlsCfg(a.urls),this.email="boolean"==typeof a.email?a.email:!0,this.twitter="boolean"==typeof a.twitter?a.twitter:!0,this.phone="boolean"==typeof a.phone?a.phone:!0,this.hashtag=a.hashtag||!1,this.newWindow="boolean"==typeof a.newWindow?a.newWindow:!0,this.stripPrefix="boolean"==typeof a.stripPrefix?a.stripPrefix:!0;var b=this.hashtag;if(b!==!1&&"twitter"!==b&&"facebook"!==b&&"instagram"!==b)throw new Error("invalid `hashtag` cfg - see docs");this.truncate=this.normalizeTruncateCfg(a.truncate),this.className=a.className||"",this.replaceFn=a.replaceFn||null,this.htmlParser=null,this.matchers=null,this.tagBuilder=null};return a.prototype={constructor:a,normalizeUrlsCfg:function(a){return null==a&&(a=!0),"boolean"==typeof a?{schemeMatches:a,wwwMatches:a,tldMatches:a}:{schemeMatches:"boolean"==typeof a.schemeMatches?a.schemeMatches:!0,wwwMatches:"boolean"==typeof a.wwwMatches?a.wwwMatches:!0,tldMatches:"boolean"==typeof a.tldMatches?a.tldMatches:!0}},normalizeTruncateCfg:function(b){return"number"==typeof b?{length:b,location:"end"}:a.Util.defaults(b||{},{length:Number.POSITIVE_INFINITY,location:"end"})},parse:function(a){for(var b=this.getHtmlParser(),c=b.parse(a),d=0,e=[],f=0,g=c.length;g>f;f++){var h=c[f],i=h.getType();if("element"===i&&"a"===h.getTagName())h.isClosing()?d=Math.max(d-1,0):d++;else if("text"===i&&0===d){var j=this.parseText(h.getText(),h.getOffset());e.push.apply(e,j)}}return e=this.compactMatches(e),this.hashtag||(e=e.filter(function(a){return"hashtag"!==a.getType()})),this.email||(e=e.filter(function(a){return"email"!==a.getType()})),this.phone||(e=e.filter(function(a){return"phone"!==a.getType()})),this.twitter||(e=e.filter(function(a){return"twitter"!==a.getType()})),this.urls.schemeMatches||(e=e.filter(function(a){return"url"!==a.getType()||"scheme"!==a.getUrlMatchType()})),this.urls.wwwMatches||(e=e.filter(function(a){return"url"!==a.getType()||"www"!==a.getUrlMatchType()})),this.urls.tldMatches||(e=e.filter(function(a){return"url"!==a.getType()||"tld"!==a.getUrlMatchType()})),e},compactMatches:function(a){a.sort(function(a,b){return a.getOffset()-b.getOffset()});for(var b=0;be;e++){for(var g=c[e].parseMatches(a),h=0,i=g.length;i>h;h++)g[h].setOffset(b+g[h].getOffset());d.push.apply(d,g)}return d},link:function(a){if(!a)return"";for(var b=this.parse(a),c=[],d=0,e=0,f=b.length;f>e;e++){var g=b[e];c.push(a.substring(d,g.getOffset())),c.push(this.createMatchReturnVal(g)),d=g.getOffset()+g.getMatchedText().length}return c.push(a.substring(d)),c.join("")},createMatchReturnVal:function(b){var c;if(this.replaceFn&&(c=this.replaceFn.call(this,this,b)),"string"==typeof c)return c;if(c===!1)return b.getMatchedText();if(c instanceof a.HtmlTag)return c.toAnchorString();var d=this.getTagBuilder(),e=d.build(b);return e.toAnchorString()},getHtmlParser:function(){var b=this.htmlParser;return b||(b=this.htmlParser=new a.htmlParser.HtmlParser),b},getMatchers:function(){if(this.matchers)return this.matchers;var b=a.matcher,c=[new b.Hashtag({serviceName:this.hashtag}),new b.Email,new b.Phone,new b.Twitter,new b.Url({stripPrefix:this.stripPrefix})];return this.matchers=c},getTagBuilder:function(){var b=this.tagBuilder;return b||(b=this.tagBuilder=new a.AnchorTagBuilder({newWindow:this.newWindow,truncate:this.truncate,className:this.className})),b}},a.link=function(b,c){var d=new a(c);return d.link(b)},a.match={},a.matcher={},a.htmlParser={},a.truncate={},a.Util={abstractMethod:function(){throw"abstract"},trimRegex:/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,assign:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},defaults:function(a,b){for(var c in b)b.hasOwnProperty(c)&&void 0===a[c]&&(a[c]=b[c]);return a},extend:function(b,c){var d=b.prototype,e=function(){};e.prototype=d;var f;f=c.hasOwnProperty("constructor")?c.constructor:function(){d.constructor.apply(this,arguments)};var g=f.prototype=new e;return g.constructor=f,g.superclass=d,delete c.constructor,a.Util.assign(g,c),f},ellipsis:function(a,b,c){return a.length>b&&(c=null==c?"..":c,a=a.substring(0,b-c.length)+c),a},indexOf:function(a,b){if(Array.prototype.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},splitAndCapture:function(a,b){if(!b.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var c,d=[],e=0;c=b.exec(a);)d.push(a.substring(e,c.index)),d.push(c[0]),e=c.index+c[0].length;return d.push(a.substring(e)),d},trim:function(a){return a.replace(this.trimRegex,"")}},a.HtmlTag=a.Util.extend(Object,{whitespaceRegex:/\s+/,constructor:function(b){a.Util.assign(this,b),this.innerHtml=this.innerHtml||this.innerHTML},setTagName:function(a){return this.tagName=a,this},getTagName:function(){return this.tagName||""},setAttr:function(a,b){var c=this.getAttrs();return c[a]=b,this},getAttr:function(a){return this.getAttrs()[a]},setAttrs:function(b){var c=this.getAttrs();return a.Util.assign(c,b),this},getAttrs:function(){return this.attrs||(this.attrs={})},setClass:function(a){return this.setAttr("class",a)},addClass:function(b){for(var c,d=this.getClass(),e=this.whitespaceRegex,f=a.Util.indexOf,g=d?d.split(e):[],h=b.split(e);c=h.shift();)-1===f(g,c)&&g.push(c);return this.getAttrs()["class"]=g.join(" "),this},removeClass:function(b){for(var c,d=this.getClass(),e=this.whitespaceRegex,f=a.Util.indexOf,g=d?d.split(e):[],h=b.split(e);g.length&&(c=h.shift());){var i=f(g,c);-1!==i&&g.splice(i,1)}return this.getAttrs()["class"]=g.join(" "),this},getClass:function(){return this.getAttrs()["class"]||""},hasClass:function(a){return-1!==(" "+this.getClass()+" ").indexOf(" "+a+" ")},setInnerHtml:function(a){return this.innerHtml=a,this},getInnerHtml:function(){return this.innerHtml||""},toAnchorString:function(){var a=this.getTagName(),b=this.buildAttrsStr();return b=b?" "+b:"",["<",a,b,">",this.getInnerHtml(),"",a,">"].join("")},buildAttrsStr:function(){if(!this.attrs)return"";var a=this.getAttrs(),b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c+'="'+a[c]+'"');return b.join(" ")}}),a.RegexLib=function(){var a="A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢴऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛱ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞭꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",b="0-9٠-٩۰-۹߀-߉०-९০-৯੦-੯૦-૯୦-୯௦-௯౦-౯೦-೯൦-൯෦-෯๐-๙໐-໙༠-༩၀-၉႐-႙០-៩᠐-᠙᥆-᥏᧐-᧙᪀-᪉᪐-᪙᭐-᭙᮰-᮹᱀-᱉᱐-᱙꘠-꘩꣐-꣙꤀-꤉꧐-꧙꧰-꧹꩐-꩙꯰-꯹0-9",c=a+b,d=new RegExp("["+c+".\\-]*["+c+"\\-]"),e=/(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|press|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/;return{alphaNumericCharsStr:c,domainNameRegex:d,tldRegex:e}}(),a.AnchorTagBuilder=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},build:function(b){return new a.HtmlTag({tagName:"a",attrs:this.createAttrs(b.getType(),b.getAnchorHref()),innerHtml:this.processAnchorText(b.getAnchorText())})},createAttrs:function(a,b){var c={href:b},d=this.createCssClass(a);return d&&(c["class"]=d),this.newWindow&&(c.target="_blank"),c},createCssClass:function(a){var b=this.className;return b?b+" "+b+"-"+a:""},processAnchorText:function(a){return a=this.doTruncate(a)},doTruncate:function(b){var c=this.truncate;if(!c)return b;var d=c.length,e=c.location;return"smart"===e?a.truncate.TruncateSmart(b,d,".."):"middle"===e?a.truncate.TruncateMiddle(b,d,".."):a.truncate.TruncateEnd(b,d,"..")}}),a.htmlParser.HtmlParser=a.Util.extend(Object,{htmlRegex:function(){var a=/!--([\s\S]+?)--/,b=/[0-9a-zA-Z][0-9a-zA-Z:]*/,c=/[^\s\0"'>\/=\x01-\x1F\x7F]+/,d=/(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/,e=c.source+"(?:\\s*=\\s*"+d.source+")?";return new RegExp(["(?:","<(!DOCTYPE)","(?:","\\s+","(?:",e,"|",d.source+")",")*",">",")","|","(?:","<(/)?","(?:",a.source,"|","(?:","("+b.source+")","(?:","\\s+",e,")*","\\s*/?",")",")",">",")"].join(""),"gi")}(),htmlCharacterEntitiesRegex:/( | |<|<|>|>|"|"|')/gi,parse:function(a){for(var b,c,d=this.htmlRegex,e=0,f=[];null!==(b=d.exec(a));){var g=b[0],h=b[3],i=b[1]||b[4],j=!!b[2],k=b.index,l=a.substring(e,k);l&&(c=this.parseTextAndEntityNodes(e,l),f.push.apply(f,c)),f.push(h?this.createCommentNode(k,g,h):this.createElementNode(k,g,i,j)),e=k+g.length}if(ef;f+=2){var h=e[f],i=e[f+1];h&&(d.push(this.createTextNode(b,h)),b+=h.length),i&&(d.push(this.createEntityNode(b,i)),b+=i.length)}return d},createCommentNode:function(b,c,d){return new a.htmlParser.CommentNode({offset:b,text:c,comment:a.Util.trim(d)})},createElementNode:function(b,c,d,e){return new a.htmlParser.ElementNode({offset:b,text:c,tagName:d.toLowerCase(),closing:e})},createEntityNode:function(b,c){return new a.htmlParser.EntityNode({offset:b,text:c})},createTextNode:function(b,c){return new a.htmlParser.TextNode({offset:b,text:c})}}),a.htmlParser.HtmlNode=a.Util.extend(Object,{offset:void 0,text:void 0,constructor:function(b){if(a.Util.assign(this,b),null==this.offset)throw new Error("`offset` cfg required");if(null==this.text)throw new Error("`text` cfg required")},getType:a.Util.abstractMethod,getOffset:function(){return this.offset},getText:function(){return this.text}}),a.htmlParser.CommentNode=a.Util.extend(a.htmlParser.HtmlNode,{comment:"",getType:function(){return"comment"},getComment:function(){return this.comment}}),a.htmlParser.ElementNode=a.Util.extend(a.htmlParser.HtmlNode,{tagName:"",closing:!1,getType:function(){return"element"},getTagName:function(){return this.tagName},isClosing:function(){return this.closing}}),a.htmlParser.EntityNode=a.Util.extend(a.htmlParser.HtmlNode,{getType:function(){return"entity"}}),a.htmlParser.TextNode=a.Util.extend(a.htmlParser.HtmlNode,{getType:function(){return"text"}}),a.match.Match=a.Util.extend(Object,{constructor:function(a,b){if(null==a)throw new Error("`matchedText` arg required");if(null==b)throw new Error("`offset` arg required");this.matchedText=a,this.offset=b},getType:a.Util.abstractMethod,getMatchedText:function(){return this.matchedText},setOffset:function(a){this.offset=a},getOffset:function(){return this.offset},getAnchorHref:a.Util.abstractMethod,getAnchorText:a.Util.abstractMethod}),a.match.Email=a.Util.extend(a.match.Match,{constructor:function(b,c,d){if(a.match.Match.prototype.constructor.call(this,b,c),!d)throw new Error("`email` arg required");this.email=d},getType:function(){return"email"},getEmail:function(){return this.email},getAnchorHref:function(){return"mailto:"+this.email},getAnchorText:function(){return this.email}}),a.match.Hashtag=a.Util.extend(a.match.Match,{constructor:function(b,c,d,e){if(a.match.Match.prototype.constructor.call(this,b,c),!e)throw new Error("`hashtag` arg required");this.serviceName=d,this.hashtag=e},getType:function(){return"hashtag"},getServiceName:function(){return this.serviceName},getHashtag:function(){return this.hashtag},getAnchorHref:function(){var a=this.serviceName,b=this.hashtag;switch(a){case"twitter":return"https://twitter.com/hashtag/"+b;case"facebook":return"https://www.facebook.com/hashtag/"+b;case"instagram":return"https://instagram.com/explore/tags/"+b;default:throw new Error("Unknown service name to point hashtag to: ",a)}},getAnchorText:function(){return"#"+this.hashtag}}),a.match.Phone=a.Util.extend(a.match.Match,{constructor:function(b,c,d,e){if(a.match.Match.prototype.constructor.call(this,b,c),!d)throw new Error("`number` arg required");if(null==e)throw new Error("`plusSign` arg required");this.number=d,this.plusSign=e},getType:function(){return"phone"},getNumber:function(){return this.number},getAnchorHref:function(){return"tel:"+(this.plusSign?"+":"")+this.number},getAnchorText:function(){return this.matchedText}}),a.match.Twitter=a.Util.extend(a.match.Match,{constructor:function(b,c,d){if(a.match.Match.prototype.constructor.call(this,b,c),!d)throw new Error("`twitterHandle` arg required");this.twitterHandle=d},getType:function(){return"twitter"},getTwitterHandle:function(){return this.twitterHandle},getAnchorHref:function(){return"https://twitter.com/"+this.twitterHandle},getAnchorText:function(){return"@"+this.twitterHandle}}),a.match.Url=a.Util.extend(a.match.Match,{constructor:function(b,c,d,e,f,g,h){if(a.match.Match.prototype.constructor.call(this,b,c),"scheme"!==e&&"www"!==e&&"tld"!==e)throw new Error('`urlMatchType` must be one of: "scheme", "www", or "tld"');if(!d)throw new Error("`url` arg required");if(null==f)throw new Error("`protocolUrlMatch` arg required");if(null==g)throw new Error("`protocolRelativeMatch` arg required");if(null==h)throw new Error("`stripPrefix` arg required");this.urlMatchType=e,this.url=d,this.protocolUrlMatch=f,this.protocolRelativeMatch=g,this.stripPrefix=h},urlPrefixRegex:/^(https?:\/\/)?(www\.)?/i,protocolRelativeRegex:/^\/\//,protocolPrepended:!1,getType:function(){return"url"},getUrlMatchType:function(){return this.urlMatchType},getUrl:function(){var a=this.url;return this.protocolRelativeMatch||this.protocolUrlMatch||this.protocolPrepended||(a=this.url="http://"+a,this.protocolPrepended=!0),a},getAnchorHref:function(){var a=this.getUrl();return a.replace(/&/g,"&")},getAnchorText:function(){var a=this.getMatchedText();return this.protocolRelativeMatch&&(a=this.stripProtocolRelativePrefix(a)),this.stripPrefix&&(a=this.stripUrlPrefix(a)),a=this.removeTrailingSlash(a)},stripUrlPrefix:function(a){return a.replace(this.urlPrefixRegex,"")},stripProtocolRelativePrefix:function(a){return a.replace(this.protocolRelativeRegex,"")},removeTrailingSlash:function(a){return"/"===a.charAt(a.length-1)&&(a=a.slice(0,-1)),a}}),a.matcher.Matcher=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},parseMatches:a.Util.abstractMethod}),a.matcher.Email=a.Util.extend(a.matcher.Matcher,{matcherRegex:function(){var b=a.RegexLib.alphaNumericCharsStr,c=new RegExp("["+b+"\\-;:&=+$.,]+@"),d=a.RegexLib.domainNameRegex,e=a.RegexLib.tldRegex;return new RegExp([c.source,d.source,"\\.",e.source].join(""),"gi")}(),parseMatches:function(b){for(var c,d=this.matcherRegex,e=[];null!==(c=d.exec(b));){var f=c[0];e.push(new a.match.Email(f,c.index,f))}return e}}),a.matcher.Hashtag=a.Util.extend(a.matcher.Matcher,{matcherRegex:new RegExp("#[_"+a.RegexLib.alphaNumericCharsStr+"]{1,139}","g"),nonWordCharRegex:new RegExp("[^"+a.RegexLib.alphaNumericCharsStr+"]"),constructor:function(){a.matcher.Matcher.prototype.constructor.apply(this,arguments)},parseMatches:function(b){for(var c,d=this.matcherRegex,e=this.nonWordCharRegex,f=this.serviceName,g=[];null!==(c=d.exec(b));){var h=c.index,i=b.charAt(h-1);if(0===h||e.test(i)){var j=c[0],k=c[0].slice(1);g.push(new a.match.Hashtag(j,h,f,k))}}return g}}),a.matcher.Phone=a.Util.extend(a.matcher.Matcher,{matcherRegex:/(?:(\+)?\d{1,3}[-\040.])?\(?\d{3}\)?[-\040.]?\d{3}[-\040.]\d{4}/g,parseMatches:function(b){for(var c,d=this.matcherRegex,e=[];null!==(c=d.exec(b));){var f=c[0],g=f.replace(/\D/g,""),h=!!c[1];e.push(new a.match.Phone(f,c.index,g,h))}return e}}),a.matcher.Twitter=a.Util.extend(a.matcher.Matcher,{matcherRegex:new RegExp("@[_"+a.RegexLib.alphaNumericCharsStr+"]{1,20}","g"),nonWordCharRegex:new RegExp("[^"+a.RegexLib.alphaNumericCharsStr+"]"),parseMatches:function(b){for(var c,d=this.matcherRegex,e=this.nonWordCharRegex,f=[];null!==(c=d.exec(b));){var g=c.index,h=b.charAt(g-1);if(0===g||e.test(h)){var i=c[0],j=c[0].slice(1);f.push(new a.match.Twitter(i,g,j))}}return f}}),a.matcher.Url=a.Util.extend(a.matcher.Matcher,{matcherRegex:function(){var b=/(?:[A-Za-z][-.+A-Za-z0-9]*:(?![A-Za-z][-.+A-Za-z0-9]*:\/\/)(?!\d+\/?)(?:\/\/)?)/,c=/(?:www\.)/,d=a.RegexLib.domainNameRegex,e=a.RegexLib.tldRegex,f=a.RegexLib.alphaNumericCharsStr,g=new RegExp("["+f+"\\-+&@#/%=~_()|'$*\\[\\]?!:,.;]*["+f+"\\-+&@#/%=~_()|'$*\\[\\]]");return new RegExp(["(?:","(",b.source,d.source,")","|","(","(//)?",c.source,d.source,")","|","(","(//)?",d.source+"\\.",e.source,")",")","(?:"+g.source+")?"].join(""),"gi")}(),wordCharRegExp:/\w/,openParensRe:/\(/g,closeParensRe:/\)/g,constructor:function(){if(a.matcher.Matcher.prototype.constructor.apply(this,arguments),null==this.stripPrefix)throw new Error("`stripPrefix` cfg required")},parseMatches:function(b){for(var c,d=this.matcherRegex,e=this.stripPrefix,f=[];null!==(c=d.exec(b));){var g=c[0],h=c[1],i=c[2],j=c[3],k=c[5],l=c.index,m=j||k,n=b.charAt(l-1);if(a.matcher.UrlMatchValidator.isValid(g,h)&&!(l>0&&"@"===n||l>0&&m&&this.wordCharRegExp.test(n))){if(this.matchHasUnbalancedClosingParen(g))g=g.substr(0,g.length-1);else{var o=this.matchHasInvalidCharAfterTld(g,h);o>-1&&(g=g.substr(0,o))}var p=h?"scheme":i?"www":"tld",q=!!h;f.push(new a.match.Url(g,l,g,p,q,!!m,e))}}return f},matchHasUnbalancedClosingParen:function(a){var b=a.charAt(a.length-1);if(")"===b){var c=a.match(this.openParensRe),d=a.match(this.closeParensRe),e=c&&c.length||0,f=d&&d.length||0;if(f>e)return!0}return!1},matchHasInvalidCharAfterTld:function(a,b){if(!a)return-1;var c=0;b&&(c=a.indexOf(":"),a=a.slice(c));var d=/^((.?\/\/)?[A-Za-z0-9\u00C0-\u017F\.\-]*[A-Za-z0-9\u00C0-\u017F\-]\.[A-Za-z]+)/,e=d.exec(a);return null===e?-1:(c+=e[1].length,a=a.slice(e[1].length),/^[^.A-Za-z:\/?#]/.test(a)?c:-1)}}),a.matcher.UrlMatchValidator={hasFullProtocolRegex:/^[A-Za-z][-.+A-Za-z0-9]*:\/\//,uriSchemeRegex:/^[A-Za-z][-.+A-Za-z0-9]*:/,hasWordCharAfterProtocolRegex:/:[^\s]*?[A-Za-z\u00C0-\u017F]/,isValid:function(a,b){return b&&!this.isValidUriScheme(b)||this.urlMatchDoesNotHaveProtocolOrDot(a,b)||this.urlMatchDoesNotHaveAtLeastOneWordChar(a,b)?!1:!0},isValidUriScheme:function(a){var b=a.match(this.uriSchemeRegex)[0].toLowerCase();return"javascript:"!==b&&"vbscript:"!==b},urlMatchDoesNotHaveProtocolOrDot:function(a,b){return!(!a||b&&this.hasFullProtocolRegex.test(b)||-1!==a.indexOf("."))},urlMatchDoesNotHaveAtLeastOneWordChar:function(a,b){return a&&b?!this.hasWordCharAfterProtocolRegex.test(a):!1}},a.truncate.TruncateEnd=function(b,c,d){return a.Util.ellipsis(b,c,d)},a.truncate.TruncateMiddle=function(a,b,c){if(a.length<=b)return a;var d=b-c.length,e="";return d>0&&(e=a.substr(-1*Math.floor(d/2))),(a.substr(0,Math.ceil(d/2))+c+e).substr(0,b)},a.truncate.TruncateSmart=function(a,b,c){var d=function(a){var b={},c=a,d=c.match(/^([a-z]+):\/\//i);return d&&(b.scheme=d[1],c=c.substr(d[0].length)),d=c.match(/^(.*?)(?=(\?|#|\/|$))/i),d&&(b.host=d[1],c=c.substr(d[0].length)),d=c.match(/^\/(.*?)(?=(\?|#|$))/i),d&&(b.path=d[1],c=c.substr(d[0].length)),d=c.match(/^\?(.*?)(?=(#|$))/i),d&&(b.query=d[1],c=c.substr(d[0].length)),d=c.match(/^#(.*?)$/i),d&&(b.fragment=d[1]),b},e=function(a){var b="";return a.scheme&&a.host&&(b+=a.scheme+"://"),a.host&&(b+=a.host),a.path&&(b+="/"+a.path),a.query&&(b+="?"+a.query),a.fragment&&(b+="#"+a.fragment),b},f=function(a,b){var d=b/2,e=Math.ceil(d),f=-1*Math.floor(d),g="";return 0>f&&(g=a.substr(f)),a.substr(0,e)+c+g};if(a.length<=b)return a;var g=b-c.length,h=d(a);if(h.query){var i=h.query.match(/^(.*?)(?=(\?|\#))(.*?)$/i);i&&(h.query=h.query.substr(0,i[1].length),a=e(h))}if(a.length<=b)return a;if(h.host&&(h.host=h.host.replace(/^www\./,""),a=e(h)),a.length<=b)return a;var j="";if(h.host&&(j+=h.host),j.length>=g)return h.host.length==b?(h.host.substr(0,b-c.length)+c).substr(0,b):f(j,g).substr(0,b);var k="";if(h.path&&(k+="/"+h.path),h.query&&(k+="?"+h.query),k){if((j+k).length>=g){if((j+k).length==b)return(j+k).substr(0,b);var l=g-j.length;return(j+f(k,l)).substr(0,b)}j+=k}if(h.fragment){var m="#"+h.fragment;if((j+m).length>=g){if((j+m).length==b)return(j+m).substr(0,b);var n=g-j.length;return(j+f(m,n)).substr(0,b)}j+=m}if(h.scheme&&h.host){var o=h.scheme+"://";if((j+o).length0&&(p=j.substr(-1*Math.floor(g/2))),(j.substr(0,Math.ceil(g/2))+c+p).substr(0,b)},a});
\ No newline at end of file
diff --git a/public/assets/js/alertify.js b/public/assets/js/alertify.js
new file mode 100644
index 00000000..d9d6a2b2
--- /dev/null
+++ b/public/assets/js/alertify.js
@@ -0,0 +1 @@
+!function(){"use strict";function t(){var t={version:"1.0.8",defaultOkLabel:"Ok",okLabel:"Ok",defaultCancelLabel:"Cancel",cancelLabel:"Cancel",defaultMaxLogItems:2,maxLogItems:2,promptValue:"",promptPlaceholder:"",closeLogOnClick:!1,closeLogOnClickDefault:!1,delay:5e3,defaultDelay:5e3,logContainerClass:"alertify-logs",logContainerDefaultClass:"alertify-logs",dialogs:{buttons:{holder:"",ok:"",cancel:""},input:"",message:"{{message}}
",log:"{{message}}"},defaultDialogs:{buttons:{holder:"",ok:"",cancel:""},input:"",message:"{{message}}
",log:"{{message}}"},build:function(t){var e=this.dialogs.buttons.ok,o=""+this.dialogs.message.replace("{{message}}",t.message);return"confirm"!==t.type&&"prompt"!==t.type||(e=this.dialogs.buttons.cancel+this.dialogs.buttons.ok),"prompt"===t.type&&(o+=this.dialogs.input),o=(o+this.dialogs.buttons.holder+"").replace("{{buttons}}",e).replace("{{ok}}",this.okLabel).replace("{{cancel}}",this.cancelLabel)},setCloseLogOnClick:function(t){this.closeLogOnClick=!!t},close:function(t,e){this.closeLogOnClick&&t.addEventListener("click",function(t){o(t.srcElement)}),e=e&&!isNaN(+e)?+e:this.delay,0>e?o(t):e>0&&setTimeout(function(){o(t)},e)},dialog:function(t,e,o,n){return this.setup({type:e,message:t,onOkay:o,onCancel:n})},log:function(t,e,o){var n=document.querySelectorAll(".alertify-logs > div");if(n){var i=n.length-this.maxLogItems;if(i>=0)for(var a=0,l=i+1;l>a;a++)this.close(n[a],-1)}this.notify(t,e,o)},setLogPosition:function(t){this.logContainerClass="alertify-logs "+t},setupLogContainer:function(){var t=document.querySelector(".alertify-logs"),e=this.logContainerClass;return t||(t=document.createElement("div"),t.className=e,document.body.appendChild(t)),t.className!==e&&(t.className=e),t},notify:function(e,o,n){var i=this.setupLogContainer(),a=document.createElement("div");a.className=o||"default",t.logTemplateMethod?a.innerHTML=t.logTemplateMethod(e):a.innerHTML=e,"function"==typeof n&&a.addEventListener("click",n),i.appendChild(a),setTimeout(function(){a.className+=" show"},10),this.close(a,this.delay)},setup:function(t){function e(e){"function"!=typeof e&&(e=function(){}),i&&i.addEventListener("click",function(i){t.onOkay&&"function"==typeof t.onOkay&&(l?t.onOkay(l.value,i):t.onOkay(i)),e(l?{buttonClicked:"ok",inputValue:l.value,event:i}:{buttonClicked:"ok",event:i}),o(n)}),a&&a.addEventListener("click",function(i){t.onCancel&&"function"==typeof t.onCancel&&t.onCancel(i),e({buttonClicked:"cancel",event:i}),o(n)})}var n=document.createElement("div");n.className="alertify hide",n.innerHTML=this.build(t);var i=n.querySelector(".ok"),a=n.querySelector(".cancel"),l=n.querySelector("input"),s=n.querySelector("label");l&&("string"==typeof this.promptPlaceholder&&(s?s.textContent=this.promptPlaceholder:l.placeholder=this.promptPlaceholder),"string"==typeof this.promptValue&&(l.value=this.promptValue));var r;return"function"==typeof Promise?r=new Promise(e):e(),document.body.appendChild(n),setTimeout(function(){n.classList.remove("hide"),l&&t.type&&"prompt"===t.type?(l.select(),l.focus()):i&&i.focus()},100),r},okBtn:function(t){return this.okLabel=t,this},setDelay:function(t){var e=parseInt(t||0,10);return this.delay=isNaN(e)?this.defultDelay:t,this},cancelBtn:function(t){return this.cancelLabel=t,this},setMaxLogItems:function(t){this.maxLogItems=parseInt(t||this.defaultMaxLogItems)},theme:function(t){switch(t.toLowerCase()){case"bootstrap":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="",this.dialogs.input="";break;case"purecss":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="";break;case"mdl":case"material-design-light":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="",this.dialogs.input="";break;case"angular-material":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="",this.dialogs.input=" ";break;case"default":default:this.dialogs.buttons.ok=this.defaultDialogs.buttons.ok,this.dialogs.buttons.cancel=this.defaultDialogs.buttons.cancel,this.dialogs.input=this.defaultDialogs.input}},reset:function(){this.theme("default"),this.okBtn(this.defaultOkLabel),this.cancelBtn(this.defaultCancelLabel),this.setMaxLogItems(),this.promptValue="",this.promptPlaceholder="",this.delay=this.defaultDelay,this.setCloseLogOnClick(this.closeLogOnClickDefault),this.setLogPosition("bottom left"),this.logTemplateMethod=null},injectCSS:function(){if(!document.querySelector("#alertifyCSS")){var t=document.getElementsByTagName("head")[0],e=document.createElement("style");e.type="text/css",e.id="alertifyCSS",e.innerHTML=".alertify-logs>*{padding:12px 24px;color:#fff;box-shadow:0 2px 5px 0 rgba(0,0,0,.2);border-radius:1px}.alertify-logs>*,.alertify-logs>.default{background:rgba(0,0,0,.8)}.alertify-logs>.error{background:rgba(244,67,54,.8)}.alertify-logs>.success{background:rgba(76,175,80,.9)}.alertify{position:fixed;background-color:rgba(0,0,0,.3);left:0;right:0;top:0;bottom:0;width:100%;height:100%;z-index:2}.alertify.hide{opacity:0;pointer-events:none}.alertify,.alertify.show{box-sizing:border-box;transition:all .33s cubic-bezier(.25,.8,.25,1)}.alertify,.alertify *{box-sizing:border-box}.alertify .dialog{padding:12px}.alertify .alert,.alertify .dialog{width:100%;margin:0 auto;position:relative;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.alertify .alert>*,.alertify .dialog>*{width:400px;max-width:95%;margin:0 auto;text-align:center;padding:12px;background:#fff;box-shadow:0 2px 4px -1px rgba(0,0,0,.14),0 4px 5px 0 rgba(0,0,0,.098),0 1px 10px 0 rgba(0,0,0,.084)}.alertify .alert .msg,.alertify .dialog .msg{padding:12px;margin-bottom:12px;margin:0;text-align:left}.alertify .alert input:not(.form-control),.alertify .dialog input:not(.form-control){margin-bottom:15px;width:100%;font-size:100%;padding:12px}.alertify .alert input:not(.form-control):focus,.alertify .dialog input:not(.form-control):focus{outline-offset:-2px}.alertify .alert nav,.alertify .dialog nav{text-align:right}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button),.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button){background:transparent;box-sizing:border-box;color:rgba(0,0,0,.87);position:relative;outline:0;border:0;display:inline-block;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;padding:0 6px;margin:6px 8px;line-height:36px;min-height:36px;white-space:nowrap;min-width:88px;text-align:center;text-transform:uppercase;font-size:14px;text-decoration:none;cursor:pointer;border:1px solid transparent;border-radius:2px}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover{background-color:rgba(0,0,0,.05)}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus{border:1px solid rgba(0,0,0,.1)}.alertify .alert nav button.btn,.alertify .dialog nav button.btn{margin:6px 4px}.alertify-logs{position:fixed;z-index:1}.alertify-logs.bottom,.alertify-logs:not(.top){bottom:16px}.alertify-logs.left,.alertify-logs:not(.right){left:16px}.alertify-logs.left>*,.alertify-logs:not(.right)>*{float:left;-webkit-transform:translateZ(0);transform:translateZ(0);height:auto}.alertify-logs.left>.show,.alertify-logs:not(.right)>.show{left:0}.alertify-logs.left>*,.alertify-logs.left>.hide,.alertify-logs:not(.right)>*,.alertify-logs:not(.right)>.hide{left:-110%}.alertify-logs.right{right:16px}.alertify-logs.right>*{float:right;-webkit-transform:translateZ(0);transform:translateZ(0)}.alertify-logs.right>.show{right:0;opacity:1}.alertify-logs.right>*,.alertify-logs.right>.hide{right:-110%;opacity:0}.alertify-logs.top{top:0}.alertify-logs>*{box-sizing:border-box;transition:all .4s cubic-bezier(.25,.8,.25,1);position:relative;clear:both;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000;max-height:0;margin:0;padding:0;overflow:hidden;opacity:0;pointer-events:none}.alertify-logs>.show{margin-top:12px;opacity:1;max-height:1000px;padding:12px;pointer-events:auto}",t.insertBefore(e,t.firstChild)}},removeCSS:function(){var t=document.querySelector("#alertifyCSS");t&&t.parentNode&&t.parentNode.removeChild(t)}};return t.injectCSS(),{_$$alertify:t,reset:function(){return t.reset(),this},alert:function(e,o,n){return t.dialog(e,"alert",o,n)||this},confirm:function(e,o,n){return t.dialog(e,"confirm",o,n)||this},prompt:function(e,o,n){return t.dialog(e,"prompt",o,n)||this},log:function(e,o){return t.log(e,"default",o),this},theme:function(e){return t.theme(e),this},success:function(e,o){return t.log(e,"success",o),this},error:function(e,o){return t.log(e,"error",o),this},cancelBtn:function(e){return t.cancelBtn(e),this},okBtn:function(e){return t.okBtn(e),this},delay:function(e){return t.setDelay(e),this},placeholder:function(e){return t.promptPlaceholder=e,this},defaultValue:function(e){return t.promptValue=e,this},maxLogItems:function(e){return t.setMaxLogItems(e),this},closeLogOnClick:function(e){return t.setCloseLogOnClick(!!e),this},logPosition:function(e){return t.setLogPosition(e||""),this},setLogTemplate:function(e){return t.logTemplateMethod=e,this},clearLogs:function(){return t.setupLogContainer().innerHTML="",this},version:t.version}}var e=500,o=function(t){if(t){var o=function(){t&&t.parentNode&&t.parentNode.removeChild(t)};t.classList.remove("show"),t.classList.add("hide"),t.addEventListener("transitionend",o),setTimeout(o,e)}};if("undefined"!=typeof module&&module&&module.exports){module.exports=function(){return new t};var n=new t;for(var i in n)module.exports[i]=n[i]}else"function"==typeof define&&define.amd?define(function(){return new t}):window.alertify=new t}();
\ No newline at end of file
diff --git a/public/assets/js/fetch.js b/public/assets/js/fetch.js
new file mode 100644
index 00000000..fac11e42
--- /dev/null
+++ b/public/assets/js/fetch.js
@@ -0,0 +1,389 @@
+(function(self) {
+ 'use strict';
+
+ if (self.fetch) {
+ return
+ }
+
+ function normalizeName(name) {
+ if (typeof name !== 'string') {
+ name = String(name)
+ }
+ if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
+ throw new TypeError('Invalid character in header field name')
+ }
+ return name.toLowerCase()
+ }
+
+ function normalizeValue(value) {
+ if (typeof value !== 'string') {
+ value = String(value)
+ }
+ return value
+ }
+
+ function Headers(headers) {
+ this.map = {}
+
+ if (headers instanceof Headers) {
+ headers.forEach(function(value, name) {
+ this.append(name, value)
+ }, this)
+
+ } else if (headers) {
+ Object.getOwnPropertyNames(headers).forEach(function(name) {
+ this.append(name, headers[name])
+ }, this)
+ }
+ }
+
+ Headers.prototype.append = function(name, value) {
+ name = normalizeName(name)
+ value = normalizeValue(value)
+ var list = this.map[name]
+ if (!list) {
+ list = []
+ this.map[name] = list
+ }
+ list.push(value)
+ }
+
+ Headers.prototype['delete'] = function(name) {
+ delete this.map[normalizeName(name)]
+ }
+
+ Headers.prototype.get = function(name) {
+ var values = this.map[normalizeName(name)]
+ return values ? values[0] : null
+ }
+
+ Headers.prototype.getAll = function(name) {
+ return this.map[normalizeName(name)] || []
+ }
+
+ Headers.prototype.has = function(name) {
+ return this.map.hasOwnProperty(normalizeName(name))
+ }
+
+ Headers.prototype.set = function(name, value) {
+ this.map[normalizeName(name)] = [normalizeValue(value)]
+ }
+
+ Headers.prototype.forEach = function(callback, thisArg) {
+ Object.getOwnPropertyNames(this.map).forEach(function(name) {
+ this.map[name].forEach(function(value) {
+ callback.call(thisArg, value, name, this)
+ }, this)
+ }, this)
+ }
+
+ function consumed(body) {
+ if (body.bodyUsed) {
+ return Promise.reject(new TypeError('Already read'))
+ }
+ body.bodyUsed = true
+ }
+
+ function fileReaderReady(reader) {
+ return new Promise(function(resolve, reject) {
+ reader.onload = function() {
+ resolve(reader.result)
+ }
+ reader.onerror = function() {
+ reject(reader.error)
+ }
+ })
+ }
+
+ function readBlobAsArrayBuffer(blob) {
+ var reader = new FileReader()
+ reader.readAsArrayBuffer(blob)
+ return fileReaderReady(reader)
+ }
+
+ function readBlobAsText(blob) {
+ var reader = new FileReader()
+ reader.readAsText(blob)
+ return fileReaderReady(reader)
+ }
+
+ var support = {
+ blob: 'FileReader' in self && 'Blob' in self && (function() {
+ try {
+ new Blob();
+ return true
+ } catch(e) {
+ return false
+ }
+ })(),
+ formData: 'FormData' in self,
+ arrayBuffer: 'ArrayBuffer' in self
+ }
+
+ function Body() {
+ this.bodyUsed = false
+
+
+ this._initBody = function(body) {
+ this._bodyInit = body
+ if (typeof body === 'string') {
+ this._bodyText = body
+ } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
+ this._bodyBlob = body
+ } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
+ this._bodyFormData = body
+ } else if (!body) {
+ this._bodyText = ''
+ } else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) {
+ // Only support ArrayBuffers for POST method.
+ // Receiving ArrayBuffers happens via Blobs, instead.
+ } else {
+ throw new Error('unsupported BodyInit type')
+ }
+
+ if (!this.headers.get('content-type')) {
+ if (typeof body === 'string') {
+ this.headers.set('content-type', 'text/plain;charset=UTF-8')
+ } else if (this._bodyBlob && this._bodyBlob.type) {
+ this.headers.set('content-type', this._bodyBlob.type)
+ }
+ }
+ }
+
+ if (support.blob) {
+ this.blob = function() {
+ var rejected = consumed(this)
+ if (rejected) {
+ return rejected
+ }
+
+ if (this._bodyBlob) {
+ return Promise.resolve(this._bodyBlob)
+ } else if (this._bodyFormData) {
+ throw new Error('could not read FormData body as blob')
+ } else {
+ return Promise.resolve(new Blob([this._bodyText]))
+ }
+ }
+
+ this.arrayBuffer = function() {
+ return this.blob().then(readBlobAsArrayBuffer)
+ }
+
+ this.text = function() {
+ var rejected = consumed(this)
+ if (rejected) {
+ return rejected
+ }
+
+ if (this._bodyBlob) {
+ return readBlobAsText(this._bodyBlob)
+ } else if (this._bodyFormData) {
+ throw new Error('could not read FormData body as text')
+ } else {
+ return Promise.resolve(this._bodyText)
+ }
+ }
+ } else {
+ this.text = function() {
+ var rejected = consumed(this)
+ return rejected ? rejected : Promise.resolve(this._bodyText)
+ }
+ }
+
+ if (support.formData) {
+ this.formData = function() {
+ return this.text().then(decode)
+ }
+ }
+
+ this.json = function() {
+ return this.text().then(JSON.parse)
+ }
+
+ return this
+ }
+
+ // HTTP methods whose capitalization should be normalized
+ var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
+
+ function normalizeMethod(method) {
+ var upcased = method.toUpperCase()
+ return (methods.indexOf(upcased) > -1) ? upcased : method
+ }
+
+ function Request(input, options) {
+ options = options || {}
+ var body = options.body
+ if (Request.prototype.isPrototypeOf(input)) {
+ if (input.bodyUsed) {
+ throw new TypeError('Already read')
+ }
+ this.url = input.url
+ this.credentials = input.credentials
+ if (!options.headers) {
+ this.headers = new Headers(input.headers)
+ }
+ this.method = input.method
+ this.mode = input.mode
+ if (!body) {
+ body = input._bodyInit
+ input.bodyUsed = true
+ }
+ } else {
+ this.url = input
+ }
+
+ this.credentials = options.credentials || this.credentials || 'omit'
+ if (options.headers || !this.headers) {
+ this.headers = new Headers(options.headers)
+ }
+ this.method = normalizeMethod(options.method || this.method || 'GET')
+ this.mode = options.mode || this.mode || null
+ this.referrer = null
+
+ if ((this.method === 'GET' || this.method === 'HEAD') && body) {
+ throw new TypeError('Body not allowed for GET or HEAD requests')
+ }
+ this._initBody(body)
+ }
+
+ Request.prototype.clone = function() {
+ return new Request(this)
+ }
+
+ function decode(body) {
+ var form = new FormData()
+ body.trim().split('&').forEach(function(bytes) {
+ if (bytes) {
+ var split = bytes.split('=')
+ var name = split.shift().replace(/\+/g, ' ')
+ var value = split.join('=').replace(/\+/g, ' ')
+ form.append(decodeURIComponent(name), decodeURIComponent(value))
+ }
+ })
+ return form
+ }
+
+ function headers(xhr) {
+ var head = new Headers()
+ var pairs = xhr.getAllResponseHeaders().trim().split('\n')
+ pairs.forEach(function(header) {
+ var split = header.trim().split(':')
+ var key = split.shift().trim()
+ var value = split.join(':').trim()
+ head.append(key, value)
+ })
+ return head
+ }
+
+ Body.call(Request.prototype)
+
+ function Response(bodyInit, options) {
+ if (!options) {
+ options = {}
+ }
+
+ this.type = 'default'
+ this.status = options.status
+ this.ok = this.status >= 200 && this.status < 300
+ this.statusText = options.statusText
+ this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
+ this.url = options.url || ''
+ this._initBody(bodyInit)
+ }
+
+ Body.call(Response.prototype)
+
+ Response.prototype.clone = function() {
+ return new Response(this._bodyInit, {
+ status: this.status,
+ statusText: this.statusText,
+ headers: new Headers(this.headers),
+ url: this.url
+ })
+ }
+
+ Response.error = function() {
+ var response = new Response(null, {status: 0, statusText: ''})
+ response.type = 'error'
+ return response
+ }
+
+ var redirectStatuses = [301, 302, 303, 307, 308]
+
+ Response.redirect = function(url, status) {
+ if (redirectStatuses.indexOf(status) === -1) {
+ throw new RangeError('Invalid status code')
+ }
+
+ return new Response(null, {status: status, headers: {location: url}})
+ }
+
+ self.Headers = Headers;
+ self.Request = Request;
+ self.Response = Response;
+
+ self.fetch = function(input, init) {
+ return new Promise(function(resolve, reject) {
+ var request
+ if (Request.prototype.isPrototypeOf(input) && !init) {
+ request = input
+ } else {
+ request = new Request(input, init)
+ }
+
+ var xhr = new XMLHttpRequest()
+
+ function responseURL() {
+ if ('responseURL' in xhr) {
+ return xhr.responseURL
+ }
+
+ // Avoid security warnings on getResponseHeader when not allowed by CORS
+ if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
+ return xhr.getResponseHeader('X-Request-URL')
+ }
+
+ return;
+ }
+
+ xhr.onload = function() {
+ var status = (xhr.status === 1223) ? 204 : xhr.status
+ if (status < 100 || status > 599) {
+ reject(new TypeError('Network request failed'))
+ return
+ }
+ var options = {
+ status: status,
+ statusText: xhr.statusText,
+ headers: headers(xhr),
+ url: responseURL()
+ }
+ var body = 'response' in xhr ? xhr.response : xhr.responseText;
+ resolve(new Response(body, options))
+ }
+
+ xhr.onerror = function() {
+ reject(new TypeError('Network request failed'))
+ }
+
+ xhr.open(request.method, request.url, true)
+
+ if (request.credentials === 'include') {
+ xhr.withCredentials = true
+ }
+
+ if ('responseType' in xhr && support.blob) {
+ xhr.responseType = 'blob'
+ }
+
+ request.headers.forEach(function(value, name) {
+ xhr.setRequestHeader(name, value)
+ })
+
+ xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
+ })
+ }
+ self.fetch.polyfill = true
+})(typeof self !== 'undefined' ? self : this);
diff --git a/public/assets/js/form-save.js b/public/assets/js/form-save.js
new file mode 100644
index 00000000..151d1219
--- /dev/null
+++ b/public/assets/js/form-save.js
@@ -0,0 +1,69 @@
+var feature = {
+ addEventListener : !!window.addEventListener,
+ querySelectorAll : !!document.querySelectorAll,
+};
+if(feature.addEventListener && feature.querySelectorAll) {
+ this.init();
+}
+function init() {
+ var keys = getKeys();
+ for(var i = 0; i < keys.length; i++) {
+ if(store.get(keys[i])) {
+ var formId = keys[i].split("~")[1];
+ document.getElementById(formId).value = store.get(keys[i]);
+ }
+ }
+}
+var timerId = window.setInterval(function() {
+ var saved = false;
+ var inputs = document.querySelectorAll('input[type=text], textarea');
+ for(var i = 0; i < inputs.length; i++) {
+ var key = getFormElement(inputs[i]).id + '~' + inputs[i].id;
+ if(store.get(key) !== inputs[i].value && inputs[i].value !== "") {
+ store.set(key, inputs[i].value);
+ saved = true;
+ }
+ }
+ if(saved === true) {
+ alertify.logPosition('top right');
+ alertify.success('Auto saved text');
+ }
+}, 5000);
+var forms = document.querySelectorAll('form');
+for(var f = 0; f < forms.length; f++) {
+ var form = forms[f];
+ form.addEventListener('submit', function() {
+ window.clearInterval(timerId);
+ var formId = form.id;
+ var storedKeys = store.keys();
+ for(var i = 0; i < storedKeys.length; i++) {
+ if(storedKeys[i].indexOf(formId) > -1) {
+ store.remove(storedKeys[i]);
+ }
+ }
+ });
+}
+function getKeys() {
+ var keys = [];
+ var formFields = document.querySelectorAll('input[type=text], textarea');
+ for(var f = 0; f < formFields.length; f++) {
+ var parent = getFormElement(formFields[f]);
+ if(parent !== false) {
+ var key = parent.id + '~' + formFields[f].id;
+ keys.push(key);
+ }
+ }
+ return keys;
+}
+function getFormElement(elem) {
+ if(elem.nodeName.toLowerCase() !== 'body') {
+ var parent = elem.parentNode;
+ if(parent.nodeName.toLowerCase() === 'form') {
+ return parent;
+ } else {
+ return getFormElement(parent);
+ }
+ } else {
+ return false;
+ }
+}
diff --git a/public/assets/js/links.js b/public/assets/js/links.js
new file mode 100644
index 00000000..b683edd5
--- /dev/null
+++ b/public/assets/js/links.js
@@ -0,0 +1,25 @@
+//the autlinker object
+var autolinker = new Autolinker();
+
+//the youtube regex
+var ytidregex = /watch\?v=([A-Za-z0-9\-_]+)/;
+
+//grab the notes and loop through them
+var notes = document.querySelectorAll('.e-content');
+for(var i = 0; i < notes.length; i++) {
+ //get Youtube ID
+ var ytid = notes[i].textContent.match(ytidregex);
+ if(ytid !== null) {
+ var id = ytid[1];
+ var iframe = document.createElement('iframe');
+ iframe.classList.add('youtube');
+ iframe.setAttribute('src', '//www.youtube.com/embed/' + id);
+ iframe.setAttribute('frameborder', 0);
+ iframe.setAttribute('allowfullscreen', 'true');
+ notes[i].appendChild(iframe);
+ }
+ //now linkify everything
+ var orig = notes[i].innerHTML;
+ var linked = autolinker.link(orig);
+ notes[i].innerHTML = linked;
+}
\ No newline at end of file
diff --git a/public/assets/js/maps.js b/public/assets/js/maps.js
new file mode 100644
index 00000000..4901c5da
--- /dev/null
+++ b/public/assets/js/maps.js
@@ -0,0 +1,15 @@
+//This code runs on page load and looks for , then adds map
+var mapDivs = document.querySelectorAll('.map');
+for(var i = 0; i < mapDivs.length; i++) {
+ var mapDiv = mapDivs[i];
+ var latitude = mapDiv.dataset.latitude;
+ var longitude = mapDiv.dataset.longitude;
+ L.mapbox.accessToken = 'pk.eyJ1Ijoiam9ubnliYXJuZXMiLCJhIjoiVlpndW1EYyJ9.aP9fxAqLKh7lj0LpFh5k1w';
+ var map = L.mapbox.map(mapDiv, 'jonnybarnes.gnoihnim')
+ .setView([latitude, longitude], 15)
+ .addLayer(L.mapbox.tileLayer('jonnybarnes.gnoihnim', {
+ detectRetina: true,
+ }));
+ var marker = L.marker([latitude, longitude]).addTo(map);
+ map.scrollWheelZoom.disable();
+}
diff --git a/public/assets/js/marked.min.js b/public/assets/js/marked.min.js
new file mode 100644
index 00000000..555c1dc1
--- /dev/null
+++ b/public/assets/js/marked.min.js
@@ -0,0 +1,6 @@
+/**
+ * marked - a markdown parser
+ * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/chjj/marked
+ */
+(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+=""+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return""+(escaped?code:escape(code,true))+"\n
"}return''+(escaped?code:escape(code,true))+"\n
\n"};Renderer.prototype.blockquote=function(quote){return"\n"+quote+"
\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"'+text+" \n"};Renderer.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+""+type+">\n"};Renderer.prototype.listitem=function(text){return""+text+" \n"};Renderer.prototype.paragraph=function(text){return""+text+"
\n"};Renderer.prototype.table=function(header,body){return"\n"+"\n"+header+"\n"+"\n"+body+"\n"+"
\n"};Renderer.prototype.tablerow=function(content){return"\n"+content+" \n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+""+type+">\n"};Renderer.prototype.strong=function(text){return""+text+""};Renderer.prototype.em=function(text){return""+text+""};Renderer.prototype.codespan=function(text){return""+text+"
"};Renderer.prototype.br=function(){return this.options.xhtml?"
":"
"};Renderer.prototype.del=function(text){return""+text+""};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='"+text+"";return out};Renderer.prototype.image=function(href,title,text){var out='
":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i /g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;iAn error occured:"+escape(e.message+"",true)+"
"}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}());
\ No newline at end of file
diff --git a/public/assets/js/newnote.js b/public/assets/js/newnote.js
new file mode 100644
index 00000000..2a26b261
--- /dev/null
+++ b/public/assets/js/newnote.js
@@ -0,0 +1,284 @@
+if ('geolocation' in navigator) {
+ var button = document.querySelector('#locate');
+ if (button.addEventListener) {
+ //if we have javascript, event listeners and geolocation, make the locate
+ //button clickable and add event
+ button.disabled = false;
+ button.addEventListener('click', getLocation);
+ }
+}
+
+function getLocation() {
+ navigator.geolocation.getCurrentPosition(function (position) {
+ //the locate button has been clicked so add the places/map
+ addPlaces(position.coords.latitude, position.coords.longitude);
+ });
+}
+
+function addPlaces(latitude, longitude) {
+ //get the nearby places
+ fetch('/places/near/' + latitude + '/' + longitude, {
+ credentials: 'same-origin',
+ method: 'get'
+ }).then(function (response) {
+ return response.json();
+ }).then(function (j) {
+ if (j.length > 0) {
+ var i;
+ var places = [];
+ for (i = 0; i < j.length; ++i) {
+ var latlng = parseLocation(j[i].location);
+ var name = j[i].name;
+ var slug = j[i].slug;
+ places.push([name, slug, latlng[0], latlng[1]]);
+ }
+ //add a map with the nearby places
+ addMap(latitude, longitude, places);
+ } else {
+ //add a map with just current location
+ addMap(latitude, longitude);
+ }
+ }).catch(function (err) {
+ console.log(err);
+ });
+}
+
+function addMap(latitude, longitude, places) {
+ //make places null if not supplied
+ if (arguments.length == 2) {
+ places = null;
+ }
+ var form = button.parentNode;
+ var div = document.createElement('div');
+ div.setAttribute('id', 'map');
+ //add the map div
+ form.appendChild(div);
+ L.mapbox.accessToken = 'pk.eyJ1Ijoiam9ubnliYXJuZXMiLCJhIjoiVlpndW1EYyJ9.aP9fxAqLKh7lj0LpFh5k1w';
+ var map = L.mapbox.map('map', 'jonnybarnes.gnoihnim')
+ .setView([latitude, longitude], 15)
+ .addLayer(L.mapbox.tileLayer('jonnybarnes.gnoihnim', {
+ detectRetina: true,
+ }));
+ //add a marker for the current location
+ var marker = L.marker([latitude, longitude], {
+ draggable: true,
+ }).addTo(map);
+ //when the location marker is dragged, if the new place form elements exist
+ //update the lat/lng values
+ marker.on('dragend', function () {
+ var placeFormLatitude = document.querySelector('#place-latitude');
+ if (placeFormLatitude !== null) {
+ placeFormLatitude.value = getLatitudeFromMapboxMarker(marker.getLatLng());
+ }
+ var placeFormLongitude = document.querySelector('#place-longitude');
+ if (placeFormLongitude !== null) {
+ placeFormLongitude.value = getLongitudeFromMapboxMarker(marker.getLatLng());
+ }
+ });
+ //create the