]> BookStack Code Mirror - bookstack/commitdiff
Merge pull request #802 from marcusforsberg/master
authorDan Brown <redacted>
Sun, 20 May 2018 11:05:11 +0000 (12:05 +0100)
committerGitHub <redacted>
Sun, 20 May 2018 11:05:11 +0000 (12:05 +0100)
Updated Swedish translation

79 files changed:
app/Attachment.php
app/Chapter.php
app/Entity.php
app/Exceptions/Handler.php
app/Http/Controllers/AttachmentController.php
app/Http/Controllers/HomeController.php
app/Http/Controllers/PageController.php
app/Http/Controllers/SearchController.php
app/Http/Kernel.php
app/Page.php
app/Repos/EntityRepo.php
app/Services/ExportService.php
app/Services/ImageService.php
app/Services/PermissionService.php
app/Services/SearchService.php
app/Services/ViewService.php
composer.json
composer.lock
config/session.php
database/seeds/DummyContentSeeder.php
database/seeds/LargeContentSeeder.php [new file with mode: 0644]
package-lock.json
package.json
readme.md
resources/assets/icons/star.svg [new file with mode: 0644]
resources/assets/icons/warning.svg
resources/assets/js/components/back-top-top.js
resources/assets/js/components/entity-selector.js
resources/assets/js/components/wysiwyg-editor.js
resources/assets/js/index.js
resources/assets/js/services/code.js
resources/assets/js/vues/attachment-manager.js
resources/assets/js/vues/page-editor.js
resources/assets/sass/_blocks.scss
resources/assets/sass/_forms.scss
resources/assets/sass/_grid.scss
resources/assets/sass/_lists.scss
resources/assets/sass/_text.scss
resources/lang/en/common.php
resources/lang/en/entities.php
resources/lang/en/settings.php
resources/lang/es_AR/common.php
resources/lang/es_AR/components.php
resources/lang/es_AR/entities.php
resources/lang/es_AR/errors.php
resources/views/books/form.blade.php
resources/views/books/grid-item.blade.php
resources/views/books/index.blade.php
resources/views/books/list.blade.php [new file with mode: 0644]
resources/views/books/show.blade.php
resources/views/books/view-toggle.blade.php [new file with mode: 0644]
resources/views/chapters/move.blade.php
resources/views/chapters/show.blade.php
resources/views/common/home-book.blade.php [new file with mode: 0644]
resources/views/common/home-custom.blade.php [new file with mode: 0644]
resources/views/common/home-sidebar.blade.php [new file with mode: 0644]
resources/views/common/home.blade.php [moved from resources/views/home.blade.php with 100% similarity]
resources/views/common/robots.blade.php [moved from resources/views/robots.blade.php with 100% similarity]
resources/views/components/entity-selector.blade.php
resources/views/components/tag-list.blade.php
resources/views/home-custom.blade.php [deleted file]
resources/views/pages/copy.blade.php [new file with mode: 0644]
resources/views/pages/export.blade.php
resources/views/pages/move.blade.php
resources/views/pages/revision.blade.php
resources/views/pages/show.blade.php
resources/views/partials/entity-meta.blade.php
resources/views/settings/index.blade.php
routes/web.php
tests/BrowserKitTest.php
tests/Entity/EntityTest.php
tests/Entity/SortTest.php
tests/HomepageTest.php
tests/ImageTest.php
tests/Permissions/RestrictionsTest.php
tests/Permissions/RolesTest.php
tests/SharedTestHelpers.php [new file with mode: 0644]
tests/TestCase.php
version

index 55344cd7d4d47c26b85d486843ce5d1c6791252e..6749130d9686268dde0ee1299d03d26284a70687 100644 (file)
@@ -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);
     }
 }
index 3726c57f4e3df340c49cce79f29c8624ec4485c3..88b4c134cf9d7b0a99d54058a7f21526acf4f956 100644 (file)
@@ -6,8 +6,6 @@ class Chapter extends Entity
 
     protected $fillable = ['name', 'description', 'priority', 'book_id'];
 
-    protected $with = ['book'];
-
     /**
      * Get the book this chapter is within.
      * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
index 67edec4e089eefe3d4eb69472da891cde409ed81..5d4449f2bd7817e38db5eb0284baa781c7e0858e 100644 (file)
@@ -197,8 +197,8 @@ class Entity extends Ownable
      * @param $path
      * @return string
      */
-    public function getUrl($path)
+    public function getUrl($path = '/')
     {
-        return '/';
+        return $path;
     }
 }
index 4f6e690bc3b10bf9f42b28b79aa803a2a3b2fa4a..0eb62dc31aab20388e8047a39788ee0c3990e4dc 100644 (file)
@@ -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
index ea41278aebc0082ef93054985b28eb4c1cff3db2..54e14bfb6f4cd654c7bf7e9c2353432b373c6e6f 100644 (file)
@@ -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)
     {
index bbe1a86799bc365b107f80c3059fac7a7dafb0ef..2077f6888fbc84e9a57c42836c0958f1d844213a 100644 (file)
@@ -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, [
+        return view('common/' . $view, [
             'activity' => $activity,
             'recents' => $recents,
             'recentlyUpdatedPages' => $recentlyUpdatedPages,
             'draftPages' => $draftPages,
-            'customHomepage' => $customHomepage
+            'customHomepage' => $customHomepage,
+            'books' => $books,
+            'booksViewType' => $booksViewType
         ]);
     }
 
@@ -89,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
@@ -131,7 +129,15 @@ class HomeController extends Controller
             $allowRobots = $sitePublic;
         }
         return response()
-            ->view('robots', ['allowRobots' => $allowRobots])
+            ->view('common/robots', ['allowRobots' => $allowRobots])
             ->header('Content-Type', 'text/plain');
     }
+
+    /**
+     * Show the route for 404 responses.
+     */
+    public function getNotFound()
+    {
+        return response()->view('errors/404', [], 404);
+    }
 }
index 9cc73ae154536abc23d0121bf3e30539ef2eb418..221e21a99e181b37c228c0fe4d1bbc95736556a4 100644 (file)
@@ -585,6 +585,8 @@ class PageController extends Controller
             return redirect()->back();
         }
 
+        $this->checkOwnablePermission('page-create', $parent);
+
         $this->entityRepo->changePageParent($page, $parent);
         Activity::add($page, 'page_move', $page->book->id);
         session()->flash('success', trans('entities.pages_move_success', ['parentName' => $parent->name]));
@@ -592,12 +594,70 @@ class PageController extends Controller
         return redirect($page->getUrl());
     }
 
+    /**
+     * Show the view to copy a page.
+     * @param string $bookSlug
+     * @param string $pageSlug
+     * @return mixed
+     * @throws NotFoundException
+     */
+    public function showCopy($bookSlug, $pageSlug)
+    {
+        $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
+        $this->checkOwnablePermission('page-update', $page);
+        session()->flashInput(['name' => $page->name]);
+        return view('pages/copy', [
+            'book' => $page->book,
+            'page' => $page
+        ]);
+    }
+
+    /**
+     * Create a copy of a page within the requested target destination.
+     * @param string $bookSlug
+     * @param string $pageSlug
+     * @param Request $request
+     * @return mixed
+     * @throws NotFoundException
+     */
+    public function copy($bookSlug, $pageSlug, Request $request)
+    {
+        $page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
+        $this->checkOwnablePermission('page-update', $page);
+
+        $entitySelection = $request->get('entity_selection', null);
+        if ($entitySelection === null || $entitySelection === '') {
+            $parent = $page->chapter ? $page->chapter : $page->book;
+        } else {
+            $stringExploded = explode(':', $entitySelection);
+            $entityType = $stringExploded[0];
+            $entityId = intval($stringExploded[1]);
+
+            try {
+                $parent = $this->entityRepo->getById($entityType, $entityId);
+            } catch (\Exception $e) {
+                session()->flash(trans('entities.selected_book_chapter_not_found'));
+                return redirect()->back();
+            }
+        }
+
+        $this->checkOwnablePermission('page-create', $parent);
+
+        $pageCopy = $this->entityRepo->copyPage($page, $parent, $request->get('name', ''));
+
+        Activity::add($pageCopy, 'page_create', $pageCopy->book->id);
+        session()->flash('success', trans('entities.pages_copy_success'));
+
+        return redirect($pageCopy->getUrl());
+    }
+
     /**
      * Set the permissions for this page.
      * @param string $bookSlug
      * @param string $pageSlug
      * @param Request $request
      * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+     * @throws NotFoundException
      */
     public function restrict($bookSlug, $pageSlug, Request $request)
     {
index ddc92f7055cb97570cfca5ce74b0398ac461d18a..49f9885adb24c1bb438f5b95f6cd20fd0f7937a7 100644 (file)
@@ -89,16 +89,17 @@ class SearchController extends Controller
     {
         $entityTypes = $request->filled('types') ? collect(explode(',', $request->get('types'))) : collect(['page', 'chapter', 'book']);
         $searchTerm =  $request->get('term', false);
+        $permission = $request->get('permission', 'view');
 
         // Search for entities otherwise show most popular
         if ($searchTerm !== false) {
             $searchTerm .= ' {type:'. implode('|', $entityTypes->toArray()) .'}';
-            $entities = $this->searchService->searchEntities($searchTerm)['results'];
+            $entities = $this->searchService->searchEntities($searchTerm, 'all', 1, 20, $permission)['results'];
         } else {
             $entityNames = $entityTypes->map(function ($type) {
                 return 'BookStack\\' . ucfirst($type);
             })->toArray();
-            $entities = $this->viewService->getPopular(20, 0, $entityNames);
+            $entities = $this->viewService->getPopular(20, 0, $entityNames, $permission);
         }
 
         return view('search/entity-ajax-list', ['entities' => $entities]);
index 9d2871bbeb828dd4c64ed63f78a35eb63c8806e5..cd894de95340471f87e73c876cac8e2a4e49a657 100644 (file)
@@ -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',
index e169a1959a98ace6a2f6007bcec2f8feb63744ea..38feb610da9ef73dc0f93668197e0dc680d0b299 100644 (file)
@@ -6,7 +6,6 @@ class Page extends Entity
 
     protected $simpleAttributes = ['name', 'id', 'slug'];
 
-    protected $with = ['book'];
     public $textField = 'text';
 
     /**
index 14f9d8d0eaf8c2d7a56fbed7280bdf3b79e4ec1e..bdd1e37b10ddfd58c699f6d345a2e53acb58850c 100644 (file)
@@ -593,6 +593,30 @@ class EntityRepo
         return $slug;
     }
 
+    /**
+     * Get a new draft page instance.
+     * @param Book $book
+     * @param Chapter|bool $chapter
+     * @return Page
+     */
+    public function getDraftPage(Book $book, $chapter = false)
+    {
+        $page = $this->page->newInstance();
+        $page->name = trans('entities.pages_initial_name');
+        $page->created_by = user()->id;
+        $page->updated_by = user()->id;
+        $page->draft = true;
+
+        if ($chapter) {
+            $page->chapter_id = $chapter->id;
+        }
+
+        $book->pages()->save($page);
+        $page = $this->page->find($page->id);
+        $this->permissionService->buildJointPermissionsForEntity($page);
+        return $page;
+    }
+
     /**
      * Publish a draft page to make it a normal page.
      * Sets the slug and updates the content.
@@ -621,6 +645,43 @@ class EntityRepo
         return $draftPage;
     }
 
+    /**
+     * Create a copy of a page in a new location with a new name.
+     * @param Page $page
+     * @param Entity $newParent
+     * @param string $newName
+     * @return Page
+     */
+    public function copyPage(Page $page, Entity $newParent, $newName = '')
+    {
+        $newBook = $newParent->isA('book') ? $newParent : $newParent->book;
+        $newChapter = $newParent->isA('chapter') ? $newParent : null;
+        $copyPage = $this->getDraftPage($newBook, $newChapter);
+        $pageData = $page->getAttributes();
+
+        // Update name
+        if (!empty($newName)) {
+            $pageData['name'] = $newName;
+        }
+
+        // Copy tags from previous page if set
+        if ($page->tags) {
+            $pageData['tags'] = [];
+            foreach ($page->tags as $tag) {
+                $pageData['tags'][] = ['name' => $tag->name, 'value' => $tag->value];
+            }
+        }
+
+        // Set priority
+        if ($newParent->isA('chapter')) {
+            $pageData['priority'] = $this->getNewChapterPriority($newParent);
+        } else {
+            $pageData['priority'] = $this->getNewBookPriority($newParent);
+        }
+
+        return $this->publishPageDraft($copyPage, $pageData);
+    }
+
     /**
      * Saves a page revision into the system.
      * @param Page $page
@@ -805,30 +866,6 @@ class EntityRepo
         return strip_tags($html);
     }
 
-    /**
-     * Get a new draft page instance.
-     * @param Book $book
-     * @param Chapter|bool $chapter
-     * @return Page
-     */
-    public function getDraftPage(Book $book, $chapter = false)
-    {
-        $page = $this->page->newInstance();
-        $page->name = trans('entities.pages_initial_name');
-        $page->created_by = user()->id;
-        $page->updated_by = user()->id;
-        $page->draft = true;
-
-        if ($chapter) {
-            $page->chapter_id = $chapter->id;
-        }
-
-        $book->pages()->save($page);
-        $page = $this->page->find($page->id);
-        $this->permissionService->buildJointPermissionsForEntity($page);
-        return $page;
-    }
-
     /**
      * Search for image usage within page content.
      * @param $imageString
index ada2261e454455a6a4f81d2e8896bf619caf9210..01e87f1678ebf7a2616b54b21e9b6cfc9d564a15 100644 (file)
@@ -9,14 +9,16 @@ class ExportService
 {
 
     protected $entityRepo;
+    protected $imageService;
 
     /**
      * ExportService constructor.
      * @param $entityRepo
      */
-    public function __construct(EntityRepo $entityRepo)
+    public function __construct(EntityRepo $entityRepo, ImageService $imageService)
     {
         $this->entityRepo = $entityRepo;
+        $this->imageService = $imageService;
     }
 
     /**
@@ -24,6 +26,7 @@ class ExportService
      * Includes required CSS & image content. Images are base64 encoded into the HTML.
      * @param Page $page
      * @return mixed|string
+     * @throws \Throwable
      */
     public function pageToContainedHtml(Page $page)
     {
@@ -38,6 +41,7 @@ class ExportService
      * Convert a chapter to a self-contained HTML file.
      * @param Chapter $chapter
      * @return mixed|string
+     * @throws \Throwable
      */
     public function chapterToContainedHtml(Chapter $chapter)
     {
@@ -56,6 +60,7 @@ class ExportService
      * Convert a book to a self-contained HTML file.
      * @param Book $book
      * @return mixed|string
+     * @throws \Throwable
      */
     public function bookToContainedHtml(Book $book)
     {
@@ -71,6 +76,7 @@ class ExportService
      * Convert a page to a PDF file.
      * @param Page $page
      * @return mixed|string
+     * @throws \Throwable
      */
     public function pageToPdf(Page $page)
     {
@@ -85,6 +91,7 @@ class ExportService
      * Convert a chapter to a PDF file.
      * @param Chapter $chapter
      * @return mixed|string
+     * @throws \Throwable
      */
     public function chapterToPdf(Chapter $chapter)
     {
@@ -103,6 +110,7 @@ class ExportService
      * Convert a book to a PDF file
      * @param Book $book
      * @return string
+     * @throws \Throwable
      */
     public function bookToPdf(Book $book)
     {
@@ -118,6 +126,7 @@ class ExportService
      * Convert normal webpage HTML to a PDF.
      * @param $html
      * @return string
+     * @throws \Exception
      */
     protected function htmlToPdf($html)
     {
@@ -146,45 +155,14 @@ class ExportService
         // Replace image src with base64 encoded image strings
         if (isset($imageTagsOutput[0]) && count($imageTagsOutput[0]) > 0) {
             foreach ($imageTagsOutput[0] as $index => $imgMatch) {
-                $oldImgString = $imgMatch;
+                $oldImgTagString = $imgMatch;
                 $srcString = $imageTagsOutput[2][$index];
-                $isLocal = strpos(trim($srcString), 'http') !== 0;
-                if ($isLocal) {
-                    $pathString = public_path(trim($srcString, '/'));
-                } else {
-                    $pathString = $srcString;
-                }
-
-                // Attempt to find local files even if url not absolute
-                $base = baseUrl('/');
-                if (strpos($srcString, $base) === 0) {
-                    $isLocal = true;
-                    $relString = str_replace($base, '', $srcString);
-                    $pathString = public_path(trim($relString, '/'));
-                }
-
-                if ($isLocal && !file_exists($pathString)) {
-                    continue;
-                }
-                try {
-                    if ($isLocal) {
-                        $imageContent = file_get_contents($pathString);
-                    } else {
-                        $ch = curl_init();
-                        curl_setopt_array($ch, [CURLOPT_URL => $pathString, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]);
-                        $imageContent = curl_exec($ch);
-                        $err = curl_error($ch);
-                        curl_close($ch);
-                        if ($err) {
-                            throw new \Exception("Image fetch failed, Received error: " . $err);
-                        }
-                    }
-                    $imageEncoded = 'data:image/' . pathinfo($pathString, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageContent);
-                    $newImageString = str_replace($srcString, $imageEncoded, $oldImgString);
-                } catch (\ErrorException $e) {
-                    $newImageString = '';
+                $imageEncoded = $this->imageService->imageUriToBase64($srcString);
+                if ($imageEncoded === null) {
+                    $imageEncoded = $srcString;
                 }
-                $htmlContent = str_replace($oldImgString, $newImageString, $htmlContent);
+                $newImgTagString = str_replace($srcString, $imageEncoded, $oldImgTagString);
+                $htmlContent = str_replace($oldImgTagString, $newImgTagString, $htmlContent);
             }
         }
 
index c2e915e2d91e64353bf303eb40dea4f0c83c3553..06ef3a0f028f4cc1af52407f9ad3cedfffe636e3 100644 (file)
@@ -321,6 +321,53 @@ class ImageService extends UploadService
         return $image;
     }
 
+    /**
+     * Convert a image URI to a Base64 encoded string.
+     * Attempts to find locally via set storage method first.
+     * @param string $uri
+     * @return null|string
+     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
+     */
+    public function imageUriToBase64(string $uri)
+    {
+        $isLocal = strpos(trim($uri), 'http') !== 0;
+
+        // Attempt to find local files even if url not absolute
+        $base = baseUrl('/');
+        if (!$isLocal && strpos($uri, $base) === 0) {
+            $isLocal = true;
+            $uri = str_replace($base, '', $uri);
+        }
+
+        $imageData = null;
+
+        if ($isLocal) {
+            $uri = trim($uri, '/');
+            $storage = $this->getStorage();
+            if ($storage->exists($uri)) {
+                $imageData = $storage->get($uri);
+            }
+        } else {
+            try {
+                $ch = curl_init();
+                curl_setopt_array($ch, [CURLOPT_URL => $uri, CURLOPT_RETURNTRANSFER => 1, CURLOPT_CONNECTTIMEOUT => 5]);
+                $imageData = curl_exec($ch);
+                $err = curl_error($ch);
+                curl_close($ch);
+                if ($err) {
+                    throw new \Exception("Image fetch failed, Received error: " . $err);
+                }
+            } catch (\Exception $e) {
+            }
+        }
+
+        if ($imageData === null) {
+            return null;
+        }
+
+        return 'data:image/' . pathinfo($uri, PATHINFO_EXTENSION) . ';base64,' . base64_encode($imageData);
+    }
+
     /**
      * Gets a public facing url for an image by checking relevant environment variables.
      * @param string $filePath
index 331ed06c87e10e8e96fb098f63a0fd714eb4ce9e..0dd316b34a06e2c2fba24b36fd09befbb74a0bd2 100644 (file)
@@ -67,13 +67,19 @@ class PermissionService
 
     /**
      * Prepare the local entity cache and ensure it's empty
+     * @param Entity[] $entities
      */
-    protected function readyEntityCache()
+    protected function readyEntityCache($entities = [])
     {
-        $this->entityCache = [
-            'books' => collect(),
-            'chapters' => collect()
-        ];
+        $this->entityCache = [];
+
+        foreach ($entities as $entity) {
+            $type = $entity->getType();
+            if (!isset($this->entityCache[$type])) {
+                $this->entityCache[$type] = collect();
+            }
+            $this->entityCache[$type]->put($entity->id, $entity);
+        }
     }
 
     /**
@@ -83,17 +89,14 @@ class PermissionService
      */
     protected function getBook($bookId)
     {
-        if (isset($this->entityCache['books']) && $this->entityCache['books']->has($bookId)) {
-            return $this->entityCache['books']->get($bookId);
+        if (isset($this->entityCache['book']) && $this->entityCache['book']->has($bookId)) {
+            return $this->entityCache['book']->get($bookId);
         }
 
         $book = $this->book->find($bookId);
         if ($book === null) {
             $book = false;
         }
-        if (isset($this->entityCache['books'])) {
-            $this->entityCache['books']->put($bookId, $book);
-        }
 
         return $book;
     }
@@ -105,17 +108,14 @@ class PermissionService
      */
     protected function getChapter($chapterId)
     {
-        if (isset($this->entityCache['chapters']) && $this->entityCache['chapters']->has($chapterId)) {
-            return $this->entityCache['chapters']->get($chapterId);
+        if (isset($this->entityCache['chapter']) && $this->entityCache['chapter']->has($chapterId)) {
+            return $this->entityCache['chapter']->get($chapterId);
         }
 
         $chapter = $this->chapter->find($chapterId);
         if ($chapter === null) {
             $chapter = false;
         }
-        if (isset($this->entityCache['chapters'])) {
-            $this->entityCache['chapters']->put($chapterId, $chapter);
-        }
 
         return $chapter;
     }
@@ -179,6 +179,7 @@ class PermissionService
      * @param Collection $books
      * @param array $roles
      * @param bool $deleteOld
+     * @throws \Throwable
      */
     protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false)
     {
@@ -250,7 +251,7 @@ class PermissionService
         $this->deleteManyJointPermissionsForRoles($roles);
 
         // Chunk through all books
-        $this->bookFetchQuery()->chunk(5, function ($books) use ($roles) {
+        $this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
             $this->buildJointPermissionsForBooks($books, $roles);
         });
     }
@@ -279,6 +280,7 @@ class PermissionService
     /**
      * Delete the entity jointPermissions for a particular entity.
      * @param Entity $entity
+     * @throws \Throwable
      */
     public function deleteJointPermissionsForEntity(Entity $entity)
     {
@@ -288,6 +290,7 @@ class PermissionService
     /**
      * Delete all of the entity jointPermissions for a list of entities.
      * @param Entity[] $entities
+     * @throws \Throwable
      */
     protected function deleteManyJointPermissionsForEntities($entities)
     {
@@ -314,10 +317,11 @@ class PermissionService
      * Create & Save entity jointPermissions for many entities and jointPermissions.
      * @param Collection $entities
      * @param array $roles
+     * @throws \Throwable
      */
     protected function createManyJointPermissions($entities, $roles)
     {
-        $this->readyEntityCache();
+        $this->readyEntityCache($entities);
         $jointPermissions = [];
 
         // Fetch Entity Permissions and create a mapping of entity restricted statuses
@@ -342,7 +346,7 @@ class PermissionService
         // Create a mapping of role permissions
         $rolePermissionMap = [];
         foreach ($roles as $role) {
-            foreach ($role->getRelationValue('permissions') as $permission) {
+            foreach ($role->permissions as $permission) {
                 $rolePermissionMap[$role->getRawAttribute('id') . ':' . $permission->getRawAttribute('name')] = true;
             }
         }
@@ -630,16 +634,17 @@ class PermissionService
      * @param string $tableName
      * @param string $entityIdColumn
      * @param string $entityTypeColumn
+     * @param string $action
      * @return mixed
      */
-    public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
+    public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view')
     {
         if ($this->isAdmin()) {
             $this->clean();
             return $query;
         }
 
-        $this->currentAction = 'view';
+        $this->currentAction = $action;
         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
 
         $q = $query->where(function ($query) use ($tableDetails) {
index 056e1f0772d3bc79f3bc3f788585875805e20e07..6390b8bc4582f3c348cf6a534544c25499ffb5d0 100644 (file)
@@ -67,7 +67,7 @@ class SearchService
      * @param int $count - Count of each entity to search, Total returned could can be larger and not guaranteed.
      * @return array[int, Collection];
      */
-    public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20)
+    public function searchEntities($searchString, $entityType = 'all', $page = 1, $count = 20, $action = 'view')
     {
         $terms = $this->parseSearchString($searchString);
         $entityTypes = array_keys($this->entities);
@@ -87,8 +87,8 @@ class SearchService
             if (!in_array($entityType, $entityTypes)) {
                 continue;
             }
-            $search = $this->searchEntityTable($terms, $entityType, $page, $count);
-            $entityTotal = $this->searchEntityTable($terms, $entityType, $page, $count, true);
+            $search = $this->searchEntityTable($terms, $entityType, $page, $count, $action);
+            $entityTotal = $this->searchEntityTable($terms, $entityType, $page, $count, $action, true);
             if ($entityTotal > $page * $count) {
                 $hasMore = true;
             }
@@ -147,12 +147,13 @@ class SearchService
      * @param string $entityType
      * @param int $page
      * @param int $count
+     * @param string $action
      * @param bool $getCount Return the total count of the search
      * @return \Illuminate\Database\Eloquent\Collection|int|static[]
      */
-    public function searchEntityTable($terms, $entityType = 'page', $page = 1, $count = 20, $getCount = false)
+    public function searchEntityTable($terms, $entityType = 'page', $page = 1, $count = 20, $action = 'view', $getCount = false)
     {
-        $query = $this->buildEntitySearchQuery($terms, $entityType);
+        $query = $this->buildEntitySearchQuery($terms, $entityType, $action);
         if ($getCount) {
             return $query->count();
         }
@@ -165,9 +166,10 @@ class SearchService
      * Create a search query for an entity
      * @param array $terms
      * @param string $entityType
+     * @param string $action
      * @return \Illuminate\Database\Eloquent\Builder
      */
-    protected function buildEntitySearchQuery($terms, $entityType = 'page')
+    protected function buildEntitySearchQuery($terms, $entityType = 'page', $action = 'view')
     {
         $entity = $this->getEntity($entityType);
         $entitySelect = $entity->newQuery();
@@ -212,7 +214,7 @@ class SearchService
             }
         }
 
-        return $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, 'view');
+        return $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, $action);
     }
 
 
index ddcf2eb7eff42e13d2c9bff56444dbe645679213..cd869018c2839fd7213b731bed259c58b3aabb61 100644 (file)
@@ -51,11 +51,13 @@ class ViewService
      * @param int $count
      * @param int $page
      * @param bool|false|array $filterModel
+     * @param string $action - used for permission checking
+     * @return
      */
-    public function getPopular($count = 10, $page = 0, $filterModel = false)
+    public function getPopular($count = 10, $page = 0, $filterModel = false, $action = 'view')
     {
         $skipCount = $count * $page;
-        $query = $this->permissionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type')
+        $query = $this->permissionService->filterRestrictedEntityRelations($this->view, 'views', 'viewable_id', 'viewable_type', $action)
             ->select('*', 'viewable_id', 'viewable_type', \DB::raw('SUM(views) as view_count'))
             ->groupBy('viewable_id', 'viewable_type')
             ->orderBy('view_count', 'desc');
index 5106ed0bb00c69eb51a75288cf2212519c77f539..3de0cb5f7b4920da2f1d527e468f7dc369a59d77 100644 (file)
@@ -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",
index 9370bebff530df225b03d58067003c030eae9138..6e0a353234ca78fd2536f05fa105d70d76ec941b 100644 (file)
@@ -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",
         },
         {
             "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": {
             },
             "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": {
                 "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": {
                 "markdown",
                 "parser"
             ],
-            "time": "2017-11-14T20:44:03+00:00"
+            "time": "2018-03-08T01:11:30+00:00"
         },
         {
             "name": "fideloper/proxy",
         },
         {
             "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": {
             },
             "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": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "6.2-dev"
+                    "dev-master": "6.3-dev"
                 }
             },
             "autoload": {
                 "rest",
                 "web service"
             ],
-            "time": "2017-06-22T18:50:49+00:00"
+            "time": "2018-04-22T15:46:56+00:00"
         },
         {
             "name": "guzzlehttp/promises",
         },
         {
             "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",
                 "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",
                 "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": {
                 "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": {
                 "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": {
                 }
             ],
             "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",
         },
         {
             "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/",
                 "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": {
                 "pseudorandom",
                 "random"
             ],
-            "time": "2017-09-27T21:40:39+00:00"
+            "time": "2018-04-04T21:24:14+00:00"
         },
         {
             "name": "phenx/php-font-lib",
         },
         {
             "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": {
                 "psr-16",
                 "simple-cache"
             ],
-            "time": "2017-01-02T13:31:39+00:00"
+            "time": "2017-10-23T01:57:42+00:00"
         },
         {
             "name": "ramsey/uuid",
         },
         {
             "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": {
                 }
             ],
             "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",
         },
         {
             "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": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.7-dev"
+                    "dev-master": "1.8-dev"
                 }
             },
             "autoload": {
                 "portable",
                 "shim"
             ],
-            "time": "2018-01-30T19:27:44+00:00"
+            "time": "2018-04-26T10:06:28+00:00"
         },
         {
             "name": "symfony/process",
     "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": {
                 "profiler",
                 "webprofiler"
             ],
-            "time": "2018-02-07T08:29:09+00:00"
+            "time": "2018-03-06T08:35:31+00:00"
         },
         {
             "name": "barryvdh/laravel-ide-helper",
         },
         {
             "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": {
                 "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": {
                     "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",
                 "test double",
                 "testing"
             ],
-            "time": "2017-10-06T16:20:43+00:00"
+            "time": "2018-05-08T08:54:48+00:00"
         },
         {
             "name": "myclabs/deep-copy",
         },
         {
             "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": {
                 "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": {
                 "testing",
                 "xunit"
             ],
-            "time": "2017-12-06T09:29:45+00:00"
+            "time": "2018-04-06T15:36:58+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
         },
         {
             "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": {
                 "testing",
                 "xunit"
             ],
-            "time": "2018-02-01T05:57:37+00:00"
+            "time": "2018-04-10T11:38:34+00:00"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
         },
         {
             "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": {
                 "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",
                 "phpcs",
                 "standards"
             ],
-            "time": "2017-12-19T21:44:46+00:00"
+            "time": "2018-02-20T21:35:23+00:00"
         },
         {
             "name": "symfony/class-loader",
index 8d8c14fe9aedec351242c7494627e05f5417f687..b334ffb3c255a5431e8694aa810105a980d8fe47 100644 (file)
@@ -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,
 
 ];
index 15cf9b4deb130f0739bcfe93154d7a3062cd4c9a..41ac6650d9d80fc5a94461329a84eb8000af14cf 100644 (file)
@@ -21,11 +21,11 @@ class DummyContentSeeder extends Seeder
         $role = \BookStack\Role::getRole('viewer');
         $viewerUser->attachRole($role);
 
-        factory(\BookStack\Book::class, 20)->create(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id])
+        factory(\BookStack\Book::class, 5)->create(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id])
             ->each(function($book) use ($editorUser) {
-                $chapters = factory(\BookStack\Chapter::class, 5)->create(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id])
+                $chapters = factory(\BookStack\Chapter::class, 3)->create(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id])
                     ->each(function($chapter) use ($editorUser, $book){
-                        $pages = factory(\BookStack\Page::class, 5)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id, 'book_id' => $book->id]);
+                        $pages = factory(\BookStack\Page::class, 3)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id, 'book_id' => $book->id]);
                         $chapter->pages()->saveMany($pages);
                     });
                 $pages = factory(\BookStack\Page::class, 3)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
diff --git a/database/seeds/LargeContentSeeder.php b/database/seeds/LargeContentSeeder.php
new file mode 100644 (file)
index 0000000..129ede9
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+use Illuminate\Database\Seeder;
+
+class LargeContentSeeder extends Seeder
+{
+    /**
+     * Run the database seeds.
+     *
+     * @return void
+     */
+    public function run()
+    {
+        // Create an editor user
+        $editorUser = factory(\BookStack\User::class)->create();
+        $editorRole = \BookStack\Role::getRole('editor');
+        $editorUser->attachRole($editorRole);
+
+        $largeBook = factory(\BookStack\Book::class)->create(['name' => 'Large book' . str_random(10), 'created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
+        $pages = factory(\BookStack\Page::class, 200)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
+        $chapters = factory(\BookStack\Chapter::class, 50)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]);
+        $largeBook->pages()->saveMany($pages);
+        $largeBook->chapters()->saveMany($chapters);
+        app(\BookStack\Services\PermissionService::class)->buildJointPermissions();
+        app(\BookStack\Services\SearchService::class)->indexAllEntities();
+    }
+}
index c794a62dae93319f0dfde2bad1b1ee45ae531b99..917a8b75e24bac5836b8d520500d49f38bf48954 100644 (file)
         }
       }
     },
+    "@babel/polyfill": {
+      "version": "7.0.0-beta.46",
+      "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.0.0-beta.46.tgz",
+      "integrity": "sha512-eFFWNiI3Os7bBkIA10ZGBUMywK+1/OTVg+qsrlaXRBTpAN0n1g1pXCkNN4rcGpgLPNyfZHQEj+aVAyWPGerSIQ==",
+      "dev": true,
+      "requires": {
+        "core-js": "2.5.5",
+        "regenerator-runtime": "0.11.1"
+      },
+      "dependencies": {
+        "core-js": {
+          "version": "2.5.5",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz",
+          "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=",
+          "dev": true
+        },
+        "regenerator-runtime": {
+          "version": "0.11.1",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+          "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
+          "dev": true
+        }
+      }
+    },
     "@babel/preset-env": {
       "version": "7.0.0-beta.40",
       "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.0.0-beta.40.tgz",
         "babel-types": "6.26.0"
       }
     },
-    "babel-polyfill": {
-      "version": "6.26.0",
-      "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
-      "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
-      "dev": true,
-      "requires": {
-        "babel-runtime": "6.26.0",
-        "core-js": "2.5.1",
-        "regenerator-runtime": "0.10.5"
-      }
-    },
     "babel-preset-es2015": {
       "version": "6.24.1",
       "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz",
         }
       }
     },
-    "moment": {
-      "version": "2.21.0",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz",
-      "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ=="
-    },
     "move-concurrently": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
         "regenerate": "1.3.3"
       }
     },
-    "regenerator-runtime": {
-      "version": "0.10.5",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
-      "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
-      "dev": true
-    },
     "regenerator-transform": {
       "version": "0.10.1",
       "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz",
       }
     },
     "sass-loader": {
-      "version": "6.0.7",
-      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.7.tgz",
-      "integrity": "sha512-JoiyD00Yo1o61OJsoP2s2kb19L1/Y2p3QFcCdWdF6oomBGKVYuZyqHWemRBfQ2uGYsk+CH3eCguXNfpjzlcpaA==",
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.0.1.tgz",
+      "integrity": "sha512-MeVVJFejJELlAbA7jrRchi88PGP6U9yIfqyiG+bBC4a9s2PX+ulJB9h8bbEohtPBfZmlLhNZ0opQM9hovRXvlw==",
       "dev": true,
       "requires": {
         "clone-deep": "2.0.2",
       }
     },
     "style-loader": {
-      "version": "0.20.3",
-      "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.20.3.tgz",
-      "integrity": "sha512-2I7AVP73MvK33U7B9TKlYZAqdROyMXDYSMvHLX43qy3GCOaJNiV6i0v/sv9idWIaQ42Yn2dNv79Q5mKXbKhAZg==",
+      "version": "0.21.0",
+      "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz",
+      "integrity": "sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg==",
       "dev": true,
       "requires": {
         "loader-utils": "1.1.0",
index 2c2d8e37092c164f5c9571d9820dc72575ab280f..e6b7180b9158194d315e73daa204b0ab5e9517e7 100644 (file)
     "@babel/preset-env": "^7.0.0-beta.40",
     "autoprefixer": "^8.1.0",
     "babel-loader": "^8.0.0-beta.0",
-    "babel-polyfill": "^6.26.0",
+    "@babel/polyfill": "^7.0.0-beta.40",
     "css-loader": "^0.28.10",
     "extract-text-webpack-plugin": "^4.0.0-beta.0",
     "livereload": "^0.7.0",
     "node-sass": "^4.7.2",
     "npm-run-all": "^4.1.2",
     "postcss-loader": "^2.1.1",
-    "sass-loader": "^6.0.7",
-    "style-loader": "^0.20.3",
+    "sass-loader": "^7.0.1",
+    "style-loader": "^0.21.0",
     "uglifyjs-webpack-plugin": "^1.2.3",
     "webpack": "^4.1.1",
     "webpack-cli": "^2.0.11"
index acf1cf3d7c39a3f6895aca3359aa8bddf85aa875..245460f9ac0e5df3fb4cf0ad0854706a4ee74bc0 100644 (file)
--- a/readme.md
+++ b/readme.md
@@ -1,7 +1,7 @@
 # BookStack
 
 [![GitHub release](https://img.shields.io/github/release/BookStackApp/BookStack.svg)](https://github.com/BookStackApp/BookStack/releases/latest)
-[![license](https://img.shields.io/github/license/BookStackApp/BookStack.svg)](https://github.com/BookStackApp/BookStack/blob/master/LICENSE)
+[![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/BookStackApp/BookStack/blob/master/LICENSE)
 [![Build Status](https://travis-ci.org/BookStackApp/BookStack.svg)](https://travis-ci.org/BookStackApp/BookStack)
 
 A platform for storing and organising information and documentation. General information and documentation for BookStack can be found at https://www.bookstackapp.com/.
diff --git a/resources/assets/icons/star.svg b/resources/assets/icons/star.svg
new file mode 100644 (file)
index 0000000..c768638
--- /dev/null
@@ -0,0 +1,5 @@
+<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+    <path d="M0 0h24v24H0z" fill="none"/>
+    <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/>
+    <path d="M0 0h24v24H0z" fill="none"/>
+</svg>
\ No newline at end of file
index dc1aefc25a58eb553120e0cd075179a4e9ca0465..b1d1ad02cb02b28d7dbbc83ca6cf362082c2b63e 100644 (file)
@@ -1,4 +1,4 @@
-<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+<svg viewBox="0 0 24 24" fill="#b6531c" xmlns="http://www.w3.org/2000/svg">
     <path d="M0 0h24v24H0z" fill="none"/>
     <path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/>
 </svg>
\ No newline at end of file
index 5fa9b3436588fdbbc050913c92902bb6a1c2f87d..f0bf263fb3d4675f69d19fa9bc533c8fe65aa912 100644 (file)
@@ -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));
     }
index 53358378a31aa5dc6303e995f48a429be8d88724..5bd0d54978db33e51f81a59f5cdbc44be195a8be 100644 (file)
@@ -7,7 +7,8 @@ class EntitySelector {
         this.lastClick = 0;
 
         let entityTypes = elem.hasAttribute('entity-types') ? elem.getAttribute('entity-types') : 'page,book,chapter';
-        this.searchUrl = window.baseUrl(`/ajax/search/entities?types=${encodeURIComponent(entityTypes)}`);
+        let entityPermission = elem.hasAttribute('entity-permission') ? elem.getAttribute('entity-permission') : 'view';
+        this.searchUrl = window.baseUrl(`/ajax/search/entities?types=${encodeURIComponent(entityTypes)}&permission=${encodeURIComponent(entityPermission)}`);
 
         this.input = elem.querySelector('[entity-selector-input]');
         this.searchInput = elem.querySelector('[entity-selector-search]');
@@ -68,7 +69,6 @@ class EntitySelector {
 
     onClick(event) {
         let t = event.target;
-        console.log('click', t);
 
         if (t.matches('.entity-list-item  *')) {
             event.preventDefault();
index 56aa294fadac9980d9f778a13b1a8d66de7ef388..701a1fec60c539064d4fb5a31d2a8b21aae205b9 100644 (file)
@@ -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'
         });
 
index bb323a08bbf74138e86617cf8a922abee582336b..6b24d5ee118820f204b10ce6b1b97982c0231f75 100644 (file)
@@ -1,5 +1,5 @@
 // Global Polyfills
-import "babel-polyfill"
+import "@babel/polyfill"
 import "./services/dom-polyfills"
 
 // Url retrieval function
index 8a5a7a022d1b617d14f1106d794eaf8f2d8bfc8a..6c04e98723fadcd8b7a31a075b10ed93de902f04 100644 (file)
@@ -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',
index 635622b93e80dcb8a584c6d6201e52ea7f678425..16f96c70b378df6b91e5a9e63f66fc979e4988b3 100644 (file)
@@ -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();
index bb8e14c4c2f298be4fa07088a8f80d2c55b19501..020e371b03021b85f9fe41fb7966c405032c18cb 100644 (file)
@@ -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;
         });
     },
index f876ff281a0b298b19100bd12ec31b48991662ab..c0f02ed7d78f34b48908f02d2ca7be84476e12c6 100644 (file)
   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;
     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;
     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;
     color: darken($warning, 16%);
   }
   &.warning:before {
-    background-image: url("/icon/warning.svg?color=b6531c");
+    background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiNiNjUzMWMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+ICAgIDxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz4gICAgPHBhdGggZD0iTTEgMjFoMjJMMTIgMiAxIDIxem0xMi0zaC0ydi0yaDJ2MnptMC00aC0ydi00aDJ2NHoiLz48L3N2Zz4=");
   }
 }
 
   }
 }
 
+.sidebar .card {
+  h3, .body, .empty-text {
+    padding: $-s $-m;
+  }
+}
+
 .card.drag-card {
   border: 1px solid #DDD;
   border-radius: 4px;
   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
index 3ab2de522f64bcff88a879d6980fd98d19f8f849..6b3ed381549e3c1c8f3dedfb99b2a1ba06b09566 100644 (file)
@@ -9,6 +9,7 @@
   color: #666;
   width: 250px;
   max-width: 100%;
+
   &.neg, &.invalid {
     border: 1px solid $negative;
   }
index 10af80a545263856fa116da809942a09f5cece6e..8f15153b5b6c0c2eba56ba7773c3c5ba0c8e5ec2 100644 (file)
@@ -55,6 +55,9 @@ body.flexbox {
   background-color: #F2F2F2;
   max-width: 360px;
   min-height: 90vh;
+  section {
+    margin: $-m;
+  }
 }
 .flex.sidebar + .flex.content {
   flex: 3;
index f4883384ad5f8ccd6939685ad2729c95f998300e..2b6fa9cb090130732de8a03390db6059c27220fb 100644 (file)
@@ -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
index d894a00e739d84cd5273bd5c00db1b1f4eaac97e..da11846d8820a563e7983276bb8ad7f3f8794ae0 100644 (file)
@@ -101,6 +101,13 @@ a, .link {
   }
 }
 
+.blended-links a {
+  color: inherit;
+  svg {
+    fill: currentColor;
+  }
+}
+
 /*
  * Other HTML Text Elements
  */
index 26f096327a324b21d2bd9fb3eeddeaf6990cafc2..c2744d906285b60af537af44f0fd2dfd566baa8e 100644 (file)
@@ -31,6 +31,7 @@ return [
     'edit' => 'Edit',
     'sort' => 'Sort',
     'move' => 'Move',
+    'copy' => 'Copy',
     'reply' => 'Reply',
     'delete' => 'Delete',
     'search' => 'Search',
index a4d3ae6e8ccb7b2c6eb002d914431ec2e1014bae..430655a87be82cd4993b6b822b0751923a5a7ad5 100644 (file)
@@ -166,6 +166,9 @@ return [
     'pages_not_in_chapter' => 'Page is not in a chapter',
     'pages_move' => 'Move Page',
     'pages_move_success' => 'Page moved to ":parentName"',
+    'pages_copy' => 'Copy Page',
+    'pages_copy_desination' => 'Copy Destination',
+    'pages_copy_success' => 'Page successfully copied',
     'pages_permissions' => 'Page Permissions',
     'pages_permissions_success' => 'Page permissions updated',
     'pages_revision' => 'Revision',
index de48942802896372dd13332f29d8591ece565780..b699b5c4b4102da84cae724a41f2b98bfa1f282c 100755 (executable)
@@ -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.',
 
index b526c962055f5bac409742e42023026353312659..fd81619f39ade0e9b0ebaf2acca83bf00b1c2ae0 100644 (file)
@@ -31,6 +31,7 @@ return [
     'edit' => 'Editar',
     'sort' => 'Ordenar',
     'move' => 'Mover',
+    'copy' => 'Copiar',
     'reply' => 'Responder',
     'delete' => 'Borrar',
     'search' => 'Buscar',
@@ -52,7 +53,6 @@ return [
     "grid_view" => "Vista de grilla",
     "list_view" => "Vista de lista",
 
-
     /**
      * Header
      */
index ae88635680d8b7cc653606e09b0214d2e445dbc0..ef4f33111635e5c60a877395cf5426a4c571fd5a 100644 (file)
@@ -21,6 +21,7 @@ return [
     'image_upload_success' => 'Imagen subida Ã©xitosamente',
     'image_update_success' => 'Detalles de la imagen actualizados exitosamente',
     'image_delete_success' => 'Imagen borrada exitosamente',
+    'image_upload_remove' => 'Quitar',
 
     /**
      * Code editor
index 75261a816fbff5d0d6ecb02901725575991c847d..b294f24f99244430c4e38c5c86aa9fecedbc9ec7 100644 (file)
@@ -19,7 +19,6 @@ return [
     'meta_created_name' => 'Creado el  :timeLength por :user',
     'meta_updated' => 'Actualizado el :timeLength',
     'meta_updated_name' => 'Actualizado el :timeLength por :user',
-    'x_pages' => ':count Páginas',
     'entity_select' => 'Seleccione entidad',
     'images' => 'Imágenes',
     'my_recent_drafts' => 'Mis borradores recientes',
@@ -167,6 +166,9 @@ return [
     'pages_not_in_chapter' => 'La página no esá en el capítulo',
     'pages_move' => 'Mover página',
     'pages_move_success' => 'Página movida a ":parentName"',
+    'pages_copy' => 'Copiar página',
+    'pages_copy_desination' => 'Destino de la copia',
+    'pages_copy_success' => 'Página copiada con Ã©xito',
     'pages_permissions' => 'Permisos de página',
     'pages_permissions_success' => 'Permisos de página actualizados',
     'pages_revision' => 'Revisión',
@@ -201,6 +203,8 @@ return [
      * Editor sidebar
      */
     'page_tags' => 'Etiquetas de página',
+    'chapter_tags' => 'Etiquetas de capítulo',
+    'book_tags' => 'Etiquetas de libro',
     'tag' => 'Etiqueta',
     'tags' =>  'Etiquetas',
     'tag_value' => 'Valor de la etiqueta (Opcional)',
@@ -246,6 +250,7 @@ return [
      */
     'comment' => 'Comentario',
     'comments' => 'Comentarios',
+    'comment_add' => 'Agregar comentario',
     'comment_placeholder' => 'DEjar un comentario aquí',
     'comment_count' => '{0} Sin Comentarios|{1} 1 Comentario|[2,*] :count Comentarios',
     'comment_save' => 'Guardar comentario',
index f2f84b0ca020605d1738f1ccf4640cd1b3947936..46f69c6f08a83fcbd12d83f76721110ecfeb7597 100644 (file)
@@ -35,6 +35,7 @@ return [
     'cannot_get_image_from_url' => 'No se puede obtener la imagen desde :url',
     'cannot_create_thumbs' => 'El servidor no puede crear la imagen miniatura. Por favor chequee que tiene la extensión GD instalada.',
     'server_upload_limit' => 'El servidor no permite la subida de ficheros de este tamañ. Por favor intente con un fichero de menor tamañ.',
+    'uploaded'  => 'El servidor no permite subir archivos de este tamaño. Por favor intente un tamaño menor.',    'image_upload_error' => 'Ha ocurrido un error al subir la imagen',
     'image_upload_error' => 'Ha ocurrido un error al subir la imagen',
     'image_upload_type_error' => 'El tipo de imagen subida es inválido.',
 
index a47c26e261f00647d37abcad9c10bbb4897481ba..bf94b5b076ed189a3dc723ad6ce5013522daf049 100644 (file)
@@ -30,9 +30,9 @@
     </div>
 </div>
 
-<div class="form-group" collapsible id="logo-control">
+<div class="form-group" collapsible id="tags-control">
     <div class="collapse-title text-primary" collapsible-trigger>
-        <label for="user-avatar">{{ trans('entities.book_tags') }}</label>
+        <label for="tag-manager">{{ trans('entities.book_tags') }}</label>
     </div>
     <div class="collapse-content" collapsible-content>
         @include('components.tag-manager', ['entity' => isset($book)?$book:null, 'entityType' => 'chapter'])
index db99d0b5403bbbb39afc178b9b3dc07456788eac..9bbf691006822cdd4935dc6c9f46c3b93c850e5c 100644 (file)
@@ -12,7 +12,7 @@
             <p >{{ $book->getExcerpt(130) }}</p>
         @endif
     </div>
-    <div class="grid-card-footer">
+    <div class="grid-card-footer text-muted text-small">
         <span>@include('partials.entity-meta', ['entity' => $book])</span>
     </div>
 </div>
\ No newline at end of file
index d1435ab662969c5f9128c6dc7d3d6dae9aae311f..84150203f081e2345f466a0f0b3399da3acb2298 100644 (file)
@@ -3,16 +3,7 @@
 @section('toolbar')
     <div class="col-xs-6">
         <div class="action-buttons text-left">
-            <form action="{{ baseUrl("/settings/users/{$currentUser->id}/switch-book-view") }}" method="POST" class="inline">
-                {!! csrf_field() !!}
-                {!! method_field('PATCH') !!}
-                <input type="hidden" value="{{ $booksViewType === 'list'? 'grid' : 'list' }}" name="book_view_type">
-                @if ($booksViewType === 'list')
-                    <button type="submit" class="text-pos text-button">@icon('grid'){{ trans('common.grid_view') }}</button>
-                @else
-                    <button type="submit" class="text-pos text-button">@icon('list'){{ trans('common.list_view') }}</button>
-                @endif
-            </form>
+            @include('books/view-toggle', ['booksViewType' => $booksViewType])
         </div>
     </div>
     <div class="col-xs-6 faded">
 @stop
 
 @section('body')
-    @if($booksViewType === 'list')
-        <div class="container small" ng-non-bindable>
-    @else
-        <div class="container" ng-non-bindable>
-    @endif
-        <h1>{{ trans('entities.books') }}</h1>
-        @if(count($books) > 0)
-            @if($booksViewType === 'list')
-                @foreach($books as $book)
-                    @include('books/list-item', ['book' => $book])
-                    <hr>
-                @endforeach
-                {!! $books->render() !!}
-            @else
-                 <div class="grid third">
-                    @foreach($books as $key => $book)
-                            @include('books/grid-item', ['book' => $book])
-                    @endforeach
-                 </div>
-                <div>
-                    {!! $books->render() !!}
-                </div>
-            @endif
-        @else
-            <p class="text-muted">{{ trans('entities.books_empty') }}</p>
-            @if(userCan('books-create-all'))
-                <a href="{{ baseUrl("/create-book") }}" class="text-pos">@icon('edit'){{ trans('entities.create_one_now') }}</a>
-            @endif
-        @endif
-    </div>
+    @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 (file)
index 0000000..1c2056a
--- /dev/null
@@ -0,0 +1,31 @@
+
+@if($booksViewType === 'list')
+    <div class="container small">
+@else
+    <div class="container">
+@endif
+    <h1>{{ trans('entities.books') }}</h1>
+    @if(count($books) > 0)
+        @if($booksViewType === 'list')
+            @foreach($books as $book)
+                @include('books/list-item', ['book' => $book])
+                <hr>
+            @endforeach
+            {!! $books->render() !!}
+        @else
+             <div class="grid third">
+                @foreach($books as $key => $book)
+                        @include('books/grid-item', ['book' => $book])
+                @endforeach
+             </div>
+            <div>
+                {!! $books->render() !!}
+            </div>
+        @endif
+    @else
+        <p class="text-muted">{{ trans('entities.books_empty') }}</p>
+        @if(userCan('books-create-all'))
+            <a href="{{ baseUrl("/create-book") }}" class="text-pos">@icon('edit'){{ trans('entities.create_one_now') }}</a>
+        @endif
+    @endif
+</div>
\ No newline at end of file
index 9f021b2b080eb12656fe7e931e37d56e333aa029..d0a2eb2f706aeeb19c82c7423f2e3670b0fcadb0 100644 (file)
 
 @section('sidebar')
 
+    @if($book->tags->count() > 0)
+        <section>
+            @include('components.tag-list', ['entity' => $book])
+        </section>
+    @endif
+
     <div class="card">
         <div class="body">
             <form v-on:submit.prevent="searchBook" class="search-box">
         </div>
     </div>
 
-    @if($book->restricted)
-        <div class="card">
-            <h3>@icon('permission') {{ trans('entities.permissions') }}</h3>
-            <div class="body">
-                <p class="text-muted">
+    <div class="card entity-details">
+        <h3>@icon('info') {{ trans('common.details') }}</h3>
+        <div class="body text-small text-muted blended-links">
+            @include('partials.entity-meta', ['entity' => $book])
+            @if($book->restricted)
+                <div class="active-restriction">
                     @if(userCan('restrictions-manage', $book))
                         <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
                     @else
                         @icon('lock'){{ trans('entities.books_permissions_active') }}
                     @endif
-                </p>
-            </div>
-        </div>
-    @endif
-
-    <div class="card">
-        <h3>@icon('info') {{ trans('common.details') }}</h3>
-        <div class="body">
-            @include('partials.entity-meta', ['entity' => $book])
+                </div>
+            @endif
         </div>
     </div>
 
-    @if($book->tags->count() > 0)
-        <div class="card tag-display">
-            <h3>@icon('tag') {{ trans('entities.book_tags') }}</h3>
-            <div class="body">
-                @include('components.tag-list', ['entity' => $book])
-            </div>
-        </div>
-    @endif
-
     @if(count($activity) > 0)
         <div class="activity card">
             <h3>@icon('time') {{ trans('entities.recent_activity') }}</h3>
diff --git a/resources/views/books/view-toggle.blade.php b/resources/views/books/view-toggle.blade.php
new file mode 100644 (file)
index 0000000..61df7ab
--- /dev/null
@@ -0,0 +1,10 @@
+<form action="{{ baseUrl("/settings/users/{$currentUser->id}/switch-book-view") }}" method="POST" class="inline">
+    {!! csrf_field() !!}
+    {!! method_field('PATCH') !!}
+    <input type="hidden" value="{{ $booksViewType === 'list'? 'grid' : 'list' }}" name="book_view_type">
+    @if ($booksViewType === 'list')
+        <button type="submit" class="text-pos text-button">@icon('grid'){{ trans('common.grid_view') }}</button>
+    @else
+        <button type="submit" class="text-pos text-button">@icon('list'){{ trans('common.list_view') }}</button>
+    @endif
+</form>
\ No newline at end of file
index f433733242bc7fd52284395389ebba9270286bab..0efc18adf3c02cb28a9be16c6ff2b64d41189466 100644 (file)
@@ -8,7 +8,7 @@
 
 @section('body')
 
-    <div class="container">
+    <div class="container small">
 
         <div class="card">
             <h3>@icon('folder') {{ trans('entities.chapters_move') }}</h3>
@@ -17,7 +17,7 @@
                     {!! csrf_field() !!}
                     <input type="hidden" name="_method" value="PUT">
 
-                    @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book'])
+                    @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book', 'entityPermission' => 'chapter-create'])
 
                     <div class="form-group text-right">
                         <a href="{{ $chapter->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
index ea98200226da2f5603a11efeeaa5049962e219ae..ae450b8ee2e57465b14d1254aff6973ae3d4996c 100644 (file)
 @stop
 
 @section('sidebar')
+
+    @if($chapter->tags->count() > 0)
+        <section>
+            @include('components.tag-list', ['entity' => $chapter])
+        </section>
+    @endif
+
     <div class="card">
         <div class="body">
             <form @submit.prevent="searchBook" class="search-box">
         </div>
     </div>
 
-    @if($book->restricted || $chapter->restricted)
-        <div class="card">
-            <h3>@icon('permission') {{ trans('entities.permissions') }}</h3>
-            <div class="body">
-                @if($book->restricted)
-                    <p class="text-muted">
-                        @if(userCan('restrictions-manage', $book))
-                            <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
-                        @else
-                            @icon('lock'){{ trans('entities.books_permissions_active') }}
-                        @endif
-                    </p>
-                @endif
-
-                @if($chapter->restricted)
-                    <p class="text-muted">
-                        @if(userCan('restrictions-manage', $chapter))
-                            <a href="{{ $chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
-                        @else
-                            @icon('lock'){{ trans('entities.chapters_permissions_active') }}
-                        @endif
-                    </p>
-                @endif
-            </div>
-        </div>
-    @endif
-
-    <div class="card">
+    <div class="card entity-details">
         <h3>@icon('info') {{ trans('common.details') }}</h3>
-        <div class="body">
+        <div class="body blended-links text-small text-muted">
             @include('partials.entity-meta', ['entity' => $chapter])
-        </div>
-    </div>
 
-    @if($chapter->tags->count() > 0)
-        <div class="card tag-display">
-            <h3>@icon('tag') {{ trans('entities.chapter_tags') }}</h3>
-            <div class="body">
-                @include('components.tag-list', ['entity' => $chapter])
-            </div>
+            @if($book->restricted)
+                <div class="active-restriction">
+                    @if(userCan('restrictions-manage', $book))
+                        <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
+                    @else
+                        @icon('lock'){{ trans('entities.books_permissions_active') }}
+                    @endif
+                </div>
+            @endif
+
+            @if($chapter->restricted)
+                <div class="active-restriction">
+                    @if(userCan('restrictions-manage', $chapter))
+                        <a href="{{ $chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
+                    @else
+                        @icon('lock'){{ trans('entities.chapters_permissions_active') }}
+                    @endif
+                </div>
+            @endif
         </div>
-    @endif
+    </div>
 
     @include('partials/book-tree', ['book' => $book, 'sidebarTree' => $sidebarTree])
 @stop
diff --git a/resources/views/common/home-book.blade.php b/resources/views/common/home-book.blade.php
new file mode 100644 (file)
index 0000000..2849d5e
--- /dev/null
@@ -0,0 +1,18 @@
+@extends('sidebar-layout')
+
+@section('toolbar')
+    <div class="col-sm-6 faded">
+        <div class="action-buttons text-left">
+            <a expand-toggle=".entity-list.compact .entity-item-snippet" class="text-primary text-button">@icon('expand-text'){{ trans('common.toggle_details') }}</a>
+            @include('books/view-toggle', ['booksViewType' => $booksViewType])
+        </div>
+    </div>
+@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 (file)
index 0000000..89154ee
--- /dev/null
@@ -0,0 +1,19 @@
+@extends('sidebar-layout')
+
+@section('toolbar')
+    <div class="col-sm-6 faded">
+        <div class="action-buttons text-left">
+            <a expand-toggle=".entity-list.compact .entity-item-snippet" class="text-primary text-button">@icon('expand-text'){{ trans('common.toggle_details') }}</a>
+        </div>
+    </div>
+@stop
+
+@section('sidebar')
+    @include('common/home-sidebar')
+@stop
+
+@section('body')
+    <div class="page-content" page-display="{{ $customHomepage->id }}">
+        @include('pages/page-display', ['page' => $customHomepage])
+    </div>
+@stop
diff --git a/resources/views/common/home-sidebar.blade.php b/resources/views/common/home-sidebar.blade.php
new file mode 100644 (file)
index 0000000..2210294
--- /dev/null
@@ -0,0 +1,31 @@
+@if(count($draftPages) > 0)
+    <div id="recent-drafts" class="card">
+        <h3>@icon('edit') {{ trans('entities.my_recent_drafts') }}</h3>
+        @include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact'])
+    </div>
+@endif
+
+<div class="card">
+    <h3>@icon($signedIn ? 'view' : 'star-circle') {{ trans('entities.' . ($signedIn ? 'my_recently_viewed' : 'books_recent')) }}</h3>
+    @include('partials/entity-list', [
+        'entities' => $recents,
+        'style' => 'compact',
+        'emptyText' => $signedIn ? trans('entities.no_pages_viewed') : trans('entities.books_empty')
+        ])
+</div>
+
+<div class="card">
+    <h3>@icon('file') <a class="no-color" href="{{ baseUrl("/pages/recently-updated") }}">{{ trans('entities.recently_updated_pages') }}</a></h3>
+    <div id="recently-updated-pages">
+        @include('partials/entity-list', [
+        'entities' => $recentlyUpdatedPages,
+        'style' => 'compact',
+        'emptyText' => trans('entities.no_pages_recently_updated')
+        ])
+    </div>
+</div>
+
+<div id="recent-activity" class="card">
+    <h3>@icon('time') {{ trans('entities.recent_activity') }}</h3>
+    @include('partials/activity-list', ['activity' => $activity])
+</div>
\ No newline at end of file
index 03e2066ed194b9f6ad597a53d497f8b0bb88745e..89c574c28c0f3090868cc43ef1f3c6794f7b6d4c 100644 (file)
@@ -1,5 +1,5 @@
 <div class="form-group">
-    <div entity-selector class="entity-selector {{$selectorSize or ''}}" entity-types="{{ $entityTypes or 'book,chapter,page' }}">
+    <div entity-selector class="entity-selector {{$selectorSize or ''}}" entity-types="{{ $entityTypes or 'book,chapter,page' }}" entity-permission="{{ $entityPermission or 'view' }}">
         <input type="hidden" entity-selector-input name="{{$name}}" value="">
         <input type="text" placeholder="{{ trans('common.search') }}" entity-selector-search>
         <div class="text-center loading" entity-selector-loading>@include('partials.loading-icon')</div>
index 9f4273c5afe5dc30d4f1301a06a8fabc8b76ab0b..1d1cc2d806153baa00d770e3ca05b4735fb5177b 100644 (file)
@@ -1,10 +1,6 @@
-<table>
-    <tbody>
-    @foreach($entity->tags as $tag)
-        <tr class="tag">
-            <td @if(!$tag->value) colspan="2" @endif><a href="{{ baseUrl('/search?term=%5B' . urlencode($tag->name) .'%5D') }}">{{ $tag->name }}</a></td>
-            @if($tag->value) <td class="tag-value"><a href="{{ baseUrl('/search?term=%5B' . urlencode($tag->name) .'%3D' . urlencode($tag->value) . '%5D') }}">{{$tag->value}}</a></td> @endif
-        </tr>
-    @endforeach
-    </tbody>
-</table>
\ No newline at end of file
+@foreach($entity->tags as $tag)
+    <div class="tag-item primary-background-light">
+        <div class="tag-name"><a href="{{ baseUrl('/search?term=%5B' . urlencode($tag->name) .'%5D') }}">@icon('tag'){{ $tag->name }}</a></div>
+        @if($tag->value) <div class="tag-value"><a href="{{ baseUrl('/search?term=%5B' . urlencode($tag->name) .'%3D' . urlencode($tag->value) . '%5D') }}">{{$tag->value}}</a></div> @endif
+    </div>
+@endforeach
\ 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 (file)
index d9fbab8..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-@extends('sidebar-layout')
-
-@section('toolbar')
-    <div class="col-sm-6 faded">
-        <div class="action-buttons text-left">
-            <a expand-toggle=".entity-list.compact .entity-item-snippet" class="text-primary text-button">@icon('expand-text'){{ trans('common.toggle_details') }}</a>
-        </div>
-    </div>
-@stop
-
-@section('sidebar')
-    @if(count($draftPages) > 0)
-        <div id="recent-drafts" class="card">
-            <h3>@icon('edit') {{ trans('entities.my_recent_drafts') }}</h3>
-            @include('partials/entity-list', ['entities' => $draftPages, 'style' => 'compact'])
-        </div>
-    @endif
-
-    <div class="card">
-        <h3>@icon($signedIn ? 'view' : 'star-circle') {{ trans('entities.' . ($signedIn ? 'my_recently_viewed' : 'books_recent')) }}</h3>
-        @include('partials/entity-list', [
-            'entities' => $recents,
-            'style' => 'compact',
-            'emptyText' => $signedIn ? trans('entities.no_pages_viewed') : trans('entities.books_empty')
-            ])
-    </div>
-
-    <div class="card">
-        <h3>@icon('file') <a class="no-color" href="{{ baseUrl("/pages/recently-updated") }}">{{ trans('entities.recently_updated_pages') }}</a></h3>
-        <div id="recently-updated-pages">
-            @include('partials/entity-list', [
-            'entities' => $recentlyUpdatedPages,
-            'style' => 'compact',
-            'emptyText' => trans('entities.no_pages_recently_updated')
-            ])
-        </div>
-    </div>
-
-    <div id="recent-activity" class="card">
-        <h3>@icon('time') {{ trans('entities.recent_activity') }}</h3>
-        @include('partials/activity-list', ['activity' => $activity])
-    </div>
-@stop
-
-@section('body')
-    <div class="page-content" ng-non-bindable>
-        @include('pages/page-display', ['page' => $customHomepage])
-    </div>
-@stop
-
-@section('scripts')
-    <script>
-        setupPageShow({{$customHomepage->id}});
-    </script>
-@stop
-
diff --git a/resources/views/pages/copy.blade.php b/resources/views/pages/copy.blade.php
new file mode 100644 (file)
index 0000000..eb6afca
--- /dev/null
@@ -0,0 +1,43 @@
+@extends('simple-layout')
+
+@section('toolbar')
+    <div class="col-sm-12 faded">
+        @include('pages._breadcrumbs', ['page' => $page])
+    </div>
+@stop
+
+@section('body')
+
+    <div class="container small">
+        <p>&nbsp;</p>
+        <div class="card">
+            <h3>@icon('copy') {{ trans('entities.pages_copy') }}</h3>
+            <div class="body">
+                <form action="{{ $page->getUrl('/copy') }}" method="POST">
+                    {!! csrf_field() !!}
+
+                    <div class="form-group title-input">
+                        <label for="name">{{ trans('common.name') }}</label>
+                        @include('form/text', ['name' => 'name'])
+                    </div>
+
+                    <div class="form-group" collapsible>
+                        <div class="collapse-title text-primary" collapsible-trigger>
+                            <label for="entity_selection">{{ trans('entities.pages_copy_desination') }}</label>
+                        </div>
+                        <div class="collapse-content" collapsible-content>
+                            @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create'])
+                        </div>
+                    </div>
+
+
+                    <div class="form-group text-right">
+                        <a href="{{ $page->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
+                        <button type="submit" class="button pos">{{ trans('entities.pages_copy') }}</button>
+                    </div>
+                </form>
+            </div>
+        </div>
+    </div>
+
+@stop
index 6c6137e17106e7db1cfd177d28de8fecc00e9e3a..8ed5b391ab8e8aa42dcc83c43395b911a8ee9387 100644 (file)
@@ -21,7 +21,9 @@
 
                 <hr>
 
-                @include('partials.entity-meta', ['entity' => $page])
+                <div class="text-muted text-small">
+                    @include('partials.entity-meta', ['entity' => $page])
+                </div>
 
             </div>
         </div>
index 9785af3575af741c5c9e152e5b63ba6fe94fae81..5a5c7e3f93c9b5ff76c7f1ebc9b04ca1870dcfb8 100644 (file)
@@ -8,7 +8,7 @@
 
 @section('body')
 
-    <div class="container">
+    <div class="container small">
         <p>&nbsp;</p>
         <div class="card">
             <h3>@icon('folder') {{ trans('entities.pages_move') }}</h3>
@@ -17,7 +17,7 @@
                     {!! csrf_field() !!}
                     <input type="hidden" name="_method" value="PUT">
 
-                    @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter'])
+                    @include('components.entity-selector', ['name' => 'entity_selection', 'selectorSize' => 'large', 'entityTypes' => 'book,chapter', 'entityPermission' => 'page-create'])
 
                     <div class="form-group text-right">
                         <a href="{{ $page->getUrl() }}" class="button outline">{{ trans('common.cancel') }}</a>
index f3243e5917e20fadfe2a597f2e9bb1896663ac6c..f9e8a22c4a42d6531147e95dc9ed6223baca3b69 100644 (file)
@@ -3,7 +3,7 @@
 @section('sidebar')
     <div class="card">
         <h3>@icon('info') {{ trans('common.details') }}</h3>
-        <div class="body">
+        <div class="body text-small text-muted">
             @include('partials.entity-meta', ['entity' => $revision])
         </div>
     </div>
index dabc7b965ed9cfe1ecba66490069e68a711b8b5d..3448a164a4c196c8b4d2b9374c1a629c4430e7a0 100644 (file)
@@ -22,6 +22,7 @@
                     <a dropdown-toggle class="text-primary text-button">@icon('more') {{ trans('common.more') }}</a>
                     <ul>
                         @if(userCan('page-update', $page))
+                            <li><a href="{{ $page->getUrl('/copy') }}" class="text-primary" >@icon('copy'){{ trans('common.copy') }}</a></li>
                             <li><a href="{{ $page->getUrl('/move') }}" class="text-primary" >@icon('folder'){{ trans('common.move') }}</a></li>
                             <li><a href="{{ $page->getUrl('/revisions') }}" class="text-primary">@icon('history'){{ trans('entities.revisions') }}</a></li>
                         @endif
 @stop
 
 @section('sidebar')
-    @if($book->restricted || ($page->chapter && $page->chapter->restricted) || $page->restricted)
-        <div class="card">
-            <h3>@icon('permission') {{ trans('entities.permissions') }}</h3>
-            <div class="body">
-                <div class="text-muted">
-
-                    @if($book->restricted)
-                        @if(userCan('restrictions-manage', $book))
-                            <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
-                        @else
-                            @icon('lock'){{ trans('entities.books_permissions_active') }}
-                        @endif
-                        <br>
-                    @endif
-
-                    @if($page->chapter && $page->chapter->restricted)
-                        @if(userCan('restrictions-manage', $page->chapter))
-                            <a href="{{ $page->chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
-                        @else
-                            @icon('lock'){{ trans('entities.chapters_permissions_active') }}
-                        @endif
-                        <br>
-                    @endif
-
-                    @if($page->restricted)
-                        @if(userCan('restrictions-manage', $page))
-                            <a href="{{ $page->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.pages_permissions_active') }}</a>
-                        @else
-                            @icon('lock'){{ trans('entities.pages_permissions_active') }}
-                        @endif
-                        <br>
-                    @endif
-                </div>
-            </div>
-        </div>
-    @endif
 
     @if($page->tags->count() > 0)
-        <div class="card tag-display">
-            <h3>@icon('tag') {{ trans('entities.page_tags') }}</h3>
-            <div class="body">
-                @include('components.tag-list', ['entity' => $page])
-            </div>
-        </div>
+        <section>
+            @include('components.tag-list', ['entity' => $page])
+        </section>
     @endif
 
     @if ($page->attachments->count() > 0)
         </div>
     @endif
 
-    <div class="card">
+    <div class="card entity-details">
         <h3>@icon('info') {{ trans('common.details') }}</h3>
-        <div class="body">
+        <div class="body text-muted text-small blended-links">
             @include('partials.entity-meta', ['entity' => $page])
+
+            @if($book->restricted)
+                <div class="active-restriction">
+                    @if(userCan('restrictions-manage', $book))
+                        <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
+                    @else
+                        @icon('lock'){{ trans('entities.books_permissions_active') }}
+                    @endif
+                </div>
+            @endif
+
+            @if($page->chapter && $page->chapter->restricted)
+                <div class="active-restriction">
+                    @if(userCan('restrictions-manage', $page->chapter))
+                        <a href="{{ $page->chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
+                    @else
+                        @icon('lock'){{ trans('entities.chapters_permissions_active') }}
+                    @endif
+                </div>
+            @endif
+
+            @if($page->restricted)
+                <div class="active-restriction">
+                    @if(userCan('restrictions-manage', $page))
+                        <a href="{{ $page->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.pages_permissions_active') }}</a>
+                    @else
+                        @icon('lock'){{ trans('entities.pages_permissions_active') }}
+                    @endif
+                </div>
+            @endif
         </div>
     </div>
 
 @stop
 
 @section('body')
-    <div class="page-content" page-display="{{ $page->id }}" ng-non-bindable>
+    <div class="page-content" page-display="{{ $page->id }}">
 
         <div class="pointer-container" id="pointer">
             <div class="pointer anim" >
index 49671ba509051a3c383a235b96a42b77b1aba34d..f759ea25b174628de3a3af0d411f1f6a076b5dda 100644 (file)
@@ -1,25 +1,34 @@
-<p class="text-muted small">
+<div class="entity-meta">
     @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 }}
         <br>
     @endif
-    @if ($entity->isA('page')) {{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }} <br> @endif
+
+    @if ($entity->isA('page'))
+        @if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}"> @endif
+            @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }} <br>
+            @if (userCan('page-update', $entity))</a>@endif
+    @endif
+
+
     @if ($entity->createdBy)
-        {!! trans('entities.meta_created_name', [
+        @icon('star'){!! trans('entities.meta_created_name', [
             'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
             'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".htmlentities($entity->createdBy->name). "</a>"
             ]) !!}
     @else
-        <span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
+        @icon('star')<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
     @endif
+
     <br>
+
     @if ($entity->updatedBy)
-        {!! trans('entities.meta_updated_name', [
+        @icon('edit'){!! trans('entities.meta_updated_name', [
                 'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
                 'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".htmlentities($entity->updatedBy->name). "</a>"
             ]) !!}
     @elseif (!$entity->isA('revision'))
-        <span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
+        @icon('edit')<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
     @endif
-</p>
\ No newline at end of file
+</div>
\ No newline at end of file
index 51ca8ccb41403033c0c7e5ac08bbd24f12a7fc9c..64017e6e00ec9ceb71b538313b0ada29a9e0f8c5 100644 (file)
@@ -80,6 +80,8 @@
                             <label for="setting-app-homepage">{{ trans('settings.app_homepage') }}</label>
                             <p class="small">{{ trans('settings.app_homepage_desc') }}</p>
                             @include('components.page-picker', ['name' => 'setting-app-homepage', 'placeholder' => trans('settings.app_homepage_default'), 'value' => setting('app-homepage')])
+                            <p class="small">{{ trans('settings.app_homepage_books') }}</p>
+                            @include('components.toggle-switch', ['name' => 'setting-app-book-homepage', 'value' => setting('app-book-homepage')])
                         </div>
                     </div>
 
index be05cda90ad87a50cf14dc74e75568e92ae52b92..a857bce6c71dc91c710dfe1863ffe2bc071372f1 100644 (file)
@@ -1,7 +1,6 @@
 <?php
 
 Route::get('/translations', 'HomeController@getTranslations');
-Route::get('/icon/{iconName}.svg', 'HomeController@getIcon');
 Route::get('/robots.txt', 'HomeController@getRobots');
 
 // Authenticated routes...
@@ -47,6 +46,8 @@ Route::group(['middleware' => 'auth'], function () {
         Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit');
         Route::get('/{bookSlug}/page/{pageSlug}/move', 'PageController@showMove');
         Route::put('/{bookSlug}/page/{pageSlug}/move', 'PageController@move');
+        Route::get('/{bookSlug}/page/{pageSlug}/copy', 'PageController@showCopy');
+        Route::post('/{bookSlug}/page/{pageSlug}/copy', 'PageController@copy');
         Route::get('/{bookSlug}/page/{pageSlug}/delete', 'PageController@showDelete');
         Route::get('/{bookSlug}/draft/{pageId}/delete', 'PageController@showDeleteDraft');
         Route::get('/{bookSlug}/page/{pageSlug}/permissions', 'PageController@showRestrict');
@@ -195,4 +196,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
index a8ff0304422cfef5cf04a964e011cb3b3ff43de1..86f61a7647181e1aa3a89e166d3202964c1c4c57 100644 (file)
@@ -12,10 +12,7 @@ abstract class BrowserKitTest extends TestCase
 {
 
     use DatabaseTransactions;
-
-    // Local user instances
-    private $admin;
-    private $editor;
+    use SharedTestHelpers;
 
     /**
      * The base URL to use while testing the application.
@@ -43,38 +40,6 @@ abstract class BrowserKitTest extends TestCase
         return $app;
     }
 
-    /**
-     * Set the current user context to be an admin.
-     * @return $this
-     */
-    public function asAdmin()
-    {
-        return $this->actingAs($this->getAdmin());
-    }
-
-    /**
-     * Get the current admin user.
-     * @return mixed
-     */
-    public function getAdmin() {
-        if($this->admin === null) {
-            $adminRole = Role::getSystemRole('admin');
-            $this->admin = $adminRole->users->first();
-        }
-        return $this->admin;
-    }
-
-    /**
-     * Set the current editor context to be an editor.
-     * @return $this
-     */
-    public function asEditor()
-    {
-        if ($this->editor === null) {
-            $this->editor = $this->getEditor();
-        }
-        return $this->actingAs($this->editor);
-    }
 
     /**
      * Get a user that's not a system user such as the guest user.
@@ -127,28 +92,6 @@ abstract class BrowserKitTest extends TestCase
         $restrictionService->buildJointPermissionsForEntity($entity);
     }
 
-    /**
-     * Get an instance of a user with 'editor' permissions
-     * @param array $attributes
-     * @return mixed
-     */
-    protected function getEditor($attributes = [])
-    {
-        $user = \BookStack\Role::getRole('editor')->users()->first();
-        if (!empty($attributes)) $user->forceFill($attributes)->save();
-        return $user;
-    }
-
-    /**
-     * Get an instance of a user with 'viewer' permissions
-     * @return mixed
-     */
-    protected function getViewer()
-    {
-        $user = \BookStack\Role::getRole('viewer')->users()->first();
-        if (!empty($attributes)) $user->forceFill($attributes)->save();
-        return $user;
-    }
 
     /**
      * Quick way to create a new user without any permissions
index 352af1e42a0efa93c97aa6ac3a70e83806881725..f8ca8ea1217d0726b1c33f4078e73a2f667001a2 100644 (file)
@@ -5,6 +5,7 @@ use BookStack\Chapter;
 use BookStack\Page;
 use BookStack\Repos\EntityRepo;
 use BookStack\Repos\UserRepo;
+use Carbon\Carbon;
 
 class EntityTest extends BrowserKitTest
 {
@@ -269,6 +270,9 @@ class EntityTest extends BrowserKitTest
     public function test_recently_updated_pages_on_home()
     {
         $page = Page::orderBy('updated_at', 'asc')->first();
+        Page::where('id', '!=', $page->id)->update([
+            'updated_at' => Carbon::now()->subSecond(1)
+        ]);
         $this->asAdmin()->visit('/')
             ->dontSeeInElement('#recently-updated-pages', $page->name);
         $this->visit($page->getUrl() . '/edit')
index 3b0831029c0ecb7dafc97df41651911057b02e40..ea5ab665d0edb732ae2681cdba8dab498089674a 100644 (file)
@@ -1,6 +1,7 @@
 <?php namespace Tests;
 
 use BookStack\Book;
+use BookStack\Chapter;
 use BookStack\Page;
 use BookStack\Repos\EntityRepo;
 
@@ -11,7 +12,7 @@ class SortTest extends TestCase
     public function setUp()
     {
         parent::setUp();
-        $this->book = \BookStack\Book::first();
+        $this->book = Book::first();
     }
 
     public function test_drafts_do_not_show_up()
@@ -29,17 +30,17 @@ class SortTest extends TestCase
 
     public function test_page_move()
     {
-        $page = \BookStack\Page::first();
+        $page = Page::first();
         $currentBook = $page->book;
-        $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
+        $newBook = Book::where('id', '!=', $currentBook->id)->first();
 
-        $resp = $this->asAdmin()->get($page->getUrl() . '/move');
+        $resp = $this->asEditor()->get($page->getUrl('/move'));
         $resp->assertSee('Move Page');
 
-        $movePageResp = $this->put($page->getUrl() . '/move', [
+        $movePageResp = $this->put($page->getUrl('/move'), [
             'entity_selection' => 'book:' . $newBook->id
         ]);
-        $page = \BookStack\Page::find($page->id);
+        $page = Page::find($page->id);
 
         $movePageResp->assertRedirect($page->getUrl());
         $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book');
@@ -49,21 +50,46 @@ class SortTest extends TestCase
         $newBookResp->assertSee($page->name);
     }
 
+    public function test_page_move_requires_create_permissions_on_parent()
+    {
+        $page = Page::first();
+        $currentBook = $page->book;
+        $newBook = Book::where('id', '!=', $currentBook->id)->first();
+        $editor = $this->getEditor();
+
+        $this->setEntityRestrictions($newBook, ['view', 'edit', 'delete'], $editor->roles);
+
+        $movePageResp = $this->actingAs($editor)->put($page->getUrl('/move'), [
+            'entity_selection' => 'book:' . $newBook->id
+        ]);
+        $this->assertPermissionError($movePageResp);
+
+        $this->setEntityRestrictions($newBook, ['view', 'edit', 'delete', 'create'], $editor->roles);
+        $movePageResp = $this->put($page->getUrl('/move'), [
+            'entity_selection' => 'book:' . $newBook->id
+        ]);
+
+        $page = Page::find($page->id);
+        $movePageResp->assertRedirect($page->getUrl());
+
+        $this->assertTrue($page->book->id == $newBook->id, 'Page book is now the new book');
+    }
+
     public function test_chapter_move()
     {
-        $chapter = \BookStack\Chapter::first();
+        $chapter = Chapter::first();
         $currentBook = $chapter->book;
         $pageToCheck = $chapter->pages->first();
-        $newBook = \BookStack\Book::where('id', '!=', $currentBook->id)->first();
+        $newBook = Book::where('id', '!=', $currentBook->id)->first();
 
-        $chapterMoveResp = $this->asAdmin()->get($chapter->getUrl() . '/move');
+        $chapterMoveResp = $this->asEditor()->get($chapter->getUrl('/move'));
         $chapterMoveResp->assertSee('Move Chapter');
 
-        $moveChapterResp = $this->put($chapter->getUrl() . '/move', [
+        $moveChapterResp = $this->put($chapter->getUrl('/move'), [
             'entity_selection' => 'book:' . $newBook->id
         ]);
 
-        $chapter = \BookStack\Chapter::find($chapter->id);
+        $chapter = Chapter::find($chapter->id);
         $moveChapterResp->assertRedirect($chapter->getUrl());
         $this->assertTrue($chapter->book->id === $newBook->id, 'Chapter Book is now the new book');
 
@@ -71,7 +97,7 @@ class SortTest extends TestCase
         $newBookResp->assertSee('moved chapter');
         $newBookResp->assertSee($chapter->name);
 
-        $pageToCheck = \BookStack\Page::find($pageToCheck->id);
+        $pageToCheck = Page::find($pageToCheck->id);
         $this->assertTrue($pageToCheck->book_id === $newBook->id, 'Chapter child page\'s book id has changed to the new book');
         $pageCheckResp = $this->get($pageToCheck->getUrl());
         $pageCheckResp->assertSee($newBook->name);
@@ -104,7 +130,7 @@ class SortTest extends TestCase
             ];
         }
 
-        $sortResp = $this->asAdmin()->put($newBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)]);
+        $sortResp = $this->asEditor()->put($newBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)]);
         $sortResp->assertRedirect($newBook->getUrl());
         $sortResp->assertStatus(302);
         $this->assertDatabaseHas('chapters', [
@@ -120,4 +146,43 @@ class SortTest extends TestCase
         $checkResp->assertSee($newBook->name);
     }
 
+    public function test_page_copy()
+    {
+        $page = Page::first();
+        $currentBook = $page->book;
+        $newBook = Book::where('id', '!=', $currentBook->id)->first();
+
+        $resp = $this->asEditor()->get($page->getUrl('/copy'));
+        $resp->assertSee('Copy Page');
+
+        $movePageResp = $this->post($page->getUrl('/copy'), [
+            'entity_selection' => 'book:' . $newBook->id,
+            'name' => 'My copied test page'
+        ]);
+
+        $pageCopy = Page::where('name', '=', 'My copied test page')->first();
+
+        $movePageResp->assertRedirect($pageCopy->getUrl());
+        $this->assertTrue($pageCopy->book->id == $newBook->id, 'Page was copied to correct book');
+    }
+
+    public function test_page_copy_with_no_destination()
+    {
+        $page = Page::first();
+        $currentBook = $page->book;
+
+        $resp = $this->asEditor()->get($page->getUrl('/copy'));
+        $resp->assertSee('Copy Page');
+
+        $movePageResp = $this->post($page->getUrl('/copy'), [
+            'name' => 'My copied test page'
+        ]);
+
+        $pageCopy = Page::where('name', '=', 'My copied test page')->first();
+
+        $movePageResp->assertRedirect($pageCopy->getUrl());
+        $this->assertTrue($pageCopy->book->id == $currentBook->id, 'Page was copied to correct book');
+        $this->assertTrue($pageCopy->id !== $page->id, 'Page copy is not the same instance');
+    }
+
 }
\ No newline at end of file
index 8570ed00b6fa8390e0475cad8fbc8fafe6bdc727..29e0985c3c416b47b340a198aaf472f95d956b90 100644 (file)
@@ -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();
+    }
 }
index 8c96ae9251b3ee4d598f5e4c6a9f09d1246279c2..49912ec4c19d899a879fbee2d1640e4a9ea76ce6 100644 (file)
@@ -106,6 +106,29 @@ class ImageTest extends TestCase
         }
     }
 
+    public function test_secure_images_included_in_exports()
+    {
+        config()->set('filesystems.default', 'local_secure');
+        $this->asEditor();
+        $galleryFile = $this->getTestImage('my-secure-test-upload');
+        $page = Page::first();
+        $expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m-M') . '/my-secure-test-upload');
+
+        $upload = $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
+        $imageUrl = json_decode($upload->getContent(), true)['url'];
+        $page->html .= "<img src=\"{$imageUrl}\">";
+        $page->save();
+        $upload->assertStatus(200);
+
+        $encodedImageContent = base64_encode(file_get_contents($expectedPath));
+        $export = $this->get($page->getUrl('/export/html'));
+        $this->assertTrue(str_contains($export->getContent(), $encodedImageContent), 'Uploaded image in export content');
+
+        if (file_exists($expectedPath)) {
+            unlink($expectedPath);
+        }
+    }
+
     public function test_system_images_remain_public()
     {
         config()->set('filesystems.default', 'local_secure');
index 433ae7ff94b4569995af23a956f686aa204a9907..53e7ad3f35871d65dcaa52fc7888da2a8929675e 100644 (file)
@@ -1,7 +1,7 @@
 <?php namespace Tests;
 
 use BookStack\Book;
-use BookStack\Services\PermissionService;
+use BookStack\Entity;
 use BookStack\User;
 use BookStack\Repos\EntityRepo;
 
@@ -18,49 +18,20 @@ class RestrictionsTest extends BrowserKitTest
      */
     protected $viewer;
 
-    /**
-     * @var PermissionService
-     */
-    protected $permissionService;
-
     public function setUp()
     {
         parent::setUp();
         $this->user = $this->getEditor();
         $this->viewer = $this->getViewer();
-        $this->permissionService = $this->app[PermissionService::class];
     }
 
-    /**
-     * Manually set some permissions on an entity.
-     * @param \BookStack\Entity $entity
-     * @param $actions
-     */
-    protected function setEntityRestrictions(\BookStack\Entity $entity, $actions)
+    protected function setEntityRestrictions(Entity $entity, $actions = [], $roles = [])
     {
-        $entity->restricted = true;
-        $entity->permissions()->delete();
-
-        $role = $this->user->roles->first();
-        $viewerRole = $this->viewer->roles->first();
-
-        $permissions = [];
-        foreach ($actions as $action) {
-            $permissions[] = [
-                'role_id' => $role->id,
-                'action' => strtolower($action)
-            ];
-            $permissions[] = [
-                'role_id' => $viewerRole->id,
-                'action' => strtolower($action)
-            ];
-        }
-        $entity->permissions()->createMany($permissions);
-
-        $entity->save();
-        $entity->load('permissions');
-        $this->permissionService->buildJointPermissionsForEntity($entity);
-        $entity->load('jointPermissions');
+        $roles = [
+            $this->user->roles->first(),
+            $this->viewer->roles->first(),
+        ];
+        parent::setEntityRestrictions($entity, $actions, $roles);
     }
 
     public function test_book_view_restriction()
index 5bc66986baee15f2d8155970193f79f1592919cc..f076e6734c98bc07fec49edf342aa9cbe5a131f9 100644 (file)
@@ -16,14 +16,6 @@ class RolesTest extends BrowserKitTest
         $this->user = $this->getViewer();
     }
 
-    protected function getViewer()
-    {
-        $role = \BookStack\Role::getRole('viewer');
-        $viewer = $this->getNewBlankUser();
-        $viewer->attachRole($role);;
-        return $viewer;
-    }
-
     /**
      * Give the given user some permissions.
      * @param \BookStack\User $user
diff --git a/tests/SharedTestHelpers.php b/tests/SharedTestHelpers.php
new file mode 100644 (file)
index 0000000..325979e
--- /dev/null
@@ -0,0 +1,143 @@
+<?php namespace Tests;
+
+use BookStack\Book;
+use BookStack\Chapter;
+use BookStack\Entity;
+use BookStack\Repos\EntityRepo;
+use BookStack\Role;
+use BookStack\Services\PermissionService;
+use BookStack\Services\SettingService;
+
+trait SharedTestHelpers
+{
+
+    protected $admin;
+    protected $editor;
+
+    /**
+     * Set the current user context to be an admin.
+     * @return $this
+     */
+    public function asAdmin()
+    {
+        return $this->actingAs($this->getAdmin());
+    }
+
+    /**
+     * Get the current admin user.
+     * @return mixed
+     */
+    public function getAdmin() {
+        if($this->admin === null) {
+            $adminRole = Role::getSystemRole('admin');
+            $this->admin = $adminRole->users->first();
+        }
+        return $this->admin;
+    }
+
+    /**
+     * Set the current user context to be an editor.
+     * @return $this
+     */
+    public function asEditor()
+    {
+        return $this->actingAs($this->getEditor());
+    }
+
+
+    /**
+     * Get a editor user.
+     * @return mixed
+     */
+    protected function getEditor() {
+        if($this->editor === null) {
+            $editorRole = Role::getRole('editor');
+            $this->editor = $editorRole->users->first();
+        }
+        return $this->editor;
+    }
+
+    /**
+     * Get an instance of a user with 'viewer' permissions
+     * @param $attributes
+     * @return mixed
+     */
+    protected function getViewer($attributes = [])
+    {
+        $user = \BookStack\Role::getRole('viewer')->users()->first();
+        if (!empty($attributes)) $user->forceFill($attributes)->save();
+        return $user;
+    }
+
+    /**
+     * Create and return a new book.
+     * @param array $input
+     * @return Book
+     */
+    public function newBook($input = ['name' => 'test book', 'description' => 'My new test book']) {
+        return $this->app[EntityRepo::class]->createFromInput('book', $input, false);
+    }
+
+    /**
+     * Create and return a new test chapter
+     * @param array $input
+     * @param Book $book
+     * @return Chapter
+     */
+    public function newChapter($input = ['name' => 'test chapter', 'description' => 'My new test chapter'], Book $book) {
+        return $this->app[EntityRepo::class]->createFromInput('chapter', $input, $book);
+    }
+
+    /**
+     * Create and return a new test page
+     * @param array $input
+     * @return Chapter
+     */
+    public function newPage($input = ['name' => 'test page', 'html' => 'My new test page']) {
+        $book = Book::first();
+        $entityRepo = $this->app[EntityRepo::class];
+        $draftPage = $entityRepo->getDraftPage($book);
+        return $entityRepo->publishPageDraft($draftPage, $input);
+    }
+
+    /**
+     * Quickly sets an array of settings.
+     * @param $settingsArray
+     */
+    protected function setSettings($settingsArray)
+    {
+        $settings = app(SettingService::class);
+        foreach ($settingsArray as $key => $value) {
+            $settings->put($key, $value);
+        }
+    }
+
+    /**
+     * Manually set some permissions on an entity.
+     * @param Entity $entity
+     * @param array $actions
+     * @param array $roles
+     */
+    protected function setEntityRestrictions(Entity $entity, $actions = [], $roles = [])
+    {
+        $entity->restricted = true;
+        $entity->permissions()->delete();
+
+        $permissions = [];
+        foreach ($actions as $action) {
+            foreach ($roles as $role) {
+                $permissions[] = [
+                    'role_id' => $role->id,
+                    'action' => strtolower($action)
+                ];
+            }
+        }
+        $entity->permissions()->createMany($permissions);
+
+        $entity->save();
+        $entity->load('permissions');
+        $this->app[PermissionService::class]->buildJointPermissionsForEntity($entity);
+        $entity->load('jointPermissions');
+    }
+
+}
\ No newline at end of file
index 5c37b61790b83d6324f7b0d255da230dc41fbbde..e0f160eed7a751e6702aac3fc04d1f2c86101181 100644 (file)
@@ -1,21 +1,14 @@
 <?php namespace Tests;
 
-use BookStack\Book;
-use BookStack\Chapter;
-use BookStack\Repos\EntityRepo;
-use BookStack\Role;
-use BookStack\Services\SettingService;
 use Illuminate\Foundation\Testing\DatabaseTransactions;
 use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
+use Illuminate\Foundation\Testing\TestResponse;
 
 abstract class TestCase extends BaseTestCase
 {
     use CreatesApplication;
     use DatabaseTransactions;
-
-    protected $admin;
-    protected $editor;
-
+    use SharedTestHelpers;
     /**
      * The base URL to use while testing the application.
      * @var string
@@ -23,100 +16,13 @@ abstract class TestCase extends BaseTestCase
     protected $baseUrl = 'http://localhost';
 
     /**
-     * Set the current user context to be an admin.
-     * @return $this
-     */
-    public function asAdmin()
-    {
-        return $this->actingAs($this->getAdmin());
-    }
-
-    /**
-     * Get the current admin user.
-     * @return mixed
-     */
-    public function getAdmin() {
-        if($this->admin === null) {
-            $adminRole = Role::getSystemRole('admin');
-            $this->admin = $adminRole->users->first();
-        }
-        return $this->admin;
-    }
-
-    /**
-     * Set the current user context to be an editor.
-     * @return $this
-     */
-    public function asEditor()
-    {
-        return $this->actingAs($this->getEditor());
-    }
-
-
-    /**
-     * Get a editor user.
-     * @return mixed
-     */
-    public function getEditor() {
-        if($this->editor === null) {
-            $editorRole = Role::getRole('editor');
-            $this->editor = $editorRole->users->first();
-        }
-        return $this->editor;
-    }
-
-    /**
-     * Get an instance of a user with 'viewer' permissions
-     * @param $attributes
-     * @return mixed
-     */
-    protected function getViewer($attributes = [])
-    {
-        $user = \BookStack\Role::getRole('viewer')->users()->first();
-        if (!empty($attributes)) $user->forceFill($attributes)->save();
-        return $user;
-    }
-
-    /**
-     * Create and return a new book.
-     * @param array $input
-     * @return Book
-     */
-    public function newBook($input = ['name' => 'test book', 'description' => 'My new test book']) {
-        return $this->app[EntityRepo::class]->createFromInput('book', $input, false);
-    }
-
-    /**
-     * Create and return a new test chapter
-     * @param array $input
-     * @param Book $book
-     * @return Chapter
-     */
-    public function newChapter($input = ['name' => 'test chapter', 'description' => 'My new test chapter'], Book $book) {
-        return $this->app[EntityRepo::class]->createFromInput('chapter', $input, $book);
-    }
-
-    /**
-     * Create and return a new test page
-     * @param array $input
-     * @return Chapter
-     */
-    public function newPage($input = ['name' => 'test page', 'html' => 'My new test page']) {
-        $book = Book::first();
-        $entityRepo = $this->app[EntityRepo::class];
-        $draftPage = $entityRepo->getDraftPage($book);
-        return $entityRepo->publishPageDraft($draftPage, $input);
-    }
-
-    /**
-     * Quickly sets an array of settings.
-     * @param $settingsArray
+     * Assert a permission error has occurred.
+     * @param TestResponse $response
      */
-    protected function setSettings($settingsArray)
+    protected function assertPermissionError(TestResponse $response)
     {
-        $settings = app(SettingService::class);
-        foreach ($settingsArray as $key => $value) {
-            $settings->put($key, $value);
-        }
+        $response->assertRedirect('/');
+        $this->assertTrue(session()->has('error'));
+        session()->remove('error');
     }
 }
\ No newline at end of file
diff --git a/version b/version
index 0507cd08e3bcd70182796a18b0e80854f9a6c35f..648d222611c66aceafe93f87d4732cab0b0ae249 100644 (file)
--- a/version
+++ b/version
@@ -1 +1 @@
-v0.20-dev
+v0.22-dev