From 12a9a45747f3ce3ff58464cd7ccb88f2c42438e8 Mon Sep 17 00:00:00 2001 From: benrubson <6764151+benrubson@users.noreply.github.com> Date: Sun, 9 Feb 2020 10:01:33 +0100 Subject: [PATCH 01/75] Log failed accesses --- app/Http/Controllers/Auth/LoginController.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index ea584a3b6..75ade74e7 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -101,6 +101,9 @@ class LoginController extends Controller $this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); + // Also log some error message + $this->logFailedAccess($request); + return $this->sendLockoutResponse($request); } @@ -117,6 +120,9 @@ class LoginController extends Controller // user surpasses their maximum number of attempts they will get locked out. $this->incrementLoginAttempts($request); + // Also log some error message + $this->logFailedAccess($request); + return $this->sendFailedLoginResponse($request); } @@ -162,4 +168,16 @@ class LoginController extends Controller return redirect('/login'); } + /** + * Log failed accesses, matching the default fail2ban nginx/apache auth rules. + */ + protected function logFailedAccess(Request $request) + { + if (isset($_SERVER['SERVER_SOFTWARE']) && preg_match('/nginx/i', $_SERVER['SERVER_SOFTWARE'])) { + error_log('user "' . $request->get($this->username()) . '" was not found in "BookStack"', 4); + } else { + error_log('user "' . $request->get($this->username()) . '" authentication failure for "BookStack"', 4); + } + } + } From 58df3ad9566061186e62110e7c1e4a4140ed02c2 Mon Sep 17 00:00:00 2001 From: benrubson <6764151+benrubson@users.noreply.github.com> Date: Sun, 3 May 2020 16:20:02 +0200 Subject: [PATCH 02/75] Log failed accesses option --- .env.example.complete | 8 ++++++- app/Http/Controllers/Auth/LoginController.php | 21 ++++++++++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.env.example.complete b/.env.example.complete index 04cd73b90..5b62b1a2a 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -266,4 +266,10 @@ API_DEFAULT_ITEM_COUNT=100 API_MAX_ITEM_COUNT=500 # The number of API requests that can be made per minute by a single user. -API_REQUESTS_PER_MIN=180 \ No newline at end of file +API_REQUESTS_PER_MIN=180 + +# Failed access +# message to log into webserver logs in case of failed access, for further processing by tools like Fail2Ban +# Apache users should use : user "%u" authentication failure for "BookStack" +# Nginx users should use : user "%u" was not found in "BookStack" +FAILED_ACCESS_MESSAGE='' diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 75ade74e7..c000af49e 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -169,15 +169,20 @@ class LoginController extends Controller } /** - * Log failed accesses, matching the default fail2ban nginx/apache auth rules. - */ - protected function logFailedAccess(Request $request) + * Log failed accesses, for further processing by tools like Fail2Ban + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function logFailedAccess($request) { - if (isset($_SERVER['SERVER_SOFTWARE']) && preg_match('/nginx/i', $_SERVER['SERVER_SOFTWARE'])) { - error_log('user "' . $request->get($this->username()) . '" was not found in "BookStack"', 4); - } else { - error_log('user "' . $request->get($this->username()) . '" authentication failure for "BookStack"', 4); - } + $log_msg = env('FAILED_ACCESS_MESSAGE', ''); + + if (!is_string($request->get($this->username())) || !is_string($log_msg) || strlen($log_msg)<1) + return; + + $log_msg = str_replace("%u", $request->get($this->username()), $log_msg); + error_log($log_msg, 4); } } From 24bad5034af0142b43c97c4dad0353bb490b54c8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 22 May 2020 22:34:18 +0100 Subject: [PATCH 03/75] Updated API auth to allow public user if given permission --- app/Http/Middleware/ApiAuthenticate.php | 4 ++-- tests/Api/ApiDocsTest.php | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/Http/Middleware/ApiAuthenticate.php b/app/Http/Middleware/ApiAuthenticate.php index 15962b3b0..728057bed 100644 --- a/app/Http/Middleware/ApiAuthenticate.php +++ b/app/Http/Middleware/ApiAuthenticate.php @@ -35,9 +35,9 @@ class ApiAuthenticate { // Return if the user is already found to be signed in via session-based auth. // This is to make it easy to browser the API via browser after just logging into the system. - if (signedInUser()) { + if (signedInUser() || session()->isStarted()) { $this->ensureEmailConfirmedIfRequested(); - if (!auth()->user()->can('access-api')) { + if (!user()->can('access-api')) { throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403); } return; diff --git a/tests/Api/ApiDocsTest.php b/tests/Api/ApiDocsTest.php index 3cbcadfa3..1687c64a1 100644 --- a/tests/Api/ApiDocsTest.php +++ b/tests/Api/ApiDocsTest.php @@ -1,5 +1,6 @@ setSettings(['app-public' => true]); + $guest = User::getDefault(); + + $this->startSession(); + $resp = $this->get('/api/docs'); + $resp->assertStatus(403); + + $this->giveUserPermissions($guest, ['access-api']); + + $resp = $this->get('/api/docs'); + $resp->assertStatus(200); + } } \ No newline at end of file From 8a6cf0cdecf8596c7cd4809188712f575c9e8202 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 23 May 2020 00:28:41 +0100 Subject: [PATCH 04/75] Added chapters to the API --- app/Actions/Tag.php | 1 + app/Actions/TagRepo.php | 5 +- app/Auth/User.php | 2 +- app/Entities/Book.php | 2 +- app/Entities/Bookshelf.php | 2 +- app/Entities/Chapter.php | 1 + app/Entities/Entity.php | 4 +- app/Entities/Page.php | 2 + app/Entities/Repos/PageRepo.php | 2 +- ...piController.php => BookApiController.php} | 4 +- ...roller.php => BookExportApiController.php} | 3 +- .../Controllers/Api/ChapterApiController.php | 104 ++++++++++ .../Api/ChapterExportApiController.php | 54 +++++ dev/api/requests/chapters-create.json | 9 + dev/api/requests/chapters-update.json | 9 + dev/api/responses/books-read.json | 7 +- dev/api/responses/chapters-create.json | 38 ++++ dev/api/responses/chapters-list.json | 29 +++ dev/api/responses/chapters-read.json | 59 ++++++ dev/api/responses/chapters-update.json | 38 ++++ dev/api/responses/shelves-read.json | 7 +- routes/api.php | 28 ++- tests/Api/ChaptersApiTest.php | 186 ++++++++++++++++++ tests/Api/TestsApi.php | 10 + 24 files changed, 575 insertions(+), 31 deletions(-) rename app/Http/Controllers/Api/{BooksApiController.php => BookApiController.php} (96%) rename app/Http/Controllers/Api/{BooksExportApiController.php => BookExportApiController.php} (96%) create mode 100644 app/Http/Controllers/Api/ChapterApiController.php create mode 100644 app/Http/Controllers/Api/ChapterExportApiController.php create mode 100644 dev/api/requests/chapters-create.json create mode 100644 dev/api/requests/chapters-update.json create mode 100644 dev/api/responses/chapters-create.json create mode 100644 dev/api/responses/chapters-list.json create mode 100644 dev/api/responses/chapters-read.json create mode 100644 dev/api/responses/chapters-update.json create mode 100644 tests/Api/ChaptersApiTest.php diff --git a/app/Actions/Tag.php b/app/Actions/Tag.php index 38d0458e4..80a911508 100644 --- a/app/Actions/Tag.php +++ b/app/Actions/Tag.php @@ -9,6 +9,7 @@ use BookStack\Model; class Tag extends Model { protected $fillable = ['name', 'value', 'order']; + protected $hidden = ['id', 'entity_id', 'entity_type']; /** * Get the entity that this tag belongs to diff --git a/app/Actions/TagRepo.php b/app/Actions/TagRepo.php index 0cbfa4163..b8b1eb464 100644 --- a/app/Actions/TagRepo.php +++ b/app/Actions/TagRepo.php @@ -106,14 +106,13 @@ class TagRepo /** * Save an array of tags to an entity - * @param \BookStack\Entities\Entity $entity - * @param array $tags * @return array|\Illuminate\Database\Eloquent\Collection */ - public function saveTagsToEntity(Entity $entity, $tags = []) + public function saveTagsToEntity(Entity $entity, array $tags = []) { $entity->tags()->delete(); $newTags = []; + foreach ($tags as $tag) { if (trim($tag['name']) === '') { continue; diff --git a/app/Auth/User.php b/app/Auth/User.php index a581d9993..40718beb6 100644 --- a/app/Auth/User.php +++ b/app/Auth/User.php @@ -49,7 +49,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ protected $hidden = [ 'password', 'remember_token', 'system_name', 'email_confirmed', 'external_auth_id', 'email', - 'created_at', 'updated_at', + 'created_at', 'updated_at', 'image_id', ]; /** diff --git a/app/Entities/Book.php b/app/Entities/Book.php index 38b7d4a8c..af8344b88 100644 --- a/app/Entities/Book.php +++ b/app/Entities/Book.php @@ -19,7 +19,7 @@ class Book extends Entity implements HasCoverImage public $searchFactor = 2; protected $fillable = ['name', 'description']; - protected $hidden = ['restricted', 'pivot']; + protected $hidden = ['restricted', 'pivot', 'image_id']; /** * Get the url for this book. diff --git a/app/Entities/Bookshelf.php b/app/Entities/Bookshelf.php index c7ba840e0..474ba51cd 100644 --- a/app/Entities/Bookshelf.php +++ b/app/Entities/Bookshelf.php @@ -12,7 +12,7 @@ class Bookshelf extends Entity implements HasCoverImage protected $fillable = ['name', 'description', 'image_id']; - protected $hidden = ['restricted']; + protected $hidden = ['restricted', 'image_id']; /** * Get the books in this shelf. diff --git a/app/Entities/Chapter.php b/app/Entities/Chapter.php index 848bc6448..3290afcfa 100644 --- a/app/Entities/Chapter.php +++ b/app/Entities/Chapter.php @@ -12,6 +12,7 @@ class Chapter extends BookChild public $searchFactor = 1.3; protected $fillable = ['name', 'description', 'priority', 'book_id']; + protected $hidden = ['restricted', 'pivot']; /** * Get the pages that this chapter contains. diff --git a/app/Entities/Entity.php b/app/Entities/Entity.php index 5013c39cf..6a5894cac 100644 --- a/app/Entities/Entity.php +++ b/app/Entities/Entity.php @@ -288,7 +288,7 @@ class Entity extends Ownable public function rebuildPermissions() { /** @noinspection PhpUnhandledExceptionInspection */ - Permissions::buildJointPermissionsForEntity($this); + Permissions::buildJointPermissionsForEntity(clone $this); } /** @@ -297,7 +297,7 @@ class Entity extends Ownable public function indexForSearch() { $searchService = app()->make(SearchService::class); - $searchService->indexEntity($this); + $searchService->indexEntity(clone $this); } /** diff --git a/app/Entities/Page.php b/app/Entities/Page.php index 76dc628fb..d10786dda 100644 --- a/app/Entities/Page.php +++ b/app/Entities/Page.php @@ -27,6 +27,8 @@ class Page extends BookChild public $textField = 'text'; + protected $hidden = ['html', 'markdown', 'text', 'restricted', 'pivot']; + /** * Get the entities that are visible to the current user. */ diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index e49eeb1ef..d92085a61 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -211,7 +211,7 @@ class PageRepo */ protected function savePageRevision(Page $page, string $summary = null) { - $revision = new PageRevision($page->toArray()); + $revision = new PageRevision($page->getAttributes()); if (setting('app-editor') !== 'markdown') { $revision->markdown = ''; diff --git a/app/Http/Controllers/Api/BooksApiController.php b/app/Http/Controllers/Api/BookApiController.php similarity index 96% rename from app/Http/Controllers/Api/BooksApiController.php rename to app/Http/Controllers/Api/BookApiController.php index ac4ea171c..8333eba3a 100644 --- a/app/Http/Controllers/Api/BooksApiController.php +++ b/app/Http/Controllers/Api/BookApiController.php @@ -8,7 +8,7 @@ use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; -class BooksApiController extends ApiController +class BookApiController extends ApiController { protected $bookRepo; @@ -17,10 +17,12 @@ class BooksApiController extends ApiController 'create' => [ 'name' => 'required|string|max:255', 'description' => 'string|max:1000', + 'tags' => 'array', ], 'update' => [ 'name' => 'string|min:1|max:255', 'description' => 'string|max:1000', + 'tags' => 'array', ], ]; diff --git a/app/Http/Controllers/Api/BooksExportApiController.php b/app/Http/Controllers/Api/BookExportApiController.php similarity index 96% rename from app/Http/Controllers/Api/BooksExportApiController.php rename to app/Http/Controllers/Api/BookExportApiController.php index 605f8f408..31fe5250f 100644 --- a/app/Http/Controllers/Api/BooksExportApiController.php +++ b/app/Http/Controllers/Api/BookExportApiController.php @@ -5,9 +5,8 @@ use BookStack\Entities\ExportService; use BookStack\Entities\Repos\BookRepo; use Throwable; -class BooksExportApiController extends ApiController +class BookExportApiController extends ApiController { - protected $bookRepo; protected $exportService; diff --git a/app/Http/Controllers/Api/ChapterApiController.php b/app/Http/Controllers/Api/ChapterApiController.php new file mode 100644 index 000000000..50aa8834e --- /dev/null +++ b/app/Http/Controllers/Api/ChapterApiController.php @@ -0,0 +1,104 @@ + [ + 'book_id' => 'required|integer', + 'name' => 'required|string|max:255', + 'description' => 'string|max:1000', + 'tags' => 'array', + ], + 'update' => [ + 'book_id' => 'integer', + 'name' => 'string|min:1|max:255', + 'description' => 'string|max:1000', + 'tags' => 'array', + ], + ]; + + /** + * ChapterController constructor. + */ + public function __construct(ChapterRepo $chapterRepo) + { + $this->chapterRepo = $chapterRepo; + } + + /** + * Get a listing of chapters visible to the user. + */ + public function list() + { + $chapters = Chapter::visible(); + return $this->apiListingResponse($chapters, [ + 'id', 'book_id', 'name', 'slug', 'description', 'priority', + 'created_at', 'updated_at', 'created_by', 'updated_by', + ]); + } + + /** + * Create a new chapter in the system. + */ + public function create(Request $request) + { + $this->validate($request, $this->rules['create']); + + $bookId = $request->get('book_id'); + $book = Book::visible()->findOrFail($bookId); + $this->checkOwnablePermission('chapter-create', $book); + + $chapter = $this->chapterRepo->create($request->all(), $book); + Activity::add($chapter, 'chapter_create', $book->id); + + return response()->json($chapter->load(['tags'])); + } + + /** + * View the details of a single chapter. + */ + public function read(string $id) + { + $chapter = Chapter::visible()->with(['tags', 'createdBy', 'updatedBy', 'pages' => function (HasMany $query) { + $query->visible()->get(['id', 'name', 'slug']); + }])->findOrFail($id); + return response()->json($chapter); + } + + /** + * Update the details of a single chapter. + */ + public function update(Request $request, string $id) + { + $chapter = Chapter::visible()->findOrFail($id); + $this->checkOwnablePermission('chapter-update', $chapter); + + $updatedChapter = $this->chapterRepo->update($chapter, $request->all()); + Activity::add($chapter, 'chapter_update', $chapter->book->id); + + return response()->json($updatedChapter->load(['tags'])); + } + + /** + * Delete a chapter from the system. + */ + public function delete(string $id) + { + $chapter = Chapter::visible()->findOrFail($id); + $this->checkOwnablePermission('chapter-delete', $chapter); + + $this->chapterRepo->destroy($chapter); + Activity::addMessage('chapter_delete', $chapter->name, $chapter->book->id); + + return response('', 204); + } +} diff --git a/app/Http/Controllers/Api/ChapterExportApiController.php b/app/Http/Controllers/Api/ChapterExportApiController.php new file mode 100644 index 000000000..f19f29e9d --- /dev/null +++ b/app/Http/Controllers/Api/ChapterExportApiController.php @@ -0,0 +1,54 @@ +chapterRepo = $chapterRepo; + $this->exportService = $exportService; + parent::__construct(); + } + + /** + * Export a chapter as a PDF file. + * @throws Throwable + */ + public function exportPdf(int $id) + { + $chapter = Chapter::visible()->findOrFail($id); + $pdfContent = $this->exportService->chapterToPdf($chapter); + return $this->downloadResponse($pdfContent, $chapter->slug . '.pdf'); + } + + /** + * Export a chapter as a contained HTML file. + * @throws Throwable + */ + public function exportHtml(int $id) + { + $chapter = Chapter::visible()->findOrFail($id); + $htmlContent = $this->exportService->chapterToContainedHtml($chapter); + return $this->downloadResponse($htmlContent, $chapter->slug . '.html'); + } + + /** + * Export a chapter as a plain text file. + */ + public function exportPlainText(int $id) + { + $chapter = Chapter::visible()->findOrFail($id); + $textContent = $this->exportService->chapterToPlainText($chapter); + return $this->downloadResponse($textContent, $chapter->slug . '.txt'); + } +} diff --git a/dev/api/requests/chapters-create.json b/dev/api/requests/chapters-create.json new file mode 100644 index 000000000..ca06fc298 --- /dev/null +++ b/dev/api/requests/chapters-create.json @@ -0,0 +1,9 @@ +{ + "book_id": 1, + "name": "My fantastic new chapter", + "description": "This is a great new chapter that I've created via the API", + "tags": [ + {"name": "Category", "value": "Top Content"}, + {"name": "Rating", "value": "Highest"} + ] +} \ No newline at end of file diff --git a/dev/api/requests/chapters-update.json b/dev/api/requests/chapters-update.json new file mode 100644 index 000000000..6bd3a3e5c --- /dev/null +++ b/dev/api/requests/chapters-update.json @@ -0,0 +1,9 @@ +{ + "book_id": 1, + "name": "My fantastic updated chapter", + "description": "This is an updated chapter that I've altered via the API", + "tags": [ + {"name": "Category", "value": "Kinda Good Content"}, + {"name": "Rating", "value": "Medium"} + ] +} \ No newline at end of file diff --git a/dev/api/responses/books-read.json b/dev/api/responses/books-read.json index 11408e9ab..2e43f5f87 100644 --- a/dev/api/responses/books-read.json +++ b/dev/api/responses/books-read.json @@ -7,15 +7,12 @@ "updated_at": "2020-01-12 14:11:51", "created_by": { "id": 1, - "name": "Admin", - "image_id": 48 + "name": "Admin" }, "updated_by": { "id": 1, - "name": "Admin", - "image_id": 48 + "name": "Admin" }, - "image_id": 452, "tags": [ { "id": 13, diff --git a/dev/api/responses/chapters-create.json b/dev/api/responses/chapters-create.json new file mode 100644 index 000000000..7aac27687 --- /dev/null +++ b/dev/api/responses/chapters-create.json @@ -0,0 +1,38 @@ +{ + "book_id": 1, + "priority": 6, + "name": "My fantastic new chapter", + "description": "This is a great new chapter that I've created via the API", + "created_by": 1, + "updated_by": 1, + "slug": "my-fantastic-new-chapter", + "updated_at": "2020-05-22 22:59:55", + "created_at": "2020-05-22 22:59:55", + "id": 74, + "book": { + "id": 1, + "name": "BookStack User Guide", + "slug": "bookstack-user-guide", + "description": "This is a general guide on using BookStack on a day-to-day basis.", + "created_at": "2019-05-05 21:48:46", + "updated_at": "2019-12-11 20:57:31", + "created_by": 1, + "updated_by": 1 + }, + "tags": [ + { + "name": "Category", + "value": "Top Content", + "order": 0, + "created_at": "2020-05-22 22:59:55", + "updated_at": "2020-05-22 22:59:55" + }, + { + "name": "Rating", + "value": "Highest", + "order": 0, + "created_at": "2020-05-22 22:59:55", + "updated_at": "2020-05-22 22:59:55" + } + ] +} \ No newline at end of file diff --git a/dev/api/responses/chapters-list.json b/dev/api/responses/chapters-list.json new file mode 100644 index 000000000..0c1fc5fc2 --- /dev/null +++ b/dev/api/responses/chapters-list.json @@ -0,0 +1,29 @@ +{ + "data": [ + { + "id": 1, + "book_id": 1, + "name": "Content Creation", + "slug": "content-creation", + "description": "How to create documentation on whatever subject you need to write about.", + "priority": 3, + "created_at": "2019-05-05 21:49:56", + "updated_at": "2019-09-28 11:24:23", + "created_by": 1, + "updated_by": 1 + }, + { + "id": 2, + "book_id": 1, + "name": "Managing Content", + "slug": "managing-content", + "description": "How to keep things organised and orderly in the system for easier navigation and better user experience.", + "priority": 5, + "created_at": "2019-05-05 21:58:07", + "updated_at": "2019-10-17 15:05:34", + "created_by": 3, + "updated_by": 3 + } + ], + "total": 40 +} \ No newline at end of file diff --git a/dev/api/responses/chapters-read.json b/dev/api/responses/chapters-read.json new file mode 100644 index 000000000..2eddad895 --- /dev/null +++ b/dev/api/responses/chapters-read.json @@ -0,0 +1,59 @@ +{ + "id": 1, + "book_id": 1, + "slug": "content-creation", + "name": "Content Creation", + "description": "How to create documentation on whatever subject you need to write about.", + "priority": 3, + "created_at": "2019-05-05 21:49:56", + "updated_at": "2019-09-28 11:24:23", + "created_by": { + "id": 1, + "name": "Admin" + }, + "updated_by": { + "id": 1, + "name": "Admin" + }, + "tags": [ + { + "name": "Category", + "value": "Guide", + "order": 0, + "created_at": "2020-05-22 22:51:51", + "updated_at": "2020-05-22 22:51:51" + } + ], + "pages": [ + { + "id": 1, + "book_id": 1, + "chapter_id": 1, + "name": "How to create page content", + "slug": "how-to-create-page-content", + "priority": 0, + "created_at": "2019-05-05 21:49:58", + "updated_at": "2019-08-26 14:32:59", + "created_by": 1, + "updated_by": 1, + "draft": 0, + "revision_count": 2, + "template": 0 + }, + { + "id": 7, + "book_id": 1, + "chapter_id": 1, + "name": "Good book structure", + "slug": "good-book-structure", + "priority": 1, + "created_at": "2019-05-05 22:01:55", + "updated_at": "2019-06-06 12:03:04", + "created_by": 3, + "updated_by": 3, + "draft": 0, + "revision_count": 1, + "template": 0 + } + ] +} \ No newline at end of file diff --git a/dev/api/responses/chapters-update.json b/dev/api/responses/chapters-update.json new file mode 100644 index 000000000..a7edb15b0 --- /dev/null +++ b/dev/api/responses/chapters-update.json @@ -0,0 +1,38 @@ +{ + "id": 75, + "book_id": 1, + "slug": "my-fantastic-updated-chapter", + "name": "My fantastic updated chapter", + "description": "This is an updated chapter that I've altered via the API", + "priority": 7, + "created_at": "2020-05-22 23:03:35", + "updated_at": "2020-05-22 23:07:20", + "created_by": 1, + "updated_by": 1, + "book": { + "id": 1, + "name": "BookStack User Guide", + "slug": "bookstack-user-guide", + "description": "This is a general guide on using BookStack on a day-to-day basis.", + "created_at": "2019-05-05 21:48:46", + "updated_at": "2019-12-11 20:57:31", + "created_by": 1, + "updated_by": 1 + }, + "tags": [ + { + "name": "Category", + "value": "Kinda Good Content", + "order": 0, + "created_at": "2020-05-22 23:07:20", + "updated_at": "2020-05-22 23:07:20" + }, + { + "name": "Rating", + "value": "Medium", + "order": 0, + "created_at": "2020-05-22 23:07:20", + "updated_at": "2020-05-22 23:07:20" + } + ] +} \ No newline at end of file diff --git a/dev/api/responses/shelves-read.json b/dev/api/responses/shelves-read.json index 8a8e2348b..634fbb5a5 100644 --- a/dev/api/responses/shelves-read.json +++ b/dev/api/responses/shelves-read.json @@ -5,15 +5,12 @@ "description": "This is my shelf with some books", "created_by": { "id": 1, - "name": "Admin", - "image_id": 48 + "name": "Admin" }, "updated_by": { "id": 1, - "name": "Admin", - "image_id": 48 + "name": "Admin" }, - "image_id": 501, "created_at": "2020-04-10 13:24:09", "updated_at": "2020-04-10 13:31:04", "tags": [ diff --git a/routes/api.php b/routes/api.php index f9c27b62f..1b90d9b8f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -9,18 +9,28 @@ Route::get('docs', 'ApiDocsController@display'); Route::get('docs.json', 'ApiDocsController@json'); -Route::get('books', 'BooksApiController@list'); -Route::post('books', 'BooksApiController@create'); -Route::get('books/{id}', 'BooksApiController@read'); -Route::put('books/{id}', 'BooksApiController@update'); -Route::delete('books/{id}', 'BooksApiController@delete'); +Route::get('books', 'BookApiController@list'); +Route::post('books', 'BookApiController@create'); +Route::get('books/{id}', 'BookApiController@read'); +Route::put('books/{id}', 'BookApiController@update'); +Route::delete('books/{id}', 'BookApiController@delete'); -Route::get('books/{id}/export/html', 'BooksExportApiController@exportHtml'); -Route::get('books/{id}/export/pdf', 'BooksExportApiController@exportPdf'); -Route::get('books/{id}/export/plaintext', 'BooksExportApiController@exportPlainText'); +Route::get('books/{id}/export/html', 'BookExportApiController@exportHtml'); +Route::get('books/{id}/export/pdf', 'BookExportApiController@exportPdf'); +Route::get('books/{id}/export/plaintext', 'BookExportApiController@exportPlainText'); + +Route::get('chapters', 'ChapterApiController@list'); +Route::post('chapters', 'ChapterApiController@create'); +Route::get('chapters/{id}', 'ChapterApiController@read'); +Route::put('chapters/{id}', 'ChapterApiController@update'); +Route::delete('chapters/{id}', 'ChapterApiController@delete'); + +Route::get('chapters/{id}/export/html', 'ChapterExportApiController@exportHtml'); +Route::get('chapters/{id}/export/pdf', 'ChapterExportApiController@exportPdf'); +Route::get('chapters/{id}/export/plaintext', 'ChapterExportApiController@exportPlainText'); Route::get('shelves', 'BookshelfApiController@list'); Route::post('shelves', 'BookshelfApiController@create'); Route::get('shelves/{id}', 'BookshelfApiController@read'); Route::put('shelves/{id}', 'BookshelfApiController@update'); -Route::delete('shelves/{id}', 'BookshelfApiController@delete'); \ No newline at end of file +Route::delete('shelves/{id}', 'BookshelfApiController@delete'); diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php new file mode 100644 index 000000000..15a44459e --- /dev/null +++ b/tests/Api/ChaptersApiTest.php @@ -0,0 +1,186 @@ +actingAsApiEditor(); + $firstChapter = Chapter::query()->orderBy('id', 'asc')->first(); + + $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id'); + $resp->assertJson(['data' => [ + [ + 'id' => $firstChapter->id, + 'name' => $firstChapter->name, + 'slug' => $firstChapter->slug, + 'book_id' => $firstChapter->book->id, + 'priority' => $firstChapter->priority, + ] + ]]); + } + + public function test_create_endpoint() + { + $this->actingAsApiEditor(); + $book = Book::query()->first(); + $details = [ + 'name' => 'My API chapter', + 'description' => 'A chapter created via the API', + 'book_id' => $book->id, + 'tags' => [ + [ + 'name' => 'tagname', + 'value' => 'tagvalue', + ] + ] + ]; + + $resp = $this->postJson($this->baseEndpoint, $details); + $resp->assertStatus(200); + $newItem = Chapter::query()->orderByDesc('id')->where('name', '=', $details['name'])->first(); + $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug])); + $this->assertDatabaseHas('tags', [ + 'entity_id' => $newItem->id, + 'entity_type' => $newItem->getMorphClass(), + 'name' => 'tagname', + 'value' => 'tagvalue', + ]); + $resp->assertJsonMissing(['pages' => []]); + $this->assertActivityExists('chapter_create', $newItem); + } + + public function test_chapter_name_needed_to_create() + { + $this->actingAsApiEditor(); + $book = Book::query()->first(); + $details = [ + 'book_id' => $book->id, + 'description' => 'A chapter created via the API', + ]; + + $resp = $this->postJson($this->baseEndpoint, $details); + $resp->assertStatus(422); + $resp->assertJson($this->validationResponse([ + "name" => ["The name field is required."] + ])); + } + + public function test_chapter_book_id_needed_to_create() + { + $this->actingAsApiEditor(); + $details = [ + 'name' => 'My api chapter', + 'description' => 'A chapter created via the API', + ]; + + $resp = $this->postJson($this->baseEndpoint, $details); + $resp->assertStatus(422); + $resp->assertJson($this->validationResponse([ + "book_id" => ["The book id field is required."] + ])); + } + + public function test_read_endpoint() + { + $this->actingAsApiEditor(); + $chapter = Chapter::visible()->first(); + $page = $chapter->pages()->first(); + + $resp = $this->getJson($this->baseEndpoint . "/{$chapter->id}"); + $resp->assertStatus(200); + $resp->assertJson([ + 'id' => $chapter->id, + 'slug' => $chapter->slug, + 'created_by' => [ + 'name' => $chapter->createdBy->name, + ], + 'book_id' => $chapter->book_id, + 'updated_by' => [ + 'name' => $chapter->createdBy->name, + ], + 'pages' => [ + [ + 'id' => $page->id, + 'slug' => $page->slug, + 'name' => $page->name, + ] + ], + ]); + $resp->assertJsonCount($chapter->pages()->count(), 'pages'); + } + + public function test_update_endpoint() + { + $this->actingAsApiEditor(); + $chapter = Chapter::visible()->first(); + $details = [ + 'name' => 'My updated API chapter', + 'description' => 'A chapter created via the API', + 'tags' => [ + [ + 'name' => 'freshtag', + 'value' => 'freshtagval', + ] + ], + ]; + + $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details); + $chapter->refresh(); + + $resp->assertStatus(200); + $resp->assertJson(array_merge($details, [ + 'id' => $chapter->id, 'slug' => $chapter->slug, 'book_id' => $chapter->book_id + ])); + $this->assertActivityExists('chapter_update', $chapter); + } + + public function test_delete_endpoint() + { + $this->actingAsApiEditor(); + $chapter = Chapter::visible()->first(); + $resp = $this->deleteJson($this->baseEndpoint . "/{$chapter->id}"); + + $resp->assertStatus(204); + $this->assertActivityExists('chapter_delete'); + } + + public function test_export_html_endpoint() + { + $this->actingAsApiEditor(); + $chapter = Chapter::visible()->first(); + + $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/html"); + $resp->assertStatus(200); + $resp->assertSee($chapter->name); + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html"'); + } + + public function test_export_plain_text_endpoint() + { + $this->actingAsApiEditor(); + $chapter = Chapter::visible()->first(); + + $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/plaintext"); + $resp->assertStatus(200); + $resp->assertSee($chapter->name); + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.txt"'); + } + + public function test_export_pdf_endpoint() + { + $this->actingAsApiEditor(); + $chapter = Chapter::visible()->first(); + + $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/pdf"); + $resp->assertStatus(200); + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"'); + } +} \ No newline at end of file diff --git a/tests/Api/TestsApi.php b/tests/Api/TestsApi.php index 623fa6969..1ad4d14b6 100644 --- a/tests/Api/TestsApi.php +++ b/tests/Api/TestsApi.php @@ -23,6 +23,16 @@ trait TestsApi return ["error" => ["code" => $code, "message" => $message]]; } + /** + * Format the given (field_name => ["messages"]) array + * into a standard validation response format. + */ + protected function validationResponse(array $messages): array + { + $err = $this->errorResponse("The given data was invalid.", 422); + $err['error']['validation'] = $messages; + return $err; + } /** * Get an approved API auth header. */ From b61f950560d4300b337c843ca242261b1bba5686 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 23 May 2020 00:29:09 +0100 Subject: [PATCH 05/75] Incremented version number --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index d40040377..31c2e1d4b 100644 --- a/version +++ b/version @@ -1 +1 @@ -v0.28-dev +v0.30-dev From 00c0815808ffd98ee32aef8be56f1ba2dba85ea5 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 23 May 2020 00:46:13 +0100 Subject: [PATCH 06/75] Fixed issue where updated page content would not be indexed - Also updated html field of pages to not be fillable. (Since HTML should always go through app id parsing) Related to #2042 --- app/Entities/Page.php | 2 +- app/Entities/Repos/PageRepo.php | 5 ++--- tests/Entity/EntitySearchTest.php | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/Entities/Page.php b/app/Entities/Page.php index d10786dda..32ba2981d 100644 --- a/app/Entities/Page.php +++ b/app/Entities/Page.php @@ -21,7 +21,7 @@ use Permissions; */ class Page extends BookChild { - protected $fillable = ['name', 'html', 'priority', 'markdown']; + protected $fillable = ['name', 'priority', 'markdown']; protected $simpleAttributes = ['name', 'id', 'slug']; diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index d92085a61..fc585b4df 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -180,12 +180,11 @@ class PageRepo $page->template = ($input['template'] === 'true'); } + $pageContent = new PageContent($page); + $pageContent->setNewHTML($input['html']); $this->baseRepo->update($page, $input); // Update with new details - $page->fill($input); - $pageContent = new PageContent($page); - $pageContent->setNewHTML($input['html']); $page->revision_count++; if (setting('app-editor') !== 'markdown') { diff --git a/tests/Entity/EntitySearchTest.php b/tests/Entity/EntitySearchTest.php index 72eb808dc..956e46c37 100644 --- a/tests/Entity/EntitySearchTest.php +++ b/tests/Entity/EntitySearchTest.php @@ -277,4 +277,20 @@ class EntitySearchTest extends TestCase $search->assertSee($expectedShelf->name); } } + + public function test_search_works_on_updated_page_content() + { + $page = Page::query()->first(); + $this->asEditor(); + + $update = $this->put($page->getUrl(), [ + 'name' => $page->name, + 'html' => '

dog pandabearmonster spaghetti

', + ]); + + $search = $this->asEditor()->get('/search?term=pandabearmonster'); + $search->assertStatus(200); + $search->assertSeeText($page->name); + $search->assertSee($page->getUrl()); + } } From bf4a3b73f87f7ce4f107ca45a292ab038703f20b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 23 May 2020 00:53:13 +0100 Subject: [PATCH 07/75] Updated listing endpoints to be clickable in api docs --- resources/views/api-docs/index.blade.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/views/api-docs/index.blade.php b/resources/views/api-docs/index.blade.php index e92b505cf..d9c3d6595 100644 --- a/resources/views/api-docs/index.blade.php +++ b/resources/views/api-docs/index.blade.php @@ -189,7 +189,11 @@
{{ $endpoint['controller_method_kebab'] }}
{{ $endpoint['method'] }} - {{ url($endpoint['uri']) }} + @if($endpoint['controller_method_kebab'] === 'list') + {{ url($endpoint['uri']) }} + @else + {{ url($endpoint['uri']) }} + @endif

{{ $endpoint['description'] ?? '' }}

@if($endpoint['body_params'] ?? false) From 8f1f73defa321f026d487b3e9344055746bc6f58 Mon Sep 17 00:00:00 2001 From: benrubson <6764151+benrubson@users.noreply.github.com> Date: Sat, 23 May 2020 12:06:37 +0200 Subject: [PATCH 08/75] Properly use env/config functions --- app/Config/logging.php | 5 +++++ app/Http/Controllers/Auth/LoginController.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Config/logging.php b/app/Config/logging.php index 0b55dc24d..406b9f2f9 100644 --- a/app/Config/logging.php +++ b/app/Config/logging.php @@ -79,4 +79,9 @@ return [ ], ], + // Failed Access Message + // Defines the message to log into webserver logs in case of failed access, + // for further processing by tools like Fail2Ban. + 'failed_access_message' => env('FAILED_ACCESS_MESSAGE', ''), + ]; diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index c000af49e..cf9e44e43 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -176,7 +176,7 @@ class LoginController extends Controller */ protected function logFailedAccess($request) { - $log_msg = env('FAILED_ACCESS_MESSAGE', ''); + $log_msg = config('logging.failed_access_message'); if (!is_string($request->get($this->username())) || !is_string($log_msg) || strlen($log_msg)<1) return; From 19bfc8ad379744e76b72c3c737018e8ce5fe4815 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 23 May 2020 11:26:48 +0100 Subject: [PATCH 09/75] Prevented entity "Not Found" events from being logged - Added testing to cover, which was more hassle than thought since Laravel did not have built in log test helpers, so: - Added Log testing helper. Related to #2110 --- app/Config/logging.php | 7 +++++++ app/Exceptions/Handler.php | 2 +- phpcs.xml | 1 + tests/ErrorTest.php | 20 ++++++++++++++++++++ tests/SharedTestHelpers.php | 31 ++++++++++++++++++++++++++----- 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/app/Config/logging.php b/app/Config/logging.php index 0b55dc24d..375e84083 100644 --- a/app/Config/logging.php +++ b/app/Config/logging.php @@ -77,6 +77,13 @@ return [ 'driver' => 'monolog', 'handler' => NullHandler::class, ], + + // Testing channel + // Uses a shared testing instance during tests + // so that logs can be checked against. + 'testing' => [ + 'driver' => 'testing', + ], ], ]; diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index a3bc1e8b9..57078522b 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -9,7 +9,6 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Http\Response; use Illuminate\Validation\ValidationException; use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -26,6 +25,7 @@ class Handler extends ExceptionHandler HttpException::class, ModelNotFoundException::class, ValidationException::class, + NotFoundException::class, ]; /** diff --git a/phpcs.xml b/phpcs.xml index 009791fc9..ccde28033 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -3,6 +3,7 @@ The coding standard for BookStack. app */migrations/* + */tests/* \ No newline at end of file diff --git a/tests/ErrorTest.php b/tests/ErrorTest.php index a5e4a4a5e..8f6867cde 100644 --- a/tests/ErrorTest.php +++ b/tests/ErrorTest.php @@ -1,5 +1,8 @@ assertDontSeeText('Log in'); $notFound->assertSeeText('tester'); } + + public function test_item_not_found_does_not_get_logged_to_file() + { + $this->actingAs($this->getViewer()); + $handler = $this->withTestLogger(); + $book = Book::query()->first(); + + // Ensure we're seeing errors + Log::error('cat'); + $this->assertTrue($handler->hasErrorThatContains('cat')); + + $this->get('/books/arandomnotfouindbook'); + $this->get($book->getUrl('/chapter/arandomnotfouindchapter')); + $this->get($book->getUrl('/chapter/arandomnotfouindpages')); + + $this->assertCount(1, $handler->getRecords()); + } } \ No newline at end of file diff --git a/tests/SharedTestHelpers.php b/tests/SharedTestHelpers.php index f7b7d5edf..c7659a02d 100644 --- a/tests/SharedTestHelpers.php +++ b/tests/SharedTestHelpers.php @@ -16,7 +16,10 @@ use BookStack\Entities\Repos\PageRepo; use BookStack\Settings\SettingService; use BookStack\Uploads\HttpFetcher; use Illuminate\Support\Env; +use Illuminate\Support\Facades\Log; use Mockery; +use Monolog\Handler\TestHandler; +use Monolog\Logger; use Throwable; trait SharedTestHelpers @@ -69,14 +72,14 @@ trait SharedTestHelpers } /** - * Get an instance of a user with 'viewer' permissions - * @param $attributes - * @return mixed + * Get an instance of a user with 'viewer' permissions. */ - protected function getViewer($attributes = []) + protected function getViewer(array $attributes = []): User { $user = Role::getRole('viewer')->users()->first(); - if (!empty($attributes)) $user->forceFill($attributes)->save(); + if (!empty($attributes)) { + $user->forceFill($attributes)->save(); + } return $user; } @@ -277,4 +280,22 @@ trait SharedTestHelpers $this->assertStringStartsWith('You do not have permission to access', $error); } + /** + * Set a test handler as the logging interface for the application. + * Allows capture of logs for checking against during tests. + */ + protected function withTestLogger(): TestHandler + { + $monolog = new Logger('testing'); + $testHandler = new TestHandler(); + $monolog->pushHandler($testHandler); + + Log::extend('testing', function() use ($monolog) { + return $monolog; + }); + Log::setDefaultDriver('testing'); + + return $testHandler; + } + } \ No newline at end of file From 31514bae06aecc595312f8c1636d03f13871c3d8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 23 May 2020 11:50:44 +0100 Subject: [PATCH 10/75] Updated framework and other deps --- composer.json | 6 +- composer.lock | 920 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 676 insertions(+), 250 deletions(-) diff --git a/composer.json b/composer.json index 80e8b0a61..59fc909d6 100644 --- a/composer.json +++ b/composer.json @@ -13,14 +13,14 @@ "ext-mbstring": "*", "ext-tidy": "*", "ext-xml": "*", - "barryvdh/laravel-dompdf": "^0.8.5", - "barryvdh/laravel-snappy": "^0.4.5", + "barryvdh/laravel-dompdf": "^0.8.6", + "barryvdh/laravel-snappy": "^0.4.7", "doctrine/dbal": "^2.9", "facade/ignition": "^1.4", "fideloper/proxy": "^4.0", "gathercontent/htmldiff": "^0.2.1", "intervention/image": "^2.5", - "laravel/framework": "^6.12", + "laravel/framework": "^6.18", "laravel/socialite": "^4.3.2", "league/commonmark": "^1.4", "league/flysystem-aws-s3-v3": "^1.0", diff --git a/composer.lock b/composer.lock index 3ddd28e5a..a8c3b1e50 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bbe47cff4f167fd6ce7047dff4602a78", + "content-hash": "34390536dd685e0bc49b179babaa06ec", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.134.3", + "version": "3.138.7", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3de2711a47e7c3f5e93a5c83f019188fd23f852f" + "reference": "6b9f3fcea4dfa6092c628c790ca6d369a75453b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3de2711a47e7c3f5e93a5c83f019188fd23f852f", - "reference": "3de2711a47e7c3f5e93a5c83f019188fd23f852f", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6b9f3fcea4dfa6092c628c790ca6d369a75453b7", + "reference": "6b9f3fcea4dfa6092c628c790ca6d369a75453b7", "shasum": "" }, "require": { @@ -88,7 +88,7 @@ "s3", "sdk" ], - "time": "2020-04-03T18:11:51+00:00" + "time": "2020-05-22T18:11:09+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -342,16 +342,16 @@ }, { "name": "doctrine/dbal", - "version": "v2.10.1", + "version": "2.10.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8" + "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", - "reference": "c2b8e6e82732a64ecde1cddf9e1e06cb8556e3d8", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/aab745e7b6b2de3b47019da81e7225e14dcfdac8", + "reference": "aab745e7b6b2de3b47019da81e7225e14dcfdac8", "shasum": "" }, "require": { @@ -363,9 +363,11 @@ "require-dev": { "doctrine/coding-standard": "^6.0", "jetbrains/phpstorm-stubs": "^2019.1", - "phpstan/phpstan": "^0.11.3", + "nikic/php-parser": "^4.4", + "phpstan/phpstan": "^0.12", "phpunit/phpunit": "^8.4.1", - "symfony/console": "^2.0.5|^3.0|^4.0|^5.0" + "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", + "vimeo/psalm": "^3.11" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -430,7 +432,21 @@ "sqlserver", "sqlsrv" ], - "time": "2020-01-04T12:56:21+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2020-04-20T17:19:26+00:00" }, { "name": "doctrine/event-manager", @@ -510,33 +526,37 @@ }, { "name": "doctrine/inflector", - "version": "1.3.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1" + "reference": "18b995743e7ec8b15fd6efc594f0fa3de4bfe6d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1", - "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/18b995743e7ec8b15fd6efc594f0fa3de4bfe6d7", + "reference": "18b995743e7ec8b15fd6efc594f0fa3de4bfe6d7", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.2" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "doctrine/coding-standard": "^7.0", + "phpstan/phpstan": "^0.11", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-strict-rules": "^0.11", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -565,15 +585,35 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", "keywords": [ "inflection", - "pluralize", - "singularize", - "string" + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" ], - "time": "2019-10-30T19:59:35+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2020-05-11T11:25:59+00:00" }, { "name": "doctrine/lexer", @@ -1040,16 +1080,16 @@ }, { "name": "filp/whoops", - "version": "2.7.1", + "version": "2.7.2", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130" + "reference": "17d0d3f266c8f925ebd035cd36f83cf802b47d4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130", - "reference": "fff6f1e4f36be0e0d0b84d66b413d9dcb0c49130", + "url": "https://api.github.com/repos/filp/whoops/zipball/17d0d3f266c8f925ebd035cd36f83cf802b47d4a", + "reference": "17d0d3f266c8f925ebd035cd36f83cf802b47d4a", "shasum": "" }, "require": { @@ -1097,7 +1137,7 @@ "throwable", "whoops" ], - "time": "2020-01-15T10:00:00+00:00" + "time": "2020-05-05T12:28:07+00:00" }, { "name": "gathercontent/htmldiff", @@ -1150,23 +1190,24 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.5.2", + "version": "6.5.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "43ece0e75098b7ecd8d13918293029e555a50f82" + "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82", - "reference": "43ece0e75098b7ecd8d13918293029e555a50f82", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aab4ebd862aa7d04f01a4b51849d657db56d882e", + "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.0", "guzzlehttp/psr7": "^1.6.1", - "php": ">=5.5" + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.11" }, "require-dev": { "ext-curl": "*", @@ -1174,7 +1215,6 @@ "psr/log": "^1.1" }, "suggest": { - "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, "type": "library", @@ -1213,7 +1253,7 @@ "rest", "web service" ], - "time": "2019-12-23T11:57:10+00:00" + "time": "2020-04-18T10:38:46+00:00" }, { "name": "guzzlehttp/promises", @@ -1565,20 +1605,20 @@ }, { "name": "laravel/framework", - "version": "v6.18.3", + "version": "v6.18.15", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "4e48acfaba87f08320a2764d36c3b6a4a4112ccf" + "reference": "a1fa3ddc0bb5285cafa6844b443633f7627d75dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/4e48acfaba87f08320a2764d36c3b6a4a4112ccf", - "reference": "4e48acfaba87f08320a2764d36c3b6a4a4112ccf", + "url": "https://api.github.com/repos/laravel/framework/zipball/a1fa3ddc0bb5285cafa6844b443633f7627d75dc", + "reference": "a1fa3ddc0bb5285cafa6844b443633f7627d75dc", "shasum": "" }, "require": { - "doctrine/inflector": "^1.1", + "doctrine/inflector": "^1.4|^2.0", "dragonmantank/cron-expression": "^2.0", "egulias/email-validator": "^2.1.10", "ext-json": "*", @@ -1599,6 +1639,7 @@ "symfony/finder": "^4.3.4", "symfony/http-foundation": "^4.3.4", "symfony/http-kernel": "^4.3.4", + "symfony/polyfill-php73": "^1.17", "symfony/process": "^4.3.4", "symfony/routing": "^4.3.4", "symfony/var-dumper": "^4.3.4", @@ -1707,7 +1748,7 @@ "framework", "laravel" ], - "time": "2020-03-24T16:37:50+00:00" + "time": "2020-05-19T17:03:02+00:00" }, { "name": "laravel/socialite", @@ -1775,16 +1816,16 @@ }, { "name": "league/commonmark", - "version": "1.4.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "9e780d972185e4f737a03bade0fd34a9e67bbf31" + "reference": "412639f7cfbc0b31ad2455b2fe965095f66ae505" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/9e780d972185e4f737a03bade0fd34a9e67bbf31", - "reference": "9e780d972185e4f737a03bade0fd34a9e67bbf31", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/412639f7cfbc0b31ad2455b2fe965095f66ae505", + "reference": "412639f7cfbc0b31ad2455b2fe965095f66ae505", "shasum": "" }, "require": { @@ -1871,20 +1912,20 @@ "type": "tidelift" } ], - "time": "2020-04-24T13:39:56+00:00" + "time": "2020-05-04T22:15:21+00:00" }, { "name": "league/flysystem", - "version": "1.0.66", + "version": "1.0.69", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21" + "reference": "7106f78428a344bc4f643c233a94e48795f10967" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/021569195e15f8209b1c4bebb78bd66aa4f08c21", - "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7106f78428a344bc4f643c233a94e48795f10967", + "reference": "7106f78428a344bc4f643c233a94e48795f10967", "shasum": "" }, "require": { @@ -1955,7 +1996,13 @@ "sftp", "storage" ], - "time": "2020-03-17T18:58:12+00:00" + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2020-05-18T15:13:39+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -2069,20 +2116,20 @@ }, { "name": "monolog/monolog", - "version": "2.0.2", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8" + "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c861fcba2ca29404dc9e617eedd9eff4616986b8", - "reference": "c861fcba2ca29404dc9e617eedd9eff4616986b8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/38914429aac460e8e4616c8cb486ecb40ec90bb1", + "reference": "38914429aac460e8e4616c8cb486ecb40ec90bb1", "shasum": "" }, "require": { - "php": "^7.2", + "php": ">=7.2", "psr/log": "^1.0.1" }, "provide": { @@ -2093,11 +2140,11 @@ "doctrine/couchdb": "~1.0@dev", "elasticsearch/elasticsearch": "^6.0", "graylog2/gelf-php": "^1.4.2", - "jakub-onderka/php-parallel-lint": "^0.9", "php-amqplib/php-amqplib": "~2.4", "php-console/php-console": "^3.1.3", + "php-parallel-lint/php-parallel-lint": "^1.0", "phpspec/prophecy": "^1.6.1", - "phpunit/phpunit": "^8.3", + "phpunit/phpunit": "^8.5", "predis/predis": "^1.1", "rollbar/rollbar": "^1.3", "ruflin/elastica": ">=0.90 <3.0", @@ -2146,7 +2193,17 @@ "logging", "psr-3" ], - "time": "2019-12-20T14:22:59+00:00" + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2020-05-22T08:12:19+00:00" }, { "name": "mtdowling/jmespath.php", @@ -2207,21 +2264,22 @@ }, { "name": "nesbot/carbon", - "version": "2.32.2", + "version": "2.34.2", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc" + "reference": "3e87404329b8072295ea11d548b47a1eefe5a162" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f10e22cf546704fab1db4ad4b9dedbc5c797a0dc", - "reference": "f10e22cf546704fab1db4ad4b9dedbc5c797a0dc", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/3e87404329b8072295ea11d548b47a1eefe5a162", + "reference": "3e87404329b8072295ea11d548b47a1eefe5a162", "shasum": "" }, "require": { "ext-json": "*", "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", "symfony/translation": "^3.4 || ^4.0 || ^5.0" }, "require-dev": { @@ -2239,7 +2297,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "2.x-dev", + "dev-3.x": "3.x-dev" }, "laravel": { "providers": [ @@ -2274,7 +2333,17 @@ "datetime", "time" ], - "time": "2020-03-31T13:43:19+00:00" + "funding": [ + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2020-05-19T22:14:16+00:00" }, { "name": "nunomaduro/collision", @@ -2392,16 +2461,16 @@ }, { "name": "opis/closure", - "version": "3.5.1", + "version": "3.5.2", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969" + "reference": "2e3299cea6f485ca64d19c540f46d7896c512ace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/93ebc5712cdad8d5f489b500c59d122df2e53969", - "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969", + "url": "https://api.github.com/repos/opis/closure/zipball/2e3299cea6f485ca64d19c540f46d7896c512ace", + "reference": "2e3299cea6f485ca64d19c540f46d7896c512ace", "shasum": "" }, "require": { @@ -2449,7 +2518,7 @@ "serialization", "serialize" ], - "time": "2019-11-29T22:36:02+00:00" + "time": "2020-05-21T20:09:36+00:00" }, { "name": "paragonie/random_compat", @@ -2498,20 +2567,20 @@ }, { "name": "phenx/php-font-lib", - "version": "0.5.1", + "version": "0.5.2", "source": { "type": "git", "url": "https://github.com/PhenX/php-font-lib.git", - "reference": "760148820110a1ae0936e5cc35851e25a938bc97" + "reference": "ca6ad461f032145fff5971b5985e5af9e7fa88d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/760148820110a1ae0936e5cc35851e25a938bc97", - "reference": "760148820110a1ae0936e5cc35851e25a938bc97", + "url": "https://api.github.com/repos/PhenX/php-font-lib/zipball/ca6ad461f032145fff5971b5985e5af9e7fa88d8", + "reference": "ca6ad461f032145fff5971b5985e5af9e7fa88d8", "shasum": "" }, "require-dev": { - "phpunit/phpunit": "^4.8" + "phpunit/phpunit": "^4.8.35 || ^5 || ^6 || ^7" }, "type": "library", "autoload": { @@ -2531,7 +2600,7 @@ ], "description": "A library to read, parse, export and make subsets of different types of font files.", "homepage": "https://github.com/PhenX/php-font-lib", - "time": "2017-09-13T16:14:37+00:00" + "time": "2020-03-08T15:31:32+00:00" }, { "name": "phenx/php-svg-lib", @@ -3001,16 +3070,16 @@ }, { "name": "robrichards/xmlseclibs", - "version": "3.0.4", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/robrichards/xmlseclibs.git", - "reference": "0a53d3c3aa87564910cae4ed01416441d3ae0db5" + "reference": "8d8e56ca7914440a8c60caff1a865e7dff1d9a5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/0a53d3c3aa87564910cae4ed01416441d3ae0db5", - "reference": "0a53d3c3aa87564910cae4ed01416441d3ae0db5", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/8d8e56ca7914440a8c60caff1a865e7dff1d9a5a", + "reference": "8d8e56ca7914440a8c60caff1a865e7dff1d9a5a", "shasum": "" }, "require": { @@ -3035,7 +3104,7 @@ "xml", "xmldsig" ], - "time": "2019-11-05T11:44:22+00:00" + "time": "2020-04-22T17:19:51+00:00" }, { "name": "sabberworm/php-css-parser", @@ -3284,21 +3353,22 @@ }, { "name": "socialiteproviders/microsoft-azure", - "version": "v3.0.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Microsoft-Azure.git", - "reference": "d7a703a782eb9f7eae0db803beaa3ddec19ef372" + "reference": "b22f4696cccecd6de902cf0bc923de7fc2e4608e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Microsoft-Azure/zipball/d7a703a782eb9f7eae0db803beaa3ddec19ef372", - "reference": "d7a703a782eb9f7eae0db803beaa3ddec19ef372", + "url": "https://api.github.com/repos/SocialiteProviders/Microsoft-Azure/zipball/b22f4696cccecd6de902cf0bc923de7fc2e4608e", + "reference": "b22f4696cccecd6de902cf0bc923de7fc2e4608e", "shasum": "" }, "require": { + "ext-json": "*", "php": "^5.6 || ^7.0", - "socialiteproviders/manager": "~3.0" + "socialiteproviders/manager": "~2.0 || ~3.0" }, "type": "library", "autoload": { @@ -3317,23 +3387,24 @@ } ], "description": "Microsoft Azure OAuth2 Provider for Laravel Socialite", - "time": "2017-01-25T09:48:29+00:00" + "time": "2020-04-30T23:01:40+00:00" }, { "name": "socialiteproviders/okta", - "version": "v1.0.0", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Okta.git", - "reference": "dcda13432c80060cd84d4cb5f2af422d280ab895" + "reference": "7c2512f0872316b139e3eea1c50c9351747a57ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/dcda13432c80060cd84d4cb5f2af422d280ab895", - "reference": "dcda13432c80060cd84d4cb5f2af422d280ab895", + "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/7c2512f0872316b139e3eea1c50c9351747a57ea", + "reference": "7c2512f0872316b139e3eea1c50c9351747a57ea", "shasum": "" }, "require": { + "ext-json": "*", "php": "^5.6 || ^7.0", "socialiteproviders/manager": "~2.0 || ~3.0" }, @@ -3354,7 +3425,7 @@ } ], "description": "Okta OAuth2 Provider for Laravel Socialite", - "time": "2017-11-21T05:31:47+00:00" + "time": "2019-09-06T15:27:03+00:00" }, { "name": "socialiteproviders/slack", @@ -3395,19 +3466,20 @@ }, { "name": "socialiteproviders/twitch", - "version": "v5.1", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Twitch.git", - "reference": "f9b1f90a94f539e1b29e84ee0f731f42d59f3213" + "reference": "9ee6fe196d7c28777139b3cde04cbd537cf7e652" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Twitch/zipball/f9b1f90a94f539e1b29e84ee0f731f42d59f3213", - "reference": "f9b1f90a94f539e1b29e84ee0f731f42d59f3213", + "url": "https://api.github.com/repos/SocialiteProviders/Twitch/zipball/9ee6fe196d7c28777139b3cde04cbd537cf7e652", + "reference": "9ee6fe196d7c28777139b3cde04cbd537cf7e652", "shasum": "" }, "require": { + "ext-json": "*", "php": "^5.6 || ^7.0", "socialiteproviders/manager": "~2.0 || ~3.0" }, @@ -3428,7 +3500,7 @@ } ], "description": "Twitch OAuth2 Provider for Laravel Socialite", - "time": "2019-07-01T10:35:46+00:00" + "time": "2020-05-06T22:51:30+00:00" }, { "name": "swiftmailer/swiftmailer", @@ -3494,7 +3566,7 @@ }, { "name": "symfony/console", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", @@ -3566,11 +3638,25 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T11:41:10+00:00" }, { "name": "symfony/css-selector", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -3619,11 +3705,25 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/debug", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -3675,11 +3775,25 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/error-handler", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", @@ -3731,11 +3845,25 @@ ], "description": "Symfony ErrorHandler Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-30T14:07:33+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -3801,6 +3929,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { @@ -3863,7 +4005,7 @@ }, { "name": "symfony/finder", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -3908,20 +4050,34 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-27T16:54:36+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b" + "reference": "ec5bd254c223786f5fa2bb49a1e705c1b8e7cee2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/62f92509c9abfd1f73e17b8cf1b72c0bdac6611b", - "reference": "62f92509c9abfd1f73e17b8cf1b72c0bdac6611b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ec5bd254c223786f5fa2bb49a1e705c1b8e7cee2", + "reference": "ec5bd254c223786f5fa2bb49a1e705c1b8e7cee2", "shasum": "" }, "require": { @@ -3963,20 +4119,34 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2020-03-30T14:07:33+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-18T20:40:08+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f356a489e51856b99908005eb7f2c51a1dfc95dc" + "reference": "1799a6c01f0db5851f399151abdb5d6393fec277" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f356a489e51856b99908005eb7f2c51a1dfc95dc", - "reference": "f356a489e51856b99908005eb7f2c51a1dfc95dc", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/1799a6c01f0db5851f399151abdb5d6393fec277", + "reference": "1799a6c01f0db5851f399151abdb5d6393fec277", "shasum": "" }, "require": { @@ -4053,20 +4223,34 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2020-03-30T14:59:15+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-28T18:47:42+00:00" }, { "name": "symfony/mime", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "6dde9dc70155e91b850b1d009d1f841c54bc4aba" + "reference": "7a583ffb6c7dd5aabb5db920817a3cc39261c517" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/6dde9dc70155e91b850b1d009d1f841c54bc4aba", - "reference": "6dde9dc70155e91b850b1d009d1f841c54bc4aba", + "url": "https://api.github.com/repos/symfony/mime/zipball/7a583ffb6c7dd5aabb5db920817a3cc39261c517", + "reference": "7a583ffb6c7dd5aabb5db920817a3cc39261c517", "shasum": "" }, "require": { @@ -4115,20 +4299,34 @@ "mime", "mime-type" ], - "time": "2020-03-27T16:54:36+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-16T14:49:30+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14" + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/4719fa9c18b0464d399f1a63bf624b42b6fa8d14", - "reference": "4719fa9c18b0464d399f1a63bf624b42b6fa8d14", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", "shasum": "" }, "require": { @@ -4140,7 +4338,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4173,20 +4371,34 @@ "polyfill", "portable" ], - "time": "2020-02-27T09:26:54+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:14:59+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8" + "reference": "c4de7601eefbf25f9d47190abe07f79fe0a27424" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", - "reference": "ad6d62792bfbcfc385dd34b424d4fcf9712a32c8", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/c4de7601eefbf25f9d47190abe07f79fe0a27424", + "reference": "c4de7601eefbf25f9d47190abe07f79fe0a27424", "shasum": "" }, "require": { @@ -4198,7 +4410,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4232,20 +4444,34 @@ "portable", "shim" ], - "time": "2020-03-09T19:04:49+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" + "reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/3bff59ea7047e925be6b7f2059d60af31bb46d6a", + "reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a", "shasum": "" }, "require": { @@ -4259,7 +4485,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4294,20 +4520,34 @@ "portable", "shim" ], - "time": "2020-03-09T19:04:49+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", "shasum": "" }, "require": { @@ -4319,7 +4559,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4353,20 +4593,34 @@ "portable", "shim" ], - "time": "2020-03-09T19:04:49+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "37b0976c78b94856543260ce09b460a7bc852747" + "reference": "f048e612a3905f34931127360bdd2def19a5e582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", - "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", + "reference": "f048e612a3905f34931127360bdd2def19a5e582", "shasum": "" }, "require": { @@ -4375,7 +4629,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4408,20 +4662,34 @@ "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7" + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a760d8964ff79ab9bf057613a5808284ec852ccc", + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc", "shasum": "" }, "require": { @@ -4430,7 +4698,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -4466,20 +4734,34 @@ "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/process", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3" + "reference": "4b6a9a4013baa65d409153cbb5a895bf093dc7f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3e40e87a20eaf83a1db825e1fa5097ae89042db3", - "reference": "3e40e87a20eaf83a1db825e1fa5097ae89042db3", + "url": "https://api.github.com/repos/symfony/process/zipball/4b6a9a4013baa65d409153cbb5a895bf093dc7f4", + "reference": "4b6a9a4013baa65d409153cbb5a895bf093dc7f4", "shasum": "" }, "require": { @@ -4515,20 +4797,34 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-15T15:56:18+00:00" }, { "name": "symfony/routing", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "0f562fa613e288d7dbae6c63abbc9b33ed75a8f8" + "reference": "67b4e1f99c050cbc310b8f3d0dbdc4b0212c052c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/0f562fa613e288d7dbae6c63abbc9b33ed75a8f8", - "reference": "0f562fa613e288d7dbae6c63abbc9b33ed75a8f8", + "url": "https://api.github.com/repos/symfony/routing/zipball/67b4e1f99c050cbc310b8f3d0dbdc4b0212c052c", + "reference": "67b4e1f99c050cbc310b8f3d0dbdc4b0212c052c", "shasum": "" }, "require": { @@ -4591,7 +4887,21 @@ "uri", "url" ], - "time": "2020-03-30T11:41:10+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-21T19:59:53+00:00" }, { "name": "symfony/service-contracts", @@ -4653,16 +4963,16 @@ }, { "name": "symfony/translation", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "4e54d336f2eca5facad449d0b0118bb449375b76" + "reference": "8272bbd2b7e220ef812eba2a2b30068a5c64b191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/4e54d336f2eca5facad449d0b0118bb449375b76", - "reference": "4e54d336f2eca5facad449d0b0118bb449375b76", + "url": "https://api.github.com/repos/symfony/translation/zipball/8272bbd2b7e220ef812eba2a2b30068a5c64b191", + "reference": "8272bbd2b7e220ef812eba2a2b30068a5c64b191", "shasum": "" }, "require": { @@ -4725,7 +5035,21 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-12T16:45:36+00:00" }, { "name": "symfony/translation-contracts", @@ -4786,16 +5110,16 @@ }, { "name": "symfony/var-dumper", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "5a0c2d93006131a36cf6f767d10e2ca8333b0d4a" + "reference": "c587e04ce5d1aa62d534a038f574d9a709e814cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5a0c2d93006131a36cf6f767d10e2ca8333b0d4a", - "reference": "5a0c2d93006131a36cf6f767d10e2ca8333b0d4a", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c587e04ce5d1aa62d534a038f574d9a709e814cf", + "reference": "c587e04ce5d1aa62d534a038f574d9a709e814cf", "shasum": "" }, "require": { @@ -4858,7 +5182,21 @@ "debug", "dump" ], - "time": "2020-03-27T16:54:36+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-12T16:14:02+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -4911,20 +5249,20 @@ }, { "name": "vlucas/phpdotenv", - "version": "v3.6.2", + "version": "v3.6.4", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "786a947e57086cf236cefdee80784634224b99fa" + "reference": "10d3f853fdf1f3a6b3c7ea0c4620d2f699713db5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/786a947e57086cf236cefdee80784634224b99fa", - "reference": "786a947e57086cf236cefdee80784634224b99fa", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/10d3f853fdf1f3a6b3c7ea0c4620d2f699713db5", + "reference": "10d3f853fdf1f3a6b3c7ea0c4620d2f699713db5", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0", + "php": "^5.4 || ^7.0 || ^8.0", "phpoption/phpoption": "^1.5", "symfony/polyfill-ctype": "^1.9" }, @@ -4970,22 +5308,32 @@ "env", "environment" ], - "time": "2020-03-27T23:36:02+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2020-05-02T13:46:13+00:00" } ], "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.2.9", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "42d5da5379a7860093f8e4032167e4cb5ebec180" + "reference": "57f2219f6d9efe41ed1bc880d86701c52f261bf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/42d5da5379a7860093f8e4032167e4cb5ebec180", - "reference": "42d5da5379a7860093f8e4032167e4cb5ebec180", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/57f2219f6d9efe41ed1bc880d86701c52f261bf5", + "reference": "57f2219f6d9efe41ed1bc880d86701c52f261bf5", "shasum": "" }, "require": { @@ -5040,20 +5388,26 @@ "profiler", "webprofiler" ], - "time": "2020-02-25T20:42:23+00:00" + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2020-05-05T10:53:32+00:00" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.6.7", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "edd69c5e0508972c81f1f7173236de2459c45814" + "reference": "5f677edc14bdcfdcac36633e6eea71b2728a4dbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/edd69c5e0508972c81f1f7173236de2459c45814", - "reference": "edd69c5e0508972c81f1f7173236de2459c45814", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/5f677edc14bdcfdcac36633e6eea71b2728a4dbc", + "reference": "5f677edc14bdcfdcac36633e6eea71b2728a4dbc", "shasum": "" }, "require": { @@ -5069,7 +5423,7 @@ "illuminate/config": "^5.5|^6|^7", "illuminate/view": "^5.5|^6|^7", "mockery/mockery": "^1.3", - "orchestra/testbench": "^3|^4", + "orchestra/testbench": "^3|^4|^5", "phpro/grumphp": "^0.17.1", "squizlabs/php_codesniffer": "^3" }, @@ -5111,7 +5465,13 @@ "phpstorm", "sublime" ], - "time": "2020-02-25T20:41:32+00:00" + "funding": [ + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2020-04-22T09:57:26+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -5164,16 +5524,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.2.6", + "version": "1.2.7", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e" + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/47fe531de31fca4a1b997f87308e7d7804348f7e", - "reference": "47fe531de31fca4a1b997f87308e7d7804348f7e", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/95c63ab2117a72f48f5a55da9740a3273d45b7fd", + "reference": "95c63ab2117a72f48f5a55da9740a3273d45b7fd", "shasum": "" }, "require": { @@ -5216,20 +5576,30 @@ "ssl", "tls" ], - "time": "2020-01-13T10:02:55+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-04-08T08:27:21+00:00" }, { "name": "composer/composer", - "version": "1.10.1", + "version": "1.10.6", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011" + "reference": "be81b9c4735362c26876bdbfd3b5bc7e7f711c88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/b912a45da3e2b22f5cb5a23e441b697a295ba011", - "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011", + "url": "https://api.github.com/repos/composer/composer/zipball/be81b9c4735362c26876bdbfd3b5bc7e7f711c88", + "reference": "be81b9c4735362c26876bdbfd3b5bc7e7f711c88", "shasum": "" }, "require": { @@ -5248,7 +5618,8 @@ "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0" }, "conflict": { - "symfony/console": "2.8.38" + "symfony/console": "2.8.38", + "symfony/phpunit-bridge": "3.4.40" }, "require-dev": { "phpspec/prophecy": "^1.10", @@ -5296,7 +5667,17 @@ "dependency", "package" ], - "time": "2020-03-13T19:34:27+00:00" + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2020-05-06T08:28:10+00:00" }, { "name": "composer/semver", @@ -5746,16 +6127,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.16.1", + "version": "v1.16.3", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "58998b818c6567fac01e35b8a4b70c1a64530556" + "reference": "1a1605b8e9bacb34cc0c6278206d699772e1d372" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/58998b818c6567fac01e35b8a4b70c1a64530556", - "reference": "58998b818c6567fac01e35b8a4b70c1a64530556", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/1a1605b8e9bacb34cc0c6278206d699772e1d372", + "reference": "1a1605b8e9bacb34cc0c6278206d699772e1d372", "shasum": "" }, "require": { @@ -5803,7 +6184,7 @@ "debug", "debugbar" ], - "time": "2019-11-24T09:46:11+00:00" + "time": "2020-05-06T07:06:27+00:00" }, { "name": "mockery/mockery", @@ -6022,24 +6403,21 @@ }, { "name": "phpdocumentor/reflection-common", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", "shasum": "" }, "require": { "php": ">=7.1" }, - "require-dev": { - "phpunit/phpunit": "~6" - }, "type": "library", "extra": { "branch-alias": { @@ -6070,7 +6448,7 @@ "reflection", "static analysis" ], - "time": "2018-08-07T13:53:10+00:00" + "time": "2020-04-27T09:25:28+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -6537,16 +6915,16 @@ }, { "name": "phpunit/phpunit", - "version": "8.5.3", + "version": "8.5.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "67750516bc02f300e2742fed2f50177f8f37bedf" + "reference": "63dda3b212a0025d380a745f91bdb4d8c985adb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/67750516bc02f300e2742fed2f50177f8f37bedf", - "reference": "67750516bc02f300e2742fed2f50177f8f37bedf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/63dda3b212a0025d380a745f91bdb4d8c985adb7", + "reference": "63dda3b212a0025d380a745f91bdb4d8c985adb7", "shasum": "" }, "require": { @@ -6616,7 +6994,17 @@ "testing", "xunit" ], - "time": "2020-03-31T08:52:04+00:00" + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-05-22T13:51:52+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -7278,20 +7666,20 @@ }, { "name": "seld/jsonlint", - "version": "1.7.2", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19" + "reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e2e5d290e4d2a4f0eb449f510071392e00e10d19", - "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1", + "reference": "ff2aa5420bfbc296cf6a0bc785fa5b35736de7c1", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0" + "php": "^5.3 || ^7.0 || ^8.0" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" @@ -7323,7 +7711,17 @@ "parser", "validator" ], - "time": "2019-10-24T14:27:39+00:00" + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2020-04-30T19:05:18+00:00" }, { "name": "seld/phar-utils", @@ -7371,16 +7769,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.5.4", + "version": "3.5.5", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "dceec07328401de6211037abbb18bda423677e26" + "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/dceec07328401de6211037abbb18bda423677e26", - "reference": "dceec07328401de6211037abbb18bda423677e26", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/73e2e7f57d958e7228fce50dc0c61f58f017f9f6", + "reference": "73e2e7f57d958e7228fce50dc0c61f58f017f9f6", "shasum": "" }, "require": { @@ -7418,11 +7816,11 @@ "phpcs", "standards" ], - "time": "2020-01-30T22:20:29+00:00" + "time": "2020-04-17T01:09:41+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", @@ -7479,20 +7877,34 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], "time": "2020-03-29T19:12:22+00:00" }, { "name": "symfony/filesystem", - "version": "v4.4.7", + "version": "v4.4.8", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "fe297193bf2e6866ed900ed2d5869362768df6a7" + "reference": "a3ebf3bfd8a98a147c010a568add5a8aa4edea0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/fe297193bf2e6866ed900ed2d5869362768df6a7", - "reference": "fe297193bf2e6866ed900ed2d5869362768df6a7", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/a3ebf3bfd8a98a147c010a568add5a8aa4edea0f", + "reference": "a3ebf3bfd8a98a147c010a568add5a8aa4edea0f", "shasum": "" }, "require": { @@ -7529,7 +7941,21 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:54:36+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-04-12T14:39:55+00:00" }, { "name": "theseer/fdomdocument", @@ -7613,16 +8039,16 @@ }, { "name": "webmozart/assert", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", + "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6", + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6", "shasum": "" }, "require": { @@ -7630,7 +8056,7 @@ "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "vimeo/psalm": "<3.6.0" + "vimeo/psalm": "<3.9.1" }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" @@ -7657,7 +8083,7 @@ "check", "validate" ], - "time": "2020-02-14T12:15:55+00:00" + "time": "2020-04-18T12:12:48+00:00" }, { "name": "wnx/laravel-stats", From 3502abdd49c1ca09e3529c7410ed626913682a8d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 23 May 2020 12:28:14 +0100 Subject: [PATCH 11/75] Fixed revision issues caused by page fillable changes --- app/Entities/Repos/PageRepo.php | 2 +- app/Http/Controllers/PageRevisionController.php | 7 +++++++ tests/Entity/PageRevisionTest.php | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index fc585b4df..e5f13463c 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -278,7 +278,7 @@ class PageRepo $revision = $page->revisions()->where('id', '=', $revisionId)->first(); $page->fill($revision->toArray()); $content = new PageContent($page); - $content->setNewHTML($page->html); + $content->setNewHTML($revision->html); $page->updated_by = user()->id; $page->refreshSlug(); $page->save(); diff --git a/app/Http/Controllers/PageRevisionController.php b/app/Http/Controllers/PageRevisionController.php index 3c65b50ac..797f5db8f 100644 --- a/app/Http/Controllers/PageRevisionController.php +++ b/app/Http/Controllers/PageRevisionController.php @@ -1,5 +1,6 @@ fill($revision->toArray()); + // TODO - Refactor PageContent so we don't need to juggle this + $page->html = $revision->html; + $page->html = (new PageContent($page))->render(); $this->setPageTitle(trans('entities.pages_revision_named', ['pageName' => $page->getShortName()])); return view('pages.revision', [ @@ -73,6 +77,9 @@ class PageRevisionController extends Controller $diff = (new Htmldiff)->diff($prevContent, $revision->html); $page->fill($revision->toArray()); + // TODO - Refactor PageContent so we don't need to juggle this + $page->html = $revision->html; + $page->html = (new PageContent($page))->render(); $this->setPageTitle(trans('entities.pages_revision_named', ['pageName'=>$page->getShortName()])); return view('pages.revision', [ diff --git a/tests/Entity/PageRevisionTest.php b/tests/Entity/PageRevisionTest.php index f8baccc54..1e9dbd626 100644 --- a/tests/Entity/PageRevisionTest.php +++ b/tests/Entity/PageRevisionTest.php @@ -24,6 +24,21 @@ class PageRevisionTest extends TestCase $revisionView->assertSee('new content'); } + public function test_page_revision_preview_shows_content_of_revision() + { + $this->asEditor(); + + $pageRepo = app(PageRepo::class); + $page = Page::first(); + $pageRepo->update($page, ['name' => 'updated page', 'html' => '

new revision content

', 'summary' => 'page revision testing']); + $pageRevision = $page->revisions->last(); + $pageRepo->update($page, ['name' => 'updated page', 'html' => '

Updated content

', 'summary' => 'page revision testing 2']); + + $revisionView = $this->get($page->getUrl() . '/revisions/' . $pageRevision->id); + $revisionView->assertStatus(200); + $revisionView->assertSee('new revision content'); + } + public function test_page_revision_restore_updates_content() { $this->asEditor(); From 71e7dd5894b855b0a6ed58fd520af49a521681c8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 23 May 2020 12:56:31 +0100 Subject: [PATCH 12/75] Removed failing URL test - Was found that the test was not testing the actual situation anyway. - A work-around in the request creation, within testing, just happened to result in the desired outcome. For reference: https://github.com/laravel/framework/pull/32345 --- tests/Unit/UrlTest.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/Unit/UrlTest.php b/tests/Unit/UrlTest.php index feff47977..b9f485da1 100644 --- a/tests/Unit/UrlTest.php +++ b/tests/Unit/UrlTest.php @@ -5,17 +5,6 @@ use Tests\TestCase; class UrlTest extends TestCase { - public function test_request_url_takes_custom_url_into_account() - { - config()->set('app.url', 'http://example.com/bookstack'); - $this->get('/'); - $this->assertEquals('http://example.com/bookstack', request()->getUri()); - - config()->set('app.url', 'http://example.com/docs/content'); - $this->get('/'); - $this->assertEquals('http://example.com/docs/content', request()->getUri()); - } - public function test_url_helper_takes_custom_url_into_account() { $this->runWithEnv('APP_URL', 'http://example.com/bookstack', function() { From 9d7ce59b18c5cbeef017349f38f063a63b762188 Mon Sep 17 00:00:00 2001 From: benrubson <6764151+benrubson@users.noreply.github.com> Date: Sat, 23 May 2020 15:37:38 +0200 Subject: [PATCH 13/75] Move logFailedAccess into Activity --- app/Actions/ActivityService.php | 17 ++++++++++++++ app/Http/Controllers/Auth/LoginController.php | 22 +++---------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/app/Actions/ActivityService.php b/app/Actions/ActivityService.php index f56f1ca57..ca09aaef1 100644 --- a/app/Actions/ActivityService.php +++ b/app/Actions/ActivityService.php @@ -183,4 +183,21 @@ class ActivityService session()->flash('success', $message); } } + + /** + * Log failed accesses, for further processing by tools like Fail2Ban + * + * @param username + * @return void + */ + public function logFailedAccess($username) + { + $log_msg = config('logging.failed_access_message'); + + if (!is_string($username) || !is_string($log_msg) || strlen($log_msg)<1) + return; + + $log_msg = str_replace("%u", $username, $log_msg); + error_log($log_msg, 4); + } } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index cf9e44e43..f5479814a 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -2,6 +2,7 @@ namespace BookStack\Http\Controllers\Auth; +use Activity; use BookStack\Auth\Access\SocialAuthService; use BookStack\Exceptions\LoginAttemptEmailNeededException; use BookStack\Exceptions\LoginAttemptException; @@ -102,7 +103,7 @@ class LoginController extends Controller $this->fireLockoutEvent($request); // Also log some error message - $this->logFailedAccess($request); + Activity::logFailedAccess($request->get($this->username())); return $this->sendLockoutResponse($request); } @@ -121,7 +122,7 @@ class LoginController extends Controller $this->incrementLoginAttempts($request); // Also log some error message - $this->logFailedAccess($request); + Activity::logFailedAccess($request->get($this->username())); return $this->sendFailedLoginResponse($request); } @@ -168,21 +169,4 @@ class LoginController extends Controller return redirect('/login'); } - /** - * Log failed accesses, for further processing by tools like Fail2Ban - * - * @param \Illuminate\Http\Request $request - * @return void - */ - protected function logFailedAccess($request) - { - $log_msg = config('logging.failed_access_message'); - - if (!is_string($request->get($this->username())) || !is_string($log_msg) || strlen($log_msg)<1) - return; - - $log_msg = str_replace("%u", $request->get($this->username()), $log_msg); - error_log($log_msg, 4); - } - } From 118e31608a987d2bfb7e4c81498b6a7cf0cac318 Mon Sep 17 00:00:00 2001 From: Honvid Date: Tue, 16 Jun 2020 11:44:08 +0800 Subject: [PATCH 14/75] fix the bug for lang's extra letter. --- resources/lang/zh_CN/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/zh_CN/settings.php b/resources/lang/zh_CN/settings.php index c2f015533..6a23b0906 100755 --- a/resources/lang/zh_CN/settings.php +++ b/resources/lang/zh_CN/settings.php @@ -57,7 +57,7 @@ return [ 'reg_enable_desc' => '启用注册后,用户将可以自己注册为站点用户。 注册后,他们将获得一个默认的单一用户角色。', 'reg_default_role' => '注册后的默认用户角色', 'reg_enable_external_warning' => '当启用外部LDAP或者SAML认证时,上面的选项会被忽略。当使用外部系统认证认证成功时,将自动创建非现有会员的用户账户。', - 'reg_email_confirmation' => '邮箱确认n', + 'reg_email_confirmation' => '邮箱确认', 'reg_email_confirmation_toggle' => '需要电子邮件确认', 'reg_confirm_email_desc' => '如果使用域名限制,则需要Email验证,并且该值将被忽略。', 'reg_confirm_restrict_domain' => '域名限制', From 76d02cd4725633c87c7885a319411a0671e814ae Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Jun 2020 20:38:08 +0100 Subject: [PATCH 15/75] Started attempt at formalising component system used in BookStack Added a document to try to define things. Updated the loading so components are registed dynamically. Added some standardised ways to reference other elems & define options --- dev/docs/components.md | 56 +++++ resources/js/components/dropdown.js | 12 +- resources/js/components/index.js | 204 ++++++++++-------- resources/views/comments/comment.blade.php | 6 +- resources/views/common/header.blade.php | 6 +- resources/views/pages/form.blade.php | 15 +- resources/views/pages/revisions.blade.php | 12 +- .../partials/breadcrumb-listing.blade.php | 6 +- .../partials/entity-export-menu.blade.php | 6 +- resources/views/partials/sort.blade.php | 6 +- 10 files changed, 213 insertions(+), 116 deletions(-) create mode 100644 dev/docs/components.md diff --git a/dev/docs/components.md b/dev/docs/components.md new file mode 100644 index 000000000..2f59ae344 --- /dev/null +++ b/dev/docs/components.md @@ -0,0 +1,56 @@ +# JavaScript Components + +This document details the format for JavaScript components in BookStack. + +#### Defining a Component in JS + +```js +class Dropdown { + setup() { + } +} +``` + +#### Using a Component in HTML + +A component is used like so: + +```html +
+ + + +
+``` + +The names will be parsed and new component instance will be created if a matching name is found in the `components/index.js` componentMapping. + +#### Element References + +Within a component you'll often need to refer to other element instances. This can be done like so: + +```html +
+ View more +
+``` + +You can then access the span element as `this.$refs.toggle` in your component. + +#### Component Options + +```html +
+
+``` + +Will result with `this.$opts` being: + +```json +{ + "delay": "500", + "show": "" +} +``` \ No newline at end of file diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js index 367c956ce..7b1ce3055 100644 --- a/resources/js/components/dropdown.js +++ b/resources/js/components/dropdown.js @@ -3,14 +3,16 @@ import {onSelect} from "../services/dom"; /** * Dropdown * Provides some simple logic to create simple dropdown menus. + * @extends {Component} */ class DropDown { - constructor(elem) { - this.container = elem; - this.menu = elem.querySelector('.dropdown-menu, [dropdown-menu]'); - this.moveMenu = elem.hasAttribute('dropdown-move-menu'); - this.toggle = elem.querySelector('[dropdown-toggle]'); + setup() { + this.container = this.$el; + this.menu = this.$refs.menu; + this.toggle = this.$refs.toggle; + this.moveMenu = this.$opts.moveMenu; + this.direction = (document.dir === 'rtl') ? 'right' : 'left'; this.body = document.body; this.showing = false; diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 112827330..da194e438 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -1,109 +1,145 @@ -import dropdown from "./dropdown"; -import overlay from "./overlay"; -import backToTop from "./back-to-top"; -import notification from "./notification"; -import chapterToggle from "./chapter-toggle"; -import expandToggle from "./expand-toggle"; -import entitySelectorPopup from "./entity-selector-popup"; -import entitySelector from "./entity-selector"; -import sidebar from "./sidebar"; -import pagePicker from "./page-picker"; -import pageComments from "./page-comments"; -import wysiwygEditor from "./wysiwyg-editor"; -import markdownEditor from "./markdown-editor"; -import editorToolbox from "./editor-toolbox"; -import imagePicker from "./image-picker"; -import collapsible from "./collapsible"; -import toggleSwitch from "./toggle-switch"; -import pageDisplay from "./page-display"; -import shelfSort from "./shelf-sort"; -import homepageControl from "./homepage-control"; -import headerMobileToggle from "./header-mobile-toggle"; -import listSortControl from "./list-sort-control"; -import triLayout from "./tri-layout"; -import breadcrumbListing from "./breadcrumb-listing"; -import permissionsTable from "./permissions-table"; -import customCheckbox from "./custom-checkbox"; -import bookSort from "./book-sort"; -import settingAppColorPicker from "./setting-app-color-picker"; -import settingColorPicker from "./setting-color-picker"; -import entityPermissionsEditor from "./entity-permissions-editor"; -import templateManager from "./template-manager"; -import newUserPassword from "./new-user-password"; -import detailsHighlighter from "./details-highlighter"; -import codeHighlighter from "./code-highlighter"; +const componentMapping = {}; -const componentMapping = { - 'dropdown': dropdown, - 'overlay': overlay, - 'back-to-top': backToTop, - 'notification': notification, - 'chapter-toggle': chapterToggle, - 'expand-toggle': expandToggle, - 'entity-selector-popup': entitySelectorPopup, - 'entity-selector': entitySelector, - 'sidebar': sidebar, - 'page-picker': pagePicker, - 'page-comments': pageComments, - 'wysiwyg-editor': wysiwygEditor, - 'markdown-editor': markdownEditor, - 'editor-toolbox': editorToolbox, - 'image-picker': imagePicker, - 'collapsible': collapsible, - 'toggle-switch': toggleSwitch, - 'page-display': pageDisplay, - 'shelf-sort': shelfSort, - 'homepage-control': homepageControl, - 'header-mobile-toggle': headerMobileToggle, - 'list-sort-control': listSortControl, - 'tri-layout': triLayout, - 'breadcrumb-listing': breadcrumbListing, - 'permissions-table': permissionsTable, - 'custom-checkbox': customCheckbox, - 'book-sort': bookSort, - 'setting-app-color-picker': settingAppColorPicker, - 'setting-color-picker': settingColorPicker, - 'entity-permissions-editor': entityPermissionsEditor, - 'template-manager': templateManager, - 'new-user-password': newUserPassword, - 'details-highlighter': detailsHighlighter, - 'code-highlighter': codeHighlighter, -}; +const definitionFiles = require.context('./', false, /\.js$/); +for (const fileName of definitionFiles.keys()) { + const name = fileName.replace('./', '').split('.')[0]; + if (name !== 'index') { + componentMapping[name] = definitionFiles(fileName).default; + } +} window.components = {}; -const componentNames = Object.keys(componentMapping); - /** * Initialize components of the given name within the given element. * @param {String} componentName * @param {HTMLElement|Document} parentElement */ -function initComponent(componentName, parentElement) { - let elems = parentElement.querySelectorAll(`[${componentName}]`); - if (elems.length === 0) return; - - let component = componentMapping[componentName]; - if (typeof window.components[componentName] === "undefined") window.components[componentName] = []; +function searchForComponentInParent(componentName, parentElement) { + const elems = parentElement.querySelectorAll(`[${componentName}]`); for (let j = 0, jLen = elems.length; j < jLen; j++) { - let instance = new component(elems[j]); - if (typeof elems[j].components === 'undefined') elems[j].components = {}; - elems[j].components[componentName] = instance; - window.components[componentName].push(instance); + initComponent(componentName, elems[j]); } } +/** + * Initialize a component instance on the given dom element. + * @param {String} name + * @param {Element} element + */ +function initComponent(name, element) { + const componentModel = componentMapping[name]; + if (componentModel === undefined) return; + + // Create our component instance + let instance; + try { + instance = new componentModel(element); + instance.$el = element; + instance.$refs = parseRefs(name, element); + instance.$opts = parseOpts(name, element); + if (typeof instance.setup === 'function') { + instance.setup(); + } + } catch (e) { + console.error('Failed to create component', e, name, element); + } + + + // Add to global listing + if (typeof window.components[name] === "undefined") { + window.components[name] = []; + } + window.components[name].push(instance); + + // Add to element listing + if (typeof element.components === 'undefined') { + element.components = {}; + } + element.components[name] = instance; +} + +/** + * Parse out the element references within the given element + * for the given component name. + * @param {String} name + * @param {Element} element + */ +function parseRefs(name, element) { + const refs = {}; + const prefix = `${name}@` + const refElems = element.querySelectorAll(`[refs*="${prefix}"]`); + for (const el of refElems) { + const refNames = el.getAttribute('refs') + .split(' ') + .filter(str => str.startsWith(prefix)) + .map(str => str.replace(prefix, '')); + for (const ref of refNames) { + refs[ref] = el; + } + } + return refs; +} + +/** + * Parse out the element component options. + * @param {String} name + * @param {Element} element + * @return {Object} + */ +function parseOpts(name, element) { + const opts = {}; + const prefix = `option:${name}:`; + for (const {name, value} of element.attributes) { + if (name.startsWith(prefix)) { + const optName = name.replace(prefix, ''); + opts[kebabToCamel(optName)] = value || ''; + } + } + return opts; +} + +/** + * Convert a kebab-case string to camelCase + * @param {String} kebab + * @returns {string} + */ +function kebabToCamel(kebab) { + const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1); + const words = kebab.split('-'); + return words[0] + words.slice(1).map(ucFirst).join(); +} + /** * Initialize all components found within the given element. * @param parentElement */ function initAll(parentElement) { if (typeof parentElement === 'undefined') parentElement = document; - for (let i = 0, len = componentNames.length; i < len; i++) { - initComponent(componentNames[i], parentElement); + + // Old attribute system + for (const componentName of Object.keys(componentMapping)) { + searchForComponentInParent(componentName, parentElement); + } + + // New component system + const componentElems = parentElement.querySelectorAll(`[component],[components]`); + + for (const el of componentElems) { + const componentNames = `${el.getAttribute('component') || ''} ${(el.getAttribute('components'))}`.toLowerCase().split(' ').filter(Boolean); + for (const name of componentNames) { + initComponent(name, el); + } } } window.components.init = initAll; export default initAll; + +/** + * @typedef Component + * @property {HTMLElement} $el + * @property {Object} $refs + * @property {Object} $opts + */ \ No newline at end of file diff --git a/resources/views/comments/comment.blade.php b/resources/views/comments/comment.blade.php index ea96a9250..322477ebd 100644 --- a/resources/views/comments/comment.blade.php +++ b/resources/views/comments/comment.blade.php @@ -27,9 +27,9 @@ @endif @if(userCan('comment-delete', $comment)) -