Compare commits

..

2 Commits

Author SHA1 Message Date
milnerTill
844c79ae72 improved error handling and tests added 2026-04-20 13:38:17 -03:00
milnerTill
f3c1fad50a add support to HS256 algorithm 2026-04-20 11:48:04 -03:00
434 changed files with 2842 additions and 7617 deletions

View File

@@ -395,19 +395,6 @@ ALLOWED_IFRAME_HOSTS=null
# Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
ALLOWED_IFRAME_SOURCES="https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com"
# A list of sources/hostnames that can be loaded as CSS styles within BookStack.
# Space separated if multiple. BookStack host domain is auto-inferred.
# Defaults to a permissive set if not provided.
# Example: ALLOWED_STYLE_SOURCES="https://fonts.googleapis.com"
ALLOWED_STYLE_SOURCES=null
# A list of sources/hostnames that can be loaded as image content within BookStack.
# Space separated if multiple. BookStack host domain is auto-inferred, in addition to
# data and blob images, due to their use for various functionality.
# Defaults to a permissive set if not provided.
# Example: ALLOWED_IMAGE_SOURCES="https://images.example.com"
ALLOWED_IMAGE_SOURCES=null
# A list of the sources/hostnames that can be reached by application SSR calls.
# This is used wherever users can provide URLs/hosts in-platform, like for webhooks.
# Host-specific functionality (usually controlled via other options) like auth

View File

@@ -1,2 +0,0 @@
Please find our community rules on our website here:
https://www.bookstackapp.com/about/community-rules/

View File

@@ -1,4 +0,0 @@
# These are supported funding model platforms
github: [ssddanbrown]
ko_fi: ssddanbrown

View File

@@ -1,13 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Community Forum Support
url: https://community.bookstackapp.com
about: Get support by talking with the BookStack team & community.
- name: Debugging & Common Issues
url: https://www.bookstackapp.com/docs/admin/debugging/
about: Find details on how to debug issues and view common issues with their resolutions.
- name: Official Support Plans
url: https://www.bookstackapp.com/support/
about: View our official support plans that offer assured support for business.

View File

@@ -1,11 +0,0 @@
## Details
<!-- Write details of your pull request in here -->
<!-- Include references to any relevant issues/discussions -->
## Checklist
<!-- Put an 'x' in between the brackets below to confirm these elements -->
- [ ] I have read the [BookStack community rules](https://www.bookstackapp.com/about/community-rules/).
- [ ] This PR does not feature significant use of LLM/AI generation as per the community rules above.

View File

@@ -1,34 +0,0 @@
name: Crowdin Action
on:
push:
branches: [ development ]
paths:
- 'lang/**.php'
schedule:
- cron: '30 4 * * *'
workflow_dispatch:
jobs:
synchronize-with-crowdin:
runs-on: docker
container:
image: docker.io/library/node:24-trixie
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v6
- name: crowdin action
uses: https://github.com/crowdin/github-action@v2
with:
crowdin_branch_name: development
upload_sources: true
upload_translations: false
download_translations: true
localization_branch_name: l10n_development
create_pull_request: false
github_base_url: codeberg.org
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View File

@@ -1,33 +0,0 @@
name: update-snyk
on:
workflow_dispatch:
push:
paths:
- 'composer*'
- 'package*'
branches:
- 'development'
- 'release'
jobs:
update:
runs-on: docker
container:
image: docker.io/library/node:24-trixie
steps:
- uses: https://code.forgejo.org/actions/checkout@v6
- name: Update Snyk for monitoring - Composer
uses: https://github.com/snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: snyk monitor --file=composer.lock --project-name=bookstack-${{forgejo.ref_name}}-composer
- name: Update Snyk for monitoring - NPM
uses: https://github.com/snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: snyk monitor --file=package-lock.json --project-name=bookstack-${{forgejo.ref_name}}-npm

View File

@@ -1,6 +1,6 @@
name: New API Endpoint or API Ability
description: Request a new endpoint or API feature be added
labels: ["Type/API Request"]
labels: [":nut_and_bolt: API Request"]
body:
- type: textarea
id: feature

View File

@@ -1,6 +1,6 @@
name: Bug Report
description: Create a report to help us fix bugs & issues in existing supported functionality
labels: ["Type/Bug Report"]
labels: [":bug: Bug"]
body:
- type: markdown
attributes:

View File

@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Open Issues Here Instead
url: https://codeberg.org/bookstack/bookstack/issues
about: This project has migrated to Codeberg, please open issues there instead.
- name: Discord Chat Support
url: https://discord.gg/ztkBqR2
about: Realtime support & chat with the BookStack community and the team.
- name: Debugging & Common Issues
url: https://www.bookstackapp.com/docs/admin/debugging/

View File

@@ -1,6 +1,6 @@
name: Feature Request
description: Request a new feature or idea to be added to BookStack
labels: ["Type/Feature Request"]
labels: [":hammer: Feature Request"]
body:
- type: textarea
id: description
@@ -33,7 +33,7 @@ body:
attributes:
label: Have you searched for an existing open/closed issue?
description: |
To help us keep these issues under control, please ensure you have first [searched our issue list](https://codeberg.org/bookstack/bookstack/issues) for any existing issues that cover the fundamental benefit/goal of your request.
To help us keep these issues under control, please ensure you have first [searched our issue list](https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue) for any existing issues that cover the fundamental benefit/goal of your request.
options:
- label: I have searched for existing issues and none cover my fundamental request
required: true

View File

@@ -1,6 +1,6 @@
name: Language Request
description: Request a new language to be added to Crowdin for you to translate
labels: ["Focus: Translations"]
labels: [":earth_africa: Translations"]
assignees:
- ssddanbrown
body:

View File

@@ -1,6 +1,6 @@
name: Support Request
description: Request support for a specific problem you have not been able to solve yourself
labels: ["Type/Support"]
labels: [":dog2: Support"]
body:
- type: checkboxes
id: useddocs
@@ -15,11 +15,11 @@ body:
- type: checkboxes
id: searchissue
attributes:
label: Searched Existing Issues
label: Searched GitHub Issues
description: |
I have searched for the issue and potential resolutions within the [project's issue list](https://codeberg.org/bookstack/bookstack/issues)
I have searched for the issue and potential resolutions within the [project's GitHub issue list](https://github.com/BookStackApp/BookStack/issues)
options:
- label: I have searched for the issue.
- label: I have searched GitHub for the issue.
required: true
- type: textarea
id: scenario

View File

@@ -2,7 +2,7 @@
## Supported Versions
Only the [latest version](https://codeberg.org/bookstack/bookstack/releases) of BookStack is supported.
Only the [latest version](https://github.com/BookStackApp/BookStack/releases) of BookStack is supported.
We generally don't support older versions of BookStack due to maintenance effort and
since we aim to provide a fairly stable upgrade path for new versions.
@@ -12,14 +12,16 @@ If you'd like to be notified of new potential security concerns you can [sign-up
## Reporting a Vulnerability
If you've found an issue that likely has no impact to existing users (For example, an issue only in the development branch)
feel free to raise it via a standard Codeberg bug report issue.
If you've found an issue that likely has no impact to existing users (For example, in a development-only branch)
feel free to raise it via a standard GitHub bug report issue.
If the issue could have a security impact to BookStack instances,
please directly contact the lead maintainer via email Dan Brown using the [details found here](https://www.bookstackapp.com/links/contact/).
please directly contact the lead maintainer [@ssddanbrown](https://github.com/ssddanbrown).
You will need to log in to be able to see the email address on the [GitHub profile page](https://github.com/ssddanbrown).
Alternatively you can send a DM via Mastodon to [@danb@fosstodon.org](https://fosstodon.org/@danb).
Please be patient while the vulnerability is being reviewed. Deploying the fix to address the vulnerability
can often take a little time due to the amount of preparation required, to ensure the vulnerability has
been covered, and to create the content required to adequately notify the user-base.
Thank you for keeping BookStack instances safe!
Thank you for keeping BookStack instances safe!

View File

@@ -1,10 +1,11 @@
**Warning:**
## Details
This project has migrated to Codeberg:
https://codeberg.org/bookstack/bookstack
<!-- Write details of your pull request in here -->
<!-- Include references to any relevant issues/discussions -->
Please open pull requests here instead.
## Checklist
ANY PULL REQUESTS OPENED HERE WILL BE CLOSED WITHOUT COMMENT OR MERGE.
<!-- Put an 'x' in between the brackets below to confirm these elements -->
---
- [ ] I have read the [BookStack community rules](https://www.bookstackapp.com/about/community-rules/).
- [ ] This PR does not feature significant use of LLM/AI generation as per the community rules above.

View File

@@ -533,18 +533,7 @@ JanDziaslo :: Polish
Charllys Fernandes (CharllysFernandes) :: Portuguese, Brazilian
Ilgiz Zigangirov (inov8) :: Russian
Max Israelsson (Blezie) :: Swedish
Skiddybison5924 (chris-devel0per) :: German Informal; German
Skiddybison5924 (chris-devel0per) :: German
Veyilla Nightwhisper (Veyilla) :: German
João Barbosa (hypeedd) :: Portuguese
Abcdefg Hijklmn (collatek) :: Korean
Suthep Yonphimai (tomztt) :: Thai
MrClock (MrClock8163) :: Hungarian
Elena0875 :: Russian
FelixFrizzy :: German
Pedro de Mattia (pdmtt) :: Portuguese, Brazilian
lonestan :: Russian
Paul Kernstock (kernstock) :: German
brtbr :: German; German Informal
Ricardo Covelo (covelo12) :: Portuguese
Bojan Maksimovic (PolarniMeda) :: Serbian (Cyrillic)
Dian Prawira (wiradian84) :: Indonesian

View File

@@ -1,7 +1,6 @@
name: analyse-php
on:
workflow_dispatch:
push:
paths:
- '**.php'
@@ -12,17 +11,15 @@ on:
jobs:
build:
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: docker
container:
image: docker.io/library/node:24-trixie
runs-on: ubuntu-24.04
steps:
- uses: https://code.forgejo.org/actions/checkout@v6
- uses: actions/checkout@v4
- name: Setup PHP
uses: https://github.com/shivammathur/setup-php@v2
uses: shivammathur/setup-php@v2
with:
php-version: 8.5
extensions: gd, mbstring, json, curl, xml, dom, mysql, ldap
php-version: 8.3
extensions: gd, mbstring, json, curl, xml, mysql, ldap
- name: Get Composer Cache Directory
id: composer-cache
@@ -30,16 +27,14 @@ jobs:
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer packages
uses: https://code.forgejo.org/actions/cache@v5
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-8.5
key: ${{ runner.os }}-composer-8.3
restore-keys: ${{ runner.os }}-composer-
- name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --ansi
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GH_TOKEN }}"}}'
- name: Run static analysis check
run: composer check-static

View File

@@ -1,7 +1,6 @@
name: lint-js
on:
workflow_dispatch:
push:
paths:
- '**.js'
@@ -14,11 +13,9 @@ on:
jobs:
build:
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: docker
container:
image: docker.io/library/node:24-trixie
runs-on: ubuntu-24.04
steps:
- uses: https://code.forgejo.org/actions/checkout@v6
- uses: actions/checkout@v4
- name: Install NPM deps
run: npm ci

View File

@@ -1,7 +1,6 @@
name: lint-php
on:
workflow_dispatch:
push:
paths:
- '**.php'
@@ -12,16 +11,14 @@ on:
jobs:
build:
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: docker
container:
image: docker.io/library/node:24-trixie
runs-on: ubuntu-24.04
steps:
- uses: https://code.forgejo.org/actions/checkout@v6
- uses: actions/checkout@v4
- name: Setup PHP
uses: https://github.com/shivammathur/setup-php@v2
uses: shivammathur/setup-php@v2
with:
php-version: 8.5
php-version: 8.3
tools: phpcs
- name: Run formatting check

View File

@@ -1,7 +1,6 @@
name: test-js
on:
workflow_dispatch:
push:
paths:
- '**.js'
@@ -16,11 +15,9 @@ on:
jobs:
build:
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: docker
container:
image: docker.io/library/node:24-trixie
runs-on: ubuntu-24.04
steps:
- uses: https://code.forgejo.org/actions/checkout@v6
- uses: actions/checkout@v6
- name: Install NPM deps
run: npm ci
@@ -29,4 +26,4 @@ jobs:
run: npm run ts:lint
- name: Run JavaScript tests
run: npm run test:ci
run: npm run test

View File

@@ -1,7 +1,6 @@
name: test-migrations
on:
workflow_dispatch:
push:
paths:
- '**.php'
@@ -14,36 +13,18 @@ on:
jobs:
build:
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: docker
container:
image: docker.io/library/node:24-trixie
runs-on: ubuntu-24.04
strategy:
matrix:
php: ['8.2', '8.3', '8.4', '8.5']
services:
mysql:
image: docker.io/library/mariadb:12.2.2-noble
options: --tmpfs /var/lib/mysql:rw
cmd:
- --innodb-flush-log-at-trx-commit=0
- --innodb-flush-method=O_DIRECT
- --innodb-doublewrite=0
- --innodb-buffer-pool-size=256M
- --skip-log-bin
- --sync-binlog=0
env:
MARIADB_USER: bookstack-test
MARIADB_PASSWORD: bookstack-test
MARIADB_DATABASE: bookstack-test
MARIADB_ROOT_PASSWORD: password
steps:
- uses: https://code.forgejo.org/actions/checkout@v6
- uses: actions/checkout@v4
- name: Setup PHP
uses: https://github.com/shivammathur/setup-php@v2
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: gd, mbstring, json, curl, xml, dom, mysql, ldap
extensions: gd, mbstring, json, curl, xml, mysql, ldap
- name: Get Composer Cache Directory
id: composer-cache
@@ -51,31 +32,34 @@ jobs:
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer packages
uses: https://code.forgejo.org/actions/cache@v5
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}
restore-keys: ${{ runner.os }}-composer-
- name: Start MySQL
run: |
sudo systemctl start mysql
- name: Create database & user
run: |
mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;'
mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'bookstack-test';"
mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
mysql -uroot -proot -e 'FLUSH PRIVILEGES;'
- name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --ansi
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GH_TOKEN }}"}}'
- name: Start migration test
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test'
run: |
php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing
- name: Start migration:rollback test
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test'
run: |
php${{ matrix.php }} artisan migrate:rollback --force -n --database=mysql_testing
- name: Start migration rerun test
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/bookstack-test'
run: |
php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing

View File

@@ -1,7 +1,6 @@
name: test-php
on:
workflow_dispatch:
push:
paths:
- '**.php'
@@ -14,38 +13,18 @@ on:
jobs:
build:
if: ${{ github.ref != 'refs/heads/l10n_development' }}
runs-on: docker
container:
image: docker.io/setupphp/node:noble
runs-on: ubuntu-24.04
strategy:
matrix:
php: ['8.2', '8.3', '8.4', '8.5']
env:
phpextensions: gd, mbstring, json, curl, xml, dom, mysql, ldap, gmp
phpextensioncachekey: cache-v1
steps:
- uses: https://code.forgejo.org/actions/checkout@v6
- name: Setup cache environment
id: extcache
uses: https://github.com/shivammathur/cache-extensions@v1
with:
php-version: ${{ matrix.php }}
extensions: ${{ env.phpextensions }}
key: ${{ env.phpextensioncachekey }}
- name: Cache extensions
uses: https://code.forgejo.org/actions/cache@v5
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- uses: actions/checkout@v4
- name: Setup PHP
uses: https://github.com/shivammathur/setup-php@v2
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: ${{ env.phpextensions }}
extensions: gd, mbstring, json, curl, xml, mysql, ldap, gmp
- name: Get Composer Cache Directory
id: composer-cache
@@ -53,36 +32,30 @@ jobs:
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache composer packages
uses: https://code.forgejo.org/actions/cache@v5
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}
restore-keys: ${{ runner.os }}-composer-
- name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --ansi
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GH_TOKEN }}"}}'
- name: Start MySQL
- name: Start Database
run: |
sudo systemctl start mysql
- name: Create database & user
- name: Setup Database
run: |
mysql -uroot -proot -e 'CREATE DATABASE IF NOT EXISTS `bookstack-test`;'
mysql -uroot -proot -e "CREATE USER 'bookstack-test'@'localhost' IDENTIFIED WITH mysql_native_password BY 'bookstack-test';"
mysql -uroot -proot -e "GRANT ALL ON \`bookstack-test\`.* TO 'bookstack-test'@'localhost';"
mysql -uroot -proot -e 'FLUSH PRIVILEGES;'
- name: Install composer dependencies
run: composer install --prefer-dist --no-interaction --ansi
- name: Migrate and seed the database
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@localhost/bookstack-test'
run: |
php${{ matrix.php }} artisan migrate --force -n --database=mysql_testing
php${{ matrix.php }} artisan db:seed --force -n --class=DummyContentSeeder --database=mysql_testing
- name: Run PHP tests
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@localhost/bookstack-test'
run: php${{ matrix.php }} ./vendor/bin/phpunit

View File

@@ -22,10 +22,4 @@ trait HandlesPartialLogins
return $user;
}
protected function clearLastAttemptedUser(): void
{
$loginService = app()->make(LoginService::class);
$loginService->clearLastLoginAttempted();
}
}

View File

@@ -8,7 +8,6 @@ use BookStack\Exceptions\LoginAttemptEmailNeededException;
use BookStack\Exceptions\LoginAttemptException;
use BookStack\Facades\Activity;
use BookStack\Http\Controller;
use BookStack\Util\UrlComparison;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
@@ -187,8 +186,7 @@ class LoginController extends Controller
{
// Store the previous location for redirect after login
$previous = url()->previous('');
$comparison = new UrlComparison($previous, url('/'));
$isPreviousFromInstance = $comparison->originsMatch() && $comparison->pathsOverlap();
$isPreviousFromInstance = str_starts_with($previous, url('/'));
if (!$previous || !setting('app-public') || !$isPreviousFromInstance) {
return;
}

View File

@@ -6,7 +6,6 @@ use BookStack\Access\LoginService;
use BookStack\Access\Mfa\BackupCodeService;
use BookStack\Access\Mfa\MfaSession;
use BookStack\Access\Mfa\MfaValue;
use BookStack\Access\Mfa\MfaVerificationLimiter;
use BookStack\Activity\ActivityType;
use BookStack\Exceptions\NotFoundException;
use BookStack\Http\Controller;
@@ -20,11 +19,6 @@ class MfaBackupCodesController extends Controller
protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-backup-codes';
public function __construct(
protected MfaVerificationLimiter $limiter,
) {
}
/**
* Show a view that generates and displays backup codes.
*/
@@ -77,12 +71,6 @@ class MfaBackupCodesController extends Controller
public function verify(Request $request, BackupCodeService $codeService, MfaSession $mfaSession, LoginService $loginService)
{
$user = $this->currentOrLastAttemptedUser();
$this->limiter->incrementAttempts($user, $request);
if ($this->limiter->hasHitLimit($user, $request)) {
$this->clearLastAttemptedUser();
$this->limiter->throwException();
}
$codes = MfaValue::getValueForUser($user, MfaValue::METHOD_BACKUP_CODES) ?? '[]';
$this->validate($request, [
@@ -101,7 +89,6 @@ class MfaBackupCodesController extends Controller
$mfaSession->markVerifiedForUser($user);
$loginService->reattemptLoginFor($user);
$this->limiter->decrementAttempts($user, $request);
if ($codeService->countCodesInSet($updatedCodes) < 5) {
$this->showWarningNotification(trans('auth.mfa_backup_codes_usage_limit_warning'));

View File

@@ -5,7 +5,6 @@ namespace BookStack\Access\Controllers;
use BookStack\Access\LoginService;
use BookStack\Access\Mfa\MfaSession;
use BookStack\Access\Mfa\MfaValue;
use BookStack\Access\Mfa\MfaVerificationLimiter;
use BookStack\Access\Mfa\TotpService;
use BookStack\Access\Mfa\TotpValidationRule;
use BookStack\Activity\ActivityType;
@@ -21,8 +20,7 @@ class MfaTotpController extends Controller
protected const SETUP_SECRET_SESSION_KEY = 'mfa-setup-totp-secret';
public function __construct(
protected TotpService $totp,
protected MfaVerificationLimiter $limiter,
protected TotpService $totp
) {
}
@@ -88,12 +86,6 @@ class MfaTotpController extends Controller
public function verify(Request $request, LoginService $loginService, MfaSession $mfaSession)
{
$user = $this->currentOrLastAttemptedUser();
$this->limiter->incrementAttempts($user, $request);
if ($this->limiter->hasHitLimit($user, $request)) {
$this->clearLastAttemptedUser();
$this->limiter->throwException();
}
$totpSecret = MfaValue::getValueForUser($user, MfaValue::METHOD_TOTP);
$this->validate($request, [
@@ -106,7 +98,6 @@ class MfaTotpController extends Controller
$mfaSession->markVerifiedForUser($user);
$loginService->reattemptLoginFor($user);
$this->limiter->decrementAttempts($user, $request);
return redirect()->intended();
}

View File

@@ -55,6 +55,7 @@ class OidcController extends Controller
}
try {
$this->throwIfAuthorizationError($request);
$this->oidcService->processAuthorizeResponse($request->query('code'));
} catch (OidcException $oidcException) {
$this->showErrorNotification($oidcException->getMessage());
@@ -72,4 +73,23 @@ class OidcController extends Controller
{
return redirect($this->oidcService->logout());
}
/**
*
* @throws OidcException
*/
private function throwIfAuthorizationError(Request $request): void
{
$errorCode = $request->query('error');
if (!$errorCode) {
return;
}
$errorDescription = $request->query('error_description');
if ($errorDescription) {
throw new OidcException($errorDescription);
}
throw new OidcException(trans('errors.oidc_fail_authed', ['system' => config('oidc.name')]));
}
}

View File

@@ -126,7 +126,7 @@ class LoginService
/**
* Clear the last login attempted session value.
*/
public function clearLastLoginAttempted(): void
protected function clearLastLoginAttempted(): void
{
session()->remove(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
}

View File

@@ -1,62 +0,0 @@
<?php
namespace BookStack\Access\Mfa;
use BookStack\Exceptions\NotifyException;
use BookStack\Users\Models\User;
use Illuminate\Cache\RateLimiter;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
/**
* A rate limit specifically for MFA verification.
* Limits across both the attempted user (on a tight limit) and the
* request IP (on a less strict limit).
*/
class MfaVerificationLimiter
{
protected int $maxUserAttemptsPerMinute = 5;
protected int $maxIpAttemptsPerMinute = 60;
public function __construct(
protected RateLimiter $rateLimiter
) {
}
public function throwException(): never
{
throw new NotifyException(
trans('auth.mfa_throttle', ['seconds' => 60]),
'/login',
Response::HTTP_TOO_MANY_REQUESTS
);
}
public function incrementAttempts(User $user, Request $request): void
{
$this->rateLimiter->hit($this->getUserKey($user));
$this->rateLimiter->hit($this->getRequestKey($request));
}
public function decrementAttempts(User $user, Request $request): void
{
$this->rateLimiter->decrement($this->getUserKey($user));
$this->rateLimiter->decrement($this->getRequestKey($request));
}
public function hasHitLimit(User $user, Request $request): bool
{
return $this->rateLimiter->tooManyAttempts($this->getUserKey($user), $this->maxUserAttemptsPerMinute + 1)
|| $this->rateLimiter->tooManyAttempts($this->getRequestKey($request), $this->maxIpAttemptsPerMinute + 1);
}
protected function getUserKey(User $user): string
{
return "mfa-attempt::user::{$user->id}";
}
protected function getRequestKey(Request $request): string
{
return "mfa-attempt::request::{$request->ip()}";
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace BookStack\Access\Oidc;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
/**
* Option provider that sends credentials via HTTP Basic Auth header
* and also includes client_id in the request body.
*/
class OidcHttpBasicWithClientIdOptionProvider extends HttpBasicAuthOptionProvider
{
public function getAccessTokenOptions($method, array $params)
{
$clientId = $params['client_id'] ?? null;
$options = parent::getAccessTokenOptions($method, $params);
if ($clientId) {
parse_str($options['body'] ?? '', $body);
$body['client_id'] = $clientId;
$options['body'] = http_build_query($body);
}
return $options;
}
}

View File

@@ -9,10 +9,10 @@ class OidcIdToken extends OidcJwtWithClaims implements ProvidesClaims
*
* @throws OidcInvalidTokenException
*/
public function validate(string $clientId): bool
public function validate(OidcProviderSettings $settings): bool
{
parent::validateCommonTokenDetails($clientId);
$this->validateTokenClaims($clientId);
parent::validateCommonTokenDetails($settings);
$this->validateTokenClaims($settings->clientId);
return true;
}

View File

@@ -9,7 +9,9 @@ class OidcJwtWithClaims implements ProvidesClaims
protected string $signature;
protected string $issuer;
protected array $tokenParts = [];
protected array $acceptedSignatures = [self::hs256Signature, self::rs256Signature];
private const hs256Signature = 'HS256'
, rs256Signature = 'RS256';
/**
* @var array[]|string[]
*/
@@ -59,11 +61,11 @@ class OidcJwtWithClaims implements ProvidesClaims
*
* @throws OidcInvalidTokenException
*/
public function validateCommonTokenDetails(string $clientId): bool
public function validateCommonTokenDetails(OidcProviderSettings $settings): bool
{
$this->validateTokenStructure();
$this->validateTokenSignature();
$this->validateCommonClaims($clientId);
$this->validateTokenSignature($settings);
$this->validateCommonClaims($settings->clientId);
return true;
}
@@ -102,12 +104,12 @@ class OidcJwtWithClaims implements ProvidesClaims
protected function validateTokenStructure(): void
{
foreach (['header', 'payload'] as $prop) {
if (empty($this->$prop)) {
if (empty($this->$prop) || !is_array($this->$prop)) {
throw new OidcInvalidTokenException("Could not parse out a valid {$prop} within the provided token");
}
}
if (empty($this->signature)) {
if (empty($this->signature) || !is_string($this->signature)) {
throw new OidcInvalidTokenException('Could not parse out a valid signature within the provided token');
}
}
@@ -117,31 +119,42 @@ class OidcJwtWithClaims implements ProvidesClaims
*
* @throws OidcInvalidTokenException
*/
protected function validateTokenSignature(): void
{
if ($this->header['alg'] !== 'RS256') {
throw new OidcInvalidTokenException("Only RS256 signature validation is supported. Token reports using {$this->header['alg']}");
protected function validateTokenSignature(OidcProviderSettings $settings): void {
$validSignatures = implode(', ',$this->acceptedSignatures);
switch ($this->header['alg']) {
case self::rs256Signature:
$parsedKeys = array_map(function ($key) {
try {
return new OidcJwtSigningKey($key);
} catch (OidcInvalidKeyException $e) {
throw new OidcInvalidTokenException('Failed to read signing key with error: ' . $e->getMessage());
}
}, $this->keys);
$parsedKeys = array_filter($parsedKeys);
$contentToSign = $this->tokenParts[0] . '.' . $this->tokenParts[1];
/** @var OidcJwtSigningKey $parsedKey */
foreach ($parsedKeys as $parsedKey) {
if ($parsedKey->verify($contentToSign, $this->signature)) {
return;
}
}
throw new OidcInvalidTokenException('Token signature could not be validated using the provided keys');
case self::hs256Signature:
$secret = $settings->clientSecret;
$contentToSign = $this->tokenParts[0] . '.' . $this->tokenParts[1];
$expectedSignature = hash_hmac('sha256', $contentToSign, $secret, true);
if (hash_equals($expectedSignature, $this->signature)) {
return;
}
throw new OidcInvalidTokenException('Token signature could not be validated using the provided secret');
default:
throw new OidcInvalidTokenException("Only $validSignatures signatures validation are supported. Token reports using {$this->header['alg']}");
}
$parsedKeys = array_map(function ($key) {
try {
return new OidcJwtSigningKey($key);
} catch (OidcInvalidKeyException $e) {
throw new OidcInvalidTokenException('Failed to read signing key with error: ' . $e->getMessage());
}
}, $this->keys);
$parsedKeys = array_filter($parsedKeys);
$contentToSign = $this->tokenParts[0] . '.' . $this->tokenParts[1];
/** @var OidcJwtSigningKey $parsedKey */
foreach ($parsedKeys as $parsedKey) {
if ($parsedKey->verify($contentToSign, $this->signature)) {
return;
}
}
throw new OidcInvalidTokenException('Token signature could not be validated using the provided keys');
}
/**

View File

@@ -74,7 +74,7 @@ class OidcProviderSettings
{
$this->validateInitial();
$required = ['keys', 'tokenEndpoint', 'authorizationEndpoint'];
$required = ['tokenEndpoint', 'authorizationEndpoint'];
foreach ($required as $prop) {
if (empty($this->$prop)) {
throw new InvalidArgumentException("Missing required configuration \"{$prop}\" value");

View File

@@ -14,7 +14,6 @@ use BookStack\Theming\ThemeEvents;
use BookStack\Uploads\UserAvatars;
use BookStack\Users\Models\User;
use Illuminate\Support\Facades\Cache;
use League\OAuth2\Client\OptionProvider\HttpBasicAuthOptionProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
/**
@@ -140,7 +139,7 @@ class OidcService
'redirectUri' => url('/oidc/callback'),
], [
'httpClient' => $this->http->buildClient(5),
'optionProvider' => new HttpBasicAuthOptionProvider(),
'optionProvider' => new OidcHttpBasicWithClientIdOptionProvider(),
]);
foreach ($this->getAdditionalScopes() as $scope) {
@@ -199,7 +198,7 @@ class OidcService
}
try {
$idToken->validate($settings->clientId);
$idToken->validate($settings);
} catch (OidcInvalidTokenException $exception) {
throw new OidcException("ID token validation failed with error: {$exception->getMessage()}");
}

View File

@@ -46,7 +46,6 @@ class ActivityType
const USER_CREATE = 'user_create';
const USER_UPDATE = 'user_update';
const USER_DELETE = 'user_delete';
const USER_MFA_RESET = 'user_mfa_reset';
const API_TOKEN_CREATE = 'api_token_create';
const API_TOKEN_UPDATE = 'api_token_update';

View File

@@ -59,7 +59,8 @@ class CommentController extends Controller
'html' => ['required', 'string'],
]);
$comment = $this->commentRepo->getVisibleById($commentId);
$comment = $this->commentRepo->getById($commentId);
$this->checkOwnablePermission(Permission::PageView, $comment->entity);
$this->checkOwnablePermission(Permission::CommentUpdate, $comment);
$comment = $this->commentRepo->update($comment, $input['html']);
@@ -75,7 +76,8 @@ class CommentController extends Controller
*/
public function archive(int $id)
{
$comment = $this->commentRepo->getVisibleById($id);
$comment = $this->commentRepo->getById($id);
$this->checkOwnablePermission(Permission::PageView, $comment->entity);
if (!userCan(Permission::CommentUpdate, $comment) && !userCan(Permission::CommentDelete, $comment)) {
$this->showPermissionError();
}
@@ -94,7 +96,8 @@ class CommentController extends Controller
*/
public function unarchive(int $id)
{
$comment = $this->commentRepo->getVisibleById($id);
$comment = $this->commentRepo->getById($id);
$this->checkOwnablePermission(Permission::PageView, $comment->entity);
if (!userCan(Permission::CommentUpdate, $comment) && !userCan(Permission::CommentDelete, $comment)) {
$this->showPermissionError();
}
@@ -113,7 +116,7 @@ class CommentController extends Controller
*/
public function destroy(int $id)
{
$comment = $this->commentRepo->getVisibleById($id);
$comment = $this->commentRepo->getById($id);
$this->checkOwnablePermission(Permission::CommentDelete, $comment);
$this->commentRepo->delete($comment);

View File

@@ -3,7 +3,6 @@
namespace BookStack\App\Providers;
use BookStack\Uploads\ImageService;
use BookStack\Util\UrlFilter;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
@@ -22,8 +21,10 @@ class ValidationRuleServiceProvider extends ServiceProvider
Validator::extend('safe_url', function ($attribute, $value, $parameters, $validator) {
$cleanLinkName = strtolower(trim($value));
$filter = new UrlFilter($cleanLinkName);
return $filter->isAllowed();
$isJs = str_starts_with($cleanLinkName, 'javascript:');
$isData = str_starts_with($cleanLinkName, 'data:');
return !$isJs && !$isData;
});
}
}

View File

@@ -10,7 +10,7 @@ class PwaManifestBuilder
// does not start a session, so we won't have current user context.
// This was attempted but removed since manifest calls could affect user session
// history tracking and back redirection.
// Context: https://codeberg.org/bookstack/bookstack/issues/4649
// Context: https://github.com/BookStackApp/BookStack/issues/4649
$darkMode = (bool) setting()->getForCurrentUser('dark-mode-enabled');
$appName = setting('app-name');

View File

@@ -72,17 +72,6 @@ return [
// Current host and source for the "DRAWIO" setting will be auto-appended to the sources configured.
'iframe_sources' => env('ALLOWED_IFRAME_SOURCES', 'https://*.draw.io https://*.youtube.com https://*.youtube-nocookie.com https://*.vimeo.com'),
// A list of style sources/hostnames that can be loaded styles within BookStack.
// Space separated if multiple. BookStack host domain is auto-inferred.
// If not set, a permissive default set is used to reduce potential breakage.
'style_sources' => env('ALLOWED_STYLE_SOURCES', null),
// A list of sources/hostnames that can be loaded as image content within BookStack.
// Space separated if multiple. BookStack host domain is auto-inferred, in addition to
// data and blob images, due to their use for various functionality.
// If not set, a permissive default set is used to reduce potential breakage.
'image_sources' => env('ALLOWED_IMAGE_SOURCES', null),
// A list of the sources/hostnames that can be reached by application SSR calls.
// This is used wherever users can provide URLs/hosts in-platform, like for webhooks.
// Host-specific functionality (usually controlled via other options) like auth

View File

@@ -81,6 +81,7 @@ return [
'strict' => false,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
// @phpstan-ignore class.notFound
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],

View File

@@ -68,7 +68,7 @@ return [
* Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic,
* Symbol, ZapfDingbats.
*/
'font_dir' => storage_path('fonts/dompdf'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
'font_dir' => storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782)
/**
* The location of the DOMPDF font cache directory.
@@ -78,7 +78,7 @@ return [
*
* Note: This directory must exist and be writable by the webserver process.
*/
'font_cache' => storage_path('fonts/dompdf/cache'),
'font_cache' => storage_path('fonts/'),
/**
* The location of a temporary directory.

View File

@@ -7,7 +7,6 @@ use BookStack\Theming\ThemeModule;
use BookStack\Theming\ThemeModuleException;
use BookStack\Theming\ThemeModuleManager;
use BookStack\Theming\ThemeModuleZip;
use BookStack\Util\UrlComparison;
use GuzzleHttp\Psr7\Request;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
@@ -200,6 +199,7 @@ class InstallModuleCommand extends Command
{
$httpRequests = app()->make(HttpRequestService::class);
$client = $httpRequests->buildClient(30, ['stream' => true]);
$originalUrl = parse_url($location);
$currentLocation = $location;
$maxRedirects = 3;
$redirectCount = 0;
@@ -212,11 +212,12 @@ class InstallModuleCommand extends Command
if ($statusCode >= 300 && $statusCode < 400 && $redirectCount < $maxRedirects) {
$redirectLocation = $resp->getHeaderLine('Location');
if ($redirectLocation) {
$comparison = new UrlComparison($location, $redirectLocation);
$redirectOriginMatches = $comparison->originsMatch();
$redirectUrl = parse_url($redirectLocation);
$redirectOriginMatches = ($originalUrl['host'] ?? '') === ($redirectUrl['host'] ?? '')
&& ($originalUrl['scheme'] ?? '') === ($redirectUrl['scheme'] ?? '')
&& ($originalUrl['port'] ?? '') === ($redirectUrl['port'] ?? '');
if (!$redirectOriginMatches) {
$redirectUrl = parse_url($redirectLocation);
$redirectOrigin = ($redirectUrl['scheme'] ?? '') . '://' . ($redirectUrl['host'] ?? '') . (isset($redirectUrl['port']) ? ':' . $redirectUrl['port'] : '');
$this->info("The download URL is redirecting to a different site: {$redirectOrigin}");
$shouldContinue = $this->confirm("Do you trust downloading the module from this site?");

View File

@@ -25,7 +25,6 @@ class Handler extends ExceptionHandler
protected $dontReport = [
NotFoundException::class,
StoppedAuthenticationException::class,
NotifyException::class,
];
/**

View File

@@ -6,22 +6,18 @@ use Exception;
use Illuminate\Contracts\Support\Responsable;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
* An exception that is thrown to notify the user of something which went wrong.
* Typically these should be translated messages since they will be shown to the end user
* via a pop up notification error message in the UI.
*
* This exception is not intended to be used for internal system/application errors,
* and therefore will not be logged by the exception handler.
*/
class NotifyException extends Exception implements Responsable, HttpExceptionInterface
{
public function __construct(
string $message,
public string $redirectLocation = '/',
protected int $status = 500
) {
public $message;
public string $redirectLocation;
protected int $status;
public function __construct(string $message, string $redirectLocation = '/', int $status = 500)
{
$this->message = $message;
$this->redirectLocation = $redirectLocation;
$this->status = $status;
parent::__construct();
}

View File

@@ -4,8 +4,6 @@ namespace BookStack\Exports;
use BookStack\Exceptions\PdfExportException;
use Dompdf\Dompdf;
use FontLib\Font;
use Illuminate\Support\Str;
use Knp\Snappy\Pdf as SnappyPdf;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;
@@ -62,65 +60,12 @@ class PdfGenerator
$domPdf = new Dompdf($options);
$domPdf->setBasePath(base_path('public'));
$fontMetrics = $domPdf->getFontMetrics();
$userFontfamilies = $this->getUserDomPdfFontFamilies();
foreach ($userFontfamilies as $fontFamily => $fonts) {
try {
$fontMetrics->setFontFamily($fontFamily, $fonts);
} catch (\Exception $exception) {
$expectedPath = storage_path('fonts/dompdf');
throw new PdfExportException("Failed to create required font data in {$expectedPath}, Ensure all content in this location is writable by the web server");
}
}
$domPdf->loadHTML($this->convertEntities($html));
$domPdf->render();
return (string) $domPdf->output();
}
/**
* @return array<string, array<string, string>>
*/
protected function getUserDomPdfFontFamilies(): array
{
$fontStore = storage_path('fonts/dompdf');
if (!is_dir($fontStore)) {
return [];
}
$fontFamilies = [];
$fontFiles = glob($fontStore . DIRECTORY_SEPARATOR . '*.ttf');
foreach ($fontFiles as $fontFile) {
$fontFileName = basename($fontFile, '.ttf');
$expectedUfm = $fontStore . DIRECTORY_SEPARATOR . $fontFileName . '.ufm';
if (!file_exists($expectedUfm)) {
$font = Font::load($fontFile);
$font->parse();
try {
$font->saveAdobeFontMetrics($expectedUfm);
} catch (\Exception $exception) {
throw new PdfExportException("Failed to create required font data at $expectedUfm, Ensure this location is writable by the web server");
}
}
$nameParts = explode('-', $fontFileName);
if (count($nameParts) === 1 || $nameParts[1] === 'Regular') {
$nameParts[1] = 'Normal';
}
$family = trim(strtolower(preg_replace('/([A-Z])/', ' $1', $nameParts[0])));
$variation = Str::snake($nameParts[1]);
if (!isset($fontFamilies[$family])) {
$fontFamilies[$family] = [];
}
$fontFamilies[$family][$variation] = $fontStore . DIRECTORY_SEPARATOR . $fontFileName;
}
return $fontFamilies;
}
/**
* @throws PdfExportException
*/

View File

@@ -119,7 +119,7 @@ class SearchIndex
* Create a scored term array from the given text, where the keys are the terms
* and the values are their scores.
*
* @return array<string, float>
* @return array<string, int>
*/
protected function generateTermScoreMapFromText(string $text, float $scoreAdjustment = 1): array
{
@@ -136,7 +136,7 @@ class SearchIndex
* Create a scored term array from the given HTML, where the keys are the terms
* and the values are their scores.
*
* @return array<string, float>
* @return array<string, int>
*/
protected function generateTermScoreMapFromHtml(string $html): array
{
@@ -177,7 +177,7 @@ class SearchIndex
*
* @param Tag[] $tags
*
* @return array<string, float>
* @return array<string, int>
*/
protected function generateTermScoreMapFromTags(array $tags): array
{
@@ -277,9 +277,9 @@ class SearchIndex
* For the given term data arrays, Merge their contents by term
* while combining any scores.
*
* @param array<string, float>[] ...$scoreMaps
* @param array<string, int>[] ...$scoreMaps
*
* @return array<string, float>
* @return array<string, int>
*/
protected function mergeTermScoreMaps(...$scoreMaps): array
{

View File

@@ -120,14 +120,8 @@ class SearchRunner
$filter = function (EloquentBuilder $query) use ($exact) {
$inputTerm = str_replace('\\', '\\\\', $exact->value);
$query->where('name', 'like', '%' . $inputTerm . '%')
->orWhere(function (EloquentBuilder $query) use ($inputTerm) {
$query->whereNotNull('description')
->where('description', 'like', '%' . $inputTerm . '%');
})
->orWhere(function (EloquentBuilder $query) use ($inputTerm) {
$query->whereNotNull('text')
->where('text', 'like', '%' . $inputTerm . '%');
});
->orWhere('description', 'like', '%' . $inputTerm . '%')
->orWhere('text', 'like', '%' . $inputTerm . '%');
};
$exact->negated ? $entityQuery->whereNot($filter) : $entityQuery->where($filter);
@@ -290,7 +284,7 @@ class SearchRunner
$query->where('name', '=', $tagParts['name']);
}
if (is_numeric($tagParts['value']) && is_finite($tagParts['value']) && $tagParts['operator'] !== 'like') {
if (is_numeric($tagParts['value']) && $tagParts['operator'] !== 'like') {
// We have to do a raw sql query for this since otherwise PDO will quote the value and MySQL will
// search the value as a string which prevents being able to do number-based operations
// on the tag values. We ensure it has a numeric value and then cast it just to be sure.

View File

@@ -4,12 +4,37 @@ namespace BookStack\Sorting;
class BookSortMapItem
{
public function __construct(
public int $id,
public int $sort,
public int|null $parentChapterId,
public string $type,
public int $parentBookId,
) {
/**
* @var int
*/
public $id;
/**
* @var int
*/
public $sort;
/**
* @var ?int
*/
public $parentChapterId;
/**
* @var string
*/
public $type;
/**
* @var int
*/
public $parentBookId;
public function __construct(int $id, int $sort, ?int $parentChapterId, string $type, int $parentBookId)
{
$this->id = $id;
$this->sort = $sort;
$this->parentChapterId = $parentChapterId;
$this->type = $type;
$this->parentBookId = $parentBookId;
}
}

View File

@@ -168,7 +168,7 @@ class BookSorter
$model->priority = $sortMapItem->sort;
}
if ($priorityChanged || $chapterChanged) {
if ($chapterChanged || $priorityChanged) {
$model::withoutTimestamps(fn () => $model->save());
}
}

View File

@@ -64,7 +64,6 @@ class LocaleManager
'sq' => 'sq_AL',
'sr' => 'sr_RS',
'sv' => 'sv_SE',
'th' => 'th_TH',
'tk' => 'tk_TM',
'tr' => 'tr_TR',
'uk' => 'uk_UA',

View File

@@ -10,7 +10,6 @@ use BookStack\Permissions\PermissionApplicator;
use BookStack\Users\Models\HasCreatorAndUpdater;
use BookStack\Users\Models\OwnableInterface;
use BookStack\Users\Models\User;
use BookStack\Util\UrlFilter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -72,7 +71,7 @@ class Attachment extends Model implements OwnableInterface
public function getUrl($openInline = false): string
{
if ($this->external && !str_starts_with($this->path, 'http')) {
return (new UrlFilter($this->path))->clean();
return $this->path;
}
return url('/attachments/' . $this->id . ($openInline ? '?open=true' : ''));

View File

@@ -11,7 +11,6 @@ use BookStack\Http\Controller;
use BookStack\Permissions\Permission;
use BookStack\Uploads\Attachment;
use BookStack\Uploads\AttachmentService;
use BookStack\Util\UrlFilter;
use Exception;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\Request;
@@ -108,9 +107,6 @@ class AttachmentController extends Controller
{
/** @var Attachment $attachment */
$attachment = Attachment::query()->findOrFail($attachmentId);
$this->checkOwnablePermission(Permission::PageView, $attachment->page);
$this->checkOwnablePermission(Permission::PageUpdate, $attachment->page);
$this->checkOwnablePermission(Permission::AttachmentUpdate, $attachment);
try {
$this->validate($request, [
@@ -124,6 +120,10 @@ class AttachmentController extends Controller
]), 422);
}
$this->checkOwnablePermission(Permission::PageView, $attachment->page);
$this->checkOwnablePermission(Permission::PageUpdate, $attachment->page);
$this->checkOwnablePermission(Permission::AttachmentUpdate, $attachment);
$attachment = $this->attachmentService->updateFile($attachment, [
'name' => $request->input('attachment_edit_name'),
'link' => $request->input('attachment_edit_url'),
@@ -142,10 +142,6 @@ class AttachmentController extends Controller
public function attachLink(Request $request)
{
$pageId = $request->input('attachment_link_uploaded_to');
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
$this->checkPermission(Permission::AttachmentCreateAll);
$this->checkOwnablePermission(Permission::PageUpdate, $page);
try {
$this->validate($request, [
@@ -160,6 +156,11 @@ class AttachmentController extends Controller
]), 422);
}
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
$this->checkPermission(Permission::AttachmentCreateAll);
$this->checkOwnablePermission(Permission::PageUpdate, $page);
$attachmentName = $request->input('attachment_link_name');
$link = $request->input('attachment_link_url');
$this->attachmentService->saveNewFromLink($attachmentName, $link, intval($pageId));
@@ -194,7 +195,6 @@ class AttachmentController extends Controller
$this->validate($request, [
'order' => ['required', 'array'],
]);
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
$this->checkOwnablePermission(Permission::PageUpdate, $page);
@@ -221,9 +221,10 @@ class AttachmentController extends Controller
throw new NotFoundException(trans('errors.attachment_not_found'));
}
$this->checkOwnablePermission(Permission::PageView, $page);
if ($attachment->external) {
$url = (new UrlFilter($attachment->path))->clean();
return redirect($url);
return redirect($attachment->path);
}
$fileName = $attachment->getFileName();
@@ -246,13 +247,6 @@ class AttachmentController extends Controller
{
/** @var Attachment $attachment */
$attachment = Attachment::query()->findOrFail($attachmentId);
try {
$this->pageQueries->findVisibleByIdOrFail($attachment->uploaded_to);
} catch (NotFoundException $exception) {
throw new NotFoundException(trans('errors.attachment_not_found'));
}
$this->checkOwnablePermission(Permission::AttachmentDelete, $attachment);
$this->attachmentService->deleteFile($attachment);

View File

@@ -2,7 +2,6 @@
namespace BookStack\Uploads\Controllers;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Http\Controller;
use BookStack\Permissions\Permission;
@@ -15,8 +14,7 @@ use Illuminate\Http\Request;
class DrawioImageController extends Controller
{
public function __construct(
protected ImageRepo $imageRepo,
protected PageQueries $pageQueries,
protected ImageRepo $imageRepo
) {
}
@@ -55,18 +53,16 @@ class DrawioImageController extends Controller
*/
public function create(Request $request)
{
$this->checkPermission(Permission::ImageCreateAll);
$validated = $this->validate($request, [
$this->validate($request, [
'image' => ['required', 'string'],
'uploaded_to' => ['required', 'integer'],
]);
$imageBase64Data = $validated['image'];
$uploadedTo = $validated['uploaded_to'];
$targetPage = $this->pageQueries->findVisibleByIdOrFail($uploadedTo);
$this->checkOwnablePermission(Permission::PageUpdate, $targetPage);
$this->checkPermission(Permission::ImageCreateAll);
$imageBase64Data = $request->input('image');
try {
$uploadedTo = $request->input('uploaded_to', 0);
$image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo);
} catch (ImageUploadException $e) {
return response($e->getMessage(), 500);

View File

@@ -2,7 +2,6 @@
namespace BookStack\Uploads\Controllers;
use BookStack\Entities\Queries\PageQueries;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Http\Controller;
use BookStack\Permissions\Permission;
@@ -15,8 +14,7 @@ use Illuminate\Validation\ValidationException;
class GalleryImageController extends Controller
{
public function __construct(
protected ImageRepo $imageRepo,
protected PageQueries $pageQueries,
protected ImageRepo $imageRepo
) {
}
@@ -58,26 +56,20 @@ class GalleryImageController extends Controller
$this->checkPermission(Permission::ImageCreateAll);
try {
$validated = $this->validate($request, [
$this->validate($request, [
'file' => $this->getImageValidationRules(),
'uploaded_to' => ['required', 'integer'],
]);
} catch (ValidationException $exception) {
$errors = $exception->errors();
$messages = array_merge($errors['file'] ?? [], $errors['uploaded_to'] ?? []);
return $this->jsonError(implode("\n", $messages));
return $this->jsonError(implode("\n", $exception->errors()['file']));
}
$uploadedTo = intval($validated['uploaded_to']);
$targetPage = $this->pageQueries->findVisibleByIdOrFail($uploadedTo);
$this->checkOwnablePermission(Permission::PageUpdate, $targetPage);
new OutOfMemoryHandler(function () {
return $this->jsonError(trans('errors.image_upload_memory_limit'));
});
try {
$imageUpload = $validated['file'];
$imageUpload = $request->file('file');
$uploadedTo = $request->input('uploaded_to', 0);
$image = $this->imageRepo->saveNew($imageUpload, 'gallery', $uploadedTo);
} catch (ImageUploadException $e) {
return response($e->getMessage(), 500);

View File

@@ -75,7 +75,6 @@ class ImageGalleryApiController extends ApiController
$this->checkPermission(Permission::ImageCreateAll);
$data = $this->validate($request, $this->rules()['create']);
$page = $this->pageQueries->findVisibleByIdOrFail($data['uploaded_to']);
$this->checkOwnablePermission(Permission::PageUpdate, $page);
$image = $this->imageRepo->saveNew($data['image'], $data['type'], $page->id);

View File

@@ -4,7 +4,6 @@ namespace BookStack\Users\Controllers;
use BookStack\Access\SocialDriverManager;
use BookStack\Access\UserInviteException;
use BookStack\Activity\ActivityType;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Exceptions\UserUpdateException;
use BookStack\Http\Controller;
@@ -209,20 +208,4 @@ class UserController extends Controller
return redirect('/settings/users');
}
/**
* Reset MFA for the specified user.
*/
public function resetMfa(Request $request, int $id)
{
$this->preventAccessInDemoMode();
$this->checkPermission(Permission::UsersManage);
$user = $this->userRepo->getById($id);
$user->mfaValues()->delete();
$this->logActivity(ActivityType::USER_MFA_RESET, $user);
return redirect("/settings/users/{$user->id}");
}
}

View File

@@ -1,16 +1,13 @@
<?php
namespace BookStack\Util\HtmlPurifier;
namespace BookStack\Util;
use BookStack\App\AppVersion;
use BookStack\Util\HtmlPurifier\Filters\UriLimitFileProtocolToAnchors;
use BookStack\Util\UrlFilter;
use HTMLPurifier;
use HTMLPurifier_Config;
use HTMLPurifier_DefinitionCache_Serializer;
use HTMLPurifier_HTML5Config;
use HTMLPurifier_HTMLDefinition;
use HTMLPurifier_URIDefinition;
/**
* Provides a configured HTML Purifier instance.
@@ -36,12 +33,7 @@ class ConfiguredHtmlPurifier
$htmlDef = $config->getDefinition('HTML', true, true);
if ($htmlDef instanceof HTMLPurifier_HTMLDefinition) {
$this->configureHtmlDefinition($htmlDef);
}
$uriDef = $config->getDefinition('URI', true, true);
if ($uriDef instanceof HTMLPurifier_URIDefinition) {
$this->configureUriDefinition($uriDef);
$this->configureDefinition($htmlDef);
}
$this->purifier = new HTMLPurifier($config);
@@ -85,18 +77,21 @@ class ConfiguredHtmlPurifier
$config->set('Attr.ID.HTML5', true);
$config->set('Output.FixInnerHTML', false);
$config->set('URI.SafeIframeRegexp', '%^(http://|https://|//)%');
$allowedSchemes = UrlFilter::getAllowedSchemes();
$allowedSchemesSetting = [];
foreach ($allowedSchemes as $scheme) {
$allowedSchemesSetting[$scheme] = true;
}
$config->set('URI.AllowedSchemes', $allowedSchemesSetting);
$config->set('URI.AllowedSchemes', [
'http' => true,
'https' => true,
'mailto' => true,
'ftp' => true,
'nntp' => true,
'news' => true,
'tel' => true,
'file' => true,
]);
// $config->set('Cache.DefinitionImpl', null); // Disable cache during testing
}
protected function configureHtmlDefinition(HTMLPurifier_HTMLDefinition $definition): void
public function configureDefinition(HTMLPurifier_HTMLDefinition $definition): void
{
// Allow the object element
$definition->addElement(
@@ -154,15 +149,6 @@ class ConfiguredHtmlPurifier
// Allow mention-ids on links
$definition->addAttribute('a', 'data-mention-user-id', 'Number');
// Set up custom handler for srcset to limit accepted types
$definition->addAttribute('img', 'srcset', new SrcsetAttrDef());
$definition->addAttribute('source', 'srcset', new SrcsetAttrDef());
}
protected function configureUriDefinition(HTMLPurifier_URIDefinition $definition): void
{
$definition->registerFilter(new UriLimitFileProtocolToAnchors());
}
public function purify(string $html): string

View File

@@ -30,8 +30,6 @@ class CspService
$this->getFrameAncestors(),
$this->getFrameSrc(),
$this->getScriptSrc(),
$this->getStyleSrc(),
$this->getImgSrc(),
$this->getObjectSrc(),
$this->getBaseUri(),
];
@@ -47,8 +45,6 @@ class CspService
$headers = [
$this->getFrameSrc(),
$this->getScriptSrc(),
$this->getStyleSrc(),
$this->getImgSrc(),
$this->getObjectSrc(),
$this->getBaseUri(),
];
@@ -119,22 +115,6 @@ class CspService
return "object-src 'self'";
}
/**
* Creates CSP 'style-src' rule to restrict where styles can be loaded from.
*/
protected function getStyleSrc(): string
{
return 'style-src ' . implode(' ', $this->getAllowedStyleSources());
}
/**
* Creates CSP 'img-src' rule to restrict where images can be loaded from.
*/
protected function getImgSrc(): string
{
return 'img-src ' . implode(' ', $this->getAllowedImageSources());
}
/**
* Creates CSP 'base-uri' rule to restrict what base tags can be set on
* the page to prevent manipulation of relative links.
@@ -164,59 +144,6 @@ class CspService
return array_filter($sources);
}
/**
* Get allowed style sources for the style-src directive.
*/
protected function getAllowedStyleSources(): array
{
$configured = config('app.style_sources');
if (is_string($configured)) {
$sources = array_filter(explode(' ', $configured));
array_unshift($sources, "'self'");
// Ensure 'unsafe-inline' is quoted if present
// This is done as attempting to pass this in env values with quotes can either
// be awkward or cause issues.
$unsafeInlineIndex = array_search('unsafe-inline', $sources, true);
if ($unsafeInlineIndex !== false) {
$sources[$unsafeInlineIndex] = "'unsafe-inline'";
}
return array_values(array_unique($sources));
}
return [
"'self'",
"'unsafe-inline'",
'http:',
'https:',
];
}
/**
* Get allowed image sources for the img-src directive.
*/
protected function getAllowedImageSources(): array
{
$configured = config('app.image_sources');
if (is_string($configured)) {
$sources = array_filter(explode(' ', $configured));
array_unshift($sources, "'self'", 'blob:', 'data:');
return array_values(array_unique($sources));
}
return [
"'self'",
'data:',
'blob:',
'http:',
'https:',
];
}
/**
* Extract the host name of the configured drawio URL for use in CSP.
* Returns empty string if not in use.

View File

@@ -2,7 +2,6 @@
namespace BookStack\Util;
use BookStack\Util\HtmlPurifier\ConfiguredHtmlPurifier;
use DOMAttr;
use DOMElement;
use DOMNodeList;

View File

@@ -1,53 +0,0 @@
<?php
namespace BookStack\Util\HtmlPurifier\Filters;
use HTMLPurifier_Config;
use HTMLPurifier_Context;
use HTMLPurifier_URI;
use HTMLPurifier_URIFilter;
/**
* Limits file:// URIs to only be used on anchor tags href attributes.
* This prevents use on iframes/embeds/images where they can be used to load external
* content on the network, triggering calls which may include NTLM auth hashes when in
* certain windows based environments.
*/
class UriLimitFileProtocolToAnchors extends HTMLPurifier_URIFilter
{
/**
* @type string
*/
public $name = 'LimitFileProtocolToAnchors';
/**
* @type bool
*/
public $always_load = true;
/**
* @param HTMLPurifier_URI $uri
* @param HTMLPurifier_Config $config
* @param HTMLPurifier_Context $context
* @return bool
*/
public function filter(&$uri, $config, $context)
{
// Ensure we're only filtering file:// URIs'
if ($uri->scheme !== 'file') {
return true;
}
$token = $context->get('CurrentToken', true);
$attr = $context->get('CurrentAttr', true);
// Only allow if used on hrefs on anchor tags
$isAnchor = $token && $token->name === 'a';
$isHref = $attr === 'href';
if ($isAnchor && $isHref) {
return true;
}
return false;
}
}

View File

@@ -1,26 +0,0 @@
<?php
namespace BookStack\Util\HtmlPurifier;
use HTMLPurifier_AttrDef;
/**
* Custom attribute definition to filter out potentially dangerous
* values from the srcset attribute.
*/
class SrcsetAttrDef extends HTMLPurifier_AttrDef
{
public function validate($string, $config, $context)
{
$lower = strtolower($string);
$nonAllowed = ['javascript:', 'vbscript:', 'data:', 'file:'];
foreach ($nonAllowed as $nonAllowedString) {
if (str_contains($lower, $nonAllowedString)) {
return false;
}
}
return $string;
}
}

View File

@@ -8,10 +8,6 @@ use BookStack\Exceptions\HttpFetchException;
* Validate the host we're connecting to when making a server-side-request.
* Will use the given hosts config if given during construction otherwise
* will look to the app configured config.
*
* The config format is a space-seperated list of URL prefixes which should contain the
* protocol and host. It can optionally define a path prefix as part of the URL.
* Wildcards, via a '*', can be used within these elements to match anything but a '/'.
*/
class SsrUrlValidator
{
@@ -52,34 +48,15 @@ class SsrUrlValidator
{
$pattern = rtrim(trim($pattern), '/');
$url = trim($url);
$urlParts = parse_url($url);
if (empty($pattern) || empty($url) || $urlParts === false) {
if (empty($pattern) || empty($url)) {
return false;
}
// Prevent potential tricks using percent encoded slashes
if (str_contains(strtolower($urlParts['host'] ?? ''), '%2f')) {
return false;
}
// Disregard query and fragment
$url = explode('?', $url, 2)[0];
$url = explode('#', $url, 2)[0];
// Disregard userinfo if existing
if (!empty($urlParts['user']) || !empty($urlParts['pass'])) {
[$start, $postUserinfo] = explode('@', $url, 2);
$preUserinfo = explode('//', $start, 2)[0];
$url = ($preUserinfo ? $preUserinfo . '//' : '') . $postUserinfo;
}
// Prepare pattern
$quoted = preg_quote($pattern, '/');
$regexPattern = str_replace('\*', '[^\/]*', $quoted);
$regexPattern = str_replace('\*', '.*', $quoted);
// Check against our URL
return preg_match('/^' . $regexPattern . '($|\/.*$)/i', $url);
return preg_match('/^' . $regexPattern . '($|\/.*$|#.*$)/i', $url);
}
/**

View File

@@ -1,36 +0,0 @@
<?php
namespace BookStack\Util;
class UrlComparison
{
public function __construct(
protected readonly string $a,
protected readonly string $b,
) {
}
/**
* Check if the two URLs have the same origin.
*/
public function originsMatch(): bool
{
$aParts = parse_url($this->a);
$bParts = parse_url($this->b);
return ($aParts['host'] ?? '') === ($bParts['host'] ?? '')
&& ($aParts['scheme'] ?? '') === ($bParts['scheme'] ?? '')
&& ($aParts['port'] ?? '') === ($bParts['port'] ?? '');
}
/**
* Check if there's some overlap between the two URLs' paths.
*/
public function pathsOverlap(): bool
{
$aPath = parse_url($this->a, PHP_URL_PATH) ?? '';
$bPath = parse_url($this->b, PHP_URL_PATH) ?? '';
return str_starts_with($aPath, $bPath) || str_starts_with($bPath, $aPath);
}
}

View File

@@ -1,103 +0,0 @@
<?php
declare(strict_types=1);
namespace BookStack\Util;
/**
* Helps filter URLs to prevent use of undesired schemes.
* Also parses and rebuilds the URL to ensure it's valid.
*/
class UrlFilter
{
protected static array $allowedSchemes = ['http', 'https', 'mailto', 'tel', 'file', 'ftp', 'nntp', 'news'];
protected string $url;
public function __construct(string $url)
{
$this->url = trim($url);
}
/**
* Check if the URL is allowed to be generally used as a link
* in the application. This does not assure the original URL string
* provided is safe as-is. Ensure you use the clean method to produce
* a URL that is considered safe to use.
*/
public function isAllowed(): bool
{
$urlParts = parse_url($this->url);
if (!$urlParts) {
return false;
}
// Extra check to help avoid scenarios where non-standard characters are used in the scheme
// to work around parse_url handling with URLs which may be interpreted by the browser differently.
if (str_contains($this->url, ':') && !preg_match('/^[a-z]+:/i', $this->url)) {
return false;
}
if (isset($urlParts['scheme'])) {
return in_array(strtolower($urlParts['scheme']), self::$allowedSchemes);
}
return true;
}
/**
* Clean the URL to ensure it's valid and only uses the allowed schemes.
* If the URL is not allowed, return a placeholder.
*/
public function clean(): string
{
if (!$this->isAllowed()) {
return '#badlink';
}
$urlParts = parse_url($this->url);
if (!$urlParts) {
return '#badlink';
}
$url = '';
if (isset($urlParts['scheme']) || isset($urlParts['host'])) {
$scheme = strtolower($urlParts['scheme'] ?? 'https');
$url = $scheme . ':' . (isset($urlParts['host']) ? '//' : '');
}
if (isset($urlParts['user']) || isset($urlParts['pass'])) {
$url .= $urlParts['user'] ?? '';
if (isset($urlParts['pass'])) {
$url .= ':' . $urlParts['pass'];
}
$url .= '@';
}
if (isset($urlParts['host'])) {
$url .= $urlParts['host'];
}
if (isset($urlParts['port'])) {
$url .= ':' . $urlParts['port'];
}
if (isset($urlParts['path'])) {
$url .= $urlParts['path'];
}
if (isset($urlParts['query'])) {
$url .= '?' . $urlParts['query'];
}
if (isset($urlParts['fragment'])) {
$url .= '#' . $urlParts['fragment'];
}
return $url;
}
/**
* Get schemes that are allowed to be used in content links.
*/
public static function getAllowedSchemes(): array
{
return self::$allowedSchemes;
}
}

818
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,10 @@
project_id: "377219"
project_identifier: bookstack
api_token_env: CROWDIN_PERSONAL_TOKEN
base_path: .
preserve_hierarchy: false
pull_request_title: Updated translations with latest Crowdin changes
pull_request_labels:
- "Translations"
- ":earth_africa: Translations"
files:
- source: /lang/en/*.php
translation: /lang/%two_letters_code%/%original_file_name%

View File

@@ -18,7 +18,7 @@ ARG BRANCH=development
# Download BookStack & install PHP deps
RUN mkdir -p /var/www && \
git clone https://codeberg.org/bookstack/bookstack.git --branch "$BRANCH" --single-branch /var/www/bookstack && \
git clone https://github.com/bookstackapp/bookstack.git --branch "$BRANCH" --single-branch /var/www/bookstack && \
cd /var/www/bookstack && \
wget https://raw.githubusercontent.com/composer/getcomposer.org/f3108f64b4e1c1ce6eb462b159956461592b3e3e/web/installer -O - -q | php -- --quiet --filename=composer && \
php composer install

View File

@@ -74,7 +74,7 @@ Theme::registerCommand(new SayHelloCommand());
## Available Events
All available events dispatched by BookStack are exposed as static properties on the `\BookStack\Theming\ThemeEvents` class, which can be found within the file `app/Theming/ThemeEvents.php` relative to your root BookStack folder. Alternatively, the events for the latest release can be [seen on Codeberg here](https://codeberg.org/bookstack/bookstack/src/branch/release/app/Theming/ThemeEvents.php).
All available events dispatched by BookStack are exposed as static properties on the `\BookStack\Theming\ThemeEvents` class, which can be found within the file `app/Theming/ThemeEvents.php` relative to your root BookStack folder. Alternatively, the events for the latest release can be [seen on GitHub here](https://github.com/BookStackApp/BookStack/blob/release/app/Theming/ThemeEvents.php).
The comments above each constant with the `ThemeEvents.php` file describe the dispatch conditions of the event, in addition to the arguments the action will receive. The comments may also describe any ways the return value of the action may be used.

View File

@@ -12,13 +12,13 @@ Feature releases are generally larger, bringing new features in addition to fixe
### Release Planning Process
Each BookStack release will have a [milestone](https://codeberg.org/bookstack/bookstack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed.
Each BookStack release will have a [milestone](https://github.com/BookStackApp/BookStack/milestones) created with issues & pull requests assigned to it to define what will be in that release. Milestones are built up then worked through until complete at which point, after some testing and documentation updates, the release will be deployed.
### Release Announcements
Feature releases, and some patch releases, will be accompanied by a post on the [BookStack blog](https://www.bookstackapp.com/blog/) which will provide additional detail on features, changes & updates otherwise the [Codeberg release page](https://codeberg.org/bookstack/bookstack/releases) will show a list of changes. You can sign up to be alerted to new BookStack blog posts (once per week maximum) [at this link](https://updates.bookstackapp.com/signup/bookstack-news-and-updates).
Feature releases, and some patch releases, will be accompanied by a post on the [BookStack blog](https://www.bookstackapp.com/blog/) which will provide additional detail on features, changes & updates otherwise the [GitHub release page](https://github.com/BookStackApp/BookStack/releases) will show a list of changes. You can sign up to be alerted to new BookStack blog posts (once per week maximum) [at this link](https://updates.bookstackapp.com/signup/bookstack-news-and-updates).
### Release Technical Process
Deploying a release, at a high level, simply involves merging the development branch into the release branch before then building & committing any release-only assets.
A helper script [can be found in our](https://codeberg.org/bookstack/devops/src/branch/main/meta-scripts/bookstack-release-steps) devops repo which provides the steps and commands for deploying a new release.
A helper script [can be found in our](https://github.com/BookStackApp/devops/blob/main/meta-scripts/bookstack-release-steps) devops repo which provides the steps and commands for deploying a new release.

View File

@@ -2,7 +2,7 @@
**Warning: This API is currently in development and may change without notice.**
Feedback is very much welcomed via this issue: https://codeberg.org/bookstack/bookstack/issues/5937
Feedback is very much welcomed via this issue: https://github.com/BookStackApp/BookStack/issues/5937
This document covers the JavaScript API for the (newer Lexical-based) WYSIWYG editor.
This API is built and designed to abstract the internals of the editor away

View File

@@ -69,9 +69,8 @@ Source: zeit/arg
Link: zeit/arg
-----------
argparse
License: MIT
License: Python-2.0
License File: node_modules/argparse/LICENSE
Copyright: Copyright (C) 2012 by Vitaly Puzrin
Source: nodeca/argparse
Link: nodeca/argparse
-----------
@@ -82,6 +81,34 @@ Copyright: Copyright (c) 2023 Inspect JS
Source: git+https://github.com/inspect-js/array-buffer-byte-length.git
Link: https://github.com/inspect-js/array-buffer-byte-length#readme
-----------
array-includes
License: MIT
License File: node_modules/array-includes/LICENSE
Copyright: Copyright (C) 2015 Jordan Harband
Source: git://github.com/es-shims/array-includes.git
Link: git://github.com/es-shims/array-includes.git
-----------
array.prototype.findlastindex
License: MIT
License File: node_modules/array.prototype.findlastindex/LICENSE
Copyright: Copyright (c) 2021 ECMAScript Shims
Source: git+https://github.com/es-shims/Array.prototype.findLastIndex.git
Link: https://github.com/es-shims/Array.prototype.findLastIndex#readme
-----------
array.prototype.flat
License: MIT
License File: node_modules/array.prototype.flat/LICENSE
Copyright: Copyright (c) 2017 ECMAScript Shims
Source: git://github.com/es-shims/Array.prototype.flat.git
Link: git://github.com/es-shims/Array.prototype.flat.git
-----------
array.prototype.flatmap
License: MIT
License File: node_modules/array.prototype.flatmap/LICENSE
Copyright: Copyright (c) 2017 ECMAScript Shims
Source: git://github.com/es-shims/Array.prototype.flatMap.git
Link: git://github.com/es-shims/Array.prototype.flatMap.git
-----------
arraybuffer.prototype.slice
License: MIT
License File: node_modules/arraybuffer.prototype.slice/LICENSE
@@ -142,14 +169,9 @@ Link: https://github.com/jestjs/jest.git
balanced-match
License: MIT
License File: node_modules/balanced-match/LICENSE.md
Copyright: Copyright (c) 2013 Julian Gruber &lt;******@************.***&gt;
Source: git://github.com/juliangruber/balanced-match.git
Link: git://github.com/juliangruber/balanced-match.git
-----------
baseline-browser-mapping
License: Apache-2.0
License File: node_modules/baseline-browser-mapping/LICENSE.txt
Source: git+https://github.com/web-platform-dx/baseline-browser-mapping.git
Link: git+https://github.com/web-platform-dx/baseline-browser-mapping.git
Link: https://github.com/juliangruber/balanced-match
-----------
binary-extensions
License: MIT
@@ -162,8 +184,9 @@ Link: sindresorhus/binary-extensions
brace-expansion
License: MIT
License File: node_modules/brace-expansion/LICENSE
Source: git+ssh://git@github.com/juliangruber/brace-expansion.git
Link: git+ssh://git@github.com/juliangruber/brace-expansion.git
Copyright: Copyright (c) 2013 Julian Gruber <******@************.***>
Source: git://github.com/juliangruber/brace-expansion.git
Link: https://github.com/juliangruber/brace-expansion
-----------
braces
License: MIT
@@ -449,8 +472,8 @@ Link: git://github.com/ljharb/define-properties.git
detect-libc
License: Apache-2.0
License File: node_modules/detect-libc/LICENSE
Source: git://github.com/lovell/detect-libc.git
Link: git://github.com/lovell/detect-libc.git
Source: git://github.com/lovell/detect-libc
Link: git://github.com/lovell/detect-libc
-----------
detect-newline
License: MIT
@@ -466,6 +489,12 @@ Copyright: Copyright (c) 2009-2015, Kevin Decker <********@*****.***>
Source: git://github.com/kpdecker/jsdiff.git
Link: git://github.com/kpdecker/jsdiff.git
-----------
doctrine
License: Apache-2.0
License File: node_modules/doctrine/LICENSE
Source: eslint/doctrine
Link: https://github.com/eslint/doctrine
-----------
dunder-proto
License: MIT
License File: node_modules/dunder-proto/LICENSE
@@ -482,8 +511,8 @@ electron-to-chromium
License: ISC
License File: node_modules/electron-to-chromium/LICENSE
Copyright: Copyright 2018 Kilian Valkhof
Source: git+https://github.com/Kilian/electron-to-chromium.git
Link: git+https://github.com/Kilian/electron-to-chromium.git
Source: https://github.com/kilian/electron-to-chromium/
Link: https://github.com/kilian/electron-to-chromium/
-----------
emittery
License: MIT
@@ -548,6 +577,13 @@ Copyright: Copyright (c) 2022 ECMAScript Shims
Source: git+https://github.com/es-shims/es-set-tostringtag.git
Link: https://github.com/es-shims/es-set-tostringtag#readme
-----------
es-shim-unscopables
License: MIT
License File: node_modules/es-shim-unscopables/LICENSE
Copyright: Copyright (c) 2022 Jordan Harband
Source: git+https://github.com/ljharb/es-shim-unscopables.git
Link: https://github.com/ljharb/es-shim-unscopables#readme
-----------
es-to-primitive
License: MIT
License File: node_modules/es-to-primitive/LICENSE
@@ -576,6 +612,27 @@ Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (https://sindres
Source: sindresorhus/escape-string-regexp
Link: sindresorhus/escape-string-regexp
-----------
eslint-import-resolver-node
License: MIT
License File: node_modules/eslint-import-resolver-node/LICENSE
Copyright: Copyright (c) 2015 Ben Mosher
Source: https://github.com/import-js/eslint-plugin-import
Link: https://github.com/import-js/eslint-plugin-import
-----------
eslint-module-utils
License: MIT
License File: node_modules/eslint-module-utils/LICENSE
Copyright: Copyright (c) 2015 Ben Mosher
Source: git+https://github.com/import-js/eslint-plugin-import.git
Link: https://github.com/import-js/eslint-plugin-import#readme
-----------
eslint-plugin-import
License: MIT
License File: node_modules/eslint-plugin-import/LICENSE
Copyright: Copyright (c) 2015 Ben Mosher
Source: https://github.com/import-js/eslint-plugin-import
Link: https://github.com/import-js/eslint-plugin-import
-----------
eslint-scope
License: BSD-2-Clause
License File: node_modules/eslint-scope/LICENSE
@@ -757,13 +814,6 @@ Copyright: Copyright (c) 2019 Jordan Harband
Source: git+https://github.com/inspect-js/functions-have-names.git
Link: https://github.com/inspect-js/functions-have-names#readme
-----------
generator-function
License: MIT
License File: node_modules/generator-function/LICENSE.md
Copyright: Copyright (c) 2015 Tiancheng “Timothy” Gu
Source: git+https://github.com/TimothyGu/generator-function.git
Link: https://github.com/TimothyGu/generator-function#readme
-----------
gensync
License: MIT
License File: node_modules/gensync/LICENSE
@@ -860,7 +910,7 @@ License: MIT
License File: node_modules/handlebars/LICENSE
Copyright: Copyright (C) 2011-2019 by Yehuda Katz
Source: https://github.com/handlebars-lang/handlebars.js.git
Link: https://handlebarsjs.com/
Link: https://www.handlebarsjs.com/
-----------
has-bigints
License: MIT
@@ -976,6 +1026,13 @@ Copyright: Copyright (c) 2014-present, Lee Byron and other contributors.
Source: git://github.com/immutable-js/immutable-js.git
Link: https://immutable-js.com
-----------
import-fresh
License: MIT
License File: node_modules/import-fresh/license
Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (https://sindresorhus.com)
Source: sindresorhus/import-fresh
Link: sindresorhus/import-fresh
-----------
import-local
License: MIT
License File: node_modules/import-local/license
@@ -1472,7 +1529,7 @@ License: MIT
License File: node_modules/js-yaml/LICENSE
Copyright: Copyright (C) 2011-2015 by Vitaly Puzrin
Source: nodeca/js-yaml
Link: https://github.com/nodeca/js-yaml
Link: nodeca/js-yaml
-----------
jsdom
License: MIT
@@ -1588,6 +1645,12 @@ License File: node_modules/lodash.memoize/LICENSE
Source: lodash/lodash
Link: https://lodash.com/
-----------
lodash.merge
License: MIT
License File: node_modules/lodash.merge/LICENSE
Source: lodash/lodash
Link: https://lodash.com/
-----------
lodash.throttle
License: MIT
License File: node_modules/lodash.throttle/LICENSE
@@ -1663,6 +1726,13 @@ Copyright: Copyright (c) Stephen Sugden <**@*************.***> (stephensugden.co
Source: grncdr/merge-stream
Link: grncdr/merge-stream
-----------
micromatch
License: MIT
License File: node_modules/micromatch/LICENSE
Copyright: Copyright (c) 2014-present, Jon Schlinkert.
Source: micromatch/micromatch
Link: https://github.com/micromatch/micromatch
-----------
mimic-fn
License: MIT
License File: node_modules/mimic-fn/license
@@ -1671,10 +1741,11 @@ Source: sindresorhus/mimic-fn
Link: sindresorhus/mimic-fn
-----------
minimatch
License: BlueOak-1.0.0
License File: node_modules/minimatch/LICENSE.md
Source: git@github.com:isaacs/minimatch
Link: git@github.com:isaacs/minimatch
License: ISC
License File: node_modules/minimatch/LICENSE
Copyright: Copyright (c) Isaac Z. Schlueter and Contributors
Source: git://github.com/isaacs/minimatch.git
Link: git://github.com/isaacs/minimatch.git
-----------
minimist
License: MIT
@@ -1683,8 +1754,9 @@ Source: git://github.com/minimistjs/minimist.git
Link: https://github.com/minimistjs/minimist
-----------
minipass
License: BlueOak-1.0.0
License File: node_modules/minipass/LICENSE.md
License: ISC
License File: node_modules/minipass/LICENSE
Copyright: Copyright (c) 2017-2023 npm, Inc., Isaac Z. Schlueter, and Contributors
Source: https://github.com/isaacs/minipass
Link: https://github.com/isaacs/minipass
-----------
@@ -1800,6 +1872,27 @@ Copyright: Copyright (c) 2014 Jordan Harband
Source: git://github.com/ljharb/object.assign.git
Link: git://github.com/ljharb/object.assign.git
-----------
object.fromentries
License: MIT
License File: node_modules/object.fromentries/LICENSE
Copyright: Copyright (c) 2018 Jordan Harband
Source: git://github.com/es-shims/Object.fromEntries.git
Link: git://github.com/es-shims/Object.fromEntries.git
-----------
object.groupby
License: MIT
License File: node_modules/object.groupby/LICENSE
Copyright: Copyright (c) 2023 ECMAScript Shims
Source: git+https://github.com/es-shims/Object.groupBy.git
Link: https://github.com/es-shims/Object.groupBy#readme
-----------
object.values
License: MIT
License File: node_modules/object.values/LICENSE
Copyright: Copyright (c) 2015 Jordan Harband
Source: git://github.com/es-shims/Object.values.git
Link: git://github.com/es-shims/Object.values.git
-----------
once
License: ISC
License File: node_modules/once/LICENSE
@@ -1855,6 +1948,13 @@ License File: node_modules/package-json-from-dist/LICENSE.md
Source: git+https://github.com/isaacs/package-json-from-dist.git
Link: git+https://github.com/isaacs/package-json-from-dist.git
-----------
parent-module
License: MIT
License File: node_modules/parent-module/license
Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (sindresorhus.com)
Source: sindresorhus/parent-module
Link: sindresorhus/parent-module
-----------
parse-json
License: MIT
License File: node_modules/parse-json/license
@@ -2059,8 +2159,8 @@ resolve
License: MIT
License File: node_modules/resolve/LICENSE
Copyright: Copyright (c) 2012 James Halliday
Source: ssh://github.com/browserify/resolve.git
Link: ssh://github.com/browserify/resolve.git
Source: git://github.com/browserify/resolve.git
Link: git://github.com/browserify/resolve.git
-----------
rrweb-cssom
License: MIT
@@ -2305,7 +2405,7 @@ Link: sindresorhus/string-width
string-width
License: MIT
License File: node_modules/string-width/license
Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (https://sindresorhus.com)
Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (sindresorhus.com)
Source: sindresorhus/string-width
Link: sindresorhus/string-width
-----------
@@ -2470,6 +2570,13 @@ Copyright: Copyright (c) 2014 Blake Embrey (*****@***********.***)
Source: git://github.com/TypeStrong/ts-node.git
Link: https://typestrong.org/ts-node
-----------
tsconfig-paths
License: MIT
License File: node_modules/tsconfig-paths/LICENSE
Copyright: Copyright (c) 2016 Jonas Kello
Source: https://github.com/dividab/tsconfig-paths
Link: https://github.com/dividab/tsconfig-paths
-----------
type-check
License: MIT
License File: node_modules/type-check/LICENSE
@@ -2703,7 +2810,7 @@ Link: chalk/wrap-ansi
wrap-ansi
License: MIT
License File: node_modules/wrap-ansi/license
Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (https://sindresorhus.com)
Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (sindresorhus.com)
Source: chalk/wrap-ansi
Link: chalk/wrap-ansi
-----------
@@ -2785,6 +2892,12 @@ Copyright: Copyright (c) Sindre Sorhus <************@*****.***> (https://sindres
Source: sindresorhus/yocto-queue
Link: sindresorhus/yocto-queue
-----------
@ampproject/remapping
License: Apache-2.0
License File: node_modules/@ampproject/remapping/LICENSE
Source: git+https://github.com/ampproject/remapping.git
Link: git+https://github.com/ampproject/remapping.git
-----------
@asamuzakjp/css-color
License: MIT
License File: node_modules/@asamuzakjp/css-color/LICENSE
@@ -3059,15 +3172,15 @@ Link: https://demurgos.github.io/v8-coverage
License: MIT
License File: node_modules/@codemirror/autocomplete/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Source: git+https://github.com/codemirror/autocomplete.git
Link: git+https://github.com/codemirror/autocomplete.git
Source: https://github.com/codemirror/autocomplete.git
Link: https://github.com/codemirror/autocomplete.git
-----------
@codemirror/commands
License: MIT
License File: node_modules/@codemirror/commands/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Source: git+https://github.com/codemirror/commands.git
Link: git+https://github.com/codemirror/commands.git
Source: https://github.com/codemirror/commands.git
Link: https://github.com/codemirror/commands.git
-----------
@codemirror/lang-css
License: MIT
@@ -3087,8 +3200,8 @@ Link: https://github.com/codemirror/lang-html.git
License: MIT
License File: node_modules/@codemirror/lang-javascript/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Source: git+https://github.com/codemirror/lang-javascript.git
Link: git+https://github.com/codemirror/lang-javascript.git
Source: https://github.com/codemirror/lang-javascript.git
Link: https://github.com/codemirror/lang-javascript.git
-----------
@codemirror/lang-json
License: MIT
@@ -3122,8 +3235,8 @@ Link: https://github.com/codemirror/lang-xml.git
License: MIT
License File: node_modules/@codemirror/language/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Source: git+https://github.com/codemirror/language.git
Link: git+https://github.com/codemirror/language.git
Source: https://github.com/codemirror/language.git
Link: https://github.com/codemirror/language.git
-----------
@codemirror/legacy-modes
License: MIT
@@ -3136,22 +3249,22 @@ Link: https://github.com/codemirror/legacy-modes.git
License: MIT
License File: node_modules/@codemirror/lint/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Source: git+https://github.com/codemirror/lint.git
Link: git+https://github.com/codemirror/lint.git
Source: https://github.com/codemirror/lint.git
Link: https://github.com/codemirror/lint.git
-----------
@codemirror/search
License: MIT
License File: node_modules/@codemirror/search/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Source: git+https://github.com/codemirror/search.git
Link: git+https://github.com/codemirror/search.git
Source: https://github.com/codemirror/search.git
Link: https://github.com/codemirror/search.git
-----------
@codemirror/state
License: MIT
License File: node_modules/@codemirror/state/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Source: git+https://github.com/codemirror/state.git
Link: git+https://github.com/codemirror/state.git
Source: https://github.com/codemirror/state.git
Link: https://github.com/codemirror/state.git
-----------
@codemirror/theme-one-dark
License: MIT
@@ -3164,8 +3277,8 @@ Link: https://github.com/codemirror/theme-one-dark.git
License: MIT
License File: node_modules/@codemirror/view/LICENSE
Copyright: Copyright (C) 2018-2021 by Marijn Haverbeke <******@*********.******> and others
Source: git+https://github.com/codemirror/view.git
Link: git+https://github.com/codemirror/view.git
Source: https://github.com/codemirror/view.git
Link: https://github.com/codemirror/view.git
-----------
@cspotcode/source-map-support
License: MIT
@@ -3245,6 +3358,12 @@ License File: node_modules/@eslint/core/LICENSE
Source: git+https://github.com/eslint/rewrite.git
Link: https://github.com/eslint/rewrite/tree/main/packages/core#readme
-----------
@eslint/eslintrc
License: MIT
License File: node_modules/@eslint/eslintrc/LICENSE
Source: eslint/eslintrc
Link: https://github.com/eslint/eslintrc#readme
-----------
@eslint/js
License: MIT
License File: node_modules/@eslint/js/LICENSE
@@ -3448,13 +3567,6 @@ Copyright: Copyright 2024 Justin Ridgewell <******@*********.****>
Source: git+https://github.com/jridgewell/sourcemaps.git
Link: https://github.com/jridgewell/sourcemaps/tree/main/packages/gen-mapping
-----------
@jridgewell/remapping
License: MIT
License File: node_modules/@jridgewell/remapping/LICENSE
Copyright: Copyright 2024 Justin Ridgewell <******@*********.****>
Source: git+https://github.com/jridgewell/sourcemaps.git
Link: https://github.com/jridgewell/sourcemaps/tree/main/packages/remapping
-----------
@jridgewell/resolve-uri
License: MIT
License File: node_modules/@jridgewell/resolve-uri/LICENSE
@@ -3567,6 +3679,13 @@ Copyright: Copyright (c) 2017-present Devon Govett
Source: https://github.com/parcel-bundler/watcher.git
Link: https://github.com/parcel-bundler/watcher.git
-----------
@parcel/watcher-linux-x64-musl
License: MIT
License File: node_modules/@parcel/watcher-linux-x64-musl/LICENSE
Copyright: Copyright (c) 2017-present Devon Govett
Source: https://github.com/parcel-bundler/watcher.git
Link: https://github.com/parcel-bundler/watcher.git
-----------
@parcel/watcher
License: MIT
License File: node_modules/@parcel/watcher/LICENSE
@@ -3585,12 +3704,18 @@ License: MIT
Source: git+https://github.com/un-ts/pkgr.git
Link: https://github.com/un-ts/pkgr/blob/master/packages/core
-----------
@rtsao/scc
License: MIT
License File: node_modules/@rtsao/scc/LICENSE
Copyright: Copyright (c) 2019 Ryan Tsao
Source: rtsao/scc
Link: rtsao/scc
-----------
@sinclair/typebox
License: MIT
License File: node_modules/@sinclair/typebox/license
Copyright: Copyright (c) 2017-2026 Haydn Paterson
Source: https://github.com/sinclairzx81/sinclair-typebox
Link: https://github.com/sinclairzx81/sinclair-typebox
Source: https://github.com/sinclairzx81/typebox
Link: https://github.com/sinclairzx81/typebox
-----------
@sinonjs/commons
License: BSD-3-Clause
@@ -3673,13 +3798,6 @@ Copyright: Copyright (c) Microsoft Corporation.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped.git
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/babel__traverse
-----------
@types/esrecurse
License: MIT
License File: node_modules/@types/esrecurse/LICENSE
Copyright: Copyright (c) Microsoft Corporation.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped.git
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/esrecurse
-----------
@types/estree
License: MIT
License File: node_modules/@types/estree/LICENSE
@@ -3729,6 +3847,11 @@ Copyright: Copyright (c) Microsoft Corporation.
Source: https://github.com/DefinitelyTyped/DefinitelyTyped.git
Link: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/json-schema
-----------
@types/json5
License: MIT
Source: https://www.github.com/DefinitelyTyped/DefinitelyTyped.git
Link: https://www.github.com/DefinitelyTyped/DefinitelyTyped.git
-----------
@types/linkify-it
License: MIT
License File: node_modules/@types/linkify-it/LICENSE
@@ -3803,3 +3926,8 @@ Link: https://github.com/ungap/structured-clone#readme
License: MIT
Source: git+https://github.com/unrs/unrs-resolver.git
Link: https://github.com/unrs/unrs-resolver#readme
-----------
@unrs/resolver-binding-linux-x64-musl
License: MIT
Source: git+https://github.com/unrs/unrs-resolver.git
Link: https://github.com/unrs/unrs-resolver#readme

View File

@@ -109,8 +109,8 @@ firebase/php-jwt
License: BSD-3-Clause
License File: vendor/firebase/php-jwt/LICENSE
Copyright: Copyright (c) 2011, Neuman Vong
Source: https://github.com/googleapis/php-jwt.git
Link: https://github.com/googleapis/php-jwt
Source: https://github.com/firebase/php-jwt.git
Link: https://github.com/firebase/php-jwt
-----------
fruitcake/php-cors
License: MIT

View File

@@ -99,8 +99,6 @@ return [
'user_update_notification' => 'تم تحديث المستخدم بنجاح',
'user_delete' => 'المستخدم المحذوف',
'user_delete_notification' => 'تم إزالة المستخدم بنجاح',
'user_mfa_reset' => 'reset MFA for user',
'user_mfa_reset_notification' => 'Multi-factor authentication methods reset',
// API Tokens
'api_token_create' => 'تم إنشاء رمز واجهة برمجة التطبيقات -API-',

View File

@@ -8,7 +8,6 @@ return [
'failed' => 'البيانات المعطاة لا توافق سجلاتنا.',
'throttle' => 'تجاوزت الحد الأقصى من المحاولات. الرجاء المحاولة مرة أخرى بعد :seconds ثانية/ثواني.',
'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.',
// Login & Register
'sign_up' => 'إنشاء حساب',

View File

@@ -173,7 +173,6 @@ return [
'books_sort_desc' => 'نقل الفصول والصفحات داخل الكتاب لإعادة تنظيم محتوياته. يمكن إضافة كتب أخرى مما يسمح بنقل الفصول والصفحات بسهولة بين الكتب. اختياريًا، يمكن تعيين قاعدة فرز تلقائي لفرز محتويات هذا الكتاب تلقائيًا عند حدوث تغييرات.',
'books_sort_auto_sort' => 'خِيار الفرز التلقائي',
'books_sort_auto_sort_active' => 'الفرز التلقائي الشَغَّال: :sortName',
'books_sort_auto_sort_creation_hint' => 'Auto sort option rules can be created in the "Lists & Sorting" settings area by a user with the relevant permissions.',
'books_sort_named' => 'فرز كتاب :bookName',
'books_sort_name' => 'ترتيب حسب الإسم',
'books_sort_created' => 'ترتيب حسب تاريخ الإنشاء',
@@ -331,9 +330,6 @@ return [
// Editor Sidebar
'toggle_sidebar' => 'تبديل الشريط الجانبي',
'page_contents' => 'Page Contents',
'page_contents_none' => 'No headings were found in the page content.',
'page_contents_info' => 'The contents menu is generated from any heading formats used in the page.',
'page_tags' => 'وسوم الصفحة',
'chapter_tags' => 'وسوم الفصل',
'book_tags' => 'وسوم الكتاب',

View File

@@ -207,7 +207,6 @@ return [
'role_all' => 'الكل',
'role_own' => 'ما يخص',
'role_controlled_by_asset' => 'يتحكم فيها الأصول التي يتم رفعها إلى',
'role_controlled_by_page_delete' => 'Controlled by page delete permissions',
'role_save' => 'حفظ الدور',
'role_users' => 'مستخدمون داخل هذا الدور',
'role_users_none' => 'لم يتم تعيين أي مستخدمين لهذا الدور',
@@ -264,9 +263,6 @@ return [
'users_mfa_desc' => 'إعداد المصادقة متعددة العوامل كطبقة إضافية من الأمان لحساب المستخدم الخاص بك.',
'users_mfa_x_methods' => ':count طريقة مُهيأة | :count طرق مُهيأة',
'users_mfa_configure' => 'إعداد الطرق',
'users_mfa_reset' => 'Reset Multi-Factor Authentication Methods',
'users_mfa_reset_desc' => 'This will reset and clear all configured multi-factor authentication methods for this user. If multi-factor authentication is required by any of their roles, they\'ll be prompted to configure new methods on their next login.',
'users_mfa_reset_confirm' => 'Are you sure you want to reset multi-factor authentication for this user?',
// API Tokens
'user_api_token_create' => 'قم بإنشاء رمز API',
@@ -366,9 +362,7 @@ return [
'ru' => 'Русский',
'sk' => 'Slovensky',
'sl' => 'Slovenščina',
'sr' => 'Српски',
'sv' => 'Svenska',
'th' => 'ภาษาไทย',
'tr' => 'Türkçe',
'uk' => 'Українська',
'uz' => 'Ozbekcha',

View File

@@ -99,8 +99,6 @@ return [
'user_update_notification' => 'Потребителят е обновен успешно',
'user_delete' => 'deleted user',
'user_delete_notification' => 'Потребителят е премахнат успешно',
'user_mfa_reset' => 'reset MFA for user',
'user_mfa_reset_notification' => 'Multi-factor authentication methods reset',
// API Tokens
'api_token_create' => 'created API token',

View File

@@ -8,7 +8,6 @@ return [
'failed' => 'Въведените данни не съвпадат с информацията в системата.',
'throttle' => 'Твърде много опити за влизане. Опитайте пак след :seconds секунди.',
'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.',
// Login & Register
'sign_up' => 'Регистриране',

View File

@@ -173,7 +173,6 @@ return [
'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books. Optionally an auto sort rule can be set to automatically sort this book\'s contents upon changes.',
'books_sort_auto_sort' => 'Auto Sort Option',
'books_sort_auto_sort_active' => 'Auto Sort Active: :sortName',
'books_sort_auto_sort_creation_hint' => 'Auto sort option rules can be created in the "Lists & Sorting" settings area by a user with the relevant permissions.',
'books_sort_named' => 'Сортирай книга :bookName',
'books_sort_name' => 'Сортиране по име',
'books_sort_created' => 'Сортирай по дата на създаване',
@@ -331,9 +330,6 @@ return [
// Editor Sidebar
'toggle_sidebar' => 'Toggle Sidebar',
'page_contents' => 'Page Contents',
'page_contents_none' => 'No headings were found in the page content.',
'page_contents_info' => 'The contents menu is generated from any heading formats used in the page.',
'page_tags' => 'Тагове на страницата',
'chapter_tags' => 'Тагове на главата',
'book_tags' => 'Тагове на книгата',

View File

@@ -207,7 +207,6 @@ return [
'role_all' => 'Всички',
'role_own' => 'Собствени',
'role_controlled_by_asset' => 'Контролирани от актива, към който са качени',
'role_controlled_by_page_delete' => 'Controlled by page delete permissions',
'role_save' => 'Запази ролята',
'role_users' => 'Потребители в тази роля',
'role_users_none' => 'В момента няма потребители, назначени за тази роля',
@@ -264,9 +263,6 @@ return [
'users_mfa_desc' => 'Настрой многофакторно удостверяване като втори слой сигурност на твоя профил.',
'users_mfa_x_methods' => ':count метод е настроен|:count методи са настроени',
'users_mfa_configure' => 'Конфигурирай методи',
'users_mfa_reset' => 'Reset Multi-Factor Authentication Methods',
'users_mfa_reset_desc' => 'This will reset and clear all configured multi-factor authentication methods for this user. If multi-factor authentication is required by any of their roles, they\'ll be prompted to configure new methods on their next login.',
'users_mfa_reset_confirm' => 'Are you sure you want to reset multi-factor authentication for this user?',
// API Tokens
'user_api_token_create' => 'Създай API маркер',
@@ -366,9 +362,7 @@ return [
'ru' => 'Русский',
'sk' => 'Slovensky',
'sl' => 'Slovenščina',
'sr' => 'Српски',
'sv' => 'Svenska',
'th' => 'ภาษาไทย',
'tr' => 'Türkçe',
'uk' => 'Українська',
'uz' => 'Ozbekcha',

View File

@@ -99,8 +99,6 @@ return [
'user_update_notification' => 'ব্যবহারকারীটি সার্থকভাবে হালনাগাদ করা হয়েছে',
'user_delete' => 'ব্যবহারকারীটি মুছে ফেলেছেন',
'user_delete_notification' => 'ব্যবহারকারীটি সার্থকভাবে মুছে ফেলা হয়েছে',
'user_mfa_reset' => 'reset MFA for user',
'user_mfa_reset_notification' => 'Multi-factor authentication methods reset',
// API Tokens
'api_token_create' => 'এপিআই টোকেনটি তৈরী করেছেন',

View File

@@ -8,7 +8,6 @@ return [
'failed' => 'প্রদত্ত তথ্যনিরূপিত কোন রেকর্ড পাওয়া যায়নি।',
'throttle' => 'লগইন প্রচেষ্টার সীমা অতিক্রান্ত। দয়া করে :seconds সেকেন্ড পর আবার চেষ্টা করুন।',
'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.',
// Login & Register
'sign_up' => 'নিবন্ধিত হোন',

View File

@@ -173,7 +173,6 @@ return [
'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books. Optionally an auto sort rule can be set to automatically sort this book\'s contents upon changes.',
'books_sort_auto_sort' => 'Auto Sort Option',
'books_sort_auto_sort_active' => 'Auto Sort Active: :sortName',
'books_sort_auto_sort_creation_hint' => 'Auto sort option rules can be created in the "Lists & Sorting" settings area by a user with the relevant permissions.',
'books_sort_named' => 'Sort Book :bookName',
'books_sort_name' => 'Sort by Name',
'books_sort_created' => 'Sort by Created Date',
@@ -331,9 +330,6 @@ return [
// Editor Sidebar
'toggle_sidebar' => 'Toggle Sidebar',
'page_contents' => 'Page Contents',
'page_contents_none' => 'No headings were found in the page content.',
'page_contents_info' => 'The contents menu is generated from any heading formats used in the page.',
'page_tags' => 'Page Tags',
'chapter_tags' => 'Chapter Tags',
'book_tags' => 'Book Tags',

View File

@@ -207,7 +207,6 @@ return [
'role_all' => 'All',
'role_own' => 'Own',
'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
'role_controlled_by_page_delete' => 'Controlled by page delete permissions',
'role_save' => 'Save Role',
'role_users' => 'Users in this role',
'role_users_none' => 'No users are currently assigned to this role',
@@ -264,9 +263,6 @@ return [
'users_mfa_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.',
'users_mfa_x_methods' => ':count method configured|:count methods configured',
'users_mfa_configure' => 'Configure Methods',
'users_mfa_reset' => 'Reset Multi-Factor Authentication Methods',
'users_mfa_reset_desc' => 'This will reset and clear all configured multi-factor authentication methods for this user. If multi-factor authentication is required by any of their roles, they\'ll be prompted to configure new methods on their next login.',
'users_mfa_reset_confirm' => 'Are you sure you want to reset multi-factor authentication for this user?',
// API Tokens
'user_api_token_create' => 'Create API Token',
@@ -366,9 +362,7 @@ return [
'ru' => 'Русский',
'sk' => 'Slovensky',
'sl' => 'Slovenščina',
'sr' => 'Српски',
'sv' => 'Svenska',
'th' => 'ภาษาไทย',
'tr' => 'Türkçe',
'uk' => 'Українська',
'uz' => 'Ozbekcha',

View File

@@ -99,8 +99,6 @@ return [
'user_update_notification' => 'User successfully updated',
'user_delete' => 'deleted user',
'user_delete_notification' => 'User successfully removed',
'user_mfa_reset' => 'reset MFA for user',
'user_mfa_reset_notification' => 'Multi-factor authentication methods reset',
// API Tokens
'api_token_create' => 'created API token',

View File

@@ -8,7 +8,6 @@ return [
'failed' => 'Ovi pristupni podaci se ne slažu sa našom evidencijom.',
'throttle' => 'Preveliki broj pokušaja prijave. Molimo vas da pokušate ponovo za :seconds sekundi.',
'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.',
// Login & Register
'sign_up' => 'Registruj se',

View File

@@ -173,7 +173,6 @@ return [
'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books. Optionally an auto sort rule can be set to automatically sort this book\'s contents upon changes.',
'books_sort_auto_sort' => 'Auto Sort Option',
'books_sort_auto_sort_active' => 'Auto Sort Active: :sortName',
'books_sort_auto_sort_creation_hint' => 'Auto sort option rules can be created in the "Lists & Sorting" settings area by a user with the relevant permissions.',
'books_sort_named' => 'Sortiraj knjigu :bookName',
'books_sort_name' => 'Sortiraj po imenu',
'books_sort_created' => 'Sortiraj po datumu kreiranja',
@@ -331,9 +330,6 @@ return [
// Editor Sidebar
'toggle_sidebar' => 'Toggle Sidebar',
'page_contents' => 'Page Contents',
'page_contents_none' => 'No headings were found in the page content.',
'page_contents_info' => 'The contents menu is generated from any heading formats used in the page.',
'page_tags' => 'Oznake stranice',
'chapter_tags' => 'Oznake poglavlja',
'book_tags' => 'Oznake knjige',

View File

@@ -207,7 +207,6 @@ return [
'role_all' => 'All',
'role_own' => 'Own',
'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to',
'role_controlled_by_page_delete' => 'Controlled by page delete permissions',
'role_save' => 'Save Role',
'role_users' => 'Users in this role',
'role_users_none' => 'No users are currently assigned to this role',
@@ -264,9 +263,6 @@ return [
'users_mfa_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.',
'users_mfa_x_methods' => ':count method configured|:count methods configured',
'users_mfa_configure' => 'Configure Methods',
'users_mfa_reset' => 'Reset Multi-Factor Authentication Methods',
'users_mfa_reset_desc' => 'This will reset and clear all configured multi-factor authentication methods for this user. If multi-factor authentication is required by any of their roles, they\'ll be prompted to configure new methods on their next login.',
'users_mfa_reset_confirm' => 'Are you sure you want to reset multi-factor authentication for this user?',
// API Tokens
'user_api_token_create' => 'Create API Token',
@@ -366,9 +362,7 @@ return [
'ru' => 'Русский',
'sk' => 'Slovensky',
'sl' => 'Slovenščina',
'sr' => 'Српски',
'sv' => 'Svenska',
'th' => 'ภาษาไทย',
'tr' => 'Türkçe',
'uk' => 'Українська',
'uz' => 'Ozbekcha',

View File

@@ -99,8 +99,6 @@ return [
'user_update_notification' => 'Sha actualitzat lusuari',
'user_delete' => 'ha suprimit lusuari',
'user_delete_notification' => 'Sha suprimit lusuari',
'user_mfa_reset' => 'reset MFA for user',
'user_mfa_reset_notification' => 'Multi-factor authentication methods reset',
// API Tokens
'api_token_create' => 'ha creat el testimoni API',

View File

@@ -8,7 +8,6 @@ return [
'failed' => 'Aquestes credencials no existeixen al nostre registre.',
'throttle' => 'Massa intents dinici de sessió. Torneu-ho a provar daquí :seconds segons.',
'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.',
// Login & Register
'sign_up' => 'Registreu-vos',

View File

@@ -173,7 +173,6 @@ return [
'books_sort_desc' => 'Mou capítols i pàgines dins d\'un llibre per reorganitzar el seu contingut. Es poden afegir altres llibres que permetin moure fàcilment capítols i pàgines entre llibres. De manera opcional, es poden establir regles d\'ordenació automàtica per ordenar automàticament el contingut d\'aquest llibre quan hi hagi canvis.',
'books_sort_auto_sort' => 'Opció d\'ordenació automàtica',
'books_sort_auto_sort_active' => 'Opció d\'ordenació activa :sortName',
'books_sort_auto_sort_creation_hint' => 'Auto sort option rules can be created in the "Lists & Sorting" settings area by a user with the relevant permissions.',
'books_sort_named' => 'Ordena el llibre &laquo;:bookName&raquo;',
'books_sort_name' => 'Ordena pel nom',
'books_sort_created' => 'Ordena per la data de creació',
@@ -331,9 +330,6 @@ return [
// Editor Sidebar
'toggle_sidebar' => 'Commuta la barra lateral',
'page_contents' => 'Page Contents',
'page_contents_none' => 'No headings were found in the page content.',
'page_contents_info' => 'The contents menu is generated from any heading formats used in the page.',
'page_tags' => 'Etiquetes de la pàgina',
'chapter_tags' => 'Etiquetes del capítol',
'book_tags' => 'Etiquetes del llibre',

View File

@@ -207,7 +207,6 @@ return [
'role_all' => 'Tot',
'role_own' => 'Propi',
'role_controlled_by_asset' => 'Controlat pel recurs a què estan pujats',
'role_controlled_by_page_delete' => 'Controlled by page delete permissions',
'role_save' => 'Desa el rol',
'role_users' => 'Usuaris assignats en aquest rol',
'role_users_none' => 'No hi ha cap usuari assignat en aquest rol',
@@ -264,9 +263,6 @@ return [
'users_mfa_desc' => 'Configureu lautenticació multifactorial per a afegir una capa de seguretat extra al vostre compte dusuari.',
'users_mfa_x_methods' => 'Hi ha :count mètode configurat|Hi ha :count mètodes configurats',
'users_mfa_configure' => 'Configura un mètode',
'users_mfa_reset' => 'Reset Multi-Factor Authentication Methods',
'users_mfa_reset_desc' => 'This will reset and clear all configured multi-factor authentication methods for this user. If multi-factor authentication is required by any of their roles, they\'ll be prompted to configure new methods on their next login.',
'users_mfa_reset_confirm' => 'Are you sure you want to reset multi-factor authentication for this user?',
// API Tokens
'user_api_token_create' => 'Crea un testimoni API',
@@ -366,9 +362,7 @@ return [
'ru' => 'Русский',
'sk' => 'Slovensky',
'sl' => 'Slovenščina',
'sr' => 'Српски',
'sv' => 'Svenska',
'th' => 'ภาษาไทย',
'tr' => 'Türkçe',
'uk' => 'Українська',
'uz' => 'Ozbekcha',

View File

@@ -99,8 +99,6 @@ return [
'user_update_notification' => 'Uživatel byl úspěšně aktualizován',
'user_delete' => 'odstranil uživatele',
'user_delete_notification' => 'Uživatel byl úspěšně odstraněn',
'user_mfa_reset' => 'obnovit vícefaktorové ověření pro uživatele',
'user_mfa_reset_notification' => 'Obnovení metod vícefaktorového ověřování',
// API Tokens
'api_token_create' => 'API token byl vytvořen',

View File

@@ -8,7 +8,6 @@ return [
'failed' => 'Neplatné přihlašovací údaje.',
'throttle' => 'Příliš mnoho pokusů o přihlášení. Zkuste to prosím znovu za :seconds sekund.',
'mfa_throttle' => '{0}Příliš mnoho pokusů o vícefázové ověření. Zkuste to prosím znovu za :seconds sekund.|{1}Příliš mnoho pokusů o vícefázové ověření. Zkuste to prosím znovu za :seconds sekundu.|[2,4]Příliš mnoho pokusů o vícefázové ověření. Zkuste to prosím znovu za :seconds sekundy.|[5,*]Příliš mnoho pokusů o vícefázové ověření. Zkuste to prosím znovu za :seconds sekund.',
// Login & Register
'sign_up' => 'Registrace',

View File

@@ -173,7 +173,6 @@ return [
'books_sort_desc' => 'Pro přeuspořádání obsahu přesuňte kapitoly a stránky v knize. Mohou být přidány další knihy, které umožní snadný přesun kapitol a stránek mezi knihami. Volitelně lze nastavit pravidlo automatického řazení, aby se při změnách automaticky seřadil obsah této knihy.',
'books_sort_auto_sort' => 'Možnost automatického řazení',
'books_sort_auto_sort_active' => 'Aktivní automatické řazení: :sortName',
'books_sort_auto_sort_creation_hint' => 'Pravidla řazení mohou být vytvořena v nastavení "Seznamy a řazení" uživatelem s příslušnými oprávněními.',
'books_sort_named' => 'Seřadit knihu :bookName',
'books_sort_name' => 'Seřadit podle názvu',
'books_sort_created' => 'Seřadit podle data vytvoření',
@@ -331,9 +330,6 @@ return [
// Editor Sidebar
'toggle_sidebar' => 'Skrýt/Zobrazit postranní panel',
'page_contents' => 'Obsah stránky',
'page_contents_none' => 'Na stránce nejsou žádné nadpisy.',
'page_contents_info' => 'Obsah se generuje ze všech použitých nadpisů na stránce.',
'page_tags' => 'Štítky stránky',
'chapter_tags' => 'Štítky kapitoly',
'book_tags' => 'Štítky knihy',

View File

@@ -207,7 +207,6 @@ return [
'role_all' => 'Vše',
'role_own' => 'Vlastní',
'role_controlled_by_asset' => 'Řídí se obsahem, do kterého jsou nahrávány',
'role_controlled_by_page_delete' => 'Řídí se právem k odstranění stránky',
'role_save' => 'Uložit roli',
'role_users' => 'Uživatelé mající tuto roli',
'role_users_none' => 'Žádný uživatel nemá tuto roli',
@@ -260,13 +259,10 @@ return [
'users_api_tokens_create' => 'Vytvořit Token',
'users_api_tokens_expires' => 'Vyprší',
'users_api_tokens_docs' => 'Dokumentace API',
'users_mfa' => 'Vícefaktorové ověření',
'users_mfa' => 'Vícefázové ověření',
'users_mfa_desc' => 'Nastavit vícefaktorové ověřování jako další vrstvu zabezpečení vašeho uživatelského účtu.',
'users_mfa_x_methods' => ':count nastavená metoda|:count nastavených metod',
'users_mfa_configure' => 'Konfigurovat metody',
'users_mfa_reset' => 'Obnova metod vícefaktorového ověření',
'users_mfa_reset_desc' => 'Tímto se obnoví a vymažou všechny nakonfigurované metody vícefaktorového ověřování pro tohoto uživatele. Pokud některá z jeho rolí vyžaduje vícefaktorové ověřování, bude při příštím přihlášení vyzván k nastavení nových metod.',
'users_mfa_reset_confirm' => 'Opravdu chcete obnovit vícefaktorové ověřování pro tohoto uživatele?',
// API Tokens
'user_api_token_create' => 'Vytvořit API Token',
@@ -366,9 +362,7 @@ return [
'ru' => 'Русский',
'sk' => 'Slovensky',
'sl' => 'Slovenščina',
'sr' => 'Српски',
'sv' => 'Svenska',
'th' => 'ภาษาไทย',
'tr' => 'Türkçe',
'uk' => 'Українська',
'uz' => 'Ozbekcha',

View File

@@ -99,8 +99,6 @@ return [
'user_update_notification' => 'Diweddarwyd y defnyddiwr yn llwyddiannus',
'user_delete' => 'dileodd ddefnyddiwr',
'user_delete_notification' => 'Tynnwyd y defnyddiwr yn llwyddiannus',
'user_mfa_reset' => 'reset MFA for user',
'user_mfa_reset_notification' => 'Multi-factor authentication methods reset',
// API Tokens
'api_token_create' => 'creodd docyn API',

View File

@@ -8,7 +8,6 @@ return [
'failed' => 'Nid yw\'r manylion hyn yn cyfateb i\'n cofnodion.',
'throttle' => 'Gormod o ymdrechion mewngofnodi. Rhowch gynnig arall arni o gwmpas :seconds eiliadau.',
'mfa_throttle' => 'Too many multi-factor verification attempts. Please try again in :seconds seconds.',
// Login & Register
'sign_up' => 'Cofrestru',

View File

@@ -173,7 +173,6 @@ return [
'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books. Optionally an auto sort rule can be set to automatically sort this book\'s contents upon changes.',
'books_sort_auto_sort' => 'Auto Sort Option',
'books_sort_auto_sort_active' => 'Auto Sort Active: :sortName',
'books_sort_auto_sort_creation_hint' => 'Auto sort option rules can be created in the "Lists & Sorting" settings area by a user with the relevant permissions.',
'books_sort_named' => 'Trefnu Llyfr :bookName',
'books_sort_name' => 'Trefnu yn ôl Enw',
'books_sort_created' => 'Trefnu yn ôl Dyddiad Creu',
@@ -331,9 +330,6 @@ return [
// Editor Sidebar
'toggle_sidebar' => 'Toglo Bar ochr',
'page_contents' => 'Page Contents',
'page_contents_none' => 'No headings were found in the page content.',
'page_contents_info' => 'The contents menu is generated from any heading formats used in the page.',
'page_tags' => 'Tagiau Tudalennau',
'chapter_tags' => 'Tagiau Penodau',
'book_tags' => 'Tagiau Llyfrau',

Some files were not shown because too many files have changed in this diff Show More