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
51 changed files with 263 additions and 337 deletions

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,33 +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:
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,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

@@ -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

@@ -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

@@ -1,7 +1,6 @@
name: analyse-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
extensions: gd, mbstring, json, curl, xml, mysql, ldap
- name: Get Composer Cache Directory
@@ -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

View File

@@ -1,7 +1,6 @@
name: test-migrations
on:
workflow_dispatch:
push:
paths:
- '**.php'
@@ -14,25 +13,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
strategy:
matrix:
php: ['8.2', '8.3', '8.4', '8.5']
services:
mysql:
image: docker.io/library/mariadb:12.2.2-noble
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, mysql, ldap
@@ -43,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,25 +13,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
strategy:
matrix:
php: ['8.2', '8.3', '8.4', '8.5']
services:
mysql:
image: docker.io/library/mariadb:12.2.2-noble
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, mysql, ldap, gmp
@@ -43,25 +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: Start Database
run: |
sudo systemctl start mysql
- 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
env:
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.GH_TOKEN }}"}}'
- name: Migrate and seed the database
env:
TEST_DATABASE_URL: 'mysql://bookstack-test:bookstack-test@mysql/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@mysql/bookstack-test'
run: php${{ matrix.php }} ./vendor/bin/phpunit

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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -1,15 +1,17 @@
# BookStack
[![Codeberg release](https://img.shields.io/gitea/v/release/bookstack/bookstack.svg?gitea_url=https://codeberg.org)](https://codeberg.org/bookstack/bookstack/releases/latest)
[![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://codeberg.org/bookstack/bookstack/src/branch/development/LICENSE)
[![GitHub release](https://img.shields.io/github/release/BookStackApp/BookStack.svg)](https://github.com/BookStackApp/BookStack/releases/latest)
[![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/BookStackApp/BookStack/blob/development/LICENSE)
[![Crowdin](https://badges.crowdin.net/bookstack/localized.svg)](https://crowdin.com/project/bookstack)
[![Build Status](https://codeberg.org/bookstack/bookstack/badges/workflows/test-php.yml/badge.svg)](https://codeberg.org/bookstack/bookstack/actions?workflow=test-php.yml)
[![Lint Status](https://codeberg.org/bookstack/bookstack/badges/workflows/lint-php.yml/badge.svg)](https://codeberg.org/bookstack/bookstack/actions?workflow=lint-php.yml)
[![Build Status](https://github.com/BookStackApp/BookStack/workflows/test-php/badge.svg)](https://github.com/BookStackApp/BookStack/actions)
[![Lint Status](https://github.com/BookStackApp/BookStack/workflows/lint-php/badge.svg)](https://github.com/BookStackApp/BookStack/actions)
[![php-metrics](https://img.shields.io/static/v1?label=Metrics&message=php&color=4F5B93)](https://source.bookstackapp.com/php-stats/index.html)
<br>
[![Alternate Source](https://img.shields.io/static/v1?label=Alt+Source&message=Git&color=ef391a&logo=git)](https://source.bookstackapp.com/)
[![Repo Stats](https://img.shields.io/static/v1?label=GitHub+project&message=stats&color=f27e3f)](https://gh-stats.bookstackapp.com/)
[![Community Discussions](https://img.shields.io/static/v1?label=Community&message=Discussions&color=4d36c4&logo=zulip)](https://community.bookstackapp.com/)
[![Mastodon](https://img.shields.io/static/v1?label=Mastodon&message=@bookstack&color=595aff&logo=mastodon)](https://www.bookstackapp.com/links/mastodon)
[![Discord](https://img.shields.io/static/v1?label=Discord&message=chat&color=738adb&logo=discord)](https://www.bookstackapp.com/links/discord)
<br>
[![PeerTube](https://img.shields.io/static/v1?label=PeerTube&message=bookstack@foss.video&color=f2690d&logo=peertube)](https://foss.video/c/bookstack)
[![YouTube](https://img.shields.io/static/v1?label=YouTube&message=bookstackapp&color=ff0000&logo=youtube)](https://www.youtube.com/bookstackapp)
@@ -21,7 +23,7 @@ A platform for storing and organising information and documentation. Details for
* [Demo Instance](https://demo.bookstackapp.com)
* [Screenshots](https://www.bookstackapp.com/#screenshots)
* [BookStack Blog](https://www.bookstackapp.com/blog)
* [Issue List](https://codeberg.org/bookstack/bookstack/issues)
* [Issue List](https://github.com/BookStackApp/BookStack/issues)
* [Community Discussions](https://community.bookstackapp.com/)
* [Support Options](https://www.bookstackapp.com/support/)
@@ -70,7 +72,7 @@ Big thanks to these companies for supporting the project.
<td align="center"><a href="https://www.stellarhosted.com/bookstack/" target="_blank">
<img width="240" src="https://www.bookstackapp.com/images/sponsors/stellarhosted.png" alt="Stellar Hosted">
</a></td>
<td align="center" style="text-align: center"><a href="https://nws.netways.de" target="_blank">
<td align="center" style="text-align: center"><a href="https://nws.netways.de/apps/bookstack/" target="_blank">
<img width="240" src="https://www.bookstackapp.com/images/sponsors/netways.png" alt="NETWAYS Web Services">
</a></td>
</tr>
@@ -111,13 +113,13 @@ Translations for text within BookStack are managed through the [BookStack projec
Please use [Crowdin](https://crowdin.com/project/bookstack) to contribute translations instead of opening a pull request. The translations within the working codebase can be out-of-date, and merging via code can cause conflicts & sync issues. If for some reason you can't use Crowdin feel free to open an issue to discuss alternative options.
If you'd like a new language to be added to Crowdin, for you to be able to provide translations for, please [open a new issue here](https://codeberg.org/bookstack/bookstack/issues/new?template=.forgejo%2fISSUE_TEMPLATE%2flanguage_request.yml).
If you'd like a new language to be added to Crowdin, for you to be able to provide translations for, please [open a new issue here](https://github.com/BookStackApp/BookStack/issues/new?template=language_request.yml).
Please note, translations in BookStack are provided to the "Crowdin Global Translation Memory" which helps BookStack and other projects with finding translations. If you are not happy with contributing to this then providing translations to BookStack, even manually via code, is not advised.
Please note, translations in BookStack are provided to the "Crowdin Global Translation Memory" which helps BookStack and other projects with finding translations. If you are not happy with contributing to this then providing translations to BookStack, even manually via GitHub, is not advised.
## 🎁 Contributing, Issues & Pull Requests
Feel free to [create issues](https://codeberg.org/bookstack/bookstack/issues/new/choose) to request new features or to report bugs & problems. Just please follow the template given when creating the issue.
Feel free to [create issues](https://github.com/BookStackApp/BookStack/issues/new/choose) to request new features or to report bugs & problems. Just please follow the template given when creating the issue.
Pull requests are welcome but, unless it's a small tweak, it may be best to open the pull request early or create an issue for your intended change to discuss how it will fit into the project and plan out the merge. Just because a feature request exists, or is tagged, does not mean that feature would be accepted into the core project.
@@ -132,7 +134,7 @@ Security information for administering a BookStack instance can be found on the
If you'd like to be notified of new potential security concerns you can [sign-up to the BookStack security mailing list](https://updates.bookstackapp.com/signup/bookstack-security-updates).
If you would like to report a security concern, details of doing so [can be found here](.forgejo/SECURITY.md).
If you would like to report a security concern, details of doing so [can be found here](https://github.com/BookStackApp/BookStack/blob/development/.github/SECURITY.md).
## ♿ Accessibility
@@ -140,18 +142,18 @@ We want BookStack to remain accessible to as many people as possible. We aim for
## 🖥️ Website, Docs & Blog
The website which contains the project docs & blog can be found in the [bookstack/website](https://codeberg.org/bookstack/website) repo.
The website which contains the project docs & blog can be found in the [BookStackApp/website](https://codeberg.org/bookstack/website) repo.
## ⚖️ License
The BookStack source is provided under the [MIT License](https://codeberg.org/bookstack/bookstack/src/branch/development/LICENSE).
The BookStack source is provided under the [MIT License](https://github.com/BookStackApp/BookStack/blob/development/LICENSE).
The libraries used by, and included with, BookStack are provided under their own licenses and copyright.
The licenses for many of our core dependencies can be found in the attribution list below, but this is not an exhaustive list of all projects used within BookStack.
## 👪 Attribution
The great people that have worked to build and improve BookStack can [be seen here](https://codeberg.org/bookstack/bookstack/activity/contributors). The wonderful people that have provided translations, either through GitHub, Codeberg or via Crowdin [can be seen here](https://codeberg.org/bookstack/bookstack/src/branch/development/.github/translators.txt).
The great people that have worked to build and improve BookStack can [be seen here](https://github.com/BookStackApp/BookStack/graphs/contributors). The wonderful people that have provided translations, either through GitHub or via Crowdin [can be seen here](https://github.com/BookStackApp/BookStack/blob/development/.github/translators.txt).
Below are the great open-source projects used to help build BookStack.
Note: This is not an exhaustive list of all libraries and projects that would be used in an active BookStack instance.

View File

@@ -14,11 +14,11 @@
HTTP POST calls upon events occurring in BookStack.
</li>
<li>
<a href="https://codeberg.org/bookstack/bookstack/src/branch/development/dev/docs/visual-theme-system.md" target="_blank" rel="noopener noreferrer">Visual Theme System</a> -
<a href="https://github.com/BookStackApp/BookStack/blob/development/dev/docs/visual-theme-system.md" target="_blank" rel="noopener noreferrer">Visual Theme System</a> -
Methods to override views, translations and icons within BookStack.
</li>
<li>
<a href="https://codeberg.org/bookstack/bookstack/src/branch/development/dev/docs/logical-theme-system.md" target="_blank" rel="noopener noreferrer">Logical Theme System</a> -
<a href="https://github.com/BookStackApp/BookStack/blob/development/dev/docs/logical-theme-system.md" target="_blank" rel="noopener noreferrer">Logical Theme System</a> -
Methods to extend back-end functionality within BookStack.
</li>
</ul>

View File

@@ -113,13 +113,13 @@
<a href="https://www.bookstackapp.com/docs/admin/debugging/" target="_blank">Review BookStack debugging documentation &raquo;</a>
</li>
<li>
<a href="https://codeberg.org/bookstack/bookstack/releases" target="_blank">Ensure your instance is up-to-date &raquo;</a>
<a href="https://github.com/BookStackApp/BookStack/releases" target="_blank">Ensure your instance is up-to-date &raquo;</a>
</li>
<li>
<a href="https://codeberg.org/bookstack/bookstack/issues?q={{ urlencode($error) }}" target="_blank">Search for the issue on GitHub &raquo;</a>
<a href="https://github.com/BookStackApp/BookStack/issues?q=is%3Aissue+{{ urlencode($error) }}" target="_blank">Search for the issue on GitHub &raquo;</a>
</li>
<li>
<a href="https://community.bookstackapp.com" target="_blank">Ask for help in our community forums &raquo;</a>
<a href="https://discord.gg/ztkBqR2" target="_blank">Ask for help via Discord &raquo;</a>
</li>
<li>
<a href="https://duckduckgo.com/?q={{urlencode("BookStack {$error}")}}" target="_blank">Search the error message &raquo;</a>

View File

@@ -18,7 +18,7 @@
<h5 class="mt-xl">{{ trans('settings.system_version') }}</h5>
<div class="py-xs">
<a target="_blank" rel="noopener noreferrer" href="https://codeberg.org/bookstack/bookstack/releases">
<a target="_blank" rel="noopener noreferrer" href="https://github.com/BookStackApp/BookStack/releases">
BookStack @if(!str_starts_with($version, 'v')) version @endif {{ $version }}
</a>
<br>

View File

@@ -1,6 +1,2 @@
# Font cache files have once been stored directly in this folder
# therefore its important the contents non-ignored by git
# are chosen selectively
*
!.gitignore
!dompdf/
!.gitignore

View File

@@ -1,3 +0,0 @@
*
!.gitignore
!cache/

View File

@@ -1,2 +0,0 @@
*
!.gitignore

View File

@@ -27,7 +27,7 @@ class DebugViewTest extends TestCase
$resp->assertSeeText('BookStack Version: ' . trim(file_get_contents(base_path('version'))));
// Dynamic help links
$this->withHtml($resp)->assertElementExists('a[href*="q=' . urlencode('BookStack An error occurred during testing') . '"]');
$this->withHtml($resp)->assertElementExists('a[href*="?q=' . urlencode('An error occurred during testing') . '"]');
$this->withHtml($resp)->assertElementExists('a[href*="?q=is%3Aissue+' . urlencode('An error occurred during testing') . '"]');
}
public function test_debug_view_only_shows_when_debug_mode_is_enabled()

View File

@@ -370,7 +370,7 @@ class PageContentTest extends TestCase
public function test_base64_images_within_html_blanked_if_not_supported_extension_for_extract()
{
// Relevant to https://codeberg.org/bookstack/bookstack/issues/3010 and other cases
// Relevant to https://github.com/BookStackApp/BookStack/issues/3010 and other cases
$extensions = [
'jiff', 'pngr', 'png ', ' png', '.png', 'png.', 'p.ng', ',png',
'data:image/png', ',data:image/png',

View File

@@ -79,39 +79,6 @@ class PdfExportTest extends TestCase
$this->assertStringContainsString('<details open="open"', $pdfHtml);
}
public function test_custom_fonts_loaded_for_dom_pdf_when_used()
{
// Set up custom font usage
$page = $this->entities->page()->forceFill([
'html' => '<p><strong>Bold</strong>text</p>',
]);
$page->save();
$this->setSettings([
'app-custom-head' => '<style>* { font-family: "meow words"}</style>'
]);
$normalFont = $this->files->testFilePath('fonts/Cardiff.ttf');
$normalFontTarget = storage_path('fonts/dompdf/MeowWords.ttf');
$boldFont = $this->files->testFilePath('fonts/Cardiff-Bold.ttf');
$boldFontTarget = storage_path('fonts/dompdf/MeowWords-Bold.ttf');
copy($normalFont, $normalFontTarget);
copy($boldFont, $boldFontTarget);
$resp = $this->asEditor()->get($page->getUrl('/export/pdf'));
$resp->assertStatus(200);
// Existance of UFM files indicates the metrics have been generated
$this->assertFileExists(storage_path('fonts/dompdf/MeowWords.ufm'));
$this->assertFileExists(storage_path('fonts/dompdf/MeowWords-Bold.ufm'));
// Existence of cache json files indicates the fonts have been used
$this->assertFileExists(storage_path('fonts/dompdf/cache/MeowWords.ufm.json'));
$this->assertFileExists(storage_path('fonts/dompdf/cache/MeowWords-Bold.ufm.json'));
$filesToCleanUp = [...glob(storage_path('fonts/dompdf/Meow*')), ...glob(storage_path('fonts/dompdf/cache/Meow*'))];
foreach ($filesToCleanUp as $file) {
unlink($file);
}
}
public function test_wkhtmltopdf_only_used_when_allow_untrusted_is_true()
{
$page = $this->entities->page();

View File

@@ -2,6 +2,7 @@
namespace Tests\Helpers;
use BookStack\Access\Oidc\OidcProviderSettings;
use phpseclib3\Crypt\RSA;
/**
@@ -119,6 +120,41 @@ q/1PY4iJviGKddtmfClH3v4=
-----END PRIVATE KEY-----';
}
public static function defaultSecret(): string
{
return 'test-client-secret-for-hs256';
}
/**
* Build a minimal OidcProviderSettings for use in token validation tests.
*/
public static function defaultProviderSettings(array $overrides = []): OidcProviderSettings
{
return new OidcProviderSettings(array_merge([
'issuer' => static::defaultIssuer(),
'clientId' => static::defaultClientId(),
'clientSecret' => static::defaultSecret(),
], $overrides));
}
/**
* Build an HS256-signed ID token using the given secret.
*/
public static function hs256IdToken(string $secret, array $payloadOverrides = []): string
{
$payload = array_merge(static::defaultPayload(), $payloadOverrides);
$header = ['alg' => 'HS256', 'typ' => 'JWT'];
$top = implode('.', [
static::base64UrlEncode(json_encode($header)),
static::base64UrlEncode(json_encode($payload)),
]);
$signature = hash_hmac('sha256', $top, $secret, true);
return $top . '.' . static::base64UrlEncode($signature);
}
public static function publicJwkKeyArray(): array
{
return [

View File

@@ -15,7 +15,7 @@ class OidcIdTokenTest extends TestCase
OidcJwtHelper::publicJwkKeyArray(),
]);
$this->assertTrue($token->validate('xxyyzz.aaa.bbccdd.123'));
$this->assertTrue($token->validate(OidcJwtHelper::defaultProviderSettings()));
}
public function test_get_claim_returns_value_if_existing()
@@ -56,7 +56,7 @@ class OidcIdTokenTest extends TestCase
$err = null;
try {
$token->validate('abc');
$token->validate(OidcJwtHelper::defaultProviderSettings());
} catch (\Exception $exception) {
$err = $exception;
}
@@ -71,7 +71,7 @@ class OidcIdTokenTest extends TestCase
$token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), []);
$this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Token signature could not be validated using the provided keys');
$token->validate('abc');
$token->validate(OidcJwtHelper::defaultProviderSettings());
}
public function test_error_thrown_if_token_signature_not_validated_from_non_matching_key()
@@ -83,7 +83,7 @@ class OidcIdTokenTest extends TestCase
]);
$this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Token signature could not be validated using the provided keys');
$token->validate('abc');
$token->validate(OidcJwtHelper::defaultProviderSettings());
}
public function test_error_thrown_if_invalid_key_provided()
@@ -91,15 +91,34 @@ class OidcIdTokenTest extends TestCase
$token = new OidcIdToken(OidcJwtHelper::idToken(), OidcJwtHelper::defaultIssuer(), ['url://example.com']);
$this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Unexpected type of key value provided');
$token->validate('abc');
$token->validate(OidcJwtHelper::defaultProviderSettings());
}
public function test_error_thrown_if_token_algorithm_is_not_rs256()
public function test_error_thrown_if_token_algorithm_is_not_supported()
{
$token = new OidcIdToken(OidcJwtHelper::idToken([], ['alg' => 'HS256']), OidcJwtHelper::defaultIssuer(), []);
$token = new OidcIdToken(OidcJwtHelper::idToken([], ['alg' => 'ES256']), OidcJwtHelper::defaultIssuer(), []);
$this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Only RS256 signature validation is supported. Token reports using HS256');
$token->validate('abc');
$this->expectExceptionMessage('Only HS256, RS256 signatures validation are supported. Token reports using ES256');
$token->validate(OidcJwtHelper::defaultProviderSettings());
}
public function test_hs256_token_passes_validation_with_correct_secret()
{
$secret = OidcJwtHelper::defaultSecret();
$token = new OidcIdToken(OidcJwtHelper::hs256IdToken($secret), OidcJwtHelper::defaultIssuer(), []);
$settings = OidcJwtHelper::defaultProviderSettings(['clientSecret' => $secret]);
$this->assertTrue($token->validate($settings));
}
public function test_hs256_token_fails_validation_with_wrong_secret()
{
$token = new OidcIdToken(OidcJwtHelper::hs256IdToken('correct-secret'), OidcJwtHelper::defaultIssuer(), []);
$settings = OidcJwtHelper::defaultProviderSettings(['clientSecret' => 'wrong-secret']);
$this->expectException(OidcInvalidTokenException::class);
$this->expectExceptionMessage('Token signature could not be validated using the provided secret');
$token->validate($settings);
}
public function test_token_claim_error_cases()
@@ -141,7 +160,7 @@ class OidcIdTokenTest extends TestCase
$err = null;
try {
$token->validate('xxyyzz.aaa.bbccdd.123');
$token->validate(OidcJwtHelper::defaultProviderSettings());
} catch (\Exception $exception) {
$err = $exception;
}
@@ -160,7 +179,7 @@ class OidcIdTokenTest extends TestCase
$testFilePath,
]);
$this->assertTrue($token->validate('xxyyzz.aaa.bbccdd.123'));
$this->assertTrue($token->validate(OidcJwtHelper::defaultProviderSettings()));
unlink($testFilePath);
}
}

View File

@@ -75,7 +75,7 @@ class ImageTest extends TestCase
public function test_image_display_thumbnail_generation_for_animated_avif_images_uses_original_file()
{
if ((gd_info()['AVIF Support'] ?? false) !== true) {
if (! function_exists('imageavif')) {
$this->markTestSkipped('imageavif() is not available');
}

Binary file not shown.

View File

@@ -1,2 +0,0 @@
Font files by Roger White, in public domain.
https://web.archive.org/web/20110609213636/http://www.rogersfonts.org.uk/

View File

@@ -1 +1 @@
v26.05-dev
v26.01-dev