From 6c5304a3deb35832d0bd5e118bf146a1c250de95 Mon Sep 17 00:00:00 2001 From: marcusforsberg Date: Sat, 14 Apr 2018 18:09:09 +0200 Subject: [PATCH 01/35] Updated Swedish translation --- resources/lang/sv/components.php | 3 ++- resources/lang/sv/entities.php | 8 ++++++-- resources/lang/sv/errors.php | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/resources/lang/sv/components.php b/resources/lang/sv/components.php index 7249c5c1f..ec1dad1aa 100644 --- a/resources/lang/sv/components.php +++ b/resources/lang/sv/components.php @@ -21,6 +21,7 @@ return [ 'image_upload_success' => 'Bilden har laddats upp', 'image_update_success' => 'Bildens uppgifter har ändrats', 'image_delete_success' => 'Bilden har tagits bort', + 'image_upload_remove' => 'Radera', /** * Code editor @@ -29,4 +30,4 @@ return [ 'code_language' => 'Språk', 'code_content' => 'Kod', 'code_save' => 'Spara', -]; \ No newline at end of file +]; diff --git a/resources/lang/sv/entities.php b/resources/lang/sv/entities.php index d35d3a65a..6aa537b2d 100644 --- a/resources/lang/sv/entities.php +++ b/resources/lang/sv/entities.php @@ -160,8 +160,9 @@ return [ 'pages_name' => 'Sidans namn', 'pages_md_editor' => 'Redigerare', 'pages_md_preview' => 'Förhandsvisa', - 'pages_md_insert_image' => 'Inoga bild', + 'pages_md_insert_image' => 'Infoga bild', 'pages_md_insert_link' => 'Infoga länk', + 'pages_md_insert_drawing' => 'Infoga teckning', 'pages_not_in_chapter' => 'Sidan ligger inte i något kapitel', 'pages_move' => 'Flytta sida', 'pages_move_success' => 'Sidan har flyttats till ":parentName"', @@ -199,8 +200,10 @@ return [ * Editor sidebar */ 'page_tags' => 'Sidtaggar', + 'chapter_tags' => 'Kapiteltaggar', + 'book_tags' => 'Boktaggar', 'tag' => 'Tagg', - 'tags' => '', + 'tags' => 'Taggar', 'tag_value' => 'Taggvärde (Frivilligt)', 'tags_explain' => "Lägg till taggar för att kategorisera ditt innehåll bättre. \n Du kan tilldela ett värde till en tagg för ännu bättre organisering.", 'tags_add' => 'Lägg till ännu en tagg', @@ -244,6 +247,7 @@ return [ */ 'comment' => 'Kommentar', 'comments' => 'Kommentarer', + 'comment_add' => 'Lägg till kommentar', 'comment_placeholder' => 'Lämna en kommentar här', 'comment_count' => '{0} Inga kommentarer|{1} 1 kommentar|[2,*] :count kommentarer', 'comment_save' => 'Spara kommentar', diff --git a/resources/lang/sv/errors.php b/resources/lang/sv/errors.php index 4dfc149f0..37526802e 100644 --- a/resources/lang/sv/errors.php +++ b/resources/lang/sv/errors.php @@ -35,10 +35,13 @@ return [ 'cannot_get_image_from_url' => 'Kan inte hämta bild från :url', 'cannot_create_thumbs' => 'Servern kan inte skapa miniatyrer. Kontrollera att du har PHPs GD-tillägg aktiverat.', 'server_upload_limit' => 'Servern tillåter inte så här stora filer. Prova en mindre fil.', + 'uploaded' => 'Servern tillåter inte så här stora filer. Prova en mindre fil.', 'image_upload_error' => 'Ett fel inträffade vid uppladdningen', + 'image_upload_type_error' => 'Filtypen du försöker ladda upp är ogiltig', // Attachments 'attachment_page_mismatch' => 'Fel i sidmatchning vid uppdatering av bilaga', + 'attachment_not_found' => 'Bilagan hittades ej', // Pages 'page_draft_autosave_fail' => 'Kunde inte spara utkastet. Kontrollera att du är ansluten till internet.', @@ -76,4 +79,4 @@ return [ 'error_occurred' => 'Ett fel inträffade', 'app_down' => ':appName är nere just nu', 'back_soon' => 'Vi är snart tillbaka.', -]; \ No newline at end of file +]; From 3d0d7f8be2fefc0975b7296b34712ab547aa1954 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 30 Apr 2018 13:52:22 +0100 Subject: [PATCH 02/35] Updated version for next block of development --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 0507cd08e..648d22261 100644 --- a/version +++ b/version @@ -1 +1 @@ -v0.20-dev +v0.22-dev From 58a0a59d7ee545c1b8052a9767184b23b2d44e46 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 30 Apr 2018 13:53:04 +0100 Subject: [PATCH 03/35] Cleaned details sidebar box and merged with permissions --- resources/assets/icons/star.svg | 5 ++ resources/assets/sass/_blocks.scss | 6 ++ resources/assets/sass/_lists.scss | 9 +++ resources/assets/sass/_text.scss | 7 ++ resources/views/books/show.blade.php | 28 +++----- resources/views/chapters/show.blade.php | 52 ++++++-------- resources/views/pages/show.blade.php | 70 +++++++++---------- .../views/partials/entity-meta.blade.php | 23 ++++-- 8 files changed, 108 insertions(+), 92 deletions(-) create mode 100644 resources/assets/icons/star.svg diff --git a/resources/assets/icons/star.svg b/resources/assets/icons/star.svg new file mode 100644 index 000000000..c7686389d --- /dev/null +++ b/resources/assets/icons/star.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/resources/assets/sass/_blocks.scss b/resources/assets/sass/_blocks.scss index f876ff281..76d4d5e0c 100644 --- a/resources/assets/sass/_blocks.scss +++ b/resources/assets/sass/_blocks.scss @@ -208,6 +208,12 @@ } } +.sidebar .card { + h3, .body, .empty-text { + padding: $-s $-m; + } +} + .card.drag-card { border: 1px solid #DDD; border-radius: 4px; diff --git a/resources/assets/sass/_lists.scss b/resources/assets/sass/_lists.scss index f4883384a..2b6fa9cb0 100644 --- a/resources/assets/sass/_lists.scss +++ b/resources/assets/sass/_lists.scss @@ -446,3 +446,12 @@ ul.pagination { margin: 0; } } + +.card.entity-details { + .active-restriction { + margin-top: $-xs; + } + .active-restriction + .active-restriction { + margin-top: 0; + } +} \ No newline at end of file diff --git a/resources/assets/sass/_text.scss b/resources/assets/sass/_text.scss index d894a00e7..da11846d8 100644 --- a/resources/assets/sass/_text.scss +++ b/resources/assets/sass/_text.scss @@ -101,6 +101,13 @@ a, .link { } } +.blended-links a { + color: inherit; + svg { + fill: currentColor; + } +} + /* * Other HTML Text Elements */ diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 9e1edbbbc..e97d9bb69 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -53,21 +53,6 @@ - @if($book->restricted) -
-

@icon('permission') {{ trans('entities.permissions') }}

-
-

- @if(userCan('restrictions-manage', $book)) - @icon('lock'){{ trans('entities.books_permissions_active') }} - @else - @icon('lock'){{ trans('entities.books_permissions_active') }} - @endif -

-
-
- @endif - @if($book->tags->count() > 0)

@icon('tag') {{ trans('entities.book_tags') }}

@@ -78,10 +63,19 @@ @endif -
+

@icon('info') {{ trans('common.details') }}

-
+
diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 274a0f8ae..acb8e118a 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -57,34 +57,6 @@
- @if($book->restricted || $chapter->restricted) -
-

@icon('permission') {{ trans('entities.permissions') }}

-
- @if($book->restricted) -

- @if(userCan('restrictions-manage', $book)) - @icon('lock'){{ trans('entities.books_permissions_active') }} - @else - @icon('lock'){{ trans('entities.books_permissions_active') }} - @endif -

- @endif - - @if($chapter->restricted) -

- @if(userCan('restrictions-manage', $chapter)) - @icon('lock'){{ trans('entities.chapters_permissions_active') }} - @else - @icon('lock'){{ trans('entities.chapters_permissions_active') }} - @endif -

- @endif -
-
- @endif - - @if($chapter->tags->count() > 0)

@icon('tag') {{ trans('entities.chapter_tags') }}

@@ -94,10 +66,30 @@
@endif -
+

@icon('info') {{ trans('common.details') }}

-
+
diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index 288de3d84..d9c984a60 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -41,42 +41,6 @@ @stop @section('sidebar') - @if($book->restricted || ($page->chapter && $page->chapter->restricted) || $page->restricted) -
-

@icon('permission') {{ trans('entities.permissions') }}

-
-
- - @if($book->restricted) - @if(userCan('restrictions-manage', $book)) - @icon('lock'){{ trans('entities.books_permissions_active') }} - @else - @icon('lock'){{ trans('entities.books_permissions_active') }} - @endif -
- @endif - - @if($page->chapter && $page->chapter->restricted) - @if(userCan('restrictions-manage', $page->chapter)) - @icon('lock'){{ trans('entities.chapters_permissions_active') }} - @else - @icon('lock'){{ trans('entities.chapters_permissions_active') }} - @endif -
- @endif - - @if($page->restricted) - @if(userCan('restrictions-manage', $page)) - @icon('lock'){{ trans('entities.pages_permissions_active') }} - @else - @icon('lock'){{ trans('entities.pages_permissions_active') }} - @endif -
- @endif -
-
-
- @endif @if($page->tags->count() > 0)
@@ -115,10 +79,40 @@
@endif -
+

@icon('info') {{ trans('common.details') }}

-
+
diff --git a/resources/views/partials/entity-meta.blade.php b/resources/views/partials/entity-meta.blade.php index 49671ba50..2784f03f0 100644 --- a/resources/views/partials/entity-meta.blade.php +++ b/resources/views/partials/entity-meta.blade.php @@ -1,25 +1,34 @@ -

+

@if($entity->isA('revision')) {{ trans('entities.pages_revision') }} {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
@endif - @if ($entity->isA('page')) {{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
@endif + + @if ($entity->isA('page')) + @if (userCan('page-update', $entity)) @endif + @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
+ @if (userCan('page-update', $entity))
@endif + @endif + + @if ($entity->createdBy) - {!! trans('entities.meta_created_name', [ + @icon('star'){!! trans('entities.meta_created_name', [ 'timeLength' => ''.$entity->created_at->diffForHumans() . '', 'user' => "".htmlentities($entity->createdBy->name). "" ]) !!} @else - {{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }} + @icon('star'){{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }} @endif +
+ @if ($entity->updatedBy) - {!! trans('entities.meta_updated_name', [ + @icon('edit'){!! trans('entities.meta_updated_name', [ 'timeLength' => '' . $entity->updated_at->diffForHumans() .'', 'user' => "".htmlentities($entity->updatedBy->name). "" ]) !!} @elseif (!$entity->isA('revision')) - {{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }} + @icon('edit'){{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }} @endif -

\ No newline at end of file +
\ No newline at end of file From 624c568008af7e8fe8c2644742cb4c6c19bc4ad8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 30 Apr 2018 14:35:15 +0100 Subject: [PATCH 04/35] Revamped tag styling --- resources/assets/sass/_blocks.scss | 30 +++++++++++++++++++ resources/assets/sass/_grid.scss | 3 ++ resources/views/books/show.blade.php | 16 ++++------ resources/views/chapters/show.blade.php | 16 +++++----- resources/views/components/tag-list.blade.php | 16 ++++------ resources/views/pages/show.blade.php | 9 ++---- 6 files changed, 55 insertions(+), 35 deletions(-) diff --git a/resources/assets/sass/_blocks.scss b/resources/assets/sass/_blocks.scss index 76d4d5e0c..b7a8dcc03 100644 --- a/resources/assets/sass/_blocks.scss +++ b/resources/assets/sass/_blocks.scss @@ -268,3 +268,33 @@ padding: $-m; border: 1px solid #DDD; } + +.tag-item { + display: inline-flex; + margin-bottom: $-xs; + margin-right: $-xs; + border-radius: 4px; + border: 1px solid #CCC; + overflow: hidden; + font-size: 0.85em; + a, a:hover, a:active { + padding: 4px 8px; + color: #777; + transition: background-color ease-in-out 80ms; + text-decoration: none; + } + a:hover { + background-color: rgba(255, 255, 255, 0.7); + } + svg { + fill: #888; + } + .tag-value { + border-left: 1px solid #DDD; + background-color: rgba(255, 255, 255, 0.5); + } +} + +.tag-list div:last-child .tag-item { + margin-bottom: 0; +} \ No newline at end of file diff --git a/resources/assets/sass/_grid.scss b/resources/assets/sass/_grid.scss index 10af80a54..8f15153b5 100644 --- a/resources/assets/sass/_grid.scss +++ b/resources/assets/sass/_grid.scss @@ -55,6 +55,9 @@ body.flexbox { background-color: #F2F2F2; max-width: 360px; min-height: 90vh; + section { + margin: $-m; + } } .flex.sidebar + .flex.content { flex: 3; diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index e97d9bb69..d0a2eb2f7 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -43,6 +43,12 @@ @section('sidebar') + @if($book->tags->count() > 0) +
+ @include('components.tag-list', ['entity' => $book]) +
+ @endif +
- @if($book->tags->count() > 0) -
-

@icon('tag') {{ trans('entities.book_tags') }}

-
- @include('components.tag-list', ['entity' => $book]) -
-
- @endif - -

@icon('info') {{ trans('common.details') }}

diff --git a/resources/views/pages/revision.blade.php b/resources/views/pages/revision.blade.php index f3243e591..f9e8a22c4 100644 --- a/resources/views/pages/revision.blade.php +++ b/resources/views/pages/revision.blade.php @@ -3,7 +3,7 @@ @section('sidebar')

@icon('info') {{ trans('common.details') }}

-
+
@include('partials.entity-meta', ['entity' => $revision])
diff --git a/resources/views/partials/entity-meta.blade.php b/resources/views/partials/entity-meta.blade.php index 2784f03f0..f759ea25b 100644 --- a/resources/views/partials/entity-meta.blade.php +++ b/resources/views/partials/entity-meta.blade.php @@ -1,6 +1,6 @@
@if($entity->isA('revision')) - {{ trans('entities.pages_revision') }} + @icon('history'){{ trans('entities.pages_revision') }} {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
@endif From 2e4863edb134ad348050ef248c3b46db008b75a6 Mon Sep 17 00:00:00 2001 From: Abijeet Date: Wed, 9 May 2018 08:26:49 +0530 Subject: [PATCH 07/35] Added an option to set books as the default homepage. Signed-off-by: Abijeet --- resources/assets/sass/_forms.scss | 2 ++ resources/lang/en/settings.php | 1 + resources/views/settings/index.blade.php | 2 ++ 3 files changed, 5 insertions(+) diff --git a/resources/assets/sass/_forms.scss b/resources/assets/sass/_forms.scss index 3ab2de522..8fad0ba51 100644 --- a/resources/assets/sass/_forms.scss +++ b/resources/assets/sass/_forms.scss @@ -9,6 +9,8 @@ color: #666; width: 250px; max-width: 100%; + margin: 6px 6px 6px 0px; + &.neg, &.invalid { border: 1px solid $negative; } diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index de4894280..b699b5c4b 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -34,6 +34,7 @@ return [ 'app_homepage' => 'Application Homepage', 'app_homepage_desc' => 'Select a page to show on the homepage instead of the default view. Page permissions are ignored for selected pages.', 'app_homepage_default' => 'Default homepage view chosen', + 'app_homepage_books' => 'Or select the books page as your homepage. This will override any page selected as your homepage.', 'app_disable_comments' => 'Disable comments', 'app_disable_comments_desc' => 'Disable comments across all pages in the application. Existing comments are not shown.', diff --git a/resources/views/settings/index.blade.php b/resources/views/settings/index.blade.php index 51ca8ccb4..64017e6e0 100644 --- a/resources/views/settings/index.blade.php +++ b/resources/views/settings/index.blade.php @@ -80,6 +80,8 @@

{{ trans('settings.app_homepage_desc') }}

@include('components.page-picker', ['name' => 'setting-app-homepage', 'placeholder' => trans('settings.app_homepage_default'), 'value' => setting('app-homepage')]) +

{{ trans('settings.app_homepage_books') }}

+ @include('components.toggle-switch', ['name' => 'setting-app-book-homepage', 'value' => setting('app-book-homepage')])
From 86b2ddbd28b49169463a1d6be6bc4e4c24490bf7 Mon Sep 17 00:00:00 2001 From: Abijeet Date: Thu, 10 May 2018 09:05:18 +0530 Subject: [PATCH 08/35] Implemented displaying of the books list on home page. --- app/Http/Controllers/HomeController.php | 35 +++++++++++++----- resources/views/books/index.blade.php | 31 +--------------- resources/views/books/list.blade.php | 31 ++++++++++++++++ resources/views/home-book.blade.php | 47 +++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 38 deletions(-) create mode 100644 resources/views/books/list.blade.php create mode 100644 resources/views/home-book.blade.php diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index bbe1a8679..da61d1c64 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -33,22 +33,41 @@ class HomeController extends Controller $recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 12*$recentFactor); $recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 12); - // Custom homepage + $customHomepage = false; - $homepageSetting = setting('app-homepage'); - if ($homepageSetting) { - $id = intval(explode(':', $homepageSetting)[0]); - $customHomepage = $this->entityRepo->getById('page', $id, false, true); - $this->entityRepo->renderPage($customHomepage, true); + $books = false; + $booksViewType = false; + + // Check book homepage + $bookHomepageSetting = setting('app-book-homepage'); + if ($bookHomepageSetting) { + $books = $this->entityRepo->getAllPaginated('book', 18); + $booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list')); + } else { + // Check custom homepage + $homepageSetting = setting('app-homepage'); + if ($homepageSetting) { + $id = intval(explode(':', $homepageSetting)[0]); + $customHomepage = $this->entityRepo->getById('page', $id, false, true); + $this->entityRepo->renderPage($customHomepage, true); + } + } + + $view = 'home'; + if ($bookHomepageSetting) { + $view = 'home-book'; + } else if ($customHomepage) { + $view = 'home-custom'; } - $view = $customHomepage ? 'home-custom' : 'home'; return view($view, [ 'activity' => $activity, 'recents' => $recents, 'recentlyUpdatedPages' => $recentlyUpdatedPages, 'draftPages' => $draftPages, - 'customHomepage' => $customHomepage + 'customHomepage' => $customHomepage, + 'books' => $books, + 'booksViewType' => $booksViewType ]); } diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index d1435ab66..f625ae6fb 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -52,34 +52,5 @@ @stop @section('body') - @if($booksViewType === 'list') -
- @else -
- @endif -

{{ trans('entities.books') }}

- @if(count($books) > 0) - @if($booksViewType === 'list') - @foreach($books as $book) - @include('books/list-item', ['book' => $book]) -
- @endforeach - {!! $books->render() !!} - @else -
- @foreach($books as $key => $book) - @include('books/grid-item', ['book' => $book]) - @endforeach -
-
- {!! $books->render() !!} -
- @endif - @else -

{{ trans('entities.books_empty') }}

- @if(userCan('books-create-all')) - @icon('edit'){{ trans('entities.create_one_now') }} - @endif - @endif -
+ @include('books/list', ['books' => $books, 'bookViewType' => $booksViewType]) @stop \ No newline at end of file diff --git a/resources/views/books/list.blade.php b/resources/views/books/list.blade.php new file mode 100644 index 000000000..1c2056a79 --- /dev/null +++ b/resources/views/books/list.blade.php @@ -0,0 +1,31 @@ + +@if($booksViewType === 'list') +
+@else +
+@endif +

{{ trans('entities.books') }}

+ @if(count($books) > 0) + @if($booksViewType === 'list') + @foreach($books as $book) + @include('books/list-item', ['book' => $book]) +
+ @endforeach + {!! $books->render() !!} + @else +
+ @foreach($books as $key => $book) + @include('books/grid-item', ['book' => $book]) + @endforeach +
+
+ {!! $books->render() !!} +
+ @endif + @else +

{{ trans('entities.books_empty') }}

+ @if(userCan('books-create-all')) + @icon('edit'){{ trans('entities.create_one_now') }} + @endif + @endif +
\ No newline at end of file diff --git a/resources/views/home-book.blade.php b/resources/views/home-book.blade.php new file mode 100644 index 000000000..ef94a78a1 --- /dev/null +++ b/resources/views/home-book.blade.php @@ -0,0 +1,47 @@ +@extends('sidebar-layout') + +@section('toolbar') + +@stop + +@section('sidebar') + @if(count($draftPages) > 0) +
+

@icon('edit') {{ trans('entities.my_recent_drafts') }}

+ @include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact']) +
+ @endif + +
+

@icon($signedIn ? 'view' : 'star-circle') {{ trans('entities.' . ($signedIn ? 'my_recently_viewed' : 'books_recent')) }}

+ @include('partials/entity-list', [ + 'entities' => $recents, + 'style' => 'compact', + 'emptyText' => $signedIn ? trans('entities.no_pages_viewed') : trans('entities.books_empty') + ]) +
+ +
+

@icon('file') {{ trans('entities.recently_updated_pages') }}

+
+ @include('partials/entity-list', [ + 'entities' => $recentlyUpdatedPages, + 'style' => 'compact', + 'emptyText' => trans('entities.no_pages_recently_updated') + ]) +
+
+ +
+

@icon('time') {{ trans('entities.recent_activity') }}

+ @include('partials/activity-list', ['activity' => $activity]) +
+@stop + +@section('body') + @include('books/list', ['books' => $books, 'bookViewType' => $booksViewType]) +@stop \ No newline at end of file From 47cb99a2d6fa0e1d0b3da903d27a1d5aa8ba753e Mon Sep 17 00:00:00 2001 From: Abijeet Date: Sat, 12 May 2018 13:07:28 +0530 Subject: [PATCH 09/35] Added test cases. Signed-off-by: Abijeet --- tests/HomepageTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/HomepageTest.php b/tests/HomepageTest.php index 8570ed00b..29e0985c3 100644 --- a/tests/HomepageTest.php +++ b/tests/HomepageTest.php @@ -49,4 +49,23 @@ class HomepageTest extends TestCase $homeVisit->assertSee($name); $homeVisit->assertStatus(200); } + + public function test_set_book_homepage() + { + $editor = $this->getEditor(); + setting()->putUser($editor, 'books_view_type', 'grid'); + + $this->setSettings(['app-book-homepage' => true]); + + $this->asEditor(); + $homeVisit = $this->get('/'); + $homeVisit->assertSee('Books'); + $homeVisit->assertSee('book-grid-item grid-card'); + $homeVisit->assertSee('grid-card-content'); + $homeVisit->assertSee('grid-card-footer'); + $homeVisit->assertSee('featured-image-container'); + + $this->setSettings(['app-book-homepage' => false]); + $this->test_default_homepage_visible(); + } } From 8254c3be8d416fce276585f81baf45f5bc755347 Mon Sep 17 00:00:00 2001 From: Abijeet Date: Sat, 12 May 2018 14:16:05 +0530 Subject: [PATCH 10/35] Added the book view toggle option on the homepage. Signed-off-by: Abijeet --- resources/views/books/index.blade.php | 11 +---------- resources/views/home-book.blade.php | 1 + resources/views/partials/book-view-toggle.blade.php | 10 ++++++++++ 3 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 resources/views/partials/book-view-toggle.blade.php diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index f625ae6fb..d3e0d1297 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -3,16 +3,7 @@ @section('toolbar')
- id}/switch-book-view") }}" method="POST" class="inline"> - {!! csrf_field() !!} - {!! method_field('PATCH') !!} - - @if ($booksViewType === 'list') - - @else - - @endif - + @include('partials/book-view-toggle', ['booksViewType' => $booksViewType])
diff --git a/resources/views/home-book.blade.php b/resources/views/home-book.blade.php index ef94a78a1..e3b523570 100644 --- a/resources/views/home-book.blade.php +++ b/resources/views/home-book.blade.php @@ -4,6 +4,7 @@
@icon('expand-text'){{ trans('common.toggle_details') }} + @include('partials/book-view-toggle', ['booksViewType' => $booksViewType])
@stop diff --git a/resources/views/partials/book-view-toggle.blade.php b/resources/views/partials/book-view-toggle.blade.php new file mode 100644 index 000000000..61df7ab8d --- /dev/null +++ b/resources/views/partials/book-view-toggle.blade.php @@ -0,0 +1,10 @@ +
id}/switch-book-view") }}" method="POST" class="inline"> + {!! csrf_field() !!} + {!! method_field('PATCH') !!} + + @if ($booksViewType === 'list') + + @else + + @endif +
\ No newline at end of file From b6bb078e0a07b56b347f8dca6397bc7c028d6f47 Mon Sep 17 00:00:00 2001 From: Abijeet Date: Sat, 12 May 2018 17:28:10 +0530 Subject: [PATCH 11/35] removed some added CSS as it was causing unintended sideffects. Signed-off-by: Abijeet --- resources/assets/sass/_forms.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/assets/sass/_forms.scss b/resources/assets/sass/_forms.scss index 8fad0ba51..6b3ed3815 100644 --- a/resources/assets/sass/_forms.scss +++ b/resources/assets/sass/_forms.scss @@ -9,7 +9,6 @@ color: #666; width: 250px; max-width: 100%; - margin: 6px 6px 6px 0px; &.neg, &.invalid { border: 1px solid $negative; From 28823c4fae3b543e63d562b0b7735d26bc6b11e1 Mon Sep 17 00:00:00 2001 From: Abijeet Date: Sat, 12 May 2018 18:26:35 +0530 Subject: [PATCH 12/35] Changed the location of the "view-toggle" to be under the books views. Signed-off-by: Abijeet --- resources/views/books/index.blade.php | 2 +- .../book-view-toggle.blade.php => books/view-toggle.blade.php} | 0 resources/views/home-book.blade.php | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename resources/views/{partials/book-view-toggle.blade.php => books/view-toggle.blade.php} (100%) diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index d3e0d1297..84150203f 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -3,7 +3,7 @@ @section('toolbar')
- @include('partials/book-view-toggle', ['booksViewType' => $booksViewType]) + @include('books/view-toggle', ['booksViewType' => $booksViewType])
diff --git a/resources/views/partials/book-view-toggle.blade.php b/resources/views/books/view-toggle.blade.php similarity index 100% rename from resources/views/partials/book-view-toggle.blade.php rename to resources/views/books/view-toggle.blade.php diff --git a/resources/views/home-book.blade.php b/resources/views/home-book.blade.php index e3b523570..03fce4b8e 100644 --- a/resources/views/home-book.blade.php +++ b/resources/views/home-book.blade.php @@ -4,7 +4,7 @@
@icon('expand-text'){{ trans('common.toggle_details') }} - @include('partials/book-view-toggle', ['booksViewType' => $booksViewType]) + @include('books/view-toggle', ['booksViewType' => $booksViewType])
@stop From d5b922aa50236c047904e94ca38b62026af4bb68 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 13 May 2018 12:07:38 +0100 Subject: [PATCH 13/35] Started work on drawing revisions Improved sidebar and selection styling of image manager. Allowed image manager imageType to be changed on open. Created models for image revisions. --- app/Http/Controllers/ImageController.php | 26 ++++++----- app/Image.php | 14 +++++- app/ImageRevision.php | 26 +++++++++++ ...13_090521_create_image_revisions_table.php | 37 +++++++++++++++ .../assets/js/components/wysiwyg-editor.js | 25 +++++++++-- resources/assets/js/vues/image-manager.js | 42 +++++++++++------ resources/assets/sass/_components.scss | 28 ++++++++---- resources/assets/sass/styles.scss | 1 + resources/lang/de/components.php | 3 +- resources/lang/en/components.php | 3 +- resources/lang/es/components.php | 3 +- resources/lang/es_AR/components.php | 3 +- resources/lang/fr/components.php | 3 +- resources/lang/it/components.php | 3 +- resources/lang/ja/components.php | 3 +- resources/lang/nl/components.php | 3 +- resources/lang/pl/components.php | 3 +- resources/lang/pt_BR/components.php | 3 +- resources/lang/ru/components.php | 3 +- resources/lang/sk/components.php | 3 +- resources/lang/sv/components.php | 3 +- resources/lang/zh_CN/components.php | 3 +- resources/lang/zh_TW/components.php | 3 +- .../views/components/image-manager.blade.php | 45 +++++++++++-------- routes/web.php | 1 + 25 files changed, 217 insertions(+), 73 deletions(-) create mode 100644 app/ImageRevision.php create mode 100644 database/migrations/2018_05_13_090521_create_image_revisions_table.php diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php index 8437c80d7..277c27069 100644 --- a/app/Http/Controllers/ImageController.php +++ b/app/Http/Controllers/ImageController.php @@ -245,26 +245,28 @@ class ImageController extends Controller } /** - * Deletes an image and all thumbnail/image files + * Show the usage of an image on pages. * @param EntityRepo $entityRepo - * @param Request $request + * @param $id + * @return \Illuminate\Http\JsonResponse + */ + public function usage(EntityRepo $entityRepo, $id) + { + $image = $this->imageRepo->getById($id); + $pageSearch = $entityRepo->searchForImage($image->url); + return response()->json($pageSearch); + } + + /** + * Deletes an image and all thumbnail/image files * @param int $id * @return \Illuminate\Http\JsonResponse */ - public function destroy(EntityRepo $entityRepo, Request $request, $id) + public function destroy($id) { $image = $this->imageRepo->getById($id); $this->checkOwnablePermission('image-delete', $image); - // Check if this image is used on any pages - $isForced = in_array($request->get('force', ''), [true, 'true']); - if (!$isForced) { - $pageSearch = $entityRepo->searchForImage($image->url); - if ($pageSearch !== false) { - return response()->json($pageSearch, 400); - } - } - $this->imageRepo->destroyImage($image); return response()->json(trans('components.images_deleted')); } diff --git a/app/Image.php b/app/Image.php index ad23a077a..30bbe21e2 100644 --- a/app/Image.php +++ b/app/Image.php @@ -9,13 +9,23 @@ class Image extends Ownable /** * Get a thumbnail for this image. - * @param int $width - * @param int $height + * @param int $width + * @param int $height * @param bool|false $keepRatio * @return string + * @throws \Exception */ public function getThumb($width, $height, $keepRatio = false) { return Images::getThumbnail($this, $width, $height, $keepRatio); } + + /** + * Get the revisions for this image. + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function revisions() + { + return $this->hasMany(ImageRevision::class); + } } diff --git a/app/ImageRevision.php b/app/ImageRevision.php new file mode 100644 index 000000000..fde232867 --- /dev/null +++ b/app/ImageRevision.php @@ -0,0 +1,26 @@ +belongsTo(User::class, 'created_by'); + } + + /** + * Get the image that this is a revision of. + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function image() + { + return $this->belongsTo(Image::class); + } +} diff --git a/database/migrations/2018_05_13_090521_create_image_revisions_table.php b/database/migrations/2018_05_13_090521_create_image_revisions_table.php new file mode 100644 index 000000000..968773a86 --- /dev/null +++ b/database/migrations/2018_05_13_090521_create_image_revisions_table.php @@ -0,0 +1,37 @@ +increments('id'); + $table->integer('image_id'); + $table->string('path'); + $table->string('url'); + $table->integer('created_by'); + + $table->index('image_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('image_revisions'); + } +} diff --git a/resources/assets/js/components/wysiwyg-editor.js b/resources/assets/js/components/wysiwyg-editor.js index 56aa294fa..03d73d2b5 100644 --- a/resources/assets/js/components/wysiwyg-editor.js +++ b/resources/assets/js/components/wysiwyg-editor.js @@ -221,8 +221,6 @@ function codePlugin() { function drawIoPlugin() { - const drawIoUrl = 'https://www.draw.io/?embed=1&ui=atlas&spin=1&proto=json'; - let iframe = null; let pageEditor = null; let currentNode = null; @@ -230,6 +228,20 @@ function drawIoPlugin() { return node.hasAttribute('drawio-diagram'); } + function showDrawingManager(mceEditor, selectedNode = null) { + // TODO - Handle how image manager links in. + // Show image manager + window.ImageManager.show(function (image) { + + + // // Replace the actively selected content with the linked image + // let html = ``; + // html += `${image.name}`; + // html += ''; + // win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html); + }, 'drawio'); + } + function showDrawingEditor(mceEditor, selectedNode = null) { pageEditor = mceEditor; currentNode = selectedNode; @@ -287,7 +299,12 @@ function drawIoPlugin() { window.tinymce.PluginManager.add('drawio', function(editor, url) { editor.addCommand('drawio', () => { - showDrawingEditor(editor); + let selectedNode = editor.selection.getNode(); + if (isDrawing(selectedNode)) { + showDrawingManager(editor, selectedNode); + } else { + showDrawingEditor(editor); + } }); editor.addButton('drawio', { @@ -443,7 +460,7 @@ class WysiwygEditor { html += `${image.name}`; html += ''; win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html); - }); + }, 'gallery'); } }, diff --git a/resources/assets/js/vues/image-manager.js b/resources/assets/js/vues/image-manager.js index 89fe6769e..bef666192 100644 --- a/resources/assets/js/vues/image-manager.js +++ b/resources/assets/js/vues/image-manager.js @@ -26,17 +26,22 @@ const data = { imageUpdateSuccess: false, imageDeleteSuccess: false, + deleteConfirm: false, }; const methods = { - show(providedCallback) { + show(providedCallback, imageType = null) { callback = providedCallback; this.showing = true; this.$el.children[0].components.overlay.show(); // Get initial images if they have not yet been loaded in. - if (dataLoaded) return; + if (dataLoaded && imageType === this.imageType) return; + if (imageType) { + this.imageType = imageType; + this.resetState(); + } this.fetchData(); dataLoaded = true; }, @@ -62,13 +67,18 @@ const methods = { }, setView(viewName) { + this.view = viewName; + this.resetState(); + this.fetchData(); + }, + + resetState() { this.cancelSearch(); this.images = []; this.hasMore = false; + this.deleteConfirm = false; page = 0; - this.view = viewName; - baseUrl = window.baseUrl(`/images/${this.imageType}/${viewName}/`); - this.fetchData(); + baseUrl = window.baseUrl(`/images/${this.imageType}/${this.view}/`); }, searchImages() { @@ -105,6 +115,7 @@ const methods = { this.callbackAndHide(image); } else { this.selectedImage = image; + this.deleteConfirm = false; this.dependantPages = false; } @@ -134,17 +145,22 @@ const methods = { }, deleteImage() { - let force = this.dependantPages !== false; - let url = window.baseUrl('/images/' + this.selectedImage.id); - if (force) url += '?force=true'; - this.$http.delete(url).then(response => { + + if (!this.deleteConfirm) { + let url = window.baseUrl(`/images/usage/${this.selectedImage.id}`); + this.$http.get(url).then(resp => { + this.dependantPages = resp.data; + }).catch(console.error).then(() => { + this.deleteConfirm = true; + }); + return; + } + + this.$http.delete(`/images/${this.selectedImage.id}`).then(resp => { this.images.splice(this.images.indexOf(this.selectedImage), 1); this.selectedImage = false; this.$events.emit('success', trans('components.image_delete_success')); - }).catch(error=> { - if (error.response.status === 400) { - this.dependantPages = error.response.data; - } + this.deleteConfirm = false; }); }, diff --git a/resources/assets/sass/_components.scss b/resources/assets/sass/_components.scss index 31e006e27..27dcfda64 100644 --- a/resources/assets/sass/_components.scss +++ b/resources/assets/sass/_components.scss @@ -146,7 +146,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { .dropzone-container { position: relative; - border: 3px dashed #DDD; + background-color: #EEE; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='%23a9a9a9' fill-opacity='0.52' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E"); } .image-manager-list .image { @@ -163,8 +164,10 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { transition: all cubic-bezier(.4, 0, 1, 1) 160ms; overflow: hidden; &.selected { - transform: scale3d(0.92, 0.92, 0.92); - border: 1px solid #444; + //transform: scale3d(0.92, 0.92, 0.92); + border: 4px solid #FFF; + overflow: hidden; + border-radius: 8px; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2); } img { @@ -210,12 +213,21 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { .image-manager-sidebar { width: 300px; margin-left: 1px; - padding: $-m $-l; overflow-y: auto; overflow-x: hidden; border-left: 1px solid #DDD; + .inner { + padding: $-m; + } + img { + max-width: 100%; + max-height: 200px; + display: block; + margin: 0 auto $-m auto; + box-shadow: $bs-light; + } .dropzone-container { - margin-top: $-m; + border-bottom: 1px solid #DDD; } } @@ -242,10 +254,10 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { * Copyright (c) 2012 Matias Meno */ .dz-message { - font-size: 1.2em; - line-height: 1.1; + font-size: 1em; + line-height: 2.35; font-style: italic; - color: #aaa; + color: #888; text-align: center; cursor: pointer; padding: $-l $-m; diff --git a/resources/assets/sass/styles.scss b/resources/assets/sass/styles.scss index c7d288ad3..0b2dfbf75 100644 --- a/resources/assets/sass/styles.scss +++ b/resources/assets/sass/styles.scss @@ -154,6 +154,7 @@ $btt-size: 40px; } input { flex: 5; + padding: $-xs $-s; &:focus, &:active { outline: 0; } diff --git a/resources/lang/de/components.php b/resources/lang/de/components.php index 510af4dd3..af07f2698 100644 --- a/resources/lang/de/components.php +++ b/resources/lang/de/components.php @@ -12,7 +12,8 @@ return [ 'image_uploaded' => 'Hochgeladen am :uploadedDate', 'image_load_more' => 'Mehr', 'image_image_name' => 'Bildname', - 'image_delete_confirm' => 'Dieses Bild wird auf den folgenden Seiten benutzt. Bitte klicken Sie erneut auf löschen, wenn Sie dieses Bild wirklich entfernen möchten.', + 'image_delete_used' => 'Dieses Bild wird auf den folgenden Seiten benutzt. ', + 'image_delete_confirm' => 'Bitte klicken Sie erneut auf löschen, wenn Sie dieses Bild wirklich entfernen möchten.', 'image_select_image' => 'Bild auswählen', 'image_dropzone' => 'Ziehen Sie Bilder hierher oder klicken Sie, um ein Bild auszuwählen', 'images_deleted' => 'Bilder gelöscht', diff --git a/resources/lang/en/components.php b/resources/lang/en/components.php index 2266fe2b2..c093f7316 100644 --- a/resources/lang/en/components.php +++ b/resources/lang/en/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Uploaded :uploadedDate', 'image_load_more' => 'Load More', 'image_image_name' => 'Image Name', - 'image_delete_confirm' => 'This image is used in the pages below, Click delete again to confirm you want to delete this image.', + 'image_delete_used' => 'This image is used in the pages below.', + 'image_delete_confirm' => 'Click delete again to confirm you want to delete this image.', 'image_select_image' => 'Select Image', 'image_dropzone' => 'Drop images or click here to upload', 'images_deleted' => 'Images Deleted', diff --git a/resources/lang/es/components.php b/resources/lang/es/components.php index a13d5d4fb..cdcba487d 100644 --- a/resources/lang/es/components.php +++ b/resources/lang/es/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Subido el :uploadedDate', 'image_load_more' => 'Cargar más', 'image_image_name' => 'Nombre de imagen', - 'image_delete_confirm' => 'Esta imagen está siendo utilizada en las páginas mostradas a continuación, haga click de nuevo para confirmar que quiere borrar esta imagen.', + 'image_delete_used' => 'Esta imagen está siendo utilizada en las páginas mostradas a continuación.', + 'image_delete_confirm' => 'Haga click de nuevo para confirmar que quiere borrar esta imagen.', 'image_select_image' => 'Seleccionar Imagen', 'image_dropzone' => 'Arrastre las imágenes o hacer click aquí para Subir', 'images_deleted' => 'Imágenes borradas', diff --git a/resources/lang/es_AR/components.php b/resources/lang/es_AR/components.php index ef4f33111..ea61f5f4c 100644 --- a/resources/lang/es_AR/components.php +++ b/resources/lang/es_AR/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Subido el :uploadedDate', 'image_load_more' => 'Cargar más', 'image_image_name' => 'Nombre de imagen', - 'image_delete_confirm' => 'Esta imagen esta siendo utilizada en las páginas a continuación, haga click de nuevo para confirmar que quiere borrar esta imagen.', + 'image_delete_used' => 'Esta imagen esta siendo utilizada en las páginas a continuación.', + 'image_delete_confirm' => 'Haga click de nuevo para confirmar que quiere borrar esta imagen.', 'image_select_image' => 'Seleccionar Imagen', 'image_dropzone' => 'Arrastre las imágenes o hacer click aquí para Subir', 'images_deleted' => 'Imágenes borradas', diff --git a/resources/lang/fr/components.php b/resources/lang/fr/components.php index ddfe665d9..438137b5e 100644 --- a/resources/lang/fr/components.php +++ b/resources/lang/fr/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Ajoutée le :uploadedDate', 'image_load_more' => 'Charger plus', 'image_image_name' => 'Nom de l\'image', - 'image_delete_confirm' => 'Cette image est utilisée dans les pages ci-dessous. Confirmez que vous souhaitez bien supprimer cette image.', + 'image_delete_used' => 'Cette image est utilisée dans les pages ci-dessous.', + 'image_delete_confirm' => 'Confirmez que vous souhaitez bien supprimer cette image.', 'image_select_image' => 'Selectionner l\'image', 'image_dropzone' => 'Glissez les images ici ou cliquez pour les ajouter', 'images_deleted' => 'Images supprimées', diff --git a/resources/lang/it/components.php b/resources/lang/it/components.php index 081a4c0e6..c9ab18a3e 100755 --- a/resources/lang/it/components.php +++ b/resources/lang/it/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Uploaded :uploadedDate', 'image_load_more' => 'Carica Altre', 'image_image_name' => 'Nome Immagine', - 'image_delete_confirm' => 'Questa immagine è usata nelle pagine elencate, clicca elimina nuovamente per confermare.', + 'image_delete_used' => 'Questa immagine è usata nelle pagine elencate.', + 'image_delete_confirm' => 'Clicca elimina nuovamente per confermare.', 'image_select_image' => 'Seleziona Immagine', 'image_dropzone' => 'Rilascia immagini o clicca qui per caricarle', 'images_deleted' => 'Immagini Eliminate', diff --git a/resources/lang/ja/components.php b/resources/lang/ja/components.php index 8e9eb2e47..53a9cda1b 100644 --- a/resources/lang/ja/components.php +++ b/resources/lang/ja/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'アップロード日時: :uploadedDate', 'image_load_more' => 'さらに読み込む', 'image_image_name' => '画像名', - 'image_delete_confirm' => 'この画像は以下のページで利用されています。削除してもよろしければ、再度ボタンを押して下さい。', + 'image_delete_used' => 'この画像は以下のページで利用されています。', + 'image_delete_confirm' => '削除してもよろしければ、再度ボタンを押して下さい。', 'image_select_image' => '選択', 'image_dropzone' => '画像をドロップするか、クリックしてアップロード', 'images_deleted' => '画像を削除しました', diff --git a/resources/lang/nl/components.php b/resources/lang/nl/components.php index fb306820e..576298ef2 100644 --- a/resources/lang/nl/components.php +++ b/resources/lang/nl/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Uploaded :uploadedDate', 'image_load_more' => 'Meer Laden', 'image_image_name' => 'Afbeeldingsnaam', - 'image_delete_confirm' => 'Deze afbeeldingen is op onderstaande pagina\'s in gebruik, Klik opnieuw op verwijderen om de afbeelding echt te verwijderen.', + 'image_delete_used' => 'Deze afbeeldingen is op onderstaande pagina\'s in gebruik.', + 'image_delete_confirm' => 'Klik opnieuw op verwijderen om de afbeelding echt te verwijderen.', 'image_select_image' => 'Kies Afbeelding', 'image_dropzone' => 'Sleep afbeeldingen hier of klik hier om te uploaden', 'images_deleted' => 'Verwijderde Afbeeldingen', diff --git a/resources/lang/pl/components.php b/resources/lang/pl/components.php index c1dbcd44b..177fdba5f 100644 --- a/resources/lang/pl/components.php +++ b/resources/lang/pl/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Udostępniono :uploadedDate', 'image_load_more' => 'Wczytaj więcej', 'image_image_name' => 'Nazwa obrazka', - 'image_delete_confirm' => 'Ten obrazek jest używany na stronach poniżej, kliknij ponownie Usuń by potwierdzić usunięcie obrazka.', + 'image_delete_used' => 'Ten obrazek jest używany na stronach poniżej.', + 'image_delete_confirm' => 'Kliknij ponownie Usuń by potwierdzić usunięcie obrazka.', 'image_select_image' => 'Wybierz obrazek', 'image_dropzone' => 'Upuść obrazki tutaj lub kliknij by wybrać obrazki do udostępnienia', 'images_deleted' => 'Usunięte obrazki', diff --git a/resources/lang/pt_BR/components.php b/resources/lang/pt_BR/components.php index 03fd4ac0c..872c00c9f 100644 --- a/resources/lang/pt_BR/components.php +++ b/resources/lang/pt_BR/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Carregado :uploadedDate', 'image_load_more' => 'Carregar Mais', 'image_image_name' => 'Nome da Imagem', - 'image_delete_confirm' => 'Essa imagem é usada nas páginas abaixo. Clique em Excluir novamente para confirmar que você deseja mesmo eliminar a imagem.', + 'image_delete_used' => 'Essa imagem é usada nas páginas abaixo.', + 'image_delete_confirm' => 'Clique em Excluir novamente para confirmar que você deseja mesmo eliminar a imagem.', 'image_select_image' => 'Selecionar Imagem', 'image_dropzone' => 'Arraste imagens ou clique aqui para fazer upload', 'images_deleted' => 'Imagens excluídas', diff --git a/resources/lang/ru/components.php b/resources/lang/ru/components.php index 49aed1e87..6ee44d6be 100644 --- a/resources/lang/ru/components.php +++ b/resources/lang/ru/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Загруженно :uploadedDate', 'image_load_more' => 'Загрузить ещё', 'image_image_name' => 'Имя изображения', - 'image_delete_confirm' => 'Это изображение используется на странице ниже. Снова кликните удалить для подтверждения того что вы хотите удалить.', + 'image_delete_used' => 'Это изображение используется на странице ниже.', + 'image_delete_confirm' => 'Снова кликните удалить для подтверждения того что вы хотите удалить.', 'image_select_image' => 'Выбрать изображение', 'image_dropzone' => 'Перетащите изображение или кликните для загрузки', 'images_deleted' => 'Изображения удалены', diff --git a/resources/lang/sk/components.php b/resources/lang/sk/components.php index f4fa92043..c20b62c7a 100644 --- a/resources/lang/sk/components.php +++ b/resources/lang/sk/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Nahrané :uploadedDate', 'image_load_more' => 'Načítať viac', 'image_image_name' => 'Názov obrázka', - 'image_delete_confirm' => 'Tento obrázok je použitý na stránkach uvedených nižšie, kliknite znova na zmazať pre potvrdenie zmazania tohto obrázka.', + 'image_delete_used' => 'Tento obrázok je použitý na stránkach uvedených nižšie.', + 'image_delete_confirm' => 'Kliknite znova na zmazať pre potvrdenie zmazania tohto obrázka.', 'image_select_image' => 'Vybrať obrázok', 'image_dropzone' => 'Presuňte obrázky sem alebo kliknite sem pre nahranie', 'images_deleted' => 'Obrázky zmazané', diff --git a/resources/lang/sv/components.php b/resources/lang/sv/components.php index 7249c5c1f..318a2ee5c 100644 --- a/resources/lang/sv/components.php +++ b/resources/lang/sv/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => 'Laddades upp :uploadedDate', 'image_load_more' => 'Ladda fler', 'image_image_name' => 'Bildnamn', - 'image_delete_confirm' => 'Den här bilden används på nedanstående sidor, klicka på "ta bort" en gång till för att bekräfta att du vill ta bort bilden.', + 'image_delete_used' => 'Den här bilden används på nedanstående sidor.', + 'image_delete_confirm' => 'Klicka på "ta bort" en gång till för att bekräfta att du vill ta bort bilden.', 'image_select_image' => 'Välj bild', 'image_dropzone' => 'Släpp bilder här eller klicka för att ladda upp', 'images_deleted' => 'Bilder borttagna', diff --git a/resources/lang/zh_CN/components.php b/resources/lang/zh_CN/components.php index 9e6f28649..42857b9e7 100644 --- a/resources/lang/zh_CN/components.php +++ b/resources/lang/zh_CN/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => '上传于 :uploadedDate', 'image_load_more' => '显示更多', 'image_image_name' => '图片名称', - 'image_delete_confirm' => '该图像用于以下页面,如果你想删除它,请再次按下按钮。', + 'image_delete_used' => '该图像用于以下页面。', + 'image_delete_confirm' => '如果你想删除它,请再次按下按钮。', 'image_select_image' => '选择图片', 'image_dropzone' => '拖放图片或点击此处上传', 'images_deleted' => '图片已删除', diff --git a/resources/lang/zh_TW/components.php b/resources/lang/zh_TW/components.php index ae0a083ad..e8826eebb 100644 --- a/resources/lang/zh_TW/components.php +++ b/resources/lang/zh_TW/components.php @@ -13,7 +13,8 @@ return [ 'image_uploaded' => '上傳於 :uploadedDate', 'image_load_more' => '載入更多', 'image_image_name' => '圖片名稱', - 'image_delete_confirm' => '所使用圖片目前用於以下頁面,如果你想刪除它,請再次按下按鈕。', + 'image_delete_used' => '所使用圖片目前用於以下頁面。', + 'image_delete_confirm' => '如果你想刪除它,請再次按下按鈕。', 'image_select_image' => '選擇圖片', 'image_dropzone' => '拖曳圖片或點選這裡上傳', 'images_deleted' => '圖片已刪除', diff --git a/resources/views/components/image-manager.blade.php b/resources/views/components/image-manager.blade.php index 78c6435d6..45b30697b 100644 --- a/resources/views/components/image-manager.blade.php +++ b/resources/views/components/image-manager.blade.php @@ -1,5 +1,5 @@
-
+
diff --git a/routes/web.php b/routes/web.php index f7b2347a5..0efd19efe 100644 --- a/routes/web.php +++ b/routes/web.php @@ -97,6 +97,7 @@ Route::group(['middleware' => 'auth'], function () { Route::put('/update/{imageId}', 'ImageController@update'); Route::post('/drawing/upload', 'ImageController@uploadDrawing'); Route::put('/drawing/upload/{id}', 'ImageController@replaceDrawing'); + Route::get('/usage/{id}', 'ImageController@usage'); Route::post('/{type}/upload', 'ImageController@uploadByType'); Route::get('/{type}/all', 'ImageController@getAllByType'); Route::get('/{type}/all/{page}', 'ImageController@getAllByType'); From 13ad0031d64ebaff08c2f7b8e1ff819958f6fce3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 13 May 2018 17:41:35 +0100 Subject: [PATCH 14/35] Drawings now generate revisions, not replace Updated drawing update test to accomodate. Image deletion system now takes revisions into account. --- app/Http/Controllers/ImageController.php | 4 +- app/Image.php | 11 ++ app/Repos/ImageRepo.php | 9 +- app/Repos/UserRepo.php | 2 +- app/Services/ImageService.php | 112 +++++++++++++----- ...13_090521_create_image_revisions_table.php | 1 + routes/web.php | 2 +- tests/ImageTest.php | 11 +- 8 files changed, 112 insertions(+), 40 deletions(-) diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php index 277c27069..b156a8425 100644 --- a/app/Http/Controllers/ImageController.php +++ b/app/Http/Controllers/ImageController.php @@ -170,7 +170,7 @@ class ImageController extends Controller * @param Request $request * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response */ - public function replaceDrawing(string $id, Request $request) + public function updateDrawing(string $id, Request $request) { $this->validate($request, [ 'image' => 'required|string' @@ -182,7 +182,7 @@ class ImageController extends Controller $this->checkOwnablePermission('image-update', $image); try { - $image = $this->imageRepo->replaceDrawingContent($image, $imageBase64Data); + $image = $this->imageRepo->updateDrawing($image, $imageBase64Data); } catch (ImageUploadException $e) { return response($e->getMessage(), 500); } diff --git a/app/Image.php b/app/Image.php index 30bbe21e2..ac94d9bf0 100644 --- a/app/Image.php +++ b/app/Image.php @@ -28,4 +28,15 @@ class Image extends Ownable { return $this->hasMany(ImageRevision::class); } + + /** + * Get the count of revisions made to this image. + * Based off numbers on revisions rather than raw count of attached revisions + * as they may be cleared up or revisions deleted at some point. + * @return int + */ + public function revisionCount() + { + return intval($this->revisions()->max('revision')); + } } diff --git a/app/Repos/ImageRepo.php b/app/Repos/ImageRepo.php index 245c0f27b..eff856872 100644 --- a/app/Repos/ImageRepo.php +++ b/app/Repos/ImageRepo.php @@ -160,9 +160,9 @@ class ImageRepo * @return Image * @throws \BookStack\Exceptions\ImageUploadException */ - public function replaceDrawingContent(Image $image, string $base64Uri) + public function updateDrawing(Image $image, string $base64Uri) { - return $this->imageService->replaceImageDataFromBase64Uri($image, $base64Uri); + return $this->imageService->updateImageFromBase64Uri($image, $base64Uri); } /** @@ -183,13 +183,14 @@ class ImageRepo /** - * Destroys an Image object along with its files and thumbnails. + * Destroys an Image object along with its revisions, files and thumbnails. * @param Image $image * @return bool + * @throws \Exception */ public function destroyImage(Image $image) { - $this->imageService->destroyImage($image); + $this->imageService->destroy($image); return true; } diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php index 3cfd61d27..d113b676a 100644 --- a/app/Repos/UserRepo.php +++ b/app/Repos/UserRepo.php @@ -166,7 +166,7 @@ class UserRepo // Delete user profile images $profileImages = $images = Image::where('type', '=', 'user')->where('created_by', '=', $user->id)->get(); foreach ($profileImages as $image) { - Images::destroyImage($image); + Images::destroy($image); } } diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index 06ef3a0f0..e83c1860b 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -2,12 +2,12 @@ use BookStack\Exceptions\ImageUploadException; use BookStack\Image; +use BookStack\ImageRevision; use BookStack\User; use Exception; use Intervention\Image\Exception\NotSupportedException; use Intervention\Image\ImageManager; use Illuminate\Contracts\Filesystem\Factory as FileSystem; -use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance; use Illuminate\Contracts\Cache\Repository as Cache; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -83,28 +83,19 @@ class ImageService extends UploadService } /** - * Replace the data for an image via a Base64 encoded string. * @param Image $image * @param string $base64Uri * @return Image * @throws ImageUploadException */ - public function replaceImageDataFromBase64Uri(Image $image, string $base64Uri) + public function updateImageFromBase64Uri(Image $image, string $base64Uri) { $splitData = explode(';base64,', $base64Uri); if (count($splitData) < 2) { throw new ImageUploadException("Invalid base64 image data provided"); } $data = base64_decode($splitData[1]); - $storage = $this->getStorage(); - - try { - $storage->put($image->path, $data); - } catch (Exception $e) { - throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $image->path])); - } - - return $image; + return $this->update($image, $data); } /** @@ -178,13 +169,57 @@ class ImageService extends UploadService } /** - * Get the storage path, Dependant of storage type. + * Update the content of an existing image. + * Uploaded the new image content and creates a revision for the old image content. * @param Image $image - * @return mixed|string + * @param $imageData + * @return Image + * @throws ImageUploadException */ - protected function getPath(Image $image) + private function update(Image $image, $imageData) { - return $image->path; + // Save image revision if not previously exists to ensure we always have + // a reference to the image files being uploaded. + if ($image->revisions()->count() === 0) { + $this->saveImageRevision($image); + } + + $pathInfo = pathinfo($image->path); + $revisionCount = $image->revisionCount() + 1; + $newFileName = preg_replace('/^(.+?)(-v\d+)?$/', '$1-v' . $revisionCount, $pathInfo['filename']); + + $image->path = str_replace_last($pathInfo['filename'], $newFileName, $image->path); + $image->url = $this->getPublicUrl($image->path); + $image->updated_by = user()->id; + + $storage = $this->getStorage(); + + try { + $storage->put($image->path, $imageData); + $storage->setVisibility($image->path, 'public'); + $image->save(); + $this->saveImageRevision($image); + } catch (Exception $e) { + throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $image->path])); + } + return $image; + } + + /** + * Save a new revision for an image. + * @param Image $image + * @return ImageRevision + */ + protected function saveImageRevision(Image $image) + { + $revision = new ImageRevision(); + $revision->image_id = $image->id; + $revision->path = $image->path; + $revision->url = $image->url; + $revision->created_by = user()->id; + $revision->revision = $image->revisionCount() + 1; + $revision->save(); + return $revision; } /** @@ -194,7 +229,7 @@ class ImageService extends UploadService */ protected function isGif(Image $image) { - return strtolower(pathinfo($this->getPath($image), PATHINFO_EXTENSION)) === 'gif'; + return strtolower(pathinfo($image->path, PATHINFO_EXTENSION)) === 'gif'; } /** @@ -212,11 +247,11 @@ class ImageService extends UploadService public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false) { if ($keepRatio && $this->isGif($image)) { - return $this->getPublicUrl($this->getPath($image)); + return $this->getPublicUrl($image->path); } $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/'; - $imagePath = $this->getPath($image); + $imagePath = $image->path; $thumbFilePath = dirname($imagePath) . $thumbDirName . basename($imagePath); if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) { @@ -262,43 +297,58 @@ class ImageService extends UploadService */ public function getImageData(Image $image) { - $imagePath = $this->getPath($image); + $imagePath = $image->path; $storage = $this->getStorage(); return $storage->get($imagePath); } /** - * Destroys an Image object along with its files and thumbnails. + * Destroy an image along with its revisions, thumbnails and remaining folders. * @param Image $image - * @return bool * @throws Exception */ - public function destroyImage(Image $image) + public function destroy(Image $image) + { + // Destroy image revisions + foreach ($image->revisions as $revision) { + $this->destroyImagesFromPath($revision->path); + $revision->delete(); + } + + // Destroy main image + $this->destroyImagesFromPath($image->path); + $image->delete(); + } + + /** + * Destroys an image at the given path. + * Searches for image thumbnails in addition to main provided path.. + * @param string $path + * @return bool + */ + protected function destroyImagesFromPath(string $path) { $storage = $this->getStorage(); - $imageFolder = dirname($this->getPath($image)); - $imageFileName = basename($this->getPath($image)); + $imageFolder = dirname($path); + $imageFileName = basename($path); $allImages = collect($storage->allFiles($imageFolder)); + // Delete image files $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) { $expectedIndex = strlen($imagePath) - strlen($imageFileName); return strpos($imagePath, $imageFileName) === $expectedIndex; }); - $storage->delete($imagesToDelete->all()); // Cleanup of empty folders - foreach ($storage->directories($imageFolder) as $directory) { + $foldersInvolved = array_merge([$imageFolder], $storage->directories($imageFolder)); + foreach ($foldersInvolved as $directory) { if ($this->isFolderEmpty($directory)) { $storage->deleteDirectory($directory); } } - if ($this->isFolderEmpty($imageFolder)) { - $storage->deleteDirectory($imageFolder); - } - $image->delete(); return true; } diff --git a/database/migrations/2018_05_13_090521_create_image_revisions_table.php b/database/migrations/2018_05_13_090521_create_image_revisions_table.php index 968773a86..d3032258f 100644 --- a/database/migrations/2018_05_13_090521_create_image_revisions_table.php +++ b/database/migrations/2018_05_13_090521_create_image_revisions_table.php @@ -16,6 +16,7 @@ class CreateImageRevisionsTable extends Migration Schema::create('image_revisions', function (Blueprint $table) { $table->increments('id'); $table->integer('image_id'); + $table->integer('revision'); $table->string('path'); $table->string('url'); $table->integer('created_by'); diff --git a/routes/web.php b/routes/web.php index 0efd19efe..40b00c75d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -96,7 +96,7 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/base64/{id}', 'ImageController@getBase64Image'); Route::put('/update/{imageId}', 'ImageController@update'); Route::post('/drawing/upload', 'ImageController@uploadDrawing'); - Route::put('/drawing/upload/{id}', 'ImageController@replaceDrawing'); + Route::put('/drawing/upload/{id}', 'ImageController@updateDrawing'); Route::get('/usage/{id}', 'ImageController@usage'); Route::post('/{type}/upload', 'ImageController@uploadByType'); Route::get('/{type}/all', 'ImageController@getAllByType'); diff --git a/tests/ImageTest.php b/tests/ImageTest.php index 49912ec4c..fd1aa010e 100644 --- a/tests/ImageTest.php +++ b/tests/ImageTest.php @@ -210,7 +210,7 @@ class ImageTest extends TestCase $this->assertTrue($testImageData === $uploadedImageData, "Uploaded image file data does not match our test image as expected"); } - public function test_drawing_replacing() + public function test_drawing_updating() { $page = Page::first(); $editor = $this->getEditor(); @@ -235,6 +235,15 @@ class ImageTest extends TestCase 'updated_by' => $editor->id, ]); + // Check a revision has been created + $this->assertDatabaseHas('image_revisions', [ + 'image_id' => $image->id, + 'revision' => 2, + 'created_by' => $editor->id, + ]); + + $image = Image::find($image->id); + $this->assertTrue(file_exists(public_path($image->path)), 'Uploaded image not found at path: '. public_path($image->path)); $testImageData = file_get_contents($this->getTestImageFilePath()); From 57c312ec3fc3c1c4bc19ab15c8f38ec67341c84b Mon Sep 17 00:00:00 2001 From: Marcos Date: Fri, 18 May 2018 03:10:49 +0200 Subject: [PATCH 15/35] Updated Spanish translation --- resources/lang/es/entities.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/es/entities.php b/resources/lang/es/entities.php index e68522820..b02b98e1a 100644 --- a/resources/lang/es/entities.php +++ b/resources/lang/es/entities.php @@ -61,7 +61,7 @@ return [ 'search_updated_after' => 'Actualizadas después de', 'search_created_before' => 'Creadas antes de', 'search_created_after' => 'Creadas después de', - 'search_set_date' => 'Ajustar Fecha', + 'search_set_date' => 'fecha', 'search_update' => 'Actualizar Búsqueda', /** From d3d8ddbe52e3642b2145be6bbf40792a3899d1c2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 19 May 2018 17:01:33 +0100 Subject: [PATCH 16/35] Improved 404 handling and fixed editor error 404 handling now not a hack-around and uses Laravel 'fallback' routes instead. Prevents errors with the session when you have mulitple errors on a page where a post/put/delete is made. --- app/Exceptions/Handler.php | 23 +-- app/Http/Controllers/HomeController.php | 8 + app/Http/Kernel.php | 8 - composer.json | 2 +- composer.lock | 237 ++++++++++++------------ resources/assets/js/vues/page-editor.js | 2 +- routes/web.php | 4 +- 7 files changed, 132 insertions(+), 152 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 4f6e690bc..0eb62dc31 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -4,8 +4,6 @@ namespace BookStack\Exceptions; use Exception; use Illuminate\Auth\AuthenticationException; -use Illuminate\Http\Request; -use Illuminate\Pipeline\Pipeline; use Illuminate\Validation\ValidationException; use Illuminate\Database\Eloquent\ModelNotFoundException; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -33,6 +31,7 @@ class Handler extends ExceptionHandler * * @param \Exception $e * @return mixed + * @throws Exception */ public function report(Exception $e) { @@ -65,30 +64,12 @@ class Handler extends ExceptionHandler // Handle 404 errors with a loaded session to enable showing user-specific information if ($this->isExceptionType($e, NotFoundHttpException::class)) { - return $this->loadErrorMiddleware($request, function ($request) use ($e) { - $message = $e->getMessage() ?: trans('errors.404_page_not_found'); - return response()->view('errors/404', ['message' => $message], 404); - }); + return \Route::respondWithRoute('fallback'); } return parent::render($request, $e); } - /** - * Load the middleware required to show state/session-enabled error pages. - * @param Request $request - * @param $callback - * @return mixed - */ - protected function loadErrorMiddleware(Request $request, $callback) - { - $middleware = (\Route::getMiddlewareGroups()['web_errors']); - return (new Pipeline($this->container)) - ->send($request) - ->through($middleware) - ->then($callback); - } - /** * Check the exception chain to compare against the original exception type. * @param Exception $e diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index bbe1a8679..32e0a743c 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -134,4 +134,12 @@ class HomeController extends Controller ->view('robots', ['allowRobots' => $allowRobots]) ->header('Content-Type', 'text/plain'); } + + /** + * Show the route for 404 responses. + */ + public function getNotFound() + { + return response()->view('errors/404', [], 404); + } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 9d2871bbe..cd894de95 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -33,14 +33,6 @@ class Kernel extends HttpKernel \Illuminate\Routing\Middleware\SubstituteBindings::class, \BookStack\Http\Middleware\Localization::class ], - 'web_errors' => [ - \BookStack\Http\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - \Illuminate\View\Middleware\ShareErrorsFromSession::class, - \BookStack\Http\Middleware\VerifyCsrfToken::class, - \BookStack\Http\Middleware\Localization::class - ], 'api' => [ 'throttle:60,1', 'bindings', diff --git a/composer.json b/composer.json index 5106ed0bb..3de0cb5f7 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "type": "project", "require": { "php": ">=7.0.0", - "laravel/framework": "5.5.*", + "laravel/framework": "~5.5.22", "fideloper/proxy": "~3.3", "ext-tidy": "*", "intervention/image": "^2.4", diff --git a/composer.lock b/composer.lock index 9370bebff..6e0a35323 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "ed85d10e69b1071020178cb400a80e48", + "content-hash": "3bf33ab103b15b06ca06c85fd8ae3b78", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.52.6", + "version": "3.56.4", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "c9af7657eddc0267cc7ac4f969c10d5c18459992" + "reference": "03273bb5c1d8098ff6c23b3fa9ee444c4cc1dcee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c9af7657eddc0267cc7ac4f969c10d5c18459992", - "reference": "c9af7657eddc0267cc7ac4f969c10d5c18459992", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/03273bb5c1d8098ff6c23b3fa9ee444c4cc1dcee", + "reference": "03273bb5c1d8098ff6c23b3fa9ee444c4cc1dcee", "shasum": "" }, "require": { @@ -84,7 +84,7 @@ "s3", "sdk" ], - "time": "2018-02-09T22:53:37+00:00" + "time": "2018-05-18T19:53:15+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -439,16 +439,16 @@ }, { "name": "egulias/email-validator", - "version": "2.1.3", + "version": "2.1.4", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "1bec00a10039b823cc94eef4eddd47dcd3b2ca04" + "reference": "8790f594151ca6a2010c6218e09d96df67173ad3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/1bec00a10039b823cc94eef4eddd47dcd3b2ca04", - "reference": "1bec00a10039b823cc94eef4eddd47dcd3b2ca04", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/8790f594151ca6a2010c6218e09d96df67173ad3", + "reference": "8790f594151ca6a2010c6218e09d96df67173ad3", "shasum": "" }, "require": { @@ -457,7 +457,7 @@ }, "require-dev": { "dominicsayers/isemail": "dev-master", - "phpunit/phpunit": "^4.8.35", + "phpunit/phpunit": "^4.8.35||^5.7||^6.0", "satooshi/php-coveralls": "^1.0.1" }, "suggest": { @@ -492,23 +492,24 @@ "validation", "validator" ], - "time": "2017-11-15T23:40:40+00:00" + "time": "2018-04-10T10:11:19+00:00" }, { "name": "erusev/parsedown", - "version": "1.6.4", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548" + "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/fbe3fe878f4fe69048bb8a52783a09802004f548", - "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", + "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", "shasum": "" }, "require": { + "ext-mbstring": "*", "php": ">=5.3.0" }, "require-dev": { @@ -537,7 +538,7 @@ "markdown", "parser" ], - "time": "2017-11-14T20:44:03+00:00" + "time": "2018-03-08T01:11:30+00:00" }, { "name": "fideloper/proxy", @@ -647,16 +648,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.3.0", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699" + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699", - "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", "shasum": "" }, "require": { @@ -666,7 +667,7 @@ }, "require-dev": { "ext-curl": "*", - "phpunit/phpunit": "^4.0 || ^5.0", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", "psr/log": "^1.0" }, "suggest": { @@ -675,7 +676,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.2-dev" + "dev-master": "6.3-dev" } }, "autoload": { @@ -708,7 +709,7 @@ "rest", "web service" ], - "time": "2017-06-22T18:50:49+00:00" + "time": "2018-04-22T15:46:56+00:00" }, { "name": "guzzlehttp/promises", @@ -964,27 +965,27 @@ }, { "name": "laravel/framework", - "version": "v5.5.34", + "version": "v5.5.40", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "1de7c0aec13eadbdddc2d1ba4019b064b2c6b966" + "reference": "d724ce0aa61bbd9adf658215eec484f5dd6711d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/1de7c0aec13eadbdddc2d1ba4019b064b2c6b966", - "reference": "1de7c0aec13eadbdddc2d1ba4019b064b2c6b966", + "url": "https://api.github.com/repos/laravel/framework/zipball/d724ce0aa61bbd9adf658215eec484f5dd6711d6", + "reference": "d724ce0aa61bbd9adf658215eec484f5dd6711d6", "shasum": "" }, "require": { "doctrine/inflector": "~1.1", - "erusev/parsedown": "~1.6", + "erusev/parsedown": "~1.7", "ext-mbstring": "*", "ext-openssl": "*", - "league/flysystem": "~1.0", + "league/flysystem": "^1.0.8", "monolog/monolog": "~1.12", "mtdowling/cron-expression": "~1.0", - "nesbot/carbon": "~1.20", + "nesbot/carbon": "^1.24.1", "php": ">=7.0", "psr/container": "~1.0", "psr/simple-cache": "^1.0", @@ -1030,7 +1031,7 @@ "illuminate/translation": "self.version", "illuminate/validation": "self.version", "illuminate/view": "self.version", - "tightenco/collect": "self.version" + "tightenco/collect": "<5.5.33" }, "require-dev": { "aws/aws-sdk-php": "~3.0", @@ -1094,20 +1095,20 @@ "framework", "laravel" ], - "time": "2018-02-06T15:36:55+00:00" + "time": "2018-03-30T13:29:30+00:00" }, { "name": "laravel/socialite", - "version": "v3.0.9", + "version": "v3.0.11", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "fc1c8d415699e502f3e61cbc61e3250d5bd942eb" + "reference": "4d29ba66fdb38ec994b778e5e51657555cc10511" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/fc1c8d415699e502f3e61cbc61e3250d5bd942eb", - "reference": "fc1c8d415699e502f3e61cbc61e3250d5bd942eb", + "url": "https://api.github.com/repos/laravel/socialite/zipball/4d29ba66fdb38ec994b778e5e51657555cc10511", + "reference": "4d29ba66fdb38ec994b778e5e51657555cc10511", "shasum": "" }, "require": { @@ -1156,20 +1157,20 @@ "laravel", "oauth" ], - "time": "2017-11-06T16:02:48+00:00" + "time": "2018-05-12T17:44:53+00:00" }, { "name": "league/flysystem", - "version": "1.0.42", + "version": "1.0.45", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "09eabc54e199950041aef258a85847676496fe8e" + "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/09eabc54e199950041aef258a85847676496fe8e", - "reference": "09eabc54e199950041aef258a85847676496fe8e", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a99f94e63b512d75f851b181afcdf0ee9ebef7e6", + "reference": "a99f94e63b512d75f851b181afcdf0ee9ebef7e6", "shasum": "" }, "require": { @@ -1240,20 +1241,20 @@ "sftp", "storage" ], - "time": "2018-01-27T16:03:56+00:00" + "time": "2018-05-07T08:44:23+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "1.0.18", + "version": "1.0.19", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "dc09b19f455750663b922ed52dcc0ff215bed284" + "reference": "f135691ef6761542af301b7c9880f140fb12dc74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/dc09b19f455750663b922ed52dcc0ff215bed284", - "reference": "dc09b19f455750663b922ed52dcc0ff215bed284", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/f135691ef6761542af301b7c9880f140fb12dc74", + "reference": "f135691ef6761542af301b7c9880f140fb12dc74", "shasum": "" }, "require": { @@ -1287,7 +1288,7 @@ } ], "description": "Flysystem adapter for the AWS S3 SDK v3.x", - "time": "2017-06-30T06:29:25+00:00" + "time": "2018-03-27T20:33:59+00:00" }, { "name": "league/oauth1-client", @@ -1531,35 +1532,30 @@ }, { "name": "nesbot/carbon", - "version": "1.22.1", + "version": "1.27.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc" + "reference": "ef81c39b67200dcd7401c24363dcac05ac3a4fe9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", - "reference": "7cdf42c0b1cc763ab7e4c33c47a24e27c66bfccc", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ef81c39b67200dcd7401c24363dcac05ac3a4fe9", + "reference": "ef81c39b67200dcd7401c24363dcac05ac3a4fe9", "shasum": "" }, "require": { - "php": ">=5.3.0", - "symfony/translation": "~2.6 || ~3.0" + "php": ">=5.3.9", + "symfony/translation": "~2.6 || ~3.0 || ~4.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "~2", - "phpunit/phpunit": "~4.0 || ~5.0" + "phpunit/phpunit": "^4.8.35 || ^5.7" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.23-dev" - } - }, "autoload": { "psr-4": { - "Carbon\\": "src/Carbon/" + "": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1580,20 +1576,20 @@ "datetime", "time" ], - "time": "2017-01-16T07:55:07+00:00" + "time": "2018-04-23T09:02:57+00:00" }, { "name": "paragonie/random_compat", - "version": "v2.0.11", + "version": "v2.0.12", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", - "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", + "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", "shasum": "" }, "require": { @@ -1628,7 +1624,7 @@ "pseudorandom", "random" ], - "time": "2017-09-27T21:40:39+00:00" + "time": "2018-04-04T21:24:14+00:00" }, { "name": "phenx/php-font-lib", @@ -1905,16 +1901,16 @@ }, { "name": "psr/simple-cache", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24" + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/753fa598e8f3b9966c886fe13f370baa45ef0e24", - "reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", "shasum": "" }, "require": { @@ -1949,7 +1945,7 @@ "psr-16", "simple-cache" ], - "time": "2017-01-02T13:31:39+00:00" + "time": "2017-10-23T01:57:42+00:00" }, { "name": "ramsey/uuid", @@ -2077,21 +2073,21 @@ }, { "name": "socialiteproviders/gitlab", - "version": "v3.0.1", + "version": "v3.0.2", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/GitLab.git", - "reference": "c96dc004563a3caf157608fe9aa9e45c79065d00" + "reference": "bab80e8e16853e062c58013b1c1f474bd5a5c49a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/GitLab/zipball/c96dc004563a3caf157608fe9aa9e45c79065d00", - "reference": "c96dc004563a3caf157608fe9aa9e45c79065d00", + "url": "https://api.github.com/repos/SocialiteProviders/GitLab/zipball/bab80e8e16853e062c58013b1c1f474bd5a5c49a", + "reference": "bab80e8e16853e062c58013b1c1f474bd5a5c49a", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "socialiteproviders/manager": "~3.0" + "socialiteproviders/manager": "~2.0 || ~3.0" }, "type": "library", "autoload": { @@ -2110,7 +2106,7 @@ } ], "description": "GitLab OAuth2 Provider for Laravel Socialite", - "time": "2017-01-31T05:06:13+00:00" + "time": "2018-05-11T03:10:27+00:00" }, { "name": "socialiteproviders/manager", @@ -2795,16 +2791,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.7.0", + "version": "v1.8.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" + "reference": "3296adf6a6454a050679cde90f95350ad604b171" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", + "reference": "3296adf6a6454a050679cde90f95350ad604b171", "shasum": "" }, "require": { @@ -2816,7 +2812,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -2850,7 +2846,7 @@ "portable", "shim" ], - "time": "2018-01-30T19:27:44+00:00" + "time": "2018-04-26T10:06:28+00:00" }, { "name": "symfony/process", @@ -3213,16 +3209,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.1.1", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "f0018d359a2ad6968ad11b283283a925e017f3c9" + "reference": "7a91480cc6e597caed5117a3c5d685f06d35c5a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f0018d359a2ad6968ad11b283283a925e017f3c9", - "reference": "f0018d359a2ad6968ad11b283283a925e017f3c9", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/7a91480cc6e597caed5117a3c5d685f06d35c5a1", + "reference": "7a91480cc6e597caed5117a3c5d685f06d35c5a1", "shasum": "" }, "require": { @@ -3277,7 +3273,7 @@ "profiler", "webprofiler" ], - "time": "2018-02-07T08:29:09+00:00" + "time": "2018-03-06T08:35:31+00:00" }, { "name": "barryvdh/laravel-ide-helper", @@ -3725,16 +3721,16 @@ }, { "name": "mockery/mockery", - "version": "1.0", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38" + "reference": "99e29d3596b16dabe4982548527d5ddf90232e99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/1bac8c362b12f522fdd1f1fa3556284c91affa38", - "reference": "1bac8c362b12f522fdd1f1fa3556284c91affa38", + "url": "https://api.github.com/repos/mockery/mockery/zipball/99e29d3596b16dabe4982548527d5ddf90232e99", + "reference": "99e29d3596b16dabe4982548527d5ddf90232e99", "shasum": "" }, "require": { @@ -3743,7 +3739,8 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "~5.7|~6.1" + "phpdocumentor/phpdocumentor": "^2.9", + "phpunit/phpunit": "~5.7.10|~6.5" }, "type": "library", "extra": { @@ -3772,8 +3769,8 @@ "homepage": "http://davedevelopment.co.uk" } ], - "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", - "homepage": "http://github.com/mockery/mockery", + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", "keywords": [ "BDD", "TDD", @@ -3786,7 +3783,7 @@ "test double", "testing" ], - "time": "2017-10-06T16:20:43+00:00" + "time": "2018-05-08T08:54:48+00:00" }, { "name": "myclabs/deep-copy", @@ -4089,28 +4086,28 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.3", + "version": "1.7.6", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf" + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", - "reference": "e4ed002c67da8eceb0eb8ddb8b3847bb53c5c2bf", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/recursion-context": "^1.0|^2.0|^3.0" }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" }, "type": "library", "extra": { @@ -4148,20 +4145,20 @@ "spy", "stub" ], - "time": "2017-11-24T13:59:53+00:00" + "time": "2018-04-18T13:57:24+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.3.0", + "version": "5.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1" + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/661f34d0bd3f1a7225ef491a70a020ad23a057a1", - "reference": "661f34d0bd3f1a7225ef491a70a020ad23a057a1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", "shasum": "" }, "require": { @@ -4211,7 +4208,7 @@ "testing", "xunit" ], - "time": "2017-12-06T09:29:45+00:00" + "time": "2018-04-06T15:36:58+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4401,16 +4398,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.6", + "version": "6.5.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3330ef26ade05359d006041316ed0fa9e8e3cefe" + "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3330ef26ade05359d006041316ed0fa9e8e3cefe", - "reference": "3330ef26ade05359d006041316ed0fa9e8e3cefe", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4f21a3c6b97c42952fd5c2837bb354ec0199b97b", + "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b", "shasum": "" }, "require": { @@ -4481,7 +4478,7 @@ "testing", "xunit" ], - "time": "2018-02-01T05:57:37+00:00" + "time": "2018-04-10T11:38:34+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -5103,16 +5100,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.2.2", + "version": "3.2.3", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1" + "reference": "4842476c434e375f9d3182ff7b89059583aa8b27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", - "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/4842476c434e375f9d3182ff7b89059583aa8b27", + "reference": "4842476c434e375f9d3182ff7b89059583aa8b27", "shasum": "" }, "require": { @@ -5122,7 +5119,7 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "bin": [ "bin/phpcs", @@ -5150,7 +5147,7 @@ "phpcs", "standards" ], - "time": "2017-12-19T21:44:46+00:00" + "time": "2018-02-20T21:35:23+00:00" }, { "name": "symfony/class-loader", diff --git a/resources/assets/js/vues/page-editor.js b/resources/assets/js/vues/page-editor.js index bb8e14c4c..020e371b0 100644 --- a/resources/assets/js/vues/page-editor.js +++ b/resources/assets/js/vues/page-editor.js @@ -99,7 +99,7 @@ let methods = { lastSave = Date.now(); }, errorRes => { if (draftErroring) return; - window.$events('error', trans('errors.page_draft_autosave_fail')); + window.$events.emit('error', trans('errors.page_draft_autosave_fail')); draftErroring = true; }); }, diff --git a/routes/web.php b/routes/web.php index f7b2347a5..794edfd01 100644 --- a/routes/web.php +++ b/routes/web.php @@ -197,4 +197,6 @@ Route::post('/password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail // Password reset routes... Route::get('/password/reset/{token}', 'Auth\ResetPasswordController@showResetForm'); -Route::post('/password/reset', 'Auth\ResetPasswordController@reset'); \ No newline at end of file +Route::post('/password/reset', 'Auth\ResetPasswordController@reset'); + +Route::fallback('HomeController@getNotFound'); \ No newline at end of file From 6cdb94391655a459760d8d20dc138b1f0eb2e4c4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 19 May 2018 18:44:40 +0100 Subject: [PATCH 17/35] Started work on revisions in image manager --- app/Http/Controllers/ImageController.php | 12 ++++++ app/Repos/ImageRepo.php | 2 +- resources/assets/js/vues/image-manager.js | 15 ++++++++ resources/assets/sass/_components.scss | 13 ++++++- .../views/components/image-manager.blade.php | 37 +++++++++++++++++-- routes/web.php | 1 + 6 files changed, 73 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php index b156a8425..bf7f3bb82 100644 --- a/app/Http/Controllers/ImageController.php +++ b/app/Http/Controllers/ImageController.php @@ -257,6 +257,18 @@ class ImageController extends Controller return response()->json($pageSearch); } + /** + * Get the revisions for an image. + * @param $id + * @return \Illuminate\Http\JsonResponse + */ + public function getRevisions($id) + { + $image = $this->imageRepo->getById($id); + $revisions = $image->revisions()->orderBy('id', 'desc')->get(); + return response()->json($revisions); + } + /** * Deletes an image and all thumbnail/image files * @param int $id diff --git a/app/Repos/ImageRepo.php b/app/Repos/ImageRepo.php index eff856872..384ea5454 100644 --- a/app/Repos/ImageRepo.php +++ b/app/Repos/ImageRepo.php @@ -201,7 +201,7 @@ class ImageRepo * @throws \BookStack\Exceptions\ImageUploadException * @throws \Exception */ - private function loadThumbs(Image $image) + protected function loadThumbs(Image $image) { $image->thumbs = [ 'gallery' => $this->getThumbnail($image, 150, 150), diff --git a/resources/assets/js/vues/image-manager.js b/resources/assets/js/vues/image-manager.js index bef666192..2a11a6ff2 100644 --- a/resources/assets/js/vues/image-manager.js +++ b/resources/assets/js/vues/image-manager.js @@ -24,6 +24,9 @@ const data = { searching: false, searchTerm: '', + revisions: [], + selectedRevision: null, + imageUpdateSuccess: false, imageDeleteSuccess: false, deleteConfirm: false, @@ -110,6 +113,8 @@ const methods = { let currentTime = Date.now(); let timeDiff = currentTime - previousClickTime; let isDblClick = timeDiff < dblClickTime && image.id === previousClickImage; + this.revisions = []; + this.selectedRevision = null if (isDblClick) { this.callbackAndHide(image); @@ -117,6 +122,11 @@ const methods = { this.selectedImage = image; this.deleteConfirm = false; this.dependantPages = false; + if (this.imageType === 'drawio') { + this.$http.get(window.baseUrl(`/images/revisions/${image.id}`)).then(resp => { + this.revisions = resp.data; + }) + } } previousClickTime = currentTime; @@ -172,6 +182,11 @@ const methods = { this.images.unshift(event.data); this.$events.emit('success', trans('components.image_upload_success')); }, + + selectRevision(revision) { + let rev = (this.selectedRevision === revision) ? null : revision; + this.selectedRevision = rev; + } }; const computed = { diff --git a/resources/assets/sass/_components.scss b/resources/assets/sass/_components.scss index 27dcfda64..76150fe44 100644 --- a/resources/assets/sass/_components.scss +++ b/resources/assets/sass/_components.scss @@ -221,10 +221,19 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { } img { max-width: 100%; - max-height: 200px; + max-height: 180px; display: block; margin: 0 auto $-m auto; - box-shadow: $bs-light; + box-shadow: 0 1px 21px 1px rgba(76, 76, 76, 0.3); + } + .image-manager-viewer { + height: 196px; + display: flex; + align-items: center; + justify-content: center; + a { + display: inline-block; + } } .dropzone-container { border-bottom: 1px solid #DDD; diff --git a/resources/views/components/image-manager.blade.php b/resources/views/components/image-manager.blade.php index 45b30697b..92a69c05d 100644 --- a/resources/views/components/image-manager.blade.php +++ b/resources/views/components/image-manager.blade.php @@ -47,10 +47,17 @@
-
-
+
+ + + +
+ + + @@ -60,7 +67,7 @@
-
+
@@ -84,6 +91,28 @@
+ {{--Revisions View--}} +
+
+
Revisions
+ + + + + +
+ + (Current) + + +
+ +
+

+ No revisions found +

+
+
diff --git a/routes/web.php b/routes/web.php index 40b00c75d..254da9cd4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -98,6 +98,7 @@ Route::group(['middleware' => 'auth'], function () { Route::post('/drawing/upload', 'ImageController@uploadDrawing'); Route::put('/drawing/upload/{id}', 'ImageController@updateDrawing'); Route::get('/usage/{id}', 'ImageController@usage'); + Route::get('/revisions/{id}', 'ImageController@getRevisions'); Route::post('/{type}/upload', 'ImageController@uploadByType'); Route::get('/{type}/all', 'ImageController@getAllByType'); Route::get('/{type}/all/{page}', 'ImageController@getAllByType'); From e0ebae19aaedf60ad5263243108f4eab87f904f2 Mon Sep 17 00:00:00 2001 From: Nikolai Nikolajevic Date: Sun, 20 May 2018 03:00:55 +0200 Subject: [PATCH 18/35] =?UTF-8?q?Update:=20=C3=9Cbersetzung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/lang/de/common.php | 1 + resources/lang/de/entities.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/resources/lang/de/common.php b/resources/lang/de/common.php index d3c0b4897..5579a488a 100644 --- a/resources/lang/de/common.php +++ b/resources/lang/de/common.php @@ -31,6 +31,7 @@ return [ 'edit' => 'Bearbeiten', 'sort' => 'Sortieren', 'move' => 'Verschieben', + 'copy' => 'Kopieren', 'reply' => 'Antworten', 'delete' => 'Löschen', 'search' => 'Suchen', diff --git a/resources/lang/de/entities.php b/resources/lang/de/entities.php index 460110eb4..250a86ea5 100644 --- a/resources/lang/de/entities.php +++ b/resources/lang/de/entities.php @@ -114,6 +114,9 @@ return [ 'chapters_move' => 'Kapitel verschieben', 'chapters_move_named' => 'Kapitel ":chapterName" verschieben', 'chapter_move_success' => 'Das Kapitel wurde in das Buch ":bookName" verschoben.', + 'pages_copy' => 'Seite kopieren', + 'pages_copy_desination' => 'Ziel', + 'pages_copy_success' => 'Seite erfolgreich kopiert', 'chapters_permissions' => 'Kapitel-Berechtigungen', 'chapters_empty' => 'Aktuell sind keine Kapitel diesem Buch hinzugefügt worden.', 'chapters_permissions_active' => 'Kapitel-Berechtigungen aktiv', From 9f4c64a676578ca18378445eac1aea23235f9da1 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 09:32:15 +0100 Subject: [PATCH 19/35] Codemirror mode now correct for c-like langs Fixes #849 --- resources/assets/js/services/code.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/assets/js/services/code.js b/resources/assets/js/services/code.js index 8a5a7a022..6c04e9872 100644 --- a/resources/assets/js/services/code.js +++ b/resources/assets/js/services/code.js @@ -20,13 +20,13 @@ const CodeMirror = require('codemirror'); const modeMap = { css: 'css', - c: 'clike', - java: 'clike', - scala: 'clike', - kotlin: 'clike', - 'c++': 'clike', - 'c#': 'clike', - csharp: 'clike', + c: 'text/x-csrc', + java: 'text/x-java', + scala: 'text/x-scala', + kotlin: 'text/x-kotlin', + 'c++': 'text/x-c++src', + 'c#': 'text/x-csharp', + csharp: 'text/x-csharp', diff: 'diff', go: 'go', html: 'htmlmixed', From 77727e7e5087f28cb55ebbf2b04402dc6b34c7fa Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 09:38:27 +0100 Subject: [PATCH 20/35] Update session config to match laravel Includes option to set secure cookies via env. Closes #817 --- config/session.php | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/config/session.php b/config/session.php index 8d8c14fe9..b334ffb3c 100644 --- a/config/session.php +++ b/config/session.php @@ -135,7 +135,7 @@ return [ | */ - 'domain' => null, + 'domain' => env('SESSION_DOMAIN', null), /* |-------------------------------------------------------------------------- @@ -148,6 +148,34 @@ return [ | */ - 'secure' => false, + 'secure' => env('SESSION_SECURE_COOKIE', false), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. You are free to modify this option if needed. + | + */ + + 'http_only' => true, + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | do not enable this as other CSRF protection services are in place. + | + | Supported: "lax", "strict" + | + */ + + 'same_site' => null, ]; From 93147f434065e474a27b31a396caa332de8ee45d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 09:48:11 +0100 Subject: [PATCH 21/35] Prevented back-to-top showing on flexbox-body pages Fixes #824 --- resources/assets/js/components/back-top-top.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/assets/js/components/back-top-top.js b/resources/assets/js/components/back-top-top.js index 5fa9b3436..f0bf263fb 100644 --- a/resources/assets/js/components/back-top-top.js +++ b/resources/assets/js/components/back-top-top.js @@ -6,6 +6,12 @@ class BackToTop { this.targetElem = document.getElementById('header'); this.showing = false; this.breakPoint = 1200; + + if (document.body.classList.contains('flexbox')) { + this.elem.style.display = 'none'; + return; + } + this.elem.addEventListener('click', this.scrollToTop.bind(this)); window.addEventListener('scroll', this.onPageScroll.bind(this)); } From 63f96c1c6f993b5decad0762e7aa874a7436ad78 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 10:11:56 +0100 Subject: [PATCH 22/35] Reorganised home and robots views Extracted home view sidebar into own view. Moved home and robot views into 'common' folder so that we only have layouts in the top-level views folder. --- app/Http/Controllers/HomeController.php | 4 +- resources/views/common/home-book.blade.php | 18 ++++++ resources/views/common/home-custom.blade.php | 19 +++++++ resources/views/common/home-sidebar.blade.php | 31 ++++++++++ resources/views/{ => common}/home.blade.php | 0 resources/views/{ => common}/robots.blade.php | 0 resources/views/home-book.blade.php | 48 ---------------- resources/views/home-custom.blade.php | 56 ------------------- resources/views/pages/show.blade.php | 2 +- 9 files changed, 71 insertions(+), 107 deletions(-) create mode 100644 resources/views/common/home-book.blade.php create mode 100644 resources/views/common/home-custom.blade.php create mode 100644 resources/views/common/home-sidebar.blade.php rename resources/views/{ => common}/home.blade.php (100%) rename resources/views/{ => common}/robots.blade.php (100%) delete mode 100644 resources/views/home-book.blade.php delete mode 100644 resources/views/home-custom.blade.php diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index dff319506..02b4789c2 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -60,7 +60,7 @@ class HomeController extends Controller $view = 'home-custom'; } - return view($view, [ + return view('common/' . $view, [ 'activity' => $activity, 'recents' => $recents, 'recentlyUpdatedPages' => $recentlyUpdatedPages, @@ -150,7 +150,7 @@ class HomeController extends Controller $allowRobots = $sitePublic; } return response() - ->view('robots', ['allowRobots' => $allowRobots]) + ->view('common/robots', ['allowRobots' => $allowRobots]) ->header('Content-Type', 'text/plain'); } diff --git a/resources/views/common/home-book.blade.php b/resources/views/common/home-book.blade.php new file mode 100644 index 000000000..2849d5ecd --- /dev/null +++ b/resources/views/common/home-book.blade.php @@ -0,0 +1,18 @@ +@extends('sidebar-layout') + +@section('toolbar') +
+
+ @icon('expand-text'){{ trans('common.toggle_details') }} + @include('books/view-toggle', ['booksViewType' => $booksViewType]) +
+
+@stop + +@section('sidebar') + @include('common/home-sidebar') +@stop + +@section('body') + @include('books/list', ['books' => $books, 'bookViewType' => $booksViewType]) +@stop \ No newline at end of file diff --git a/resources/views/common/home-custom.blade.php b/resources/views/common/home-custom.blade.php new file mode 100644 index 000000000..89154ee13 --- /dev/null +++ b/resources/views/common/home-custom.blade.php @@ -0,0 +1,19 @@ +@extends('sidebar-layout') + +@section('toolbar') + +@stop + +@section('sidebar') + @include('common/home-sidebar') +@stop + +@section('body') +
+ @include('pages/page-display', ['page' => $customHomepage]) +
+@stop diff --git a/resources/views/common/home-sidebar.blade.php b/resources/views/common/home-sidebar.blade.php new file mode 100644 index 000000000..221029481 --- /dev/null +++ b/resources/views/common/home-sidebar.blade.php @@ -0,0 +1,31 @@ +@if(count($draftPages) > 0) +
+

@icon('edit') {{ trans('entities.my_recent_drafts') }}

+ @include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact']) +
+@endif + +
+

@icon($signedIn ? 'view' : 'star-circle') {{ trans('entities.' . ($signedIn ? 'my_recently_viewed' : 'books_recent')) }}

+ @include('partials/entity-list', [ + 'entities' => $recents, + 'style' => 'compact', + 'emptyText' => $signedIn ? trans('entities.no_pages_viewed') : trans('entities.books_empty') + ]) +
+ +
+

@icon('file') {{ trans('entities.recently_updated_pages') }}

+
+ @include('partials/entity-list', [ + 'entities' => $recentlyUpdatedPages, + 'style' => 'compact', + 'emptyText' => trans('entities.no_pages_recently_updated') + ]) +
+
+ +
+

@icon('time') {{ trans('entities.recent_activity') }}

+ @include('partials/activity-list', ['activity' => $activity]) +
\ No newline at end of file diff --git a/resources/views/home.blade.php b/resources/views/common/home.blade.php similarity index 100% rename from resources/views/home.blade.php rename to resources/views/common/home.blade.php diff --git a/resources/views/robots.blade.php b/resources/views/common/robots.blade.php similarity index 100% rename from resources/views/robots.blade.php rename to resources/views/common/robots.blade.php diff --git a/resources/views/home-book.blade.php b/resources/views/home-book.blade.php deleted file mode 100644 index 03fce4b8e..000000000 --- a/resources/views/home-book.blade.php +++ /dev/null @@ -1,48 +0,0 @@ -@extends('sidebar-layout') - -@section('toolbar') -
-
- @icon('expand-text'){{ trans('common.toggle_details') }} - @include('books/view-toggle', ['booksViewType' => $booksViewType]) -
-
-@stop - -@section('sidebar') - @if(count($draftPages) > 0) -
-

@icon('edit') {{ trans('entities.my_recent_drafts') }}

- @include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact']) -
- @endif - -
-

@icon($signedIn ? 'view' : 'star-circle') {{ trans('entities.' . ($signedIn ? 'my_recently_viewed' : 'books_recent')) }}

- @include('partials/entity-list', [ - 'entities' => $recents, - 'style' => 'compact', - 'emptyText' => $signedIn ? trans('entities.no_pages_viewed') : trans('entities.books_empty') - ]) -
- -
-

@icon('file') {{ trans('entities.recently_updated_pages') }}

-
- @include('partials/entity-list', [ - 'entities' => $recentlyUpdatedPages, - 'style' => 'compact', - 'emptyText' => trans('entities.no_pages_recently_updated') - ]) -
-
- -
-

@icon('time') {{ trans('entities.recent_activity') }}

- @include('partials/activity-list', ['activity' => $activity]) -
-@stop - -@section('body') - @include('books/list', ['books' => $books, 'bookViewType' => $booksViewType]) -@stop \ No newline at end of file diff --git a/resources/views/home-custom.blade.php b/resources/views/home-custom.blade.php deleted file mode 100644 index d9fbab887..000000000 --- a/resources/views/home-custom.blade.php +++ /dev/null @@ -1,56 +0,0 @@ -@extends('sidebar-layout') - -@section('toolbar') - -@stop - -@section('sidebar') - @if(count($draftPages) > 0) -
-

@icon('edit') {{ trans('entities.my_recent_drafts') }}

- @include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact']) -
- @endif - -
-

@icon($signedIn ? 'view' : 'star-circle') {{ trans('entities.' . ($signedIn ? 'my_recently_viewed' : 'books_recent')) }}

- @include('partials/entity-list', [ - 'entities' => $recents, - 'style' => 'compact', - 'emptyText' => $signedIn ? trans('entities.no_pages_viewed') : trans('entities.books_empty') - ]) -
- -
-

@icon('file') {{ trans('entities.recently_updated_pages') }}

-
- @include('partials/entity-list', [ - 'entities' => $recentlyUpdatedPages, - 'style' => 'compact', - 'emptyText' => trans('entities.no_pages_recently_updated') - ]) -
-
- -
-

@icon('time') {{ trans('entities.recent_activity') }}

- @include('partials/activity-list', ['activity' => $activity]) -
-@stop - -@section('body') -
- @include('pages/page-display', ['page' => $customHomepage]) -
-@stop - -@section('scripts') - -@stop - diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index 16f967179..3448a164a 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -118,7 +118,7 @@ @stop @section('body') -
+
From a1ecdcacbaf3194ae44a588cdd2f853f5edef647 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 11:06:10 +0100 Subject: [PATCH 23/35] Fixed attachment error handling, Allowed all link types Related to #812 --- app/Attachment.php | 3 +++ app/Http/Controllers/AttachmentController.php | 5 +++-- resources/assets/js/vues/attachment-manager.js | 10 ++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/Attachment.php b/app/Attachment.php index 55344cd7d..6749130d9 100644 --- a/app/Attachment.php +++ b/app/Attachment.php @@ -31,6 +31,9 @@ class Attachment extends Ownable */ public function getUrl() { + if ($this->external && strpos($this->path, 'http') !== 0) { + return $this->path; + } return baseUrl('/attachments/' . $this->id); } } diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index ea41278ae..54e14bfb6 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -103,7 +103,7 @@ class AttachmentController extends Controller $this->validate($request, [ 'uploaded_to' => 'required|integer|exists:pages,id', 'name' => 'required|string|min:1|max:255', - 'link' => 'url|min:1|max:255' + 'link' => 'string|min:1|max:255' ]); $pageId = $request->get('uploaded_to'); @@ -131,7 +131,7 @@ class AttachmentController extends Controller $this->validate($request, [ 'uploaded_to' => 'required|integer|exists:pages,id', 'name' => 'required|string|min:1|max:255', - 'link' => 'required|url|min:1|max:255' + 'link' => 'required|string|min:1|max:255' ]); $pageId = $request->get('uploaded_to'); @@ -184,6 +184,7 @@ class AttachmentController extends Controller * @param $attachmentId * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\Response * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + * @throws NotFoundException */ public function get($attachmentId) { diff --git a/resources/assets/js/vues/attachment-manager.js b/resources/assets/js/vues/attachment-manager.js index 635622b93..16f96c70b 100644 --- a/resources/assets/js/vues/attachment-manager.js +++ b/resources/assets/js/vues/attachment-manager.js @@ -31,6 +31,9 @@ let methods = { }, getFileUrl(file) { + if (file.external && file.path.indexOf('http') !== 0) { + return file.path; + } return window.baseUrl(`/attachments/${file.id}`); }, @@ -79,10 +82,8 @@ let methods = { }, checkValidationErrors(groupName, err) { - console.error(err); - if (typeof err.response.data === "undefined" && typeof err.response.data.validation === "undefined") return; - this.errors[groupName] = err.response.data.validation; - console.log(this.errors[groupName]); + if (typeof err.response.data === "undefined" && typeof err.response.data === "undefined") return; + this.errors[groupName] = err.response.data; }, getUploadUrl(file) { @@ -97,6 +98,7 @@ let methods = { attachNewLink(file) { file.uploaded_to = this.pageId; + this.errors.link = {}; this.$http.post(window.baseUrl('/attachments/link'), file).then(resp => { this.files.push(resp.data); this.file = this.newFile(); From 6e7adcc095432948cab8e5de5bb17c2ad6724c12 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 11:55:23 +0100 Subject: [PATCH 24/35] Embedded SVG icons in css/js files Allows removal of hacky /icon endpoint solution. Fixes PDF exports with WKHTML and allows the icon to show in HTML exports. Fixes #796 --- app/Http/Controllers/HomeController.php | 21 ------------------- resources/assets/icons/warning.svg | 2 +- .../assets/js/components/wysiwyg-editor.js | 2 +- resources/assets/sass/_blocks.scss | 8 +++---- routes/web.php | 1 - 5 files changed, 6 insertions(+), 28 deletions(-) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 02b4789c2..2077f6888 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -108,27 +108,6 @@ class HomeController extends Controller ]); } - /** - * Get an icon via image request. - * Can provide a 'color' parameter with hex value to color the icon. - * @param $iconName - * @param Request $request - * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response - */ - public function getIcon($iconName, Request $request) - { - $attrs = []; - if ($request->filled('color')) { - $attrs['fill'] = '#' . $request->get('color'); - } - - $icon = icon($iconName, $attrs); - return response($icon, 200, [ - 'Content-Type' => 'image/svg+xml', - 'Cache-Control' => 'max-age=3600', - ]); - } - /** * Get custom head HTML, Used in ajax calls to show in editor. * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View diff --git a/resources/assets/icons/warning.svg b/resources/assets/icons/warning.svg index dc1aefc25..b1d1ad02c 100644 --- a/resources/assets/icons/warning.svg +++ b/resources/assets/icons/warning.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/resources/assets/js/components/wysiwyg-editor.js b/resources/assets/js/components/wysiwyg-editor.js index 56aa294fa..701a1fec6 100644 --- a/resources/assets/js/components/wysiwyg-editor.js +++ b/resources/assets/js/components/wysiwyg-editor.js @@ -292,7 +292,7 @@ function drawIoPlugin() { editor.addButton('drawio', { tooltip: 'Drawing', - image: window.baseUrl('/icon/drawing.svg?color=000000'), + image: `data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMwMDAwMDAiICB4bWxucz0iaHR0cDovL3d3 dy53My5vcmcvMjAwMC9zdmciPgogICAgPHBhdGggZD0iTTIzIDdWMWgtNnYySDdWMUgxdjZoMnYx MEgxdjZoNnYtMmgxMHYyaDZ2LTZoLTJWN2gyek0zIDNoMnYySDNWM3ptMiAxOEgzdi0yaDJ2Mnpt MTItMkg3di0ySDVWN2gyVjVoMTB2MmgydjEwaC0ydjJ6bTQgMmgtMnYtMmgydjJ6TTE5IDVWM2gy djJoLTJ6bS01LjI3IDloLTMuNDlsLS43MyAySDcuODlsMy40LTloMS40bDMuNDEgOWgtMS42M2wt Ljc0LTJ6bS0zLjA0LTEuMjZoMi42MUwxMiA4LjkxbC0xLjMxIDMuODN6Ii8+CiAgICA8cGF0aCBk PSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+Cjwvc3ZnPg==`, cmd: 'drawio' }); diff --git a/resources/assets/sass/_blocks.scss b/resources/assets/sass/_blocks.scss index b7a8dcc03..c0f02ed7d 100644 --- a/resources/assets/sass/_blocks.scss +++ b/resources/assets/sass/_blocks.scss @@ -138,7 +138,7 @@ display: block; position: relative; &:before { - background-image: url("/icon/info-filled.svg?color=015380"); + background-image: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMwMTUzODAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTEgMTVoLTJ2LTZoMnY2em0wLThoLTJWN2gydjJ6Ii8+PC9zdmc+'); background-repeat: no-repeat; content: ''; width: 1.2em; @@ -157,7 +157,7 @@ color: darken($positive, 16%); } &.success:before { - background-image: url("/icon/check-circle.svg?color=376c39"); + background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiMzNzZjMzkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bS0yIDE1bC01LTUgMS40MS0xLjQxTDEwIDE0LjE3bDcuNTktNy41OUwxOSA4bC05IDl6Ii8+PC9zdmc+"); } &.danger { border-left-color: $negative; @@ -165,7 +165,7 @@ color: darken($negative, 20%); } &.danger:before { - background-image: url("/icon/danger.svg?color=b91818"); + background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiOTE4MTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0xNS43MyAzSDguMjdMMyA4LjI3djcuNDZMOC4yNyAyMWg3LjQ2TDIxIDE1LjczVjguMjdMMTUuNzMgM3pNMTIgMTcuM2MtLjcyIDAtMS4zLS41OC0xLjMtMS4zIDAtLjcyLjU4LTEuMyAxLjMtMS4zLjcyIDAgMS4zLjU4IDEuMyAxLjMgMCAuNzItLjU4IDEuMy0xLjMgMS4zem0xLTQuM2gtMlY3aDJ2NnoiLz4gICAgPHBhdGggZD0iTTAgMGgyNHYyNEgweiIgZmlsbD0ibm9uZSIvPjwvc3ZnPg=="); } &.info { border-left-color: $info; @@ -178,7 +178,7 @@ color: darken($warning, 16%); } &.warning:before { - background-image: url("/icon/warning.svg?color=b6531c"); + background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiNjUzMWMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEgMjFoMjJMMTIgMiAxIDIxem0xMi0zaC0ydi0yaDJ2MnptMC00aC0ydi00aDJ2NHoiLz48L3N2Zz4="); } } diff --git a/routes/web.php b/routes/web.php index 794edfd01..a857bce6c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,7 +1,6 @@ Date: Sun, 20 May 2018 16:40:30 +0100 Subject: [PATCH 25/35] Reverted work on revisions Improved linkage of drawings and image manager. Updated image updates to create new versions. --- app/Http/Controllers/ImageController.php | 39 +--------- app/Image.php | 19 ----- app/ImageRevision.php | 26 ------- app/Repos/ImageRepo.php | 13 +--- app/Services/ImageService.php | 77 ------------------- ...13_090521_create_image_revisions_table.php | 38 --------- .../assets/js/components/wysiwyg-editor.js | 42 ++++++---- resources/assets/js/vues/image-manager.js | 19 +---- .../views/components/image-manager.blade.php | 35 +-------- routes/web.php | 2 - 10 files changed, 33 insertions(+), 277 deletions(-) delete mode 100644 app/ImageRevision.php delete mode 100644 database/migrations/2018_05_13_090521_create_image_revisions_table.php diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php index bf7f3bb82..eb92ae9a8 100644 --- a/app/Http/Controllers/ImageController.php +++ b/app/Http/Controllers/ImageController.php @@ -164,32 +164,6 @@ class ImageController extends Controller return response()->json($image); } - /** - * Replace the data content of a drawing. - * @param string $id - * @param Request $request - * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response - */ - public function updateDrawing(string $id, Request $request) - { - $this->validate($request, [ - 'image' => 'required|string' - ]); - $this->checkPermission('image-create-all'); - - $imageBase64Data = $request->get('image'); - $image = $this->imageRepo->getById($id); - $this->checkOwnablePermission('image-update', $image); - - try { - $image = $this->imageRepo->updateDrawing($image, $imageBase64Data); - } catch (ImageUploadException $e) { - return response($e->getMessage(), 500); - } - - return response()->json($image); - } - /** * Get the content of an image based64 encoded. * @param $id @@ -257,22 +231,11 @@ class ImageController extends Controller return response()->json($pageSearch); } - /** - * Get the revisions for an image. - * @param $id - * @return \Illuminate\Http\JsonResponse - */ - public function getRevisions($id) - { - $image = $this->imageRepo->getById($id); - $revisions = $image->revisions()->orderBy('id', 'desc')->get(); - return response()->json($revisions); - } - /** * Deletes an image and all thumbnail/image files * @param int $id * @return \Illuminate\Http\JsonResponse + * @throws \Exception */ public function destroy($id) { diff --git a/app/Image.php b/app/Image.php index ac94d9bf0..412beea90 100644 --- a/app/Image.php +++ b/app/Image.php @@ -20,23 +20,4 @@ class Image extends Ownable return Images::getThumbnail($this, $width, $height, $keepRatio); } - /** - * Get the revisions for this image. - * @return \Illuminate\Database\Eloquent\Relations\HasMany - */ - public function revisions() - { - return $this->hasMany(ImageRevision::class); - } - - /** - * Get the count of revisions made to this image. - * Based off numbers on revisions rather than raw count of attached revisions - * as they may be cleared up or revisions deleted at some point. - * @return int - */ - public function revisionCount() - { - return intval($this->revisions()->max('revision')); - } } diff --git a/app/ImageRevision.php b/app/ImageRevision.php deleted file mode 100644 index fde232867..000000000 --- a/app/ImageRevision.php +++ /dev/null @@ -1,26 +0,0 @@ -belongsTo(User::class, 'created_by'); - } - - /** - * Get the image that this is a revision of. - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function image() - { - return $this->belongsTo(Image::class); - } -} diff --git a/app/Repos/ImageRepo.php b/app/Repos/ImageRepo.php index 384ea5454..4ccd719ad 100644 --- a/app/Repos/ImageRepo.php +++ b/app/Repos/ImageRepo.php @@ -153,17 +153,6 @@ class ImageRepo return $image; } - /** - * Replace the image content of a drawing. - * @param Image $image - * @param string $base64Uri - * @return Image - * @throws \BookStack\Exceptions\ImageUploadException - */ - public function updateDrawing(Image $image, string $base64Uri) - { - return $this->imageService->updateImageFromBase64Uri($image, $base64Uri); - } /** * Update the details of an image via an array of properties. @@ -251,7 +240,7 @@ class ImageRepo */ public function isValidType($type) { - $validTypes = ['drawing', 'gallery', 'cover', 'system', 'user']; + $validTypes = ['gallery', 'cover', 'system', 'user']; return in_array($type, $validTypes); } } diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index e83c1860b..736b30705 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -2,7 +2,6 @@ use BookStack\Exceptions\ImageUploadException; use BookStack\Image; -use BookStack\ImageRevision; use BookStack\User; use Exception; use Intervention\Image\Exception\NotSupportedException; @@ -82,22 +81,6 @@ class ImageService extends UploadService return $this->saveNew($name, $data, $type, $uploadedTo); } - /** - * @param Image $image - * @param string $base64Uri - * @return Image - * @throws ImageUploadException - */ - public function updateImageFromBase64Uri(Image $image, string $base64Uri) - { - $splitData = explode(';base64,', $base64Uri); - if (count($splitData) < 2) { - throw new ImageUploadException("Invalid base64 image data provided"); - } - $data = base64_decode($splitData[1]); - return $this->update($image, $data); - } - /** * Gets an image from url and saves it to the database. * @param $url @@ -168,59 +151,6 @@ class ImageService extends UploadService return $image; } - /** - * Update the content of an existing image. - * Uploaded the new image content and creates a revision for the old image content. - * @param Image $image - * @param $imageData - * @return Image - * @throws ImageUploadException - */ - private function update(Image $image, $imageData) - { - // Save image revision if not previously exists to ensure we always have - // a reference to the image files being uploaded. - if ($image->revisions()->count() === 0) { - $this->saveImageRevision($image); - } - - $pathInfo = pathinfo($image->path); - $revisionCount = $image->revisionCount() + 1; - $newFileName = preg_replace('/^(.+?)(-v\d+)?$/', '$1-v' . $revisionCount, $pathInfo['filename']); - - $image->path = str_replace_last($pathInfo['filename'], $newFileName, $image->path); - $image->url = $this->getPublicUrl($image->path); - $image->updated_by = user()->id; - - $storage = $this->getStorage(); - - try { - $storage->put($image->path, $imageData); - $storage->setVisibility($image->path, 'public'); - $image->save(); - $this->saveImageRevision($image); - } catch (Exception $e) { - throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $image->path])); - } - return $image; - } - - /** - * Save a new revision for an image. - * @param Image $image - * @return ImageRevision - */ - protected function saveImageRevision(Image $image) - { - $revision = new ImageRevision(); - $revision->image_id = $image->id; - $revision->path = $image->path; - $revision->url = $image->url; - $revision->created_by = user()->id; - $revision->revision = $image->revisionCount() + 1; - $revision->save(); - return $revision; - } /** * Checks if the image is a gif. Returns true if it is, else false. @@ -309,13 +239,6 @@ class ImageService extends UploadService */ public function destroy(Image $image) { - // Destroy image revisions - foreach ($image->revisions as $revision) { - $this->destroyImagesFromPath($revision->path); - $revision->delete(); - } - - // Destroy main image $this->destroyImagesFromPath($image->path); $image->delete(); } diff --git a/database/migrations/2018_05_13_090521_create_image_revisions_table.php b/database/migrations/2018_05_13_090521_create_image_revisions_table.php deleted file mode 100644 index d3032258f..000000000 --- a/database/migrations/2018_05_13_090521_create_image_revisions_table.php +++ /dev/null @@ -1,38 +0,0 @@ -increments('id'); - $table->integer('image_id'); - $table->integer('revision'); - $table->string('path'); - $table->string('url'); - $table->integer('created_by'); - - $table->index('image_id'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::dropIfExists('image_revisions'); - } -} diff --git a/resources/assets/js/components/wysiwyg-editor.js b/resources/assets/js/components/wysiwyg-editor.js index 03d73d2b5..764b0a929 100644 --- a/resources/assets/js/components/wysiwyg-editor.js +++ b/resources/assets/js/components/wysiwyg-editor.js @@ -229,16 +229,18 @@ function drawIoPlugin() { } function showDrawingManager(mceEditor, selectedNode = null) { - // TODO - Handle how image manager links in. + pageEditor = mceEditor; + currentNode = selectedNode; // Show image manager window.ImageManager.show(function (image) { - - - // // Replace the actively selected content with the linked image - // let html = ``; - // html += `${image.name}`; - // html += ''; - // win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html); + if (selectedNode) { + let imgElem = selectedNode.querySelector('img'); + pageEditor.dom.setAttrib(imgElem, 'src', image.url); + pageEditor.dom.setAttrib(selectedNode, 'drawio-diagram', image.id); + } else { + let imgHTML = `
`; + pageEditor.insertContent(imgHTML); + } }, 'drawio'); } @@ -260,9 +262,9 @@ function drawIoPlugin() { if (currentNode) { DrawIO.close(); let imgElem = currentNode.querySelector('img'); - let drawingId = currentNode.getAttribute('drawio-diagram'); - window.$http.put(window.baseUrl(`/images/drawing/upload/${drawingId}`), data).then(resp => { - pageEditor.dom.setAttrib(imgElem, 'src', `${resp.data.url}?updated=${Date.now()}`); + window.$http.post(window.baseUrl(`/images/drawing/upload`), data).then(resp => { + pageEditor.dom.setAttrib(imgElem, 'src', resp.data.url); + pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', resp.data.id); }).catch(err => { window.$events.emit('error', trans('errors.image_upload_error')); console.log(err); @@ -300,17 +302,23 @@ function drawIoPlugin() { editor.addCommand('drawio', () => { let selectedNode = editor.selection.getNode(); - if (isDrawing(selectedNode)) { - showDrawingManager(editor, selectedNode); - } else { - showDrawingEditor(editor); - } + showDrawingEditor(editor, isDrawing(selectedNode) ? selectedNode : null); }); editor.addButton('drawio', { + type: 'splitbutton', tooltip: 'Drawing', image: window.baseUrl('/icon/drawing.svg?color=000000'), - cmd: 'drawio' + cmd: 'drawio', + menu: [ + { + text: 'Drawing Manager', + onclick() { + let selectedNode = editor.selection.getNode(); + showDrawingManager(editor, isDrawing(selectedNode) ? selectedNode : null); + } + } + ] }); editor.on('dblclick', event => { diff --git a/resources/assets/js/vues/image-manager.js b/resources/assets/js/vues/image-manager.js index 2a11a6ff2..f355107c0 100644 --- a/resources/assets/js/vues/image-manager.js +++ b/resources/assets/js/vues/image-manager.js @@ -24,9 +24,6 @@ const data = { searching: false, searchTerm: '', - revisions: [], - selectedRevision: null, - imageUpdateSuccess: false, imageDeleteSuccess: false, deleteConfirm: false, @@ -50,9 +47,11 @@ const methods = { }, hide() { + if (this.$refs.dropzone) { + this.$refs.dropzone.onClose(); + } this.showing = false; this.selectedImage = false; - this.$refs.dropzone.onClose(); this.$el.children[0].components.overlay.hide(); }, @@ -113,8 +112,6 @@ const methods = { let currentTime = Date.now(); let timeDiff = currentTime - previousClickTime; let isDblClick = timeDiff < dblClickTime && image.id === previousClickImage; - this.revisions = []; - this.selectedRevision = null if (isDblClick) { this.callbackAndHide(image); @@ -122,11 +119,6 @@ const methods = { this.selectedImage = image; this.deleteConfirm = false; this.dependantPages = false; - if (this.imageType === 'drawio') { - this.$http.get(window.baseUrl(`/images/revisions/${image.id}`)).then(resp => { - this.revisions = resp.data; - }) - } } previousClickTime = currentTime; @@ -182,11 +174,6 @@ const methods = { this.images.unshift(event.data); this.$events.emit('success', trans('components.image_upload_success')); }, - - selectRevision(revision) { - let rev = (this.selectedRevision === revision) ? null : revision; - this.selectedRevision = rev; - } }; const computed = { diff --git a/resources/views/components/image-manager.blade.php b/resources/views/components/image-manager.blade.php index 92a69c05d..eca35b8aa 100644 --- a/resources/views/components/image-manager.blade.php +++ b/resources/views/components/image-manager.blade.php @@ -41,20 +41,13 @@
- +
-
- - - -
- -
+
-
+
@@ -91,28 +84,6 @@
- {{--Revisions View--}} -
-
-
Revisions
- - - - - -
- - (Current) - - -
- -
-

- No revisions found -

-
-
diff --git a/routes/web.php b/routes/web.php index 254da9cd4..7cad0c585 100644 --- a/routes/web.php +++ b/routes/web.php @@ -96,9 +96,7 @@ Route::group(['middleware' => 'auth'], function () { Route::get('/base64/{id}', 'ImageController@getBase64Image'); Route::put('/update/{imageId}', 'ImageController@update'); Route::post('/drawing/upload', 'ImageController@uploadDrawing'); - Route::put('/drawing/upload/{id}', 'ImageController@updateDrawing'); Route::get('/usage/{id}', 'ImageController@usage'); - Route::get('/revisions/{id}', 'ImageController@getRevisions'); Route::post('/{type}/upload', 'ImageController@uploadByType'); Route::get('/{type}/all', 'ImageController@getAllByType'); Route::get('/{type}/all/{page}', 'ImageController@getAllByType'); From 8c4c8cd95b97f4a1a0d4a831673d50982c53e305 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 16:47:53 +0100 Subject: [PATCH 26/35] Updated secure-images option to not effect image name Instead only the image path is altered. Also fixed image manger mode not changing on button press. --- app/Services/ImageService.php | 8 ++++---- resources/assets/js/components/wysiwyg-editor.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index 736b30705..c087c1e03 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -114,16 +114,16 @@ class ImageService extends UploadService $secureUploads = setting('app-secure-images'); $imageName = str_replace(' ', '-', $imageName); - if ($secureUploads) { - $imageName = str_random(16) . '-' . $imageName; - } - $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/'; while ($storage->exists($imagePath . $imageName)) { $imageName = str_random(3) . $imageName; } + $fullPath = $imagePath . $imageName; + if ($secureUploads) { + $fullPath = $imagePath . str_random(16) . '-' . $imageName; + } try { $storage->put($fullPath, $imageData); diff --git a/resources/assets/js/components/wysiwyg-editor.js b/resources/assets/js/components/wysiwyg-editor.js index 764b0a929..72263e2f2 100644 --- a/resources/assets/js/components/wysiwyg-editor.js +++ b/resources/assets/js/components/wysiwyg-editor.js @@ -547,7 +547,7 @@ class WysiwygEditor { html += `${image.name}`; html += '
'; editor.execCommand('mceInsertContent', false, html); - }); + }, 'gallery'); } }); From 61c9324229c073f9a60d1b7992b85c499ef0878c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 17:12:44 +0100 Subject: [PATCH 27/35] Removed old image versions test --- tests/ImageTest.php | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/tests/ImageTest.php b/tests/ImageTest.php index fd1aa010e..810fb3edf 100644 --- a/tests/ImageTest.php +++ b/tests/ImageTest.php @@ -210,47 +210,6 @@ class ImageTest extends TestCase $this->assertTrue($testImageData === $uploadedImageData, "Uploaded image file data does not match our test image as expected"); } - public function test_drawing_updating() - { - $page = Page::first(); - $editor = $this->getEditor(); - $this->actingAs($editor); - - $this->postJson('images/drawing/upload', [ - 'uploaded_to' => $page->id, - 'image' => 'image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEcDQ4S1RUeKwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAFElEQVQI12NctNWSAQkwMaACUvkAfCkBmjyhGl4AAAAASUVORK5CYII=' - ]); - - $image = Image::where('type', '=', 'drawio')->first(); - - $replace = $this->putJson("images/drawing/upload/{$image->id}", [ - 'image' => 'image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEcDCo5iYNs+gAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAFElEQVQI12O0jN/KgASYGFABqXwAZtoBV6Sl3hIAAAAASUVORK5CYII=' - ]); - - $replace->assertStatus(200); - $replace->assertJson([ - 'type' => 'drawio', - 'uploaded_to' => $page->id, - 'created_by' => $editor->id, - 'updated_by' => $editor->id, - ]); - - // Check a revision has been created - $this->assertDatabaseHas('image_revisions', [ - 'image_id' => $image->id, - 'revision' => 2, - 'created_by' => $editor->id, - ]); - - $image = Image::find($image->id); - - $this->assertTrue(file_exists(public_path($image->path)), 'Uploaded image not found at path: '. public_path($image->path)); - - $testImageData = file_get_contents($this->getTestImageFilePath()); - $uploadedImageData = file_get_contents(public_path($image->path)); - $this->assertTrue($testImageData === $uploadedImageData, "Uploaded image file data does not match our test image as expected"); - } - public function test_user_images_deleted_on_user_deletion() { $editor = $this->getEditor(); From c31e6a03ce3d45df2d79399820bbbfc7df7fe2b8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 18:16:01 +0100 Subject: [PATCH 28/35] Added command to clean-up old images, Unfinished --- app/Console/Commands/CleanupImages.php | 68 ++++++++++++++++++++++++++ app/Providers/CustomFacadeProvider.php | 2 + app/Services/ImageService.php | 51 +++++++++++++++++-- 3 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 app/Console/Commands/CleanupImages.php diff --git a/app/Console/Commands/CleanupImages.php b/app/Console/Commands/CleanupImages.php new file mode 100644 index 000000000..310a7bb24 --- /dev/null +++ b/app/Console/Commands/CleanupImages.php @@ -0,0 +1,68 @@ +imageService = $imageService; + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $checkRevisions = $this->option('all') ? false : true; + $dryRun = $this->option('force') ? false : true; + + if (!$dryRun) { + $proceed = $this->confirm('This operation is destructive and is not guaranteed to be fully accurate. Ensure you have a backup of your images. Are you sure you want to proceed?'); + if (!$proceed) { + return; + } + } + + $deleteCount = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun); + + if ($dryRun) { + $this->comment('Dry run, No images have been deleted'); + $this->comment($deleteCount . ' images found that would have been deleted'); + $this->comment('Run with -f or --force to perform deletions'); + return; + } + + $this->comment($deleteCount . ' images deleted'); + } +} diff --git a/app/Providers/CustomFacadeProvider.php b/app/Providers/CustomFacadeProvider.php index a97512e8c..c81a5529d 100644 --- a/app/Providers/CustomFacadeProvider.php +++ b/app/Providers/CustomFacadeProvider.php @@ -3,6 +3,7 @@ namespace BookStack\Providers; use BookStack\Activity; +use BookStack\Image; use BookStack\Services\ImageService; use BookStack\Services\PermissionService; use BookStack\Services\ViewService; @@ -57,6 +58,7 @@ class CustomFacadeProvider extends ServiceProvider $this->app->bind('images', function () { return new ImageService( + $this->app->make(Image::class), $this->app->make(ImageManager::class), $this->app->make(Factory::class), $this->app->make(Repository::class) diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index c087c1e03..ce108e172 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -3,6 +3,7 @@ use BookStack\Exceptions\ImageUploadException; use BookStack\Image; use BookStack\User; +use DB; use Exception; use Intervention\Image\Exception\NotSupportedException; use Intervention\Image\ImageManager; @@ -16,15 +17,18 @@ class ImageService extends UploadService protected $imageTool; protected $cache; protected $storageUrl; + protected $image; /** * ImageService constructor. - * @param $imageTool - * @param $fileSystem - * @param $cache + * @param Image $image + * @param ImageManager $imageTool + * @param FileSystem $fileSystem + * @param Cache $cache */ - public function __construct(ImageManager $imageTool, FileSystem $fileSystem, Cache $cache) + public function __construct(Image $image, ImageManager $imageTool, FileSystem $fileSystem, Cache $cache) { + $this->image = $image; $this->imageTool = $imageTool; $this->cache = $cache; parent::__construct($fileSystem); @@ -146,7 +150,7 @@ class ImageService extends UploadService $imageDetails['updated_by'] = $userId; } - $image = (new Image()); + $image = $this->image->newInstance(); $image->forceFill($imageDetails)->save(); return $image; } @@ -294,6 +298,43 @@ class ImageService extends UploadService return $image; } + + /** + * Delete gallery and drawings that are not within HTML content of pages or page revisions. + * @param bool $checkRevisions + * @param array $types + * @param bool $dryRun + * @return int + */ + public function deleteUnusedImages($checkRevisions = true, $types = ['gallery', 'drawio'], $dryRun = true) + { + // TODO - The checking here isn't really good enough. + // Thumbnails would also need to be searched for as we can't guarantee the full image will be in the content. + // Would also be best to simplify the string to not include the base host? + $types = array_intersect($types, ['gallery', 'drawio']); + $deleteCount = 0; + $this->image->newQuery()->whereIn('type', $types) + ->chunk(1000, function($images) use ($types, $checkRevisions, &$deleteCount, $dryRun) { + foreach ($images as $image) { + $inPage = DB::table('pages') + ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + $inRevision = false; + if ($checkRevisions) { + $inRevision = DB::table('page_revisions') + ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + } + + if (!$inPage && !$inRevision) { + $deleteCount++; + if (!$dryRun) { + $this->destroy($image); + } + } + } + }); + return $deleteCount; + } + /** * Convert a image URI to a Base64 encoded string. * Attempts to find locally via set storage method first. From 1df0bcaf852511add1a6e77c568af574e4215d27 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 27 May 2018 14:33:50 +0100 Subject: [PATCH 29/35] Made image cleanup safer Also fixed drawing update in markdown editor. Added shortcut for MD editor to view drawing manager. --- app/Console/Commands/CleanupImages.php | 19 ++++++++++-- app/Services/ImageService.php | 23 ++++++++------- .../assets/js/components/markdown-editor.js | 29 ++++++++++++++----- resources/assets/js/vues/image-manager.js | 1 + 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/app/Console/Commands/CleanupImages.php b/app/Console/Commands/CleanupImages.php index 310a7bb24..5eadf2751 100644 --- a/app/Console/Commands/CleanupImages.php +++ b/app/Console/Commands/CleanupImages.php @@ -4,6 +4,7 @@ namespace BookStack\Console\Commands; use BookStack\Services\ImageService; use Illuminate\Console\Command; +use Symfony\Component\Console\Output\OutputInterface; class CleanupImages extends Command { @@ -48,21 +49,35 @@ class CleanupImages extends Command $dryRun = $this->option('force') ? false : true; if (!$dryRun) { - $proceed = $this->confirm('This operation is destructive and is not guaranteed to be fully accurate. Ensure you have a backup of your images. Are you sure you want to proceed?'); + $proceed = $this->confirm("This operation is destructive and is not guaranteed to be fully accurate.\nEnsure you have a backup of your images.\nAre you sure you want to proceed?"); if (!$proceed) { return; } } - $deleteCount = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun); + $deleted = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun); + $deleteCount = count($deleted); if ($dryRun) { $this->comment('Dry run, No images have been deleted'); $this->comment($deleteCount . ' images found that would have been deleted'); + $this->showDeletedImages($deleted); $this->comment('Run with -f or --force to perform deletions'); return; } + $this->showDeletedImages($deleted); $this->comment($deleteCount . ' images deleted'); } + + protected function showDeletedImages($paths) + { + if ($this->getOutput()->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) return; + if (count($paths) > 0) { + $this->line('Images to delete:'); + } + foreach ($paths as $path) { + $this->line($path); + } + } } diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index ce108e172..d1193ab4f 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -301,38 +301,41 @@ class ImageService extends UploadService /** * Delete gallery and drawings that are not within HTML content of pages or page revisions. + * Checks based off of only the image name. + * Could be much improved to be more specific but kept it generic for now to be safe. + * + * Returns the path of the images that would be/have been deleted. * @param bool $checkRevisions * @param array $types * @param bool $dryRun - * @return int + * @return array */ public function deleteUnusedImages($checkRevisions = true, $types = ['gallery', 'drawio'], $dryRun = true) { - // TODO - The checking here isn't really good enough. - // Thumbnails would also need to be searched for as we can't guarantee the full image will be in the content. - // Would also be best to simplify the string to not include the base host? $types = array_intersect($types, ['gallery', 'drawio']); - $deleteCount = 0; + $deletedPaths = []; + $this->image->newQuery()->whereIn('type', $types) - ->chunk(1000, function($images) use ($types, $checkRevisions, &$deleteCount, $dryRun) { + ->chunk(1000, function($images) use ($types, $checkRevisions, &$deletedPaths, $dryRun) { foreach ($images as $image) { + $searchQuery = '%' . basename($image->path) . '%'; $inPage = DB::table('pages') - ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + ->where('html', 'like', $searchQuery)->count() > 0; $inRevision = false; if ($checkRevisions) { $inRevision = DB::table('page_revisions') - ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + ->where('html', 'like', $searchQuery)->count() > 0; } if (!$inPage && !$inRevision) { - $deleteCount++; + $deletedPaths[] = $image->path; if (!$dryRun) { $this->destroy($image); } } } }); - return $deleteCount; + return $deletedPaths; } /** diff --git a/resources/assets/js/components/markdown-editor.js b/resources/assets/js/components/markdown-editor.js index 46c54408b..06426bf34 100644 --- a/resources/assets/js/components/markdown-editor.js +++ b/resources/assets/js/components/markdown-editor.js @@ -52,6 +52,10 @@ class MarkdownEditor { let action = button.getAttribute('data-action'); if (action === 'insertImage') this.actionInsertImage(); if (action === 'insertLink') this.actionShowLinkSelector(); + if (action === 'insertDrawing' && event.ctrlKey) { + this.actionShowImageManager(); + return; + } if (action === 'insertDrawing') this.actionStartDrawing(); }); @@ -293,7 +297,14 @@ class MarkdownEditor { this.cm.focus(); this.cm.replaceSelection(newText); this.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length); - }); + }, 'gallery'); + } + + actionShowImageManager() { + let cursorPos = this.cm.getCursor('from'); + window.ImageManager.show(image => { + this.insertDrawing(image, cursorPos); + }, 'drawio'); } // Show the popup link selector and insert a link when finished @@ -324,10 +335,7 @@ class MarkdownEditor { }; window.$http.post(window.baseUrl('/images/drawing/upload'), data).then(resp => { - let newText = `
`; - this.cm.focus(); - this.cm.replaceSelection(newText); - this.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length); + this.insertDrawing(resp.data, cursorPos); DrawIO.close(); }).catch(err => { window.$events.emit('error', trans('errors.image_upload_error')); @@ -336,6 +344,13 @@ class MarkdownEditor { }); } + insertDrawing(image, originalCursor) { + let newText = `
`; + this.cm.focus(); + this.cm.replaceSelection(newText); + this.cm.setCursor(originalCursor.line, originalCursor.ch + newText.length); + } + // Show draw.io if enabled and handle save. actionEditDrawing(imgContainer) { if (document.querySelector('[drawio-enabled]').getAttribute('drawio-enabled') !== 'true') return; @@ -353,8 +368,8 @@ class MarkdownEditor { uploaded_to: Number(document.getElementById('page-editor').getAttribute('page-id')) }; - window.$http.put(window.baseUrl(`/images/drawing/upload/${drawingId}`), data).then(resp => { - let newText = `
`; + window.$http.post(window.baseUrl(`/images/drawing/upload`), data).then(resp => { + let newText = `
`; let newContent = this.cm.getValue().split('\n').map(line => { if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) { return newText; diff --git a/resources/assets/js/vues/image-manager.js b/resources/assets/js/vues/image-manager.js index f355107c0..16c8ef9cf 100644 --- a/resources/assets/js/vues/image-manager.js +++ b/resources/assets/js/vues/image-manager.js @@ -101,6 +101,7 @@ const methods = { }, cancelSearch() { + if (!this.searching) return; this.searching = false; this.searchTerm = ''; this.images = preSearchImages; From 2bd6ba98955bea62be0bedd09cf3a6d20f42c50a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 27 May 2018 19:40:07 +0100 Subject: [PATCH 30/35] Added maintenance view with image-cleanup --- app/Console/Commands/CleanupImages.php | 2 +- app/Http/Controllers/SettingController.php | 47 ++++++++++++++- app/Services/ImageService.php | 4 +- resources/assets/icons/spanner.svg | 4 ++ resources/lang/en/settings.php | 13 +++++ .../views/settings/maintenance.blade.php | 49 ++++++++++++++++ resources/views/settings/navbar.blade.php | 1 + routes/web.php | 4 ++ tests/ImageTest.php | 57 +++++++++++++++++++ 9 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 resources/assets/icons/spanner.svg create mode 100644 resources/views/settings/maintenance.blade.php diff --git a/app/Console/Commands/CleanupImages.php b/app/Console/Commands/CleanupImages.php index 5eadf2751..e05508d5e 100644 --- a/app/Console/Commands/CleanupImages.php +++ b/app/Console/Commands/CleanupImages.php @@ -55,7 +55,7 @@ class CleanupImages extends Command } } - $deleted = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun); + $deleted = $this->imageService->deleteUnusedImages($checkRevisions, $dryRun); $deleteCount = count($deleted); if ($dryRun) { diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index e0e351458..d9d66042e 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -1,5 +1,6 @@ checkPermission('settings-manage'); - $this->setPageTitle('Settings'); + $this->setPageTitle(trans('settings.settings')); // Get application version $version = trim(file_get_contents(base_path('version'))); @@ -43,4 +44,48 @@ class SettingController extends Controller session()->flash('success', trans('settings.settings_save_success')); return redirect('/settings'); } + + /** + * Show the page for application maintenance. + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showMaintenance() + { + $this->checkPermission('settings-manage'); + $this->setPageTitle(trans('settings.maint')); + + // Get application version + $version = trim(file_get_contents(base_path('version'))); + + return view('settings/maintenance', ['version' => $version]); + } + + /** + * Action to clean-up images in the system. + * @param Request $request + * @param ImageService $imageService + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function cleanupImages(Request $request, ImageService $imageService) + { + $this->checkPermission('settings-manage'); + + $checkRevisions = !($request->get('ignore_revisions', 'false') === 'true'); + $dryRun = !($request->has('confirm')); + + $imagesToDelete = $imageService->deleteUnusedImages($checkRevisions, $dryRun); + $deleteCount = count($imagesToDelete); + if ($deleteCount === 0) { + session()->flash('warning', trans('settings.maint_image_cleanup_nothing_found')); + return redirect('/settings/maintenance')->withInput(); + } + + if ($dryRun) { + session()->flash('cleanup-images-warning', trans('settings.maint_image_cleanup_warning', ['count' => $deleteCount])); + } else { + session()->flash('success', trans('settings.maint_image_cleanup_success', ['count' => $deleteCount])); + } + + return redirect('/settings/maintenance#image-cleanup')->withInput(); + } } diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index d1193ab4f..73a677ac2 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -306,11 +306,11 @@ class ImageService extends UploadService * * Returns the path of the images that would be/have been deleted. * @param bool $checkRevisions - * @param array $types * @param bool $dryRun + * @param array $types * @return array */ - public function deleteUnusedImages($checkRevisions = true, $types = ['gallery', 'drawio'], $dryRun = true) + public function deleteUnusedImages($checkRevisions = true, $dryRun = true, $types = ['gallery', 'drawio']) { $types = array_intersect($types, ['gallery', 'drawio']); $deletedPaths = []; diff --git a/resources/assets/icons/spanner.svg b/resources/assets/icons/spanner.svg new file mode 100644 index 000000000..8ab25a247 --- /dev/null +++ b/resources/assets/icons/spanner.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index de4894280..3a3dbb9a4 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -50,6 +50,19 @@ return [ 'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application.
Note that users will be able to change their email addresses after successful registration.', 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', + /** + * Maintenance settings + */ + + 'maint' => 'Maintenance', + 'maint_image_cleanup' => 'Cleanup Images', + 'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.", + 'maint_image_cleanup_ignore_revisions' => 'Ignore images in revisions', + 'maint_image_cleanup_run' => 'Run Cleanup', + 'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?', + 'maint_image_cleanup_success' => ':count potentially unused images found and deleted!', + 'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!', + /** * Role settings */ diff --git a/resources/views/settings/maintenance.blade.php b/resources/views/settings/maintenance.blade.php new file mode 100644 index 000000000..abf793ade --- /dev/null +++ b/resources/views/settings/maintenance.blade.php @@ -0,0 +1,49 @@ +@extends('simple-layout') + +@section('toolbar') + @include('settings/navbar', ['selected' => 'maintenance']) +@stop + +@section('body') +
+ +
+
+ BookStack @if(strpos($version, 'v') !== 0) version @endif {{ $version }} +
+ + +
+

@icon('images') {{ trans('settings.maint_image_cleanup') }}

+
+
+
+

{{ trans('settings.maint_image_cleanup_desc') }}

+
+
+
+ {!! csrf_field() !!} + +
+ @if(session()->has('cleanup-images-warning')) +

+ {{ session()->get('cleanup-images-warning') }} +

+ + + @else + + @endif +
+ +
+
+
+
+
+ +
+@stop diff --git a/resources/views/settings/navbar.blade.php b/resources/views/settings/navbar.blade.php index 0547b168c..f9db96894 100644 --- a/resources/views/settings/navbar.blade.php +++ b/resources/views/settings/navbar.blade.php @@ -2,6 +2,7 @@