Merge branch 'development' into release

This commit is contained in:
Dan Brown
2022-08-09 13:55:40 +01:00
18 changed files with 98 additions and 23 deletions

View File

@ -267,3 +267,4 @@ Filip Antala (AntalaFilip) :: Slovak
mcgong (GongMingCai) :: Chinese Simplified; Chinese Traditional
Nanang Setia Budi (sefidananang) :: Indonesian
Андрей Павлов (andrei.pavlov) :: Russian
Alex Navarro (alex.n.navarro) :: Portuguese, Brazilian

View File

@ -29,6 +29,9 @@ class ActivityType
const COMMENTED_ON = 'commented_on';
const PERMISSIONS_UPDATE = 'permissions_update';
const REVISION_RESTORE = 'revision_restore';
const REVISION_DELETE = 'revision_delete';
const SETTINGS_UPDATE = 'settings_update';
const MAINTENANCE_ACTION_RUN = 'maintenance_action_run';

View File

@ -3,6 +3,7 @@
namespace BookStack\Entities\Models;
use BookStack\Auth\User;
use BookStack\Interfaces\Loggable;
use BookStack\Model;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -27,7 +28,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property Page $page
* @property-read ?User $createdBy
*/
class PageRevision extends Model
class PageRevision extends Model implements Loggable
{
protected $fillable = ['name', 'text', 'summary'];
protected $hidden = ['html', 'markdown', 'restricted', 'text'];
@ -83,4 +84,9 @@ class PageRevision extends Model
{
return $type === 'revision';
}
public function logDescriptor(): string
{
return "Revision #{$this->revision_number} (ID: {$this->id}) for page ID {$this->page_id}";
}
}

View File

@ -337,6 +337,7 @@ class PageRepo
$this->savePageRevision($page, $summary);
Activity::add(ActivityType::PAGE_RESTORE, $page);
Activity::add(ActivityType::REVISION_RESTORE, $revision);
return $page;
}

View File

@ -5,7 +5,7 @@ namespace BookStack\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @see \BookStack\Actions\ActivityLogger
* @mixin \BookStack\Actions\ActivityLogger
*/
class Activity extends Facade
{

View File

@ -36,26 +36,26 @@ class UserApiController extends ApiController
{
return [
'create' => [
'name' => ['required', 'min:2'],
'name' => ['required', 'min:2', 'max:100'],
'email' => [
'required', 'min:2', 'email', new Unique('users', 'email'),
],
'external_auth_id' => ['string'],
'language' => ['string'],
'language' => ['string', 'max:15', 'alpha_dash'],
'password' => [Password::default()],
'roles' => ['array'],
'roles.*' => ['integer'],
'send_invite' => ['boolean'],
],
'update' => [
'name' => ['min:2'],
'name' => ['min:2', 'max:100'],
'email' => [
'min:2',
'email',
(new Unique('users', 'email'))->ignore($userId ?? null),
],
'external_auth_id' => ['string'],
'language' => ['string'],
'language' => ['string', 'max:15', 'alpha_dash'],
'password' => [Password::default()],
'roles' => ['array'],
'roles.*' => ['integer'],

View File

@ -30,9 +30,9 @@ class RegisterController extends Controller
use RegistersUsers;
protected $socialAuthService;
protected $registrationService;
protected $loginService;
protected SocialAuthService $socialAuthService;
protected RegistrationService $registrationService;
protected LoginService $loginService;
/**
* Where to redirect users after login / registration.
@ -69,7 +69,7 @@ class RegisterController extends Controller
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'min:2', 'max:255'],
'name' => ['required', 'min:2', 'max:100'],
'email' => ['required', 'email', 'max:255', 'unique:users'],
'password' => ['required', Password::default()],
]);

View File

@ -2,9 +2,11 @@
namespace BookStack\Http\Controllers;
use BookStack\Actions\ActivityType;
use BookStack\Entities\Repos\PageRepo;
use BookStack\Entities\Tools\PageContent;
use BookStack\Exceptions\NotFoundException;
use BookStack\Facades\Activity;
use Ssddanbrown\HtmlDiff\Diff;
class PageRevisionController extends Controller
@ -132,6 +134,7 @@ class PageRevisionController extends Controller
}
$revision->delete();
Activity::add(ActivityType::REVISION_DELETE, $revision);
$this->showSuccessNotification(trans('entities.revision_delete_success'));
return redirect($page->getUrl('/revisions'));

View File

@ -18,8 +18,8 @@ use Illuminate\Validation\ValidationException;
class UserController extends Controller
{
protected $userRepo;
protected $imageRepo;
protected UserRepo $userRepo;
protected ImageRepo $imageRepo;
/**
* UserController constructor.
@ -81,9 +81,9 @@ class UserController extends Controller
$passwordRequired = ($authMethod === 'standard' && !$sendInvite);
$validationRules = [
'name' => ['required'],
'name' => ['required', 'max:100'],
'email' => ['required', 'email', 'unique:users,email'],
'language' => ['string'],
'language' => ['string', 'max:15', 'alpha_dash'],
'roles' => ['array'],
'roles.*' => ['integer'],
'password' => $passwordRequired ? ['required', Password::default()] : null,
@ -139,11 +139,11 @@ class UserController extends Controller
$this->checkPermissionOrCurrentUser('users-manage', $id);
$validated = $this->validate($request, [
'name' => ['min:2'],
'name' => ['min:2', 'max:100'],
'email' => ['min:2', 'email', 'unique:users,email,' . $id],
'password' => ['required_with:password_confirm', Password::default()],
'password-confirm' => ['same:password', 'required_with:password'],
'language' => ['string'],
'language' => ['string', 'max:15', 'alpha_dash'],
'roles' => ['array'],
'roles.*' => ['integer'],
'external_auth_id' => ['string'],

View File

@ -171,7 +171,7 @@ return [
'chapters_permissions_active' => 'Hoofdstuk Machtigingen Actief',
'chapters_permissions_success' => 'Hoofdstuk Machtigingen Bijgewerkt',
'chapters_search_this' => 'Doorzoek dit hoofdstuk',
'chapter_sort_book' => 'Sort Book',
'chapter_sort_book' => 'Sorteer Boek',
// Pages
'page' => 'Pagina',

View File

@ -28,8 +28,8 @@ return [
// Books
'book_create' => 'criou o livro',
'book_create_notification' => 'Livro criado com sucesso',
'book_create_from_chapter' => 'converted chapter to book',
'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
'book_create_from_chapter' => 'capítulo convertido em livro',
'book_create_from_chapter_notification' => 'Capítulo convertido com sucesso em um livro',
'book_update' => 'atualizou o livro',
'book_update_notification' => 'Livro atualizado com sucesso',
'book_delete' => 'excluiu o livro',
@ -40,8 +40,8 @@ return [
// Bookshelves
'bookshelf_create' => 'prateleira criada',
'bookshelf_create_notification' => 'Prateleira criada com sucesso',
'bookshelf_create_from_book' => 'converted book to bookshelf',
'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf',
'bookshelf_create_from_book' => 'livro convertido em estante',
'bookshelf_create_from_book_notification' => 'Capítulo convertido com sucesso em um livro',
'bookshelf_update' => 'atualizou a prateleira',
'bookshelf_update_notification' => 'Prateleira atualizada com sucesso',
'bookshelf_delete' => 'excluiu a prateleira',

View File

@ -12,8 +12,10 @@
@include('exports.parts.custom-head')
</head>
<body class="export export-format-{{ $format }} export-engine-{{ $engine ?? 'none' }}">
@include('layouts.parts.export-body-start')
<div class="page-content">
@yield('content')
</div>
@include('layouts.parts.export-body-end')
</body>
</html>

View File

@ -0,0 +1,2 @@
{{-- This is a placeholder template file provided as a --}}
{{-- convenience to users of the visual theme system. --}}

View File

@ -0,0 +1,2 @@
{{-- This is a placeholder template file provided as a --}}
{{-- convenience to users of the visual theme system. --}}

View File

@ -13,7 +13,12 @@
<div component="dropdown" class="list-sort-type dropdown-container">
<label for="">{{ trans('settings.audit_event_filter') }}</label>
<button refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button>
<button refs="dropdown@toggle"
type="button"
aria-haspopup="true"
aria-expanded="false"
aria-label="{{ trans('common.sort_options') }}"
class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button>
<ul refs="dropdown@menu" class="dropdown-menu">
<li @if($listDetails['event'] === '') class="active" @endif><a href="{{ sortUrl('/settings/audit', $listDetails, ['event' => '']) }}" class="text-item">{{ trans('settings.audit_event_filter_no_filter') }}</a></li>
@foreach($activityTypes as $type)

View File

@ -2,6 +2,7 @@
namespace Tests\Entity;
use BookStack\Actions\ActivityType;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Repos\PageRepo;
use Tests\TestCase;
@ -117,6 +118,9 @@ class PageRevisionTest extends TestCase
'type' => 'version',
'summary' => "Restored from #{$revToRestore->id}; My first update",
]);
$detail = "Revision #{$revToRestore->revision_number} (ID: {$revToRestore->id}) for page ID {$revToRestore->page_id}";
$this->assertActivityExists(ActivityType::REVISION_RESTORE, null, $detail);
}
public function test_page_revision_count_increments_on_update()
@ -164,6 +168,9 @@ class PageRevisionTest extends TestCase
$this->assertTrue($beforeRevisionCount === ($afterRevisionCount + 1));
$detail = "Revision #{$revision->revision_number} (ID: {$revision->id}) for page ID {$revision->page_id}";
$this->assertActivityExists(ActivityType::REVISION_DELETE, null, $detail);
// Try to delete the latest revision
$beforeRevisionCount = $page->revisions->count();
$resp = $this->asEditor()->delete($page->currentRevision->getUrl('/delete/'));

View File

@ -272,7 +272,7 @@ class ThemeTest extends TestCase
$this->assertStringContainsString('Command ran!', $output);
}
public function test_body_start_and_end_template_files_can_be_used()
public function test_base_body_start_and_end_template_files_can_be_used()
{
$bodyStartStr = 'barry-fought-against-the-panther';
$bodyEndStr = 'barry-lost-his-fight-with-grace';
@ -289,6 +289,25 @@ class ThemeTest extends TestCase
});
}
public function test_export_body_start_and_end_template_files_can_be_used()
{
$bodyStartStr = 'barry-fought-against-the-panther';
$bodyEndStr = 'barry-lost-his-fight-with-grace';
/** @var Page $page */
$page = Page::query()->first();
$this->usingThemeFolder(function (string $folder) use ($bodyStartStr, $bodyEndStr, $page) {
$viewDir = theme_path('layouts/parts');
mkdir($viewDir, 0777, true);
file_put_contents($viewDir . '/export-body-start.blade.php', $bodyStartStr);
file_put_contents($viewDir . '/export-body-end.blade.php', $bodyEndStr);
$resp = $this->asEditor()->get($page->getUrl('/export/html'));
$resp->assertSee($bodyStartStr);
$resp->assertSee($bodyEndStr);
});
}
protected function usingThemeFolder(callable $callback)
{
// Create a folder and configure a theme

View File

@ -234,4 +234,28 @@ class UserManagementTest extends TestCase
$this->assertDatabaseMissing('activities', ['type' => 'USER_CREATE']);
}
public function test_user_create_update_fails_if_locale_is_invalid()
{
$user = $this->getEditor();
// Too long
$resp = $this->asAdmin()->put($user->getEditUrl(), ['language' => 'this_is_too_long']);
$resp->assertSessionHasErrors(['language' => 'The language may not be greater than 15 characters.']);
session()->flush();
// Invalid characters
$resp = $this->put($user->getEditUrl(), ['language' => 'en<GB']);
$resp->assertSessionHasErrors(['language' => 'The language may only contain letters, numbers, dashes and underscores.']);
session()->flush();
// Both on create
$resp = $this->post('/settings/users/create', [
'language' => 'en<GB_and_this_is_longer',
'name' => 'My name',
'email' => 'jimmy@example.com',
]);
$resp->assertSessionHasErrors(['language' => 'The language may not be greater than 15 characters.']);
$resp->assertSessionHasErrors(['language' => 'The language may only contain letters, numbers, dashes and underscores.']);
}
}