Skip to content

Commit b0c5743

Browse files
committed
Merge branch 'development' into release
2 parents 07e45a2 + 980a684 commit b0c5743

File tree

619 files changed

+16849
-5887
lines changed

Some content is hidden

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

619 files changed

+16849
-5887
lines changed

.github/translators.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,3 +455,9 @@ Rivo Zängov (Eraser) :: Estonian
455455
Francisco Rafael Fonseca (chicoraf) :: Portuguese, Brazilian
456456
ИEØ_ΙΙØZ (NEO_IIOZ) :: Chinese Traditional
457457
madnjpn (madnjpn.) :: Georgian
458+
Ásgeir Shiny Ásgeirsson (AsgeirShiny) :: Icelandic
459+
Mohammad Aftab Uddin (chirohorit) :: Bengali
460+
Yannis Karlaftis (meliseus) :: Greek
461+
felixxx :: German Informal
462+
randi (randi65535) :: Korean
463+
test65428 :: Greek

.github/workflows/analyse-php.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ on:
1111
jobs:
1212
build:
1313
if: ${{ github.ref != 'refs/heads/l10n_development' }}
14-
runs-on: ubuntu-22.04
14+
runs-on: ubuntu-24.04
1515
steps:
16-
- uses: actions/checkout@v1
16+
- uses: actions/checkout@v4
1717

1818
- name: Setup PHP
1919
uses: shivammathur/setup-php@v2

.github/workflows/lint-php.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ on:
1111
jobs:
1212
build:
1313
if: ${{ github.ref != 'refs/heads/l10n_development' }}
14-
runs-on: ubuntu-22.04
14+
runs-on: ubuntu-24.04
1515
steps:
16-
- uses: actions/checkout@v1
16+
- uses: actions/checkout@v4
1717

1818
- name: Setup PHP
1919
uses: shivammathur/setup-php@v2
2020
with:
21-
php-version: 8.1
21+
php-version: 8.3
2222
tools: phpcs
2323

2424
- name: Run formatting check

.github/workflows/test-migrations.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ on:
1313
jobs:
1414
build:
1515
if: ${{ github.ref != 'refs/heads/l10n_development' }}
16-
runs-on: ubuntu-22.04
16+
runs-on: ubuntu-24.04
1717
strategy:
1818
matrix:
19-
php: ['8.1', '8.2', '8.3']
19+
php: ['8.1', '8.2', '8.3', '8.4']
2020
steps:
21-
- uses: actions/checkout@v1
21+
- uses: actions/checkout@v4
2222

2323
- name: Setup PHP
2424
uses: shivammathur/setup-php@v2

.github/workflows/test-php.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ jobs:
1616
runs-on: ubuntu-22.04
1717
strategy:
1818
matrix:
19-
php: ['8.1', '8.2', '8.3']
19+
php: ['8.1', '8.2', '8.3', '8.4']
2020
steps:
21-
- uses: actions/checkout@v1
21+
- uses: actions/checkout@v4
2222

2323
- name: Setup PHP
2424
uses: shivammathur/setup-php@v2

app/Access/LdapService.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,26 @@ private function getUserWithAttributes(string $userName, array $attributes): ?ar
7171
return $users[0];
7272
}
7373

74+
/**
75+
* Build the user display name from the (potentially multiple) attributes defined by the configuration.
76+
*/
77+
protected function getUserDisplayName(array $userDetails, array $displayNameAttrs, string $defaultValue): string
78+
{
79+
$displayNameParts = [];
80+
foreach ($displayNameAttrs as $dnAttr) {
81+
$dnComponent = $this->getUserResponseProperty($userDetails, $dnAttr, null);
82+
if ($dnComponent) {
83+
$displayNameParts[] = $dnComponent;
84+
}
85+
}
86+
87+
if (empty($displayNameParts)) {
88+
return $defaultValue;
89+
}
90+
91+
return implode(' ', $displayNameParts);
92+
}
93+
7494
/**
7595
* Get the details of a user from LDAP using the given username.
7696
* User found via configurable user filter.
@@ -81,11 +101,11 @@ public function getUserDetails(string $userName): ?array
81101
{
82102
$idAttr = $this->config['id_attribute'];
83103
$emailAttr = $this->config['email_attribute'];
84-
$displayNameAttr = $this->config['display_name_attribute'];
104+
$displayNameAttrs = explode('|', $this->config['display_name_attribute']);
85105
$thumbnailAttr = $this->config['thumbnail_attribute'];
86106

87107
$user = $this->getUserWithAttributes($userName, array_filter([
88-
'cn', 'dn', $idAttr, $emailAttr, $displayNameAttr, $thumbnailAttr,
108+
'cn', 'dn', $idAttr, $emailAttr, ...$displayNameAttrs, $thumbnailAttr,
89109
]));
90110

91111
if (is_null($user)) {
@@ -95,7 +115,7 @@ public function getUserDetails(string $userName): ?array
95115
$userCn = $this->getUserResponseProperty($user, 'cn', null);
96116
$formatted = [
97117
'uid' => $this->getUserResponseProperty($user, $idAttr, $user['dn']),
98-
'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
118+
'name' => $this->getUserDisplayName($user, $displayNameAttrs, $userCn),
99119
'dn' => $user['dn'],
100120
'email' => $this->getUserResponseProperty($user, $emailAttr, null),
101121
'avatar' => $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null,

app/Access/LoginService.php

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BookStack\Access\Mfa\MfaSession;
66
use BookStack\Activity\ActivityType;
77
use BookStack\Exceptions\LoginAttemptException;
8+
use BookStack\Exceptions\LoginAttemptInvalidUserException;
89
use BookStack\Exceptions\StoppedAuthenticationException;
910
use BookStack\Facades\Activity;
1011
use BookStack\Facades\Theme;
@@ -29,10 +30,14 @@ public function __construct(
2930
* a reason to (MFA or Unconfirmed Email).
3031
* Returns a boolean to indicate the current login result.
3132
*
32-
* @throws StoppedAuthenticationException
33+
* @throws StoppedAuthenticationException|LoginAttemptInvalidUserException
3334
*/
3435
public function login(User $user, string $method, bool $remember = false): void
3536
{
37+
if ($user->isGuest()) {
38+
throw new LoginAttemptInvalidUserException('Login not allowed for guest user');
39+
}
40+
3641
if ($this->awaitingEmailConfirmation($user) || $this->needsMfaVerification($user)) {
3742
$this->setLastLoginAttemptedForUser($user, $method, $remember);
3843

@@ -58,7 +63,7 @@ public function login(User $user, string $method, bool $remember = false): void
5863
*
5964
* @throws Exception
6065
*/
61-
public function reattemptLoginFor(User $user)
66+
public function reattemptLoginFor(User $user): void
6267
{
6368
if ($user->id !== ($this->getLastLoginAttemptUser()->id ?? null)) {
6469
throw new Exception('Login reattempt user does align with current session state');
@@ -152,16 +157,40 @@ public function awaitingEmailConfirmation(User $user): bool
152157
*/
153158
public function attempt(array $credentials, string $method, bool $remember = false): bool
154159
{
160+
if ($this->areCredentialsForGuest($credentials)) {
161+
return false;
162+
}
163+
155164
$result = auth()->attempt($credentials, $remember);
156165
if ($result) {
157166
$user = auth()->user();
158167
auth()->logout();
159-
$this->login($user, $method, $remember);
168+
try {
169+
$this->login($user, $method, $remember);
170+
} catch (LoginAttemptInvalidUserException $e) {
171+
// Catch and return false for non-login accounts
172+
// so it looks like a normal invalid login.
173+
return false;
174+
}
160175
}
161176

162177
return $result;
163178
}
164179

180+
/**
181+
* Check if the given credentials are likely for the system guest account.
182+
*/
183+
protected function areCredentialsForGuest(array $credentials): bool
184+
{
185+
if (isset($credentials['email'])) {
186+
return User::query()->where('email', '=', $credentials['email'])
187+
->where('system_name', '=', 'public')
188+
->exists();
189+
}
190+
191+
return false;
192+
}
193+
165194
/**
166195
* Logs the current user out of the application.
167196
* Returns an app post-redirect path.

app/Activity/ActivityType.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ class ActivityType
6767
const WEBHOOK_UPDATE = 'webhook_update';
6868
const WEBHOOK_DELETE = 'webhook_delete';
6969

70+
const IMPORT_CREATE = 'import_create';
71+
const IMPORT_RUN = 'import_run';
72+
const IMPORT_DELETE = 'import_delete';
73+
7074
/**
7175
* Get all the possible values.
7276
*/

app/Activity/Notifications/Handlers/BaseNotificationHandler.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use BookStack\Entities\Models\Entity;
88
use BookStack\Permissions\PermissionApplicator;
99
use BookStack\Users\Models\User;
10+
use Illuminate\Support\Facades\Log;
1011

1112
abstract class BaseNotificationHandler implements NotificationHandler
1213
{
@@ -36,7 +37,11 @@ protected function sendNotificationToUserIds(string $notification, array $userId
3637
}
3738

3839
// Send the notification
39-
$user->notify(new $notification($detail, $initiator));
40+
try {
41+
$user->notify(new $notification($detail, $initiator));
42+
} catch (\Exception $exception) {
43+
Log::error("Failed to send email notification to user [id:{$user->id}] with error: {$exception->getMessage()}");
44+
}
4045
}
4146
}
4247
}

app/Api/ApiEntityListFormatter.php

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace BookStack\Api;
44

5+
use BookStack\Entities\Models\BookChild;
56
use BookStack\Entities\Models\Entity;
7+
use BookStack\Entities\Models\Page;
68

79
class ApiEntityListFormatter
810
{
@@ -20,8 +22,16 @@ class ApiEntityListFormatter
2022
* @var array<string|int, string|callable>
2123
*/
2224
protected array $fields = [
23-
'id', 'name', 'slug', 'book_id', 'chapter_id', 'draft',
24-
'template', 'priority', 'created_at', 'updated_at',
25+
'id',
26+
'name',
27+
'slug',
28+
'book_id',
29+
'chapter_id',
30+
'draft',
31+
'template',
32+
'priority',
33+
'created_at',
34+
'updated_at',
2535
];
2636

2737
public function __construct(array $list)
@@ -62,6 +72,28 @@ public function withTags(): self
6272
return $this;
6373
}
6474

75+
/**
76+
* Include parent book/chapter info in the formatted data.
77+
*/
78+
public function withParents(): self
79+
{
80+
$this->withField('book', function (Entity $entity) {
81+
if ($entity instanceof BookChild && $entity->book) {
82+
return $entity->book->only(['id', 'name', 'slug']);
83+
}
84+
return null;
85+
});
86+
87+
$this->withField('chapter', function (Entity $entity) {
88+
if ($entity instanceof Page && $entity->chapter) {
89+
return $entity->chapter->only(['id', 'name', 'slug']);
90+
}
91+
return null;
92+
});
93+
94+
return $this;
95+
}
96+
6597
/**
6698
* Format the data and return an array of formatted content.
6799
* @return array[]

0 commit comments

Comments
 (0)