mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Merge branch 'master' into draw.io
This commit is contained in:
commit
34782fbc91
62
app/Console/Commands/DeleteUsers.php
Normal file
62
app/Console/Commands/DeleteUsers.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\User;
|
||||
use BookStack\Repos\UserRepo;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DeleteUsers extends Command{
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'bookstack:delete-users';
|
||||
|
||||
protected $user;
|
||||
|
||||
protected $userRepo;
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Delete users that are not "admin" or system users.';
|
||||
|
||||
public function __construct(User $user, UserRepo $userRepo)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->userRepo = $userRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$confirm = $this->ask('This will delete all users from the system that are not "admin" or system users. Are you sure you want to continue? (Type "yes" to continue)');
|
||||
$numDeleted = 0;
|
||||
if (strtolower(trim($confirm)) === 'yes')
|
||||
{
|
||||
$totalUsers = $this->user->count();
|
||||
$users = $this->user->where('system_name', '=', null)->with('roles')->get();
|
||||
foreach ($users as $user)
|
||||
{
|
||||
if ($user->hasSystemRole('admin'))
|
||||
{
|
||||
// don't delete users with "admin" role
|
||||
continue;
|
||||
}
|
||||
$this->userRepo->destroy($user);
|
||||
++$numDeleted;
|
||||
}
|
||||
$this->info("Deleted $numDeleted of $totalUsers total users.");
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->info('Exiting...');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -4,11 +4,14 @@ 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;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
@ -60,9 +63,32 @@ class Handler extends ExceptionHandler
|
||||
return response()->view('errors/' . $code, ['message' => $message], $code);
|
||||
}
|
||||
|
||||
// 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 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
|
||||
|
@ -155,7 +155,7 @@ class BookController extends Controller
|
||||
$book = $this->entityRepo->getBySlug('book', $bookSlug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
$bookChildren = $this->entityRepo->getBookChildren($book, true);
|
||||
$books = $this->entityRepo->getAll('book', false);
|
||||
$books = $this->entityRepo->getAll('book', false, 'update');
|
||||
$this->setPageTitle(trans('entities.books_sort_named', ['bookName'=>$book->getShortName()]));
|
||||
return view('books/sort', ['book' => $book, 'current' => $book, 'books' => $books, 'bookChildren' => $bookChildren]);
|
||||
}
|
||||
@ -190,42 +190,56 @@ class BookController extends Controller
|
||||
}
|
||||
|
||||
// Sort pages and chapters
|
||||
$sortedBooks = [];
|
||||
$updatedModels = collect();
|
||||
$sortMap = json_decode($request->get('sort-tree'));
|
||||
$defaultBookId = $book->id;
|
||||
$sortMap = collect(json_decode($request->get('sort-tree')));
|
||||
$bookIdsInvolved = collect([$book->id]);
|
||||
|
||||
// Loop through contents of provided map and update entities accordingly
|
||||
foreach ($sortMap as $bookChild) {
|
||||
$priority = $bookChild->sort;
|
||||
$id = intval($bookChild->id);
|
||||
$isPage = $bookChild->type == 'page';
|
||||
$bookId = $this->entityRepo->exists('book', $bookChild->book) ? intval($bookChild->book) : $defaultBookId;
|
||||
$chapterId = ($isPage && $bookChild->parentChapter === false) ? 0 : intval($bookChild->parentChapter);
|
||||
$model = $this->entityRepo->getById($isPage?'page':'chapter', $id);
|
||||
// Load models into map
|
||||
$sortMap->each(function($mapItem) use ($bookIdsInvolved) {
|
||||
$mapItem->type = ($mapItem->type === 'page' ? 'page' : 'chapter');
|
||||
$mapItem->model = $this->entityRepo->getById($mapItem->type, $mapItem->id);
|
||||
// Store source and target books
|
||||
$bookIdsInvolved->push(intval($mapItem->model->book_id));
|
||||
$bookIdsInvolved->push(intval($mapItem->book));
|
||||
});
|
||||
|
||||
// Update models only if there's a change in parent chain or ordering.
|
||||
if ($model->priority !== $priority || $model->book_id !== $bookId || ($isPage && $model->chapter_id !== $chapterId)) {
|
||||
$this->entityRepo->changeBook($isPage?'page':'chapter', $bookId, $model);
|
||||
$model->priority = $priority;
|
||||
if ($isPage) $model->chapter_id = $chapterId;
|
||||
// Get the books involved in the sort
|
||||
$bookIdsInvolved = $bookIdsInvolved->unique()->toArray();
|
||||
$booksInvolved = $this->entityRepo->book->newQuery()->whereIn('id', $bookIdsInvolved)->get();
|
||||
// Throw permission error if invalid ids or inaccessible books given.
|
||||
if (count($bookIdsInvolved) !== count($booksInvolved)) {
|
||||
$this->showPermissionError();
|
||||
}
|
||||
// Check permissions of involved books
|
||||
$booksInvolved->each(function(Book $book) {
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
});
|
||||
|
||||
// Perform the sort
|
||||
$sortMap->each(function($mapItem) {
|
||||
$model = $mapItem->model;
|
||||
|
||||
$priorityChanged = intval($model->priority) !== intval($mapItem->sort);
|
||||
$bookChanged = intval($model->book_id) !== intval($mapItem->book);
|
||||
$chapterChanged = ($mapItem->type === 'page') && intval($model->chapter_id) !== $mapItem->parentChapter;
|
||||
|
||||
if ($bookChanged) {
|
||||
$this->entityRepo->changeBook($mapItem->type, $mapItem->book, $model);
|
||||
}
|
||||
if ($chapterChanged) {
|
||||
$model->chapter_id = intval($mapItem->parentChapter);
|
||||
$model->save();
|
||||
$updatedModels->push($model);
|
||||
}
|
||||
if ($priorityChanged) {
|
||||
$model->priority = intval($mapItem->sort);
|
||||
$model->save();
|
||||
}
|
||||
});
|
||||
|
||||
// Store involved books to be sorted later
|
||||
if (!in_array($bookId, $sortedBooks)) {
|
||||
$sortedBooks[] = $bookId;
|
||||
}
|
||||
}
|
||||
|
||||
// Add activity for books
|
||||
foreach ($sortedBooks as $bookId) {
|
||||
/** @var Book $updatedBook */
|
||||
$updatedBook = $this->entityRepo->getById('book', $bookId);
|
||||
$this->entityRepo->buildJointPermissionsForBook($updatedBook);
|
||||
Activity::add($updatedBook, 'book_sort', $updatedBook->id);
|
||||
}
|
||||
// Rebuild permissions and add activity for involved books.
|
||||
$booksInvolved->each(function(Book $book) {
|
||||
$this->entityRepo->buildJointPermissionsForBook($book);
|
||||
Activity::add($book, 'book_sort', $book->id);
|
||||
});
|
||||
|
||||
return redirect($book->getUrl());
|
||||
}
|
||||
|
@ -145,6 +145,7 @@ class PageController extends Controller
|
||||
* @param string $bookSlug
|
||||
* @param string $pageSlug
|
||||
* @return Response
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function show($bookSlug, $pageSlug)
|
||||
{
|
||||
@ -152,7 +153,7 @@ class PageController extends Controller
|
||||
$page = $this->entityRepo->getBySlug('page', $pageSlug, $bookSlug);
|
||||
} catch (NotFoundException $e) {
|
||||
$page = $this->entityRepo->getPageByOldSlug($pageSlug, $bookSlug);
|
||||
if ($page === null) abort(404);
|
||||
if ($page === null) throw $e;
|
||||
return redirect($page->getUrl());
|
||||
}
|
||||
|
||||
|
@ -249,4 +249,27 @@ class UserController extends Controller
|
||||
'assetCounts' => $assetCounts
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's preferred book-list display setting.
|
||||
* @param $id
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function switchBookView($id, Request $request) {
|
||||
$this->checkPermissionOr('users-manage', function () use ($id) {
|
||||
return $this->currentUser->id == $id;
|
||||
});
|
||||
|
||||
$viewType = $request->get('book_view_type');
|
||||
if (!in_array($viewType, ['grid', 'list'])) {
|
||||
$viewType = 'list';
|
||||
}
|
||||
|
||||
$user = $this->user->findOrFail($id);
|
||||
setting()->putUser($user, 'books_view_type', $viewType);
|
||||
|
||||
return redirect()->back(302, [], "/settings/users/$id");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,6 +33,14 @@ 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',
|
||||
|
@ -113,9 +113,9 @@ class EntityRepo
|
||||
* @param bool $allowDrafts
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
protected function entityQuery($type, $allowDrafts = false)
|
||||
protected function entityQuery($type, $allowDrafts = false, $permission = 'view')
|
||||
{
|
||||
$q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), 'view');
|
||||
$q = $this->permissionService->enforceEntityRestrictions($type, $this->getEntity($type), $permission);
|
||||
if (strtolower($type) === 'page' && !$allowDrafts) {
|
||||
$q = $q->where('draft', '=', false);
|
||||
}
|
||||
@ -196,14 +196,15 @@ class EntityRepo
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entities of a type limited by count unless count if false.
|
||||
* Get all entities of a type with the given permission, limited by count unless count is false.
|
||||
* @param string $type
|
||||
* @param integer|bool $count
|
||||
* @param string $permission
|
||||
* @return Collection
|
||||
*/
|
||||
public function getAll($type, $count = 20)
|
||||
public function getAll($type, $count = 20, $permission = 'view')
|
||||
{
|
||||
$q = $this->entityQuery($type)->orderBy('name', 'asc');
|
||||
$q = $this->entityQuery($type, false, $permission)->orderBy('name', 'asc');
|
||||
if ($count !== false) $q = $q->take($count);
|
||||
return $q->get();
|
||||
}
|
||||
@ -690,6 +691,7 @@ class EntityRepo
|
||||
preg_match_all("/{{@\s?([0-9].*?)}}/", $content, $matches);
|
||||
if (count($matches[0]) === 0) return $content;
|
||||
|
||||
$topLevelTags = ['table', 'ul', 'ol'];
|
||||
foreach ($matches[1] as $index => $includeId) {
|
||||
$splitInclude = explode('#', $includeId, 2);
|
||||
$pageId = intval($splitInclude[0]);
|
||||
@ -714,9 +716,14 @@ class EntityRepo
|
||||
continue;
|
||||
}
|
||||
$innerContent = '';
|
||||
$isTopLevel = in_array(strtolower($matchingElem->nodeName), $topLevelTags);
|
||||
if ($isTopLevel) {
|
||||
$innerContent .= $doc->saveHTML($matchingElem);
|
||||
} else {
|
||||
foreach ($matchingElem->childNodes as $childNode) {
|
||||
$innerContent .= $doc->saveHTML($childNode);
|
||||
}
|
||||
}
|
||||
$content = str_replace($matches[0][$index], trim($innerContent), $content);
|
||||
}
|
||||
|
||||
|
@ -115,9 +115,9 @@ class UserRepo
|
||||
*/
|
||||
public function isOnlyAdmin(User $user)
|
||||
{
|
||||
if (!$user->roles->pluck('name')->contains('admin')) return false;
|
||||
if (!$user->hasSystemRole('admin')) return false;
|
||||
|
||||
$adminRole = $this->role->getRole('admin');
|
||||
$adminRole = $this->role->getSystemRole('admin');
|
||||
if ($adminRole->users->count() > 1) return false;
|
||||
return true;
|
||||
}
|
||||
|
@ -98,6 +98,9 @@ class SettingService
|
||||
{
|
||||
$cacheKey = $this->cachePrefix . $key;
|
||||
$this->cache->forget($cacheKey);
|
||||
if (isset($this->localCache[$key])) {
|
||||
unset($this->localCache[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -81,7 +81,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
||||
*/
|
||||
public function hasSystemRole($role)
|
||||
{
|
||||
return $this->roles->pluck('system_name')->contains('admin');
|
||||
return $this->roles->pluck('system_name')->contains($role);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
$viewPaths = [realpath(base_path('resources/views'))];
|
||||
if ($theme = env('APP_THEME', false)) {
|
||||
array_unshift($viewPaths, base_path('themes/' . $theme));
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
@ -13,9 +18,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'paths' => [
|
||||
realpath(base_path('resources/views')),
|
||||
],
|
||||
'paths' => $viewPaths,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -72,7 +72,13 @@ Some strings have colon-prefixed variables in such as `:userName`. Leave these v
|
||||
|
||||
Feel free to create issues to request new features or to report bugs and problems. Just please follow the template given when creating the issue.
|
||||
|
||||
Pull requests are very welcome. If the scope of your pull request is very large it may be best to open the pull request early or create an issue for it to discuss how it will fit in to the project and plan out the merge.
|
||||
### Pull Request
|
||||
|
||||
Pull requests are very welcome. If the scope of your pull request is large it may be best to open the pull request early or create an issue for it to discuss how it will fit in to the project and plan out the merge.
|
||||
|
||||
Pull requests should be created from the `master` branch and should be merged back into `master` once done. Please do not build from or request a merge into the `release` branch as this is only for publishing releases.
|
||||
|
||||
If you are looking to alter CSS or JavaScript content please edit the source files found in `resources/assets`. Any CSS or JS files within `public` are built from these source files and therefore should not be edited directly.
|
||||
|
||||
## Website, Docs & Blog
|
||||
|
||||
|
@ -549,7 +549,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||
.content {
|
||||
padding: $-s;
|
||||
font-size: 0.666em;
|
||||
p, ul {
|
||||
p, ul, ol {
|
||||
font-size: $fs-m;
|
||||
margin: .5em 0;
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ return [
|
||||
'toggle_details' => 'Toggle Details',
|
||||
'toggle_thumbnails' => 'Toggle Thumbnails',
|
||||
'details' => 'Details',
|
||||
'grid_view' => 'Grid View',
|
||||
'list_view' => 'List View',
|
||||
|
||||
/**
|
||||
* Header
|
||||
|
@ -96,7 +96,6 @@ return [
|
||||
'users_external_auth_id' => 'External Authentication ID',
|
||||
'users_password_warning' => 'Only fill the below if you would like to change your password:',
|
||||
'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.',
|
||||
'users_books_view_type' => 'Preferred layout for books viewing',
|
||||
'users_delete' => 'Delete User',
|
||||
'users_delete_named' => 'Delete user :userName',
|
||||
'users_delete_warning' => 'This will fully delete this user with the name \':userName\' from the system.',
|
||||
|
@ -1,8 +1,21 @@
|
||||
@extends('sidebar-layout')
|
||||
|
||||
@section('toolbar')
|
||||
<div class="col-xs-1"></div>
|
||||
<div class="col-xs-11 faded">
|
||||
<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"><i class="zmdi zmdi-view-module"></i>{{ trans('common.grid_view') }}</button>
|
||||
@else
|
||||
<button type="submit" class="text-pos text-button"><i class="zmdi zmdi-view-list"></i>{{ trans('common.list_view') }}</button>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6 faded">
|
||||
<div class="action-buttons">
|
||||
@if($currentUser->can('book-create-all'))
|
||||
<a href="{{ baseUrl("/books/create") }}" class="text-pos text-button"><i class="zmdi zmdi-plus"></i>{{ trans('entities.books_create') }}</a>
|
||||
|
@ -1,8 +1,6 @@
|
||||
@extends('simple-layout')
|
||||
|
||||
@section('content')
|
||||
|
||||
|
||||
<div class="container">
|
||||
|
||||
<p> </p>
|
||||
@ -16,7 +14,6 @@
|
||||
</div>
|
||||
|
||||
@if (setting('app-public') || !user()->isDefault())
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
|
@ -6,7 +6,7 @@
|
||||
<div class="card">
|
||||
<h3 class="text-muted">{{ trans('errors.error_occurred') }}</h3>
|
||||
<div class="body">
|
||||
<h5>{{ $message }}</h5>
|
||||
<h5>{{ $message or 'An unknown error occurred' }}</h5>
|
||||
<p><a href="{{ baseUrl('/') }}" class="button outline">{{ trans('errors.return_home') }}</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -43,13 +43,6 @@
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="books-view-type">{{ trans('settings.users_books_view_type') }}</label>
|
||||
<select name="setting[books_view_type]" id="books-view-type">
|
||||
<option @if(setting()->getUser($user, 'books_view_type', 'list') === 'list') selected @endif value="list">List</option>
|
||||
<option @if(setting()->getUser($user, 'books_view_type', 'list') === 'grid') selected @endif value="grid">Grid</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group text-right">
|
||||
|
@ -149,6 +149,7 @@ Route::group(['middleware' => 'auth'], function () {
|
||||
Route::get('/users', 'UserController@index');
|
||||
Route::get('/users/create', 'UserController@create');
|
||||
Route::get('/users/{id}/delete', 'UserController@delete');
|
||||
Route::patch('/users/{id}/switch-book-view', 'UserController@switchBookView');
|
||||
Route::post('/users/create', 'UserController@store');
|
||||
Route::get('/users/{id}', 'UserController@edit');
|
||||
Route::put('/users/{id}', 'UserController@update');
|
||||
|
@ -3,7 +3,6 @@
|
||||
use BookStack\Entity;
|
||||
use BookStack\Role;
|
||||
use BookStack\Services\PermissionService;
|
||||
use BookStack\User;
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Laravel\BrowserKitTesting\TestCase;
|
||||
|
@ -82,6 +82,27 @@ class EntityTest extends BrowserKitTest
|
||||
->see($firstChapter->name);
|
||||
}
|
||||
|
||||
public function test_toggle_book_view()
|
||||
{
|
||||
$editor = $this->getEditor();
|
||||
setting()->putUser($editor, 'books_view_type', 'grid');
|
||||
|
||||
$this->actingAs($editor)
|
||||
->visit('/books')
|
||||
->pageHasElement('.featured-image-container')
|
||||
->submitForm('List View')
|
||||
// Check redirection.
|
||||
->seePageIs('/books')
|
||||
->pageNotHasElement('.featured-image-container');
|
||||
|
||||
$this->actingAs($editor)
|
||||
->visit('/books')
|
||||
->submitForm('Grid View')
|
||||
->seePageIs('/books')
|
||||
->pageHasElement('.featured-image-container');
|
||||
|
||||
}
|
||||
|
||||
public function pageCreation($chapter)
|
||||
{
|
||||
$page = factory(Page::class)->make([
|
||||
|
@ -9,7 +9,7 @@ class PageContentTest extends TestCase
|
||||
public function test_page_includes()
|
||||
{
|
||||
$page = Page::first();
|
||||
$secondPage = Page::all()->get(2);
|
||||
$secondPage = Page::where('id', '!=', $page->id)->first();
|
||||
|
||||
$secondPage->html = "<p id='section1'>Hello, This is a test</p><p id='section2'>This is a second block of content</p>";
|
||||
$secondPage->save();
|
||||
@ -38,7 +38,7 @@ class PageContentTest extends TestCase
|
||||
public function test_saving_page_with_includes()
|
||||
{
|
||||
$page = Page::first();
|
||||
$secondPage = Page::all()->get(2);
|
||||
$secondPage = Page::where('id', '!=', $page->id)->first();
|
||||
$this->asEditor();
|
||||
$page->html = "<p>{{@$secondPage->id}}</p>";
|
||||
|
||||
@ -50,6 +50,23 @@ class PageContentTest extends TestCase
|
||||
$this->assertContains("{{@$secondPage->id}}", $page->html);
|
||||
}
|
||||
|
||||
public function test_page_includes_do_not_break_tables()
|
||||
{
|
||||
$page = Page::first();
|
||||
$secondPage = Page::where('id', '!=', $page->id)->first();
|
||||
|
||||
$content = '<table id="table"><tbody><tr><td>test</td></tr></tbody></table>';
|
||||
$secondPage->html = $content;
|
||||
$secondPage->save();
|
||||
|
||||
$page->html = "{{@{$secondPage->id}#table}}";
|
||||
$page->save();
|
||||
|
||||
$this->asEditor();
|
||||
$pageResp = $this->get($page->getUrl());
|
||||
$pageResp->assertSee($content);
|
||||
}
|
||||
|
||||
public function test_page_revision_views_viewable()
|
||||
{
|
||||
$this->asEditor();
|
||||
|
18
tests/ErrorTest.php
Normal file
18
tests/ErrorTest.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php namespace Tests;
|
||||
|
||||
class ErrorTest extends TestCase
|
||||
{
|
||||
|
||||
public function test_404_page_does_not_show_login()
|
||||
{
|
||||
// Due to middleware being handled differently this will not fail
|
||||
// if our custom, middleware-loaded handler fails but this is here
|
||||
// as a reminder and as a general check in the event of other issues.
|
||||
$editor = $this->getEditor();
|
||||
$this->actingAs($editor);
|
||||
$notFound = $this->get('/fgfdngldfnotfound');
|
||||
$notFound->assertStatus(404);
|
||||
$notFound->assertDontSeeText('Log in');
|
||||
$notFound->assertSeeText($editor->getShortName(9));
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
use BookStack\Book;
|
||||
use BookStack\Services\PermissionService;
|
||||
use BookStack\User;
|
||||
use BookStack\Repos\EntityRepo;
|
||||
|
||||
class RestrictionsTest extends BrowserKitTest
|
||||
{
|
||||
@ -554,4 +555,70 @@ class RestrictionsTest extends BrowserKitTest
|
||||
$this->dontSee(substr($bookChapter->name, 0, 15));
|
||||
}
|
||||
|
||||
public function test_book_sort_view_permission()
|
||||
{
|
||||
$firstBook = Book::first();
|
||||
$secondBook = Book::find(2);
|
||||
$thirdBook = Book::find(3);
|
||||
|
||||
$this->setEntityRestrictions($firstBook, ['view', 'update']);
|
||||
$this->setEntityRestrictions($secondBook, ['view']);
|
||||
$this->setEntityRestrictions($thirdBook, ['view', 'update']);
|
||||
|
||||
// Test sort page visibility
|
||||
$this->actingAs($this->user)->visit($secondBook->getUrl() . '/sort')
|
||||
->see('You do not have permission')
|
||||
->seePageIs('/');
|
||||
|
||||
// Check sort page on first book
|
||||
$this->actingAs($this->user)->visit($firstBook->getUrl() . '/sort')
|
||||
->see($thirdBook->name)
|
||||
->dontSee($secondBook->name);
|
||||
}
|
||||
|
||||
public function test_book_sort_permission() {
|
||||
$firstBook = Book::first();
|
||||
$secondBook = Book::find(2);
|
||||
|
||||
$this->setEntityRestrictions($firstBook, ['view', 'update']);
|
||||
$this->setEntityRestrictions($secondBook, ['view']);
|
||||
|
||||
$firstBookChapter = $this->app[EntityRepo::class]->createFromInput('chapter',
|
||||
['name' => 'first book chapter'], $firstBook);
|
||||
$secondBookChapter = $this->app[EntityRepo::class]->createFromInput('chapter',
|
||||
['name' => 'second book chapter'], $secondBook);
|
||||
|
||||
// Create request data
|
||||
$reqData = [
|
||||
[
|
||||
'id' => $firstBookChapter->id,
|
||||
'sort' => 0,
|
||||
'parentChapter' => false,
|
||||
'type' => 'chapter',
|
||||
'book' => $secondBook->id
|
||||
]
|
||||
];
|
||||
|
||||
// Move chapter from first book to a second book
|
||||
$this->actingAs($this->user)->put($firstBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)])
|
||||
->followRedirects()
|
||||
->see('You do not have permission')
|
||||
->seePageIs('/');
|
||||
|
||||
$reqData = [
|
||||
[
|
||||
'id' => $secondBookChapter->id,
|
||||
'sort' => 0,
|
||||
'parentChapter' => false,
|
||||
'type' => 'chapter',
|
||||
'book' => $firstBook->id
|
||||
]
|
||||
];
|
||||
|
||||
// Move chapter from second book to first book
|
||||
$this->actingAs($this->user)->put($firstBook->getUrl() . '/sort', ['sort-tree' => json_encode($reqData)])
|
||||
->followRedirects()
|
||||
->see('You do not have permission')
|
||||
->seePageIs('/');
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ class UserProfileTest extends BrowserKitTest
|
||||
$this->actingAs($editor)
|
||||
->visit('/books')
|
||||
->pageNotHasElement('.featured-image-container')
|
||||
->pageHasElement('.entity-list-item');
|
||||
->pageHasElement('.content .entity-list-item');
|
||||
}
|
||||
|
||||
public function test_books_view_is_grid()
|
||||
|
2
themes/.gitignore
vendored
Executable file
2
themes/.gitignore
vendored
Executable file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
Loading…
Reference in New Issue
Block a user