Skip to content

Commit 13115ac

Browse files
committed
Merge branch 'development' into release
2 parents 73f9834 + 5c9b90e commit 13115ac

File tree

202 files changed

+3762
-1487
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

202 files changed

+3762
-1487
lines changed

.github/ISSUE_TEMPLATE/support_request.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ body:
4242
label: Log Content
4343
description: If the issue has produced an error, provide any [BookStack or server log](https://www.bookstackapp.com/docs/admin/debugging/) content below.
4444
placeholder: Be sure to remove any confidential details in your logs
45+
render: text
4546
validations:
4647
required: false
4748
- type: textarea
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: Blank Request (Maintainers Only)
2+
description: For maintainers only - Start a blank request
3+
body:
4+
- type: markdown
5+
attributes:
6+
value: "**This blank request option is only for existing official maintainers of the project!** Please instead use a different request option. If you use this your issue will be closed off."
7+
- type: textarea
8+
attributes:
9+
label: Description

app/Access/Oidc/OidcService.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use BookStack\Facades\Theme;
1212
use BookStack\Http\HttpRequestService;
1313
use BookStack\Theming\ThemeEvents;
14+
use BookStack\Uploads\UserAvatars;
1415
use BookStack\Users\Models\User;
1516
use Illuminate\Support\Facades\Cache;
1617
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
@@ -26,7 +27,8 @@ public function __construct(
2627
protected RegistrationService $registrationService,
2728
protected LoginService $loginService,
2829
protected HttpRequestService $http,
29-
protected GroupSyncService $groupService
30+
protected GroupSyncService $groupService,
31+
protected UserAvatars $userAvatars
3032
) {
3133
}
3234

@@ -220,6 +222,10 @@ protected function processAccessTokenCallback(OidcAccessToken $accessToken, Oidc
220222
throw new OidcException($exception->getMessage());
221223
}
222224

225+
if ($this->config()['fetch_avatar'] && !$user->avatar()->exists() && $userDetails->picture) {
226+
$this->userAvatars->assignToUserFromUrl($user, $userDetails->picture);
227+
}
228+
223229
if ($this->shouldSyncGroups()) {
224230
$detachExisting = $this->config()['remove_from_groups'];
225231
$this->groupService->syncUserWithFoundGroups($user, $userDetails->groups ?? [], $detachExisting);

app/Access/Oidc/OidcUserDetails.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public function __construct(
1111
public ?string $email = null,
1212
public ?string $name = null,
1313
public ?array $groups = null,
14+
public ?string $picture = null,
1415
) {
1516
}
1617

@@ -40,15 +41,16 @@ public function populate(
4041
$this->email = $claims->getClaim('email') ?? $this->email;
4142
$this->name = static::getUserDisplayName($displayNameClaims, $claims) ?? $this->name;
4243
$this->groups = static::getUserGroups($groupsClaim, $claims) ?? $this->groups;
44+
$this->picture = static::getPicture($claims) ?: $this->picture;
4345
}
4446

45-
protected static function getUserDisplayName(string $displayNameClaims, ProvidesClaims $token): string
47+
protected static function getUserDisplayName(string $displayNameClaims, ProvidesClaims $claims): string
4648
{
4749
$displayNameClaimParts = explode('|', $displayNameClaims);
4850

4951
$displayName = [];
5052
foreach ($displayNameClaimParts as $claim) {
51-
$component = $token->getClaim(trim($claim)) ?? '';
53+
$component = $claims->getClaim(trim($claim)) ?? '';
5254
if ($component !== '') {
5355
$displayName[] = $component;
5456
}
@@ -57,13 +59,13 @@ protected static function getUserDisplayName(string $displayNameClaims, Provides
5759
return implode(' ', $displayName);
5860
}
5961

60-
protected static function getUserGroups(string $groupsClaim, ProvidesClaims $token): ?array
62+
protected static function getUserGroups(string $groupsClaim, ProvidesClaims $claims): ?array
6163
{
6264
if (empty($groupsClaim)) {
6365
return null;
6466
}
6567

66-
$groupsList = Arr::get($token->getAllClaims(), $groupsClaim);
68+
$groupsList = Arr::get($claims->getAllClaims(), $groupsClaim);
6769
if (!is_array($groupsList)) {
6870
return null;
6971
}
@@ -72,4 +74,14 @@ protected static function getUserGroups(string $groupsClaim, ProvidesClaims $tok
7274
return is_string($val);
7375
}));
7476
}
77+
78+
protected static function getPicture(ProvidesClaims $claims): ?string
79+
{
80+
$picture = $claims->getClaim('picture');
81+
if (is_string($picture) && str_starts_with($picture, 'http')) {
82+
return $picture;
83+
}
84+
85+
return null;
86+
}
7587
}

app/Activity/CommentRepo.php

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use BookStack\Activity\Models\Comment;
66
use BookStack\Entities\Models\Entity;
7+
use BookStack\Exceptions\NotifyException;
8+
use BookStack\Exceptions\PrettyException;
79
use BookStack\Facades\Activity as ActivityService;
810
use BookStack\Util\HtmlDescriptionFilter;
911

@@ -20,7 +22,7 @@ public function getById(int $id): Comment
2022
/**
2123
* Create a new comment on an entity.
2224
*/
23-
public function create(Entity $entity, string $html, ?int $parent_id): Comment
25+
public function create(Entity $entity, string $html, ?int $parentId, string $contentRef): Comment
2426
{
2527
$userId = user()->id;
2628
$comment = new Comment();
@@ -29,7 +31,8 @@ public function create(Entity $entity, string $html, ?int $parent_id): Comment
2931
$comment->created_by = $userId;
3032
$comment->updated_by = $userId;
3133
$comment->local_id = $this->getNextLocalId($entity);
32-
$comment->parent_id = $parent_id;
34+
$comment->parent_id = $parentId;
35+
$comment->content_ref = preg_match('/^bkmrk-(.*?):\d+:(\d*-\d*)?$/', $contentRef) === 1 ? $contentRef : '';
3336

3437
$entity->comments()->save($comment);
3538
ActivityService::add(ActivityType::COMMENT_CREATE, $comment);
@@ -52,6 +55,41 @@ public function update(Comment $comment, string $html): Comment
5255
return $comment;
5356
}
5457

58+
59+
/**
60+
* Archive an existing comment.
61+
*/
62+
public function archive(Comment $comment): Comment
63+
{
64+
if ($comment->parent_id) {
65+
throw new NotifyException('Only top-level comments can be archived.', '/', 400);
66+
}
67+
68+
$comment->archived = true;
69+
$comment->save();
70+
71+
ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
72+
73+
return $comment;
74+
}
75+
76+
/**
77+
* Un-archive an existing comment.
78+
*/
79+
public function unarchive(Comment $comment): Comment
80+
{
81+
if ($comment->parent_id) {
82+
throw new NotifyException('Only top-level comments can be un-archived.', '/', 400);
83+
}
84+
85+
$comment->archived = false;
86+
$comment->save();
87+
88+
ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
89+
90+
return $comment;
91+
}
92+
5593
/**
5694
* Delete a comment from the system.
5795
*/

app/Activity/Controllers/CommentController.php

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace BookStack\Activity\Controllers;
44

55
use BookStack\Activity\CommentRepo;
6+
use BookStack\Activity\Tools\CommentTree;
7+
use BookStack\Activity\Tools\CommentTreeNode;
68
use BookStack\Entities\Queries\PageQueries;
79
use BookStack\Http\Controller;
810
use Illuminate\Http\Request;
@@ -26,6 +28,7 @@ public function savePageComment(Request $request, int $pageId)
2628
$input = $this->validate($request, [
2729
'html' => ['required', 'string'],
2830
'parent_id' => ['nullable', 'integer'],
31+
'content_ref' => ['string'],
2932
]);
3033

3134
$page = $this->pageQueries->findVisibleById($pageId);
@@ -40,14 +43,12 @@ public function savePageComment(Request $request, int $pageId)
4043

4144
// Create a new comment.
4245
$this->checkPermission('comment-create-all');
43-
$comment = $this->commentRepo->create($page, $input['html'], $input['parent_id'] ?? null);
46+
$contentRef = $input['content_ref'] ?? '';
47+
$comment = $this->commentRepo->create($page, $input['html'], $input['parent_id'] ?? null, $contentRef);
4448

4549
return view('comments.comment-branch', [
4650
'readOnly' => false,
47-
'branch' => [
48-
'comment' => $comment,
49-
'children' => [],
50-
]
51+
'branch' => new CommentTreeNode($comment, 0, []),
5152
]);
5253
}
5354

@@ -74,6 +75,46 @@ public function update(Request $request, int $commentId)
7475
]);
7576
}
7677

78+
/**
79+
* Mark a comment as archived.
80+
*/
81+
public function archive(int $id)
82+
{
83+
$comment = $this->commentRepo->getById($id);
84+
$this->checkOwnablePermission('page-view', $comment->entity);
85+
if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) {
86+
$this->showPermissionError();
87+
}
88+
89+
$this->commentRepo->archive($comment);
90+
91+
$tree = new CommentTree($comment->entity);
92+
return view('comments.comment-branch', [
93+
'readOnly' => false,
94+
'branch' => $tree->getCommentNodeForId($id),
95+
]);
96+
}
97+
98+
/**
99+
* Unmark a comment as archived.
100+
*/
101+
public function unarchive(int $id)
102+
{
103+
$comment = $this->commentRepo->getById($id);
104+
$this->checkOwnablePermission('page-view', $comment->entity);
105+
if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) {
106+
$this->showPermissionError();
107+
}
108+
109+
$this->commentRepo->unarchive($comment);
110+
111+
$tree = new CommentTree($comment->entity);
112+
return view('comments.comment-branch', [
113+
'readOnly' => false,
114+
'branch' => $tree->getCommentNodeForId($id),
115+
]);
116+
}
117+
77118
/**
78119
* Delete a comment from the system.
79120
*/

app/Activity/Models/Comment.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
* @property int $entity_id
2020
* @property int $created_by
2121
* @property int $updated_by
22+
* @property string $content_ref
23+
* @property bool $archived
2224
*/
2325
class Comment extends Model implements Loggable
2426
{

app/Activity/Tools/CommentTree.php

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class CommentTree
99
{
1010
/**
1111
* The built nested tree structure array.
12-
* @var array{comment: Comment, depth: int, children: array}[]
12+
* @var CommentTreeNode[]
1313
*/
1414
protected array $tree;
1515
protected array $comments;
@@ -28,17 +28,43 @@ public function enabled(): bool
2828

2929
public function empty(): bool
3030
{
31-
return count($this->tree) === 0;
31+
return count($this->getActive()) === 0;
3232
}
3333

3434
public function count(): int
3535
{
3636
return count($this->comments);
3737
}
3838

39-
public function get(): array
39+
public function getActive(): array
4040
{
41-
return $this->tree;
41+
return array_filter($this->tree, fn (CommentTreeNode $node) => !$node->comment->archived);
42+
}
43+
44+
public function activeThreadCount(): int
45+
{
46+
return count($this->getActive());
47+
}
48+
49+
public function getArchived(): array
50+
{
51+
return array_filter($this->tree, fn (CommentTreeNode $node) => $node->comment->archived);
52+
}
53+
54+
public function archivedThreadCount(): int
55+
{
56+
return count($this->getArchived());
57+
}
58+
59+
public function getCommentNodeForId(int $commentId): ?CommentTreeNode
60+
{
61+
foreach ($this->tree as $node) {
62+
if ($node->comment->id === $commentId) {
63+
return $node;
64+
}
65+
}
66+
67+
return null;
4268
}
4369

4470
public function canUpdateAny(): bool
@@ -54,6 +80,7 @@ public function canUpdateAny(): bool
5480

5581
/**
5682
* @param Comment[] $comments
83+
* @return CommentTreeNode[]
5784
*/
5885
protected function createTree(array $comments): array
5986
{
@@ -77,26 +104,22 @@ protected function createTree(array $comments): array
77104

78105
$tree = [];
79106
foreach ($childMap[0] ?? [] as $childId) {
80-
$tree[] = $this->createTreeForId($childId, 0, $byId, $childMap);
107+
$tree[] = $this->createTreeNodeForId($childId, 0, $byId, $childMap);
81108
}
82109

83110
return $tree;
84111
}
85112

86-
protected function createTreeForId(int $id, int $depth, array &$byId, array &$childMap): array
113+
protected function createTreeNodeForId(int $id, int $depth, array &$byId, array &$childMap): CommentTreeNode
87114
{
88115
$childIds = $childMap[$id] ?? [];
89116
$children = [];
90117

91118
foreach ($childIds as $childId) {
92-
$children[] = $this->createTreeForId($childId, $depth + 1, $byId, $childMap);
119+
$children[] = $this->createTreeNodeForId($childId, $depth + 1, $byId, $childMap);
93120
}
94121

95-
return [
96-
'comment' => $byId[$id],
97-
'depth' => $depth,
98-
'children' => $children,
99-
];
122+
return new CommentTreeNode($byId[$id], $depth, $children);
100123
}
101124

102125
protected function loadComments(): array
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace BookStack\Activity\Tools;
4+
5+
use BookStack\Activity\Models\Comment;
6+
7+
class CommentTreeNode
8+
{
9+
public Comment $comment;
10+
public int $depth;
11+
12+
/**
13+
* @var CommentTreeNode[]
14+
*/
15+
public array $children;
16+
17+
public function __construct(Comment $comment, int $depth, array $children)
18+
{
19+
$this->comment = $comment;
20+
$this->depth = $depth;
21+
$this->children = $children;
22+
}
23+
}

0 commit comments

Comments
 (0)